]> git.sesse.net Git - vlc/blob - modules/misc/text_renderer/freetype.c
Added fully configurable outline support (freetype).
[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
46 /* Default fonts */
47 #ifdef __APPLE__
48 # define DEFAULT_FONT_FILE "/Library/Fonts/Arial Black.ttf"
49 # define DEFAULT_FAMILY "Arial Black"
50 #elif defined( WIN32 )
51 # define DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
52 # define DEFAULT_FAMILY "Arial"
53 #elif defined( HAVE_MAEMO )
54 # define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf"
55 # define DEFAULT_FAMILY "Nokia Sans Bold"
56 #else
57 # define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
58 # define DEFAULT_FAMILY "Serif Bold"
59 #endif
60
61 /* Freetype */
62 #include <freetype/ftsynth.h>
63 #include FT_FREETYPE_H
64 #include FT_GLYPH_H
65 #include FT_STROKER_H
66
67 #define FT_FLOOR(X)     ((X & -64) >> 6)
68 #define FT_CEIL(X)      (((X + 63) & -64) >> 6)
69 #ifndef FT_MulFix
70  #define FT_MulFix(v, s) (((v)*(s))>>16)
71 #endif
72
73 /* RTL */
74 #if defined(HAVE_FRIBIDI)
75 # include <fribidi/fribidi.h>
76 #endif
77
78 /* Win32 GDI */
79 #ifdef WIN32
80 # include <windows.h>
81 # include <shlobj.h>
82 # define HAVE_STYLES
83 # undef HAVE_FONTCONFIG
84 #endif
85
86 /* FontConfig */
87 #ifdef HAVE_FONTCONFIG
88 # include <fontconfig/fontconfig.h>
89 # define HAVE_STYLES
90 #endif
91
92 #include <assert.h>
93
94 /*****************************************************************************
95  * Module descriptor
96  *****************************************************************************/
97 static int  Create ( vlc_object_t * );
98 static void Destroy( vlc_object_t * );
99
100 #define FONT_TEXT N_("Font")
101
102 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
103 #define FONT_LONGTEXT N_("Font file for the font you want to use")
104
105 #define FONTSIZE_TEXT N_("Font size in pixels")
106 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
107     "that will be rendered on the video. " \
108     "If set to something different than 0 this option will override the " \
109     "relative font size." )
110 #define OPACITY_TEXT N_("Opacity")
111 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
112     "text that will be rendered on the video. 0 = transparent, " \
113     "255 = totally opaque. " )
114 #define COLOR_TEXT N_("Text default color")
115 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
116     "the video. This must be an hexadecimal (like HTML colors). The first two "\
117     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
118     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
119 #define FONTSIZER_TEXT N_("Relative font size")
120 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
121     "fonts that will be rendered on the video. If absolute font size is set, "\
122     "relative size will be overridden." )
123
124 #define BG_OPACITY_TEXT N_("Background opacity")
125 #define BG_COLOR_TEXT N_("Background color")
126
127 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
128 #define OUTLINE_COLOR_TEXT N_("Outline color")
129 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
130
131 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
132 static const char *const ppsz_sizes_text[] = {
133     N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
134 #define YUVP_TEXT N_("Use YUVP renderer")
135 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
136   "This option is only needed if you want to encode into DVB subtitles" )
137
138 static const int pi_color_values[] = {
139   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
140   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
141   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
142
143 static const char *const ppsz_color_descriptions[] = {
144   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
145   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
146   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
147
148 static const int pi_outline_thickness[] = {
149     0, 2, 4, 6,
150 };
151 static const char *const ppsz_outline_thickness[] = {
152     N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
153 };
154
155 vlc_module_begin ()
156     set_shortname( N_("Text renderer"))
157     set_description( N_("Freetype2 font renderer") )
158     set_category( CAT_VIDEO )
159     set_subcategory( SUBCAT_VIDEO_SUBPIC )
160
161 #ifdef HAVE_STYLES
162     add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
163 #else
164     add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
165 #endif
166
167     add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
168                  FONTSIZE_LONGTEXT, true )
169         change_safe()
170
171     /* opacity valid on 0..255, with default 255 = fully opaque */
172     add_integer_with_range( "freetype-opacity", 255, 0, 255,
173         OPACITY_TEXT, OPACITY_LONGTEXT, false )
174         change_safe()
175
176     /* hook to the color values list, with default 0x00ffffff = white */
177     add_integer( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
178                  COLOR_LONGTEXT, false )
179         change_integer_list( pi_color_values, ppsz_color_descriptions )
180         change_safe()
181
182     add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
183                             BG_OPACITY_TEXT, "", false )
184         change_safe()
185     add_integer( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
186                  "", false )
187         change_integer_list( pi_color_values, ppsz_color_descriptions )
188         change_safe()
189
190     add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
191                             OUTLINE_OPACITY_TEXT, "", false )
192         change_safe()
193     add_integer( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
194                  "", false )
195         change_integer_list( pi_color_values, ppsz_color_descriptions )
196         change_safe()
197     add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
198                  "", false )
199         change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
200         change_safe()
201
202     add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
203                  FONTSIZER_LONGTEXT, false )
204         change_integer_list( pi_sizes, ppsz_sizes_text )
205         change_safe()
206
207     add_obsolete_integer( "freetype-effect" );
208
209     add_bool( "freetype-yuvp", false, YUVP_TEXT,
210               YUVP_LONGTEXT, true )
211     set_capability( "text renderer", 100 )
212     add_shortcut( "text" )
213     set_callbacks( Create, Destroy )
214 vlc_module_end ()
215
216
217 /*****************************************************************************
218  * Local prototypes
219  *****************************************************************************/
220
221 typedef struct
222 {
223     FT_BitmapGlyph p_glyph;
224     FT_BitmapGlyph p_outline;
225     uint32_t       i_color;             /* ARGB color */
226     int            i_line_offset;       /* underline/strikethrough offset */
227     int            i_line_thickness;    /* underline/strikethrough thickness */
228 } line_character_t;
229
230 typedef struct line_desc_t line_desc_t;
231 struct line_desc_t
232 {
233     line_desc_t    *p_next;
234
235     int              i_width;
236     int              i_base_line;
237     int              i_character_count;
238     line_character_t *p_character;
239 };
240
241 typedef struct font_stack_t font_stack_t;
242 struct font_stack_t
243 {
244     char          *psz_name;
245     int            i_size;
246     uint32_t       i_color;            /* ARGB */
247     uint32_t       i_karaoke_bg_color; /* ARGB */
248
249     font_stack_t  *p_next;
250 };
251
252 /*****************************************************************************
253  * filter_sys_t: freetype local data
254  *****************************************************************************
255  * This structure is part of the video output thread descriptor.
256  * It describes the freetype specific properties of an output thread.
257  *****************************************************************************/
258 struct filter_sys_t
259 {
260     FT_Library     p_library;   /* handle to library     */
261     FT_Face        p_face;      /* handle to face object */
262     FT_Stroker     p_stroker;
263     uint8_t        i_font_opacity;
264     int            i_font_color;
265     int            i_font_size;
266
267     uint8_t        i_background_opacity;
268     int            i_background_color;
269
270     double         f_outline_thickness;
271     uint8_t        i_outline_opacity;
272     int            i_outline_color;
273
274     int            i_default_font_size;
275     int            i_display_height;
276     char*          psz_fontfamily;
277 #ifdef HAVE_STYLES
278     xml_reader_t  *p_xml;
279 #ifdef WIN32
280     char*          psz_win_fonts_path;
281 #endif
282 #endif
283
284     input_attachment_t **pp_font_attachments;
285     int                  i_font_attachments;
286 };
287
288 /* */
289 static void YUVFromRGB( uint32_t i_argb,
290                     uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
291 {
292     int i_red   = ( i_argb & 0x00ff0000 ) >> 16;
293     int i_green = ( i_argb & 0x0000ff00 ) >>  8;
294     int i_blue  = ( i_argb & 0x000000ff );
295
296     *pi_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
297                       802 * i_blue + 4096 + 131072 ) >> 13, 235);
298     *pi_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
299                      3598 * i_blue + 4096 + 1048576) >> 13, 240);
300     *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
301                       -585 * i_blue + 4096 + 1048576) >> 13, 240);
302 }
303
304 /*****************************************************************************
305  * Make any TTF/OTF fonts present in the attachments of the media file
306  * and store them for later use by the FreeType Engine
307  *****************************************************************************/
308 static int LoadFontsFromAttachments( filter_t *p_filter )
309 {
310     filter_sys_t         *p_sys = p_filter->p_sys;
311     input_attachment_t  **pp_attachments;
312     int                   i_attachments_cnt;
313
314     if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
315         return VLC_EGENERIC;
316
317     p_sys->i_font_attachments = 0;
318     p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
319     if( !p_sys->pp_font_attachments )
320         return VLC_ENOMEM;
321
322     for( int k = 0; k < i_attachments_cnt; k++ )
323     {
324         input_attachment_t *p_attach = pp_attachments[k];
325
326         if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
327               !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) &&    // OTF
328             p_attach->i_data > 0 && p_attach->p_data )
329         {
330             p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
331         }
332         else
333         {
334             vlc_input_attachment_Delete( p_attach );
335         }
336     }
337     free( pp_attachments );
338
339     return VLC_SUCCESS;
340 }
341
342 static int GetFontSize( filter_t *p_filter )
343 {
344     filter_sys_t *p_sys = p_filter->p_sys;
345     int           i_size = 0;
346
347     if( p_sys->i_default_font_size )
348     {
349         i_size = p_sys->i_default_font_size;
350     }
351     else
352     {
353         int i_ratio = var_GetInteger( p_filter, "freetype-rel-fontsize" );
354         if( i_ratio > 0 )
355         {
356             i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
357             p_filter->p_sys->i_display_height = p_filter->fmt_out.video.i_height;
358         }
359     }
360     if( i_size <= 0 )
361     {
362         msg_Warn( p_filter, "invalid fontsize, using 12" );
363         i_size = 12;
364     }
365     return i_size;
366 }
367
368 static int SetFontSize( filter_t *p_filter, int i_size )
369 {
370     filter_sys_t *p_sys = p_filter->p_sys;
371
372     if( !i_size )
373     {
374         i_size = GetFontSize( p_filter );
375
376         msg_Dbg( p_filter, "using fontsize: %i", i_size );
377     }
378
379     p_sys->i_font_size = i_size;
380
381     if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
382     {
383         msg_Err( p_filter, "couldn't set font size to %d", i_size );
384         return VLC_EGENERIC;
385     }
386
387     return VLC_SUCCESS;
388 }
389
390 #ifdef HAVE_STYLES
391 #ifdef HAVE_FONTCONFIG
392 static void FontConfig_BuildCache( filter_t *p_filter )
393 {
394     /* */
395     msg_Dbg( p_filter, "Building font databases.");
396     mtime_t t1, t2;
397     t1 = mdate();
398
399 #ifdef WIN32
400     dialog_progress_bar_t *p_dialog = NULL;
401     FcConfig *fcConfig = FcInitLoadConfig();
402
403     p_dialog = dialog_ProgressCreate( p_filter,
404             _("Building font cache"),
405             _("Please wait while your font cache is rebuilt.\n"
406                 "This should take less than a few minutes."), NULL );
407
408 /*    if( p_dialog )
409         dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
410
411     FcConfigBuildFonts( fcConfig );
412     if( p_dialog )
413     {
414 //        dialog_ProgressSet( p_dialog, NULL, 1.0 );
415         dialog_ProgressDestroy( p_dialog );
416         p_dialog = NULL;
417     }
418 #endif
419     t2 = mdate();
420     msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
421 }
422
423 /***
424  * \brief Selects a font matching family, bold, italic provided
425  ***/
426 static char* FontConfig_Select( FcConfig* config, const char* family,
427                           bool b_bold, bool b_italic, int i_size, int *i_idx )
428 {
429     FcResult result = FcResultMatch;
430     FcPattern *pat, *p_pat;
431     FcChar8* val_s;
432     FcBool val_b;
433
434     /* Create a pattern and fills it */
435     pat = FcPatternCreate();
436     if (!pat) return NULL;
437
438     /* */
439     FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
440     FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
441     FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
442     FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
443     if( i_size != -1 )
444     {
445         char *psz_fontsize;
446         if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
447         {
448             FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
449             free( psz_fontsize );
450         }
451     }
452
453     /* */
454     FcDefaultSubstitute( pat );
455     if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
456     {
457         FcPatternDestroy( pat );
458         return NULL;
459     }
460
461     /* Find the best font for the pattern, destroy the pattern */
462     p_pat = FcFontMatch( config, pat, &result );
463     FcPatternDestroy( pat );
464     if( !p_pat || result == FcResultNoMatch ) return NULL;
465
466     /* Check the new pattern */
467     if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
468         || ( val_b != FcTrue ) )
469     {
470         FcPatternDestroy( p_pat );
471         return NULL;
472     }
473     if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
474     {
475         *i_idx = 0;
476     }
477
478     if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
479     {
480         FcPatternDestroy( p_pat );
481         return NULL;
482     }
483
484     /* if( strcasecmp((const char*)val_s, family ) != 0 )
485         msg_Warn( p_filter, "fontconfig: selected font family is not"
486                             "the requested one: '%s' != '%s'\n",
487                             (const char*)val_s, family );   */
488
489     if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
490     {
491         FcPatternDestroy( p_pat );
492         return NULL;
493     }
494
495     FcPatternDestroy( p_pat );
496     return strdup( (const char*)val_s );
497 }
498 #endif
499
500 #ifdef WIN32
501 #define UNICODE
502 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
503
504 static int GetFileFontByName( const char *font_name, char **psz_filename )
505 {
506     HKEY hKey;
507     wchar_t vbuffer[MAX_PATH];
508     wchar_t dbuffer[256];
509
510     if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey) != ERROR_SUCCESS )
511         return 1;
512
513     for( int index = 0;; index++ )
514     {
515         DWORD vbuflen = MAX_PATH - 1;
516         DWORD dbuflen = 255;
517
518         if( RegEnumValueW( hKey, index, vbuffer, &vbuflen,
519                            NULL, NULL, (LPBYTE)dbuffer, &dbuflen) != ERROR_SUCCESS )
520             return 2;
521
522         char *psz_value = FromWide( vbuffer );
523
524         char *s = strchr( psz_value,'(' );
525         if( s != NULL && s != psz_value ) s[-1] = '\0';
526
527         /* Manage concatenated font names */
528         if( strchr( psz_value, '&') ) {
529             if( strcasestr( psz_value, font_name ) != NULL )
530                 break;
531         }
532         else {
533             if( strcasecmp( psz_value, font_name ) == 0 )
534                 break;
535         }
536     }
537
538     *psz_filename = FromWide( dbuffer );
539     return 0;
540 }
541
542
543 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
544                                      DWORD type, LPARAM lParam)
545 {
546     VLC_UNUSED( metric );
547     if( (type & RASTER_FONTTYPE) ) return 1;
548     // if( lpelfe->elfScript ) FIXME
549
550     return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
551 }
552
553 static char* Win32_Select( filter_t *p_filter, const char* family,
554                            bool b_bold, bool b_italic, int i_size, int *i_idx )
555 {
556     VLC_UNUSED( i_size );
557     // msg_Dbg( p_filter, "Here in Win32_Select, asking for %s", family );
558
559     /* */
560     LOGFONT lf;
561     lf.lfCharSet = DEFAULT_CHARSET;
562     if( b_italic )
563         lf.lfItalic = true;
564     if( b_bold )
565         lf.lfWeight = FW_BOLD;
566     strncpy( (LPSTR)&lf.lfFaceName, family, 32);
567
568     /* */
569     char *psz_filename = NULL;
570     HDC hDC = GetDC( NULL );
571     EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
572     ReleaseDC(NULL, hDC);
573
574     if( psz_filename == NULL )
575         return NULL;
576
577     /* FIXME: increase i_idx, when concatenated strings  */
578     i_idx = 0;
579
580     /* */
581     char *psz_tmp;
582     if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
583         return NULL;
584     return psz_tmp;
585 }
586 #endif
587
588 #endif
589
590
591 /*****************************************************************************
592  * RenderYUVP: place string in picture
593  *****************************************************************************
594  * This function merges the previously rendered freetype glyphs into a picture
595  *****************************************************************************/
596 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
597                        line_desc_t *p_line,
598                        FT_BBox *p_bbox )
599 {
600     VLC_UNUSED(p_filter);
601     static const uint8_t pi_gamma[16] =
602         {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
603           0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
604
605     uint8_t *p_dst;
606     video_format_t fmt;
607     int i, x, y, i_pitch;
608     uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
609
610     /* Create a new subpicture region */
611     video_format_Init( &fmt, VLC_CODEC_YUVP );
612     fmt.i_width          =
613     fmt.i_visible_width  = p_bbox->xMax - p_bbox->xMin + 4;
614     fmt.i_height         =
615     fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
616
617     assert( !p_region->p_picture );
618     p_region->p_picture = picture_NewFromFormat( &fmt );
619     if( !p_region->p_picture )
620         return VLC_EGENERIC;
621     fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
622     p_region->fmt = fmt;
623
624     /* Calculate text color components
625      * Only use the first color */
626     int i_alpha = 0xff - ((p_line->p_character[0].i_color >> 24) & 0xff);
627     YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
628
629     /* Build palette */
630     fmt.p_palette->i_entries = 16;
631     for( i = 0; i < 8; i++ )
632     {
633         fmt.p_palette->palette[i][0] = 0;
634         fmt.p_palette->palette[i][1] = 0x80;
635         fmt.p_palette->palette[i][2] = 0x80;
636         fmt.p_palette->palette[i][3] = pi_gamma[i];
637         fmt.p_palette->palette[i][3] =
638             (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
639     }
640     for( i = 8; i < fmt.p_palette->i_entries; i++ )
641     {
642         fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
643         fmt.p_palette->palette[i][1] = i_u;
644         fmt.p_palette->palette[i][2] = i_v;
645         fmt.p_palette->palette[i][3] = pi_gamma[i];
646         fmt.p_palette->palette[i][3] =
647             (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
648     }
649
650     p_dst = p_region->p_picture->Y_PIXELS;
651     i_pitch = p_region->p_picture->Y_PITCH;
652
653     /* Initialize the region pixels */
654     memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
655
656     for( ; p_line != NULL; p_line = p_line->p_next )
657     {
658         int i_align_left = 0;
659         if( p_line->i_width < fmt.i_visible_width )
660         {
661             if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
662                 i_align_left = ( fmt.i_visible_width - p_line->i_width );
663             else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
664                 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
665         }
666         int i_align_top = 0;
667
668         for( i = 0; i < p_line->i_character_count; i++ )
669         {
670             const line_character_t *ch = &p_line->p_character[i];
671             FT_BitmapGlyph p_glyph = ch->p_glyph;
672
673             int i_glyph_y = i_align_top  - p_glyph->top  + p_bbox->yMax + p_line->i_base_line;
674             int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
675
676             for( y = 0; y < p_glyph->bitmap.rows; y++ )
677             {
678                 for( x = 0; x < p_glyph->bitmap.width; x++ )
679                 {
680                     if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
681                         p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
682                             (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
683                 }
684             }
685         }
686     }
687
688     /* Outlining (find something better than nearest neighbour filtering ?) */
689     if( 1 )
690     {
691         uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
692         uint8_t *p_top = p_dst; /* Use 1st line as a cache */
693         uint8_t left, current;
694
695         for( y = 1; y < (int)fmt.i_height - 1; y++ )
696         {
697             if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
698             p_dst += p_region->p_picture->Y_PITCH;
699             left = 0;
700
701             for( x = 1; x < (int)fmt.i_width - 1; x++ )
702             {
703                 current = p_dst[x];
704                 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
705                              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;
706                 left = current;
707             }
708         }
709         memset( p_top, 0, fmt.i_width );
710     }
711
712     return VLC_SUCCESS;
713 }
714
715 /*****************************************************************************
716  * RenderYUVA: place string in picture
717  *****************************************************************************
718  * This function merges the previously rendered freetype glyphs into a picture
719  *****************************************************************************/
720 static inline void BlendYUVAPixel( picture_t *p_picture,
721                                    int i_picture_x, int i_picture_y,
722                                    int i_a, int i_y, int i_u, int i_v,
723                                    int i_alpha )
724 {
725     int i_an = i_a * i_alpha / 255;
726
727     uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
728     uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
729     uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
730     uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
731
732     int i_ao = *p_a;
733     if( i_ao == 0 )
734     {
735         *p_y = i_y;
736         *p_u = i_u;
737         *p_v = i_v;
738         *p_a = i_an;
739     }
740     else
741     {
742         *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
743         if( *p_a != 0 )
744         {
745             *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
746             *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
747             *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
748         }
749     }
750 }
751
752 static inline void BlendYUVAGlyph( picture_t *p_picture,
753                                    int i_picture_x, int i_picture_y,
754                                    int i_a, int i_y, int i_u, int i_v,
755                                    FT_BitmapGlyph p_glyph )
756 {
757     for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
758     {
759         for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
760             BlendYUVAPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
761                             i_a, i_y, i_u, i_v,
762                             p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
763     }
764 }
765
766 static inline void BlendYUVALine( picture_t *p_picture,
767                                   int i_picture_x, int i_picture_y,
768                                   int i_a, int i_y, int i_u, int i_v,
769                                   const line_character_t *p_current,
770                                   const line_character_t *p_next )
771 {
772     int i_line_width = p_current->p_glyph->bitmap.width;
773     if( p_next )
774         i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
775
776     for( int dx = 0; dx < i_line_width; dx++ )
777     {
778         for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
779             BlendYUVAPixel( p_picture,
780                             i_picture_x + dx,
781                             i_picture_y + p_current->i_line_offset + dy,
782                             i_a, i_y, i_u, i_v, 0xff );
783     }
784 }
785
786 static int RenderYUVA( filter_t *p_filter,
787                        subpicture_region_t *p_region,
788                        line_desc_t *p_line_head,
789                        FT_BBox *p_bbox,
790                        int i_margin )
791 {
792     filter_sys_t *p_sys = p_filter->p_sys;
793
794     /* Create a new subpicture region */
795     const int i_text_width  = p_bbox->xMax - p_bbox->xMin;
796     const int i_text_height = p_bbox->yMax - p_bbox->yMin;
797     video_format_t fmt;
798     video_format_Init( &fmt, VLC_CODEC_YUVA );
799     fmt.i_width          =
800     fmt.i_visible_width  = i_text_width  + 2 * i_margin;
801     fmt.i_height         =
802     fmt.i_visible_height = i_text_height + 2 * i_margin;
803
804     picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
805     if( !p_region->p_picture )
806         return VLC_EGENERIC;
807     p_region->fmt = fmt;
808
809     /* Initialize the picture background */
810     uint8_t i_a = p_sys->i_background_opacity;
811     uint8_t i_y, i_u, i_v;
812     YUVFromRGB( p_sys->i_background_color, &i_y, &i_u, &i_v );
813
814     memset( p_picture->p[0].p_pixels, i_y,
815             p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
816     memset( p_picture->p[1].p_pixels, i_u,
817             p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
818     memset( p_picture->p[2].p_pixels, i_v,
819             p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
820     memset( p_picture->p[3].p_pixels, i_a,
821             p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
822
823     /* Render outline glyphs in the first pass, and then the normal glyphs */
824     for( int g = 0; g < 2; g++ )
825     {
826         /* Render all lines */
827         for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
828         {
829             int i_align_left = i_margin;
830             if( p_line->i_width < i_text_width )
831             {
832                 /* Left offset to take into account alignment */
833                 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
834                     i_align_left += ( i_text_width - p_line->i_width );
835                 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
836                     i_align_left += ( i_text_width - p_line->i_width ) / 2;
837             }
838             int i_align_top = i_margin;
839
840             /* Render all glyphs and underline/strikethrough */
841             for( int i = 0; i < p_line->i_character_count; i++ )
842             {
843                 const line_character_t *ch = &p_line->p_character[i];
844                 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_outline : ch->p_glyph;
845                 if( !p_glyph )
846                     continue;
847
848                 uint32_t i_color = ch->i_color;
849                 i_a = 0xff - ((i_color >> 24) & 0xff);
850                 if( g == 0 )
851                 {
852                     i_a = i_a * p_sys->i_outline_opacity / 255;
853                     i_color = p_sys->i_outline_color;
854                 }
855                 YUVFromRGB( i_color, &i_y, &i_u, &i_v );
856
857                 int i_glyph_y = i_align_top  - p_glyph->top  + p_bbox->yMax + p_line->i_base_line;
858                 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
859
860                 BlendYUVAGlyph( p_picture,
861                                 i_glyph_x, i_glyph_y,
862                                 i_a, i_y, i_u, i_v,
863                                 p_glyph );
864
865                 /* underline/strikethrough are only rendered for the normal glyph */
866                 if( g == 1 && ch->i_line_thickness > 0 )
867                     BlendYUVALine( p_picture,
868                                    i_glyph_x, i_glyph_y + p_glyph->top,
869                                    i_a, i_y, i_u, i_v,
870                                    &ch[0],
871                                    i + 1 < p_line->i_character_count ? &ch[1] : NULL );
872             }
873         }
874     }
875
876     return VLC_SUCCESS;
877 }
878
879 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
880                                   uint32_t i_font_color, uint32_t i_karaoke_bg_color,
881                                   int i_style_flags )
882 {
883     text_style_t *p_style = text_style_New();
884     if( !p_style )
885         return NULL;
886
887     p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
888     p_style->i_font_size  = i_font_size;
889     p_style->i_font_color = (i_font_color & 0x00ffffff) >>  0;
890     p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
891     p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >>  0;
892     p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
893     p_style->i_style_flags |= i_style_flags;
894     return p_style;
895 }
896
897 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
898                      uint32_t i_color, uint32_t i_karaoke_bg_color )
899 {
900     if( !p_font )
901         return VLC_EGENERIC;
902
903     font_stack_t *p_new = malloc( sizeof(*p_new) );
904     if( !p_new )
905         return VLC_ENOMEM;
906
907     p_new->p_next = NULL;
908
909     if( psz_name )
910         p_new->psz_name = strdup( psz_name );
911     else
912         p_new->psz_name = NULL;
913
914     p_new->i_size              = i_size;
915     p_new->i_color             = i_color;
916     p_new->i_karaoke_bg_color  = i_karaoke_bg_color;
917
918     if( !*p_font )
919     {
920         *p_font = p_new;
921     }
922     else
923     {
924         font_stack_t *p_last;
925
926         for( p_last = *p_font;
927              p_last->p_next;
928              p_last = p_last->p_next )
929         ;
930
931         p_last->p_next = p_new;
932     }
933     return VLC_SUCCESS;
934 }
935
936 static int PopFont( font_stack_t **p_font )
937 {
938     font_stack_t *p_last, *p_next_to_last;
939
940     if( !p_font || !*p_font )
941         return VLC_EGENERIC;
942
943     p_next_to_last = NULL;
944     for( p_last = *p_font;
945          p_last->p_next;
946          p_last = p_last->p_next )
947     {
948         p_next_to_last = p_last;
949     }
950
951     if( p_next_to_last )
952         p_next_to_last->p_next = NULL;
953     else
954         *p_font = NULL;
955
956     free( p_last->psz_name );
957     free( p_last );
958
959     return VLC_SUCCESS;
960 }
961
962 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
963                      uint32_t *i_color, uint32_t *i_karaoke_bg_color )
964 {
965     font_stack_t *p_last;
966
967     if( !p_font || !*p_font )
968         return VLC_EGENERIC;
969
970     for( p_last=*p_font;
971          p_last->p_next;
972          p_last=p_last->p_next )
973     ;
974
975     *psz_name            = p_last->psz_name;
976     *i_size              = p_last->i_size;
977     *i_color             = p_last->i_color;
978     *i_karaoke_bg_color  = p_last->i_karaoke_bg_color;
979
980     return VLC_SUCCESS;
981 }
982
983 static const struct {
984     const char *psz_name;
985     uint32_t   i_value;
986 } p_html_colors[] = {
987     /* Official html colors */
988     { "Aqua",    0x00FFFF },
989     { "Black",   0x000000 },
990     { "Blue",    0x0000FF },
991     { "Fuchsia", 0xFF00FF },
992     { "Gray",    0x808080 },
993     { "Green",   0x008000 },
994     { "Lime",    0x00FF00 },
995     { "Maroon",  0x800000 },
996     { "Navy",    0x000080 },
997     { "Olive",   0x808000 },
998     { "Purple",  0x800080 },
999     { "Red",     0xFF0000 },
1000     { "Silver",  0xC0C0C0 },
1001     { "Teal",    0x008080 },
1002     { "White",   0xFFFFFF },
1003     { "Yellow",  0xFFFF00 },
1004
1005     /* Common ones */
1006     { "AliceBlue", 0xF0F8FF },
1007     { "AntiqueWhite", 0xFAEBD7 },
1008     { "Aqua", 0x00FFFF },
1009     { "Aquamarine", 0x7FFFD4 },
1010     { "Azure", 0xF0FFFF },
1011     { "Beige", 0xF5F5DC },
1012     { "Bisque", 0xFFE4C4 },
1013     { "Black", 0x000000 },
1014     { "BlanchedAlmond", 0xFFEBCD },
1015     { "Blue", 0x0000FF },
1016     { "BlueViolet", 0x8A2BE2 },
1017     { "Brown", 0xA52A2A },
1018     { "BurlyWood", 0xDEB887 },
1019     { "CadetBlue", 0x5F9EA0 },
1020     { "Chartreuse", 0x7FFF00 },
1021     { "Chocolate", 0xD2691E },
1022     { "Coral", 0xFF7F50 },
1023     { "CornflowerBlue", 0x6495ED },
1024     { "Cornsilk", 0xFFF8DC },
1025     { "Crimson", 0xDC143C },
1026     { "Cyan", 0x00FFFF },
1027     { "DarkBlue", 0x00008B },
1028     { "DarkCyan", 0x008B8B },
1029     { "DarkGoldenRod", 0xB8860B },
1030     { "DarkGray", 0xA9A9A9 },
1031     { "DarkGrey", 0xA9A9A9 },
1032     { "DarkGreen", 0x006400 },
1033     { "DarkKhaki", 0xBDB76B },
1034     { "DarkMagenta", 0x8B008B },
1035     { "DarkOliveGreen", 0x556B2F },
1036     { "Darkorange", 0xFF8C00 },
1037     { "DarkOrchid", 0x9932CC },
1038     { "DarkRed", 0x8B0000 },
1039     { "DarkSalmon", 0xE9967A },
1040     { "DarkSeaGreen", 0x8FBC8F },
1041     { "DarkSlateBlue", 0x483D8B },
1042     { "DarkSlateGray", 0x2F4F4F },
1043     { "DarkSlateGrey", 0x2F4F4F },
1044     { "DarkTurquoise", 0x00CED1 },
1045     { "DarkViolet", 0x9400D3 },
1046     { "DeepPink", 0xFF1493 },
1047     { "DeepSkyBlue", 0x00BFFF },
1048     { "DimGray", 0x696969 },
1049     { "DimGrey", 0x696969 },
1050     { "DodgerBlue", 0x1E90FF },
1051     { "FireBrick", 0xB22222 },
1052     { "FloralWhite", 0xFFFAF0 },
1053     { "ForestGreen", 0x228B22 },
1054     { "Fuchsia", 0xFF00FF },
1055     { "Gainsboro", 0xDCDCDC },
1056     { "GhostWhite", 0xF8F8FF },
1057     { "Gold", 0xFFD700 },
1058     { "GoldenRod", 0xDAA520 },
1059     { "Gray", 0x808080 },
1060     { "Grey", 0x808080 },
1061     { "Green", 0x008000 },
1062     { "GreenYellow", 0xADFF2F },
1063     { "HoneyDew", 0xF0FFF0 },
1064     { "HotPink", 0xFF69B4 },
1065     { "IndianRed", 0xCD5C5C },
1066     { "Indigo", 0x4B0082 },
1067     { "Ivory", 0xFFFFF0 },
1068     { "Khaki", 0xF0E68C },
1069     { "Lavender", 0xE6E6FA },
1070     { "LavenderBlush", 0xFFF0F5 },
1071     { "LawnGreen", 0x7CFC00 },
1072     { "LemonChiffon", 0xFFFACD },
1073     { "LightBlue", 0xADD8E6 },
1074     { "LightCoral", 0xF08080 },
1075     { "LightCyan", 0xE0FFFF },
1076     { "LightGoldenRodYellow", 0xFAFAD2 },
1077     { "LightGray", 0xD3D3D3 },
1078     { "LightGrey", 0xD3D3D3 },
1079     { "LightGreen", 0x90EE90 },
1080     { "LightPink", 0xFFB6C1 },
1081     { "LightSalmon", 0xFFA07A },
1082     { "LightSeaGreen", 0x20B2AA },
1083     { "LightSkyBlue", 0x87CEFA },
1084     { "LightSlateGray", 0x778899 },
1085     { "LightSlateGrey", 0x778899 },
1086     { "LightSteelBlue", 0xB0C4DE },
1087     { "LightYellow", 0xFFFFE0 },
1088     { "Lime", 0x00FF00 },
1089     { "LimeGreen", 0x32CD32 },
1090     { "Linen", 0xFAF0E6 },
1091     { "Magenta", 0xFF00FF },
1092     { "Maroon", 0x800000 },
1093     { "MediumAquaMarine", 0x66CDAA },
1094     { "MediumBlue", 0x0000CD },
1095     { "MediumOrchid", 0xBA55D3 },
1096     { "MediumPurple", 0x9370D8 },
1097     { "MediumSeaGreen", 0x3CB371 },
1098     { "MediumSlateBlue", 0x7B68EE },
1099     { "MediumSpringGreen", 0x00FA9A },
1100     { "MediumTurquoise", 0x48D1CC },
1101     { "MediumVioletRed", 0xC71585 },
1102     { "MidnightBlue", 0x191970 },
1103     { "MintCream", 0xF5FFFA },
1104     { "MistyRose", 0xFFE4E1 },
1105     { "Moccasin", 0xFFE4B5 },
1106     { "NavajoWhite", 0xFFDEAD },
1107     { "Navy", 0x000080 },
1108     { "OldLace", 0xFDF5E6 },
1109     { "Olive", 0x808000 },
1110     { "OliveDrab", 0x6B8E23 },
1111     { "Orange", 0xFFA500 },
1112     { "OrangeRed", 0xFF4500 },
1113     { "Orchid", 0xDA70D6 },
1114     { "PaleGoldenRod", 0xEEE8AA },
1115     { "PaleGreen", 0x98FB98 },
1116     { "PaleTurquoise", 0xAFEEEE },
1117     { "PaleVioletRed", 0xD87093 },
1118     { "PapayaWhip", 0xFFEFD5 },
1119     { "PeachPuff", 0xFFDAB9 },
1120     { "Peru", 0xCD853F },
1121     { "Pink", 0xFFC0CB },
1122     { "Plum", 0xDDA0DD },
1123     { "PowderBlue", 0xB0E0E6 },
1124     { "Purple", 0x800080 },
1125     { "Red", 0xFF0000 },
1126     { "RosyBrown", 0xBC8F8F },
1127     { "RoyalBlue", 0x4169E1 },
1128     { "SaddleBrown", 0x8B4513 },
1129     { "Salmon", 0xFA8072 },
1130     { "SandyBrown", 0xF4A460 },
1131     { "SeaGreen", 0x2E8B57 },
1132     { "SeaShell", 0xFFF5EE },
1133     { "Sienna", 0xA0522D },
1134     { "Silver", 0xC0C0C0 },
1135     { "SkyBlue", 0x87CEEB },
1136     { "SlateBlue", 0x6A5ACD },
1137     { "SlateGray", 0x708090 },
1138     { "SlateGrey", 0x708090 },
1139     { "Snow", 0xFFFAFA },
1140     { "SpringGreen", 0x00FF7F },
1141     { "SteelBlue", 0x4682B4 },
1142     { "Tan", 0xD2B48C },
1143     { "Teal", 0x008080 },
1144     { "Thistle", 0xD8BFD8 },
1145     { "Tomato", 0xFF6347 },
1146     { "Turquoise", 0x40E0D0 },
1147     { "Violet", 0xEE82EE },
1148     { "Wheat", 0xF5DEB3 },
1149     { "White", 0xFFFFFF },
1150     { "WhiteSmoke", 0xF5F5F5 },
1151     { "Yellow", 0xFFFF00 },
1152     { "YellowGreen", 0x9ACD32 },
1153
1154     { NULL, 0 }
1155 };
1156
1157 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1158                                  font_stack_t **p_fonts )
1159 {
1160     int        rv;
1161     char      *psz_fontname = NULL;
1162     uint32_t   i_font_color = 0xffffff;
1163     int        i_font_alpha = 0;
1164     uint32_t   i_karaoke_bg_color = 0x00ffffff;
1165     int        i_font_size  = 24;
1166
1167     /* Default all attributes to the top font in the stack -- in case not
1168      * all attributes are specified in the sub-font
1169      */
1170     if( VLC_SUCCESS == PeekFont( p_fonts,
1171                                  &psz_fontname,
1172                                  &i_font_size,
1173                                  &i_font_color,
1174                                  &i_karaoke_bg_color ))
1175     {
1176         psz_fontname = strdup( psz_fontname );
1177         i_font_size = i_font_size;
1178     }
1179     i_font_alpha = (i_font_color >> 24) & 0xff;
1180     i_font_color &= 0x00ffffff;
1181
1182     const char *name, *value;
1183     while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1184     {
1185         if( !strcasecmp( "face", name ) )
1186         {
1187             free( psz_fontname );
1188             psz_fontname = strdup( value );
1189         }
1190         else if( !strcasecmp( "size", name ) )
1191         {
1192             if( ( *value == '+' ) || ( *value == '-' ) )
1193             {
1194                 int i_value = atoi( value );
1195
1196                 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1197                     i_font_size += ( i_value * i_font_size ) / 10;
1198                 else if( i_value < -5 )
1199                     i_font_size = - i_value;
1200                 else if( i_value > 5 )
1201                     i_font_size = i_value;
1202             }
1203             else
1204                 i_font_size = atoi( value );
1205         }
1206         else if( !strcasecmp( "color", name ) )
1207         {
1208             if( value[0] == '#' )
1209             {
1210                 i_font_color = strtol( value + 1, NULL, 16 );
1211                 i_font_color &= 0x00ffffff;
1212             }
1213             else
1214             {
1215                 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1216                 {
1217                     if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1218                     {
1219                         i_font_color = p_html_colors[i].i_value;
1220                         break;
1221                     }
1222                 }
1223             }
1224         }
1225         else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1226         {
1227             i_font_alpha = strtol( value + 1, NULL, 16 );
1228             i_font_alpha &= 0xff;
1229         }
1230     }
1231     rv = PushFont( p_fonts,
1232                    psz_fontname,
1233                    i_font_size,
1234                    (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1235                    i_karaoke_bg_color );
1236
1237     free( psz_fontname );
1238
1239     return rv;
1240 }
1241
1242 /* Turn any multiple-whitespaces into single spaces */
1243 static void HandleWhiteSpace( char *psz_node )
1244 {
1245     char *s = strpbrk( psz_node, "\t\r\n " );
1246     while( s )
1247     {
1248         int i_whitespace = strspn( s, "\t\r\n " );
1249
1250         if( i_whitespace > 1 )
1251             memmove( &s[1],
1252                      &s[i_whitespace],
1253                      strlen( s ) - i_whitespace + 1 );
1254         *s++ = ' ';
1255
1256         s = strpbrk( s, "\t\r\n " );
1257     }
1258 }
1259
1260
1261 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1262                                             font_stack_t **p_fonts,
1263                                             int i_style_flags )
1264 {
1265     char       *psz_fontname = NULL;
1266     uint32_t    i_font_color = p_sys->i_font_color & 0x00ffffff;
1267     uint32_t    i_karaoke_bg_color = i_font_color;
1268     int         i_font_size  = p_sys->i_font_size;
1269
1270     if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1271                   &i_font_color, &i_karaoke_bg_color ) )
1272         return NULL;
1273
1274     return CreateStyle( psz_fontname, i_font_size, i_font_color,
1275                         i_karaoke_bg_color,
1276                         i_style_flags );
1277 }
1278
1279 static unsigned SetupText( filter_t *p_filter,
1280                            uint32_t *psz_text_out,
1281                            text_style_t **pp_styles,
1282                            uint32_t *pi_k_dates,
1283
1284                            const char *psz_text_in,
1285                            text_style_t *p_style,
1286                            uint32_t i_k_date )
1287 {
1288     size_t i_string_length;
1289
1290     size_t i_string_bytes;
1291 #if defined(WORDS_BIGENDIAN)
1292     uint32_t *psz_tmp = ToCharset( "UCS-4BE", psz_text_in, &i_string_bytes );
1293 #else
1294     uint32_t *psz_tmp = ToCharset( "UCS-4LE", psz_text_in, &i_string_bytes );
1295 #endif
1296     if( psz_tmp )
1297     {
1298         memcpy( psz_text_out, psz_tmp, i_string_bytes );
1299         i_string_length = i_string_bytes / 4;
1300         free( psz_tmp );
1301     }
1302     else
1303     {
1304         msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1305         i_string_length = 0;
1306     }
1307
1308     if( i_string_length > 0 )
1309     {
1310         for( unsigned i = 0; i < i_string_length; i++ )
1311             pp_styles[i] = p_style;
1312     }
1313     else
1314     {
1315         text_style_Delete( p_style );
1316     }
1317     if( i_string_length > 0 && pi_k_dates )
1318     {
1319         for( unsigned i = 0; i < i_string_length; i++ )
1320             pi_k_dates[i] = i_k_date;
1321     }
1322     return i_string_length;
1323 }
1324
1325 static int ProcessNodes( filter_t *p_filter,
1326                          uint32_t *psz_text,
1327                          text_style_t **pp_styles,
1328                          uint32_t *pi_k_dates,
1329                          int *pi_len,
1330                          xml_reader_t *p_xml_reader,
1331                          text_style_t *p_font_style )
1332 {
1333     int           rv      = VLC_SUCCESS;
1334     filter_sys_t *p_sys   = p_filter->p_sys;
1335     int i_text_length     = 0;
1336     font_stack_t *p_fonts = NULL;
1337     uint32_t i_k_date     = 0;
1338
1339     int i_style_flags = 0;
1340
1341     if( p_font_style )
1342     {
1343         rv = PushFont( &p_fonts,
1344                p_font_style->psz_fontname,
1345                p_font_style->i_font_size,
1346                (p_font_style->i_font_color & 0xffffff) |
1347                    ((p_font_style->i_font_alpha & 0xff) << 24),
1348                (p_font_style->i_karaoke_background_color & 0xffffff) |
1349                    ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1350
1351         i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1352                                                        STYLE_ITALIC |
1353                                                        STYLE_UNDERLINE |
1354                                                        STYLE_STRIKEOUT);
1355     }
1356 #ifdef HAVE_STYLES
1357     else
1358     {
1359         rv = PushFont( &p_fonts,
1360                        p_sys->psz_fontfamily,
1361                        p_sys->i_font_size,
1362                        (p_sys->i_font_color & 0xffffff) |
1363                           (((255-p_sys->i_font_opacity) & 0xff) << 24),
1364                        0x00ffffff );
1365     }
1366 #endif
1367
1368     if( rv != VLC_SUCCESS )
1369         return rv;
1370
1371     const char *node;
1372     int type;
1373
1374     while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1375     {
1376         switch ( type )
1377         {
1378             case XML_READER_ENDELEM:
1379                 if( !strcasecmp( "font", node ) )
1380                     PopFont( &p_fonts );
1381                 else if( !strcasecmp( "b", node ) )
1382                     i_style_flags &= ~STYLE_BOLD;
1383                else if( !strcasecmp( "i", node ) )
1384                     i_style_flags &= ~STYLE_ITALIC;
1385                 else if( !strcasecmp( "u", node ) )
1386                     i_style_flags &= ~STYLE_UNDERLINE;
1387                 else if( !strcasecmp( "s", node ) )
1388                     i_style_flags &= ~STYLE_STRIKEOUT;
1389                 break;
1390
1391             case XML_READER_STARTELEM:
1392                 if( !strcasecmp( "font", node ) )
1393                     HandleFontAttributes( p_xml_reader, &p_fonts );
1394                 else if( !strcasecmp( "b", node ) )
1395                     i_style_flags |= STYLE_BOLD;
1396                 else if( !strcasecmp( "i", node ) )
1397                     i_style_flags |= STYLE_ITALIC;
1398                 else if( !strcasecmp( "u", node ) )
1399                     i_style_flags |= STYLE_UNDERLINE;
1400                 else if( !strcasecmp( "s", node ) )
1401                     i_style_flags |= STYLE_STRIKEOUT;
1402                 else if( !strcasecmp( "br", node ) )
1403                 {
1404                     i_text_length += SetupText( p_filter,
1405                                                 &psz_text[i_text_length],
1406                                                 &pp_styles[i_text_length],
1407                                                 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1408                                                 "\n",
1409                                                 GetStyleFromFontStack( p_sys,
1410                                                                        &p_fonts,
1411                                                                        i_style_flags ),
1412                                                 i_k_date );
1413                 }
1414                 else if( !strcasecmp( "k", node ) )
1415                 {
1416                     /* Karaoke tags */
1417                     const char *name, *value;
1418                     while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1419                     {
1420                         if( !strcasecmp( "t", name ) && value )
1421                             i_k_date += atoi( value );
1422                     }
1423                 }
1424                 break;
1425
1426             case XML_READER_TEXT:
1427             {
1428                 char *psz_node = strdup( node );
1429                 if( unlikely(!psz_node) )
1430                     break;
1431
1432                 HandleWhiteSpace( psz_node );
1433                 resolve_xml_special_chars( psz_node );
1434
1435                 i_text_length += SetupText( p_filter,
1436                                             &psz_text[i_text_length],
1437                                             &pp_styles[i_text_length],
1438                                             pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1439                                             psz_node,
1440                                             GetStyleFromFontStack( p_sys,
1441                                                                    &p_fonts,
1442                                                                    i_style_flags ),
1443                                             i_k_date );
1444                 free( psz_node );
1445                 break;
1446             }
1447         }
1448     }
1449
1450     *pi_len = i_text_length;
1451
1452     while( VLC_SUCCESS == PopFont( &p_fonts ) );
1453
1454     return VLC_SUCCESS;
1455 }
1456
1457 static void FreeLine( line_desc_t *p_line )
1458 {
1459     for( int i = 0; i < p_line->i_character_count; i++ )
1460     {
1461         line_character_t *ch = &p_line->p_character[i];
1462         FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1463         if( ch->p_outline )
1464             FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1465     }
1466
1467     free( p_line->p_character );
1468     free( p_line );
1469 }
1470
1471 static void FreeLines( line_desc_t *p_lines )
1472 {
1473     for( line_desc_t *p_line = p_lines; p_line != NULL; )
1474     {
1475         line_desc_t *p_next = p_line->p_next;
1476         FreeLine( p_line );
1477         p_line = p_next;
1478     }
1479 }
1480
1481 static line_desc_t *NewLine( int i_count )
1482 {
1483     line_desc_t *p_line = malloc( sizeof(*p_line) );
1484
1485     if( !p_line )
1486         return NULL;
1487
1488     p_line->p_next = NULL;
1489     p_line->i_width = 0;
1490     p_line->i_base_line = 0;
1491     p_line->i_character_count = 0;
1492
1493     p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1494     if( !p_line->p_character )
1495     {
1496         free( p_line );
1497         return NULL;
1498     }
1499     return p_line;
1500 }
1501
1502 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1503 {
1504     for( int k = 0; k < p_sys->i_font_attachments; k++ )
1505     {
1506         input_attachment_t *p_attach   = p_sys->pp_font_attachments[k];
1507         int                 i_font_idx = 0;
1508         FT_Face             p_face = NULL;
1509
1510         while( 0 == FT_New_Memory_Face( p_sys->p_library,
1511                                         p_attach->p_data,
1512                                         p_attach->i_data,
1513                                         i_font_idx,
1514                                         &p_face ))
1515         {
1516             if( p_face )
1517             {
1518                 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD)    ? STYLE_BOLD   : 0) |
1519                                        ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1520                 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1521                     (p_style->i_style_flags & (STYLE_BOLD | STYLE_BOLD)) == i_style_received )
1522                     return p_face;
1523
1524                 FT_Done_Face( p_face );
1525             }
1526             i_font_idx++;
1527         }
1528     }
1529     return NULL;
1530 }
1531
1532 static FT_Face LoadFace( filter_t *p_filter,
1533                          const text_style_t *p_style )
1534 {
1535     filter_sys_t *p_sys = p_filter->p_sys;
1536
1537     /* Look for a match amongst our attachments first */
1538     FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1539
1540     /* Load system wide font otheriwse */
1541     if( !p_face )
1542     {
1543         int  i_idx = 0;
1544         char *psz_fontfile;
1545 #ifdef HAVE_FONTCONFIG
1546         psz_fontfile = FontConfig_Select( NULL,
1547                                           p_style->psz_fontname,
1548                                           (p_style->i_style_flags & STYLE_BOLD) != 0,
1549                                           (p_style->i_style_flags & STYLE_ITALIC) != 0,
1550                                           -1,
1551                                           &i_idx );
1552 #elif defined( WIN32 )
1553         psz_fontfile = Win32_Select( p_filter,
1554                                     p_style->psz_fontname,
1555                                     (p_style->i_style_flags & STYLE_BOLD) != 0,
1556                                     (p_style->i_style_flags & STYLE_ITALIC) != 0,
1557                                     -1,
1558                                     &i_idx );
1559 #else
1560         psz_fontfile = NULL;
1561 #endif
1562         if( !psz_fontfile )
1563             return NULL;
1564
1565         if( *psz_fontfile == '\0' )
1566         {
1567             msg_Warn( p_filter,
1568                       "We were not able to find a matching font: \"%s\" (%s %s),"
1569                       " so using default font",
1570                       p_style->psz_fontname,
1571                       (p_style->i_style_flags & STYLE_BOLD)   ? "Bold" : "",
1572                       (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1573             p_face = NULL;
1574         }
1575         else
1576         {
1577             if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1578                 p_face = NULL;
1579         }
1580         free( psz_fontfile );
1581     }
1582     if( !p_face )
1583         return NULL;
1584
1585     if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1586     {
1587         /* We've loaded a font face which is unhelpful for actually
1588          * rendering text - fallback to the default one.
1589          */
1590         FT_Done_Face( p_face );
1591         return NULL;
1592     }
1593     return p_face;
1594 }
1595
1596 static bool FaceStyleEquals( const text_style_t *p_style1,
1597                              const text_style_t *p_style2 )
1598 {
1599     if( !p_style1 || !p_style2 )
1600         return false;
1601     if( p_style1 == p_style2 )
1602         return true;
1603
1604     const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
1605     return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
1606            !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
1607 }
1608
1609 static int GetGlyph( filter_t *p_filter,
1610                      FT_Glyph *pp_glyph,   FT_BBox *p_glyph_bbox,
1611                      FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1612
1613                      FT_Face  p_face,
1614                      int i_glyph_index,
1615                      int i_style_flags,
1616                      FT_Vector *p_pen )
1617 {
1618     if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1619         FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1620     {
1621         msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1622         return VLC_EGENERIC;
1623     }
1624
1625     /* Do synthetic styling now that Freetype supports it;
1626      * ie. if the font we have loaded is NOT already in the
1627      * style that the tags want, then switch it on; if they
1628      * are then don't. */
1629     if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1630         FT_GlyphSlot_Embolden( p_face->glyph );
1631     if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1632         FT_GlyphSlot_Oblique( p_face->glyph );
1633
1634     FT_Glyph glyph;
1635     if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1636     {
1637         msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1638         return VLC_EGENERIC;
1639     }
1640
1641     FT_Glyph outline = NULL;
1642     if( p_filter->p_sys->p_stroker )
1643     {
1644         outline = glyph;
1645         FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 );
1646         FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1647
1648         FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1649     }
1650     *pp_outline = outline;
1651
1652     if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1653     {
1654         FT_Done_Glyph( glyph );
1655         return VLC_EGENERIC;
1656     }
1657
1658     FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1659     *pp_glyph = glyph;
1660
1661     return VLC_SUCCESS;
1662 }
1663
1664 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1665 {
1666     FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1667     if( p_bbox->xMin >= p_bbox->xMax )
1668     {
1669         p_bbox->xMin = FT_CEIL(p_pen->x);
1670         p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1671         glyph_bmp->left = p_bbox->xMin;
1672     }
1673     if( p_bbox->yMin >= p_bbox->yMax )
1674     {
1675         p_bbox->yMax = FT_CEIL(p_pen->y);
1676         p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1677         glyph_bmp->top  = p_bbox->yMax;
1678     }
1679 }
1680
1681 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1682 {
1683     p_max->xMin = __MIN(p_max->xMin, p->xMin);
1684     p_max->yMin = __MIN(p_max->yMin, p->yMin);
1685     p_max->xMax = __MAX(p_max->xMax, p->xMax);
1686     p_max->yMax = __MAX(p_max->yMax, p->yMax);
1687 }
1688
1689 static int ProcessLines( filter_t *p_filter,
1690                          line_desc_t **pp_lines,
1691                          FT_BBox     *p_bbox,
1692                          int         *pi_max_face_height,
1693
1694                          uint32_t *psz_text,
1695                          text_style_t **pp_styles,
1696                          uint32_t *pi_k_dates,
1697                          int i_len )
1698 {
1699     filter_sys_t   *p_sys = p_filter->p_sys;
1700     uint32_t       *p_fribidi_string = NULL;
1701     text_style_t   **pp_fribidi_styles = NULL;
1702     int            *p_new_positions = NULL;
1703
1704 #if defined(HAVE_FRIBIDI)
1705     {
1706         int    *p_old_positions;
1707         int start_pos, pos = 0;
1708
1709         pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1710
1711         p_fribidi_string  = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1712         p_old_positions   = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1713         p_new_positions   = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1714
1715         if( ! pp_fribidi_styles ||
1716             ! p_fribidi_string ||
1717             ! p_old_positions ||
1718             ! p_new_positions )
1719         {
1720             free( p_old_positions );
1721             free( p_new_positions );
1722             free( p_fribidi_string );
1723             free( pp_fribidi_styles );
1724             return VLC_ENOMEM;
1725         }
1726
1727         /* Do bidi conversion line-by-line */
1728         while(pos < i_len)
1729         {
1730             while(pos < i_len) {
1731                 if (psz_text[pos] != '\n')
1732                     break;
1733                 p_fribidi_string[pos] = psz_text[pos];
1734                 pp_fribidi_styles[pos] = pp_styles[pos];
1735                 p_new_positions[pos] = pos;
1736                 ++pos;
1737             }
1738             start_pos = pos;
1739             while(pos < i_len) {
1740                 if (psz_text[pos] == '\n')
1741                     break;
1742                 ++pos;
1743             }
1744             if (pos > start_pos)
1745             {
1746 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1747                 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1748 #else
1749                 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1750 #endif
1751                 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1752                         pos - start_pos, &base_dir,
1753                         (FriBidiChar*)p_fribidi_string + start_pos,
1754                         p_new_positions + start_pos,
1755                         p_old_positions,
1756                         NULL );
1757                 for( int j = start_pos; j < pos; j++ )
1758                 {
1759                     pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1760                     p_new_positions[ j ] += start_pos;
1761                 }
1762             }
1763         }
1764         p_fribidi_string[ i_len ] = 0;
1765         free( p_old_positions );
1766
1767         pp_styles = pp_fribidi_styles;
1768         psz_text = p_fribidi_string;
1769     }
1770 #endif
1771     /* Work out the karaoke */
1772     uint8_t *pi_karaoke_bar = NULL;
1773     if( pi_k_dates )
1774     {
1775         pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1776         if( pi_karaoke_bar )
1777         {
1778             int64_t i_elapsed  = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1779             for( int i = 0; i < i_len; i++ )
1780             {
1781                 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1782                 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1783             }
1784         }
1785     }
1786     free( p_new_positions );
1787
1788     *pi_max_face_height = 0;
1789     *pp_lines = NULL;
1790     line_desc_t **pp_line_next = pp_lines;
1791
1792     FT_BBox bbox = {
1793         .xMin = INT_MAX,
1794         .yMin = INT_MAX,
1795         .xMax = INT_MIN,
1796         .yMax = INT_MIN,
1797     };
1798     int i_face_height_previous = 0;
1799     int i_base_line = 0;
1800     const text_style_t *p_previous_style = NULL;
1801     FT_Face p_face = NULL;
1802     for( int i_start = 0; i_start < i_len; )
1803     {
1804         /* Compute the length of the current text line */
1805         int i_length = 0;
1806         while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
1807             i_length++;
1808
1809         /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1810         line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
1811         int i_index = i_start;
1812         FT_Vector pen = {
1813             .x = 0,
1814             .y = 0,
1815         };
1816         int i_face_height = 0;
1817         FT_BBox line_bbox = {
1818             .xMin = INT_MAX,
1819             .yMin = INT_MAX,
1820             .xMax = INT_MIN,
1821             .yMax = INT_MIN,
1822         };
1823         int i_ul_offset = 0;
1824         int i_ul_thickness = 0;
1825         typedef struct {
1826             int       i_index;
1827             FT_Vector pen;
1828             FT_BBox   line_bbox;
1829             int i_face_height;
1830             int i_ul_offset;
1831             int i_ul_thickness;
1832         } break_point_t;
1833         break_point_t break_point;
1834         break_point_t break_point_fallback;
1835
1836 #define SAVE_BP(dst) do { \
1837         dst.i_index = i_index; \
1838         dst.pen = pen; \
1839         dst.line_bbox = line_bbox; \
1840         dst.i_face_height = i_face_height; \
1841         dst.i_ul_offset = i_ul_offset; \
1842         dst.i_ul_thickness = i_ul_thickness; \
1843     } while(0)
1844
1845         SAVE_BP( break_point );
1846         SAVE_BP( break_point_fallback );
1847
1848         while( i_index < i_start + i_length )
1849         {
1850             /* Split by common FT_Face + Size */
1851             const text_style_t *p_current_style = pp_styles[i_index];
1852             int i_part_length = 0;
1853             while( i_index + i_part_length < i_start + i_length )
1854             {
1855                 const text_style_t *p_style = pp_styles[i_index + i_part_length];
1856                 if( !FaceStyleEquals( p_style, p_current_style ) ||
1857                     p_style->i_font_size != p_current_style->i_font_size )
1858                     break;
1859                 i_part_length++;
1860             }
1861
1862             /* (Re)load/reconfigure the face if needed */
1863             if( !FaceStyleEquals( p_current_style, p_previous_style ) )
1864             {
1865                 if( p_face )
1866                     FT_Done_Face( p_face );
1867                 p_previous_style = NULL;
1868
1869                 p_face = LoadFace( p_filter, p_current_style );
1870             }
1871             FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
1872             if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
1873             {
1874                 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
1875                     msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
1876                 if( p_sys->p_stroker )
1877                 {
1878                     int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
1879                     FT_Stroker_Set( p_sys->p_stroker,
1880                                     i_radius,
1881                                     FT_STROKER_LINECAP_ROUND,
1882                                     FT_STROKER_LINEJOIN_ROUND, 0 );
1883                 }
1884             }
1885             p_previous_style = p_current_style;
1886
1887             i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
1888                                                                    p_current_face->size->metrics.y_scale)));
1889
1890             /* Render the part */
1891             bool b_break_line = false;
1892             int i_glyph_last = 0;
1893             while( i_part_length > 0 )
1894             {
1895                 const text_style_t *p_glyph_style = pp_styles[i_index];
1896                 uint32_t character = psz_text[i_index];
1897                 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
1898
1899                 /* Get kerning vector */
1900                 FT_Vector kerning = { .x = 0, .y = 0 };
1901                 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
1902                     FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
1903
1904                 /* Get the glyph bitmap and its bounding box and all the associated properties */
1905                 FT_Vector pen_new = {
1906                     .x = pen.x + kerning.x,
1907                     .y = pen.y + kerning.y,
1908                 };
1909                 FT_Glyph glyph;
1910                 FT_BBox  glyph_bbox;
1911                 FT_Glyph outline;
1912                 FT_BBox  outline_bbox;
1913                 if( GetGlyph( p_filter,
1914                               &glyph, &glyph_bbox,
1915                               &outline, &outline_bbox,
1916                               p_current_face, i_glyph_index, p_glyph_style->i_style_flags, &pen_new ) )
1917                     goto next;
1918
1919                 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
1920                 if( outline )
1921                     FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
1922                 /* FIXME and what about outline */
1923
1924                 bool     b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
1925                 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
1926                                                 (p_glyph_style->i_karaoke_background_alpha << 24))
1927                                              : (p_glyph_style->i_font_color |
1928                                                 (p_glyph_style->i_font_alpha << 24));
1929                 int i_line_offset    = 0;
1930                 int i_line_thickness = 0;
1931                 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1932                 {
1933                     i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
1934                                                             p_current_face->size->metrics.y_scale)) );
1935
1936                     i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
1937                                                               p_current_face->size->metrics.y_scale)) );
1938
1939                     if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
1940                     {
1941                         /* Move the baseline to make it strikethrough instead of
1942                          * underline. That means that strikethrough takes precedence
1943                          */
1944                         i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
1945                                                                  p_current_face->size->metrics.y_scale)) );
1946                     }
1947                     else if( i_line_thickness > 0 )
1948                     {
1949                         glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
1950
1951                         /* The real underline thickness and position are
1952                          * updated once the whole line has been parsed */
1953                         i_ul_offset = __MAX( i_ul_offset, i_line_offset );
1954                         i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
1955                         i_line_thickness = -1;
1956                     }
1957                 }
1958                 FT_BBox line_bbox_new = line_bbox;
1959                 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
1960                 if( outline )
1961                     BBoxEnlarge( &line_bbox_new, &outline_bbox );
1962
1963                 b_break_line = i_index > i_start &&
1964                                line_bbox_new.xMax - line_bbox_new.xMin >= p_filter->fmt_out.video.i_visible_width;
1965                 if( b_break_line )
1966                 {
1967                     FT_Done_Glyph( glyph );
1968                     if( outline )
1969                         FT_Done_Glyph( outline );
1970
1971                     break_point_t *p_bp = NULL;
1972                     if( break_point.i_index > i_start )
1973                         p_bp = &break_point;
1974                     else if( break_point_fallback.i_index > i_start )
1975                         p_bp = &break_point_fallback;
1976
1977                     if( p_bp )
1978                     {
1979                         msg_Dbg( p_filter, "Breaking line");
1980                         for( int i = p_bp->i_index; i < i_index; i++ )
1981                         {
1982                             line_character_t *ch = &p_line->p_character[i - i_start];
1983                             FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1984                             if( ch->p_outline )
1985                                 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1986                         }
1987                         p_line->i_character_count = p_bp->i_index - i_start;
1988
1989                         i_index = p_bp->i_index;
1990                         pen = p_bp->pen;
1991                         line_bbox = p_bp->line_bbox;
1992                         i_face_height = p_bp->i_face_height;
1993                         i_ul_offset = p_bp->i_ul_offset;
1994                         i_ul_thickness = p_bp->i_ul_thickness;
1995                     }
1996                     else
1997                     {
1998                         msg_Err( p_filter, "Breaking unbreakable line");
1999                     }
2000                     break;
2001                 }
2002
2003                 assert( p_line->i_character_count == i_index - i_start);
2004                 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2005                     .p_glyph = (FT_BitmapGlyph)glyph,
2006                     .p_outline = (FT_BitmapGlyph)outline,
2007                     .i_color = i_color,
2008                     .i_line_offset = i_line_offset,
2009                     .i_line_thickness = i_line_thickness,
2010                 };
2011
2012                 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2013                 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2014                 line_bbox = line_bbox_new;
2015             next:
2016                 i_glyph_last = i_glyph_index;
2017                 i_part_length--;
2018                 i_index++;
2019
2020                 if( character == ' ' || character == '\t' )
2021                     SAVE_BP( break_point );
2022                 else if( character == 160 )
2023                     SAVE_BP( break_point_fallback );
2024             }
2025             if( b_break_line )
2026                 break;
2027         }
2028 #undef SAVE_BP
2029         /* Update our baseline */
2030         if( i_face_height_previous > 0 )
2031             i_base_line += __MAX(i_face_height, i_face_height_previous);
2032         i_face_height_previous = i_face_height;
2033
2034         /* Update the line bbox with the actual base line */
2035         if (line_bbox.yMax > line_bbox.yMin) {
2036             line_bbox.yMin -= i_base_line;
2037             line_bbox.yMax -= i_base_line;
2038         }
2039         BBoxEnlarge( &bbox, &line_bbox );
2040
2041         /* Terminate and append the line */
2042         if( p_line )
2043         {
2044             p_line->i_width  = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2045             p_line->i_base_line = i_base_line;
2046             if( i_ul_thickness > 0 )
2047             {
2048                 for( int i = 0; i < p_line->i_character_count; i++ )
2049                 {
2050                     line_character_t *ch = &p_line->p_character[i];
2051                     if( ch->i_line_thickness < 0 )
2052                     {
2053                         ch->i_line_offset    = i_ul_offset;
2054                         ch->i_line_thickness = i_ul_thickness;
2055                     }
2056                 }
2057             }
2058
2059             *pp_line_next = p_line;
2060             pp_line_next = &p_line->p_next;
2061         }
2062
2063         *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2064
2065         /* Skip what we have rendered and the line delimitor if present */
2066         i_start = i_index;
2067         if( i_start < i_len && psz_text[i_start] == '\n' )
2068             i_start++;
2069
2070         if( bbox.yMax - bbox.yMin >= p_filter->fmt_out.video.i_visible_height )
2071         {
2072             msg_Err( p_filter, "Truncated too high subtitle" );
2073             break;
2074         }
2075     }
2076     if( p_face )
2077         FT_Done_Face( p_face );
2078
2079     free( pp_fribidi_styles );
2080     free( p_fribidi_string );
2081     free( pi_karaoke_bar );
2082
2083     *p_bbox = bbox;
2084     return VLC_SUCCESS;
2085 }
2086
2087 /**
2088  * This function renders a text subpicture region into another one.
2089  * It also calculates the size needed for this string, and renders the
2090  * needed glyphs into memory. It is used as pf_add_string callback in
2091  * the vout method by this module
2092  */
2093 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2094                          subpicture_region_t *p_region_in, bool b_html )
2095 {
2096     filter_sys_t *p_sys = p_filter->p_sys;
2097
2098     if( !p_region_in )
2099         return VLC_EGENERIC;
2100     if( b_html && !p_region_in->psz_html )
2101         return VLC_EGENERIC;
2102     if( !b_html && !p_region_in->psz_text )
2103         return VLC_EGENERIC;
2104
2105     const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2106                                              : p_region_in->psz_text );
2107
2108     uint32_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2109     text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2110     if( !psz_text || !pp_styles )
2111     {
2112         free( psz_text );
2113         free( pp_styles );
2114         return VLC_EGENERIC;
2115     }
2116
2117     /* Reset the default fontsize in case screen metrics have changed */
2118     p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2119
2120     /* */
2121     int rv = VLC_SUCCESS;
2122     int i_text_length = 0;
2123     FT_BBox bbox;
2124     int i_max_face_height;
2125     line_desc_t *p_lines = NULL;
2126
2127     uint32_t *pi_k_durations   = NULL;
2128
2129 #ifdef HAVE_STYLES
2130     if( b_html )
2131     {
2132         stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2133                                             (uint8_t *) p_region_in->psz_html,
2134                                             strlen( p_region_in->psz_html ),
2135                                             true );
2136         if( unlikely(p_sub == NULL) )
2137             return VLC_SUCCESS;
2138
2139         xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2140         if( !p_xml_reader )
2141             p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2142         else
2143             p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2144         p_filter->p_sys->p_xml = p_xml_reader;
2145
2146         if( !p_xml_reader )
2147             rv = VLC_EGENERIC;
2148
2149         if( !rv )
2150         {
2151             /* Look for Root Node */
2152             const char *node;
2153
2154             if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2155             {
2156                 if( strcasecmp( "karaoke", node ) == 0 )
2157                 {
2158                     pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2159                 }
2160                 else if( strcasecmp( "text", node ) != 0 )
2161                 {
2162                     /* Only text and karaoke tags are supported */
2163                     msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2164                              node );
2165                     rv = VLC_EGENERIC;
2166                 }
2167             }
2168             else
2169             {
2170                 msg_Err( p_filter, "Malformed HTML subtitle" );
2171                 rv = VLC_EGENERIC;
2172             }
2173         }
2174         if( !rv )
2175         {
2176             rv = ProcessNodes( p_filter,
2177                                psz_text, pp_styles, pi_k_durations, &i_text_length,
2178                                p_xml_reader, p_region_in->p_style );
2179         }
2180
2181         if( p_xml_reader )
2182             p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2183
2184         stream_Delete( p_sub );
2185     }
2186     else
2187 #endif
2188     {
2189         text_style_t *p_style;
2190         if( p_region_in->p_style )
2191             p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2192                                    p_region_in->p_style->i_font_size,
2193                                    (p_region_in->p_style->i_font_color & 0xffffff) |
2194                                    ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2195                                    0x00ffffff,
2196                                    p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2197                                                                           STYLE_ITALIC |
2198                                                                           STYLE_UNDERLINE |
2199                                                                           STYLE_STRIKEOUT) );
2200         else
2201             p_style = CreateStyle( p_sys->psz_fontfamily,
2202                                    p_sys->i_font_size,
2203                                    (p_sys->i_font_color & 0xffffff) |
2204                                    (((255-p_sys->i_font_opacity) & 0xff) << 24),
2205                                    0x00ffffff, 0);
2206
2207         i_text_length = SetupText( p_filter,
2208                                    psz_text,
2209                                    pp_styles,
2210                                    NULL,
2211                                    p_region_in->psz_text, p_style, 0 );
2212     }
2213
2214     if( !rv && i_text_length > 0 )
2215     {
2216         rv = ProcessLines( p_filter,
2217                            &p_lines, &bbox, &i_max_face_height,
2218                            psz_text, pp_styles, pi_k_durations, i_text_length );
2219     }
2220
2221     p_region_out->i_x = p_region_in->i_x;
2222     p_region_out->i_y = p_region_in->i_y;
2223
2224     /* Don't attempt to render text that couldn't be layed out
2225      * properly. */
2226     if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2227     {
2228         if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2229             RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2230         else
2231             RenderYUVA( p_filter, p_region_out,
2232                         p_lines,
2233                         &bbox,
2234                         p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0 );
2235
2236         /* With karaoke, we're going to have to render the text a number
2237          * of times to show the progress marker on the text.
2238          */
2239         if( pi_k_durations )
2240             var_SetBool( p_filter, "text-rerender", true );
2241     }
2242
2243     FreeLines( p_lines );
2244
2245     free( psz_text );
2246     for( int i = 0; i < i_text_length; i++ )
2247     {
2248         if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2249             text_style_Delete( pp_styles[i] );
2250     }
2251     free( pp_styles );
2252     free( pi_k_durations );
2253
2254     return rv;
2255 }
2256
2257 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2258                        subpicture_region_t *p_region_in )
2259 {
2260     return RenderCommon( p_filter, p_region_out, p_region_in, false );
2261 }
2262
2263 #ifdef HAVE_STYLES
2264
2265 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2266                        subpicture_region_t *p_region_in )
2267 {
2268     return RenderCommon( p_filter, p_region_out, p_region_in, true );
2269 }
2270
2271 #endif
2272
2273 /*****************************************************************************
2274  * Create: allocates osd-text video thread output method
2275  *****************************************************************************
2276  * This function allocates and initializes a Clone vout method.
2277  *****************************************************************************/
2278 static int Create( vlc_object_t *p_this )
2279 {
2280     filter_t      *p_filter = (filter_t *)p_this;
2281     filter_sys_t  *p_sys;
2282     char          *psz_fontfile   = NULL;
2283     char          *psz_fontfamily = NULL;
2284     int            i_error = 0, fontindex = 0;
2285
2286     /* Allocate structure */
2287     p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2288     if( !p_sys )
2289         return VLC_ENOMEM;
2290
2291     p_sys->psz_fontfamily   = NULL;
2292 #ifdef HAVE_STYLES
2293     p_sys->p_xml            = NULL;
2294 #endif
2295     p_sys->p_face           = 0;
2296     p_sys->p_library        = 0;
2297     p_sys->i_font_size      = 0;
2298     p_sys->i_display_height = 0;
2299
2300     var_Create( p_filter, "freetype-rel-fontsize",
2301                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2302
2303     psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2304     p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2305     p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2306     p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
2307     p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2308     p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
2309
2310     p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
2311     p_sys->i_background_opacity = __MAX( __MIN( p_sys->i_background_opacity, 255 ), 0 );
2312     p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
2313     p_sys->i_background_color = __MAX( __MIN( p_sys->i_background_color, 0xFFFFFF ), 0 );
2314
2315     p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2316     p_sys->f_outline_thickness = __MAX( __MIN( p_sys->f_outline_thickness, 0.5 ), 0.0 );
2317     p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2318     p_sys->i_outline_opacity = __MAX( __MIN( p_sys->i_outline_opacity, 255 ), 0 );
2319     p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2320     p_sys->i_outline_color = __MAX( __MIN( p_sys->i_outline_color, 0xFFFFFF ), 0 );
2321
2322 #ifdef WIN32
2323     /* Get Windows Font folder */
2324     wchar_t wdir[MAX_PATH];
2325     if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2326     {
2327         GetWindowsDirectoryW( wdir, MAX_PATH );
2328         wcscat( wdir, L"\\fonts" );
2329     }
2330     p_sys->psz_win_fonts_path = FromWide( wdir );
2331 #endif
2332
2333     /* Set default psz_fontfamily */
2334     if( !psz_fontfamily || !*psz_fontfamily )
2335     {
2336         free( psz_fontfamily );
2337 #ifdef HAVE_STYLES
2338         psz_fontfamily = strdup( DEFAULT_FAMILY );
2339 #else
2340 # ifdef WIN32
2341         if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2342             goto error;
2343 # else
2344         psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2345 # endif
2346         msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2347 #endif
2348     }
2349
2350     /* Set the current font file */
2351     p_sys->psz_fontfamily = psz_fontfamily;
2352 #ifdef HAVE_STYLES
2353 #ifdef HAVE_FONTCONFIG
2354     FontConfig_BuildCache( p_filter );
2355
2356     /* */
2357     psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2358                                       p_sys->i_default_font_size, &fontindex );
2359 #elif defined(WIN32)
2360     psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2361                                  p_sys->i_default_font_size, &fontindex );
2362
2363 #endif
2364     msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2365
2366     /* If nothing is found, use the default family */
2367     if( !psz_fontfile )
2368         psz_fontfile = strdup( psz_fontfamily );
2369
2370 #else /* !HAVE_STYLES */
2371     /* Use the default file */
2372     psz_fontfile = psz_fontfamily;
2373 #endif
2374
2375     /* */
2376     i_error = FT_Init_FreeType( &p_sys->p_library );
2377     if( i_error )
2378     {
2379         msg_Err( p_filter, "couldn't initialize freetype" );
2380         goto error;
2381     }
2382
2383     i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2384                            fontindex, &p_sys->p_face );
2385
2386     if( i_error == FT_Err_Unknown_File_Format )
2387     {
2388         msg_Err( p_filter, "file %s have unknown format",
2389                  psz_fontfile ? psz_fontfile : "(null)" );
2390         goto error;
2391     }
2392     else if( i_error )
2393     {
2394         msg_Err( p_filter, "failed to load font file %s",
2395                  psz_fontfile ? psz_fontfile : "(null)" );
2396         goto error;
2397     }
2398 #ifdef HAVE_STYLES
2399     free( psz_fontfile );
2400 #endif
2401
2402     i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2403     if( i_error )
2404     {
2405         msg_Err( p_filter, "font has no unicode translation table" );
2406         goto error;
2407     }
2408
2409     if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2410
2411     p_sys->p_stroker = NULL;
2412     if( p_sys->f_outline_thickness > 0.001 )
2413     {
2414         i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2415         if( i_error )
2416             msg_Err( p_filter, "Failed to create stroker for outlining" );
2417     }
2418
2419     p_sys->pp_font_attachments = NULL;
2420     p_sys->i_font_attachments = 0;
2421
2422     p_filter->pf_render_text = RenderText;
2423 #ifdef HAVE_STYLES
2424     p_filter->pf_render_html = RenderHtml;
2425 #else
2426     p_filter->pf_render_html = NULL;
2427 #endif
2428
2429     LoadFontsFromAttachments( p_filter );
2430
2431     return VLC_SUCCESS;
2432
2433 error:
2434     if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2435     if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2436 #ifdef HAVE_STYLES
2437     free( psz_fontfile );
2438 #endif
2439     free( psz_fontfamily );
2440     free( p_sys );
2441     return VLC_EGENERIC;
2442 }
2443
2444 /*****************************************************************************
2445  * Destroy: destroy Clone video thread output method
2446  *****************************************************************************
2447  * Clean up all data and library connections
2448  *****************************************************************************/
2449 static void Destroy( vlc_object_t *p_this )
2450 {
2451     filter_t *p_filter = (filter_t *)p_this;
2452     filter_sys_t *p_sys = p_filter->p_sys;
2453
2454     if( p_sys->pp_font_attachments )
2455     {
2456         for( int k = 0; k < p_sys->i_font_attachments; k++ )
2457             vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2458
2459         free( p_sys->pp_font_attachments );
2460     }
2461
2462 #ifdef HAVE_STYLES
2463     if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2464 #endif
2465     free( p_sys->psz_fontfamily );
2466
2467     /* FcFini asserts calling the subfunction FcCacheFini()
2468      * even if no other library functions have been made since FcInit(),
2469      * so don't call it. */
2470
2471     if( p_sys->p_stroker )
2472         FT_Stroker_Done( p_sys->p_stroker );
2473     FT_Done_Face( p_sys->p_face );
2474     FT_Done_FreeType( p_sys->p_library );
2475     free( p_sys );
2476 }
2477
2478