]> git.sesse.net Git - vlc/blob - modules/misc/freetype.c
971b2f6e12afbb18b14c6d2e03dcf6914193349e
[vlc] / modules / misc / freetype.c
1 /*****************************************************************************
2  * freetype.c : Put text on the video, using freetype2
3  *****************************************************************************
4  * Copyright (C) 2002 - 2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8  *          Gildas Bazin <gbazin@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>                                      /* malloc(), free() */
29 #include <string.h>
30
31 #ifdef HAVE_LINUX_LIMITS_H
32 #   include <linux/limits.h>
33 #endif
34
35 #include <vlc/vlc.h>
36 #include <vlc/vout.h>
37 #include "vlc_osd.h"
38 #include "vlc_block.h"
39 #include "vlc_filter.h"
40
41 #include <math.h>
42
43 #ifdef HAVE_ERRNO_H
44 #   include <errno.h>
45 #endif
46
47 #include <ft2build.h>
48 #include FT_FREETYPE_H
49 #include FT_GLYPH_H
50
51 #ifdef __APPLE__
52 #define DEFAULT_FONT "/System/Library/Fonts/LucidaGrande.dfont"
53 #elif defined( SYS_BEOS )
54 #define DEFAULT_FONT "/boot/beos/etc/fonts/ttfonts/Swiss721.ttf"
55 #elif defined( WIN32 )
56 #define DEFAULT_FONT "" /* Default font found at run-time */
57 #else
58 #define DEFAULT_FONT "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
59 #endif
60
61 #if defined(HAVE_FRIBIDI)
62 #include <fribidi/fribidi.h>
63 #endif
64
65 typedef struct line_desc_t line_desc_t;
66
67 /*****************************************************************************
68  * Local prototypes
69  *****************************************************************************/
70 static int  Create ( vlc_object_t * );
71 static void Destroy( vlc_object_t * );
72
73 /* The RenderText call maps to pf_render_string, defined in vlc_filter.h */
74 static int RenderText( filter_t *, subpicture_region_t *,
75                        subpicture_region_t * );
76 static line_desc_t *NewLine( byte_t * );
77
78 static int SetFontSize( filter_t *, int );
79
80 /*****************************************************************************
81  * Module descriptor
82  *****************************************************************************/
83 #define FONT_TEXT N_("Font")
84 #define FONT_LONGTEXT N_("Filename for the font you want to use")
85 #define FONTSIZE_TEXT N_("Font size in pixels")
86 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
87     "that will be rendered on the video. " \
88     "If set to something different than 0 this option will override the " \
89     "relative font size. " )
90 #define OPACITY_TEXT N_("Opacity")
91 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
92     "text that will be rendered on the video. 0 = transparent, " \
93     "255 = totally opaque. " )
94 #define COLOR_TEXT N_("Text default color")
95 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
96     "the video. This must be an hexadecimal (like HTML colors). The first two "\
97     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
98     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
99 #define FONTSIZER_TEXT N_("Relative font size")
100 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
101     "fonts that will be rendered on the video. If absolute font size is set, "\
102     "relative size will be overriden." )
103
104 static int   pi_sizes[] = { 20, 18, 16, 12, 6 };
105 static char *ppsz_sizes_text[] = { N_("Smaller"), N_("Small"), N_("Normal"),
106                                    N_("Large"), N_("Larger") };
107 #define YUVP_TEXT N_("Use YUVP renderer")
108 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
109   "This option is only needed if you want to encode into DVB subtitles" )
110 #define EFFECT_TEXT N_("Font Effect")
111 /// \bug [String] Missing space
112 #define EFFECT_LONGTEXT N_("It is possible to apply effects to the rendered" \
113 "text to improve its readability." )
114
115 #define EFFECT_BACKGROUND  1 
116 #define EFFECT_OUTLINE     2
117 #define EFFECT_OUTLINE_FAT 3
118
119 static int   pi_effects[] = { 1, 2, 3 };
120 static char *ppsz_effects_text[] = { N_("Background"),N_("Outline"),
121                                      N_("Fat Outline") };
122 static int pi_color_values[] = {
123   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
124   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080, 
125   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF }; 
126
127 static char *ppsz_color_descriptions[] = {
128   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
129   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
130   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
131
132 vlc_module_begin();
133     set_shortname( _("Text renderer"));
134     set_description( _("Freetype2 font renderer") );
135     set_category( CAT_VIDEO );
136     set_subcategory( SUBCAT_VIDEO_SUBPIC );
137
138     add_file( "freetype-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
139               VLC_FALSE );
140
141     add_integer( "freetype-fontsize", 0, NULL, FONTSIZE_TEXT,
142                  FONTSIZE_LONGTEXT, VLC_TRUE );
143
144     /* opacity valid on 0..255, with default 255 = fully opaque */
145     add_integer_with_range( "freetype-opacity", 255, 0, 255, NULL,
146         OPACITY_TEXT, OPACITY_LONGTEXT, VLC_TRUE );
147
148     /* hook to the color values list, with default 0x00ffffff = white */
149     add_integer( "freetype-color", 0x00FFFFFF, NULL, COLOR_TEXT,
150                  COLOR_LONGTEXT, VLC_FALSE );
151         change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
152
153     add_integer( "freetype-rel-fontsize", 16, NULL, FONTSIZER_TEXT,
154                  FONTSIZER_LONGTEXT, VLC_FALSE );
155         change_integer_list( pi_sizes, ppsz_sizes_text, 0 );
156     add_integer( "freetype-effect", 2, NULL, EFFECT_TEXT,
157                  EFFECT_LONGTEXT, VLC_FALSE );
158         change_integer_list( pi_effects, ppsz_effects_text, 0 );
159
160     add_bool( "freetype-yuvp", 0, NULL, YUVP_TEXT,
161               YUVP_LONGTEXT, VLC_TRUE );
162     set_capability( "text renderer", 100 );
163     add_shortcut( "text" );
164     set_callbacks( Create, Destroy );
165 vlc_module_end();
166
167 struct line_desc_t
168 {
169     /** NULL-terminated list of glyphs making the string */
170     FT_BitmapGlyph *pp_glyphs;
171     /** list of relative positions for the glyphs */
172     FT_Vector      *p_glyph_pos;
173
174     int             i_height;
175     int             i_width;
176     int             i_red, i_green, i_blue;
177     int             i_alpha;
178
179     line_desc_t    *p_next;
180 };
181
182 static int Render( filter_t *, subpicture_region_t *, line_desc_t *, int, int);
183 static void FreeLines( line_desc_t * );
184 static void FreeLine( line_desc_t * );
185
186 /*****************************************************************************
187  * filter_sys_t: freetype local data
188  *****************************************************************************
189  * This structure is part of the video output thread descriptor.
190  * It describes the freetype specific properties of an output thread.
191  *****************************************************************************/
192 struct filter_sys_t
193 {
194     FT_Library     p_library;   /* handle to library     */
195     FT_Face        p_face;      /* handle to face object */
196     vlc_bool_t     i_use_kerning;
197     uint8_t        i_font_opacity;
198     int            i_font_color;
199     int            i_font_size;
200     int            i_effect;
201
202     int            i_default_font_size;
203     int            i_display_height;
204 };
205
206 /*****************************************************************************
207  * Create: allocates osd-text video thread output method
208  *****************************************************************************
209  * This function allocates and initializes a Clone vout method.
210  *****************************************************************************/
211 static int Create( vlc_object_t *p_this )
212 {
213     filter_t *p_filter = (filter_t *)p_this;
214     filter_sys_t *p_sys;
215     char *psz_fontfile = NULL;
216     int i_error;
217     vlc_value_t val;
218
219     /* Allocate structure */
220     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
221     if( !p_sys )
222     {
223         msg_Err( p_filter, "out of memory" );
224         return VLC_ENOMEM;
225     }
226     p_sys->p_face = 0;
227     p_sys->p_library = 0;
228     p_sys->i_font_size = 0;
229     p_sys->i_display_height = 0;
230
231     var_Create( p_filter, "freetype-font",
232                 VLC_VAR_STRING | VLC_VAR_DOINHERIT );
233     var_Create( p_filter, "freetype-fontsize",
234                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
235     var_Create( p_filter, "freetype-rel-fontsize",
236                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
237     var_Create( p_filter, "freetype-opacity",
238                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
239     var_Create( p_filter, "freetype-effect",
240                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
241     var_Get( p_filter, "freetype-opacity", &val );
242     p_sys->i_font_opacity = __MAX( __MIN( val.i_int, 255 ), 0 );
243     var_Create( p_filter, "freetype-color",
244                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
245     var_Get( p_filter, "freetype-color", &val );
246     p_sys->i_font_color = __MAX( __MIN( val.i_int, 0xFFFFFF ), 0 );
247     p_sys->i_effect = var_GetInteger( p_filter, "freetype-effect" );
248     
249     /* Look what method was requested */
250     var_Get( p_filter, "freetype-font", &val );
251     psz_fontfile = val.psz_string;
252     if( !psz_fontfile || !*psz_fontfile )
253     {
254         if( psz_fontfile ) free( psz_fontfile );
255         psz_fontfile = (char *)malloc( PATH_MAX + 1 );
256 #ifdef WIN32
257         GetWindowsDirectory( psz_fontfile, PATH_MAX + 1 );
258         strcat( psz_fontfile, "\\fonts\\arial.ttf" );
259 #elif __APPLE__
260         strcpy( psz_fontfile, DEFAULT_FONT );
261 #else
262         msg_Err( p_filter, "user didn't specify a font" );
263         goto error;
264 #endif
265     }
266
267     i_error = FT_Init_FreeType( &p_sys->p_library );
268     if( i_error )
269     {
270         msg_Err( p_filter, "couldn't initialize freetype" );
271         goto error;
272     }
273
274     i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
275                            0, &p_sys->p_face );
276     if( i_error == FT_Err_Unknown_File_Format )
277     {
278         msg_Err( p_filter, "file %s have unknown format", psz_fontfile );
279         goto error;
280     }
281     else if( i_error )
282     {
283         msg_Err( p_filter, "failed to load font file %s", psz_fontfile );
284         goto error;
285     }
286
287     i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
288     if( i_error )
289     {
290         msg_Err( p_filter, "font has no unicode translation table" );
291         goto error;
292     }
293
294     p_sys->i_use_kerning = FT_HAS_KERNING( p_sys->p_face );
295
296     var_Get( p_filter, "freetype-fontsize", &val );
297     p_sys->i_default_font_size = val.i_int;
298     if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
299
300     if( psz_fontfile ) free( psz_fontfile );
301     p_filter->pf_render_text = RenderText;
302     return VLC_SUCCESS;
303
304  error:
305     if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
306     if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
307     if( psz_fontfile ) free( psz_fontfile );
308     free( p_sys );
309     return VLC_EGENERIC;
310 }
311
312 /*****************************************************************************
313  * Destroy: destroy Clone video thread output method
314  *****************************************************************************
315  * Clean up all data and library connections
316  *****************************************************************************/
317 static void Destroy( vlc_object_t *p_this )
318 {
319     filter_t *p_filter = (filter_t *)p_this;
320     filter_sys_t *p_sys = p_filter->p_sys;
321
322     FT_Done_Face( p_sys->p_face );
323     FT_Done_FreeType( p_sys->p_library );
324     free( p_sys );
325 }
326
327 /*****************************************************************************
328  * Render: place string in picture
329  *****************************************************************************
330  * This function merges the previously rendered freetype glyphs into a picture
331  *****************************************************************************/
332 static int Render( filter_t *p_filter, subpicture_region_t *p_region,
333                    line_desc_t *p_line, int i_width, int i_height )
334 {
335     static uint8_t pi_gamma[16] =
336         {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
337           0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
338
339     uint8_t *p_dst;
340     video_format_t fmt;
341     int i, x, y, i_pitch;
342     uint8_t i_y; /* YUV values, derived from incoming RGB */
343     int8_t i_u, i_v;
344     subpicture_region_t *p_region_tmp;
345
346     /* Create a new subpicture region */
347     memset( &fmt, 0, sizeof(video_format_t) );
348     fmt.i_chroma = VLC_FOURCC('Y','U','V','P');
349     fmt.i_aspect = 0;
350     fmt.i_width = fmt.i_visible_width = i_width + 4;
351     fmt.i_height = fmt.i_visible_height = i_height + 4;
352     fmt.i_x_offset = fmt.i_y_offset = 0;
353     p_region_tmp = spu_CreateRegion( p_filter, &fmt );
354     if( !p_region_tmp )
355     {
356         msg_Err( p_filter, "cannot allocate SPU region" );
357         return VLC_EGENERIC;
358     }
359
360     p_region->fmt = p_region_tmp->fmt;
361     p_region->picture = p_region_tmp->picture;
362     free( p_region_tmp );
363
364     /* Calculate text color components */
365     i_y = (uint8_t)(( 66 * p_line->i_red  + 129 * p_line->i_green +
366                       25 * p_line->i_blue + 128) >> 8) +  16;
367     i_u = (int8_t)(( -38 * p_line->i_red  -  74 * p_line->i_green +
368                      112 * p_line->i_blue + 128) >> 8) + 128;
369     i_v = (int8_t)(( 112 * p_line->i_red  -  94 * p_line->i_green -
370                       18 * p_line->i_blue + 128) >> 8) + 128;
371
372     /* Build palette */
373     fmt.p_palette->i_entries = 16;
374     for( i = 0; i < 8; i++ )
375     {
376         fmt.p_palette->palette[i][0] = 0;
377         fmt.p_palette->palette[i][1] = 0x80;
378         fmt.p_palette->palette[i][2] = 0x80;
379         fmt.p_palette->palette[i][3] = pi_gamma[i];
380         fmt.p_palette->palette[i][3] =
381             (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
382     }
383     for( i = 8; i < fmt.p_palette->i_entries; i++ )
384     {
385         fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
386         fmt.p_palette->palette[i][1] = i_u;
387         fmt.p_palette->palette[i][2] = i_v;
388         fmt.p_palette->palette[i][3] = pi_gamma[i];
389         fmt.p_palette->palette[i][3] =
390             (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
391     }
392
393     p_dst = p_region->picture.Y_PIXELS;
394     i_pitch = p_region->picture.Y_PITCH;
395
396     /* Initialize the region pixels */
397     memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
398
399     for( ; p_line != NULL; p_line = p_line->p_next )
400     {
401         int i_glyph_tmax = 0;
402         int i_bitmap_offset, i_offset, i_align_offset = 0;
403         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
404         {
405             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
406             i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
407         }
408
409         if( p_line->i_width < i_width )
410         {
411             if( ( p_region->p_style && p_region->p_style->i_text_align == SUBPICTURE_ALIGN_RIGHT ) ||
412                 ( !p_region->p_style && (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT ) )
413             {
414                 i_align_offset = i_width - p_line->i_width;
415             }
416             else if( ( p_region->p_style && p_region->p_style->i_text_align != SUBPICTURE_ALIGN_LEFT ) ||
417                 ( !p_region->p_style && (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT ) )
418             {
419                 i_align_offset = ( i_width - p_line->i_width ) / 2;
420             }
421         }
422
423         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
424         {
425             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
426
427             i_offset = ( p_line->p_glyph_pos[ i ].y +
428                 i_glyph_tmax - p_glyph->top + 2 ) *
429                 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 2 +
430                 i_align_offset;
431
432             for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
433             {
434                 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
435                 {
436                     if( p_glyph->bitmap.buffer[i_bitmap_offset] )
437                         p_dst[i_offset+x] =
438                          ((int)p_glyph->bitmap.buffer[i_bitmap_offset] + 8)/16;
439                 }
440                 i_offset += i_pitch;
441             }
442         }
443     }
444
445     /* Outlining (find something better than nearest neighbour filtering ?) */
446     if( 1 )
447     {
448         uint8_t *p_dst = p_region->picture.Y_PIXELS;
449         uint8_t *p_top = p_dst; /* Use 1st line as a cache */
450         uint8_t left, current;
451
452         for( y = 1; y < (int)fmt.i_height - 1; y++ )
453         {
454             if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
455             p_dst += p_region->picture.Y_PITCH;
456             left = 0;
457
458             for( x = 1; x < (int)fmt.i_width - 1; x++ )
459             {
460                 current = p_dst[x];
461                 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
462                              p_dst[x -1 + p_region->picture.Y_PITCH ] + p_dst[x + p_region->picture.Y_PITCH] + p_dst[x + 1 + p_region->picture.Y_PITCH]) / 16;
463                 left = current;
464             }
465         }
466         memset( p_top, 0, fmt.i_width );
467     }
468
469     return VLC_SUCCESS;
470 }
471
472 static void DrawBlack( line_desc_t *p_line, int i_width, subpicture_region_t *p_region, int xoffset, int yoffset )
473 {
474     uint8_t *p_dst = p_region->picture.A_PIXELS;
475     int i_pitch = p_region->picture.A_PITCH;
476     int x,y;
477
478     for( ; p_line != NULL; p_line = p_line->p_next )
479     {
480         int i_glyph_tmax=0, i = 0;
481         int i_bitmap_offset, i_offset, i_align_offset = 0;
482         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
483         {
484             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
485             i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
486         }
487
488         if( p_line->i_width < i_width )
489         {
490             if( ( p_region->p_style && p_region->p_style->i_text_align == SUBPICTURE_ALIGN_RIGHT ) ||
491                 ( !p_region->p_style && (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT ) )
492             {
493                 i_align_offset = i_width - p_line->i_width;
494             }
495             else if( ( p_region->p_style && p_region->p_style->i_text_align != SUBPICTURE_ALIGN_LEFT ) ||
496                 ( !p_region->p_style && (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT ) )
497             {
498                 i_align_offset = ( i_width - p_line->i_width ) / 2;
499             }
500         }
501
502         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
503         {
504             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
505
506             i_offset = ( p_line->p_glyph_pos[ i ].y +
507                 i_glyph_tmax - p_glyph->top + 3 + yoffset ) *
508                 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
509                 i_align_offset +xoffset;
510
511             for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
512             {
513                 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
514                 {
515                     if( p_glyph->bitmap.buffer[i_bitmap_offset] )
516                         if( p_dst[i_offset+x] <
517                             ((int)p_glyph->bitmap.buffer[i_bitmap_offset]) )
518                             p_dst[i_offset+x] =
519                                 ((int)p_glyph->bitmap.buffer[i_bitmap_offset]);
520                 }
521                 i_offset += i_pitch;
522             }
523         }
524     }
525     
526 }
527
528 /*****************************************************************************
529  * Render: place string in picture
530  *****************************************************************************
531  * This function merges the previously rendered freetype glyphs into a picture
532  *****************************************************************************/
533 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
534                    line_desc_t *p_line, int i_width, int i_height )
535 {
536     static uint8_t pi_gamma[16] =
537         {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
538           0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
539
540     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
541     video_format_t fmt;
542     int i, x, y, i_pitch, i_alpha;
543     uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
544     subpicture_region_t *p_region_tmp;
545
546     if( i_width == 0 || i_height == 0 )
547         return VLC_SUCCESS;
548
549     /* Create a new subpicture region */
550     memset( &fmt, 0, sizeof(video_format_t) );
551     fmt.i_chroma = VLC_FOURCC('Y','U','V','A');
552     fmt.i_aspect = 0;
553     fmt.i_width = fmt.i_visible_width = i_width + 6;
554     fmt.i_height = fmt.i_visible_height = i_height + 6;
555     fmt.i_x_offset = fmt.i_y_offset = 0;
556     p_region_tmp = spu_CreateRegion( p_filter, &fmt );
557     if( !p_region_tmp )
558     {
559         msg_Err( p_filter, "cannot allocate SPU region" );
560         return VLC_EGENERIC;
561     }
562
563     p_region->fmt = p_region_tmp->fmt;
564     p_region->picture = p_region_tmp->picture;
565     free( p_region_tmp );
566
567     /* Calculate text color components */
568     i_y = (uint8_t)__MIN(abs( 2104 * p_line->i_red  + 4130 * p_line->i_green +
569                       802 * p_line->i_blue + 4096 + 131072 ) >> 13, 235);
570     i_u = (uint8_t)__MIN(abs( -1214 * p_line->i_red  + -2384 * p_line->i_green +
571                      3598 * p_line->i_blue + 4096 + 1048576) >> 13, 240);
572     i_v = (uint8_t)__MIN(abs( 3598 * p_line->i_red + -3013 * p_line->i_green +
573                       -585 * p_line->i_blue + 4096 + 1048576) >> 13, 240);
574     i_alpha = p_line->i_alpha;
575
576     p_dst_y = p_region->picture.Y_PIXELS;
577     p_dst_u = p_region->picture.U_PIXELS;
578     p_dst_v = p_region->picture.V_PIXELS;
579     p_dst_a = p_region->picture.A_PIXELS;
580     i_pitch = p_region->picture.A_PITCH;
581
582     /* Initialize the region pixels */
583     if( p_filter->p_sys->i_effect != EFFECT_BACKGROUND )
584     {
585         memset( p_dst_y, 0x00, i_pitch * p_region->fmt.i_height );
586         memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
587         memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
588         memset( p_dst_a, 0, i_pitch * p_region->fmt.i_height );
589     }
590     else
591     {
592         memset( p_dst_y, 0x0, i_pitch * p_region->fmt.i_height );
593         memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
594         memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
595         memset( p_dst_a, 0x80, i_pitch * p_region->fmt.i_height );
596     }
597     if( p_filter->p_sys->i_effect == EFFECT_OUTLINE ||
598         p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
599     {
600         DrawBlack( p_line, i_width, p_region,  0,  0);
601         DrawBlack( p_line, i_width, p_region, -1,  0);
602         DrawBlack( p_line, i_width, p_region,  0, -1);
603         DrawBlack( p_line, i_width, p_region,  1,  0);
604         DrawBlack( p_line, i_width, p_region,  0,  1);
605     }
606     
607     if( p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
608     {
609         DrawBlack( p_line, i_width, p_region, -1, -1);
610         DrawBlack( p_line, i_width, p_region, -1,  1);
611         DrawBlack( p_line, i_width, p_region,  1, -1);
612         DrawBlack( p_line, i_width, p_region,  1,  1);
613
614         DrawBlack( p_line, i_width, p_region, -2,  0);
615         DrawBlack( p_line, i_width, p_region,  0, -2);
616         DrawBlack( p_line, i_width, p_region,  2,  0);
617         DrawBlack( p_line, i_width, p_region,  0,  2);
618
619         DrawBlack( p_line, i_width, p_region, -2, -2);
620         DrawBlack( p_line, i_width, p_region, -2,  2);
621         DrawBlack( p_line, i_width, p_region,  2, -2);
622         DrawBlack( p_line, i_width, p_region,  2,  2);
623
624         DrawBlack( p_line, i_width, p_region, -3,  0);
625         DrawBlack( p_line, i_width, p_region,  0, -3);
626         DrawBlack( p_line, i_width, p_region,  3,  0);
627         DrawBlack( p_line, i_width, p_region,  0,  3);
628     }
629
630     for( ; p_line != NULL; p_line = p_line->p_next )
631     {
632         int i_glyph_tmax = 0;
633         int i_bitmap_offset, i_offset, i_align_offset = 0;
634         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
635         {
636             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
637             i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
638         }
639
640         if( p_line->i_width < i_width )
641         {
642             if( ( p_region->p_style && p_region->p_style->i_text_align == SUBPICTURE_ALIGN_RIGHT ) ||
643                 ( !p_region->p_style && (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT ) )
644             {
645                 i_align_offset = i_width - p_line->i_width;
646             }
647             else if( ( p_region->p_style && p_region->p_style->i_text_align != SUBPICTURE_ALIGN_LEFT ) ||
648                 ( !p_region->p_style && (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT ) )
649             {
650                 i_align_offset = ( i_width - p_line->i_width ) / 2;
651             }
652         }
653
654         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
655         {
656             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
657
658             i_offset = ( p_line->p_glyph_pos[ i ].y +
659                 i_glyph_tmax - p_glyph->top + 3 ) *
660                 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
661                 i_align_offset;
662
663             for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
664             {
665                 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
666                 {
667                     if( p_glyph->bitmap.buffer[i_bitmap_offset] )
668                     {
669                         p_dst_y[i_offset+x] = ((p_dst_y[i_offset+x] *(255-(int)p_glyph->bitmap.buffer[i_bitmap_offset])) +
670                                               i_y * ((int)p_glyph->bitmap.buffer[i_bitmap_offset])) >> 8;
671
672                         p_dst_u[i_offset+x] = i_u;
673                         p_dst_v[i_offset+x] = i_v;
674                         
675                         if( p_filter->p_sys->i_effect == EFFECT_BACKGROUND )
676                             p_dst_a[i_offset+x] = 0xff;
677                     }
678                 }
679                 i_offset += i_pitch;
680             }
681         }
682     }
683     
684     /* Apply the alpha setting */
685     for( i = 0; i < (int)fmt.i_height * i_pitch; i++ )
686         p_dst_a[i] = p_dst_a[i] * (255 - i_alpha) / 255;
687
688     return VLC_SUCCESS;
689 }
690
691 /**
692  * This function renders a text subpicture region into another one.
693  * It also calculates the size needed for this string, and renders the
694  * needed glyphs into memory. It is used as pf_add_string callback in
695  * the vout method by this module
696  */
697 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
698                        subpicture_region_t *p_region_in )
699 {
700     filter_sys_t *p_sys = p_filter->p_sys;
701     line_desc_t  *p_lines = 0, *p_line = 0, *p_next = 0, *p_prev = 0;
702     int i, i_pen_y, i_pen_x, i_error, i_glyph_index, i_previous;
703     uint32_t *psz_unicode, *psz_unicode_orig = 0, i_char, *psz_line_start;
704     int i_string_length;
705     char *psz_string;
706     vlc_iconv_t iconv_handle = (vlc_iconv_t)(-1);
707     int i_font_color, i_font_alpha, i_font_size, i_red, i_green, i_blue;
708
709     FT_BBox line;
710     FT_BBox glyph_size;
711     FT_Vector result;
712     FT_Glyph tmp_glyph;
713
714     /* Sanity check */
715     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
716     psz_string = p_region_in->psz_text;
717     if( !psz_string || !*psz_string ) return VLC_EGENERIC;
718
719     if( p_region_in->p_style )
720     {
721         i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
722         i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
723         i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 );
724     }
725     else
726     {
727         i_font_color = p_sys->i_font_color;
728         i_font_alpha = 255 - p_sys->i_font_opacity;
729         i_font_size  = p_sys->i_default_font_size;
730     }
731
732     if( i_font_color == 0xFFFFFF ) i_font_color = p_sys->i_font_color;
733     if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
734     SetFontSize( p_filter, i_font_size );
735
736     i_red   = ( i_font_color & 0x00FF0000 ) >> 16;
737     i_green = ( i_font_color & 0x0000FF00 ) >>  8;
738     i_blue  =   i_font_color & 0x000000FF;
739
740     result.x =  result.y = 0;
741     line.xMin = line.xMax = line.yMin = line.yMax = 0;
742
743     psz_unicode = psz_unicode_orig =
744         malloc( ( strlen(psz_string) + 1 ) * sizeof(uint32_t) );
745     if( psz_unicode == NULL )
746     {
747         msg_Err( p_filter, "out of memory" );
748         goto error;
749     }
750 #if defined(WORDS_BIGENDIAN)
751     iconv_handle = vlc_iconv_open( "UCS-4BE", "UTF-8" );
752 #else
753     iconv_handle = vlc_iconv_open( "UCS-4LE", "UTF-8" );
754 #endif
755     if( iconv_handle == (vlc_iconv_t)-1 )
756     {
757         msg_Warn( p_filter, "unable to do conversion" );
758         goto error;
759     }
760
761     {
762         char *p_in_buffer, *p_out_buffer;
763         size_t i_in_bytes, i_out_bytes, i_out_bytes_left, i_ret;
764         i_in_bytes = strlen( psz_string );
765         i_out_bytes = i_in_bytes * sizeof( uint32_t );
766         i_out_bytes_left = i_out_bytes;
767         p_in_buffer = psz_string;
768         p_out_buffer = (char *)psz_unicode;
769         i_ret = vlc_iconv( iconv_handle, &p_in_buffer, &i_in_bytes,
770                            &p_out_buffer, &i_out_bytes_left );
771
772         vlc_iconv_close( iconv_handle );
773
774         if( i_in_bytes )
775         {
776             msg_Warn( p_filter, "failed to convert string to unicode (%s), "
777                       "bytes left %d", strerror(errno), (int)i_in_bytes );
778             goto error;
779         }
780         *(uint32_t*)p_out_buffer = 0;
781         i_string_length = (i_out_bytes - i_out_bytes_left) / sizeof(uint32_t);
782     }
783
784 #if defined(HAVE_FRIBIDI)
785     {
786         uint32_t *p_fribidi_string;
787         int start_pos, pos = 0;
788         
789         p_fribidi_string = malloc( (i_string_length + 1) * sizeof(uint32_t) );
790
791         /* Do bidi conversion line-by-line */
792         while(pos < i_string_length)
793         {
794             while(pos < i_string_length) {
795                 i_char = psz_unicode[pos];
796                 if (i_char != '\r' && i_char != '\n')
797                     break;
798                 p_fribidi_string[pos] = i_char;
799                 ++pos;
800             }
801             start_pos = pos;
802             while(pos < i_string_length) {
803                 i_char = psz_unicode[pos];
804                 if (i_char == '\r' || i_char == '\n')
805                     break;
806                 ++pos;
807             }
808             if (pos > start_pos)
809             {
810                 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
811                 fribidi_log2vis((FriBidiChar*)psz_unicode + start_pos, pos - start_pos,
812                                 &base_dir, (FriBidiChar*)p_fribidi_string + start_pos, 0, 0, 0);
813             }
814         }
815
816         free( psz_unicode_orig );
817         psz_unicode = psz_unicode_orig = p_fribidi_string;
818         p_fribidi_string[ i_string_length ] = 0;
819     }
820 #endif
821
822     /* Calculate relative glyph positions and a bounding box for the
823      * entire string */
824     if( !(p_line = NewLine( (byte_t *)psz_string )) )
825     {
826         msg_Err( p_filter, "out of memory" );
827         goto error;
828     }
829     p_lines = p_line;
830     i_pen_x = i_pen_y = 0;
831     i_previous = i = 0;
832     psz_line_start = psz_unicode;
833
834 #define face p_sys->p_face
835 #define glyph face->glyph
836
837     while( *psz_unicode )
838     {
839         i_char = *psz_unicode++;
840         if( i_char == '\r' ) /* ignore CR chars wherever they may be */
841         {
842             continue;
843         }
844
845         if( i_char == '\n' )
846         {
847             psz_line_start = psz_unicode;
848             if( !(p_next = NewLine( (byte_t *)psz_string )) )
849             {
850                 msg_Err( p_filter, "out of memory" );
851                 goto error;
852             }
853             p_line->p_next = p_next;
854             p_line->i_width = line.xMax;
855             p_line->i_height = face->size->metrics.height >> 6;
856             p_line->pp_glyphs[ i ] = NULL;
857             p_line->i_alpha = i_font_alpha;
858             p_line->i_red = i_red;
859             p_line->i_green = i_green;
860             p_line->i_blue = i_blue;
861             p_prev = p_line;
862             p_line = p_next;
863             result.x = __MAX( result.x, line.xMax );
864             result.y += face->size->metrics.height >> 6;
865             i_pen_x = 0;
866             i_previous = i = 0;
867             line.xMin = line.xMax = line.yMin = line.yMax = 0;
868             i_pen_y += face->size->metrics.height >> 6;
869 #if 0
870             msg_Dbg( p_filter, "Creating new line, i is %d", i );
871 #endif
872             continue;
873         }
874
875         i_glyph_index = FT_Get_Char_Index( face, i_char );
876         if( p_sys->i_use_kerning && i_glyph_index
877             && i_previous )
878         {
879             FT_Vector delta;
880             FT_Get_Kerning( face, i_previous, i_glyph_index,
881                             ft_kerning_default, &delta );
882             i_pen_x += delta.x >> 6;
883
884         }
885         p_line->p_glyph_pos[ i ].x = i_pen_x;
886         p_line->p_glyph_pos[ i ].y = i_pen_y;
887         i_error = FT_Load_Glyph( face, i_glyph_index, FT_LOAD_DEFAULT );
888         if( i_error )
889         {
890             msg_Err( p_filter, "unable to render text FT_Load_Glyph returned"
891                                " %d", i_error );
892             goto error;
893         }
894         i_error = FT_Get_Glyph( glyph, &tmp_glyph );
895         if( i_error )
896         {
897             msg_Err( p_filter, "unable to render text FT_Get_Glyph returned "
898                                "%d", i_error );
899             goto error;
900         }
901         FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
902         i_error = FT_Glyph_To_Bitmap( &tmp_glyph, ft_render_mode_normal, 0, 1);
903         if( i_error )
904         {
905             FT_Done_Glyph( tmp_glyph );
906             continue;
907         }
908         p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
909
910         /* Do rest */
911         line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
912             glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
913         if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
914         {
915             p_line->pp_glyphs[ i ] = NULL;
916             FreeLine( p_line );
917             p_line = NewLine( (byte_t *)psz_string );
918             if( p_prev ) p_prev->p_next = p_line;
919             else p_lines = p_line;
920
921             while( psz_unicode > psz_line_start && *psz_unicode != ' ' )
922             {
923                 psz_unicode--;
924             }
925             if( psz_unicode == psz_line_start )
926             {
927                 msg_Warn( p_filter, "unbreakable string" );
928                 goto error;
929             }
930             else
931             {
932                 *psz_unicode = '\n';
933             }
934             psz_unicode = psz_line_start;
935             i_pen_x = 0;
936             i_previous = i = 0;
937             line.xMin = line.xMax = line.yMin = line.yMax = 0;
938             continue;
939         }
940         line.yMax = __MAX( line.yMax, glyph_size.yMax );
941         line.yMin = __MIN( line.yMin, glyph_size.yMin );
942
943         i_previous = i_glyph_index;
944         i_pen_x += glyph->advance.x >> 6;
945         i++;
946     }
947
948     p_line->i_width = line.xMax;
949     p_line->i_height = face->size->metrics.height >> 6;
950     p_line->pp_glyphs[ i ] = NULL;
951     p_line->i_alpha = i_font_alpha;
952     p_line->i_red = i_red;
953     p_line->i_green = i_green;
954     p_line->i_blue = i_blue;
955     result.x = __MAX( result.x, line.xMax );
956     result.y += line.yMax - line.yMin;
957
958 #undef face
959 #undef glyph
960
961     p_region_out->i_x = p_region_in->i_x;
962     p_region_out->i_y = p_region_in->i_y;
963
964     if( config_GetInt( p_filter, "freetype-yuvp" ) )
965         Render( p_filter, p_region_out, p_lines, result.x, result.y );
966     else
967         RenderYUVA( p_filter, p_region_out, p_lines, result.x, result.y );
968
969     if( psz_unicode_orig ) free( psz_unicode_orig );
970     FreeLines( p_lines );
971     return VLC_SUCCESS;
972
973  error:
974     if( psz_unicode_orig ) free( psz_unicode_orig );
975     FreeLines( p_lines );
976     return VLC_EGENERIC;
977 }
978
979 static void FreeLine( line_desc_t *p_line )
980 {
981     unsigned int i;
982     for( i = 0; p_line->pp_glyphs[ i ] != NULL; i++ )
983     {
984         FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
985     }
986     free( p_line->pp_glyphs );
987     free( p_line->p_glyph_pos );
988     free( p_line );
989 }
990
991 static void FreeLines( line_desc_t *p_lines )
992 {
993     line_desc_t *p_line, *p_next;
994
995     if( !p_lines ) return;
996
997     for( p_line = p_lines; p_line != NULL; p_line = p_next )
998     {
999         p_next = p_line->p_next;
1000         FreeLine( p_line );
1001     }
1002 }
1003
1004 static line_desc_t *NewLine( byte_t *psz_string )
1005 {
1006     int i_count;
1007     line_desc_t *p_line = malloc( sizeof(line_desc_t) );
1008
1009     if( !p_line ) return NULL;
1010     p_line->i_height = 0;
1011     p_line->i_width = 0;
1012     p_line->p_next = NULL;
1013
1014     /* We don't use CountUtf8Characters() here because we are not acutally
1015      * sure the string is utf8. Better be safe than sorry. */
1016     i_count = strlen( (char *)psz_string );
1017
1018     p_line->pp_glyphs = malloc( sizeof(FT_BitmapGlyph) * ( i_count + 1 ) );
1019     if( p_line->pp_glyphs == NULL )
1020     {
1021         free( p_line );
1022         return NULL;
1023     }
1024     p_line->pp_glyphs[0] = NULL;
1025
1026     p_line->p_glyph_pos = malloc( sizeof( FT_Vector ) * i_count + 1 );
1027     if( p_line->p_glyph_pos == NULL )
1028     {
1029         free( p_line->pp_glyphs );
1030         free( p_line );
1031         return NULL;
1032     }
1033
1034     return p_line;
1035 }
1036
1037 static int SetFontSize( filter_t *p_filter, int i_size )
1038 {
1039     filter_sys_t *p_sys = p_filter->p_sys;
1040
1041     if( i_size && i_size == p_sys->i_font_size ) return VLC_SUCCESS;
1042
1043     if( !i_size )
1044     {
1045         vlc_value_t val;
1046
1047         if( !p_sys->i_default_font_size &&
1048             p_sys->i_display_height == (int)p_filter->fmt_out.video.i_height )
1049             return VLC_SUCCESS;
1050
1051         if( p_sys->i_default_font_size )
1052         {
1053             i_size = p_sys->i_default_font_size;
1054         }
1055         else
1056         {
1057             var_Get( p_filter, "freetype-rel-fontsize", &val );
1058             i_size = (int)p_filter->fmt_out.video.i_height / val.i_int;
1059             p_filter->p_sys->i_display_height =
1060                 p_filter->fmt_out.video.i_height;
1061         }
1062         if( i_size <= 0 )
1063         {
1064             msg_Warn( p_filter, "invalid fontsize, using 12" );
1065             i_size = 12;
1066         }
1067
1068         msg_Dbg( p_filter, "using fontsize: %i", i_size );
1069     }
1070
1071     p_sys->i_font_size = i_size;
1072
1073     if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
1074     {
1075         msg_Err( p_filter, "couldn't set font size to %d", i_size );
1076         return VLC_EGENERIC;
1077     }
1078
1079     return VLC_SUCCESS;
1080 }