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