1 /*****************************************************************************
2 * freetype.c : Put text on the video, using freetype2
3 *****************************************************************************
4 * Copyright (C) 2002 - 2012 the VideoLAN team
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
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 *****************************************************************************/
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 DEFAULT_FONT_FILE "/Library/Fonts/Arial Unicode.ttf"
51 # define DEFAULT_FAMILY "Arial Unicode MS"
52 #elif defined( WIN32 )
53 # define DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
54 # define DEFAULT_FAMILY "Arial"
55 #elif defined( __OS2__ )
56 # define DEFAULT_FONT_FILE "/psfonts/tnrwt_k.ttf"
57 # define DEFAULT_FAMILY "Times New Roman WT K"
58 #elif defined( HAVE_MAEMO )
59 # define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf"
60 # define DEFAULT_FAMILY "Nokia Sans Bold"
61 #elif defined( __ANDROID__ )
62 # define DEFAULT_FONT_FILE "/system/fonts/DroidSans-Bold.ttf"
63 # define DEFAULT_FAMILY "Droid Sans Bold"
65 # define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
66 # define DEFAULT_FAMILY "Serif Bold"
70 #include <freetype/ftsynth.h>
71 #include FT_FREETYPE_H
75 #define FT_FLOOR(X) ((X & -64) >> 6)
76 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
78 #define FT_MulFix(v, s) (((v)*(s))>>16)
83 #include <Carbon/Carbon.h>
84 #include <sys/param.h> /* for MAXPATHLEN */
85 #undef HAVE_FONTCONFIG
90 #if defined(HAVE_FRIBIDI)
91 # include <fribidi/fribidi.h>
99 # undef HAVE_FONTCONFIG
103 #ifdef HAVE_FONTCONFIG
104 # include <fontconfig/fontconfig.h>
111 typedef uint16_t uni_char_t;
112 # define FREETYPE_TO_UCS "UCS-2LE"
114 typedef uint32_t uni_char_t;
115 # if defined(WORDS_BIGENDIAN)
116 # define FREETYPE_TO_UCS "UCS-4BE"
118 # define FREETYPE_TO_UCS "UCS-4LE"
122 /*****************************************************************************
124 *****************************************************************************/
125 static int Create ( vlc_object_t * );
126 static void Destroy( vlc_object_t * );
128 #define FONT_TEXT N_("Font")
130 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
131 #define FONT_LONGTEXT N_("Font file for the font you want to use")
133 #define FONTSIZE_TEXT N_("Font size in pixels")
134 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
135 "that will be rendered on the video. " \
136 "If set to something different than 0 this option will override the " \
137 "relative font size." )
138 #define OPACITY_TEXT N_("Text opacity")
139 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
140 "text that will be rendered on the video. 0 = transparent, " \
141 "255 = totally opaque. " )
142 #define COLOR_TEXT N_("Text default color")
143 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
144 "the video. This must be an hexadecimal (like HTML colors). The first two "\
145 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
146 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
147 #define FONTSIZER_TEXT N_("Relative font size")
148 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
149 "fonts that will be rendered on the video. If absolute font size is set, "\
150 "relative size will be overridden." )
151 #define BOLD_TEXT N_("Force bold")
153 #define BG_OPACITY_TEXT N_("Background opacity")
154 #define BG_COLOR_TEXT N_("Background color")
156 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
157 #define OUTLINE_COLOR_TEXT N_("Outline color")
158 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
160 #define SHADOW_OPACITY_TEXT N_("Shadow opacity")
161 #define SHADOW_COLOR_TEXT N_("Shadow color")
162 #define SHADOW_ANGLE_TEXT N_("Shadow angle")
163 #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
166 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
167 static const char *const ppsz_sizes_text[] = {
168 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
169 #define YUVP_TEXT N_("Use YUVP renderer")
170 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
171 "This option is only needed if you want to encode into DVB subtitles" )
173 static const int pi_color_values[] = {
174 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
175 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
176 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
178 static const char *const ppsz_color_descriptions[] = {
179 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
180 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
181 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
183 static const int pi_outline_thickness[] = {
186 static const char *const ppsz_outline_thickness[] = {
187 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
191 set_shortname( N_("Text renderer"))
192 set_description( N_("Freetype2 font renderer") )
193 set_category( CAT_VIDEO )
194 set_subcategory( SUBCAT_VIDEO_SUBPIC )
197 add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
199 add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
202 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
203 FONTSIZE_LONGTEXT, true )
204 change_integer_range( 0, 4096)
207 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
208 FONTSIZER_LONGTEXT, false )
209 change_integer_list( pi_sizes, ppsz_sizes_text )
212 /* opacity valid on 0..255, with default 255 = fully opaque */
213 add_integer_with_range( "freetype-opacity", 255, 0, 255,
214 OPACITY_TEXT, OPACITY_LONGTEXT, false )
217 /* hook to the color values list, with default 0x00ffffff = white */
218 add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
219 COLOR_LONGTEXT, false )
220 change_integer_list( pi_color_values, ppsz_color_descriptions )
223 add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
226 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
227 BG_OPACITY_TEXT, NULL, false )
229 add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
231 change_integer_list( pi_color_values, ppsz_color_descriptions )
234 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
235 OUTLINE_OPACITY_TEXT, NULL, false )
237 add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
239 change_integer_list( pi_color_values, ppsz_color_descriptions )
241 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
243 change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
246 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
247 SHADOW_OPACITY_TEXT, NULL, false )
249 add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
251 change_integer_list( pi_color_values, ppsz_color_descriptions )
253 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
254 SHADOW_ANGLE_TEXT, NULL, false )
256 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
257 SHADOW_DISTANCE_TEXT, NULL, false )
260 add_obsolete_integer( "freetype-effect" );
262 add_bool( "freetype-yuvp", false, YUVP_TEXT,
263 YUVP_LONGTEXT, true )
264 set_capability( "text renderer", 100 )
265 add_shortcut( "text" )
266 set_callbacks( Create, Destroy )
270 /*****************************************************************************
272 *****************************************************************************/
276 FT_BitmapGlyph p_glyph;
277 FT_BitmapGlyph p_outline;
278 FT_BitmapGlyph p_shadow;
279 uint32_t i_color; /* ARGB color */
280 int i_line_offset; /* underline/strikethrough offset */
281 int i_line_thickness; /* underline/strikethrough thickness */
284 typedef struct line_desc_t line_desc_t;
291 int i_character_count;
292 line_character_t *p_character;
295 typedef struct font_stack_t font_stack_t;
300 uint32_t i_color; /* ARGB */
301 uint32_t i_karaoke_bg_color; /* ARGB */
303 font_stack_t *p_next;
306 /*****************************************************************************
307 * filter_sys_t: freetype local data
308 *****************************************************************************
309 * This structure is part of the video output thread descriptor.
310 * It describes the freetype specific properties of an output thread.
311 *****************************************************************************/
314 FT_Library p_library; /* handle to library */
315 FT_Face p_face; /* handle to face object */
316 FT_Stroker p_stroker;
317 uint8_t i_font_opacity;
322 uint8_t i_background_opacity;
323 int i_background_color;
325 double f_outline_thickness;
326 uint8_t i_outline_opacity;
329 float f_shadow_vector_x;
330 float f_shadow_vector_y;
331 uint8_t i_shadow_opacity;
334 int i_default_font_size;
335 int i_display_height;
336 char* psz_fontfamily;
339 char* psz_win_fonts_path;
342 input_attachment_t **pp_font_attachments;
343 int i_font_attachments;
347 static void YUVFromRGB( uint32_t i_argb,
348 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
350 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
351 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
352 int i_blue = ( i_argb & 0x000000ff );
354 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
355 802 * i_blue + 4096 + 131072 ) >> 13, 235);
356 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
357 3598 * i_blue + 4096 + 1048576) >> 13, 240);
358 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
359 -585 * i_blue + 4096 + 1048576) >> 13, 240);
361 static void RGBFromRGB( uint32_t i_argb,
362 uint8_t *pi_r, uint8_t *pi_g, uint8_t *pi_b )
364 *pi_r = ( i_argb & 0x00ff0000 ) >> 16;
365 *pi_g = ( i_argb & 0x0000ff00 ) >> 8;
366 *pi_b = ( i_argb & 0x000000ff );
368 /*****************************************************************************
369 * Make any TTF/OTF fonts present in the attachments of the media file
370 * and store them for later use by the FreeType Engine
371 *****************************************************************************/
372 static int LoadFontsFromAttachments( filter_t *p_filter )
374 filter_sys_t *p_sys = p_filter->p_sys;
375 input_attachment_t **pp_attachments;
376 int i_attachments_cnt;
378 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
381 p_sys->i_font_attachments = 0;
382 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
383 if( !p_sys->pp_font_attachments )
386 for( int k = 0; k < i_attachments_cnt; k++ )
388 input_attachment_t *p_attach = pp_attachments[k];
390 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
391 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
392 p_attach->i_data > 0 && p_attach->p_data )
394 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
398 vlc_input_attachment_Delete( p_attach );
401 free( pp_attachments );
406 static int GetFontSize( filter_t *p_filter )
408 filter_sys_t *p_sys = p_filter->p_sys;
411 if( p_sys->i_default_font_size )
413 i_size = p_sys->i_default_font_size;
417 int i_ratio = var_GetInteger( p_filter, "freetype-rel-fontsize" );
420 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
421 p_filter->p_sys->i_display_height = p_filter->fmt_out.video.i_height;
426 msg_Warn( p_filter, "invalid fontsize, using 12" );
432 static int SetFontSize( filter_t *p_filter, int i_size )
434 filter_sys_t *p_sys = p_filter->p_sys;
438 i_size = GetFontSize( p_filter );
440 msg_Dbg( p_filter, "using fontsize: %i", i_size );
443 p_sys->i_font_size = i_size;
445 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
447 msg_Err( p_filter, "couldn't set font size to %d", i_size );
455 #ifdef HAVE_FONTCONFIG
456 static void FontConfig_BuildCache( filter_t *p_filter )
459 msg_Dbg( p_filter, "Building font databases.");
467 #if defined( WIN32 ) || defined( __APPLE__ )
468 dialog_progress_bar_t *p_dialog = NULL;
469 FcConfig *fcConfig = FcInitLoadConfig();
471 p_dialog = dialog_ProgressCreate( p_filter,
472 _("Building font cache"),
473 _("Please wait while your font cache is rebuilt.\n"
474 "This should take less than a few minutes."), NULL );
477 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
479 FcConfigBuildFonts( fcConfig );
480 #if defined( __APPLE__ )
481 // By default, scan only the directory /System/Library/Fonts.
482 // So build the set of available fonts under another directories,
483 // and add the set to the current configuration.
484 FcConfigAppFontAddDir( NULL, "~/Library/Fonts" );
485 FcConfigAppFontAddDir( NULL, "/Library/Fonts" );
486 FcConfigAppFontAddDir( NULL, "/Network/Library/Fonts" );
487 //FcConfigAppFontAddDir( NULL, "/System/Library/Fonts" );
491 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
492 dialog_ProgressDestroy( p_dialog );
497 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
501 * \brief Selects a font matching family, bold, italic provided
503 static char* FontConfig_Select( FcConfig* config, const char* family,
504 bool b_bold, bool b_italic, int i_size, int *i_idx )
506 FcResult result = FcResultMatch;
507 FcPattern *pat, *p_pat;
511 /* Create a pattern and fills it */
512 pat = FcPatternCreate();
513 if (!pat) return NULL;
516 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
517 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
518 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
519 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
523 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
525 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
526 free( psz_fontsize );
531 FcDefaultSubstitute( pat );
532 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
534 FcPatternDestroy( pat );
538 /* Find the best font for the pattern, destroy the pattern */
539 p_pat = FcFontMatch( config, pat, &result );
540 FcPatternDestroy( pat );
541 if( !p_pat || result == FcResultNoMatch ) return NULL;
543 /* Check the new pattern */
544 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
545 || ( val_b != FcTrue ) )
547 FcPatternDestroy( p_pat );
550 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
555 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
557 FcPatternDestroy( p_pat );
561 /* if( strcasecmp((const char*)val_s, family ) != 0 )
562 msg_Warn( p_filter, "fontconfig: selected font family is not"
563 "the requested one: '%s' != '%s'\n",
564 (const char*)val_s, family ); */
566 if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
568 FcPatternDestroy( p_pat );
572 FcPatternDestroy( p_pat );
573 return strdup( (const char*)val_s );
578 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
580 static int GetFileFontByName( const char *font_name, char **psz_filename )
583 wchar_t vbuffer[MAX_PATH];
584 wchar_t dbuffer[256];
586 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
590 MultiByteToWideChar( CP_ACP, 0, font_name, -1, dbuffer, 256 );
591 char *font_name_temp = FromWide( dbuffer );
592 size_t fontname_len = strlen( font_name_temp );
594 for( int index = 0;; index++ )
596 DWORD vbuflen = MAX_PATH - 1;
599 LONG i_result = RegEnumValueW( hKey, index, vbuffer, &vbuflen,
600 NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
601 if( i_result != ERROR_SUCCESS )
607 char *psz_value = FromWide( vbuffer );
609 char *s = strchr( psz_value,'(' );
610 if( s != NULL && s != psz_value ) s[-1] = '\0';
612 /* Manage concatenated font names */
613 if( strchr( psz_value, '&') ) {
614 if( strcasestr( psz_value, font_name_temp ) != NULL )
621 if( strncasecmp( psz_value, font_name_temp, fontname_len ) == 0 )
631 *psz_filename = FromWide( dbuffer );
632 free( font_name_temp );
638 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
639 DWORD type, LPARAM lParam)
641 VLC_UNUSED( metric );
642 if( (type & RASTER_FONTTYPE) ) return 1;
643 // if( lpelfe->elfScript ) FIXME
645 return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
648 static char* Win32_Select( filter_t *p_filter, const char* family,
649 bool b_bold, bool b_italic, int i_size, int *i_idx )
651 VLC_UNUSED( i_size );
653 if( !family || strlen( family ) < 1 )
658 lf.lfCharSet = DEFAULT_CHARSET;
662 lf.lfWeight = FW_BOLD;
665 wchar_t* psz_fbuffer = ToWide( family );
666 WideCharToMultiByte( CP_ACP, 0, psz_fbuffer, -1, facename, 32, " ", 0 );
667 strncpy( (LPSTR)&lf.lfFaceName, facename, 32 );
671 char *psz_filename = NULL;
672 HDC hDC = GetDC( NULL );
673 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
674 ReleaseDC(NULL, hDC);
677 if( psz_filename != NULL )
679 /* FIXME: increase i_idx, when concatenated strings */
682 /* Prepend the Windows Font path, when only a filename was provided */
683 if( strchr( psz_filename, DIR_SEP_CHAR ) )
688 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
690 free( psz_filename );
693 free( psz_filename );
697 else /* Let's take any font we can */
701 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, "arial.ttf" ) == -1 )
707 #endif /* HAVE_WIN32 */
710 static char* MacLegacy_Select( filter_t *p_filter, const char* psz_fontname,
711 bool b_bold, bool b_italic, int i_size, int *i_idx )
713 VLC_UNUSED( b_bold );
714 VLC_UNUSED( b_italic );
715 VLC_UNUSED( i_size );
717 unsigned char path[MAXPATHLEN];
720 CFStringRef cf_fontName;
721 ATSFontRef ats_font_id;
725 msg_Dbg( p_filter, "looking for %s", psz_fontname );
726 cf_fontName = CFStringCreateWithCString( kCFAllocatorDefault, psz_fontname, kCFStringEncodingUTF8 );
728 ats_font_id = ATSFontFindFromName( cf_fontName, kATSOptionFlagsIncludeDisabledMask );
729 CFRelease( cf_fontName );
731 if ( ats_font_id == 0xFFFFFFFFUL )
733 msg_Dbg( p_filter, "ATS couldn't find %s by name, checking family", psz_fontname );
734 ats_font_id = ATSFontFamilyFindFromName( cf_fontName, kATSOptionFlagsDefault );
736 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
738 msg_Err( p_filter, "ATS couldn't find either %s nor its family", psz_fontname );
742 else if( ats_font_id == 0 )
744 msg_Err( p_filter, "ATS couldn't find %s by name, won't check family", psz_fontname );
748 if ( noErr != ATSFontGetFileReference( ats_font_id, &ref ) )
750 msg_Err( p_filter, "ATS couldn't get file ref for %s", psz_fontname );
754 /* i_idx calculation by searching preceding fontIDs */
755 /* with same FSRef */
757 ATSFontRef id2 = ats_font_id - 1;
762 if ( noErr != ATSFontGetFileReference( id2, &ref2 ) )
764 if ( noErr != FSCompareFSRefs( &ref, &ref2 ) )
769 *i_idx = ats_font_id - ( id2 + 1 );
772 if ( noErr != FSRefMakePath( &ref, path, sizeof(path) ) )
774 msg_Err( p_filter, "failure when getting path from FSRef" );
777 msg_Dbg( p_filter, "found %s", path );
779 psz_path = strdup( (char *)path );
785 #endif /* HAVE_STYLES */
788 /*****************************************************************************
789 * RenderYUVP: place string in picture
790 *****************************************************************************
791 * This function merges the previously rendered freetype glyphs into a picture
792 *****************************************************************************/
793 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
797 VLC_UNUSED(p_filter);
798 static const uint8_t pi_gamma[16] =
799 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
800 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
804 int i, x, y, i_pitch;
805 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
807 /* Create a new subpicture region */
808 video_format_Init( &fmt, VLC_CODEC_YUVP );
810 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
812 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
814 assert( !p_region->p_picture );
815 p_region->p_picture = picture_NewFromFormat( &fmt );
816 if( !p_region->p_picture )
818 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
821 /* Calculate text color components
822 * Only use the first color */
823 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
824 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
827 fmt.p_palette->i_entries = 16;
828 for( i = 0; i < 8; i++ )
830 fmt.p_palette->palette[i][0] = 0;
831 fmt.p_palette->palette[i][1] = 0x80;
832 fmt.p_palette->palette[i][2] = 0x80;
833 fmt.p_palette->palette[i][3] = pi_gamma[i];
834 fmt.p_palette->palette[i][3] =
835 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
837 for( i = 8; i < fmt.p_palette->i_entries; i++ )
839 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
840 fmt.p_palette->palette[i][1] = i_u;
841 fmt.p_palette->palette[i][2] = i_v;
842 fmt.p_palette->palette[i][3] = pi_gamma[i];
843 fmt.p_palette->palette[i][3] =
844 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
847 p_dst = p_region->p_picture->Y_PIXELS;
848 i_pitch = p_region->p_picture->Y_PITCH;
850 /* Initialize the region pixels */
851 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
853 for( ; p_line != NULL; p_line = p_line->p_next )
855 int i_align_left = 0;
856 if( p_line->i_width < (int)fmt.i_visible_width )
858 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
859 i_align_left = ( fmt.i_visible_width - p_line->i_width );
860 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
861 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
865 for( i = 0; i < p_line->i_character_count; i++ )
867 const line_character_t *ch = &p_line->p_character[i];
868 FT_BitmapGlyph p_glyph = ch->p_glyph;
870 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
871 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
873 for( y = 0; y < p_glyph->bitmap.rows; y++ )
875 for( x = 0; x < p_glyph->bitmap.width; x++ )
877 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
878 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
879 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
885 /* Outlining (find something better than nearest neighbour filtering ?) */
888 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
889 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
890 uint8_t left, current;
892 for( y = 1; y < (int)fmt.i_height - 1; y++ )
894 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
895 p_dst += p_region->p_picture->Y_PITCH;
898 for( x = 1; x < (int)fmt.i_width - 1; x++ )
901 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
902 p_dst[x -1 + p_region->p_picture->Y_PITCH ] + p_dst[x + p_region->p_picture->Y_PITCH] + p_dst[x + 1 + p_region->p_picture->Y_PITCH]) / 16;
906 memset( p_top, 0, fmt.i_width );
912 /*****************************************************************************
913 * RenderYUVA: place string in picture
914 *****************************************************************************
915 * This function merges the previously rendered freetype glyphs into a picture
916 *****************************************************************************/
917 static void FillYUVAPicture( picture_t *p_picture,
918 int i_a, int i_y, int i_u, int i_v )
920 memset( p_picture->p[0].p_pixels, i_y,
921 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
922 memset( p_picture->p[1].p_pixels, i_u,
923 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
924 memset( p_picture->p[2].p_pixels, i_v,
925 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
926 memset( p_picture->p[3].p_pixels, i_a,
927 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
930 static inline void BlendYUVAPixel( picture_t *p_picture,
931 int i_picture_x, int i_picture_y,
932 int i_a, int i_y, int i_u, int i_v,
935 int i_an = i_a * i_alpha / 255;
937 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
938 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
939 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
940 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
952 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
955 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
956 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
957 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
962 static void FillRGBAPicture( picture_t *p_picture,
963 int i_a, int i_r, int i_g, int i_b )
965 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
967 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
969 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
978 static inline void BlendRGBAPixel( picture_t *p_picture,
979 int i_picture_x, int i_picture_y,
980 int i_a, int i_r, int i_g, int i_b,
983 int i_an = i_a * i_alpha / 255;
985 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
987 int i_ao = p_rgba[3];
997 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
1000 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
1001 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
1002 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
1007 static inline void BlendAXYZGlyph( picture_t *p_picture,
1008 int i_picture_x, int i_picture_y,
1009 int i_a, int i_x, int i_y, int i_z,
1010 FT_BitmapGlyph p_glyph,
1011 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1014 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
1016 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
1017 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
1019 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
1023 static inline void BlendAXYZLine( picture_t *p_picture,
1024 int i_picture_x, int i_picture_y,
1025 int i_a, int i_x, int i_y, int i_z,
1026 const line_character_t *p_current,
1027 const line_character_t *p_next,
1028 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1030 int i_line_width = p_current->p_glyph->bitmap.width;
1032 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
1034 for( int dx = 0; dx < i_line_width; dx++ )
1036 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
1037 BlendPixel( p_picture,
1039 i_picture_y + p_current->i_line_offset + dy,
1040 i_a, i_x, i_y, i_z, 0xff );
1044 static inline int RenderAXYZ( filter_t *p_filter,
1045 subpicture_region_t *p_region,
1046 line_desc_t *p_line_head,
1049 vlc_fourcc_t i_chroma,
1050 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
1051 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
1052 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1054 filter_sys_t *p_sys = p_filter->p_sys;
1056 /* Create a new subpicture region */
1057 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
1058 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
1060 video_format_Init( &fmt, i_chroma );
1062 fmt.i_visible_width = i_text_width + 2 * i_margin;
1064 fmt.i_visible_height = i_text_height + 2 * i_margin;
1066 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
1067 if( !p_region->p_picture )
1068 return VLC_EGENERIC;
1069 p_region->fmt = fmt;
1071 /* Initialize the picture background */
1072 uint8_t i_a = p_sys->i_background_opacity;
1073 uint8_t i_x, i_y, i_z;
1074 ExtractComponents( p_sys->i_background_color, &i_x, &i_y, &i_z );
1076 FillPicture( p_picture, i_a, i_x, i_y, i_z );
1078 /* Render shadow then outline and then normal glyphs */
1079 for( int g = 0; g < 3; g++ )
1081 /* Render all lines */
1082 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
1084 int i_align_left = i_margin;
1085 if( p_line->i_width < i_text_width )
1087 /* Left offset to take into account alignment */
1088 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1089 i_align_left += ( i_text_width - p_line->i_width );
1090 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1091 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1093 int i_align_top = i_margin;
1095 /* Render all glyphs and underline/strikethrough */
1096 for( int i = 0; i < p_line->i_character_count; i++ )
1098 const line_character_t *ch = &p_line->p_character[i];
1099 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
1103 i_a = (ch->i_color >> 24) & 0xff;
1107 i_a = i_a * p_sys->i_shadow_opacity / 255;
1108 i_color = p_sys->i_shadow_color;
1111 i_a = i_a * p_sys->i_outline_opacity / 255;
1112 i_color = p_sys->i_outline_color;
1115 i_color = ch->i_color;
1118 ExtractComponents( i_color, &i_x, &i_y, &i_z );
1120 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
1121 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
1123 BlendAXYZGlyph( p_picture,
1124 i_glyph_x, i_glyph_y,
1129 /* underline/strikethrough are only rendered for the normal glyph */
1130 if( g == 2 && ch->i_line_thickness > 0 )
1131 BlendAXYZLine( p_picture,
1132 i_glyph_x, i_glyph_y + p_glyph->top,
1135 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
1144 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
1145 uint32_t i_font_color, uint32_t i_karaoke_bg_color,
1148 text_style_t *p_style = text_style_New();
1152 p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
1153 p_style->i_font_size = i_font_size;
1154 p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0;
1155 p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
1156 p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0;
1157 p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
1158 p_style->i_style_flags |= i_style_flags;
1162 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1163 uint32_t i_color, uint32_t i_karaoke_bg_color )
1166 return VLC_EGENERIC;
1168 font_stack_t *p_new = malloc( sizeof(*p_new) );
1172 p_new->p_next = NULL;
1175 p_new->psz_name = strdup( psz_name );
1177 p_new->psz_name = NULL;
1179 p_new->i_size = i_size;
1180 p_new->i_color = i_color;
1181 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1189 font_stack_t *p_last;
1191 for( p_last = *p_font;
1193 p_last = p_last->p_next )
1196 p_last->p_next = p_new;
1201 static int PopFont( font_stack_t **p_font )
1203 font_stack_t *p_last, *p_next_to_last;
1205 if( !p_font || !*p_font )
1206 return VLC_EGENERIC;
1208 p_next_to_last = NULL;
1209 for( p_last = *p_font;
1211 p_last = p_last->p_next )
1213 p_next_to_last = p_last;
1216 if( p_next_to_last )
1217 p_next_to_last->p_next = NULL;
1221 free( p_last->psz_name );
1227 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1228 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1230 font_stack_t *p_last;
1232 if( !p_font || !*p_font )
1233 return VLC_EGENERIC;
1235 for( p_last=*p_font;
1237 p_last=p_last->p_next )
1240 *psz_name = p_last->psz_name;
1241 *i_size = p_last->i_size;
1242 *i_color = p_last->i_color;
1243 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1248 static const struct {
1249 const char *psz_name;
1251 } p_html_colors[] = {
1252 /* Official html colors */
1253 { "Aqua", 0x00FFFF },
1254 { "Black", 0x000000 },
1255 { "Blue", 0x0000FF },
1256 { "Fuchsia", 0xFF00FF },
1257 { "Gray", 0x808080 },
1258 { "Green", 0x008000 },
1259 { "Lime", 0x00FF00 },
1260 { "Maroon", 0x800000 },
1261 { "Navy", 0x000080 },
1262 { "Olive", 0x808000 },
1263 { "Purple", 0x800080 },
1264 { "Red", 0xFF0000 },
1265 { "Silver", 0xC0C0C0 },
1266 { "Teal", 0x008080 },
1267 { "White", 0xFFFFFF },
1268 { "Yellow", 0xFFFF00 },
1271 { "AliceBlue", 0xF0F8FF },
1272 { "AntiqueWhite", 0xFAEBD7 },
1273 { "Aqua", 0x00FFFF },
1274 { "Aquamarine", 0x7FFFD4 },
1275 { "Azure", 0xF0FFFF },
1276 { "Beige", 0xF5F5DC },
1277 { "Bisque", 0xFFE4C4 },
1278 { "Black", 0x000000 },
1279 { "BlanchedAlmond", 0xFFEBCD },
1280 { "Blue", 0x0000FF },
1281 { "BlueViolet", 0x8A2BE2 },
1282 { "Brown", 0xA52A2A },
1283 { "BurlyWood", 0xDEB887 },
1284 { "CadetBlue", 0x5F9EA0 },
1285 { "Chartreuse", 0x7FFF00 },
1286 { "Chocolate", 0xD2691E },
1287 { "Coral", 0xFF7F50 },
1288 { "CornflowerBlue", 0x6495ED },
1289 { "Cornsilk", 0xFFF8DC },
1290 { "Crimson", 0xDC143C },
1291 { "Cyan", 0x00FFFF },
1292 { "DarkBlue", 0x00008B },
1293 { "DarkCyan", 0x008B8B },
1294 { "DarkGoldenRod", 0xB8860B },
1295 { "DarkGray", 0xA9A9A9 },
1296 { "DarkGrey", 0xA9A9A9 },
1297 { "DarkGreen", 0x006400 },
1298 { "DarkKhaki", 0xBDB76B },
1299 { "DarkMagenta", 0x8B008B },
1300 { "DarkOliveGreen", 0x556B2F },
1301 { "Darkorange", 0xFF8C00 },
1302 { "DarkOrchid", 0x9932CC },
1303 { "DarkRed", 0x8B0000 },
1304 { "DarkSalmon", 0xE9967A },
1305 { "DarkSeaGreen", 0x8FBC8F },
1306 { "DarkSlateBlue", 0x483D8B },
1307 { "DarkSlateGray", 0x2F4F4F },
1308 { "DarkSlateGrey", 0x2F4F4F },
1309 { "DarkTurquoise", 0x00CED1 },
1310 { "DarkViolet", 0x9400D3 },
1311 { "DeepPink", 0xFF1493 },
1312 { "DeepSkyBlue", 0x00BFFF },
1313 { "DimGray", 0x696969 },
1314 { "DimGrey", 0x696969 },
1315 { "DodgerBlue", 0x1E90FF },
1316 { "FireBrick", 0xB22222 },
1317 { "FloralWhite", 0xFFFAF0 },
1318 { "ForestGreen", 0x228B22 },
1319 { "Fuchsia", 0xFF00FF },
1320 { "Gainsboro", 0xDCDCDC },
1321 { "GhostWhite", 0xF8F8FF },
1322 { "Gold", 0xFFD700 },
1323 { "GoldenRod", 0xDAA520 },
1324 { "Gray", 0x808080 },
1325 { "Grey", 0x808080 },
1326 { "Green", 0x008000 },
1327 { "GreenYellow", 0xADFF2F },
1328 { "HoneyDew", 0xF0FFF0 },
1329 { "HotPink", 0xFF69B4 },
1330 { "IndianRed", 0xCD5C5C },
1331 { "Indigo", 0x4B0082 },
1332 { "Ivory", 0xFFFFF0 },
1333 { "Khaki", 0xF0E68C },
1334 { "Lavender", 0xE6E6FA },
1335 { "LavenderBlush", 0xFFF0F5 },
1336 { "LawnGreen", 0x7CFC00 },
1337 { "LemonChiffon", 0xFFFACD },
1338 { "LightBlue", 0xADD8E6 },
1339 { "LightCoral", 0xF08080 },
1340 { "LightCyan", 0xE0FFFF },
1341 { "LightGoldenRodYellow", 0xFAFAD2 },
1342 { "LightGray", 0xD3D3D3 },
1343 { "LightGrey", 0xD3D3D3 },
1344 { "LightGreen", 0x90EE90 },
1345 { "LightPink", 0xFFB6C1 },
1346 { "LightSalmon", 0xFFA07A },
1347 { "LightSeaGreen", 0x20B2AA },
1348 { "LightSkyBlue", 0x87CEFA },
1349 { "LightSlateGray", 0x778899 },
1350 { "LightSlateGrey", 0x778899 },
1351 { "LightSteelBlue", 0xB0C4DE },
1352 { "LightYellow", 0xFFFFE0 },
1353 { "Lime", 0x00FF00 },
1354 { "LimeGreen", 0x32CD32 },
1355 { "Linen", 0xFAF0E6 },
1356 { "Magenta", 0xFF00FF },
1357 { "Maroon", 0x800000 },
1358 { "MediumAquaMarine", 0x66CDAA },
1359 { "MediumBlue", 0x0000CD },
1360 { "MediumOrchid", 0xBA55D3 },
1361 { "MediumPurple", 0x9370D8 },
1362 { "MediumSeaGreen", 0x3CB371 },
1363 { "MediumSlateBlue", 0x7B68EE },
1364 { "MediumSpringGreen", 0x00FA9A },
1365 { "MediumTurquoise", 0x48D1CC },
1366 { "MediumVioletRed", 0xC71585 },
1367 { "MidnightBlue", 0x191970 },
1368 { "MintCream", 0xF5FFFA },
1369 { "MistyRose", 0xFFE4E1 },
1370 { "Moccasin", 0xFFE4B5 },
1371 { "NavajoWhite", 0xFFDEAD },
1372 { "Navy", 0x000080 },
1373 { "OldLace", 0xFDF5E6 },
1374 { "Olive", 0x808000 },
1375 { "OliveDrab", 0x6B8E23 },
1376 { "Orange", 0xFFA500 },
1377 { "OrangeRed", 0xFF4500 },
1378 { "Orchid", 0xDA70D6 },
1379 { "PaleGoldenRod", 0xEEE8AA },
1380 { "PaleGreen", 0x98FB98 },
1381 { "PaleTurquoise", 0xAFEEEE },
1382 { "PaleVioletRed", 0xD87093 },
1383 { "PapayaWhip", 0xFFEFD5 },
1384 { "PeachPuff", 0xFFDAB9 },
1385 { "Peru", 0xCD853F },
1386 { "Pink", 0xFFC0CB },
1387 { "Plum", 0xDDA0DD },
1388 { "PowderBlue", 0xB0E0E6 },
1389 { "Purple", 0x800080 },
1390 { "Red", 0xFF0000 },
1391 { "RosyBrown", 0xBC8F8F },
1392 { "RoyalBlue", 0x4169E1 },
1393 { "SaddleBrown", 0x8B4513 },
1394 { "Salmon", 0xFA8072 },
1395 { "SandyBrown", 0xF4A460 },
1396 { "SeaGreen", 0x2E8B57 },
1397 { "SeaShell", 0xFFF5EE },
1398 { "Sienna", 0xA0522D },
1399 { "Silver", 0xC0C0C0 },
1400 { "SkyBlue", 0x87CEEB },
1401 { "SlateBlue", 0x6A5ACD },
1402 { "SlateGray", 0x708090 },
1403 { "SlateGrey", 0x708090 },
1404 { "Snow", 0xFFFAFA },
1405 { "SpringGreen", 0x00FF7F },
1406 { "SteelBlue", 0x4682B4 },
1407 { "Tan", 0xD2B48C },
1408 { "Teal", 0x008080 },
1409 { "Thistle", 0xD8BFD8 },
1410 { "Tomato", 0xFF6347 },
1411 { "Turquoise", 0x40E0D0 },
1412 { "Violet", 0xEE82EE },
1413 { "Wheat", 0xF5DEB3 },
1414 { "White", 0xFFFFFF },
1415 { "WhiteSmoke", 0xF5F5F5 },
1416 { "Yellow", 0xFFFF00 },
1417 { "YellowGreen", 0x9ACD32 },
1422 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1423 font_stack_t **p_fonts )
1426 char *psz_fontname = NULL;
1427 uint32_t i_font_color = 0xffffff;
1428 int i_font_alpha = 255;
1429 uint32_t i_karaoke_bg_color = 0x00ffffff;
1430 int i_font_size = 24;
1432 /* Default all attributes to the top font in the stack -- in case not
1433 * all attributes are specified in the sub-font
1435 if( VLC_SUCCESS == PeekFont( p_fonts,
1439 &i_karaoke_bg_color ))
1441 psz_fontname = strdup( psz_fontname );
1443 i_font_alpha = (i_font_color >> 24) & 0xff;
1444 i_font_color &= 0x00ffffff;
1446 const char *name, *value;
1447 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1449 if( !strcasecmp( "face", name ) )
1451 free( psz_fontname );
1452 psz_fontname = strdup( value );
1454 else if( !strcasecmp( "size", name ) )
1456 if( ( *value == '+' ) || ( *value == '-' ) )
1458 int i_value = atoi( value );
1460 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1461 i_font_size += ( i_value * i_font_size ) / 10;
1462 else if( i_value < -5 )
1463 i_font_size = - i_value;
1464 else if( i_value > 5 )
1465 i_font_size = i_value;
1468 i_font_size = atoi( value );
1470 else if( !strcasecmp( "color", name ) )
1472 if( value[0] == '#' )
1474 i_font_color = strtol( value + 1, NULL, 16 );
1475 i_font_color &= 0x00ffffff;
1480 uint32_t i_value = strtol( value, &end, 16 );
1481 if( *end == '\0' || *end == ' ' )
1482 i_font_color = i_value & 0x00ffffff;
1484 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1486 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1488 i_font_color = p_html_colors[i].i_value;
1494 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1496 i_font_alpha = strtol( value + 1, NULL, 16 );
1497 i_font_alpha &= 0xff;
1500 rv = PushFont( p_fonts,
1503 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1504 i_karaoke_bg_color );
1506 free( psz_fontname );
1511 /* Turn any multiple-whitespaces into single spaces */
1512 static void HandleWhiteSpace( char *psz_node )
1514 char *s = strpbrk( psz_node, "\t\r\n " );
1517 int i_whitespace = strspn( s, "\t\r\n " );
1519 if( i_whitespace > 1 )
1522 strlen( s ) - i_whitespace + 1 );
1525 s = strpbrk( s, "\t\r\n " );
1530 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1531 font_stack_t **p_fonts,
1534 char *psz_fontname = NULL;
1535 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1536 uint32_t i_karaoke_bg_color = i_font_color;
1537 int i_font_size = p_sys->i_font_size;
1539 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1540 &i_font_color, &i_karaoke_bg_color ) )
1543 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1548 static unsigned SetupText( filter_t *p_filter,
1549 uni_char_t *psz_text_out,
1550 text_style_t **pp_styles,
1551 uint32_t *pi_k_dates,
1553 const char *psz_text_in,
1554 text_style_t *p_style,
1557 size_t i_string_length;
1559 size_t i_string_bytes;
1560 uni_char_t *psz_tmp = ToCharset( FREETYPE_TO_UCS, psz_text_in, &i_string_bytes );
1563 memcpy( psz_text_out, psz_tmp, i_string_bytes );
1564 i_string_length = i_string_bytes / sizeof( *psz_tmp );
1569 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1570 i_string_length = 0;
1573 if( i_string_length > 0 )
1575 for( unsigned i = 0; i < i_string_length; i++ )
1576 pp_styles[i] = p_style;
1580 text_style_Delete( p_style );
1582 if( i_string_length > 0 && pi_k_dates )
1584 for( unsigned i = 0; i < i_string_length; i++ )
1585 pi_k_dates[i] = i_k_date;
1587 return i_string_length;
1590 static int ProcessNodes( filter_t *p_filter,
1591 uni_char_t *psz_text,
1592 text_style_t **pp_styles,
1593 uint32_t *pi_k_dates,
1595 xml_reader_t *p_xml_reader,
1596 text_style_t *p_font_style )
1598 int rv = VLC_SUCCESS;
1599 filter_sys_t *p_sys = p_filter->p_sys;
1600 int i_text_length = 0;
1601 font_stack_t *p_fonts = NULL;
1602 uint32_t i_k_date = 0;
1604 int i_style_flags = 0;
1608 rv = PushFont( &p_fonts,
1609 p_font_style->psz_fontname,
1610 p_font_style->i_font_size > 0 ? p_font_style->i_font_size
1611 : p_sys->i_font_size,
1612 (p_font_style->i_font_color & 0xffffff) |
1613 ((p_font_style->i_font_alpha & 0xff) << 24),
1614 (p_font_style->i_karaoke_background_color & 0xffffff) |
1615 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1617 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1624 rv = PushFont( &p_fonts,
1625 p_sys->psz_fontfamily,
1627 (p_sys->i_font_color & 0xffffff) |
1628 ((p_sys->i_font_opacity & 0xff) << 24),
1631 if( p_sys->b_font_bold )
1632 i_style_flags |= STYLE_BOLD;
1634 if( rv != VLC_SUCCESS )
1640 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1644 case XML_READER_ENDELEM:
1645 if( !strcasecmp( "font", node ) )
1646 PopFont( &p_fonts );
1647 else if( !strcasecmp( "b", node ) )
1648 i_style_flags &= ~STYLE_BOLD;
1649 else if( !strcasecmp( "i", node ) )
1650 i_style_flags &= ~STYLE_ITALIC;
1651 else if( !strcasecmp( "u", node ) )
1652 i_style_flags &= ~STYLE_UNDERLINE;
1653 else if( !strcasecmp( "s", node ) )
1654 i_style_flags &= ~STYLE_STRIKEOUT;
1657 case XML_READER_STARTELEM:
1658 if( !strcasecmp( "font", node ) )
1659 HandleFontAttributes( p_xml_reader, &p_fonts );
1660 else if( !strcasecmp( "b", node ) )
1661 i_style_flags |= STYLE_BOLD;
1662 else if( !strcasecmp( "i", node ) )
1663 i_style_flags |= STYLE_ITALIC;
1664 else if( !strcasecmp( "u", node ) )
1665 i_style_flags |= STYLE_UNDERLINE;
1666 else if( !strcasecmp( "s", node ) )
1667 i_style_flags |= STYLE_STRIKEOUT;
1668 else if( !strcasecmp( "br", node ) )
1670 i_text_length += SetupText( p_filter,
1671 &psz_text[i_text_length],
1672 &pp_styles[i_text_length],
1673 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1675 GetStyleFromFontStack( p_sys,
1680 else if( !strcasecmp( "k", node ) )
1683 const char *name, *value;
1684 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1686 if( !strcasecmp( "t", name ) && value )
1687 i_k_date += atoi( value );
1692 case XML_READER_TEXT:
1694 char *psz_node = strdup( node );
1695 if( unlikely(!psz_node) )
1698 HandleWhiteSpace( psz_node );
1699 resolve_xml_special_chars( psz_node );
1701 i_text_length += SetupText( p_filter,
1702 &psz_text[i_text_length],
1703 &pp_styles[i_text_length],
1704 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1706 GetStyleFromFontStack( p_sys,
1716 *pi_len = i_text_length;
1718 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1723 static void FreeLine( line_desc_t *p_line )
1725 for( int i = 0; i < p_line->i_character_count; i++ )
1727 line_character_t *ch = &p_line->p_character[i];
1728 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1730 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1732 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1735 free( p_line->p_character );
1739 static void FreeLines( line_desc_t *p_lines )
1741 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1743 line_desc_t *p_next = p_line->p_next;
1749 static line_desc_t *NewLine( int i_count )
1751 line_desc_t *p_line = malloc( sizeof(*p_line) );
1756 p_line->p_next = NULL;
1757 p_line->i_width = 0;
1758 p_line->i_base_line = 0;
1759 p_line->i_character_count = 0;
1761 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1762 if( !p_line->p_character )
1770 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1772 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1774 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1776 FT_Face p_face = NULL;
1778 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1786 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1787 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1788 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1789 (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC)) == i_style_received )
1792 FT_Done_Face( p_face );
1800 static FT_Face LoadFace( filter_t *p_filter,
1801 const text_style_t *p_style )
1803 filter_sys_t *p_sys = p_filter->p_sys;
1805 /* Look for a match amongst our attachments first */
1806 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1808 /* Load system wide font otheriwse */
1812 char *psz_fontfile = NULL;
1813 #ifdef HAVE_FONTCONFIG
1814 psz_fontfile = FontConfig_Select( NULL,
1815 p_style->psz_fontname,
1816 (p_style->i_style_flags & STYLE_BOLD) != 0,
1817 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1820 #elif defined( __APPLE__ )
1821 psz_fontfile = MacLegacy_Select( p_filter, p_style->psz_fontname, false, false, -1, &i_idx );
1822 #elif defined( WIN32 )
1823 psz_fontfile = Win32_Select( p_filter,
1824 p_style->psz_fontname,
1825 (p_style->i_style_flags & STYLE_BOLD) != 0,
1826 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1830 psz_fontfile = NULL;
1835 if( *psz_fontfile == '\0' )
1838 "We were not able to find a matching font: \"%s\" (%s %s),"
1839 " so using default font",
1840 p_style->psz_fontname,
1841 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1842 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1847 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1850 free( psz_fontfile );
1855 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1857 /* We've loaded a font face which is unhelpful for actually
1858 * rendering text - fallback to the default one.
1860 FT_Done_Face( p_face );
1866 static bool FaceStyleEquals( const text_style_t *p_style1,
1867 const text_style_t *p_style2 )
1869 if( !p_style1 || !p_style2 )
1871 if( p_style1 == p_style2 )
1874 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
1875 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
1876 !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
1879 static int GetGlyph( filter_t *p_filter,
1880 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1881 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1882 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1888 FT_Vector *p_pen_shadow )
1890 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1891 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1893 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1894 return VLC_EGENERIC;
1897 /* Do synthetic styling now that Freetype supports it;
1898 * ie. if the font we have loaded is NOT already in the
1899 * style that the tags want, then switch it on; if they
1900 * are then don't. */
1901 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1902 FT_GlyphSlot_Embolden( p_face->glyph );
1903 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1904 FT_GlyphSlot_Oblique( p_face->glyph );
1907 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1909 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1910 return VLC_EGENERIC;
1913 FT_Glyph outline = NULL;
1914 if( p_filter->p_sys->p_stroker )
1917 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1921 FT_Glyph shadow = NULL;
1922 if( p_filter->p_sys->i_shadow_opacity > 0 )
1924 shadow = outline ? outline : glyph;
1925 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1931 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1934 *pp_shadow = shadow;
1936 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1938 FT_Done_Glyph( glyph );
1940 FT_Done_Glyph( outline );
1942 FT_Done_Glyph( shadow );
1943 return VLC_EGENERIC;
1945 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1950 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1951 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1953 *pp_outline = outline;
1958 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1960 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1961 if( p_bbox->xMin >= p_bbox->xMax )
1963 p_bbox->xMin = FT_CEIL(p_pen->x);
1964 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1965 glyph_bmp->left = p_bbox->xMin;
1967 if( p_bbox->yMin >= p_bbox->yMax )
1969 p_bbox->yMax = FT_CEIL(p_pen->y);
1970 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1971 glyph_bmp->top = p_bbox->yMax;
1975 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1977 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1978 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1979 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1980 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1983 static int ProcessLines( filter_t *p_filter,
1984 line_desc_t **pp_lines,
1986 int *pi_max_face_height,
1988 uni_char_t *psz_text,
1989 text_style_t **pp_styles,
1990 uint32_t *pi_k_dates,
1993 filter_sys_t *p_sys = p_filter->p_sys;
1994 uni_char_t *p_fribidi_string = NULL;
1995 text_style_t **pp_fribidi_styles = NULL;
1996 int *p_new_positions = NULL;
1998 #if defined(HAVE_FRIBIDI)
2000 int *p_old_positions;
2001 int start_pos, pos = 0;
2003 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
2005 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
2006 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
2007 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
2009 if( ! pp_fribidi_styles ||
2010 ! p_fribidi_string ||
2011 ! p_old_positions ||
2014 free( p_old_positions );
2015 free( p_new_positions );
2016 free( p_fribidi_string );
2017 free( pp_fribidi_styles );
2021 /* Do bidi conversion line-by-line */
2024 while(pos < i_len) {
2025 if (psz_text[pos] != '\n')
2027 p_fribidi_string[pos] = psz_text[pos];
2028 pp_fribidi_styles[pos] = pp_styles[pos];
2029 p_new_positions[pos] = pos;
2033 while(pos < i_len) {
2034 if (psz_text[pos] == '\n')
2038 if (pos > start_pos)
2040 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
2041 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
2043 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
2045 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
2046 pos - start_pos, &base_dir,
2047 (FriBidiChar*)p_fribidi_string + start_pos,
2048 p_new_positions + start_pos,
2051 for( int j = start_pos; j < pos; j++ )
2053 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
2054 p_new_positions[ j ] += start_pos;
2058 p_fribidi_string[ i_len ] = 0;
2059 free( p_old_positions );
2061 pp_styles = pp_fribidi_styles;
2062 psz_text = p_fribidi_string;
2065 /* Work out the karaoke */
2066 uint8_t *pi_karaoke_bar = NULL;
2069 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
2070 if( pi_karaoke_bar )
2072 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
2073 for( int i = 0; i < i_len; i++ )
2075 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
2076 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
2080 free( p_new_positions );
2082 *pi_max_face_height = 0;
2084 line_desc_t **pp_line_next = pp_lines;
2092 int i_face_height_previous = 0;
2093 int i_base_line = 0;
2094 const text_style_t *p_previous_style = NULL;
2095 FT_Face p_face = NULL;
2096 for( int i_start = 0; i_start < i_len; )
2098 /* Compute the length of the current text line */
2100 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
2103 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
2104 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
2105 int i_index = i_start;
2110 int i_face_height = 0;
2111 FT_BBox line_bbox = {
2117 int i_ul_offset = 0;
2118 int i_ul_thickness = 0;
2127 break_point_t break_point;
2128 break_point_t break_point_fallback;
2130 #define SAVE_BP(dst) do { \
2131 dst.i_index = i_index; \
2133 dst.line_bbox = line_bbox; \
2134 dst.i_face_height = i_face_height; \
2135 dst.i_ul_offset = i_ul_offset; \
2136 dst.i_ul_thickness = i_ul_thickness; \
2139 SAVE_BP( break_point );
2140 SAVE_BP( break_point_fallback );
2142 while( i_index < i_start + i_length )
2144 /* Split by common FT_Face + Size */
2145 const text_style_t *p_current_style = pp_styles[i_index];
2146 int i_part_length = 0;
2147 while( i_index + i_part_length < i_start + i_length )
2149 const text_style_t *p_style = pp_styles[i_index + i_part_length];
2150 if( !FaceStyleEquals( p_style, p_current_style ) ||
2151 p_style->i_font_size != p_current_style->i_font_size )
2156 /* (Re)load/reconfigure the face if needed */
2157 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
2160 FT_Done_Face( p_face );
2161 p_previous_style = NULL;
2163 p_face = LoadFace( p_filter, p_current_style );
2165 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
2166 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
2168 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
2169 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
2170 if( p_sys->p_stroker )
2172 int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
2173 FT_Stroker_Set( p_sys->p_stroker,
2175 FT_STROKER_LINECAP_ROUND,
2176 FT_STROKER_LINEJOIN_ROUND, 0 );
2179 p_previous_style = p_current_style;
2181 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
2182 p_current_face->size->metrics.y_scale)));
2184 /* Render the part */
2185 bool b_break_line = false;
2186 int i_glyph_last = 0;
2187 while( i_part_length > 0 )
2189 const text_style_t *p_glyph_style = pp_styles[i_index];
2190 uni_char_t character = psz_text[i_index];
2191 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
2193 /* Get kerning vector */
2194 FT_Vector kerning = { .x = 0, .y = 0 };
2195 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
2196 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
2198 /* Get the glyph bitmap and its bounding box and all the associated properties */
2199 FT_Vector pen_new = {
2200 .x = pen.x + kerning.x,
2201 .y = pen.y + kerning.y,
2203 FT_Vector pen_shadow_new = {
2204 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
2205 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
2210 FT_BBox outline_bbox;
2212 FT_BBox shadow_bbox;
2214 if( GetGlyph( p_filter,
2215 &glyph, &glyph_bbox,
2216 &outline, &outline_bbox,
2217 &shadow, &shadow_bbox,
2218 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
2219 &pen_new, &pen_shadow_new ) )
2222 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
2224 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
2226 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
2228 /* FIXME and what about outline */
2230 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
2231 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
2232 (p_glyph_style->i_karaoke_background_alpha << 24))
2233 : (p_glyph_style->i_font_color |
2234 (p_glyph_style->i_font_alpha << 24));
2235 int i_line_offset = 0;
2236 int i_line_thickness = 0;
2237 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
2239 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
2240 p_current_face->size->metrics.y_scale)) );
2242 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
2243 p_current_face->size->metrics.y_scale)) );
2245 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
2247 /* Move the baseline to make it strikethrough instead of
2248 * underline. That means that strikethrough takes precedence
2250 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
2251 p_current_face->size->metrics.y_scale)) );
2253 else if( i_line_thickness > 0 )
2255 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
2257 /* The real underline thickness and position are
2258 * updated once the whole line has been parsed */
2259 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
2260 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
2261 i_line_thickness = -1;
2264 FT_BBox line_bbox_new = line_bbox;
2265 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
2267 BBoxEnlarge( &line_bbox_new, &outline_bbox );
2269 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
2271 b_break_line = i_index > i_start &&
2272 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
2275 FT_Done_Glyph( glyph );
2277 FT_Done_Glyph( outline );
2279 FT_Done_Glyph( shadow );
2281 break_point_t *p_bp = NULL;
2282 if( break_point.i_index > i_start )
2283 p_bp = &break_point;
2284 else if( break_point_fallback.i_index > i_start )
2285 p_bp = &break_point_fallback;
2289 msg_Dbg( p_filter, "Breaking line");
2290 for( int i = p_bp->i_index; i < i_index; i++ )
2292 line_character_t *ch = &p_line->p_character[i - i_start];
2293 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
2295 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
2297 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
2299 p_line->i_character_count = p_bp->i_index - i_start;
2301 i_index = p_bp->i_index;
2303 line_bbox = p_bp->line_bbox;
2304 i_face_height = p_bp->i_face_height;
2305 i_ul_offset = p_bp->i_ul_offset;
2306 i_ul_thickness = p_bp->i_ul_thickness;
2310 msg_Err( p_filter, "Breaking unbreakable line");
2315 assert( p_line->i_character_count == i_index - i_start);
2316 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2317 .p_glyph = (FT_BitmapGlyph)glyph,
2318 .p_outline = (FT_BitmapGlyph)outline,
2319 .p_shadow = (FT_BitmapGlyph)shadow,
2321 .i_line_offset = i_line_offset,
2322 .i_line_thickness = i_line_thickness,
2325 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2326 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2327 line_bbox = line_bbox_new;
2329 i_glyph_last = i_glyph_index;
2333 if( character == ' ' || character == '\t' )
2334 SAVE_BP( break_point );
2335 else if( character == 160 )
2336 SAVE_BP( break_point_fallback );
2342 /* Update our baseline */
2343 if( i_face_height_previous > 0 )
2344 i_base_line += __MAX(i_face_height, i_face_height_previous);
2345 if( i_face_height > 0 )
2346 i_face_height_previous = i_face_height;
2348 /* Update the line bbox with the actual base line */
2349 if (line_bbox.yMax > line_bbox.yMin) {
2350 line_bbox.yMin -= i_base_line;
2351 line_bbox.yMax -= i_base_line;
2353 BBoxEnlarge( &bbox, &line_bbox );
2355 /* Terminate and append the line */
2358 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2359 p_line->i_base_line = i_base_line;
2360 if( i_ul_thickness > 0 )
2362 for( int i = 0; i < p_line->i_character_count; i++ )
2364 line_character_t *ch = &p_line->p_character[i];
2365 if( ch->i_line_thickness < 0 )
2367 ch->i_line_offset = i_ul_offset;
2368 ch->i_line_thickness = i_ul_thickness;
2373 *pp_line_next = p_line;
2374 pp_line_next = &p_line->p_next;
2377 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2379 /* Skip what we have rendered and the line delimitor if present */
2381 if( i_start < i_len && psz_text[i_start] == '\n' )
2384 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
2386 msg_Err( p_filter, "Truncated too high subtitle" );
2391 FT_Done_Face( p_face );
2393 free( pp_fribidi_styles );
2394 free( p_fribidi_string );
2395 free( pi_karaoke_bar );
2402 * This function renders a text subpicture region into another one.
2403 * It also calculates the size needed for this string, and renders the
2404 * needed glyphs into memory. It is used as pf_add_string callback in
2405 * the vout method by this module
2407 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2408 subpicture_region_t *p_region_in, bool b_html,
2409 const vlc_fourcc_t *p_chroma_list )
2411 filter_sys_t *p_sys = p_filter->p_sys;
2414 return VLC_EGENERIC;
2415 if( b_html && !p_region_in->psz_html )
2416 return VLC_EGENERIC;
2417 if( !b_html && !p_region_in->psz_text )
2418 return VLC_EGENERIC;
2420 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2421 : p_region_in->psz_text );
2423 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2424 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2425 if( !psz_text || !pp_styles )
2429 return VLC_EGENERIC;
2432 /* Reset the default fontsize in case screen metrics have changed */
2433 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2436 int rv = VLC_SUCCESS;
2437 int i_text_length = 0;
2439 int i_max_face_height;
2440 line_desc_t *p_lines = NULL;
2442 uint32_t *pi_k_durations = NULL;
2446 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2447 (uint8_t *) p_region_in->psz_html,
2448 strlen( p_region_in->psz_html ),
2450 if( unlikely(p_sub == NULL) )
2453 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2455 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2457 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2458 p_filter->p_sys->p_xml = p_xml_reader;
2465 /* Look for Root Node */
2468 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2470 if( strcasecmp( "karaoke", node ) == 0 )
2472 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2474 else if( strcasecmp( "text", node ) != 0 )
2476 /* Only text and karaoke tags are supported */
2477 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2484 msg_Err( p_filter, "Malformed HTML subtitle" );
2490 rv = ProcessNodes( p_filter,
2491 psz_text, pp_styles, pi_k_durations, &i_text_length,
2492 p_xml_reader, p_region_in->p_style );
2496 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2498 stream_Delete( p_sub );
2502 text_style_t *p_style;
2503 if( p_region_in->p_style )
2504 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2505 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2506 : p_sys->i_font_size,
2507 (p_region_in->p_style->i_font_color & 0xffffff) |
2508 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2510 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2515 p_style = CreateStyle( p_sys->psz_fontfamily,
2517 (p_sys->i_font_color & 0xffffff) |
2518 ((p_sys->i_font_opacity & 0xff) << 24),
2520 if( p_sys->b_font_bold )
2521 p_style->i_style_flags |= STYLE_BOLD;
2523 i_text_length = SetupText( p_filter,
2527 p_region_in->psz_text, p_style, 0 );
2530 if( !rv && i_text_length > 0 )
2532 rv = ProcessLines( p_filter,
2533 &p_lines, &bbox, &i_max_face_height,
2534 psz_text, pp_styles, pi_k_durations, i_text_length );
2537 p_region_out->i_x = p_region_in->i_x;
2538 p_region_out->i_y = p_region_in->i_y;
2540 /* Don't attempt to render text that couldn't be layed out
2542 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2544 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2545 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2547 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2548 p_chroma_list = p_chroma_list_yuvp;
2549 else if( !p_chroma_list || *p_chroma_list == 0 )
2550 p_chroma_list = p_chroma_list_rgba;
2552 const int i_margin = p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2553 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2556 if( *p_chroma == VLC_CODEC_YUVP )
2557 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2558 else if( *p_chroma == VLC_CODEC_YUVA )
2559 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2564 else if( *p_chroma == VLC_CODEC_RGBA )
2565 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2574 /* With karaoke, we're going to have to render the text a number
2575 * of times to show the progress marker on the text.
2577 if( pi_k_durations )
2578 var_SetBool( p_filter, "text-rerender", true );
2581 FreeLines( p_lines );
2584 for( int i = 0; i < i_text_length; i++ )
2586 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2587 text_style_Delete( pp_styles[i] );
2590 free( pi_k_durations );
2595 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2596 subpicture_region_t *p_region_in,
2597 const vlc_fourcc_t *p_chroma_list )
2599 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2602 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2603 subpicture_region_t *p_region_in,
2604 const vlc_fourcc_t *p_chroma_list )
2606 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2609 /*****************************************************************************
2610 * Create: allocates osd-text video thread output method
2611 *****************************************************************************
2612 * This function allocates and initializes a Clone vout method.
2613 *****************************************************************************/
2614 static int Create( vlc_object_t *p_this )
2616 filter_t *p_filter = (filter_t *)p_this;
2617 filter_sys_t *p_sys;
2618 char *psz_fontfile = NULL;
2619 char *psz_fontfamily = NULL;
2620 int i_error = 0, fontindex = 0;
2622 /* Allocate structure */
2623 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2627 p_sys->psz_fontfamily = NULL;
2628 p_sys->p_xml = NULL;
2630 p_sys->p_library = 0;
2631 p_sys->i_font_size = 0;
2632 p_sys->i_display_height = 0;
2634 var_Create( p_filter, "freetype-rel-fontsize",
2635 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2637 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2638 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2639 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2640 p_sys->i_font_opacity = VLC_CLIP( p_sys->i_font_opacity, 0, 255 );
2641 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2642 p_sys->i_font_color = VLC_CLIP( p_sys->i_font_color, 0, 0xFFFFFF );
2643 p_sys->b_font_bold = var_InheritBool( p_filter, "freetype-bold" );
2645 p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
2646 p_sys->i_background_opacity = VLC_CLIP( p_sys->i_background_opacity, 0, 255 );
2647 p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
2648 p_sys->i_background_color = VLC_CLIP( p_sys->i_background_color, 0, 0xFFFFFF );
2650 p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2651 p_sys->f_outline_thickness = VLC_CLIP( p_sys->f_outline_thickness, 0.0, 0.5 );
2652 p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2653 p_sys->i_outline_opacity = VLC_CLIP( p_sys->i_outline_opacity, 0, 255 );
2654 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2655 p_sys->i_outline_color = VLC_CLIP( p_sys->i_outline_color, 0, 0xFFFFFF );
2657 p_sys->i_shadow_opacity = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2658 p_sys->i_shadow_opacity = VLC_CLIP( p_sys->i_shadow_opacity, 0, 255 );
2659 p_sys->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2660 p_sys->i_shadow_color = VLC_CLIP( p_sys->i_shadow_color, 0, 0xFFFFFF );
2661 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2662 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2663 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
2664 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2665 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2668 /* Get Windows Font folder */
2669 wchar_t wdir[MAX_PATH];
2670 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2672 GetWindowsDirectoryW( wdir, MAX_PATH );
2673 wcscat( wdir, L"\\fonts" );
2675 p_sys->psz_win_fonts_path = FromWide( wdir );
2678 /* Set default psz_fontfamily */
2679 if( !psz_fontfamily || !*psz_fontfamily )
2681 free( psz_fontfamily );
2683 psz_fontfamily = strdup( DEFAULT_FAMILY );
2686 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2689 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2691 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2695 /* Set the current font file */
2696 p_sys->psz_fontfamily = psz_fontfamily;
2698 #ifdef HAVE_FONTCONFIG
2699 FontConfig_BuildCache( p_filter );
2702 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2703 p_sys->i_default_font_size, &fontindex );
2704 #elif defined(__APPLE__)
2705 psz_fontfile = MacLegacy_Select( p_filter, psz_fontfamily, false, false, 0, &fontindex );
2706 #elif defined(WIN32)
2707 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2708 p_sys->i_default_font_size, &fontindex );
2711 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2713 /* If nothing is found, use the default family */
2715 psz_fontfile = strdup( psz_fontfamily );
2717 #else /* !HAVE_STYLES */
2718 /* Use the default file */
2719 psz_fontfile = psz_fontfamily;
2723 i_error = FT_Init_FreeType( &p_sys->p_library );
2726 msg_Err( p_filter, "couldn't initialize freetype" );
2730 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2731 fontindex, &p_sys->p_face );
2733 if( i_error == FT_Err_Unknown_File_Format )
2735 msg_Err( p_filter, "file %s have unknown format",
2736 psz_fontfile ? psz_fontfile : "(null)" );
2741 msg_Err( p_filter, "failed to load font file %s",
2742 psz_fontfile ? psz_fontfile : "(null)" );
2746 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2749 msg_Err( p_filter, "font has no unicode translation table" );
2753 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2755 p_sys->p_stroker = NULL;
2756 if( p_sys->f_outline_thickness > 0.001 )
2758 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2760 msg_Err( p_filter, "Failed to create stroker for outlining" );
2763 p_sys->pp_font_attachments = NULL;
2764 p_sys->i_font_attachments = 0;
2766 p_filter->pf_render_text = RenderText;
2767 p_filter->pf_render_html = RenderHtml;
2769 LoadFontsFromAttachments( p_filter );
2772 free( psz_fontfile );
2778 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2779 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2781 free( psz_fontfile );
2783 free( psz_fontfamily );
2785 return VLC_EGENERIC;
2788 /*****************************************************************************
2789 * Destroy: destroy Clone video thread output method
2790 *****************************************************************************
2791 * Clean up all data and library connections
2792 *****************************************************************************/
2793 static void Destroy( vlc_object_t *p_this )
2795 filter_t *p_filter = (filter_t *)p_this;
2796 filter_sys_t *p_sys = p_filter->p_sys;
2798 if( p_sys->pp_font_attachments )
2800 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2801 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2803 free( p_sys->pp_font_attachments );
2806 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2807 free( p_sys->psz_fontfamily );
2809 /* FcFini asserts calling the subfunction FcCacheFini()
2810 * even if no other library functions have been made since FcInit(),
2811 * so don't call it. */
2813 if( p_sys->p_stroker )
2814 FT_Stroker_Done( p_sys->p_stroker );
2815 FT_Done_Face( p_sys->p_face );
2816 FT_Done_FreeType( p_sys->p_library );