]> git.sesse.net Git - vlc/blob - modules/misc/text_renderer/freetype.c
Merged text_renderer.h into freetype.c
[vlc] / modules / misc / text_renderer / freetype.c
1 /*****************************************************************************
2  * freetype.c : Put text on the video, using freetype2
3  *****************************************************************************
4  * Copyright (C) 2002 - 2011 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8  *          Gildas Bazin <gbazin@videolan.org>
9  *          Bernie Purcell <bitmap@videolan.org>
10  *          Jean-Baptiste Kempf <jb@videolan.org>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software Foundation, Inc.,
24  * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30
31 #ifdef HAVE_CONFIG_H
32 # include "config.h"
33 #endif
34
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
37 #include <vlc_stream.h>                        /* stream_MemoryNew */
38 #include <vlc_input.h>                         /* vlc_input_attachment_* */
39 #include <vlc_xml.h>                           /* xml_reader */
40 #include <vlc_strings.h>                       /* resolve_xml_special_chars */
41 #include <vlc_charset.h>                       /* ToCharset */
42 #include <vlc_dialog.h>                        /* FcCache dialog */
43 #include <vlc_filter.h>                                      /* filter_sys_t */
44 #include <vlc_text_style.h>                                   /* text_style_t*/
45 #include <vlc_memory.h>                                   /* realloc_or_free */
46
47 /* Default fonts */
48 #ifdef __APPLE__
49 # define DEFAULT_FONT_FILE "/Library/Fonts/Arial Black.ttf"
50 # define DEFAULT_FAMILY "Arial Black"
51 #elif defined( WIN32 )
52 # define DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
53 # define DEFAULT_FAMILY "Arial"
54 #elif defined( HAVE_MAEMO )
55 # define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf"
56 # define DEFAULT_FAMILY "Nokia Sans Bold"
57 #else
58 # define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
59 # define DEFAULT_FAMILY "Serif Bold"
60 #endif
61
62 /* Freetype */
63 #include <freetype/ftsynth.h>
64 #include FT_FREETYPE_H
65 #include FT_GLYPH_H
66 #define FT_FLOOR(X)     ((X & -64) >> 6)
67 #define FT_CEIL(X)      (((X + 63) & -64) >> 6)
68 #ifndef FT_MulFix
69  #define FT_MulFix(v, s) (((v)*(s))>>16)
70 #endif
71
72 /* RTL */
73 #if defined(HAVE_FRIBIDI)
74 # include <fribidi/fribidi.h>
75 #endif
76
77 /* Win32 GDI */
78 #ifdef WIN32
79 # include <windows.h>
80 # include <shlobj.h>
81 # define HAVE_STYLES
82 # undef HAVE_FONTCONFIG
83 #endif
84
85 /* FontConfig */
86 #ifdef HAVE_FONTCONFIG
87 # include <fontconfig/fontconfig.h>
88 # define HAVE_STYLES
89 #endif
90
91 #include <assert.h>
92
93 /*****************************************************************************
94  * Module descriptor
95  *****************************************************************************/
96 static int  Create ( vlc_object_t * );
97 static void Destroy( vlc_object_t * );
98
99 #define FONT_TEXT N_("Font")
100
101 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
102 #define FONT_LONGTEXT N_("Font file for the font you want to use")
103
104 #define FONTSIZE_TEXT N_("Font size in pixels")
105 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
106     "that will be rendered on the video. " \
107     "If set to something different than 0 this option will override the " \
108     "relative font size." )
109 #define OPACITY_TEXT N_("Opacity")
110 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
111     "text that will be rendered on the video. 0 = transparent, " \
112     "255 = totally opaque. " )
113 #define COLOR_TEXT N_("Text default color")
114 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
115     "the video. This must be an hexadecimal (like HTML colors). The first two "\
116     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
117     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
118 #define FONTSIZER_TEXT N_("Relative font size")
119 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
120     "fonts that will be rendered on the video. If absolute font size is set, "\
121     "relative size will be overridden." )
122
123 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
124 static const char *const ppsz_sizes_text[] = {
125     N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
126 #define YUVP_TEXT N_("Use YUVP renderer")
127 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
128   "This option is only needed if you want to encode into DVB subtitles" )
129 #define EFFECT_TEXT N_("Font Effect")
130 #define EFFECT_LONGTEXT N_("It is possible to apply effects to the rendered " \
131 "text to improve its readability." )
132
133 enum { EFFECT_BACKGROUND  = 1,
134        EFFECT_OUTLINE     = 2,
135        EFFECT_OUTLINE_FAT = 3,
136 };
137 static int const pi_effects[] = { EFFECT_BACKGROUND, EFFECT_OUTLINE, EFFECT_OUTLINE_FAT };
138 static const char *const ppsz_effects_text[] = {
139     N_("Background"),N_("Outline"), N_("Fat Outline") };
140
141 static const int pi_color_values[] = {
142   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
143   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
144   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
145
146 static const char *const ppsz_color_descriptions[] = {
147   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
148   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
149   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
150
151 vlc_module_begin ()
152     set_shortname( N_("Text renderer"))
153     set_description( N_("Freetype2 font renderer") )
154     set_category( CAT_VIDEO )
155     set_subcategory( SUBCAT_VIDEO_SUBPIC )
156
157 #ifdef HAVE_STYLES
158     add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
159 #else
160     add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
161 #endif
162
163     add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
164                  FONTSIZE_LONGTEXT, true )
165         change_safe()
166
167     /* opacity valid on 0..255, with default 255 = fully opaque */
168     add_integer_with_range( "freetype-opacity", 255, 0, 255,
169         OPACITY_TEXT, OPACITY_LONGTEXT, false )
170         change_safe()
171
172     /* hook to the color values list, with default 0x00ffffff = white */
173     add_integer( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
174                  COLOR_LONGTEXT, false )
175         change_integer_list( pi_color_values, ppsz_color_descriptions )
176         change_safe()
177
178     add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
179                  FONTSIZER_LONGTEXT, false )
180         change_integer_list( pi_sizes, ppsz_sizes_text )
181         change_safe()
182
183     add_integer( "freetype-effect", 2, EFFECT_TEXT,
184                  EFFECT_LONGTEXT, false )
185         change_integer_list( pi_effects, ppsz_effects_text )
186         change_safe()
187
188     add_bool( "freetype-yuvp", false, YUVP_TEXT,
189               YUVP_LONGTEXT, true )
190     set_capability( "text renderer", 100 )
191     add_shortcut( "text" )
192     set_callbacks( Create, Destroy )
193 vlc_module_end ()
194
195
196 /*****************************************************************************
197  * Local prototypes
198  *****************************************************************************/
199
200 /* The RenderText call maps to pf_render_string, defined in vlc_filter.h */
201 static int RenderText( filter_t *, subpicture_region_t *,
202                        subpicture_region_t * );
203
204 #ifdef HAVE_STYLES
205 static int RenderHtml( filter_t *, subpicture_region_t *,
206                        subpicture_region_t * );
207 #endif
208 #ifdef HAVE_FONTCONFIG
209 static void FontConfig_BuildCache( filter_t * );
210 static char* FontConfig_Select( FcConfig *, const char *,
211                                 bool, bool, int, int * );
212 #endif
213 #ifdef WIN32
214 static char* Win32_Select( filter_t *, const char *,
215                            bool, bool, int, int * );
216 #endif
217
218 static int LoadFontsFromAttachments( filter_t *p_filter );
219
220 static int GetFontSize( filter_t *p_filter );
221 static int SetFontSize( filter_t *, int );
222 static void YUVFromRGB( uint32_t i_argb,
223                         uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v );
224
225 typedef struct line_desc_t line_desc_t;
226 struct line_desc_t
227 {
228     /** NULL-terminated list of glyphs making the string */
229     FT_BitmapGlyph *pp_glyphs;
230     /** list of relative positions for the glyphs */
231     FT_Vector      *p_glyph_pos;
232     /** list of RGB information for styled text
233      * -- if the rendering mode supports it (RenderYUVA) and
234      *  b_new_color_mode is set, then it becomes possible to
235      *  have multicoloured text within the subtitles. */
236     uint32_t       *p_fg_rgb;
237     uint32_t       *p_bg_rgb;
238     uint8_t        *p_fg_bg_ratio; /* 0x00=100% FG --> 0x7F=100% BG */
239     bool            b_new_color_mode;
240     /** underline information -- only supplied if text should be underlined */
241     int            *pi_underline_offset;
242     uint16_t       *pi_underline_thickness;
243
244     int             i_height;
245     int             i_width;
246     int             i_red, i_green, i_blue;
247     int             i_alpha;
248
249     line_desc_t    *p_next;
250 };
251 static line_desc_t *NewLine( int );
252
253 typedef struct
254 {
255     int         i_font_size;
256     uint32_t    i_font_color;         /* ARGB */
257     uint32_t    i_karaoke_bg_color;   /* ARGB */
258     bool        b_italic;
259     bool        b_bold;
260     bool        b_underline;
261     bool        b_through;
262     char       *psz_fontname;
263 } ft_style_t;
264
265 typedef struct font_stack_t font_stack_t;
266 struct font_stack_t
267 {
268     char          *psz_name;
269     int            i_size;
270     uint32_t       i_color;            /* ARGB */
271     uint32_t       i_karaoke_bg_color; /* ARGB */
272
273     font_stack_t  *p_next;
274 };
275
276 static int RenderYUVP( filter_t *, subpicture_region_t *, line_desc_t *, int, int);
277 static void FreeLines( line_desc_t * );
278 static void FreeLine( line_desc_t * );
279
280 /*****************************************************************************
281  * filter_sys_t: freetype local data
282  *****************************************************************************
283  * This structure is part of the video output thread descriptor.
284  * It describes the freetype specific properties of an output thread.
285  *****************************************************************************/
286 struct filter_sys_t
287 {
288     FT_Library     p_library;   /* handle to library     */
289     FT_Face        p_face;      /* handle to face object */
290     bool           i_use_kerning;
291     uint8_t        i_font_opacity;
292     int            i_font_color;
293     int            i_font_size;
294     int            i_effect;
295
296     int            i_default_font_size;
297     int            i_display_height;
298     char*          psz_fontfamily;
299 #ifdef HAVE_STYLES
300     xml_reader_t  *p_xml;
301 #ifdef WIN32
302     char*          psz_win_fonts_path;
303 #endif
304 #endif
305
306     input_attachment_t **pp_font_attachments;
307     int                  i_font_attachments;
308 };
309
310 /* */
311 /*****************************************************************************
312  * Create: allocates osd-text video thread output method
313  *****************************************************************************
314  * This function allocates and initializes a Clone vout method.
315  *****************************************************************************/
316 static int Create( vlc_object_t *p_this )
317 {
318     filter_t      *p_filter = (filter_t *)p_this;
319     filter_sys_t  *p_sys;
320     char          *psz_fontfile   = NULL;
321     char          *psz_fontfamily = NULL;
322     int            i_error = 0, fontindex = 0;
323
324     /* Allocate structure */
325     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
326     if( !p_sys )
327         return VLC_ENOMEM;
328
329     p_sys->psz_fontfamily   = NULL;
330 #ifdef HAVE_STYLES
331     p_sys->p_xml            = NULL;
332 #endif
333     p_sys->p_face           = 0;
334     p_sys->p_library        = 0;
335     p_sys->i_font_size      = 0;
336     p_sys->i_display_height = 0;
337
338     var_Create( p_filter, "freetype-rel-fontsize",
339                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
340
341     psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
342     p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
343     p_sys->i_effect = var_InheritInteger( p_filter, "freetype-effect" );
344     p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
345     p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
346     p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
347     p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
348
349 #ifdef WIN32
350     /* Get Windows Font folder */
351     wchar_t wdir[MAX_PATH];
352     if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
353     {
354         GetWindowsDirectoryW( wdir, MAX_PATH );
355         wcscat( wdir, L"\\fonts" );
356     }
357     p_sys->psz_win_fonts_path = FromWide( wdir );
358 #endif
359
360     /* Set default psz_fontfamily */
361     if( !psz_fontfamily || !*psz_fontfamily )
362     {
363         free( psz_fontfamily );
364 #ifdef HAVE_STYLES
365         psz_fontfamily = strdup( DEFAULT_FAMILY );
366 #else
367 # ifdef WIN32
368         if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
369             goto error;
370 # else
371         psz_fontfamily = strdup( DEFAULT_FONT_FILE );
372 # endif
373         msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
374 #endif
375     }
376
377     /* Set the current font file */
378     p_sys->psz_fontfamily = psz_fontfamily;
379 #ifdef HAVE_STYLES
380 #ifdef HAVE_FONTCONFIG
381     FontConfig_BuildCache( p_filter );
382
383     /* */
384     psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
385                                       p_sys->i_default_font_size, &fontindex );
386 #elif defined(WIN32)
387     psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
388                                  p_sys->i_default_font_size, &fontindex );
389
390 #endif
391     msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
392
393     /* If nothing is found, use the default family */
394     if( !psz_fontfile )
395         psz_fontfile = strdup( psz_fontfamily );
396
397 #else /* !HAVE_STYLES */
398     /* Use the default file */
399     psz_fontfile = psz_fontfamily;
400 #endif
401
402     /* */
403     i_error = FT_Init_FreeType( &p_sys->p_library );
404     if( i_error )
405     {
406         msg_Err( p_filter, "couldn't initialize freetype" );
407         goto error;
408     }
409
410     i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
411                            fontindex, &p_sys->p_face );
412
413     if( i_error == FT_Err_Unknown_File_Format )
414     {
415         msg_Err( p_filter, "file %s have unknown format",
416                  psz_fontfile ? psz_fontfile : "(null)" );
417         goto error;
418     }
419     else if( i_error )
420     {
421         msg_Err( p_filter, "failed to load font file %s",
422                  psz_fontfile ? psz_fontfile : "(null)" );
423         goto error;
424     }
425 #ifdef HAVE_STYLES
426     free( psz_fontfile );
427 #endif
428
429     i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
430     if( i_error )
431     {
432         msg_Err( p_filter, "font has no unicode translation table" );
433         goto error;
434     }
435
436     p_sys->i_use_kerning = FT_HAS_KERNING( p_sys->p_face );
437
438     if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
439
440
441     p_sys->pp_font_attachments = NULL;
442     p_sys->i_font_attachments = 0;
443
444     p_filter->pf_render_text = RenderText;
445 #ifdef HAVE_STYLES
446     p_filter->pf_render_html = RenderHtml;
447 #else
448     p_filter->pf_render_html = NULL;
449 #endif
450
451     LoadFontsFromAttachments( p_filter );
452
453     return VLC_SUCCESS;
454
455 error:
456     if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
457     if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
458 #ifdef HAVE_STYLES
459     free( psz_fontfile );
460 #endif
461     free( psz_fontfamily );
462     free( p_sys );
463     return VLC_EGENERIC;
464 }
465
466 /*****************************************************************************
467  * Destroy: destroy Clone video thread output method
468  *****************************************************************************
469  * Clean up all data and library connections
470  *****************************************************************************/
471 static void Destroy( vlc_object_t *p_this )
472 {
473     filter_t *p_filter = (filter_t *)p_this;
474     filter_sys_t *p_sys = p_filter->p_sys;
475
476     if( p_sys->pp_font_attachments )
477     {
478         for( int k = 0; k < p_sys->i_font_attachments; k++ )
479             vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
480
481         free( p_sys->pp_font_attachments );
482     }
483
484 #ifdef HAVE_STYLES
485     if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
486 #endif
487     free( p_sys->psz_fontfamily );
488
489     /* FcFini asserts calling the subfunction FcCacheFini()
490      * even if no other library functions have been made since FcInit(),
491      * so don't call it. */
492
493     FT_Done_Face( p_sys->p_face );
494     FT_Done_FreeType( p_sys->p_library );
495     free( p_sys );
496 }
497
498 /*****************************************************************************
499  * Make any TTF/OTF fonts present in the attachments of the media file
500  * and store them for later use by the FreeType Engine
501  *****************************************************************************/
502 static int LoadFontsFromAttachments( filter_t *p_filter )
503 {
504     filter_sys_t         *p_sys = p_filter->p_sys;
505     input_attachment_t  **pp_attachments;
506     int                   i_attachments_cnt;
507
508     if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
509         return VLC_EGENERIC;
510
511     p_sys->i_font_attachments = 0;
512     p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof( input_attachment_t * ));
513     if( !p_sys->pp_font_attachments )
514         return VLC_ENOMEM;
515
516     for( int k = 0; k < i_attachments_cnt; k++ )
517     {
518         input_attachment_t *p_attach = pp_attachments[k];
519
520         if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
521               !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) &&    // OTF
522             p_attach->i_data > 0 && p_attach->p_data )
523         {
524             p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
525         }
526         else
527         {
528             vlc_input_attachment_Delete( p_attach );
529         }
530     }
531     free( pp_attachments );
532
533     return VLC_SUCCESS;
534 }
535
536 /*****************************************************************************
537  * RenderYUVP: place string in picture
538  *****************************************************************************
539  * This function merges the previously rendered freetype glyphs into a picture
540  *****************************************************************************/
541 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
542                        line_desc_t *p_line, int i_width, int i_height )
543 {
544     VLC_UNUSED(p_filter);
545     static const uint8_t pi_gamma[16] =
546         {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
547           0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
548
549     uint8_t *p_dst;
550     video_format_t fmt;
551     int i, x, y, i_pitch;
552     uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
553
554     /* Create a new subpicture region */
555     memset( &fmt, 0, sizeof(video_format_t) );
556     fmt.i_chroma = VLC_CODEC_YUVP;
557     fmt.i_width = fmt.i_visible_width = i_width + 4;
558     fmt.i_height = fmt.i_visible_height = i_height + 4;
559     if( p_region->fmt.i_visible_width > 0 )
560         fmt.i_visible_width = p_region->fmt.i_visible_width;
561     if( p_region->fmt.i_visible_height > 0 )
562         fmt.i_visible_height = p_region->fmt.i_visible_height;
563     fmt.i_x_offset = fmt.i_y_offset = 0;
564     fmt.i_sar_num = 1;
565     fmt.i_sar_den = 1;
566
567     assert( !p_region->p_picture );
568     p_region->p_picture = picture_NewFromFormat( &fmt );
569     if( !p_region->p_picture )
570         return VLC_EGENERIC;
571     fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
572     p_region->fmt = fmt;
573
574     /* Calculate text color components */
575     YUVFromRGB( (p_line->i_red   << 16) |
576                 (p_line->i_green <<  8) |
577                 (p_line->i_blue       ),
578                 &i_y, &i_u, &i_v);
579
580     /* Build palette */
581     fmt.p_palette->i_entries = 16;
582     for( i = 0; i < 8; i++ )
583     {
584         fmt.p_palette->palette[i][0] = 0;
585         fmt.p_palette->palette[i][1] = 0x80;
586         fmt.p_palette->palette[i][2] = 0x80;
587         fmt.p_palette->palette[i][3] = pi_gamma[i];
588         fmt.p_palette->palette[i][3] =
589             (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
590     }
591     for( i = 8; i < fmt.p_palette->i_entries; i++ )
592     {
593         fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
594         fmt.p_palette->palette[i][1] = i_u;
595         fmt.p_palette->palette[i][2] = i_v;
596         fmt.p_palette->palette[i][3] = pi_gamma[i];
597         fmt.p_palette->palette[i][3] =
598             (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
599     }
600
601     p_dst = p_region->p_picture->Y_PIXELS;
602     i_pitch = p_region->p_picture->Y_PITCH;
603
604     /* Initialize the region pixels */
605     memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
606
607     for( ; p_line != NULL; p_line = p_line->p_next )
608     {
609         int i_glyph_tmax = 0;
610         int i_bitmap_offset, i_offset, i_align_offset = 0;
611         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
612         {
613             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
614             i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
615         }
616
617         if( p_line->i_width < i_width )
618         {
619             if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
620             {
621                 i_align_offset = i_width - p_line->i_width;
622             }
623             else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
624             {
625                 i_align_offset = ( i_width - p_line->i_width ) / 2;
626             }
627         }
628
629         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
630         {
631             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
632
633             i_offset = ( p_line->p_glyph_pos[ i ].y +
634                 i_glyph_tmax - p_glyph->top + 2 ) *
635                 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 2 +
636                 i_align_offset;
637
638             for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
639             {
640                 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
641                 {
642                     if( p_glyph->bitmap.buffer[i_bitmap_offset] )
643                         p_dst[i_offset+x] =
644                          ((int)p_glyph->bitmap.buffer[i_bitmap_offset] + 8)/16;
645                 }
646                 i_offset += i_pitch;
647             }
648         }
649     }
650
651     /* Outlining (find something better than nearest neighbour filtering ?) */
652     if( 1 )
653     {
654         uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
655         uint8_t *p_top = p_dst; /* Use 1st line as a cache */
656         uint8_t left, current;
657
658         for( y = 1; y < (int)fmt.i_height - 1; y++ )
659         {
660             if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
661             p_dst += p_region->p_picture->Y_PITCH;
662             left = 0;
663
664             for( x = 1; x < (int)fmt.i_width - 1; x++ )
665             {
666                 current = p_dst[x];
667                 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
668                              p_dst[x -1 + p_region->p_picture->Y_PITCH ] + p_dst[x + p_region->p_picture->Y_PITCH] + p_dst[x + 1 + p_region->p_picture->Y_PITCH]) / 16;
669                 left = current;
670             }
671         }
672         memset( p_top, 0, fmt.i_width );
673     }
674
675     return VLC_SUCCESS;
676 }
677
678 static void UnderlineGlyphYUVA( int i_line_thickness, int i_line_offset, bool b_ul_next_char,
679                                 FT_BitmapGlyph  p_this_glyph, FT_Vector *p_this_glyph_pos,
680                                 FT_BitmapGlyph  p_next_glyph, FT_Vector *p_next_glyph_pos,
681                                 int i_glyph_tmax, int i_align_offset,
682                                 uint8_t i_y, uint8_t i_u, uint8_t i_v,
683                                 subpicture_region_t *p_region)
684 {
685     int i_pitch;
686     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
687
688     p_dst_y = p_region->p_picture->Y_PIXELS;
689     p_dst_u = p_region->p_picture->U_PIXELS;
690     p_dst_v = p_region->p_picture->V_PIXELS;
691     p_dst_a = p_region->p_picture->A_PIXELS;
692     i_pitch = p_region->p_picture->A_PITCH;
693
694     int i_offset = ( p_this_glyph_pos->y + i_glyph_tmax + i_line_offset + 3 ) * i_pitch +
695                      p_this_glyph_pos->x + p_this_glyph->left + 3 + i_align_offset;
696
697     for( int y = 0; y < i_line_thickness; y++ )
698     {
699         int i_extra = p_this_glyph->bitmap.width;
700
701         if( b_ul_next_char )
702         {
703             i_extra = (p_next_glyph_pos->x + p_next_glyph->left) -
704                       (p_this_glyph_pos->x + p_this_glyph->left);
705         }
706         for( int x = 0; x < i_extra; x++ )
707         {
708             bool b_ok = true;
709
710             /* break the underline around the tails of any glyphs which cross it */
711             /* Strikethrough doesn't get broken */
712             for( int z = x - i_line_thickness;
713                  z < x + i_line_thickness && b_ok && (i_line_offset >= 0);
714                  z++ )
715             {
716                 if( p_next_glyph && ( z >= i_extra ) )
717                 {
718                     int i_row = i_line_offset + p_next_glyph->top + y;
719
720                     if( ( p_next_glyph->bitmap.rows > i_row ) &&
721                         p_next_glyph->bitmap.buffer[p_next_glyph->bitmap.width * i_row + z-i_extra] )
722                     {
723                         b_ok = false;
724                     }
725                 }
726                 else if ((z > 0 ) && (z < p_this_glyph->bitmap.width))
727                 {
728                     int i_row = i_line_offset + p_this_glyph->top + y;
729
730                     if( ( p_this_glyph->bitmap.rows > i_row ) &&
731                         p_this_glyph->bitmap.buffer[p_this_glyph->bitmap.width * i_row + z] )
732                     {
733                         b_ok = false;
734                     }
735                 }
736             }
737
738             if( b_ok )
739             {
740                 p_dst_y[i_offset+x] = (i_y * 255) >> 8;
741                 p_dst_u[i_offset+x] = i_u;
742                 p_dst_v[i_offset+x] = i_v;
743                 p_dst_a[i_offset+x] = 255;
744             }
745         }
746         i_offset += i_pitch;
747     }
748 }
749
750 static void DrawBlack( line_desc_t *p_line, int i_width, subpicture_region_t *p_region, int xoffset, int yoffset )
751 {
752     uint8_t *p_dst = p_region->p_picture->A_PIXELS;
753     int i_pitch = p_region->p_picture->A_PITCH;
754     int y;
755
756     for( ; p_line != NULL; p_line = p_line->p_next )
757     {
758         int i_glyph_tmax=0, i = 0;
759         int i_bitmap_offset, i_offset, i_align_offset = 0;
760         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
761         {
762             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
763             i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
764         }
765
766         if( p_line->i_width < i_width )
767         {
768             if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
769             {
770                 i_align_offset = i_width - p_line->i_width;
771             }
772             else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
773             {
774                 i_align_offset = ( i_width - p_line->i_width ) / 2;
775             }
776         }
777
778         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
779         {
780             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
781
782             i_offset = ( p_line->p_glyph_pos[ i ].y +
783                 i_glyph_tmax - p_glyph->top + 3 + yoffset ) *
784                 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
785                 i_align_offset +xoffset;
786
787             for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
788             {
789                 for( int x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
790                 {
791                     if( p_glyph->bitmap.buffer[i_bitmap_offset] )
792                         if( p_dst[i_offset+x] <
793                             ((int)p_glyph->bitmap.buffer[i_bitmap_offset]) )
794                             p_dst[i_offset+x] =
795                                 ((int)p_glyph->bitmap.buffer[i_bitmap_offset]);
796                 }
797                 i_offset += i_pitch;
798             }
799         }
800     }
801 }
802
803 /*****************************************************************************
804  * RenderYUVA: place string in picture
805  *****************************************************************************
806  * This function merges the previously rendered freetype glyphs into a picture
807  *****************************************************************************/
808 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
809                    line_desc_t *p_line, int i_width, int i_height )
810 {
811     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
812     video_format_t fmt;
813     int i, y, i_pitch, i_alpha;
814     uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
815
816     if( i_width == 0 || i_height == 0 )
817         return VLC_SUCCESS;
818
819     /* Create a new subpicture region */
820     memset( &fmt, 0, sizeof(video_format_t) );
821     fmt.i_chroma = VLC_CODEC_YUVA;
822     fmt.i_width = fmt.i_visible_width = i_width + 6;
823     fmt.i_height = fmt.i_visible_height = i_height + 6;
824     if( p_region->fmt.i_visible_width > 0 )
825         fmt.i_visible_width = p_region->fmt.i_visible_width;
826     if( p_region->fmt.i_visible_height > 0 )
827         fmt.i_visible_height = p_region->fmt.i_visible_height;
828     fmt.i_x_offset = fmt.i_y_offset = 0;
829     fmt.i_sar_num = 1;
830     fmt.i_sar_den = 1;
831
832     p_region->p_picture = picture_NewFromFormat( &fmt );
833     if( !p_region->p_picture )
834         return VLC_EGENERIC;
835     p_region->fmt = fmt;
836
837     /* Calculate text color components */
838     YUVFromRGB( (p_line->i_red   << 16) |
839                 (p_line->i_green <<  8) |
840                 (p_line->i_blue       ),
841                 &i_y, &i_u, &i_v);
842     i_alpha = p_line->i_alpha;
843
844     p_dst_y = p_region->p_picture->Y_PIXELS;
845     p_dst_u = p_region->p_picture->U_PIXELS;
846     p_dst_v = p_region->p_picture->V_PIXELS;
847     p_dst_a = p_region->p_picture->A_PIXELS;
848     i_pitch = p_region->p_picture->A_PITCH;
849
850     /* Initialize the region pixels */
851     if( p_filter->p_sys->i_effect != EFFECT_BACKGROUND )
852     {
853         memset( p_dst_y, 0x00, i_pitch * p_region->fmt.i_height );
854         memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
855         memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
856         memset( p_dst_a, 0, i_pitch * p_region->fmt.i_height );
857     }
858     else
859     {
860         memset( p_dst_y, 0x0, i_pitch * p_region->fmt.i_height );
861         memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
862         memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
863         memset( p_dst_a, 0x80, i_pitch * p_region->fmt.i_height );
864     }
865     if( p_filter->p_sys->i_effect == EFFECT_OUTLINE ||
866         p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
867     {
868         DrawBlack( p_line, i_width, p_region,  0,  0);
869         DrawBlack( p_line, i_width, p_region, -1,  0);
870         DrawBlack( p_line, i_width, p_region,  0, -1);
871         DrawBlack( p_line, i_width, p_region,  1,  0);
872         DrawBlack( p_line, i_width, p_region,  0,  1);
873     }
874
875     if( p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
876     {
877         DrawBlack( p_line, i_width, p_region, -1, -1);
878         DrawBlack( p_line, i_width, p_region, -1,  1);
879         DrawBlack( p_line, i_width, p_region,  1, -1);
880         DrawBlack( p_line, i_width, p_region,  1,  1);
881
882         DrawBlack( p_line, i_width, p_region, -2,  0);
883         DrawBlack( p_line, i_width, p_region,  0, -2);
884         DrawBlack( p_line, i_width, p_region,  2,  0);
885         DrawBlack( p_line, i_width, p_region,  0,  2);
886
887         DrawBlack( p_line, i_width, p_region, -2, -2);
888         DrawBlack( p_line, i_width, p_region, -2,  2);
889         DrawBlack( p_line, i_width, p_region,  2, -2);
890         DrawBlack( p_line, i_width, p_region,  2,  2);
891
892         DrawBlack( p_line, i_width, p_region, -3,  0);
893         DrawBlack( p_line, i_width, p_region,  0, -3);
894         DrawBlack( p_line, i_width, p_region,  3,  0);
895         DrawBlack( p_line, i_width, p_region,  0,  3);
896     }
897
898     for( ; p_line != NULL; p_line = p_line->p_next )
899     {
900         int i_glyph_tmax = 0;
901         int i_bitmap_offset, i_offset, i_align_offset = 0;
902         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
903         {
904             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
905             i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
906         }
907
908         if( p_line->i_width < i_width )
909         {
910             if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
911             {
912                 i_align_offset = i_width - p_line->i_width;
913             }
914             else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
915             {
916                 i_align_offset = ( i_width - p_line->i_width ) / 2;
917             }
918         }
919
920         for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
921         {
922             FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
923
924             i_offset = ( p_line->p_glyph_pos[ i ].y +
925                 i_glyph_tmax - p_glyph->top + 3 ) *
926                 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
927                 i_align_offset;
928
929             if( p_line->b_new_color_mode )
930             {
931                 /* Every glyph can (and in fact must) have its own color */
932                 YUVFromRGB( p_line->p_fg_rgb[ i ], &i_y, &i_u, &i_v );
933             }
934
935             for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
936             {
937                 for( int x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
938                 {
939                     uint8_t i_y_local = i_y;
940                     uint8_t i_u_local = i_u;
941                     uint8_t i_v_local = i_v;
942
943                     if( p_line->p_fg_bg_ratio != 0x00 )
944                     {
945                         int i_split = p_glyph->bitmap.width *
946                                       p_line->p_fg_bg_ratio[ i ] / 0x7f;
947
948                         if( x > i_split )
949                         {
950                             YUVFromRGB( p_line->p_bg_rgb[ i ],
951                                         &i_y_local, &i_u_local, &i_v_local );
952                         }
953                     }
954
955                     if( p_glyph->bitmap.buffer[i_bitmap_offset] )
956                     {
957                         p_dst_y[i_offset+x] = ((p_dst_y[i_offset+x] *(255-(int)p_glyph->bitmap.buffer[i_bitmap_offset])) +
958                                               i_y * ((int)p_glyph->bitmap.buffer[i_bitmap_offset])) >> 8;
959
960                         p_dst_u[i_offset+x] = i_u;
961                         p_dst_v[i_offset+x] = i_v;
962
963                         if( p_filter->p_sys->i_effect == EFFECT_BACKGROUND )
964                             p_dst_a[i_offset+x] = 0xff;
965                     }
966                 }
967                 i_offset += i_pitch;
968             }
969
970             if( p_line->pi_underline_thickness[ i ] )
971             {
972                 UnderlineGlyphYUVA( p_line->pi_underline_thickness[ i ],
973                                     p_line->pi_underline_offset[ i ],
974                                    (p_line->pp_glyphs[i+1] && (p_line->pi_underline_thickness[ i + 1] > 0)),
975                                     p_line->pp_glyphs[i], &(p_line->p_glyph_pos[i]),
976                                     p_line->pp_glyphs[i+1], &(p_line->p_glyph_pos[i+1]),
977                                     i_glyph_tmax, i_align_offset,
978                                     i_y, i_u, i_v,
979                                     p_region);
980             }
981         }
982     }
983
984     /* Apply the alpha setting */
985     for( i = 0; i < (int)fmt.i_height * i_pitch; i++ )
986         p_dst_a[i] = p_dst_a[i] * (255 - i_alpha) / 255;
987
988     return VLC_SUCCESS;
989 }
990
991 static ft_style_t *CreateStyle( char *psz_fontname, int i_font_size,
992         uint32_t i_font_color, uint32_t i_karaoke_bg_color, bool b_bold,
993         bool b_italic, bool b_uline, bool b_through )
994 {
995     ft_style_t *p_style = malloc( sizeof( *p_style ));
996     if( !p_style )
997         return NULL;
998
999     p_style->i_font_size        = i_font_size;
1000     p_style->i_font_color       = i_font_color;
1001     p_style->i_karaoke_bg_color = i_karaoke_bg_color;
1002     p_style->b_italic           = b_italic;
1003     p_style->b_bold             = b_bold;
1004     p_style->b_underline        = b_uline;
1005     p_style->b_through          = b_through;
1006
1007     p_style->psz_fontname = strdup( psz_fontname );
1008
1009     return p_style;
1010 }
1011
1012 static void DeleteStyle( ft_style_t *p_style )
1013 {
1014     if( !p_style )
1015         return;
1016
1017     free( p_style->psz_fontname );
1018     free( p_style );
1019 }
1020
1021 static bool StyleEquals( ft_style_t *s1, ft_style_t *s2 )
1022 {
1023     if( !s1 || !s2 )
1024         return false;
1025     if( s1 == s2 )
1026         return true;
1027
1028     if(( s1->i_font_size  == s2->i_font_size ) &&
1029        ( s1->i_font_color == s2->i_font_color ) &&
1030        ( s1->b_italic     == s2->b_italic ) &&
1031        ( s1->b_through    == s2->b_through ) &&
1032        ( s1->b_bold       == s2->b_bold ) &&
1033        ( s1->b_underline  == s2->b_underline ) &&
1034        ( !strcmp( s1->psz_fontname, s2->psz_fontname )))
1035     {
1036         return true;
1037     }
1038     return false;
1039 }
1040
1041 static void IconvText( filter_t *p_filter, const char *psz_string,
1042                        size_t *i_string_length, uint32_t *psz_unicode )
1043 {
1044     *i_string_length = 0;
1045     if( psz_unicode == NULL )
1046         return;
1047
1048     size_t i_length;
1049     uint32_t *psz_tmp =
1050 #if defined(WORDS_BIGENDIAN)
1051             ToCharset( "UCS-4BE", psz_string, &i_length );
1052 #else
1053             ToCharset( "UCS-4LE", psz_string, &i_length );
1054 #endif
1055     if( !psz_tmp )
1056     {
1057         msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1058         return;
1059     }
1060     memcpy( psz_unicode, psz_tmp, i_length );
1061     *i_string_length = i_length / 4;
1062
1063     free( psz_tmp );
1064 }
1065
1066 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1067                      uint32_t i_color, uint32_t i_karaoke_bg_color )
1068 {
1069     font_stack_t *p_new;
1070
1071     if( !p_font )
1072         return VLC_EGENERIC;
1073
1074     p_new = malloc( sizeof( font_stack_t ) );
1075     if( ! p_new )
1076         return VLC_ENOMEM;
1077
1078     p_new->p_next = NULL;
1079
1080     if( psz_name )
1081         p_new->psz_name = strdup( psz_name );
1082     else
1083         p_new->psz_name = NULL;
1084
1085     p_new->i_size              = i_size;
1086     p_new->i_color             = i_color;
1087     p_new->i_karaoke_bg_color  = i_karaoke_bg_color;
1088
1089     if( !*p_font )
1090     {
1091         *p_font = p_new;
1092     }
1093     else
1094     {
1095         font_stack_t *p_last;
1096
1097         for( p_last = *p_font;
1098              p_last->p_next;
1099              p_last = p_last->p_next )
1100         ;
1101
1102         p_last->p_next = p_new;
1103     }
1104     return VLC_SUCCESS;
1105 }
1106
1107 static int PopFont( font_stack_t **p_font )
1108 {
1109     font_stack_t *p_last, *p_next_to_last;
1110
1111     if( !p_font || !*p_font )
1112         return VLC_EGENERIC;
1113
1114     p_next_to_last = NULL;
1115     for( p_last = *p_font;
1116          p_last->p_next;
1117          p_last = p_last->p_next )
1118     {
1119         p_next_to_last = p_last;
1120     }
1121
1122     if( p_next_to_last )
1123         p_next_to_last->p_next = NULL;
1124     else
1125         *p_font = NULL;
1126
1127     free( p_last->psz_name );
1128     free( p_last );
1129
1130     return VLC_SUCCESS;
1131 }
1132
1133 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1134                      uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1135 {
1136     font_stack_t *p_last;
1137
1138     if( !p_font || !*p_font )
1139         return VLC_EGENERIC;
1140
1141     for( p_last=*p_font;
1142          p_last->p_next;
1143          p_last=p_last->p_next )
1144     ;
1145
1146     *psz_name            = p_last->psz_name;
1147     *i_size              = p_last->i_size;
1148     *i_color             = p_last->i_color;
1149     *i_karaoke_bg_color  = p_last->i_karaoke_bg_color;
1150
1151     return VLC_SUCCESS;
1152 }
1153
1154 static const struct {
1155     const char *psz_name;
1156     uint32_t   i_value;
1157 } p_html_colors[] = {
1158     /* Official html colors */
1159     { "Aqua",    0x00FFFF },
1160     { "Black",   0x000000 },
1161     { "Blue",    0x0000FF },
1162     { "Fuchsia", 0xFF00FF },
1163     { "Gray",    0x808080 },
1164     { "Green",   0x008000 },
1165     { "Lime",    0x00FF00 },
1166     { "Maroon",  0x800000 },
1167     { "Navy",    0x000080 },
1168     { "Olive",   0x808000 },
1169     { "Purple",  0x800080 },
1170     { "Red",     0xFF0000 },
1171     { "Silver",  0xC0C0C0 },
1172     { "Teal",    0x008080 },
1173     { "White",   0xFFFFFF },
1174     { "Yellow",  0xFFFF00 },
1175
1176     /* Common ones */
1177     { "AliceBlue", 0xF0F8FF },
1178     { "AntiqueWhite", 0xFAEBD7 },
1179     { "Aqua", 0x00FFFF },
1180     { "Aquamarine", 0x7FFFD4 },
1181     { "Azure", 0xF0FFFF },
1182     { "Beige", 0xF5F5DC },
1183     { "Bisque", 0xFFE4C4 },
1184     { "Black", 0x000000 },
1185     { "BlanchedAlmond", 0xFFEBCD },
1186     { "Blue", 0x0000FF },
1187     { "BlueViolet", 0x8A2BE2 },
1188     { "Brown", 0xA52A2A },
1189     { "BurlyWood", 0xDEB887 },
1190     { "CadetBlue", 0x5F9EA0 },
1191     { "Chartreuse", 0x7FFF00 },
1192     { "Chocolate", 0xD2691E },
1193     { "Coral", 0xFF7F50 },
1194     { "CornflowerBlue", 0x6495ED },
1195     { "Cornsilk", 0xFFF8DC },
1196     { "Crimson", 0xDC143C },
1197     { "Cyan", 0x00FFFF },
1198     { "DarkBlue", 0x00008B },
1199     { "DarkCyan", 0x008B8B },
1200     { "DarkGoldenRod", 0xB8860B },
1201     { "DarkGray", 0xA9A9A9 },
1202     { "DarkGrey", 0xA9A9A9 },
1203     { "DarkGreen", 0x006400 },
1204     { "DarkKhaki", 0xBDB76B },
1205     { "DarkMagenta", 0x8B008B },
1206     { "DarkOliveGreen", 0x556B2F },
1207     { "Darkorange", 0xFF8C00 },
1208     { "DarkOrchid", 0x9932CC },
1209     { "DarkRed", 0x8B0000 },
1210     { "DarkSalmon", 0xE9967A },
1211     { "DarkSeaGreen", 0x8FBC8F },
1212     { "DarkSlateBlue", 0x483D8B },
1213     { "DarkSlateGray", 0x2F4F4F },
1214     { "DarkSlateGrey", 0x2F4F4F },
1215     { "DarkTurquoise", 0x00CED1 },
1216     { "DarkViolet", 0x9400D3 },
1217     { "DeepPink", 0xFF1493 },
1218     { "DeepSkyBlue", 0x00BFFF },
1219     { "DimGray", 0x696969 },
1220     { "DimGrey", 0x696969 },
1221     { "DodgerBlue", 0x1E90FF },
1222     { "FireBrick", 0xB22222 },
1223     { "FloralWhite", 0xFFFAF0 },
1224     { "ForestGreen", 0x228B22 },
1225     { "Fuchsia", 0xFF00FF },
1226     { "Gainsboro", 0xDCDCDC },
1227     { "GhostWhite", 0xF8F8FF },
1228     { "Gold", 0xFFD700 },
1229     { "GoldenRod", 0xDAA520 },
1230     { "Gray", 0x808080 },
1231     { "Grey", 0x808080 },
1232     { "Green", 0x008000 },
1233     { "GreenYellow", 0xADFF2F },
1234     { "HoneyDew", 0xF0FFF0 },
1235     { "HotPink", 0xFF69B4 },
1236     { "IndianRed", 0xCD5C5C },
1237     { "Indigo", 0x4B0082 },
1238     { "Ivory", 0xFFFFF0 },
1239     { "Khaki", 0xF0E68C },
1240     { "Lavender", 0xE6E6FA },
1241     { "LavenderBlush", 0xFFF0F5 },
1242     { "LawnGreen", 0x7CFC00 },
1243     { "LemonChiffon", 0xFFFACD },
1244     { "LightBlue", 0xADD8E6 },
1245     { "LightCoral", 0xF08080 },
1246     { "LightCyan", 0xE0FFFF },
1247     { "LightGoldenRodYellow", 0xFAFAD2 },
1248     { "LightGray", 0xD3D3D3 },
1249     { "LightGrey", 0xD3D3D3 },
1250     { "LightGreen", 0x90EE90 },
1251     { "LightPink", 0xFFB6C1 },
1252     { "LightSalmon", 0xFFA07A },
1253     { "LightSeaGreen", 0x20B2AA },
1254     { "LightSkyBlue", 0x87CEFA },
1255     { "LightSlateGray", 0x778899 },
1256     { "LightSlateGrey", 0x778899 },
1257     { "LightSteelBlue", 0xB0C4DE },
1258     { "LightYellow", 0xFFFFE0 },
1259     { "Lime", 0x00FF00 },
1260     { "LimeGreen", 0x32CD32 },
1261     { "Linen", 0xFAF0E6 },
1262     { "Magenta", 0xFF00FF },
1263     { "Maroon", 0x800000 },
1264     { "MediumAquaMarine", 0x66CDAA },
1265     { "MediumBlue", 0x0000CD },
1266     { "MediumOrchid", 0xBA55D3 },
1267     { "MediumPurple", 0x9370D8 },
1268     { "MediumSeaGreen", 0x3CB371 },
1269     { "MediumSlateBlue", 0x7B68EE },
1270     { "MediumSpringGreen", 0x00FA9A },
1271     { "MediumTurquoise", 0x48D1CC },
1272     { "MediumVioletRed", 0xC71585 },
1273     { "MidnightBlue", 0x191970 },
1274     { "MintCream", 0xF5FFFA },
1275     { "MistyRose", 0xFFE4E1 },
1276     { "Moccasin", 0xFFE4B5 },
1277     { "NavajoWhite", 0xFFDEAD },
1278     { "Navy", 0x000080 },
1279     { "OldLace", 0xFDF5E6 },
1280     { "Olive", 0x808000 },
1281     { "OliveDrab", 0x6B8E23 },
1282     { "Orange", 0xFFA500 },
1283     { "OrangeRed", 0xFF4500 },
1284     { "Orchid", 0xDA70D6 },
1285     { "PaleGoldenRod", 0xEEE8AA },
1286     { "PaleGreen", 0x98FB98 },
1287     { "PaleTurquoise", 0xAFEEEE },
1288     { "PaleVioletRed", 0xD87093 },
1289     { "PapayaWhip", 0xFFEFD5 },
1290     { "PeachPuff", 0xFFDAB9 },
1291     { "Peru", 0xCD853F },
1292     { "Pink", 0xFFC0CB },
1293     { "Plum", 0xDDA0DD },
1294     { "PowderBlue", 0xB0E0E6 },
1295     { "Purple", 0x800080 },
1296     { "Red", 0xFF0000 },
1297     { "RosyBrown", 0xBC8F8F },
1298     { "RoyalBlue", 0x4169E1 },
1299     { "SaddleBrown", 0x8B4513 },
1300     { "Salmon", 0xFA8072 },
1301     { "SandyBrown", 0xF4A460 },
1302     { "SeaGreen", 0x2E8B57 },
1303     { "SeaShell", 0xFFF5EE },
1304     { "Sienna", 0xA0522D },
1305     { "Silver", 0xC0C0C0 },
1306     { "SkyBlue", 0x87CEEB },
1307     { "SlateBlue", 0x6A5ACD },
1308     { "SlateGray", 0x708090 },
1309     { "SlateGrey", 0x708090 },
1310     { "Snow", 0xFFFAFA },
1311     { "SpringGreen", 0x00FF7F },
1312     { "SteelBlue", 0x4682B4 },
1313     { "Tan", 0xD2B48C },
1314     { "Teal", 0x008080 },
1315     { "Thistle", 0xD8BFD8 },
1316     { "Tomato", 0xFF6347 },
1317     { "Turquoise", 0x40E0D0 },
1318     { "Violet", 0xEE82EE },
1319     { "Wheat", 0xF5DEB3 },
1320     { "White", 0xFFFFFF },
1321     { "WhiteSmoke", 0xF5F5F5 },
1322     { "Yellow", 0xFFFF00 },
1323     { "YellowGreen", 0x9ACD32 },
1324
1325     { NULL, 0 }
1326 };
1327
1328 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1329                                   font_stack_t **p_fonts, int i_scale )
1330 {
1331     int        rv;
1332     char      *psz_fontname = NULL;
1333     uint32_t   i_font_color = 0xffffff;
1334     int        i_font_alpha = 0;
1335     uint32_t   i_karaoke_bg_color = 0x00ffffff;
1336     int        i_font_size  = 24;
1337
1338     /* Default all attributes to the top font in the stack -- in case not
1339      * all attributes are specified in the sub-font
1340      */
1341     if( VLC_SUCCESS == PeekFont( p_fonts,
1342                                  &psz_fontname,
1343                                  &i_font_size,
1344                                  &i_font_color,
1345                                  &i_karaoke_bg_color ))
1346     {
1347         psz_fontname = strdup( psz_fontname );
1348         i_font_size = i_font_size * 1000 / i_scale;
1349     }
1350     i_font_alpha = (i_font_color >> 24) & 0xff;
1351     i_font_color &= 0x00ffffff;
1352
1353     const char *name, *value;
1354     while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1355     {
1356         if( !strcasecmp( "face", name ) )
1357         {
1358             free( psz_fontname );
1359             psz_fontname = strdup( value );
1360         }
1361         else if( !strcasecmp( "size", name ) )
1362         {
1363             if( ( *value == '+' ) || ( *value == '-' ) )
1364             {
1365                 int i_value = atoi( value );
1366
1367                 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1368                     i_font_size += ( i_value * i_font_size ) / 10;
1369                 else if( i_value < -5 )
1370                     i_font_size = - i_value;
1371                 else if( i_value > 5 )
1372                     i_font_size = i_value;
1373             }
1374             else
1375                 i_font_size = atoi( value );
1376         }
1377         else if( !strcasecmp( "color", name ) )
1378         {
1379             if( value[0] == '#' )
1380             {
1381                 i_font_color = strtol( value + 1, NULL, 16 );
1382                 i_font_color &= 0x00ffffff;
1383             }
1384             else
1385             {
1386                 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1387                 {
1388                     if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1389                     {
1390                         i_font_color = p_html_colors[i].i_value;
1391                         break;
1392                     }
1393                 }
1394             }
1395         }
1396         else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1397         {
1398             i_font_alpha = strtol( value + 1, NULL, 16 );
1399             i_font_alpha &= 0xff;
1400         }
1401     }
1402     rv = PushFont( p_fonts,
1403                    psz_fontname,
1404                    i_font_size * i_scale / 1000,
1405                    (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1406                    i_karaoke_bg_color );
1407
1408     free( psz_fontname );
1409
1410     return rv;
1411 }
1412
1413 static void SetKaraokeLen( uint32_t i_runs, uint32_t *pi_run_lengths,
1414                            uint32_t i_k_runs, uint32_t *pi_k_run_lengths )
1415 {
1416     /* Karaoke tags _PRECEDE_ the text they specify a duration
1417      * for, therefore we are working out the length for the
1418      * previous tag, and first time through we have nothing
1419      */
1420     if( pi_k_run_lengths )
1421     {
1422         int i_chars = 0;
1423         uint32_t i;
1424
1425         /* Work out how many characters are presently in the string
1426          */
1427         for( i = 0; i < i_runs; i++ )
1428             i_chars += pi_run_lengths[ i ];
1429
1430         /* Subtract away those we've already allocated to other
1431          * karaoke tags
1432          */
1433         for( i = 0; i < i_k_runs; i++ )
1434             i_chars -= pi_k_run_lengths[ i ];
1435
1436         pi_k_run_lengths[ i_k_runs - 1 ] = i_chars;
1437     }
1438 }
1439
1440 static void SetupKaraoke( xml_reader_t *p_xml_reader, uint32_t *pi_k_runs,
1441                           uint32_t **ppi_k_run_lengths,
1442                           uint32_t **ppi_k_durations )
1443 {
1444     const char *name, *value;
1445
1446     while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1447     {
1448         if( !strcasecmp( "t", name ) )
1449         {
1450             if( ppi_k_durations && ppi_k_run_lengths )
1451             {
1452                 (*pi_k_runs)++;
1453
1454                 if( *ppi_k_durations )
1455                 {
1456                     *ppi_k_durations = realloc_or_free( *ppi_k_durations,
1457                                  *pi_k_runs * sizeof( uint32_t ) );
1458                 }
1459                 else if( *pi_k_runs == 1 )
1460                 {
1461                     *ppi_k_durations = (uint32_t *)
1462                         malloc( *pi_k_runs * sizeof( uint32_t ) );
1463                 }
1464
1465                 if( *ppi_k_run_lengths )
1466                 {
1467                     *ppi_k_run_lengths = realloc_or_free( *ppi_k_run_lengths,
1468                                  *pi_k_runs * sizeof( uint32_t ) );
1469                 }
1470                 else if( *pi_k_runs == 1 )
1471                 {
1472                     *ppi_k_run_lengths = (uint32_t *)
1473                         malloc( *pi_k_runs * sizeof( uint32_t ) );
1474                 }
1475                 if( *ppi_k_durations )
1476                     (*ppi_k_durations)[ *pi_k_runs - 1 ] = atoi( value );
1477
1478                 if( *ppi_k_run_lengths )
1479                     (*ppi_k_run_lengths)[ *pi_k_runs - 1 ] = 0;
1480             }
1481         }
1482     }
1483 }
1484
1485 /* Turn any multiple-whitespaces into single spaces */
1486 static void HandleWhiteSpace( char *psz_node )
1487 {
1488     char *s = strpbrk( psz_node, "\t\r\n " );
1489     while( s )
1490     {
1491         int i_whitespace = strspn( s, "\t\r\n " );
1492
1493         if( i_whitespace > 1 )
1494             memmove( &s[1],
1495                      &s[i_whitespace],
1496                      strlen( s ) - i_whitespace + 1 );
1497         *s++ = ' ';
1498
1499         s = strpbrk( s, "\t\r\n " );
1500     }
1501 }
1502
1503
1504 static ft_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1505                                           font_stack_t **p_fonts,
1506                                           bool b_bold, bool b_italic,
1507                                           bool b_uline, bool b_through )
1508 {
1509     char       *psz_fontname = NULL;
1510     uint32_t    i_font_color = p_sys->i_font_color & 0x00ffffff;
1511     uint32_t    i_karaoke_bg_color = i_font_color;
1512     int         i_font_size  = p_sys->i_font_size;
1513
1514     if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1515                   &i_font_color, &i_karaoke_bg_color ) )
1516         return NULL;
1517
1518     return CreateStyle( psz_fontname, i_font_size, i_font_color,
1519                         i_karaoke_bg_color, b_bold, b_italic, b_uline, b_through );
1520 }
1521
1522 static int RenderTag( filter_t *p_filter, FT_Face p_face, int i_font_color,
1523                       bool b_uline, bool b_through, bool b_bold,
1524                       bool b_italic, int i_karaoke_bgcolor,
1525                       line_desc_t *p_line, uint32_t *psz_unicode,
1526                       int *pi_pen_x, int i_pen_y, int *pi_start,
1527                       FT_Vector *p_result )
1528 {
1529     FT_BBox      line;
1530     int          i_yMin, i_yMax;
1531     int          i;
1532     bool   b_first_on_line = true;
1533
1534     int          i_previous = 0;
1535     int          i_pen_x_start = *pi_pen_x;
1536
1537     uint32_t *psz_unicode_start = psz_unicode;
1538
1539     line.xMin = line.xMax = line.yMin = line.yMax = 0;
1540
1541     /* Account for part of line already in position */
1542     for( i = 0; i<*pi_start; i++ )
1543     {
1544         FT_BBox glyph_size;
1545
1546         FT_Glyph_Get_CBox( (FT_Glyph) p_line->pp_glyphs[ i ],
1547                             ft_glyph_bbox_pixels, &glyph_size );
1548
1549         line.xMax = p_line->p_glyph_pos[ i ].x + glyph_size.xMax -
1550             glyph_size.xMin + p_line->pp_glyphs[ i ]->left;
1551         line.yMax = __MAX( line.yMax, glyph_size.yMax );
1552         line.yMin = __MIN( line.yMin, glyph_size.yMin );
1553     }
1554     i_yMin = line.yMin;
1555     i_yMax = line.yMax;
1556
1557     if( line.xMax > 0 )
1558         b_first_on_line = false;
1559
1560     while( *psz_unicode && ( *psz_unicode != '\n' ) )
1561     {
1562         FT_BBox glyph_size;
1563         FT_Glyph tmp_glyph;
1564         int i_error;
1565
1566         int i_glyph_index = FT_Get_Char_Index( p_face, *psz_unicode++ );
1567         if( FT_HAS_KERNING( p_face ) && i_glyph_index
1568             && i_previous )
1569         {
1570             FT_Vector delta;
1571             FT_Get_Kerning( p_face, i_previous, i_glyph_index,
1572                             ft_kerning_default, &delta );
1573             *pi_pen_x += delta.x >> 6;
1574         }
1575         p_line->p_glyph_pos[ i ].x = *pi_pen_x;
1576         p_line->p_glyph_pos[ i ].y = i_pen_y;
1577
1578         i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT );
1579         if( i_error )
1580         {
1581             i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT );
1582             if( i_error )
1583             {
1584                 msg_Err( p_filter,
1585                        "unable to render text FT_Load_Glyph returned %d", i_error );
1586                 p_line->pp_glyphs[ i ] = NULL;
1587                 return VLC_EGENERIC;
1588             }
1589         }
1590
1591         /* Do synthetic styling now that Freetype supports it;
1592          * ie. if the font we have loaded is NOT already in the
1593          * style that the tags want, then switch it on; if they
1594          * are then don't. */
1595         if (b_bold && !( p_face->style_flags & FT_STYLE_FLAG_BOLD ))
1596             FT_GlyphSlot_Embolden( p_face->glyph );
1597         if (b_italic && !( p_face->style_flags & FT_STYLE_FLAG_ITALIC ))
1598             FT_GlyphSlot_Oblique( p_face->glyph );
1599
1600         i_error = FT_Get_Glyph( p_face->glyph, &tmp_glyph );
1601         if( i_error )
1602         {
1603             msg_Err( p_filter,
1604                     "unable to render text FT_Get_Glyph returned %d", i_error );
1605             p_line->pp_glyphs[ i ] = NULL;
1606             return VLC_EGENERIC;
1607         }
1608         FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
1609         i_error = FT_Glyph_To_Bitmap( &tmp_glyph, FT_RENDER_MODE_NORMAL, 0, 1);
1610         if( i_error )
1611         {
1612             FT_Done_Glyph( tmp_glyph );
1613             continue;
1614         }
1615         if( b_uline || b_through )
1616         {
1617             float aOffset = FT_FLOOR(FT_MulFix(p_face->underline_position,
1618                                                p_face->size->metrics.y_scale));
1619             float aSize = FT_CEIL(FT_MulFix(p_face->underline_thickness,
1620                                             p_face->size->metrics.y_scale));
1621
1622             p_line->pi_underline_offset[ i ]  =
1623                                        ( aOffset < 0 ) ? -aOffset : aOffset;
1624             p_line->pi_underline_thickness[ i ] =
1625                                        ( aSize < 0 ) ? -aSize   : aSize;
1626             if (b_through)
1627             {
1628                 /* Move the baseline to make it strikethrough instead of
1629                  * underline. That means that strikethrough takes precedence
1630                  */
1631                 float aDescent = FT_FLOOR(FT_MulFix(p_face->descender*2,
1632                                                     p_face->size->metrics.y_scale));
1633
1634                 p_line->pi_underline_offset[ i ]  -=
1635                                        ( aDescent < 0 ) ? -aDescent : aDescent;
1636             }
1637         }
1638
1639         p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
1640         p_line->p_fg_rgb[ i ] = i_font_color & 0x00ffffff;
1641         p_line->p_bg_rgb[ i ] = i_karaoke_bgcolor & 0x00ffffff;
1642         p_line->p_fg_bg_ratio[ i ] = 0x00;
1643
1644         line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
1645                     glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
1646         if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
1647         {
1648             for( ; i >= *pi_start; i-- )
1649                 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
1650             i = *pi_start;
1651
1652             while( psz_unicode > psz_unicode_start && *psz_unicode != ' ' )
1653             {
1654                 psz_unicode--;
1655             }
1656             if( psz_unicode == psz_unicode_start )
1657             {
1658                 if( b_first_on_line )
1659                 {
1660                     msg_Warn( p_filter, "unbreakable string" );
1661                     p_line->pp_glyphs[ i ] = NULL;
1662                     return VLC_EGENERIC;
1663                 }
1664                 *pi_pen_x = i_pen_x_start;
1665
1666                 p_line->i_width = line.xMax;
1667                 p_line->i_height = __MAX( p_line->i_height,
1668                                           p_face->size->metrics.height >> 6 );
1669                 p_line->pp_glyphs[ i ] = NULL;
1670
1671                 p_result->x = __MAX( p_result->x, line.xMax );
1672                 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1673                                                          i_yMax - i_yMin ) );
1674                 return VLC_SUCCESS;
1675             }
1676             else
1677             {
1678                 *psz_unicode = '\n';
1679             }
1680             psz_unicode = psz_unicode_start;
1681             *pi_pen_x = i_pen_x_start;
1682             i_previous = 0;
1683
1684             line.yMax = i_yMax;
1685             line.yMin = i_yMin;
1686
1687             continue;
1688         }
1689         line.yMax = __MAX( line.yMax, glyph_size.yMax );
1690         line.yMin = __MIN( line.yMin, glyph_size.yMin );
1691
1692         i_previous = i_glyph_index;
1693         *pi_pen_x += p_face->glyph->advance.x >> 6;
1694         i++;
1695     }
1696     p_line->i_width = line.xMax;
1697     p_line->i_height = __MAX( p_line->i_height,
1698                               p_face->size->metrics.height >> 6 );
1699     p_line->pp_glyphs[ i ] = NULL;
1700
1701     p_result->x = __MAX( p_result->x, line.xMax );
1702     p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1703                          line.yMax - line.yMin ) );
1704
1705     *pi_start = i;
1706
1707     /* Get rid of any text processed - if necessary repositioning
1708      * at the start of a new line of text
1709      */
1710     if( !*psz_unicode )
1711     {
1712         *psz_unicode_start = '\0';
1713     }
1714     else if( psz_unicode > psz_unicode_start )
1715     {
1716         for( i=0; psz_unicode[ i ]; i++ )
1717             psz_unicode_start[ i ] = psz_unicode[ i ];
1718         psz_unicode_start[ i ] = '\0';
1719     }
1720
1721     return VLC_SUCCESS;
1722 }
1723
1724 static void SetupLine( filter_t *p_filter, const char *psz_text_in,
1725                        uint32_t **ppsz_text_out, uint32_t *pi_runs,
1726                        uint32_t **ppi_run_lengths, ft_style_t ***ppp_styles,
1727                        ft_style_t *p_style )
1728 {
1729     size_t i_string_length;
1730
1731     IconvText( p_filter, psz_text_in, &i_string_length, *ppsz_text_out );
1732     *ppsz_text_out += i_string_length;
1733
1734     if( ppp_styles && ppi_run_lengths )
1735     {
1736         (*pi_runs)++;
1737
1738         /* XXX this logic looks somewhat broken */
1739
1740         if( *ppp_styles )
1741         {
1742             *ppp_styles = realloc_or_free( *ppp_styles,
1743                                           *pi_runs * sizeof( ft_style_t * ) );
1744         }
1745         else if( *pi_runs == 1 )
1746         {
1747             *ppp_styles = malloc( *pi_runs * sizeof( ft_style_t * ) );
1748         }
1749
1750         /* We have just malloc'ed this memory successfully -
1751          * *pi_runs HAS to be within the memory area of *ppp_styles */
1752         if( *ppp_styles )
1753         {
1754             (*ppp_styles)[ *pi_runs - 1 ] = p_style;
1755             p_style = NULL;
1756         }
1757
1758         /* XXX more iffy logic */
1759
1760         if( *ppi_run_lengths )
1761         {
1762             *ppi_run_lengths = realloc_or_free( *ppi_run_lengths,
1763                                               *pi_runs * sizeof( uint32_t ) );
1764         }
1765         else if( *pi_runs == 1 )
1766         {
1767             *ppi_run_lengths = (uint32_t *)
1768                 malloc( *pi_runs * sizeof( uint32_t ) );
1769         }
1770
1771         /* same remarks here */
1772         if( *ppi_run_lengths )
1773         {
1774             (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
1775         }
1776     }
1777     /* If we couldn't use the p_style argument due to memory allocation
1778      * problems above, release it here.
1779      */
1780     DeleteStyle( p_style );
1781 }
1782
1783 static int CheckForEmbeddedFont( filter_sys_t *p_sys, FT_Face *pp_face, ft_style_t *p_style )
1784 {
1785     for( int k = 0; k < p_sys->i_font_attachments; k++ )
1786     {
1787         input_attachment_t *p_attach   = p_sys->pp_font_attachments[k];
1788         int                 i_font_idx = 0;
1789         FT_Face             p_face = NULL;
1790
1791         while( 0 == FT_New_Memory_Face( p_sys->p_library,
1792                                         p_attach->p_data,
1793                                         p_attach->i_data,
1794                                         i_font_idx,
1795                                         &p_face ))
1796         {
1797             if( p_face )
1798             {
1799                 bool match = !strcasecmp( p_face->family_name,
1800                                                 p_style->psz_fontname );
1801
1802                 if( p_face->style_flags & FT_STYLE_FLAG_BOLD )
1803                     match = match && p_style->b_bold;
1804                 else
1805                     match = match && !p_style->b_bold;
1806
1807                 if( p_face->style_flags & FT_STYLE_FLAG_ITALIC )
1808                     match = match && p_style->b_italic;
1809                 else
1810                     match = match && !p_style->b_italic;
1811
1812                 if(  match )
1813                 {
1814                     *pp_face = p_face;
1815                     return VLC_SUCCESS;
1816                 }
1817
1818                 FT_Done_Face( p_face );
1819             }
1820             i_font_idx++;
1821         }
1822     }
1823     return VLC_EGENERIC;
1824 }
1825
1826 static int ProcessNodes( filter_t *p_filter,
1827                          xml_reader_t *p_xml_reader,
1828                          text_style_t *p_font_style,
1829                          uint32_t *psz_text,
1830                          int *pi_len,
1831
1832                          uint32_t *pi_runs,
1833                          uint32_t **ppi_run_lengths,
1834                          ft_style_t * **ppp_styles,
1835
1836                          bool b_karaoke,
1837                          uint32_t *pi_k_runs,
1838                          uint32_t **ppi_k_run_lengths,
1839                          uint32_t **ppi_k_durations )
1840 {
1841     int           rv             = VLC_SUCCESS;
1842     filter_sys_t *p_sys          = p_filter->p_sys;
1843     uint32_t        *psz_text_orig  = psz_text;
1844     font_stack_t *p_fonts        = NULL;
1845     vlc_value_t   val;
1846     int           i_scale        = 1000;
1847
1848     bool b_italic = false;
1849     bool b_bold   = false;
1850     bool b_uline  = false;
1851     bool b_through = false;
1852
1853     if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
1854         i_scale = val.i_int;
1855
1856     if( p_font_style )
1857     {
1858         rv = PushFont( &p_fonts,
1859                p_font_style->psz_fontname,
1860                p_font_style->i_font_size * i_scale / 1000,
1861                (p_font_style->i_font_color & 0xffffff) |
1862                    ((p_font_style->i_font_alpha & 0xff) << 24),
1863                (p_font_style->i_karaoke_background_color & 0xffffff) |
1864                    ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1865
1866         if( p_font_style->i_style_flags & STYLE_BOLD )
1867             b_bold = true;
1868         if( p_font_style->i_style_flags & STYLE_ITALIC )
1869             b_italic = true;
1870         if( p_font_style->i_style_flags & STYLE_UNDERLINE )
1871             b_uline = true;
1872         if( p_font_style->i_style_flags & STYLE_STRIKEOUT )
1873             b_through = true;
1874     }
1875 #ifdef HAVE_STYLES
1876     else
1877     {
1878         rv = PushFont( &p_fonts,
1879                        p_sys->psz_fontfamily,
1880                        p_sys->i_font_size,
1881                        (p_sys->i_font_color & 0xffffff) |
1882                           (((255-p_sys->i_font_opacity) & 0xff) << 24),
1883                        0x00ffffff );
1884     }
1885 #endif
1886
1887     if( rv != VLC_SUCCESS )
1888         return rv;
1889
1890     const char *node;
1891     int type;
1892
1893     while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1894     {
1895         switch ( type )
1896         {
1897             case XML_READER_ENDELEM:
1898                 if( !strcasecmp( "font", node ) )
1899                     PopFont( &p_fonts );
1900                 else if( !strcasecmp( "b", node ) )
1901                     b_bold   = false;
1902                else if( !strcasecmp( "i", node ) )
1903                     b_italic = false;
1904                 else if( !strcasecmp( "u", node ) )
1905                     b_uline  = false;
1906                 else if( !strcasecmp( "s", node ) )
1907                     b_through = false;
1908                 break;
1909
1910             case XML_READER_STARTELEM:
1911                 if( !strcasecmp( "font", node ) )
1912                     rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
1913                 else if( !strcasecmp( "b", node ) )
1914                     b_bold = true;
1915                 else if( !strcasecmp( "i", node ) )
1916                     b_italic = true;
1917                 else if( !strcasecmp( "u", node ) )
1918                     b_uline = true;
1919                 else if( !strcasecmp( "s", node ) )
1920                     b_through = true;
1921                 else if( !strcasecmp( "br", node ) )
1922                 {
1923                     SetupLine( p_filter, "\n", &psz_text,
1924                                pi_runs, ppi_run_lengths, ppp_styles,
1925                                GetStyleFromFontStack( p_sys,
1926                                                       &p_fonts,
1927                                                       b_bold,
1928                                                       b_italic,
1929                                                       b_uline,
1930                                                       b_through) );
1931                 }
1932                 else if( !strcasecmp( "k", node ) )
1933                 {
1934                     /* Only valid in karaoke */
1935                     if( b_karaoke )
1936                     {
1937                         if( *pi_k_runs > 0 )
1938                             SetKaraokeLen( *pi_runs, *ppi_run_lengths,
1939                                            *pi_k_runs, *ppi_k_run_lengths );
1940                         SetupKaraoke( p_xml_reader, pi_k_runs,
1941                                       ppi_k_run_lengths, ppi_k_durations );
1942                     }
1943                 }
1944                 break;
1945
1946             case XML_READER_TEXT:
1947             {
1948                 char *psz_node = strdup( node );
1949                 if( unlikely(!psz_node) )
1950                     break;
1951
1952                 HandleWhiteSpace( psz_node );
1953                 resolve_xml_special_chars( psz_node );
1954
1955                 SetupLine( p_filter, psz_node, &psz_text,
1956                            pi_runs, ppi_run_lengths, ppp_styles,
1957                            GetStyleFromFontStack( p_sys,
1958                                                   &p_fonts,
1959                                                   b_bold,
1960                                                   b_italic,
1961                                                   b_uline,
1962                                                   b_through) );
1963                 free( psz_node );
1964                 break;
1965             }
1966         }
1967         if( rv != VLC_SUCCESS )
1968         {
1969             psz_text = psz_text_orig;
1970             break;
1971         }
1972     }
1973     if( b_karaoke )
1974     {
1975         SetKaraokeLen( *pi_runs, *ppi_run_lengths,
1976                        *pi_k_runs, *ppi_k_run_lengths );
1977     }
1978
1979     *pi_len = psz_text - psz_text_orig;
1980
1981     while( VLC_SUCCESS == PopFont( &p_fonts ) );
1982
1983     return rv;
1984 }
1985
1986
1987 static int ProcessLines( filter_t *p_filter,
1988                          uint32_t *psz_text,
1989                          int i_len,
1990
1991                          uint32_t i_runs,
1992                          uint32_t *pi_run_lengths,
1993                          ft_style_t **pp_styles,
1994                          line_desc_t **pp_lines,
1995
1996                          FT_Vector *p_result,
1997
1998                          bool b_karaoke,
1999                          uint32_t i_k_runs,
2000                          uint32_t *pi_k_run_lengths,
2001                          uint32_t *pi_k_durations )
2002 {
2003     filter_sys_t   *p_sys = p_filter->p_sys;
2004     ft_style_t    **pp_char_styles;
2005     int            *p_new_positions = NULL;
2006     int8_t         *p_levels = NULL;
2007     uint8_t        *pi_karaoke_bar = NULL;
2008     uint32_t        i, j, k;
2009     int             i_prev;
2010
2011     /* Assign each character in the text string its style explicitly, so that
2012      * after the characters have been shuffled around by Fribidi, we can re-apply
2013      * the styles, and to simplify the calculation of runs within a line.
2014      */
2015     pp_char_styles = (ft_style_t **) malloc( i_len * sizeof( ft_style_t * ));
2016     if( !pp_char_styles )
2017         return VLC_ENOMEM;
2018
2019     if( b_karaoke )
2020     {
2021         pi_karaoke_bar = (uint8_t *) malloc( i_len * sizeof( uint8_t ));
2022         /* If we can't allocate sufficient memory for karaoke, continue anyway -
2023          * we just won't be able to display the progress bar; at least we'll
2024          * get the text.
2025          */
2026     }
2027
2028     i = 0;
2029     for( j = 0; j < i_runs; j++ )
2030         for( k = 0; k < pi_run_lengths[ j ]; k++ )
2031             pp_char_styles[ i++ ] = pp_styles[ j ];
2032
2033 #if defined(HAVE_FRIBIDI)
2034     {
2035         ft_style_t  **pp_char_styles_new;
2036         int         *p_old_positions;
2037         uint32_t    *p_fribidi_string;
2038         int start_pos, pos = 0;
2039
2040         pp_char_styles_new  = (ft_style_t **)
2041             malloc( i_len * sizeof( ft_style_t * ));
2042
2043         p_fribidi_string = (uint32_t *)
2044             malloc( (i_len + 1) * sizeof(uint32_t) );
2045         p_old_positions = (int *)
2046             malloc( (i_len + 1) * sizeof( int ) );
2047         p_new_positions = (int *)
2048             malloc( (i_len + 1) * sizeof( int ) );
2049         p_levels = (int8_t *)
2050             malloc( (i_len + 1) * sizeof( int8_t ) );
2051
2052         if( ! pp_char_styles_new ||
2053             ! p_fribidi_string ||
2054             ! p_old_positions ||
2055             ! p_new_positions ||
2056             ! p_levels )
2057         {
2058             free( p_levels );
2059             free( p_old_positions );
2060             free( p_new_positions );
2061             free( p_fribidi_string );
2062             free( pp_char_styles_new );
2063             free( pi_karaoke_bar );
2064
2065             free( pp_char_styles );
2066             return VLC_ENOMEM;
2067         }
2068
2069         /* Do bidi conversion line-by-line */
2070         while(pos < i_len)
2071         {
2072             while(pos < i_len) {
2073                 if (psz_text[pos] != '\n')
2074                     break;
2075                 p_fribidi_string[pos] = psz_text[pos];
2076                 pp_char_styles_new[pos] = pp_char_styles[pos];
2077                 p_new_positions[pos] = pos;
2078                 p_levels[pos] = 0;
2079                 ++pos;
2080             }
2081             start_pos = pos;
2082             while(pos < i_len) {
2083                 if (psz_text[pos] == '\n')
2084                     break;
2085                 ++pos;
2086             }
2087             if (pos > start_pos)
2088             {
2089 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
2090                 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
2091 #else
2092                 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
2093 #endif
2094                 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
2095                         pos - start_pos, &base_dir,
2096                         (FriBidiChar*)p_fribidi_string + start_pos,
2097                         p_new_positions + start_pos,
2098                         p_old_positions,
2099                         p_levels + start_pos );
2100                 for( j = (uint32_t) start_pos; j < (uint32_t) pos; j++ )
2101                 {
2102                     pp_char_styles_new[ j ] = pp_char_styles[ start_pos +
2103                                                 p_old_positions[ j - start_pos ] ];
2104                     p_new_positions[ j ] += start_pos;
2105                 }
2106             }
2107         }
2108         free( p_old_positions );
2109         free( pp_char_styles );
2110         pp_char_styles = pp_char_styles_new;
2111         psz_text = p_fribidi_string;
2112         p_fribidi_string[ i_len ] = 0;
2113     }
2114 #endif
2115     /* Work out the karaoke */
2116     if( pi_karaoke_bar )
2117     {
2118         int64_t i_last_duration = 0;
2119         int64_t i_duration = 0;
2120         int64_t i_start_pos = 0;
2121         int64_t i_elapsed  = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
2122
2123         for( k = 0; k< i_k_runs; k++ )
2124         {
2125              double fraction = 0.0;
2126
2127              i_duration += pi_k_durations[ k ];
2128
2129              if( i_duration < i_elapsed )
2130              {
2131                  /* Completely finished this run-length -
2132                   * let it render normally */
2133
2134                  fraction = 1.0;
2135              }
2136              else if( i_elapsed < i_last_duration )
2137              {
2138                  /* Haven't got up to this segment yet -
2139                   * render it completely in karaoke BG mode */
2140
2141                  fraction = 0.0;
2142              }
2143              else
2144              {
2145                  /* Partway through this run */
2146
2147                  fraction = (double)(i_elapsed - i_last_duration) /
2148                             (double)pi_k_durations[ k ];
2149              }
2150              for( i = 0; i < pi_k_run_lengths[ k ]; i++ )
2151              {
2152                  double shade = pi_k_run_lengths[ k ] * fraction;
2153
2154                  if( p_new_positions )
2155                      j = p_new_positions[ i_start_pos + i ];
2156                  else
2157                      j = i_start_pos + i;
2158
2159                  if( i < (uint32_t)shade )
2160                      pi_karaoke_bar[ j ] = 0xff;
2161                  else if( (double)i > shade )
2162                      pi_karaoke_bar[ j ] = 0x00;
2163                  else
2164                  {
2165                      shade -= (int)shade;
2166                      pi_karaoke_bar[ j ] = ((int)(shade * 128.0) & 0x7f) |
2167                                    ((p_levels ? (p_levels[ j ] % 2) : 0 ) << 7);
2168                  }
2169              }
2170
2171              i_last_duration = i_duration;
2172              i_start_pos += pi_k_run_lengths[ k ];
2173         }
2174     }
2175     free( p_levels );
2176     free( p_new_positions );
2177
2178     FT_Vector tmp_result;
2179
2180     line_desc_t *p_line = NULL;
2181     line_desc_t *p_prev = NULL;
2182
2183     int i_pen_x = 0;
2184     int i_pen_y = 0;
2185     int i_posn  = 0;
2186
2187     p_result->x = p_result->y = 0;
2188     tmp_result.x = tmp_result.y = 0;
2189
2190     i_prev = 0;
2191     for( k = 0; k <= (uint32_t) i_len; k++ )
2192     {
2193         if( ( k == (uint32_t) i_len ) ||
2194           ( ( k > 0 ) &&
2195             !StyleEquals( pp_char_styles[ k ], pp_char_styles[ k - 1] ) ) )
2196         {
2197             ft_style_t *p_style = pp_char_styles[ k - 1 ];
2198
2199             /* End of the current style run */
2200             FT_Face p_face = NULL;
2201             int      i_idx = 0;
2202
2203             /* Look for a match amongst our attachments first */
2204             CheckForEmbeddedFont( p_sys, &p_face, p_style );
2205
2206             if( ! p_face )
2207             {
2208                 char *psz_fontfile;
2209
2210 #ifdef HAVE_FONTCONFIG
2211                 psz_fontfile = FontConfig_Select( NULL,
2212                                                   p_style->psz_fontname,
2213                                                   p_style->b_bold,
2214                                                   p_style->b_italic,
2215                                                   -1,
2216                                                   &i_idx );
2217 #elif defined( WIN32 )
2218                 psz_fontfile = Win32_Select( p_filter,
2219                                             p_style->psz_fontname,
2220                                             p_style->b_bold,
2221                                             p_style->b_italic,
2222                                             -1,
2223                                             &i_idx );
2224 #else
2225                 psz_fontfile = NULL;
2226 #endif
2227                 if( psz_fontfile && ! *psz_fontfile )
2228                 {
2229                     msg_Warn( p_filter, "We were not able to find a matching font: \"%s\" %s,"
2230                         " so using default font", p_style->psz_fontname,
2231                         ((p_style->b_bold && p_style->b_italic) ? "(Bold,Italic)" :
2232                                                (p_style->b_bold ? "(Bold)" :
2233                                              (p_style->b_italic ? "(Italic)" : ""))) );
2234                     free( psz_fontfile );
2235                     psz_fontfile = NULL;
2236                 }
2237
2238                 if( psz_fontfile )
2239                 {
2240                     if( FT_New_Face( p_sys->p_library,
2241                                 psz_fontfile, i_idx, &p_face ) )
2242                     {
2243                         free( psz_fontfile );
2244                         free( pp_char_styles );
2245 #if defined(HAVE_FRIBIDI)
2246                         free( psz_text );
2247 #endif
2248                         free( pi_karaoke_bar );
2249                         return VLC_EGENERIC;
2250                     }
2251                     free( psz_fontfile );
2252                 }
2253             }
2254             if( p_face &&
2255                 FT_Select_Charmap( p_face, ft_encoding_unicode ) )
2256             {
2257                 /* We've loaded a font face which is unhelpful for actually
2258                  * rendering text - fallback to the default one.
2259                  */
2260                  FT_Done_Face( p_face );
2261                  p_face = NULL;
2262             }
2263
2264             if( FT_Select_Charmap( p_face ? p_face : p_sys->p_face,
2265                         ft_encoding_unicode ) ||
2266                 FT_Set_Pixel_Sizes( p_face ? p_face : p_sys->p_face, 0,
2267                     p_style->i_font_size ) )
2268             {
2269                 if( p_face ) FT_Done_Face( p_face );
2270                 free( pp_char_styles );
2271 #if defined(HAVE_FRIBIDI)
2272                 free( psz_text );
2273 #endif
2274                 free( pi_karaoke_bar );
2275                 return VLC_EGENERIC;
2276             }
2277             p_sys->i_use_kerning =
2278                         FT_HAS_KERNING( ( p_face  ? p_face : p_sys->p_face ) );
2279
2280
2281             uint32_t *psz_unicode = (uint32_t *)
2282                               malloc( (k - i_prev + 1) * sizeof( uint32_t ));
2283             if( !psz_unicode )
2284             {
2285                 if( p_face ) FT_Done_Face( p_face );
2286                 free( pp_char_styles );
2287                 free( psz_unicode );
2288 #if defined(HAVE_FRIBIDI)
2289                 free( psz_text );
2290 #endif
2291                 free( pi_karaoke_bar );
2292                 return VLC_ENOMEM;
2293             }
2294             memcpy( psz_unicode, psz_text + i_prev,
2295                                         sizeof( uint32_t ) * ( k - i_prev ) );
2296             psz_unicode[ k - i_prev ] = 0;
2297             while( *psz_unicode )
2298             {
2299                 if( !p_line )
2300                 {
2301                     if( !(p_line = NewLine( i_len - i_prev)) )
2302                     {
2303                         if( p_face ) FT_Done_Face( p_face );
2304                         free( pp_char_styles );
2305                         free( psz_unicode );
2306 #if defined(HAVE_FRIBIDI)
2307                         free( psz_text );
2308 #endif
2309                         free( pi_karaoke_bar );
2310                         return VLC_ENOMEM;
2311                     }
2312                     /* New Color mode only works in YUVA rendering mode --
2313                      * (RGB mode has palette constraints on it). We therefore
2314                      * need to populate the legacy colour fields also.
2315                      */
2316                     p_line->b_new_color_mode = true;
2317                     p_line->i_alpha = ( p_style->i_font_color & 0xff000000 ) >> 24;
2318                     p_line->i_red   = ( p_style->i_font_color & 0x00ff0000 ) >> 16;
2319                     p_line->i_green = ( p_style->i_font_color & 0x0000ff00 ) >>  8;
2320                     p_line->i_blue  = ( p_style->i_font_color & 0x000000ff );
2321                     p_line->p_next = NULL;
2322                     i_pen_x = 0;
2323                     i_pen_y += tmp_result.y;
2324                     tmp_result.x = 0;
2325                     tmp_result.y = 0;
2326                     i_posn = 0;
2327                     if( p_prev ) p_prev->p_next = p_line;
2328                     else *pp_lines = p_line;
2329                 }
2330
2331                 if( RenderTag( p_filter, p_face ? p_face : p_sys->p_face,
2332                                p_style->i_font_color, p_style->b_underline,
2333                                p_style->b_through,
2334                                p_style->b_bold,
2335                                p_style->b_italic,
2336                                p_style->i_karaoke_bg_color,
2337                                p_line, psz_unicode, &i_pen_x, i_pen_y, &i_posn,
2338                                &tmp_result ) != VLC_SUCCESS )
2339                 {
2340                     if( p_face ) FT_Done_Face( p_face );
2341                     free( pp_char_styles );
2342                     free( psz_unicode );
2343 #if defined(HAVE_FRIBIDI)
2344                     free( psz_text );
2345 #endif
2346                     free( pi_karaoke_bar );
2347                     return VLC_EGENERIC;
2348                 }
2349
2350                 if( *psz_unicode )
2351                 {
2352                     p_result->x = __MAX( p_result->x, tmp_result.x );
2353                     p_result->y += tmp_result.y;
2354
2355                     p_prev = p_line;
2356                     p_line = NULL;
2357
2358                     if( *psz_unicode == '\n')
2359                     {
2360                         uint32_t *c_ptr;
2361
2362                         for( c_ptr = psz_unicode; *c_ptr; c_ptr++ )
2363                         {
2364                             *c_ptr = *(c_ptr+1);
2365                         }
2366                     }
2367                 }
2368             }
2369             free( psz_unicode );
2370             if( p_face ) FT_Done_Face( p_face );
2371             i_prev = k;
2372         }
2373     }
2374     free( pp_char_styles );
2375 #if defined(HAVE_FRIBIDI)
2376     free( psz_text );
2377 #endif
2378     if( p_line )
2379     {
2380         p_result->x = __MAX( p_result->x, tmp_result.x );
2381         p_result->y += tmp_result.y;
2382     }
2383
2384     if( pi_karaoke_bar )
2385     {
2386         int i = 0;
2387         for( p_line = *pp_lines; p_line; p_line=p_line->p_next )
2388         {
2389             for( k = 0; p_line->pp_glyphs[ k ]; k++, i++ )
2390             {
2391                 if( (pi_karaoke_bar[ i ] & 0x7f) == 0x7f)
2392                 {
2393                     /* do nothing */
2394                 }
2395                 else if( (pi_karaoke_bar[ i ] & 0x7f) == 0x00)
2396                 {
2397                     /* 100% BG colour will render faster if we
2398                      * instead make it 100% FG colour, so leave
2399                      * the ratio alone and copy the value across
2400                      */
2401                     p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2402                 }
2403                 else
2404                 {
2405                     if( pi_karaoke_bar[ i ] & 0x80 )
2406                     {
2407                         /* Swap Left and Right sides over for Right aligned
2408                          * language text (eg. Arabic, Hebrew)
2409                          */
2410                         uint32_t i_tmp = p_line->p_fg_rgb[ k ];
2411
2412                         p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2413                         p_line->p_bg_rgb[ k ] = i_tmp;
2414                     }
2415                     p_line->p_fg_bg_ratio[ k ] = (pi_karaoke_bar[ i ] & 0x7f);
2416                 }
2417             }
2418             /* Jump over the '\n' at the line-end */
2419             i++;
2420         }
2421         free( pi_karaoke_bar );
2422     }
2423
2424     return VLC_SUCCESS;
2425 }
2426
2427 /**
2428  * This function renders a text subpicture region into another one.
2429  * It also calculates the size needed for this string, and renders the
2430  * needed glyphs into memory. It is used as pf_add_string callback in
2431  * the vout method by this module
2432  */
2433 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2434                          subpicture_region_t *p_region_in, bool b_html )
2435 {
2436     filter_sys_t *p_sys = p_filter->p_sys;
2437
2438     if( !p_region_in )
2439         return VLC_EGENERIC;
2440     if( b_html && !p_region_in->psz_html )
2441         return VLC_EGENERIC;
2442     if( !b_html && !p_region_in->psz_text )
2443         return VLC_EGENERIC;
2444
2445     uint32_t *psz_text = calloc( strlen( b_html ? p_region_in->psz_html
2446                                                 : p_region_in->psz_text ),
2447                                  sizeof( *psz_text ) );
2448     if( !psz_text )
2449         return VLC_EGENERIC;
2450
2451
2452     /* Reset the default fontsize in case screen metrics have changed */
2453     p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2454
2455     /* */
2456     int rv = VLC_SUCCESS;
2457     int i_text_length = 0;
2458     FT_Vector result = {0, 0};
2459     line_desc_t *p_lines = NULL;
2460
2461 #ifdef HAVE_STYLES
2462     if( b_html )
2463     {
2464         stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2465                                             (uint8_t *) p_region_in->psz_html,
2466                                             strlen( p_region_in->psz_html ),
2467                                             true );
2468         if( unlikely(p_sub == NULL) )
2469             return VLC_SUCCESS;
2470
2471         xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2472         if( !p_xml_reader )
2473             p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2474         else
2475             p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2476         p_filter->p_sys->p_xml = p_xml_reader;
2477
2478         if( !p_xml_reader )
2479             rv = VLC_EGENERIC;
2480
2481         bool b_karaoke = false;
2482         if( !rv )
2483         {
2484             /* Look for Root Node */
2485             const char *node;
2486
2487             if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2488             {
2489                 if( !strcasecmp( "karaoke", node ) )
2490                 {
2491                     /* We're going to have to render the text a number
2492                      * of times to show the progress marker on the text.
2493                      */
2494                     var_SetBool( p_filter, "text-rerender", true );
2495                     b_karaoke = true;
2496                 }
2497                 else if( !strcasecmp( "text", node ) )
2498                 {
2499                     b_karaoke = false;
2500                 }
2501                 else
2502                 {
2503                     /* Only text and karaoke tags are supported */
2504                     msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2505                              node );
2506                     rv = VLC_EGENERIC;
2507                 }
2508             }
2509             else
2510             {
2511                 msg_Err( p_filter, "Malformed HTML subtitle" );
2512                 rv = VLC_EGENERIC;
2513             }
2514         }
2515         if( !rv )
2516         {
2517             uint32_t    i_runs           = 0;
2518             uint32_t    i_k_runs         = 0;
2519             uint32_t   *pi_run_lengths   = NULL;
2520             uint32_t   *pi_k_run_lengths = NULL;
2521             uint32_t   *pi_k_durations   = NULL;
2522             ft_style_t  **pp_styles      = NULL;
2523
2524             rv = ProcessNodes( p_filter, p_xml_reader,
2525                                p_region_in->p_style, psz_text, &i_text_length,
2526                                &i_runs, &pi_run_lengths, &pp_styles,
2527                                b_karaoke, &i_k_runs, &pi_k_run_lengths,
2528                                &pi_k_durations );
2529
2530             if( !rv && i_text_length > 0 )
2531             {
2532                 rv = ProcessLines( p_filter, psz_text, i_text_length, i_runs,
2533                                    pi_run_lengths, pp_styles, &p_lines,
2534                                    &result, b_karaoke, i_k_runs,
2535                                    pi_k_run_lengths, pi_k_durations );
2536             }
2537
2538             for( uint32_t k = 0; k < i_runs; k++ )
2539                  DeleteStyle( pp_styles[k] );
2540             free( pp_styles );
2541             free( pi_run_lengths );
2542
2543         }
2544
2545         if( p_xml_reader )
2546             p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2547
2548         stream_Delete( p_sub );
2549     }
2550     else
2551 #endif
2552     {
2553
2554         size_t i_iconv_length;
2555         IconvText( p_filter, p_region_in->psz_text, &i_iconv_length, psz_text );
2556         i_text_length = i_iconv_length;
2557
2558         int i_scale = 1000;
2559         vlc_value_t val;
2560         if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ) )
2561             i_scale = val.i_int;
2562
2563         ft_style_t *p_style;
2564         if( p_region_in->p_style )
2565             p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2566                                    p_region_in->p_style->i_font_size * i_scale / 1000,
2567                                    (p_region_in->p_style->i_font_color & 0xffffff) |
2568                                    ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2569                                    0x00ffffff,
2570                                    p_region_in->p_style->i_style_flags & STYLE_BOLD,
2571                                    p_region_in->p_style->i_style_flags & STYLE_ITALIC,
2572                                    p_region_in->p_style->i_style_flags & STYLE_UNDERLINE,
2573                                    p_region_in->p_style->i_style_flags & STYLE_STRIKEOUT);
2574         else
2575             p_style = CreateStyle( p_sys->psz_fontfamily,
2576                                    p_sys->i_font_size * i_scale / 1000,
2577                                    (p_sys->i_font_color & 0xffffff) |
2578                                    (((255-p_sys->i_font_opacity) & 0xff) << 24),
2579                                    0x00ffffff,
2580                                    false, false, false, false );
2581         uint32_t i_run_length = i_text_length;
2582
2583         rv = ProcessLines( p_filter, psz_text, i_text_length,
2584                            1, &i_run_length, &p_style,
2585                            &p_lines, &result,
2586                            false, 0, NULL, NULL );
2587         DeleteStyle( p_style );
2588     }
2589     free( psz_text );
2590
2591     p_region_out->i_x = p_region_in->i_x;
2592     p_region_out->i_y = p_region_in->i_y;
2593
2594     /* Don't attempt to render text that couldn't be layed out
2595      * properly. */
2596     if( !rv && i_text_length > 0 )
2597     {
2598         if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2599             RenderYUVP( p_filter, p_region_out, p_lines,
2600                         result.x, result.y );
2601         else
2602             RenderYUVA( p_filter, p_region_out, p_lines,
2603                         result.x, result.y );
2604     }
2605
2606     FreeLines( p_lines );
2607     return rv;
2608 }
2609
2610 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2611                        subpicture_region_t *p_region_in )
2612 {
2613     return RenderCommon( p_filter, p_region_out, p_region_in, false );
2614 }
2615
2616 #ifdef HAVE_STYLES
2617
2618 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2619                        subpicture_region_t *p_region_in )
2620 {
2621     return RenderCommon( p_filter, p_region_out, p_region_in, true );
2622 }
2623
2624 #ifdef WIN32
2625 #define UNICODE
2626 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
2627
2628 static int GetFileFontByName( const char *font_name, char **psz_filename )
2629 {
2630     HKEY hKey;
2631     wchar_t vbuffer[MAX_PATH];
2632     wchar_t dbuffer[256];
2633
2634     if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey) != ERROR_SUCCESS )
2635         return 1;
2636
2637     for( int index = 0;; index++ )
2638     {
2639         DWORD vbuflen = MAX_PATH - 1;
2640         DWORD dbuflen = 255;
2641
2642         if( RegEnumValueW( hKey, index, vbuffer, &vbuflen,
2643                            NULL, NULL, (LPBYTE)dbuffer, &dbuflen) != ERROR_SUCCESS )
2644             return 2;
2645
2646         char *psz_value = FromWide( vbuffer );
2647
2648         char *s = strchr( psz_value,'(' );
2649         if( s != NULL && s != psz_value ) s[-1] = '\0';
2650
2651         /* Manage concatenated font names */
2652         if( strchr( psz_value, '&') ) {
2653             if( strcasestr( psz_value, font_name ) != NULL )
2654                 break;
2655         }
2656         else {
2657             if( strcasecmp( psz_value, font_name ) == 0 )
2658                 break;
2659         }
2660     }
2661
2662     *psz_filename = FromWide( dbuffer );
2663     return 0;
2664 }
2665
2666
2667 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
2668                                      DWORD type, LPARAM lParam)
2669 {
2670     VLC_UNUSED( metric );
2671     if( (type & RASTER_FONTTYPE) ) return 1;
2672     // if( lpelfe->elfScript ) FIXME
2673
2674     return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
2675 }
2676
2677 static char* Win32_Select( filter_t *p_filter, const char* family,
2678                            bool b_bold, bool b_italic, int i_size, int *i_idx )
2679 {
2680     VLC_UNUSED( i_size );
2681     // msg_Dbg( p_filter, "Here in Win32_Select, asking for %s", family );
2682
2683     /* */
2684     LOGFONT lf;
2685     lf.lfCharSet = DEFAULT_CHARSET;
2686     if( b_italic )
2687         lf.lfItalic = true;
2688     if( b_bold )
2689         lf.lfWeight = FW_BOLD;
2690     strncpy( (LPSTR)&lf.lfFaceName, family, 32);
2691
2692     /* */
2693     char *psz_filename = NULL;
2694     HDC hDC = GetDC( NULL );
2695     EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
2696     ReleaseDC(NULL, hDC);
2697
2698     if( psz_filename == NULL )
2699         return NULL;
2700
2701     /* FIXME: increase i_idx, when concatenated strings  */
2702     i_idx = 0;
2703
2704     /* */
2705     char *psz_tmp;
2706     if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
2707         return NULL;
2708     return psz_tmp;
2709 }
2710 #endif
2711
2712 #ifdef HAVE_FONTCONFIG
2713 static void FontConfig_BuildCache( filter_t *p_filter )
2714 {
2715     /* */
2716     msg_Dbg( p_filter, "Building font databases.");
2717     mtime_t t1, t2;
2718     t1 = mdate();
2719
2720 #ifdef WIN32
2721     dialog_progress_bar_t *p_dialog = NULL;
2722     FcConfig *fcConfig = FcInitLoadConfig();
2723
2724     p_dialog = dialog_ProgressCreate( p_filter,
2725             _("Building font cache"),
2726             _("Please wait while your font cache is rebuilt.\n"
2727                 "This should take less than a few minutes."), NULL );
2728
2729 /*    if( p_dialog )
2730         dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
2731
2732     FcConfigBuildFonts( fcConfig );
2733     if( p_dialog )
2734     {
2735 //        dialog_ProgressSet( p_dialog, NULL, 1.0 );
2736         dialog_ProgressDestroy( p_dialog );
2737         p_dialog = NULL;
2738     }
2739 #endif
2740     t2 = mdate();
2741     msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
2742 }
2743
2744 /***
2745  * \brief Selects a font matching family, bold, italic provided
2746  ***/
2747 static char* FontConfig_Select( FcConfig* config, const char* family,
2748                           bool b_bold, bool b_italic, int i_size, int *i_idx )
2749 {
2750     FcResult result = FcResultMatch;
2751     FcPattern *pat, *p_pat;
2752     FcChar8* val_s;
2753     FcBool val_b;
2754
2755     /* Create a pattern and fills it */
2756     pat = FcPatternCreate();
2757     if (!pat) return NULL;
2758
2759     /* */
2760     FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
2761     FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
2762     FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
2763     FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
2764     if( i_size != -1 )
2765     {
2766         char *psz_fontsize;
2767         if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
2768         {
2769             FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
2770             free( psz_fontsize );
2771         }
2772     }
2773
2774     /* */
2775     FcDefaultSubstitute( pat );
2776     if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
2777     {
2778         FcPatternDestroy( pat );
2779         return NULL;
2780     }
2781
2782     /* Find the best font for the pattern, destroy the pattern */
2783     p_pat = FcFontMatch( config, pat, &result );
2784     FcPatternDestroy( pat );
2785     if( !p_pat || result == FcResultNoMatch ) return NULL;
2786
2787     /* Check the new pattern */
2788     if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
2789         || ( val_b != FcTrue ) )
2790     {
2791         FcPatternDestroy( p_pat );
2792         return NULL;
2793     }
2794     if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
2795     {
2796         *i_idx = 0;
2797     }
2798
2799     if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
2800     {
2801         FcPatternDestroy( p_pat );
2802         return NULL;
2803     }
2804
2805     /* if( strcasecmp((const char*)val_s, family ) != 0 )
2806         msg_Warn( p_filter, "fontconfig: selected font family is not"
2807                             "the requested one: '%s' != '%s'\n",
2808                             (const char*)val_s, family );   */
2809
2810     if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
2811     {
2812         FcPatternDestroy( p_pat );
2813         return NULL;
2814     }
2815
2816     FcPatternDestroy( p_pat );
2817     return strdup( (const char*)val_s );
2818 }
2819 #endif
2820 #endif
2821
2822 static void FreeLine( line_desc_t *p_line )
2823 {
2824     unsigned int i;
2825     for( i = 0; p_line->pp_glyphs[ i ] != NULL; i++ )
2826     {
2827         FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
2828     }
2829     free( p_line->pp_glyphs );
2830     free( p_line->p_glyph_pos );
2831     free( p_line->p_fg_rgb );
2832     free( p_line->p_bg_rgb );
2833     free( p_line->p_fg_bg_ratio );
2834     free( p_line->pi_underline_offset );
2835     free( p_line->pi_underline_thickness );
2836     free( p_line );
2837 }
2838
2839 static void FreeLines( line_desc_t *p_lines )
2840 {
2841     line_desc_t *p_line, *p_next;
2842
2843     if( !p_lines ) return;
2844
2845     for( p_line = p_lines; p_line != NULL; p_line = p_next )
2846     {
2847         p_next = p_line->p_next;
2848         FreeLine( p_line );
2849     }
2850 }
2851
2852 static line_desc_t *NewLine( int i_count )
2853 {
2854     line_desc_t *p_line = malloc( sizeof(line_desc_t) );
2855
2856     if( !p_line ) return NULL;
2857     p_line->i_height = 0;
2858     p_line->i_width = 0;
2859     p_line->p_next = NULL;
2860
2861     p_line->pp_glyphs = malloc( sizeof(FT_BitmapGlyph) * ( i_count + 1 ) );
2862     p_line->p_glyph_pos = malloc( sizeof( FT_Vector ) * ( i_count + 1 ) );
2863     p_line->p_fg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) );
2864     p_line->p_bg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) );
2865     p_line->p_fg_bg_ratio = calloc( i_count + 1, sizeof( uint8_t ) );
2866     p_line->pi_underline_offset = calloc( i_count + 1, sizeof( int ) );
2867     p_line->pi_underline_thickness = calloc( i_count + 1, sizeof( uint16_t ) );
2868     if( ( p_line->pp_glyphs == NULL ) ||
2869         ( p_line->p_glyph_pos == NULL ) ||
2870         ( p_line->p_fg_rgb == NULL ) ||
2871         ( p_line->p_bg_rgb == NULL ) ||
2872         ( p_line->p_fg_bg_ratio == NULL ) ||
2873         ( p_line->pi_underline_offset == NULL ) ||
2874         ( p_line->pi_underline_thickness == NULL ) )
2875     {
2876         free( p_line->pi_underline_thickness );
2877         free( p_line->pi_underline_offset );
2878         free( p_line->p_fg_rgb );
2879         free( p_line->p_bg_rgb );
2880         free( p_line->p_fg_bg_ratio );
2881         free( p_line->p_glyph_pos );
2882         free( p_line->pp_glyphs );
2883         free( p_line );
2884         return NULL;
2885     }
2886     p_line->pp_glyphs[0] = NULL;
2887     p_line->b_new_color_mode = false;
2888
2889     return p_line;
2890 }
2891
2892 static int GetFontSize( filter_t *p_filter )
2893 {
2894     filter_sys_t *p_sys = p_filter->p_sys;
2895     vlc_value_t   val;
2896     int           i_size = 0;
2897
2898     if( p_sys->i_default_font_size )
2899     {
2900         if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
2901             i_size = p_sys->i_default_font_size * val.i_int / 1000;
2902         else
2903             i_size = p_sys->i_default_font_size;
2904     }
2905     else
2906     {
2907         var_Get( p_filter, "freetype-rel-fontsize", &val );
2908         if( val.i_int  > 0 )
2909         {
2910             i_size = (int)p_filter->fmt_out.video.i_height / val.i_int;
2911             p_filter->p_sys->i_display_height =
2912                 p_filter->fmt_out.video.i_height;
2913         }
2914     }
2915     if( i_size <= 0 )
2916     {
2917         msg_Warn( p_filter, "invalid fontsize, using 12" );
2918         if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
2919             i_size = 12 * val.i_int / 1000;
2920         else
2921             i_size = 12;
2922     }
2923     return i_size;
2924 }
2925
2926 static int SetFontSize( filter_t *p_filter, int i_size )
2927 {
2928     filter_sys_t *p_sys = p_filter->p_sys;
2929
2930     if( !i_size )
2931     {
2932         i_size = GetFontSize( p_filter );
2933
2934         msg_Dbg( p_filter, "using fontsize: %i", i_size );
2935     }
2936
2937     p_sys->i_font_size = i_size;
2938
2939     if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
2940     {
2941         msg_Err( p_filter, "couldn't set font size to %d", i_size );
2942         return VLC_EGENERIC;
2943     }
2944
2945     return VLC_SUCCESS;
2946 }
2947
2948 static void YUVFromRGB( uint32_t i_argb,
2949                     uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
2950 {
2951     int i_red   = ( i_argb & 0x00ff0000 ) >> 16;
2952     int i_green = ( i_argb & 0x0000ff00 ) >>  8;
2953     int i_blue  = ( i_argb & 0x000000ff );
2954
2955     *pi_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
2956                       802 * i_blue + 4096 + 131072 ) >> 13, 235);
2957     *pi_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
2958                      3598 * i_blue + 4096 + 1048576) >> 13, 240);
2959     *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
2960                       -585 * i_blue + 4096 + 1048576) >> 13, 240);
2961 }