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