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