]> git.sesse.net Git - vlc/blob - modules/video_filter/osd_text.c
modules/video_filter/Modules.am: forgot to add the osdtest module
[vlc] / modules / video_filter / osd_text.c
1 /*****************************************************************************
2  * osd_text.c : Filter to put text on the video, using freetype2
3  *****************************************************************************
4  * Copyright (C) 2002, 2003 VideoLAN
5  * $Id: osd_text.c,v 1.2 2003/04/08 07:22:10 sigmunau Exp $
6  *
7  * Authors: Sigmund Augdal <sigmunau@idi.ntnu.no>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>                                      /* malloc(), free() */
28 #include <string.h>
29
30 #include <vlc/vlc.h>
31 #include <vlc/vout.h>
32 #include <osd.h>
33 #include <math.h>
34
35 #include "filter_common.h"
36
37 #include <ft2build.h>
38 #include FT_FREETYPE_H
39 #include FT_GLYPH_H
40
41 /*****************************************************************************
42  * Local prototypes
43  *****************************************************************************/
44 static int  Create    ( vlc_object_t * );
45 static void Destroy   ( vlc_object_t * );
46
47 static int  Init      ( vout_thread_t * );
48 static void End       ( vout_thread_t * );
49 static void Render    ( vout_thread_t *, picture_t * );
50
51 static int  SendEvents( vlc_object_t *, char const *,
52                         vlc_value_t, vlc_value_t, void * );
53 static int  SetMargin ( vlc_object_t *, char const *,
54                         vlc_value_t, vlc_value_t, void * );
55 static int  AddText   ( vlc_object_t *, char const *,
56                         vlc_value_t, vlc_value_t, void * );
57
58
59 /*****************************************************************************
60  * Module descriptor
61  *****************************************************************************/
62 #define FONT_TEXT N_("Font")
63 #define FONT_LONGTEXT N_("Filename of Font")
64 #define FONTSIZE_TEXT N_("Font size")
65 #define FONTSIZE_LONGTEXT N_("The size of the fonts used by the osd module" )
66
67 vlc_module_begin();
68     add_category_hint( N_("OSD"), NULL, VLC_FALSE );
69     add_file( "osd-font", "", NULL, FONT_TEXT, FONT_LONGTEXT, VLC_FALSE );
70     add_integer( "osd-fontsize", 16, NULL, FONTSIZE_TEXT, FONTSIZE_LONGTEXT, VLC_FALSE );
71     set_description( _("osd text filter") );
72     set_capability( "video filter", 0 );
73     add_shortcut( "text" );
74     set_callbacks( Create, Destroy );
75 vlc_module_end();
76
77 /**
78  Describes a string to be displayed on the video, or a linked list of
79  such
80 */
81 typedef struct string_info_s string_info_t;
82 struct string_info_s
83 {
84     string_info_t *p_next;
85     int            i_x_margin;
86     int            i_y_margin;
87     int            i_width;
88     int            i_height;
89     int            i_flags;
90     mtime_t        i_start_date;
91     mtime_t        i_end_date;
92     char          *psz_text;
93     FT_Glyph      *pp_glyphs;
94     FT_Vector     *p_glyph_pos;
95 };
96
97 /*****************************************************************************
98  * vout_sys_t: osd_text local data
99  *****************************************************************************
100  * This structure is part of the video output thread descriptor.
101  * It describes the osd-text specific properties of an output thread.
102  *****************************************************************************/
103 struct vout_sys_t
104 {
105     int            i_clones;
106     vout_thread_t *p_vout;
107     FT_Library     p_library;   /* handle to library     */
108     FT_Face        p_face;      /* handle to face object */
109     string_info_t *p_strings;
110     int            i_x_margin;
111     int            i_y_margin;
112     int            i_flags;
113     int            i_duration;
114     mtime_t        i_start_date;
115     mtime_t        i_end_date;
116     vlc_mutex_t   *lock;
117     vlc_bool_t     i_use_kerning;
118     uint8_t        pi_gamma[256];
119 };
120 /* more prototypes */
121 static void ComputeBoundingBox( string_info_t * );
122 static void FreeString( string_info_t * );
123
124 /*****************************************************************************
125  * Create: allocates osd-text video thread output method
126  *****************************************************************************
127  * This function allocates and initializes a Clone vout method.
128  *****************************************************************************/
129 #define gamma_value 2.0
130 static int Create( vlc_object_t *p_this )
131 {
132     vout_thread_t *p_vout = (vout_thread_t *)p_this;
133     char *psz_fontfile;
134     int i, i_error;
135     vlc_value_t val;
136     double gamma_inv = 1.0f / gamma_value;
137     
138     /* Allocate structure */
139     p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
140     if( p_vout->p_sys == NULL )
141     {
142         msg_Err( p_vout, "out of memory" );
143         return VLC_ENOMEM;
144     }
145     p_vout->p_sys->p_strings = NULL;
146     p_vout->p_sys->i_x_margin = 50;
147     p_vout->p_sys->i_y_margin = 50;
148     p_vout->p_sys->i_flags = 0;
149     
150     p_vout->p_sys->i_duration = 2000000;
151     p_vout->p_sys->i_start_date = 0;
152     p_vout->p_sys->i_end_date = 0;
153     p_vout->p_sys->lock = malloc( sizeof(vlc_mutex_t));
154     if( p_vout->p_sys->lock == NULL )
155     {
156         msg_Err( p_vout, "out of memory" );
157         return VLC_ENOMEM;
158     }
159     vlc_mutex_init( p_vout, p_vout->p_sys->lock);
160
161     for (i = 0; i < 256; i++) {
162         p_vout->p_sys->pi_gamma[i] =
163             (uint8_t)( pow( (double)i / 255.0f, gamma_inv) * 255.0f );
164         msg_Dbg( p_vout, "%d", p_vout->p_sys->pi_gamma[i]);
165     }
166     p_vout->pf_init = Init;
167     p_vout->pf_end = End;
168     p_vout->pf_manage = NULL;
169     p_vout->pf_render = Render;
170     p_vout->pf_display = NULL;
171
172     /* Look what method was requested */
173     psz_fontfile = config_GetPsz( p_vout, "osd-font" );
174     i_error = FT_Init_FreeType( &p_vout->p_sys->p_library );
175     if( i_error )
176     {
177         msg_Err( p_vout, "couldn't initialize freetype" );
178         free( p_vout->p_sys );
179         return VLC_EGENERIC;
180     }
181     i_error = FT_New_Face( p_vout->p_sys->p_library, psz_fontfile, 0,
182                            &p_vout->p_sys->p_face );
183     if( i_error == FT_Err_Unknown_File_Format )
184     {
185         msg_Err( p_vout, "file %s have unknown format", psz_fontfile );
186         free( p_vout->p_sys );
187         return VLC_EGENERIC;
188     }
189     else if( i_error )
190     {
191         msg_Err( p_vout, "failed to load font file" );
192         free( p_vout->p_sys );
193         return VLC_EGENERIC;
194     }
195     p_vout->p_sys->i_use_kerning = FT_HAS_KERNING(p_vout->p_sys->p_face);
196     
197     i_error = FT_Set_Pixel_Sizes( p_vout->p_sys->p_face, 0, config_GetInt( p_vout, "osd-fontsize" ) );    
198     if( i_error )
199     {
200         msg_Err( p_vout, "couldn't set font size to %d",
201                  config_GetInt( p_vout, "osd-fontsize" ) );
202         free( p_vout->p_sys );
203         return VLC_EGENERIC;
204     }
205     var_Create( p_vout, "lock", VLC_VAR_MUTEX );
206     var_Create( p_vout, "flags", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
207     var_AddCallback( p_vout, "flags", SetMargin, NULL );
208     var_Create( p_vout, "x-margin", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
209     var_AddCallback( p_vout, "x-margin", SetMargin, NULL );
210     var_Create( p_vout, "y-margin", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
211     var_AddCallback( p_vout, "y-margin", SetMargin, NULL );
212     var_Create( p_vout, "duration", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
213     var_AddCallback( p_vout, "duration", SetMargin, NULL );
214     var_Create( p_vout, "start-date", VLC_VAR_TIME | VLC_VAR_ISCOMMAND );
215     var_AddCallback( p_vout, "start-date", SetMargin, NULL );
216     var_Create( p_vout, "end-date", VLC_VAR_TIME | VLC_VAR_ISCOMMAND );
217     var_AddCallback( p_vout, "end-date", SetMargin, NULL );
218     var_Create( p_vout, "string", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
219     var_AddCallback( p_vout, "string", AddText, NULL );
220
221     val.psz_string = "Videolan";
222 //    var_Set( p_vout, "string", val );
223 //    p_vout->p_sys->p_strings->i_end_date = 0xFFFFFFFFFFFFFFF;
224
225     return VLC_SUCCESS;
226 }
227
228 /*****************************************************************************
229  * Init: initialize the video thread output method
230  *****************************************************************************/
231 static int Init( vout_thread_t *p_vout )
232 {
233     int i_index;
234     picture_t *p_pic;
235
236     I_OUTPUTPICTURES = 0;
237
238     /* Initialize the output structure */
239     p_vout->output.i_chroma = p_vout->render.i_chroma;
240     p_vout->output.i_width  = p_vout->render.i_width;
241     p_vout->output.i_height = p_vout->render.i_height;
242     p_vout->output.i_aspect = p_vout->render.i_aspect;
243
244     /* Try to open the real video output */
245     msg_Dbg( p_vout, "spawning the real video output" );
246
247     p_vout->p_sys->p_vout = vout_Create( p_vout,
248                             p_vout->render.i_width, p_vout->render.i_height,
249                             p_vout->render.i_chroma, p_vout->render.i_aspect );
250     if( p_vout->p_sys->p_vout == NULL )
251     {
252         msg_Err( p_vout, "failed to start vout" );
253         return VLC_EGENERIC;
254     }
255
256     ADD_CALLBACKS( p_vout->p_sys->p_vout, SendEvents );
257
258     ALLOCATE_DIRECTBUFFERS( VOUT_MAX_PICTURES );
259
260     return VLC_SUCCESS;
261 }
262
263 /*****************************************************************************
264  * End: terminate Clone video thread output method
265  *****************************************************************************/
266 static void End( vout_thread_t *p_vout )
267 {
268     int i_index;
269
270     /* Free the fake output buffers we allocated */
271     for( i_index = I_OUTPUTPICTURES ; i_index ; )
272     {
273         i_index--;
274         free( PP_OUTPUTPICTURE[ i_index ]->p_data_orig );
275     }
276 }
277
278 /*****************************************************************************
279  * Destroy: destroy Clone video thread output method
280  *****************************************************************************
281  * Terminate an output method created by CloneCreateOutputMethod
282  *****************************************************************************/
283 static void Destroy( vlc_object_t *p_this )
284 {
285     vout_thread_t *p_vout = (vout_thread_t *)p_this;
286     string_info_t *p_string1, *p_string2;
287     DEL_CALLBACKS( p_vout->p_sys->p_vout, SendEvents );
288     vlc_object_detach( p_vout->p_sys->p_vout );
289     vout_Destroy( p_vout->p_sys->p_vout );
290     vlc_mutex_destroy( p_vout->p_sys->lock );
291     p_string1 = p_vout->p_sys->p_strings;
292     while( p_string1 )
293     {
294         p_string2 = p_string1->p_next;
295         FreeString( p_string1 );
296         p_string1 = p_string2;
297     }
298     FT_Done_Face( p_vout->p_sys->p_face );
299     FT_Done_FreeType( p_vout->p_sys->p_library );
300     free( p_vout->p_sys );
301 }
302
303 /*****************************************************************************
304  * SendEvents: forward mouse and keyboard events to the parent p_vout
305  *****************************************************************************/
306 static int SendEvents( vlc_object_t *p_this, char const *psz_var,
307                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
308 {
309     var_Set( (vlc_object_t *)p_data, psz_var, newval );
310
311     return VLC_SUCCESS;
312 }
313
314
315 /*****************************************************************************
316  * Render: displays previously rendered output
317  *****************************************************************************
318  * This function send the currently rendered image to Clone image, waits
319  * until it is displayed and switch the two rendering buffers, preparing next
320  * frame.
321  *****************************************************************************/
322 static void Render( vout_thread_t *p_vout, picture_t *p_pic )
323 {
324     picture_t *p_outpic = NULL;
325     int i_plane, i_error,x,y,pen_x, pen_y;
326     unsigned int i;
327     string_info_t *p_string;
328     mtime_t date;
329     
330     while( ( p_outpic =
331              vout_CreatePicture( p_vout->p_sys->p_vout, 0, 0, 0 )
332                ) == NULL )
333     {
334         if( p_vout->b_die || p_vout->b_error )
335         {
336             vout_DestroyPicture(
337                 p_vout->p_sys->p_vout, p_outpic );
338             return;
339         }
340
341         msleep( VOUT_OUTMEM_SLEEP );
342     }
343     vout_DatePicture( p_vout->p_sys->p_vout,
344                       p_outpic, p_pic->date );
345     if( p_vout->i_changes )
346     {
347         p_vout->p_sys->p_vout->i_changes = p_vout->i_changes;
348         p_vout->i_changes = 0;
349     }
350
351     date = mdate();
352     /* trash old strings */
353     while( p_vout->p_sys->p_strings &&
354            p_vout->p_sys->p_strings->i_end_date < date )
355     {
356         p_string = p_vout->p_sys->p_strings;
357         p_vout->p_sys->p_strings = p_string->p_next;
358         msg_Dbg( p_vout, "trashing string "I64Fd" < "I64Fd, p_string->i_end_date, date );
359         FreeString( p_string );
360     }
361
362
363     vlc_mutex_lock( p_vout->p_sys->lock );
364
365     for( i_plane = 0 ; i_plane < p_pic->i_planes ; i_plane++ )
366     {
367         uint8_t *p_in, *p_in_end, *p_out;
368         int i_in_pitch = p_pic->p[i_plane].i_pitch;
369         const int i_out_pitch = p_outpic->p[i_plane].i_pitch;
370         const int i_copy_pitch = p_outpic->p[i_plane].i_visible_pitch;
371
372         p_in = p_pic->p[i_plane].p_pixels;
373         p_out = p_outpic->p[i_plane].p_pixels;
374
375         if( i_in_pitch == i_copy_pitch
376             && i_out_pitch == i_copy_pitch )
377         {
378             p_vout->p_vlc->pf_memcpy( p_out, p_in, i_in_pitch
379                                       * p_outpic->p[i_plane].i_lines );
380         }
381         else
382         {
383             p_in_end = p_in + i_in_pitch * p_outpic->p[i_plane].i_lines;
384
385             while( p_in < p_in_end )
386             {
387                 p_vout->p_vlc->pf_memcpy( p_out, p_in, i_copy_pitch );
388                 p_in += i_in_pitch;
389                 p_out += i_out_pitch;
390             }
391         }
392 /*        pen_x = 20;
393           pen_y = 100;*/
394         if ( i_plane == 0 )
395         {
396             for( p_string = p_vout->p_sys->p_strings; p_string != NULL;
397                  p_string = p_string->p_next )
398             {
399                 if( p_string->i_start_date > date )
400                 {
401                     continue;
402                 }
403                 if ( p_string->i_flags & OSD_ALIGN_BOTTOM )
404                 {
405                     pen_y = p_outpic->p[i_plane].i_lines - p_string->i_height -
406                         p_string->i_y_margin;
407                 }
408                 else
409                 {
410                     pen_y = p_string->i_y_margin;
411                 }
412                 if ( p_string->i_flags & OSD_ALIGN_RIGHT )
413                 {
414                     pen_x = i_out_pitch - p_string->i_width
415                         - p_string->i_x_margin;
416                 }
417                 else
418                 {
419                     pen_x = p_string->i_x_margin;
420                 }
421
422                 for( i = 0; i < strlen( p_string->psz_text ); i++ )
423                 {
424                     if( p_string->pp_glyphs[i] )
425                     {
426                         FT_Glyph p_glyph = p_string->pp_glyphs[i];
427                         FT_BitmapGlyph p_image;
428                         i_error = FT_Glyph_To_Bitmap( &p_glyph,
429                                                       FT_RENDER_MODE_NORMAL,
430                                                       &p_string->p_glyph_pos[i],
431                                                       0 );
432                         if ( i_error ) continue;
433                         p_image = (FT_BitmapGlyph)p_glyph;
434 #define alpha p_vout->p_sys->pi_gamma[p_image->bitmap.buffer[x+ y*p_image->bitmap.width]]
435 #define pixel p_out[(p_string->p_glyph_pos[i].y + pen_y + y - p_image->top)*i_out_pitch+x+pen_x+p_string->p_glyph_pos[i].x+p_image->left]
436                         for(y = 0; y < p_image->bitmap.rows; y++ )
437                         {
438                             for( x = 0; x < p_image->bitmap.width; x++ )
439                             {
440 //                                pixel = alpha;
441 //                                pixel = (pixel^alpha)^pixel;
442                                 pixel = ((pixel*(255-alpha))>>8) + (255*alpha>>8);
443                             }
444                         }
445                         FT_Done_Glyph( p_glyph );
446                     }
447                 }
448             }
449         }
450     }
451     vlc_mutex_unlock( p_vout->p_sys->lock );
452
453     vout_DisplayPicture( p_vout->p_sys->p_vout, p_outpic );
454 }
455
456 static int  SetMargin ( vlc_object_t *p_this, char const *psz_command,
457                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
458 {
459     vout_thread_t *p_vout = (vout_thread_t*)p_this;
460     if( !strcmp( psz_command, "x-margin" ) )
461     {
462         p_vout->p_sys->i_x_margin = newval.i_int;
463     }
464     else if( !strcmp( psz_command, "y-margin" ) )
465     {
466         p_vout->p_sys->i_y_margin = newval.i_int;
467     }
468     else if( !strcmp( psz_command, "duration" ) )
469     {
470         p_vout->p_sys->i_duration = newval.i_int;
471         msg_Dbg( p_vout, "setting duration %d", p_vout->p_sys->i_duration );
472     }
473     else if( !strcmp( psz_command, "start-date" ) )
474     {
475         p_vout->p_sys->i_start_date = newval.i_int;
476     }
477     else if( !strcmp( psz_command, "end-date" ) )
478     {
479         p_vout->p_sys->i_end_date = newval.i_int;
480     }
481     else if( !strcmp( psz_command, "flags" ) )
482     {
483         p_vout->p_sys->i_flags = newval.i_int;
484     }
485     else
486     {
487         msg_Err( p_vout, "Invalid command" );
488         return VLC_EGENERIC;
489     }
490     return VLC_SUCCESS;
491 }
492
493 static int  AddText ( vlc_object_t *p_this, char const *psz_command,
494                       vlc_value_t oldval, vlc_value_t newval, void *p_data )
495 {
496     vout_thread_t *p_vout = (vout_thread_t*)p_this;
497     string_info_t **pp_string;
498     string_info_t *p_string;
499     char *psz_string;
500     int i, i_pen_x, i_error, i_glyph_index, i_previous;
501     
502     p_string = malloc( sizeof(string_info_t) );
503     p_string->i_flags = p_vout->p_sys->i_flags;
504     p_string->i_x_margin = p_vout->p_sys->i_x_margin;
505     p_string->i_y_margin = p_vout->p_sys->i_y_margin;
506     if( p_vout->p_sys->i_start_date && p_vout->p_sys->i_end_date )
507     {
508         p_string->i_end_date = p_vout->p_sys->i_end_date;
509         p_string->i_start_date = p_vout->p_sys->i_start_date;
510     }
511     else
512     {
513         p_string->i_end_date = mdate() + p_vout->p_sys->i_duration;
514         p_string->i_start_date = 0;
515     }
516     p_vout->p_sys->i_end_date = 0;
517     p_vout->p_sys->i_start_date = 0;
518     p_string->psz_text = strdup( newval.psz_string );
519     p_string->pp_glyphs = malloc( sizeof(FT_GlyphSlot)
520                                   * strlen( p_string->psz_text ) );
521     if( p_string->pp_glyphs == NULL )
522     {
523         msg_Err( p_this, "Out of memory" );
524         return VLC_ENOMEM;
525     }
526     p_string->p_glyph_pos = malloc( sizeof( FT_Vector )
527                                   * strlen( p_string->psz_text ) );
528     if( p_string->p_glyph_pos == NULL )
529     {
530         msg_Err( p_this, "Out of memory" );
531         return VLC_ENOMEM;
532     }
533
534     /* Calculate relative glyph positions and a bounding box for the
535      * entire string */
536     i_pen_x = 0;
537     i_previous = 0;
538     psz_string = p_string->psz_text;
539     for( i = 0; *psz_string; i++, psz_string++ )
540     {
541 #define face p_vout->p_sys->p_face
542 #define glyph face->glyph
543         if ( *psz_string == '\n' )
544         {
545             i_pen_x = 0;
546             p_string->pp_glyphs[ i ] = NULL;
547             continue;
548         }
549         i_glyph_index = FT_Get_Char_Index( face, *psz_string);
550         if ( p_vout->p_sys->i_use_kerning && i_glyph_index
551             && i_previous )
552         {
553             FT_Vector delta;
554             FT_Get_Kerning( face, i_previous, i_glyph_index,
555                             ft_kerning_default, &delta );
556             i_pen_x += delta.x >> 6;
557             
558         }
559         p_string->p_glyph_pos[ i ].x = i_pen_x;
560         p_string->p_glyph_pos[ i ].y = 0;
561         i_error = FT_Load_Glyph( face, i_glyph_index, FT_LOAD_DEFAULT );
562         if ( i_error )
563         {
564             msg_Err( p_this, "FT_Load_Glyph returned %d", i_error );
565             return VLC_EGENERIC;
566         }
567         i_error = FT_Get_Glyph( glyph, &p_string->pp_glyphs[ i ] );
568         if ( i_error )
569         {
570             msg_Err( p_this, "FT_Get_Glyph returned %d", i_error );
571             return VLC_EGENERIC;
572         }
573         
574         i_previous = i_glyph_index;
575         i_pen_x += glyph->advance.x >> 6;
576     }
577
578     ComputeBoundingBox( p_string );
579     msg_Dbg( p_this, "string height is %d width is %d", p_string->i_height, p_string->i_width );
580     p_string->p_next = NULL;
581     msg_Dbg( p_this, "adding string \"%s\" at (%d,%d) start_date "I64Fd
582              " end_date" I64Fd, p_string->psz_text, p_string->i_x_margin,
583              p_string->i_y_margin, p_string->i_start_date,
584              p_string->i_end_date );
585     vlc_mutex_lock( p_vout->p_sys->lock );
586     pp_string  = &p_vout->p_sys->p_strings;
587     while( *pp_string && (*pp_string)->i_end_date < p_string->i_end_date )
588     {        
589         pp_string = &(*pp_string)->p_next;
590     }
591     p_string->p_next = (*pp_string);
592     *pp_string = p_string;
593     vlc_mutex_unlock( p_vout->p_sys->lock );
594     return VLC_SUCCESS;
595 }
596
597 static void ComputeBoundingBox( string_info_t *p_string )
598 {
599     unsigned int i, i2;
600     int pen_y = 0;
601     FT_Vector result;
602     FT_Vector line;
603     FT_BBox glyph_size;
604     
605     result.x = 0;
606     result.y = 0;
607     line.x = 0;
608     line.y = 0;
609     for ( i = 0; i < strlen( p_string->psz_text ); i++ )
610     {
611         if ( p_string->psz_text[i] == '\n' )
612         {
613             i2 = i - 1;
614             while ( i2 >= 0 && p_string->psz_text[i2] )
615             {
616                 p_string->p_glyph_pos[i2].y += line.y;
617                 i2--;
618             }
619             result.x = __MAX( result.x, line.x );
620             result.y += line.y;
621             line.y = 0;
622             line.x = 0;
623             pen_y = result.y;
624             continue;
625         }
626         p_string->p_glyph_pos[ i ].y = pen_y;
627         FT_Glyph_Get_CBox( p_string->pp_glyphs[i],
628                            ft_glyph_bbox_pixels, &glyph_size );
629         /* Do rest */
630         line.x = p_string->p_glyph_pos[i].x + glyph_size.xMax - glyph_size.xMin;
631         line.y = __MAX( line.y, glyph_size.yMax - glyph_size.yMin );
632     }
633     result.x = __MAX( result.x, line.x );
634     result.y += line.y;
635     i2 = i - 1;
636     while ( i2 >= 0 && p_string->psz_text[i2] )
637     {
638         p_string->p_glyph_pos[i2].y += line.y;
639         i2--;
640     }
641     p_string->i_height = result.y;
642     p_string->i_width = result.x;
643     return;    
644 }
645
646 static void FreeString( string_info_t *p_string )
647 {
648     unsigned int i;
649     for ( i = 0; i < strlen( p_string->psz_text ); i++ )
650     {
651         if ( p_string->pp_glyphs[ i ] )
652         {
653             FT_Done_Glyph( p_string->pp_glyphs[ i ] );
654         }
655     }
656     free( p_string->psz_text );
657     free( p_string->p_glyph_pos );
658     free( p_string->pp_glyphs );
659     free( p_string );
660 }
661