1 /*****************************************************************************
2 * freetype.c : Put text on the video, using freetype2
3 *****************************************************************************
4 * Copyright (C) 2002 - 2012 VLC authors and VideoLAN
7 * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8 * Gildas Bazin <gbazin@videolan.org>
9 * Bernie Purcell <bitmap@videolan.org>
10 * Jean-Baptiste Kempf <jb@videolan.org>
11 * Felix Paul Kühne <fkuehne@videolan.org>
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU Lesser General Public License as published by
15 * the Free Software Foundation; either version 2.1 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public License
24 * along with this program; if not, write to the Free Software Foundation, Inc.,
25 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 *****************************************************************************/
28 /*****************************************************************************
30 *****************************************************************************/
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
39 #include <vlc_stream.h> /* stream_MemoryNew */
40 #include <vlc_input.h> /* vlc_input_attachment_* */
41 #include <vlc_xml.h> /* xml_reader */
42 #include <vlc_strings.h> /* resolve_xml_special_chars */
43 #include <vlc_charset.h> /* ToCharset */
44 #include <vlc_dialog.h> /* FcCache dialog */
45 #include <vlc_filter.h> /* filter_sys_t */
46 #include <vlc_text_style.h> /* text_style_t*/
50 # define SYSTEM_DEFAULT_FONT_FILE "/Library/Fonts/Arial Unicode.ttf"
51 # define SYSTEM_DEFAULT_FAMILY "Arial Unicode MS"
52 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/System/Library/Fonts/Monaco.dfont"
53 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monaco"
54 #elif defined( WIN32 )
55 # define SYSTEM_DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
56 # define SYSTEM_DEFAULT_FAMILY "Arial"
57 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "cour.ttf"
58 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Courier New"
59 #elif defined( __OS2__ )
60 # define SYSTEM_DEFAULT_FONT_FILE "/psfonts/tnrwt_k.ttf"
61 # define SYSTEM_DEFAULT_FAMILY "Times New Roman WT K"
62 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/psfonts/mtsansdk.ttf"
63 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monotype Sans Duospace WT K"
64 #elif defined( __ANDROID__ )
65 # define SYSTEM_DEFAULT_FONT_FILE "/system/fonts/DroidSans-Bold.ttf"
66 # define SYSTEM_DEFAULT_FAMILY "Droid Sans Bold"
67 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/system/fonts/DroidSansMono.ttf"
68 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Droid Sans Mono"
70 # define SYSTEM_DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
71 # define SYSTEM_DEFAULT_FAMILY "Serif Bold"
72 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeMono.ttf"
73 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monospace"
76 #ifndef DEFAULT_FONT_FILE
77 #define DEFAULT_FONT_FILE SYSTEM_DEFAULT_FONT_FILE
80 #ifndef DEFAULT_FAMILY
81 #define DEFAULT_FAMILY SYSTEM_DEFAULT_FAMILY
84 #ifndef DEFAULT_MONOSPACE_FONT_FILE
85 #define DEFAULT_MONOSPACE_FONT_FILE SYSTEM_DEFAULT_MONOSPACE_FONT_FILE
88 #ifndef DEFAULT_MONOSPACE_FAMILY
89 #define DEFAULT_MONOSPACE_FAMILY SYSTEM_DEFAULT_MONOSPACE_FAMILY
93 #include <freetype/ftsynth.h>
94 #include FT_FREETYPE_H
98 #define FT_FLOOR(X) ((X & -64) >> 6)
99 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
101 #define FT_MulFix(v, s) (((v)*(s))>>16)
106 #include <TargetConditionals.h>
107 #if !TARGET_OS_IPHONE
108 #include <Carbon/Carbon.h>
110 #include <sys/param.h> /* for MAXPATHLEN */
111 #undef HAVE_FONTCONFIG
116 #if defined(HAVE_FRIBIDI)
117 # include <fribidi/fribidi.h>
122 # include <windows.h>
125 # undef HAVE_FONTCONFIG
129 #ifdef HAVE_FONTCONFIG
130 # include <fontconfig/fontconfig.h>
137 typedef uint16_t uni_char_t;
138 # define FREETYPE_TO_UCS "UCS-2LE"
140 typedef uint32_t uni_char_t;
141 # if defined(WORDS_BIGENDIAN)
142 # define FREETYPE_TO_UCS "UCS-4BE"
144 # define FREETYPE_TO_UCS "UCS-4LE"
148 /*****************************************************************************
150 *****************************************************************************/
151 static int Create ( vlc_object_t * );
152 static void Destroy( vlc_object_t * );
154 #define FONT_TEXT N_("Font")
155 #define MONOSPACE_FONT_TEXT N_("Monospace Font")
157 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
158 #define FONT_LONGTEXT N_("Font file for the font you want to use")
160 #define FONTSIZE_TEXT N_("Font size in pixels")
161 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
162 "that will be rendered on the video. " \
163 "If set to something different than 0 this option will override the " \
164 "relative font size." )
165 #define OPACITY_TEXT N_("Text opacity")
166 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
167 "text that will be rendered on the video. 0 = transparent, " \
168 "255 = totally opaque. " )
169 #define COLOR_TEXT N_("Text default color")
170 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
171 "the video. This must be an hexadecimal (like HTML colors). The first two "\
172 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
173 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
174 #define FONTSIZER_TEXT N_("Relative font size")
175 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
176 "fonts that will be rendered on the video. If absolute font size is set, "\
177 "relative size will be overridden." )
178 #define BOLD_TEXT N_("Force bold")
180 #define BG_OPACITY_TEXT N_("Background opacity")
181 #define BG_COLOR_TEXT N_("Background color")
183 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
184 #define OUTLINE_COLOR_TEXT N_("Outline color")
185 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
187 #define SHADOW_OPACITY_TEXT N_("Shadow opacity")
188 #define SHADOW_COLOR_TEXT N_("Shadow color")
189 #define SHADOW_ANGLE_TEXT N_("Shadow angle")
190 #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
193 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
194 static const char *const ppsz_sizes_text[] = {
195 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
196 #define YUVP_TEXT N_("Use YUVP renderer")
197 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
198 "This option is only needed if you want to encode into DVB subtitles" )
200 static const int pi_color_values[] = {
201 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
202 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
203 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
205 static const char *const ppsz_color_descriptions[] = {
206 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
207 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
208 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
210 static const int pi_outline_thickness[] = {
213 static const char *const ppsz_outline_thickness[] = {
214 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
218 set_shortname( N_("Text renderer"))
219 set_description( N_("Freetype2 font renderer") )
220 set_category( CAT_VIDEO )
221 set_subcategory( SUBCAT_VIDEO_SUBPIC )
224 add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
225 add_font( "freetype-monofont", DEFAULT_MONOSPACE_FAMILY, MONOSPACE_FONT_TEXT, FAMILY_LONGTEXT, false )
227 add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
228 add_loadfile( "freetype-monofont", DEFAULT_MONOSPACE_FONT_FILE, MONOSPACE_FONT_TEXT, FONT_LONGTEXT, false )
231 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
232 FONTSIZE_LONGTEXT, true )
233 change_integer_range( 0, 4096)
236 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
237 FONTSIZER_LONGTEXT, false )
238 change_integer_list( pi_sizes, ppsz_sizes_text )
241 /* opacity valid on 0..255, with default 255 = fully opaque */
242 add_integer_with_range( "freetype-opacity", 255, 0, 255,
243 OPACITY_TEXT, OPACITY_LONGTEXT, false )
246 /* hook to the color values list, with default 0x00ffffff = white */
247 add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
248 COLOR_LONGTEXT, false )
249 change_integer_list( pi_color_values, ppsz_color_descriptions )
252 add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
255 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
256 BG_OPACITY_TEXT, NULL, false )
258 add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
260 change_integer_list( pi_color_values, ppsz_color_descriptions )
263 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
264 OUTLINE_OPACITY_TEXT, NULL, false )
266 add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
268 change_integer_list( pi_color_values, ppsz_color_descriptions )
270 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
272 change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
275 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
276 SHADOW_OPACITY_TEXT, NULL, false )
278 add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
280 change_integer_list( pi_color_values, ppsz_color_descriptions )
282 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
283 SHADOW_ANGLE_TEXT, NULL, false )
285 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
286 SHADOW_DISTANCE_TEXT, NULL, false )
289 add_obsolete_integer( "freetype-effect" );
291 add_bool( "freetype-yuvp", false, YUVP_TEXT,
292 YUVP_LONGTEXT, true )
293 set_capability( "text renderer", 100 )
294 add_shortcut( "text" )
295 set_callbacks( Create, Destroy )
299 /*****************************************************************************
301 *****************************************************************************/
305 FT_BitmapGlyph p_glyph;
306 FT_BitmapGlyph p_outline;
307 FT_BitmapGlyph p_shadow;
308 uint32_t i_color; /* ARGB color */
309 int i_line_offset; /* underline/strikethrough offset */
310 int i_line_thickness; /* underline/strikethrough thickness */
313 typedef struct line_desc_t line_desc_t;
321 int i_character_count;
322 line_character_t *p_character;
325 typedef struct font_stack_t font_stack_t;
330 uint32_t i_color; /* ARGB */
331 uint32_t i_karaoke_bg_color; /* ARGB */
333 font_stack_t *p_next;
336 /*****************************************************************************
337 * filter_sys_t: freetype local data
338 *****************************************************************************
339 * This structure is part of the video output thread descriptor.
340 * It describes the freetype specific properties of an output thread.
341 *****************************************************************************/
344 FT_Library p_library; /* handle to library */
345 FT_Face p_face; /* handle to face object */
346 FT_Stroker p_stroker;
347 uint8_t i_font_opacity;
351 uint8_t i_outline_opacity;
354 float f_shadow_vector_x;
355 float f_shadow_vector_y;
356 uint8_t i_shadow_opacity;
359 int i_default_font_size;
360 int i_display_height;
361 char* psz_fontfamily;
362 char* psz_monofontfamily;
365 char* psz_win_fonts_path;
368 input_attachment_t **pp_font_attachments;
369 int i_font_attachments;
373 static void YUVFromRGB( uint32_t i_argb,
374 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
376 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
377 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
378 int i_blue = ( i_argb & 0x000000ff );
380 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
381 802 * i_blue + 4096 + 131072 ) >> 13, 235);
382 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
383 3598 * i_blue + 4096 + 1048576) >> 13, 240);
384 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
385 -585 * i_blue + 4096 + 1048576) >> 13, 240);
387 static void RGBFromRGB( uint32_t i_argb,
388 uint8_t *pi_r, uint8_t *pi_g, uint8_t *pi_b )
390 *pi_r = ( i_argb & 0x00ff0000 ) >> 16;
391 *pi_g = ( i_argb & 0x0000ff00 ) >> 8;
392 *pi_b = ( i_argb & 0x000000ff );
394 /*****************************************************************************
395 * Make any TTF/OTF fonts present in the attachments of the media file
396 * and store them for later use by the FreeType Engine
397 *****************************************************************************/
398 static int LoadFontsFromAttachments( filter_t *p_filter )
400 filter_sys_t *p_sys = p_filter->p_sys;
401 input_attachment_t **pp_attachments;
402 int i_attachments_cnt;
404 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
407 p_sys->i_font_attachments = 0;
408 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
409 if( !p_sys->pp_font_attachments )
412 for( int k = 0; k < i_attachments_cnt; k++ )
414 input_attachment_t *p_attach = pp_attachments[k];
416 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
417 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
418 p_attach->i_data > 0 && p_attach->p_data )
420 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
424 vlc_input_attachment_Delete( p_attach );
427 free( pp_attachments );
432 static int GetFontSize( filter_t *p_filter )
434 filter_sys_t *p_sys = p_filter->p_sys;
437 if( p_sys->i_default_font_size )
439 i_size = p_sys->i_default_font_size;
443 int i_ratio = var_InheritInteger( p_filter, "freetype-rel-fontsize" );
446 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
447 p_filter->p_sys->i_display_height = p_filter->fmt_out.video.i_height;
452 msg_Warn( p_filter, "invalid fontsize, using 12" );
458 static int SetFontSize( filter_t *p_filter, int i_size )
460 filter_sys_t *p_sys = p_filter->p_sys;
464 i_size = GetFontSize( p_filter );
466 msg_Dbg( p_filter, "using fontsize: %i", i_size );
469 p_sys->i_font_size = i_size;
471 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
473 msg_Err( p_filter, "couldn't set font size to %d", i_size );
481 #ifdef HAVE_FONTCONFIG
482 static void FontConfig_BuildCache( filter_t *p_filter )
485 msg_Dbg( p_filter, "Building font databases.");
493 #if defined( WIN32 ) || defined( __APPLE__ )
494 dialog_progress_bar_t *p_dialog = NULL;
495 FcConfig *fcConfig = FcInitLoadConfig();
497 p_dialog = dialog_ProgressCreate( p_filter,
498 _("Building font cache"),
499 _("Please wait while your font cache is rebuilt.\n"
500 "This should take less than a few minutes."), NULL );
503 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
505 FcConfigBuildFonts( fcConfig );
506 #if defined( __APPLE__ )
507 // By default, scan only the directory /System/Library/Fonts.
508 // So build the set of available fonts under another directories,
509 // and add the set to the current configuration.
510 FcConfigAppFontAddDir( NULL, "~/Library/Fonts" );
511 FcConfigAppFontAddDir( NULL, "/Library/Fonts" );
512 FcConfigAppFontAddDir( NULL, "/Network/Library/Fonts" );
513 //FcConfigAppFontAddDir( NULL, "/System/Library/Fonts" );
517 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
518 dialog_ProgressDestroy( p_dialog );
523 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
527 * \brief Selects a font matching family, bold, italic provided
529 static char* FontConfig_Select( FcConfig* config, const char* family,
530 bool b_bold, bool b_italic, int i_size, int *i_idx )
532 FcResult result = FcResultMatch;
533 FcPattern *pat, *p_pat;
538 /* Create a pattern and fills it */
539 pat = FcPatternCreate();
540 if (!pat) return NULL;
543 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
544 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
545 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
546 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
550 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
552 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
553 free( psz_fontsize );
558 FcDefaultSubstitute( pat );
559 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
561 FcPatternDestroy( pat );
565 /* Find the best font for the pattern, destroy the pattern */
566 p_pat = FcFontMatch( config, pat, &result );
567 FcPatternDestroy( pat );
568 if( !p_pat || result == FcResultNoMatch ) return NULL;
570 /* Check the new pattern */
571 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
572 || ( val_b != FcTrue ) )
574 FcPatternDestroy( p_pat );
577 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
582 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
584 FcPatternDestroy( p_pat );
588 /* if( strcasecmp((const char*)val_s, family ) != 0 )
589 msg_Warn( p_filter, "fontconfig: selected font family is not"
590 "the requested one: '%s' != '%s'\n",
591 (const char*)val_s, family ); */
593 if( FcResultMatch == FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
594 ret = strdup( (const char*)val_s );
596 FcPatternDestroy( p_pat );
602 #define FONT_DIR_NT _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts")
604 static int GetFileFontByName( LPCTSTR font_name, char **psz_filename )
607 TCHAR vbuffer[MAX_PATH];
610 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
614 char *font_name_temp = FromT( font_name );
615 size_t fontname_len = strlen( font_name_temp );
617 for( int index = 0;; index++ )
619 DWORD vbuflen = MAX_PATH - 1;
622 LONG i_result = RegEnumValue( hKey, index, vbuffer, &vbuflen,
623 NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
624 if( i_result != ERROR_SUCCESS )
630 char *psz_value = FromT( vbuffer );
632 char *s = strchr( psz_value,'(' );
633 if( s != NULL && s != psz_value ) s[-1] = '\0';
635 /* Manage concatenated font names */
636 if( strchr( psz_value, '&') ) {
637 if( strcasestr( psz_value, font_name_temp ) != NULL )
644 if( strncasecmp( psz_value, font_name_temp, fontname_len ) == 0 )
654 *psz_filename = FromT( dbuffer );
655 free( font_name_temp );
661 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
662 DWORD type, LPARAM lParam)
664 VLC_UNUSED( metric );
665 if( (type & RASTER_FONTTYPE) ) return 1;
666 // if( lpelfe->elfScript ) FIXME
668 return GetFileFontByName( (LPCTSTR)lpelfe->elfFullName, (char **)lParam );
671 static char* Win32_Select( filter_t *p_filter, const char* family,
672 bool b_bold, bool b_italic, int i_size, int *i_idx )
674 VLC_UNUSED( i_size );
676 if( !family || strlen( family ) < 1 )
681 lf.lfCharSet = DEFAULT_CHARSET;
685 lf.lfWeight = FW_BOLD;
687 LPTSTR psz_fbuffer = ToT( family );
688 _tcsncpy( (LPTSTR)&lf.lfFaceName, psz_fbuffer, LF_FACESIZE );
692 char *psz_filename = NULL;
693 HDC hDC = GetDC( NULL );
694 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
695 ReleaseDC(NULL, hDC);
698 if( psz_filename != NULL )
700 /* FIXME: increase i_idx, when concatenated strings */
703 /* Prepend the Windows Font path, when only a filename was provided */
704 if( strchr( psz_filename, DIR_SEP_CHAR ) )
709 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
711 free( psz_filename );
714 free( psz_filename );
718 else /* Let's take any font we can */
722 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, "arial.ttf" ) == -1 )
728 #endif /* HAVE_WIN32 */
731 #if !TARGET_OS_IPHONE
732 static char* MacLegacy_Select( filter_t *p_filter, const char* psz_fontname,
733 bool b_bold, bool b_italic, int i_size, int *i_idx )
735 VLC_UNUSED( b_bold );
736 VLC_UNUSED( b_italic );
737 VLC_UNUSED( i_size );
739 unsigned char path[MAXPATHLEN];
742 CFStringRef cf_fontName;
743 ATSFontRef ats_font_id;
747 if( psz_fontname == NULL )
750 msg_Dbg( p_filter, "looking for %s", psz_fontname );
751 cf_fontName = CFStringCreateWithCString( kCFAllocatorDefault, psz_fontname, kCFStringEncodingUTF8 );
753 ats_font_id = ATSFontFindFromName( cf_fontName, kATSOptionFlagsIncludeDisabledMask );
755 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
757 msg_Dbg( p_filter, "ATS couldn't find %s by name, checking family", psz_fontname );
758 ats_font_id = ATSFontFamilyFindFromName( cf_fontName, kATSOptionFlagsDefault );
760 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
762 msg_Dbg( p_filter, "ATS couldn't find either %s nor its family, checking PS name", psz_fontname );
763 ats_font_id = ATSFontFindFromPostScriptName( cf_fontName, kATSOptionFlagsDefault );
765 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
767 msg_Err( p_filter, "ATS couldn't find %s (no font name, family or PS name)", psz_fontname );
768 CFRelease( cf_fontName );
773 CFRelease( cf_fontName );
775 if ( noErr != ATSFontGetFileReference( ats_font_id, &ref ) )
777 msg_Err( p_filter, "ATS couldn't get file ref for %s", psz_fontname );
781 /* i_idx calculation by searching preceding fontIDs */
782 /* with same FSRef */
784 ATSFontRef id2 = ats_font_id - 1;
789 if ( noErr != ATSFontGetFileReference( id2, &ref2 ) )
791 if ( noErr != FSCompareFSRefs( &ref, &ref2 ) )
796 *i_idx = ats_font_id - ( id2 + 1 );
799 if ( noErr != FSRefMakePath( &ref, path, sizeof(path) ) )
801 msg_Err( p_filter, "failure when getting path from FSRef" );
804 msg_Dbg( p_filter, "found %s", path );
806 psz_path = strdup( (char *)path );
813 #endif /* HAVE_STYLES */
816 /*****************************************************************************
817 * RenderYUVP: place string in picture
818 *****************************************************************************
819 * This function merges the previously rendered freetype glyphs into a picture
820 *****************************************************************************/
821 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
825 VLC_UNUSED(p_filter);
826 static const uint8_t pi_gamma[16] =
827 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
828 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
832 int i, x, y, i_pitch;
833 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
835 /* Create a new subpicture region */
836 video_format_Init( &fmt, VLC_CODEC_YUVP );
838 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
840 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
842 assert( !p_region->p_picture );
843 p_region->p_picture = picture_NewFromFormat( &fmt );
844 if( !p_region->p_picture )
846 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
849 /* Calculate text color components
850 * Only use the first color */
851 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
852 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
855 fmt.p_palette->i_entries = 16;
856 for( i = 0; i < 8; i++ )
858 fmt.p_palette->palette[i][0] = 0;
859 fmt.p_palette->palette[i][1] = 0x80;
860 fmt.p_palette->palette[i][2] = 0x80;
861 fmt.p_palette->palette[i][3] = pi_gamma[i];
862 fmt.p_palette->palette[i][3] =
863 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
865 for( i = 8; i < fmt.p_palette->i_entries; i++ )
867 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
868 fmt.p_palette->palette[i][1] = i_u;
869 fmt.p_palette->palette[i][2] = i_v;
870 fmt.p_palette->palette[i][3] = pi_gamma[i];
871 fmt.p_palette->palette[i][3] =
872 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
875 p_dst = p_region->p_picture->Y_PIXELS;
876 i_pitch = p_region->p_picture->Y_PITCH;
878 /* Initialize the region pixels */
879 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
881 for( ; p_line != NULL; p_line = p_line->p_next )
883 int i_align_left = 0;
884 if( p_line->i_width < (int)fmt.i_visible_width )
886 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
887 i_align_left = ( fmt.i_visible_width - p_line->i_width );
888 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
889 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
893 for( i = 0; i < p_line->i_character_count; i++ )
895 const line_character_t *ch = &p_line->p_character[i];
896 FT_BitmapGlyph p_glyph = ch->p_glyph;
898 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
899 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
901 for( y = 0; y < p_glyph->bitmap.rows; y++ )
903 for( x = 0; x < p_glyph->bitmap.width; x++ )
905 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
906 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
907 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
913 /* Outlining (find something better than nearest neighbour filtering ?) */
916 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
917 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
918 uint8_t left, current;
920 for( y = 1; y < (int)fmt.i_height - 1; y++ )
922 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
923 p_dst += p_region->p_picture->Y_PITCH;
926 for( x = 1; x < (int)fmt.i_width - 1; x++ )
929 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
930 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;
934 memset( p_top, 0, fmt.i_width );
940 /*****************************************************************************
941 * RenderYUVA: place string in picture
942 *****************************************************************************
943 * This function merges the previously rendered freetype glyphs into a picture
944 *****************************************************************************/
945 static void FillYUVAPicture( picture_t *p_picture,
946 int i_a, int i_y, int i_u, int i_v )
948 memset( p_picture->p[0].p_pixels, i_y,
949 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
950 memset( p_picture->p[1].p_pixels, i_u,
951 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
952 memset( p_picture->p[2].p_pixels, i_v,
953 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
954 memset( p_picture->p[3].p_pixels, i_a,
955 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
958 static inline void BlendYUVAPixel( picture_t *p_picture,
959 int i_picture_x, int i_picture_y,
960 int i_a, int i_y, int i_u, int i_v,
963 int i_an = i_a * i_alpha / 255;
965 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
966 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
967 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
968 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
980 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
983 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
984 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
985 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
990 static void FillRGBAPicture( picture_t *p_picture,
991 int i_a, int i_r, int i_g, int i_b )
993 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
995 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
997 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
1006 static inline void BlendRGBAPixel( picture_t *p_picture,
1007 int i_picture_x, int i_picture_y,
1008 int i_a, int i_r, int i_g, int i_b,
1011 int i_an = i_a * i_alpha / 255;
1013 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
1015 int i_ao = p_rgba[3];
1025 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
1026 if( p_rgba[3] != 0 )
1028 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
1029 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
1030 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
1035 static inline void BlendAXYZGlyph( picture_t *p_picture,
1036 int i_picture_x, int i_picture_y,
1037 int i_a, int i_x, int i_y, int i_z,
1038 FT_BitmapGlyph p_glyph,
1039 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1042 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
1044 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
1045 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
1047 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
1051 static inline void BlendAXYZLine( picture_t *p_picture,
1052 int i_picture_x, int i_picture_y,
1053 int i_a, int i_x, int i_y, int i_z,
1054 const line_character_t *p_current,
1055 const line_character_t *p_next,
1056 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1058 int i_line_width = p_current->p_glyph->bitmap.width;
1060 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
1062 for( int dx = 0; dx < i_line_width; dx++ )
1064 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
1065 BlendPixel( p_picture,
1067 i_picture_y + p_current->i_line_offset + dy,
1068 i_a, i_x, i_y, i_z, 0xff );
1072 static inline void RenderBackground( subpicture_region_t *p_region,
1073 line_desc_t *p_line_head,
1076 picture_t *p_picture,
1078 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
1079 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1081 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
1083 int i_align_left = i_margin;
1084 int i_align_top = i_margin;
1087 unsigned line_top = 0;
1088 int line_bottom = 0;
1091 if( p_line->i_width < i_text_width )
1093 /* Left offset to take into account alignment */
1094 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1095 i_align_left += ( i_text_width - p_line->i_width );
1096 else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
1097 i_align_left = i_margin; /* Keep it the way it is */
1098 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1099 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1102 /* Find the tallest character in the line */
1103 for( int i = 0; i < p_line->i_character_count; i++ ) {
1104 const line_character_t *ch = &p_line->p_character[i];
1105 FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
1106 if (p_glyph->top > max_height)
1107 max_height = p_glyph->top;
1110 /* Compute the background for the line (identify leading/trailing space) */
1111 for( int i = 0; i < p_line->i_character_count; i++ ) {
1112 const line_character_t *ch = &p_line->p_character[i];
1113 FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
1114 if (p_glyph && p_glyph->bitmap.rows > 0) {
1115 // Found a non-whitespace character
1116 line_start = i_align_left + p_glyph->left - p_bbox->xMin;
1121 /* Fudge factor to make sure caption background edges are left aligned
1122 despite variable font width */
1123 if (line_start < 12)
1126 /* Find right boundary for bounding box for background */
1127 for( int i = p_line->i_character_count; i > 0; i-- ) {
1128 const line_character_t *ch = &p_line->p_character[i - 1];
1129 FT_BitmapGlyph p_glyph = ch->p_shadow ? ch->p_shadow : ch->p_glyph;
1130 if (p_glyph && p_glyph->bitmap.rows > 0) {
1131 // Found a non-whitespace character
1132 line_end = i_align_left + p_glyph->left - p_bbox->xMin + p_glyph->bitmap.width;
1137 /* Setup color for the background */
1138 uint8_t i_x, i_y, i_z;
1139 ExtractComponents( 0x000000, &i_x, &i_y, &i_z );
1141 /* Compute the upper boundary for the background */
1142 if ((i_align_top + p_line->i_base_line - max_height) < 0)
1143 line_top = i_align_top + p_line->i_base_line;
1145 line_top = i_align_top + p_line->i_base_line - max_height;
1147 /* Compute lower boundary for the background */
1148 line_bottom = __MIN(line_top + p_line->i_height, p_region->fmt.i_visible_height);
1150 /* Render the actual background */
1151 for( int dy = line_top; dy < line_bottom; dy++ )
1153 for( int dx = line_start; dx < line_end; dx++ )
1154 BlendPixel( p_picture, dx, dy, 0xff, i_x, i_y, i_z, 0xff );
1159 static inline int RenderAXYZ( filter_t *p_filter,
1160 subpicture_region_t *p_region,
1161 line_desc_t *p_line_head,
1164 vlc_fourcc_t i_chroma,
1165 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
1166 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
1167 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1169 filter_sys_t *p_sys = p_filter->p_sys;
1171 /* Create a new subpicture region */
1172 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
1173 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
1175 video_format_Init( &fmt, i_chroma );
1177 fmt.i_visible_width = i_text_width + 2 * i_margin;
1179 fmt.i_visible_height = i_text_height + 2 * i_margin;
1181 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
1182 if( !p_region->p_picture )
1183 return VLC_EGENERIC;
1184 p_region->fmt = fmt;
1186 /* Initialize the picture background */
1187 uint8_t i_a = var_InheritInteger( p_filter, "freetype-background-opacity" );
1188 i_a = VLC_CLIP( i_a, 0, 255 );
1189 uint8_t i_x, i_y, i_z;
1191 if (p_region->b_renderbg) {
1192 /* Render the background just under the text */
1193 FillPicture( p_picture, 0x00, 0x00, 0x00, 0x00 );
1194 RenderBackground(p_region, p_line_head, p_bbox, i_margin, p_picture, i_text_width,
1195 ExtractComponents, BlendPixel);
1197 /* Render background under entire subpicture block */
1198 int i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
1199 i_background_color = VLC_CLIP( i_background_color, 0, 0xFFFFFF );
1200 ExtractComponents( i_background_color, &i_x, &i_y, &i_z );
1201 FillPicture( p_picture, i_a, i_x, i_y, i_z );
1204 /* Render shadow then outline and then normal glyphs */
1205 for( int g = 0; g < 3; g++ )
1207 /* Render all lines */
1208 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
1210 int i_align_left = i_margin;
1211 if( p_line->i_width < i_text_width )
1213 /* Left offset to take into account alignment */
1214 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1215 i_align_left += ( i_text_width - p_line->i_width );
1216 else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
1217 i_align_left = i_margin; /* Keep it the way it is */
1218 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1219 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1221 int i_align_top = i_margin;
1223 /* Render all glyphs and underline/strikethrough */
1224 for( int i = 0; i < p_line->i_character_count; i++ )
1226 const line_character_t *ch = &p_line->p_character[i];
1227 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
1231 i_a = (ch->i_color >> 24) & 0xff;
1235 i_a = i_a * p_sys->i_shadow_opacity / 255;
1236 i_color = p_sys->i_shadow_color;
1239 i_a = i_a * p_sys->i_outline_opacity / 255;
1240 i_color = p_sys->i_outline_color;
1243 i_color = ch->i_color;
1246 ExtractComponents( i_color, &i_x, &i_y, &i_z );
1248 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
1249 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
1251 BlendAXYZGlyph( p_picture,
1252 i_glyph_x, i_glyph_y,
1257 /* underline/strikethrough are only rendered for the normal glyph */
1258 if( g == 2 && ch->i_line_thickness > 0 )
1259 BlendAXYZLine( p_picture,
1260 i_glyph_x, i_glyph_y + p_glyph->top,
1263 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
1272 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
1273 uint32_t i_font_color, uint32_t i_karaoke_bg_color,
1276 text_style_t *p_style = text_style_New();
1280 p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
1281 p_style->i_font_size = i_font_size;
1282 p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0;
1283 p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
1284 p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0;
1285 p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
1286 p_style->i_style_flags |= i_style_flags;
1290 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1291 uint32_t i_color, uint32_t i_karaoke_bg_color )
1294 return VLC_EGENERIC;
1296 font_stack_t *p_new = malloc( sizeof(*p_new) );
1300 p_new->p_next = NULL;
1303 p_new->psz_name = strdup( psz_name );
1305 p_new->psz_name = NULL;
1307 p_new->i_size = i_size;
1308 p_new->i_color = i_color;
1309 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1317 font_stack_t *p_last;
1319 for( p_last = *p_font;
1321 p_last = p_last->p_next )
1324 p_last->p_next = p_new;
1329 static int PopFont( font_stack_t **p_font )
1331 font_stack_t *p_last, *p_next_to_last;
1333 if( !p_font || !*p_font )
1334 return VLC_EGENERIC;
1336 p_next_to_last = NULL;
1337 for( p_last = *p_font;
1339 p_last = p_last->p_next )
1341 p_next_to_last = p_last;
1344 if( p_next_to_last )
1345 p_next_to_last->p_next = NULL;
1349 free( p_last->psz_name );
1355 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1356 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1358 font_stack_t *p_last;
1360 if( !p_font || !*p_font )
1361 return VLC_EGENERIC;
1363 for( p_last=*p_font;
1365 p_last=p_last->p_next )
1368 *psz_name = p_last->psz_name;
1369 *i_size = p_last->i_size;
1370 *i_color = p_last->i_color;
1371 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1376 static const struct {
1377 const char *psz_name;
1379 } p_html_colors[] = {
1380 /* Official html colors */
1381 { "Aqua", 0x00FFFF },
1382 { "Black", 0x000000 },
1383 { "Blue", 0x0000FF },
1384 { "Fuchsia", 0xFF00FF },
1385 { "Gray", 0x808080 },
1386 { "Green", 0x008000 },
1387 { "Lime", 0x00FF00 },
1388 { "Maroon", 0x800000 },
1389 { "Navy", 0x000080 },
1390 { "Olive", 0x808000 },
1391 { "Purple", 0x800080 },
1392 { "Red", 0xFF0000 },
1393 { "Silver", 0xC0C0C0 },
1394 { "Teal", 0x008080 },
1395 { "White", 0xFFFFFF },
1396 { "Yellow", 0xFFFF00 },
1399 { "AliceBlue", 0xF0F8FF },
1400 { "AntiqueWhite", 0xFAEBD7 },
1401 { "Aqua", 0x00FFFF },
1402 { "Aquamarine", 0x7FFFD4 },
1403 { "Azure", 0xF0FFFF },
1404 { "Beige", 0xF5F5DC },
1405 { "Bisque", 0xFFE4C4 },
1406 { "Black", 0x000000 },
1407 { "BlanchedAlmond", 0xFFEBCD },
1408 { "Blue", 0x0000FF },
1409 { "BlueViolet", 0x8A2BE2 },
1410 { "Brown", 0xA52A2A },
1411 { "BurlyWood", 0xDEB887 },
1412 { "CadetBlue", 0x5F9EA0 },
1413 { "Chartreuse", 0x7FFF00 },
1414 { "Chocolate", 0xD2691E },
1415 { "Coral", 0xFF7F50 },
1416 { "CornflowerBlue", 0x6495ED },
1417 { "Cornsilk", 0xFFF8DC },
1418 { "Crimson", 0xDC143C },
1419 { "Cyan", 0x00FFFF },
1420 { "DarkBlue", 0x00008B },
1421 { "DarkCyan", 0x008B8B },
1422 { "DarkGoldenRod", 0xB8860B },
1423 { "DarkGray", 0xA9A9A9 },
1424 { "DarkGrey", 0xA9A9A9 },
1425 { "DarkGreen", 0x006400 },
1426 { "DarkKhaki", 0xBDB76B },
1427 { "DarkMagenta", 0x8B008B },
1428 { "DarkOliveGreen", 0x556B2F },
1429 { "Darkorange", 0xFF8C00 },
1430 { "DarkOrchid", 0x9932CC },
1431 { "DarkRed", 0x8B0000 },
1432 { "DarkSalmon", 0xE9967A },
1433 { "DarkSeaGreen", 0x8FBC8F },
1434 { "DarkSlateBlue", 0x483D8B },
1435 { "DarkSlateGray", 0x2F4F4F },
1436 { "DarkSlateGrey", 0x2F4F4F },
1437 { "DarkTurquoise", 0x00CED1 },
1438 { "DarkViolet", 0x9400D3 },
1439 { "DeepPink", 0xFF1493 },
1440 { "DeepSkyBlue", 0x00BFFF },
1441 { "DimGray", 0x696969 },
1442 { "DimGrey", 0x696969 },
1443 { "DodgerBlue", 0x1E90FF },
1444 { "FireBrick", 0xB22222 },
1445 { "FloralWhite", 0xFFFAF0 },
1446 { "ForestGreen", 0x228B22 },
1447 { "Fuchsia", 0xFF00FF },
1448 { "Gainsboro", 0xDCDCDC },
1449 { "GhostWhite", 0xF8F8FF },
1450 { "Gold", 0xFFD700 },
1451 { "GoldenRod", 0xDAA520 },
1452 { "Gray", 0x808080 },
1453 { "Grey", 0x808080 },
1454 { "Green", 0x008000 },
1455 { "GreenYellow", 0xADFF2F },
1456 { "HoneyDew", 0xF0FFF0 },
1457 { "HotPink", 0xFF69B4 },
1458 { "IndianRed", 0xCD5C5C },
1459 { "Indigo", 0x4B0082 },
1460 { "Ivory", 0xFFFFF0 },
1461 { "Khaki", 0xF0E68C },
1462 { "Lavender", 0xE6E6FA },
1463 { "LavenderBlush", 0xFFF0F5 },
1464 { "LawnGreen", 0x7CFC00 },
1465 { "LemonChiffon", 0xFFFACD },
1466 { "LightBlue", 0xADD8E6 },
1467 { "LightCoral", 0xF08080 },
1468 { "LightCyan", 0xE0FFFF },
1469 { "LightGoldenRodYellow", 0xFAFAD2 },
1470 { "LightGray", 0xD3D3D3 },
1471 { "LightGrey", 0xD3D3D3 },
1472 { "LightGreen", 0x90EE90 },
1473 { "LightPink", 0xFFB6C1 },
1474 { "LightSalmon", 0xFFA07A },
1475 { "LightSeaGreen", 0x20B2AA },
1476 { "LightSkyBlue", 0x87CEFA },
1477 { "LightSlateGray", 0x778899 },
1478 { "LightSlateGrey", 0x778899 },
1479 { "LightSteelBlue", 0xB0C4DE },
1480 { "LightYellow", 0xFFFFE0 },
1481 { "Lime", 0x00FF00 },
1482 { "LimeGreen", 0x32CD32 },
1483 { "Linen", 0xFAF0E6 },
1484 { "Magenta", 0xFF00FF },
1485 { "Maroon", 0x800000 },
1486 { "MediumAquaMarine", 0x66CDAA },
1487 { "MediumBlue", 0x0000CD },
1488 { "MediumOrchid", 0xBA55D3 },
1489 { "MediumPurple", 0x9370D8 },
1490 { "MediumSeaGreen", 0x3CB371 },
1491 { "MediumSlateBlue", 0x7B68EE },
1492 { "MediumSpringGreen", 0x00FA9A },
1493 { "MediumTurquoise", 0x48D1CC },
1494 { "MediumVioletRed", 0xC71585 },
1495 { "MidnightBlue", 0x191970 },
1496 { "MintCream", 0xF5FFFA },
1497 { "MistyRose", 0xFFE4E1 },
1498 { "Moccasin", 0xFFE4B5 },
1499 { "NavajoWhite", 0xFFDEAD },
1500 { "Navy", 0x000080 },
1501 { "OldLace", 0xFDF5E6 },
1502 { "Olive", 0x808000 },
1503 { "OliveDrab", 0x6B8E23 },
1504 { "Orange", 0xFFA500 },
1505 { "OrangeRed", 0xFF4500 },
1506 { "Orchid", 0xDA70D6 },
1507 { "PaleGoldenRod", 0xEEE8AA },
1508 { "PaleGreen", 0x98FB98 },
1509 { "PaleTurquoise", 0xAFEEEE },
1510 { "PaleVioletRed", 0xD87093 },
1511 { "PapayaWhip", 0xFFEFD5 },
1512 { "PeachPuff", 0xFFDAB9 },
1513 { "Peru", 0xCD853F },
1514 { "Pink", 0xFFC0CB },
1515 { "Plum", 0xDDA0DD },
1516 { "PowderBlue", 0xB0E0E6 },
1517 { "Purple", 0x800080 },
1518 { "Red", 0xFF0000 },
1519 { "RosyBrown", 0xBC8F8F },
1520 { "RoyalBlue", 0x4169E1 },
1521 { "SaddleBrown", 0x8B4513 },
1522 { "Salmon", 0xFA8072 },
1523 { "SandyBrown", 0xF4A460 },
1524 { "SeaGreen", 0x2E8B57 },
1525 { "SeaShell", 0xFFF5EE },
1526 { "Sienna", 0xA0522D },
1527 { "Silver", 0xC0C0C0 },
1528 { "SkyBlue", 0x87CEEB },
1529 { "SlateBlue", 0x6A5ACD },
1530 { "SlateGray", 0x708090 },
1531 { "SlateGrey", 0x708090 },
1532 { "Snow", 0xFFFAFA },
1533 { "SpringGreen", 0x00FF7F },
1534 { "SteelBlue", 0x4682B4 },
1535 { "Tan", 0xD2B48C },
1536 { "Teal", 0x008080 },
1537 { "Thistle", 0xD8BFD8 },
1538 { "Tomato", 0xFF6347 },
1539 { "Turquoise", 0x40E0D0 },
1540 { "Violet", 0xEE82EE },
1541 { "Wheat", 0xF5DEB3 },
1542 { "White", 0xFFFFFF },
1543 { "WhiteSmoke", 0xF5F5F5 },
1544 { "Yellow", 0xFFFF00 },
1545 { "YellowGreen", 0x9ACD32 },
1550 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1551 font_stack_t **p_fonts )
1554 char *psz_fontname = NULL;
1555 uint32_t i_font_color = 0xffffff;
1556 int i_font_alpha = 255;
1557 uint32_t i_karaoke_bg_color = 0x00ffffff;
1558 int i_font_size = 24;
1560 /* Default all attributes to the top font in the stack -- in case not
1561 * all attributes are specified in the sub-font
1563 if( VLC_SUCCESS == PeekFont( p_fonts,
1567 &i_karaoke_bg_color ))
1569 psz_fontname = strdup( psz_fontname );
1571 i_font_alpha = (i_font_color >> 24) & 0xff;
1572 i_font_color &= 0x00ffffff;
1574 const char *name, *value;
1575 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1577 if( !strcasecmp( "face", name ) )
1579 free( psz_fontname );
1580 psz_fontname = strdup( value );
1582 else if( !strcasecmp( "size", name ) )
1584 if( ( *value == '+' ) || ( *value == '-' ) )
1586 int i_value = atoi( value );
1588 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1589 i_font_size += ( i_value * i_font_size ) / 10;
1590 else if( i_value < -5 )
1591 i_font_size = - i_value;
1592 else if( i_value > 5 )
1593 i_font_size = i_value;
1596 i_font_size = atoi( value );
1598 else if( !strcasecmp( "color", name ) )
1600 if( value[0] == '#' )
1602 i_font_color = strtol( value + 1, NULL, 16 );
1603 i_font_color &= 0x00ffffff;
1608 uint32_t i_value = strtol( value, &end, 16 );
1609 if( *end == '\0' || *end == ' ' )
1610 i_font_color = i_value & 0x00ffffff;
1612 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1614 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1616 i_font_color = p_html_colors[i].i_value;
1622 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1624 i_font_alpha = strtol( value + 1, NULL, 16 );
1625 i_font_alpha &= 0xff;
1628 rv = PushFont( p_fonts,
1631 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1632 i_karaoke_bg_color );
1634 free( psz_fontname );
1639 static int HandleTT(font_stack_t **p_fonts, const char *p_fontfamily )
1641 char *psz_unused_fontname = NULL;
1642 uint32_t i_font_color = 0xffffff;
1643 uint32_t i_karaoke_bg_color = 0x00ffffff;
1644 int i_font_size = 24;
1646 /* Default all attributes to the top font in the stack -- in case not
1647 * all attributes are specified in the sub-font
1650 &psz_unused_fontname,
1653 &i_karaoke_bg_color );
1655 /* Keep all the parent's font attributes, but change to a monospace font */
1656 return PushFont( p_fonts,
1660 i_karaoke_bg_color );
1663 /* Turn any multiple-whitespaces into single spaces */
1664 static void HandleWhiteSpace( char *psz_node )
1666 char *s = strpbrk( psz_node, "\t\r\n " );
1669 int i_whitespace = strspn( s, "\t\r\n " );
1671 if( i_whitespace > 1 )
1674 strlen( s ) - i_whitespace + 1 );
1677 s = strpbrk( s, "\t\r\n " );
1682 static text_style_t *GetStyleFromFontStack( filter_t *p_filter,
1683 font_stack_t **p_fonts,
1686 char *psz_fontname = NULL;
1687 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
1688 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
1689 i_font_color = i_font_color & 0x00ffffff;
1690 int i_font_size = p_filter->p_sys->i_font_size;
1691 uint32_t i_karaoke_bg_color = i_font_color;
1693 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1694 &i_font_color, &i_karaoke_bg_color ) )
1697 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1702 static unsigned SetupText( filter_t *p_filter,
1703 uni_char_t *psz_text_out,
1704 text_style_t **pp_styles,
1705 uint32_t *pi_k_dates,
1707 const char *psz_text_in,
1708 text_style_t *p_style,
1711 size_t i_string_length;
1713 size_t i_string_bytes;
1714 uni_char_t *psz_tmp = ToCharset( FREETYPE_TO_UCS, psz_text_in, &i_string_bytes );
1717 memcpy( psz_text_out, psz_tmp, i_string_bytes );
1718 i_string_length = i_string_bytes / sizeof( *psz_tmp );
1723 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1724 i_string_length = 0;
1727 if( i_string_length > 0 )
1729 for( unsigned i = 0; i < i_string_length; i++ )
1730 pp_styles[i] = p_style;
1734 text_style_Delete( p_style );
1736 if( i_string_length > 0 && pi_k_dates )
1738 for( unsigned i = 0; i < i_string_length; i++ )
1739 pi_k_dates[i] = i_k_date;
1741 return i_string_length;
1744 static int ProcessNodes( filter_t *p_filter,
1745 uni_char_t *psz_text,
1746 text_style_t **pp_styles,
1747 uint32_t *pi_k_dates,
1749 xml_reader_t *p_xml_reader,
1750 text_style_t *p_font_style )
1752 int rv = VLC_SUCCESS;
1753 filter_sys_t *p_sys = p_filter->p_sys;
1754 int i_text_length = 0;
1755 font_stack_t *p_fonts = NULL;
1756 uint32_t i_k_date = 0;
1758 int i_style_flags = 0;
1762 /* If the font is not specified in the style, assume the system font */
1763 if(!p_font_style->psz_fontname)
1764 p_font_style->psz_fontname = strdup(p_sys->psz_fontfamily);
1766 rv = PushFont( &p_fonts,
1767 p_font_style->psz_fontname,
1768 p_font_style->i_font_size > 0 ? p_font_style->i_font_size
1769 : p_sys->i_font_size,
1770 (p_font_style->i_font_color & 0xffffff) |
1771 ((p_font_style->i_font_alpha & 0xff) << 24),
1772 (p_font_style->i_karaoke_background_color & 0xffffff) |
1773 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1775 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1782 uint32_t i_font_size = p_sys->i_font_size;
1783 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
1784 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
1785 uint32_t i_font_opacity = p_sys->i_font_opacity;
1786 rv = PushFont( &p_fonts,
1787 p_sys->psz_fontfamily,
1789 (i_font_color & 0xffffff) |
1790 ((i_font_opacity & 0xff) << 24),
1793 if( p_sys->b_font_bold )
1794 i_style_flags |= STYLE_BOLD;
1796 if( rv != VLC_SUCCESS )
1802 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1806 case XML_READER_ENDELEM:
1807 if( !strcasecmp( "font", node ) )
1808 PopFont( &p_fonts );
1809 else if( !strcasecmp( "tt", node ) )
1810 PopFont( &p_fonts );
1811 else if( !strcasecmp( "b", node ) )
1812 i_style_flags &= ~STYLE_BOLD;
1813 else if( !strcasecmp( "i", node ) )
1814 i_style_flags &= ~STYLE_ITALIC;
1815 else if( !strcasecmp( "u", node ) )
1816 i_style_flags &= ~STYLE_UNDERLINE;
1817 else if( !strcasecmp( "s", node ) )
1818 i_style_flags &= ~STYLE_STRIKEOUT;
1821 case XML_READER_STARTELEM:
1822 if( !strcasecmp( "font", node ) )
1823 HandleFontAttributes( p_xml_reader, &p_fonts );
1824 else if( !strcasecmp( "tt", node ) )
1825 HandleTT( &p_fonts, p_sys->psz_monofontfamily );
1826 else if( !strcasecmp( "b", node ) )
1827 i_style_flags |= STYLE_BOLD;
1828 else if( !strcasecmp( "i", node ) )
1829 i_style_flags |= STYLE_ITALIC;
1830 else if( !strcasecmp( "u", node ) )
1831 i_style_flags |= STYLE_UNDERLINE;
1832 else if( !strcasecmp( "s", node ) )
1833 i_style_flags |= STYLE_STRIKEOUT;
1834 else if( !strcasecmp( "br", node ) )
1836 i_text_length += SetupText( p_filter,
1837 &psz_text[i_text_length],
1838 &pp_styles[i_text_length],
1839 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1841 GetStyleFromFontStack( p_filter,
1846 else if( !strcasecmp( "k", node ) )
1849 const char *name, *value;
1850 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1852 if( !strcasecmp( "t", name ) && value )
1853 i_k_date += atoi( value );
1858 case XML_READER_TEXT:
1860 char *psz_node = strdup( node );
1861 if( unlikely(!psz_node) )
1864 HandleWhiteSpace( psz_node );
1865 resolve_xml_special_chars( psz_node );
1867 i_text_length += SetupText( p_filter,
1868 &psz_text[i_text_length],
1869 &pp_styles[i_text_length],
1870 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1872 GetStyleFromFontStack( p_filter,
1882 *pi_len = i_text_length;
1884 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1889 static void FreeLine( line_desc_t *p_line )
1891 for( int i = 0; i < p_line->i_character_count; i++ )
1893 line_character_t *ch = &p_line->p_character[i];
1894 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1896 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1898 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1901 free( p_line->p_character );
1905 static void FreeLines( line_desc_t *p_lines )
1907 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1909 line_desc_t *p_next = p_line->p_next;
1915 static line_desc_t *NewLine( int i_count )
1917 line_desc_t *p_line = malloc( sizeof(*p_line) );
1922 p_line->p_next = NULL;
1923 p_line->i_width = 0;
1924 p_line->i_base_line = 0;
1925 p_line->i_character_count = 0;
1927 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1928 if( !p_line->p_character )
1936 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1938 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1940 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1942 FT_Face p_face = NULL;
1944 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1952 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1953 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1954 if( p_face->family_name != NULL
1955 && !strcasecmp( p_face->family_name, p_style->psz_fontname )
1956 && (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC))
1957 == i_style_received )
1960 FT_Done_Face( p_face );
1968 static FT_Face LoadFace( filter_t *p_filter,
1969 const text_style_t *p_style )
1971 filter_sys_t *p_sys = p_filter->p_sys;
1973 /* Look for a match amongst our attachments first */
1974 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1976 /* Load system wide font otheriwse */
1980 char *psz_fontfile = NULL;
1981 #ifdef HAVE_FONTCONFIG
1982 psz_fontfile = FontConfig_Select( NULL,
1983 p_style->psz_fontname,
1984 (p_style->i_style_flags & STYLE_BOLD) != 0,
1985 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1988 #elif defined( __APPLE__ )
1989 #if !TARGET_OS_IPHONE
1990 psz_fontfile = MacLegacy_Select( p_filter, p_style->psz_fontname, false, false, -1, &i_idx );
1992 #elif defined( WIN32 )
1993 psz_fontfile = Win32_Select( p_filter,
1994 p_style->psz_fontname,
1995 (p_style->i_style_flags & STYLE_BOLD) != 0,
1996 (p_style->i_style_flags & STYLE_ITALIC) != 0,
2000 psz_fontfile = NULL;
2005 if( *psz_fontfile == '\0' )
2008 "We were not able to find a matching font: \"%s\" (%s %s),"
2009 " so using default font",
2010 p_style->psz_fontname,
2011 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
2012 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
2017 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
2020 free( psz_fontfile );
2025 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
2027 /* We've loaded a font face which is unhelpful for actually
2028 * rendering text - fallback to the default one.
2030 FT_Done_Face( p_face );
2036 static bool FaceStyleEquals( const text_style_t *p_style1,
2037 const text_style_t *p_style2 )
2039 if( !p_style1 || !p_style2 )
2041 if( p_style1 == p_style2 )
2044 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
2045 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
2046 !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
2049 static int GetGlyph( filter_t *p_filter,
2050 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
2051 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
2052 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
2058 FT_Vector *p_pen_shadow )
2060 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
2061 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
2063 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
2064 return VLC_EGENERIC;
2067 /* Do synthetic styling now that Freetype supports it;
2068 * ie. if the font we have loaded is NOT already in the
2069 * style that the tags want, then switch it on; if they
2070 * are then don't. */
2071 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
2072 FT_GlyphSlot_Embolden( p_face->glyph );
2073 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
2074 FT_GlyphSlot_Oblique( p_face->glyph );
2077 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
2079 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
2080 return VLC_EGENERIC;
2083 FT_Glyph outline = NULL;
2084 if( p_filter->p_sys->p_stroker )
2087 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
2091 FT_Glyph shadow = NULL;
2092 if( p_filter->p_sys->i_shadow_opacity > 0 )
2094 shadow = outline ? outline : glyph;
2095 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
2101 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
2104 *pp_shadow = shadow;
2106 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
2108 FT_Done_Glyph( glyph );
2110 FT_Done_Glyph( outline );
2112 FT_Done_Glyph( shadow );
2113 return VLC_EGENERIC;
2115 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
2120 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
2121 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
2123 *pp_outline = outline;
2128 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
2130 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
2131 if( p_bbox->xMin >= p_bbox->xMax )
2133 p_bbox->xMin = FT_CEIL(p_pen->x);
2134 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
2135 glyph_bmp->left = p_bbox->xMin;
2137 if( p_bbox->yMin >= p_bbox->yMax )
2139 p_bbox->yMax = FT_CEIL(p_pen->y);
2140 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
2141 glyph_bmp->top = p_bbox->yMax;
2145 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
2147 p_max->xMin = __MIN(p_max->xMin, p->xMin);
2148 p_max->yMin = __MIN(p_max->yMin, p->yMin);
2149 p_max->xMax = __MAX(p_max->xMax, p->xMax);
2150 p_max->yMax = __MAX(p_max->yMax, p->yMax);
2153 static int ProcessLines( filter_t *p_filter,
2154 line_desc_t **pp_lines,
2156 int *pi_max_face_height,
2158 uni_char_t *psz_text,
2159 text_style_t **pp_styles,
2160 uint32_t *pi_k_dates,
2163 filter_sys_t *p_sys = p_filter->p_sys;
2164 uni_char_t *p_fribidi_string = NULL;
2165 text_style_t **pp_fribidi_styles = NULL;
2166 int *p_new_positions = NULL;
2168 #if defined(HAVE_FRIBIDI)
2170 int *p_old_positions;
2171 int start_pos, pos = 0;
2173 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
2175 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
2176 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
2177 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
2179 if( ! pp_fribidi_styles ||
2180 ! p_fribidi_string ||
2181 ! p_old_positions ||
2184 free( p_old_positions );
2185 free( p_new_positions );
2186 free( p_fribidi_string );
2187 free( pp_fribidi_styles );
2191 /* Do bidi conversion line-by-line */
2194 while(pos < i_len) {
2195 if (psz_text[pos] != '\n')
2197 p_fribidi_string[pos] = psz_text[pos];
2198 pp_fribidi_styles[pos] = pp_styles[pos];
2199 p_new_positions[pos] = pos;
2203 while(pos < i_len) {
2204 if (psz_text[pos] == '\n')
2208 if (pos > start_pos)
2210 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
2211 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
2213 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
2215 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
2216 pos - start_pos, &base_dir,
2217 (FriBidiChar*)p_fribidi_string + start_pos,
2218 p_new_positions + start_pos,
2221 for( int j = start_pos; j < pos; j++ )
2223 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
2224 p_new_positions[ j ] += start_pos;
2228 p_fribidi_string[ i_len ] = 0;
2229 free( p_old_positions );
2231 pp_styles = pp_fribidi_styles;
2232 psz_text = p_fribidi_string;
2235 /* Work out the karaoke */
2236 uint8_t *pi_karaoke_bar = NULL;
2239 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
2240 if( pi_karaoke_bar )
2242 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
2243 for( int i = 0; i < i_len; i++ )
2245 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
2246 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
2250 free( p_new_positions );
2252 *pi_max_face_height = 0;
2254 line_desc_t **pp_line_next = pp_lines;
2262 int i_face_height_previous = 0;
2263 int i_base_line = 0;
2264 const text_style_t *p_previous_style = NULL;
2265 FT_Face p_face = NULL;
2266 for( int i_start = 0; i_start < i_len; )
2268 /* Compute the length of the current text line */
2270 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
2273 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
2274 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
2275 int i_index = i_start;
2280 int i_face_height = 0;
2281 FT_BBox line_bbox = {
2287 int i_ul_offset = 0;
2288 int i_ul_thickness = 0;
2297 break_point_t break_point;
2298 break_point_t break_point_fallback;
2300 #define SAVE_BP(dst) do { \
2301 dst.i_index = i_index; \
2303 dst.line_bbox = line_bbox; \
2304 dst.i_face_height = i_face_height; \
2305 dst.i_ul_offset = i_ul_offset; \
2306 dst.i_ul_thickness = i_ul_thickness; \
2309 SAVE_BP( break_point );
2310 SAVE_BP( break_point_fallback );
2312 while( i_index < i_start + i_length )
2314 /* Split by common FT_Face + Size */
2315 const text_style_t *p_current_style = pp_styles[i_index];
2316 int i_part_length = 0;
2317 while( i_index + i_part_length < i_start + i_length )
2319 const text_style_t *p_style = pp_styles[i_index + i_part_length];
2320 if( !FaceStyleEquals( p_style, p_current_style ) ||
2321 p_style->i_font_size != p_current_style->i_font_size )
2326 /* (Re)load/reconfigure the face if needed */
2327 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
2330 FT_Done_Face( p_face );
2331 p_previous_style = NULL;
2333 p_face = LoadFace( p_filter, p_current_style );
2335 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
2336 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
2338 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
2339 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
2340 if( p_sys->p_stroker )
2342 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2343 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
2344 int i_radius = (p_current_style->i_font_size << 6) * f_outline_thickness;
2345 FT_Stroker_Set( p_sys->p_stroker,
2347 FT_STROKER_LINECAP_ROUND,
2348 FT_STROKER_LINEJOIN_ROUND, 0 );
2351 p_previous_style = p_current_style;
2353 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
2354 p_current_face->size->metrics.y_scale)));
2356 /* Render the part */
2357 bool b_break_line = false;
2358 int i_glyph_last = 0;
2359 while( i_part_length > 0 )
2361 const text_style_t *p_glyph_style = pp_styles[i_index];
2362 uni_char_t character = psz_text[i_index];
2363 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
2365 /* Get kerning vector */
2366 FT_Vector kerning = { .x = 0, .y = 0 };
2367 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
2368 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
2370 /* Get the glyph bitmap and its bounding box and all the associated properties */
2371 FT_Vector pen_new = {
2372 .x = pen.x + kerning.x,
2373 .y = pen.y + kerning.y,
2375 FT_Vector pen_shadow_new = {
2376 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
2377 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
2382 FT_BBox outline_bbox;
2384 FT_BBox shadow_bbox;
2386 if( GetGlyph( p_filter,
2387 &glyph, &glyph_bbox,
2388 &outline, &outline_bbox,
2389 &shadow, &shadow_bbox,
2390 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
2391 &pen_new, &pen_shadow_new ) )
2394 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
2396 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
2398 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
2400 /* FIXME and what about outline */
2402 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
2403 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
2404 (p_glyph_style->i_karaoke_background_alpha << 24))
2405 : (p_glyph_style->i_font_color |
2406 (p_glyph_style->i_font_alpha << 24));
2407 int i_line_offset = 0;
2408 int i_line_thickness = 0;
2409 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
2411 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
2412 p_current_face->size->metrics.y_scale)) );
2414 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
2415 p_current_face->size->metrics.y_scale)) );
2417 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
2419 /* Move the baseline to make it strikethrough instead of
2420 * underline. That means that strikethrough takes precedence
2422 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
2423 p_current_face->size->metrics.y_scale)) );
2425 else if( i_line_thickness > 0 )
2427 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
2429 /* The real underline thickness and position are
2430 * updated once the whole line has been parsed */
2431 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
2432 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
2433 i_line_thickness = -1;
2436 FT_BBox line_bbox_new = line_bbox;
2437 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
2439 BBoxEnlarge( &line_bbox_new, &outline_bbox );
2441 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
2443 b_break_line = i_index > i_start &&
2444 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
2447 FT_Done_Glyph( glyph );
2449 FT_Done_Glyph( outline );
2451 FT_Done_Glyph( shadow );
2453 break_point_t *p_bp = NULL;
2454 if( break_point.i_index > i_start )
2455 p_bp = &break_point;
2456 else if( break_point_fallback.i_index > i_start )
2457 p_bp = &break_point_fallback;
2461 msg_Dbg( p_filter, "Breaking line");
2462 for( int i = p_bp->i_index; i < i_index; i++ )
2464 line_character_t *ch = &p_line->p_character[i - i_start];
2465 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
2467 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
2469 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
2471 p_line->i_character_count = p_bp->i_index - i_start;
2473 i_index = p_bp->i_index;
2475 line_bbox = p_bp->line_bbox;
2476 i_face_height = p_bp->i_face_height;
2477 i_ul_offset = p_bp->i_ul_offset;
2478 i_ul_thickness = p_bp->i_ul_thickness;
2482 msg_Err( p_filter, "Breaking unbreakable line");
2487 assert( p_line->i_character_count == i_index - i_start);
2488 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2489 .p_glyph = (FT_BitmapGlyph)glyph,
2490 .p_outline = (FT_BitmapGlyph)outline,
2491 .p_shadow = (FT_BitmapGlyph)shadow,
2493 .i_line_offset = i_line_offset,
2494 .i_line_thickness = i_line_thickness,
2497 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2498 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2499 line_bbox = line_bbox_new;
2501 i_glyph_last = i_glyph_index;
2505 if( character == ' ' || character == '\t' )
2506 SAVE_BP( break_point );
2507 else if( character == 160 )
2508 SAVE_BP( break_point_fallback );
2514 /* Update our baseline */
2515 if( i_face_height_previous > 0 )
2516 i_base_line += __MAX(i_face_height, i_face_height_previous);
2517 if( i_face_height > 0 )
2518 i_face_height_previous = i_face_height;
2520 /* Update the line bbox with the actual base line */
2521 if (line_bbox.yMax > line_bbox.yMin) {
2522 line_bbox.yMin -= i_base_line;
2523 line_bbox.yMax -= i_base_line;
2525 BBoxEnlarge( &bbox, &line_bbox );
2527 /* Terminate and append the line */
2530 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2531 p_line->i_base_line = i_base_line;
2532 p_line->i_height = __MAX(i_face_height, i_face_height_previous);
2533 if( i_ul_thickness > 0 )
2535 for( int i = 0; i < p_line->i_character_count; i++ )
2537 line_character_t *ch = &p_line->p_character[i];
2538 if( ch->i_line_thickness < 0 )
2540 ch->i_line_offset = i_ul_offset;
2541 ch->i_line_thickness = i_ul_thickness;
2546 *pp_line_next = p_line;
2547 pp_line_next = &p_line->p_next;
2550 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2552 /* Skip what we have rendered and the line delimitor if present */
2554 if( i_start < i_len && psz_text[i_start] == '\n' )
2557 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
2559 msg_Err( p_filter, "Truncated too high subtitle" );
2564 FT_Done_Face( p_face );
2566 free( pp_fribidi_styles );
2567 free( p_fribidi_string );
2568 free( pi_karaoke_bar );
2575 * This function renders a text subpicture region into another one.
2576 * It also calculates the size needed for this string, and renders the
2577 * needed glyphs into memory. It is used as pf_add_string callback in
2578 * the vout method by this module
2580 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2581 subpicture_region_t *p_region_in, bool b_html,
2582 const vlc_fourcc_t *p_chroma_list )
2584 filter_sys_t *p_sys = p_filter->p_sys;
2587 return VLC_EGENERIC;
2588 if( b_html && !p_region_in->psz_html )
2589 return VLC_EGENERIC;
2590 if( !b_html && !p_region_in->psz_text )
2591 return VLC_EGENERIC;
2593 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2594 : p_region_in->psz_text );
2596 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2597 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2598 if( !psz_text || !pp_styles )
2602 return VLC_EGENERIC;
2605 /* Reset the default fontsize in case screen metrics have changed */
2606 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2609 int rv = VLC_SUCCESS;
2610 int i_text_length = 0;
2612 int i_max_face_height;
2613 line_desc_t *p_lines = NULL;
2615 uint32_t *pi_k_durations = NULL;
2619 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2620 (uint8_t *) p_region_in->psz_html,
2621 strlen( p_region_in->psz_html ),
2623 if( unlikely(p_sub == NULL) )
2626 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2628 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2630 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2631 p_filter->p_sys->p_xml = p_xml_reader;
2638 /* Look for Root Node */
2641 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2643 if( strcasecmp( "karaoke", node ) == 0 )
2645 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2647 else if( strcasecmp( "text", node ) != 0 )
2649 /* Only text and karaoke tags are supported */
2650 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2657 msg_Err( p_filter, "Malformed HTML subtitle" );
2663 rv = ProcessNodes( p_filter,
2664 psz_text, pp_styles, pi_k_durations, &i_text_length,
2665 p_xml_reader, p_region_in->p_style );
2669 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2671 stream_Delete( p_sub );
2675 text_style_t *p_style;
2676 if( p_region_in->p_style )
2677 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2678 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2679 : p_sys->i_font_size,
2680 (p_region_in->p_style->i_font_color & 0xffffff) |
2681 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2683 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2689 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2690 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
2691 p_style = CreateStyle( p_sys->psz_fontfamily,
2693 (i_font_color & 0xffffff) |
2694 ((p_sys->i_font_opacity & 0xff) << 24),
2697 if( p_sys->b_font_bold )
2698 p_style->i_style_flags |= STYLE_BOLD;
2700 i_text_length = SetupText( p_filter,
2704 p_region_in->psz_text, p_style, 0 );
2707 if( !rv && i_text_length > 0 )
2709 rv = ProcessLines( p_filter,
2710 &p_lines, &bbox, &i_max_face_height,
2711 psz_text, pp_styles, pi_k_durations, i_text_length );
2714 p_region_out->i_x = p_region_in->i_x;
2715 p_region_out->i_y = p_region_in->i_y;
2717 /* Don't attempt to render text that couldn't be layed out
2719 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2721 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2722 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2724 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2725 p_chroma_list = p_chroma_list_yuvp;
2726 else if( !p_chroma_list || *p_chroma_list == 0 )
2727 p_chroma_list = p_chroma_list_rgba;
2729 uint8_t i_background_opacity = var_InheritInteger( p_filter, "freetype-background-opacity" );
2730 i_background_opacity = VLC_CLIP( i_background_opacity, 0, 255 );
2731 const int i_margin = i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2732 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2735 if( *p_chroma == VLC_CODEC_YUVP )
2736 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2737 else if( *p_chroma == VLC_CODEC_YUVA )
2738 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2743 else if( *p_chroma == VLC_CODEC_RGBA )
2744 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2753 /* With karaoke, we're going to have to render the text a number
2754 * of times to show the progress marker on the text.
2756 if( pi_k_durations )
2757 var_SetBool( p_filter, "text-rerender", true );
2760 FreeLines( p_lines );
2763 for( int i = 0; i < i_text_length; i++ )
2765 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2766 text_style_Delete( pp_styles[i] );
2769 free( pi_k_durations );
2774 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2775 subpicture_region_t *p_region_in,
2776 const vlc_fourcc_t *p_chroma_list )
2778 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2781 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2782 subpicture_region_t *p_region_in,
2783 const vlc_fourcc_t *p_chroma_list )
2785 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2788 /*****************************************************************************
2789 * Create: allocates osd-text video thread output method
2790 *****************************************************************************
2791 * This function allocates and initializes a Clone vout method.
2792 *****************************************************************************/
2793 static int Create( vlc_object_t *p_this )
2795 filter_t *p_filter = (filter_t *)p_this;
2796 filter_sys_t *p_sys;
2797 char *psz_fontfile = NULL;
2798 char *psz_fontfamily = NULL;
2799 char *psz_monofontfile = NULL;
2800 char *psz_monofontfamily = NULL;
2801 int i_error = 0, fontindex = 0, monofontindex = 0;
2803 /* Allocate structure */
2804 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2808 p_sys->psz_fontfamily = NULL;
2809 p_sys->p_xml = NULL;
2811 p_sys->p_library = 0;
2812 p_sys->i_font_size = 0;
2813 p_sys->i_display_height = 0;
2816 * The following variables should not be cached, as they might be changed on-the-fly:
2817 * freetype-rel-fontsize, freetype-background-opacity, freetype-background-color,
2818 * freetype-outline-thickness, freetype-color
2822 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2823 psz_monofontfamily = var_InheritString( p_filter, "freetype-monofont" );
2824 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2825 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2826 p_sys->i_font_opacity = VLC_CLIP( p_sys->i_font_opacity, 0, 255 );
2827 p_sys->b_font_bold = var_InheritBool( p_filter, "freetype-bold" );
2829 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2830 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
2831 p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2832 p_sys->i_outline_opacity = VLC_CLIP( p_sys->i_outline_opacity, 0, 255 );
2833 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2834 p_sys->i_outline_color = VLC_CLIP( p_sys->i_outline_color, 0, 0xFFFFFF );
2836 p_sys->i_shadow_opacity = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2837 p_sys->i_shadow_opacity = VLC_CLIP( p_sys->i_shadow_opacity, 0, 255 );
2838 p_sys->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2839 p_sys->i_shadow_color = VLC_CLIP( p_sys->i_shadow_color, 0, 0xFFFFFF );
2840 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2841 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2842 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
2843 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2844 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2847 /* Get Windows Font folder */
2848 wchar_t wdir[MAX_PATH];
2849 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2851 GetWindowsDirectoryW( wdir, MAX_PATH );
2852 wcscat( wdir, L"\\fonts" );
2854 p_sys->psz_win_fonts_path = FromWide( wdir );
2857 /* Set default psz_fontfamily */
2858 if( !psz_fontfamily || !*psz_fontfamily )
2860 free( psz_fontfamily );
2862 psz_fontfamily = strdup( DEFAULT_FAMILY );
2865 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2868 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2870 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2874 /* Set the current font file */
2875 p_sys->psz_fontfamily = psz_fontfamily;
2877 #ifdef HAVE_FONTCONFIG
2878 FontConfig_BuildCache( p_filter );
2881 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2882 p_sys->i_default_font_size, &fontindex );
2883 psz_monofontfile = FontConfig_Select( NULL, psz_monofontfamily, false,
2884 false, p_sys->i_default_font_size,
2886 #elif defined(__APPLE__)
2887 #if !TARGET_OS_IPHONE
2888 psz_fontfile = MacLegacy_Select( p_filter, psz_fontfamily, false, false, 0, &fontindex );
2890 #elif defined(WIN32)
2891 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2892 p_sys->i_default_font_size, &fontindex );
2895 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2897 /* If nothing is found, use the default family */
2899 psz_fontfile = strdup( psz_fontfamily );
2900 if( !psz_monofontfile )
2901 psz_monofontfile = strdup( psz_monofontfamily );
2903 #else /* !HAVE_STYLES */
2904 /* Use the default file */
2905 psz_fontfile = psz_fontfamily;
2906 psz_monofontfile = psz_monofontfamily;
2908 p_sys->psz_monofontfamily = psz_monofontfamily;
2911 i_error = FT_Init_FreeType( &p_sys->p_library );
2914 msg_Err( p_filter, "couldn't initialize freetype" );
2918 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2919 fontindex, &p_sys->p_face );
2921 if( i_error == FT_Err_Unknown_File_Format )
2923 msg_Err( p_filter, "file %s have unknown format",
2924 psz_fontfile ? psz_fontfile : "(null)" );
2929 msg_Err( p_filter, "failed to load font file %s",
2930 psz_fontfile ? psz_fontfile : "(null)" );
2934 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2937 msg_Err( p_filter, "font has no unicode translation table" );
2941 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2943 p_sys->p_stroker = NULL;
2944 if( f_outline_thickness > 0.001 )
2946 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2948 msg_Err( p_filter, "Failed to create stroker for outlining" );
2951 p_sys->pp_font_attachments = NULL;
2952 p_sys->i_font_attachments = 0;
2954 p_filter->pf_render_text = RenderText;
2955 p_filter->pf_render_html = RenderHtml;
2957 LoadFontsFromAttachments( p_filter );
2960 free( psz_fontfile );
2961 free( psz_monofontfile );
2967 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2968 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2970 free( psz_fontfile );
2971 free( psz_monofontfile );
2973 free( psz_fontfamily );
2974 free( psz_monofontfamily );
2976 return VLC_EGENERIC;
2979 /*****************************************************************************
2980 * Destroy: destroy Clone video thread output method
2981 *****************************************************************************
2982 * Clean up all data and library connections
2983 *****************************************************************************/
2984 static void Destroy( vlc_object_t *p_this )
2986 filter_t *p_filter = (filter_t *)p_this;
2987 filter_sys_t *p_sys = p_filter->p_sys;
2989 if( p_sys->pp_font_attachments )
2991 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2992 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2994 free( p_sys->pp_font_attachments );
2997 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2998 free( p_sys->psz_fontfamily );
3001 free( p_sys->psz_win_fonts_path );
3004 /* FcFini asserts calling the subfunction FcCacheFini()
3005 * even if no other library functions have been made since FcInit(),
3006 * so don't call it. */
3008 if( p_sys->p_stroker )
3009 FT_Stroker_Done( p_sys->p_stroker );
3010 FT_Done_Face( p_sys->p_face );
3011 FT_Done_FreeType( p_sys->p_library );