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