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