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