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