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