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;
512 /* Create a pattern and fills it */
513 pat = FcPatternCreate();
514 if (!pat) return NULL;
517 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
518 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
519 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
520 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
524 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
526 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
527 free( psz_fontsize );
532 FcDefaultSubstitute( pat );
533 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
535 FcPatternDestroy( pat );
539 /* Find the best font for the pattern, destroy the pattern */
540 p_pat = FcFontMatch( config, pat, &result );
541 FcPatternDestroy( pat );
542 if( !p_pat || result == FcResultNoMatch ) return NULL;
544 /* Check the new pattern */
545 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
546 || ( val_b != FcTrue ) )
548 FcPatternDestroy( p_pat );
551 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
556 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
558 FcPatternDestroy( p_pat );
562 /* if( strcasecmp((const char*)val_s, family ) != 0 )
563 msg_Warn( p_filter, "fontconfig: selected font family is not"
564 "the requested one: '%s' != '%s'\n",
565 (const char*)val_s, family ); */
567 if( FcResultMatch == FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
568 ret = strdup( (const char*)val_s );
570 FcPatternDestroy( p_pat );
576 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
578 static int GetFileFontByName( const char *font_name, char **psz_filename )
581 wchar_t vbuffer[MAX_PATH];
582 wchar_t dbuffer[256];
584 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
588 MultiByteToWideChar( CP_ACP, 0, font_name, -1, dbuffer, 256 );
589 char *font_name_temp = FromWide( dbuffer );
590 size_t fontname_len = strlen( font_name_temp );
592 for( int index = 0;; index++ )
594 DWORD vbuflen = MAX_PATH - 1;
597 LONG i_result = RegEnumValueW( hKey, index, vbuffer, &vbuflen,
598 NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
599 if( i_result != ERROR_SUCCESS )
605 char *psz_value = FromWide( vbuffer );
607 char *s = strchr( psz_value,'(' );
608 if( s != NULL && s != psz_value ) s[-1] = '\0';
610 /* Manage concatenated font names */
611 if( strchr( psz_value, '&') ) {
612 if( strcasestr( psz_value, font_name_temp ) != NULL )
619 if( strncasecmp( psz_value, font_name_temp, fontname_len ) == 0 )
629 *psz_filename = FromWide( dbuffer );
630 free( font_name_temp );
636 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
637 DWORD type, LPARAM lParam)
639 VLC_UNUSED( metric );
640 if( (type & RASTER_FONTTYPE) ) return 1;
641 // if( lpelfe->elfScript ) FIXME
643 return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
646 static char* Win32_Select( filter_t *p_filter, const char* family,
647 bool b_bold, bool b_italic, int i_size, int *i_idx )
649 VLC_UNUSED( i_size );
651 if( !family || strlen( family ) < 1 )
656 lf.lfCharSet = DEFAULT_CHARSET;
660 lf.lfWeight = FW_BOLD;
663 wchar_t* psz_fbuffer = ToWide( family );
664 WideCharToMultiByte( CP_ACP, 0, psz_fbuffer, -1, facename, 32, " ", 0 );
665 strncpy( (LPSTR)&lf.lfFaceName, facename, 32 );
669 char *psz_filename = NULL;
670 HDC hDC = GetDC( NULL );
671 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
672 ReleaseDC(NULL, hDC);
675 if( psz_filename != NULL )
677 /* FIXME: increase i_idx, when concatenated strings */
680 /* Prepend the Windows Font path, when only a filename was provided */
681 if( strchr( psz_filename, DIR_SEP_CHAR ) )
686 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
688 free( psz_filename );
691 free( psz_filename );
695 else /* Let's take any font we can */
699 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, "arial.ttf" ) == -1 )
705 #endif /* HAVE_WIN32 */
708 static char* MacLegacy_Select( filter_t *p_filter, const char* psz_fontname,
709 bool b_bold, bool b_italic, int i_size, int *i_idx )
711 VLC_UNUSED( b_bold );
712 VLC_UNUSED( b_italic );
713 VLC_UNUSED( i_size );
715 unsigned char path[MAXPATHLEN];
718 CFStringRef cf_fontName;
719 ATSFontRef ats_font_id;
723 if( psz_fontname == NULL )
726 msg_Dbg( p_filter, "looking for %s", psz_fontname );
727 cf_fontName = CFStringCreateWithCString( kCFAllocatorDefault, psz_fontname, kCFStringEncodingUTF8 );
729 ats_font_id = ATSFontFindFromName( cf_fontName, kATSOptionFlagsIncludeDisabledMask );
731 if ( ats_font_id == 0 || 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_Dbg( p_filter, "ATS couldn't find either %s nor its family, checking PS name", psz_fontname );
739 ats_font_id = ATSFontFindFromPostScriptName( cf_fontName, kATSOptionFlagsDefault );
741 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
743 msg_Err( p_filter, "ATS couldn't find %s (no font name, family or PS name)", psz_fontname );
744 CFRelease( cf_fontName );
749 CFRelease( cf_fontName );
751 if ( noErr != ATSFontGetFileReference( ats_font_id, &ref ) )
753 msg_Err( p_filter, "ATS couldn't get file ref for %s", psz_fontname );
757 /* i_idx calculation by searching preceding fontIDs */
758 /* with same FSRef */
760 ATSFontRef id2 = ats_font_id - 1;
765 if ( noErr != ATSFontGetFileReference( id2, &ref2 ) )
767 if ( noErr != FSCompareFSRefs( &ref, &ref2 ) )
772 *i_idx = ats_font_id - ( id2 + 1 );
775 if ( noErr != FSRefMakePath( &ref, path, sizeof(path) ) )
777 msg_Err( p_filter, "failure when getting path from FSRef" );
780 msg_Dbg( p_filter, "found %s", path );
782 psz_path = strdup( (char *)path );
788 #endif /* HAVE_STYLES */
791 /*****************************************************************************
792 * RenderYUVP: place string in picture
793 *****************************************************************************
794 * This function merges the previously rendered freetype glyphs into a picture
795 *****************************************************************************/
796 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
800 VLC_UNUSED(p_filter);
801 static const uint8_t pi_gamma[16] =
802 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
803 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
807 int i, x, y, i_pitch;
808 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
810 /* Create a new subpicture region */
811 video_format_Init( &fmt, VLC_CODEC_YUVP );
813 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
815 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
817 assert( !p_region->p_picture );
818 p_region->p_picture = picture_NewFromFormat( &fmt );
819 if( !p_region->p_picture )
821 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
824 /* Calculate text color components
825 * Only use the first color */
826 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
827 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
830 fmt.p_palette->i_entries = 16;
831 for( i = 0; i < 8; i++ )
833 fmt.p_palette->palette[i][0] = 0;
834 fmt.p_palette->palette[i][1] = 0x80;
835 fmt.p_palette->palette[i][2] = 0x80;
836 fmt.p_palette->palette[i][3] = pi_gamma[i];
837 fmt.p_palette->palette[i][3] =
838 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
840 for( i = 8; i < fmt.p_palette->i_entries; i++ )
842 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
843 fmt.p_palette->palette[i][1] = i_u;
844 fmt.p_palette->palette[i][2] = i_v;
845 fmt.p_palette->palette[i][3] = pi_gamma[i];
846 fmt.p_palette->palette[i][3] =
847 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
850 p_dst = p_region->p_picture->Y_PIXELS;
851 i_pitch = p_region->p_picture->Y_PITCH;
853 /* Initialize the region pixels */
854 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
856 for( ; p_line != NULL; p_line = p_line->p_next )
858 int i_align_left = 0;
859 if( p_line->i_width < (int)fmt.i_visible_width )
861 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
862 i_align_left = ( fmt.i_visible_width - p_line->i_width );
863 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
864 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
868 for( i = 0; i < p_line->i_character_count; i++ )
870 const line_character_t *ch = &p_line->p_character[i];
871 FT_BitmapGlyph p_glyph = ch->p_glyph;
873 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
874 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
876 for( y = 0; y < p_glyph->bitmap.rows; y++ )
878 for( x = 0; x < p_glyph->bitmap.width; x++ )
880 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
881 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
882 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
888 /* Outlining (find something better than nearest neighbour filtering ?) */
891 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
892 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
893 uint8_t left, current;
895 for( y = 1; y < (int)fmt.i_height - 1; y++ )
897 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
898 p_dst += p_region->p_picture->Y_PITCH;
901 for( x = 1; x < (int)fmt.i_width - 1; x++ )
904 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
905 p_dst[x -1 + p_region->p_picture->Y_PITCH ] + p_dst[x + p_region->p_picture->Y_PITCH] + p_dst[x + 1 + p_region->p_picture->Y_PITCH]) / 16;
909 memset( p_top, 0, fmt.i_width );
915 /*****************************************************************************
916 * RenderYUVA: place string in picture
917 *****************************************************************************
918 * This function merges the previously rendered freetype glyphs into a picture
919 *****************************************************************************/
920 static void FillYUVAPicture( picture_t *p_picture,
921 int i_a, int i_y, int i_u, int i_v )
923 memset( p_picture->p[0].p_pixels, i_y,
924 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
925 memset( p_picture->p[1].p_pixels, i_u,
926 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
927 memset( p_picture->p[2].p_pixels, i_v,
928 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
929 memset( p_picture->p[3].p_pixels, i_a,
930 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
933 static inline void BlendYUVAPixel( picture_t *p_picture,
934 int i_picture_x, int i_picture_y,
935 int i_a, int i_y, int i_u, int i_v,
938 int i_an = i_a * i_alpha / 255;
940 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
941 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
942 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
943 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
955 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
958 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
959 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
960 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
965 static void FillRGBAPicture( picture_t *p_picture,
966 int i_a, int i_r, int i_g, int i_b )
968 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
970 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
972 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
981 static inline void BlendRGBAPixel( picture_t *p_picture,
982 int i_picture_x, int i_picture_y,
983 int i_a, int i_r, int i_g, int i_b,
986 int i_an = i_a * i_alpha / 255;
988 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
990 int i_ao = p_rgba[3];
1000 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
1001 if( p_rgba[3] != 0 )
1003 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
1004 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
1005 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
1010 static inline void BlendAXYZGlyph( picture_t *p_picture,
1011 int i_picture_x, int i_picture_y,
1012 int i_a, int i_x, int i_y, int i_z,
1013 FT_BitmapGlyph p_glyph,
1014 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1017 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
1019 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
1020 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
1022 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
1026 static inline void BlendAXYZLine( picture_t *p_picture,
1027 int i_picture_x, int i_picture_y,
1028 int i_a, int i_x, int i_y, int i_z,
1029 const line_character_t *p_current,
1030 const line_character_t *p_next,
1031 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1033 int i_line_width = p_current->p_glyph->bitmap.width;
1035 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
1037 for( int dx = 0; dx < i_line_width; dx++ )
1039 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
1040 BlendPixel( p_picture,
1042 i_picture_y + p_current->i_line_offset + dy,
1043 i_a, i_x, i_y, i_z, 0xff );
1047 static inline int RenderAXYZ( filter_t *p_filter,
1048 subpicture_region_t *p_region,
1049 line_desc_t *p_line_head,
1052 vlc_fourcc_t i_chroma,
1053 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
1054 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
1055 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1057 filter_sys_t *p_sys = p_filter->p_sys;
1059 /* Create a new subpicture region */
1060 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
1061 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
1063 video_format_Init( &fmt, i_chroma );
1065 fmt.i_visible_width = i_text_width + 2 * i_margin;
1067 fmt.i_visible_height = i_text_height + 2 * i_margin;
1069 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
1070 if( !p_region->p_picture )
1071 return VLC_EGENERIC;
1072 p_region->fmt = fmt;
1074 /* Initialize the picture background */
1075 uint8_t i_a = p_sys->i_background_opacity;
1076 uint8_t i_x, i_y, i_z;
1077 ExtractComponents( p_sys->i_background_color, &i_x, &i_y, &i_z );
1079 FillPicture( p_picture, i_a, i_x, i_y, i_z );
1081 /* Render shadow then outline and then normal glyphs */
1082 for( int g = 0; g < 3; g++ )
1084 /* Render all lines */
1085 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
1087 int i_align_left = i_margin;
1088 if( p_line->i_width < i_text_width )
1090 /* Left offset to take into account alignment */
1091 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1092 i_align_left += ( i_text_width - p_line->i_width );
1093 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1094 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1096 int i_align_top = i_margin;
1098 /* Render all glyphs and underline/strikethrough */
1099 for( int i = 0; i < p_line->i_character_count; i++ )
1101 const line_character_t *ch = &p_line->p_character[i];
1102 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
1106 i_a = (ch->i_color >> 24) & 0xff;
1110 i_a = i_a * p_sys->i_shadow_opacity / 255;
1111 i_color = p_sys->i_shadow_color;
1114 i_a = i_a * p_sys->i_outline_opacity / 255;
1115 i_color = p_sys->i_outline_color;
1118 i_color = ch->i_color;
1121 ExtractComponents( i_color, &i_x, &i_y, &i_z );
1123 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
1124 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
1126 BlendAXYZGlyph( p_picture,
1127 i_glyph_x, i_glyph_y,
1132 /* underline/strikethrough are only rendered for the normal glyph */
1133 if( g == 2 && ch->i_line_thickness > 0 )
1134 BlendAXYZLine( p_picture,
1135 i_glyph_x, i_glyph_y + p_glyph->top,
1138 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
1147 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
1148 uint32_t i_font_color, uint32_t i_karaoke_bg_color,
1151 text_style_t *p_style = text_style_New();
1155 p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
1156 p_style->i_font_size = i_font_size;
1157 p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0;
1158 p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
1159 p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0;
1160 p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
1161 p_style->i_style_flags |= i_style_flags;
1165 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1166 uint32_t i_color, uint32_t i_karaoke_bg_color )
1169 return VLC_EGENERIC;
1171 font_stack_t *p_new = malloc( sizeof(*p_new) );
1175 p_new->p_next = NULL;
1178 p_new->psz_name = strdup( psz_name );
1180 p_new->psz_name = NULL;
1182 p_new->i_size = i_size;
1183 p_new->i_color = i_color;
1184 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1192 font_stack_t *p_last;
1194 for( p_last = *p_font;
1196 p_last = p_last->p_next )
1199 p_last->p_next = p_new;
1204 static int PopFont( font_stack_t **p_font )
1206 font_stack_t *p_last, *p_next_to_last;
1208 if( !p_font || !*p_font )
1209 return VLC_EGENERIC;
1211 p_next_to_last = NULL;
1212 for( p_last = *p_font;
1214 p_last = p_last->p_next )
1216 p_next_to_last = p_last;
1219 if( p_next_to_last )
1220 p_next_to_last->p_next = NULL;
1224 free( p_last->psz_name );
1230 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1231 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1233 font_stack_t *p_last;
1235 if( !p_font || !*p_font )
1236 return VLC_EGENERIC;
1238 for( p_last=*p_font;
1240 p_last=p_last->p_next )
1243 *psz_name = p_last->psz_name;
1244 *i_size = p_last->i_size;
1245 *i_color = p_last->i_color;
1246 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1251 static const struct {
1252 const char *psz_name;
1254 } p_html_colors[] = {
1255 /* Official html colors */
1256 { "Aqua", 0x00FFFF },
1257 { "Black", 0x000000 },
1258 { "Blue", 0x0000FF },
1259 { "Fuchsia", 0xFF00FF },
1260 { "Gray", 0x808080 },
1261 { "Green", 0x008000 },
1262 { "Lime", 0x00FF00 },
1263 { "Maroon", 0x800000 },
1264 { "Navy", 0x000080 },
1265 { "Olive", 0x808000 },
1266 { "Purple", 0x800080 },
1267 { "Red", 0xFF0000 },
1268 { "Silver", 0xC0C0C0 },
1269 { "Teal", 0x008080 },
1270 { "White", 0xFFFFFF },
1271 { "Yellow", 0xFFFF00 },
1274 { "AliceBlue", 0xF0F8FF },
1275 { "AntiqueWhite", 0xFAEBD7 },
1276 { "Aqua", 0x00FFFF },
1277 { "Aquamarine", 0x7FFFD4 },
1278 { "Azure", 0xF0FFFF },
1279 { "Beige", 0xF5F5DC },
1280 { "Bisque", 0xFFE4C4 },
1281 { "Black", 0x000000 },
1282 { "BlanchedAlmond", 0xFFEBCD },
1283 { "Blue", 0x0000FF },
1284 { "BlueViolet", 0x8A2BE2 },
1285 { "Brown", 0xA52A2A },
1286 { "BurlyWood", 0xDEB887 },
1287 { "CadetBlue", 0x5F9EA0 },
1288 { "Chartreuse", 0x7FFF00 },
1289 { "Chocolate", 0xD2691E },
1290 { "Coral", 0xFF7F50 },
1291 { "CornflowerBlue", 0x6495ED },
1292 { "Cornsilk", 0xFFF8DC },
1293 { "Crimson", 0xDC143C },
1294 { "Cyan", 0x00FFFF },
1295 { "DarkBlue", 0x00008B },
1296 { "DarkCyan", 0x008B8B },
1297 { "DarkGoldenRod", 0xB8860B },
1298 { "DarkGray", 0xA9A9A9 },
1299 { "DarkGrey", 0xA9A9A9 },
1300 { "DarkGreen", 0x006400 },
1301 { "DarkKhaki", 0xBDB76B },
1302 { "DarkMagenta", 0x8B008B },
1303 { "DarkOliveGreen", 0x556B2F },
1304 { "Darkorange", 0xFF8C00 },
1305 { "DarkOrchid", 0x9932CC },
1306 { "DarkRed", 0x8B0000 },
1307 { "DarkSalmon", 0xE9967A },
1308 { "DarkSeaGreen", 0x8FBC8F },
1309 { "DarkSlateBlue", 0x483D8B },
1310 { "DarkSlateGray", 0x2F4F4F },
1311 { "DarkSlateGrey", 0x2F4F4F },
1312 { "DarkTurquoise", 0x00CED1 },
1313 { "DarkViolet", 0x9400D3 },
1314 { "DeepPink", 0xFF1493 },
1315 { "DeepSkyBlue", 0x00BFFF },
1316 { "DimGray", 0x696969 },
1317 { "DimGrey", 0x696969 },
1318 { "DodgerBlue", 0x1E90FF },
1319 { "FireBrick", 0xB22222 },
1320 { "FloralWhite", 0xFFFAF0 },
1321 { "ForestGreen", 0x228B22 },
1322 { "Fuchsia", 0xFF00FF },
1323 { "Gainsboro", 0xDCDCDC },
1324 { "GhostWhite", 0xF8F8FF },
1325 { "Gold", 0xFFD700 },
1326 { "GoldenRod", 0xDAA520 },
1327 { "Gray", 0x808080 },
1328 { "Grey", 0x808080 },
1329 { "Green", 0x008000 },
1330 { "GreenYellow", 0xADFF2F },
1331 { "HoneyDew", 0xF0FFF0 },
1332 { "HotPink", 0xFF69B4 },
1333 { "IndianRed", 0xCD5C5C },
1334 { "Indigo", 0x4B0082 },
1335 { "Ivory", 0xFFFFF0 },
1336 { "Khaki", 0xF0E68C },
1337 { "Lavender", 0xE6E6FA },
1338 { "LavenderBlush", 0xFFF0F5 },
1339 { "LawnGreen", 0x7CFC00 },
1340 { "LemonChiffon", 0xFFFACD },
1341 { "LightBlue", 0xADD8E6 },
1342 { "LightCoral", 0xF08080 },
1343 { "LightCyan", 0xE0FFFF },
1344 { "LightGoldenRodYellow", 0xFAFAD2 },
1345 { "LightGray", 0xD3D3D3 },
1346 { "LightGrey", 0xD3D3D3 },
1347 { "LightGreen", 0x90EE90 },
1348 { "LightPink", 0xFFB6C1 },
1349 { "LightSalmon", 0xFFA07A },
1350 { "LightSeaGreen", 0x20B2AA },
1351 { "LightSkyBlue", 0x87CEFA },
1352 { "LightSlateGray", 0x778899 },
1353 { "LightSlateGrey", 0x778899 },
1354 { "LightSteelBlue", 0xB0C4DE },
1355 { "LightYellow", 0xFFFFE0 },
1356 { "Lime", 0x00FF00 },
1357 { "LimeGreen", 0x32CD32 },
1358 { "Linen", 0xFAF0E6 },
1359 { "Magenta", 0xFF00FF },
1360 { "Maroon", 0x800000 },
1361 { "MediumAquaMarine", 0x66CDAA },
1362 { "MediumBlue", 0x0000CD },
1363 { "MediumOrchid", 0xBA55D3 },
1364 { "MediumPurple", 0x9370D8 },
1365 { "MediumSeaGreen", 0x3CB371 },
1366 { "MediumSlateBlue", 0x7B68EE },
1367 { "MediumSpringGreen", 0x00FA9A },
1368 { "MediumTurquoise", 0x48D1CC },
1369 { "MediumVioletRed", 0xC71585 },
1370 { "MidnightBlue", 0x191970 },
1371 { "MintCream", 0xF5FFFA },
1372 { "MistyRose", 0xFFE4E1 },
1373 { "Moccasin", 0xFFE4B5 },
1374 { "NavajoWhite", 0xFFDEAD },
1375 { "Navy", 0x000080 },
1376 { "OldLace", 0xFDF5E6 },
1377 { "Olive", 0x808000 },
1378 { "OliveDrab", 0x6B8E23 },
1379 { "Orange", 0xFFA500 },
1380 { "OrangeRed", 0xFF4500 },
1381 { "Orchid", 0xDA70D6 },
1382 { "PaleGoldenRod", 0xEEE8AA },
1383 { "PaleGreen", 0x98FB98 },
1384 { "PaleTurquoise", 0xAFEEEE },
1385 { "PaleVioletRed", 0xD87093 },
1386 { "PapayaWhip", 0xFFEFD5 },
1387 { "PeachPuff", 0xFFDAB9 },
1388 { "Peru", 0xCD853F },
1389 { "Pink", 0xFFC0CB },
1390 { "Plum", 0xDDA0DD },
1391 { "PowderBlue", 0xB0E0E6 },
1392 { "Purple", 0x800080 },
1393 { "Red", 0xFF0000 },
1394 { "RosyBrown", 0xBC8F8F },
1395 { "RoyalBlue", 0x4169E1 },
1396 { "SaddleBrown", 0x8B4513 },
1397 { "Salmon", 0xFA8072 },
1398 { "SandyBrown", 0xF4A460 },
1399 { "SeaGreen", 0x2E8B57 },
1400 { "SeaShell", 0xFFF5EE },
1401 { "Sienna", 0xA0522D },
1402 { "Silver", 0xC0C0C0 },
1403 { "SkyBlue", 0x87CEEB },
1404 { "SlateBlue", 0x6A5ACD },
1405 { "SlateGray", 0x708090 },
1406 { "SlateGrey", 0x708090 },
1407 { "Snow", 0xFFFAFA },
1408 { "SpringGreen", 0x00FF7F },
1409 { "SteelBlue", 0x4682B4 },
1410 { "Tan", 0xD2B48C },
1411 { "Teal", 0x008080 },
1412 { "Thistle", 0xD8BFD8 },
1413 { "Tomato", 0xFF6347 },
1414 { "Turquoise", 0x40E0D0 },
1415 { "Violet", 0xEE82EE },
1416 { "Wheat", 0xF5DEB3 },
1417 { "White", 0xFFFFFF },
1418 { "WhiteSmoke", 0xF5F5F5 },
1419 { "Yellow", 0xFFFF00 },
1420 { "YellowGreen", 0x9ACD32 },
1425 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1426 font_stack_t **p_fonts )
1429 char *psz_fontname = NULL;
1430 uint32_t i_font_color = 0xffffff;
1431 int i_font_alpha = 255;
1432 uint32_t i_karaoke_bg_color = 0x00ffffff;
1433 int i_font_size = 24;
1435 /* Default all attributes to the top font in the stack -- in case not
1436 * all attributes are specified in the sub-font
1438 if( VLC_SUCCESS == PeekFont( p_fonts,
1442 &i_karaoke_bg_color ))
1444 psz_fontname = strdup( psz_fontname );
1446 i_font_alpha = (i_font_color >> 24) & 0xff;
1447 i_font_color &= 0x00ffffff;
1449 const char *name, *value;
1450 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1452 if( !strcasecmp( "face", name ) )
1454 free( psz_fontname );
1455 psz_fontname = strdup( value );
1457 else if( !strcasecmp( "size", name ) )
1459 if( ( *value == '+' ) || ( *value == '-' ) )
1461 int i_value = atoi( value );
1463 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1464 i_font_size += ( i_value * i_font_size ) / 10;
1465 else if( i_value < -5 )
1466 i_font_size = - i_value;
1467 else if( i_value > 5 )
1468 i_font_size = i_value;
1471 i_font_size = atoi( value );
1473 else if( !strcasecmp( "color", name ) )
1475 if( value[0] == '#' )
1477 i_font_color = strtol( value + 1, NULL, 16 );
1478 i_font_color &= 0x00ffffff;
1483 uint32_t i_value = strtol( value, &end, 16 );
1484 if( *end == '\0' || *end == ' ' )
1485 i_font_color = i_value & 0x00ffffff;
1487 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1489 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1491 i_font_color = p_html_colors[i].i_value;
1497 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1499 i_font_alpha = strtol( value + 1, NULL, 16 );
1500 i_font_alpha &= 0xff;
1503 rv = PushFont( p_fonts,
1506 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1507 i_karaoke_bg_color );
1509 free( psz_fontname );
1514 /* Turn any multiple-whitespaces into single spaces */
1515 static void HandleWhiteSpace( char *psz_node )
1517 char *s = strpbrk( psz_node, "\t\r\n " );
1520 int i_whitespace = strspn( s, "\t\r\n " );
1522 if( i_whitespace > 1 )
1525 strlen( s ) - i_whitespace + 1 );
1528 s = strpbrk( s, "\t\r\n " );
1533 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1534 font_stack_t **p_fonts,
1537 char *psz_fontname = NULL;
1538 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1539 uint32_t i_karaoke_bg_color = i_font_color;
1540 int i_font_size = p_sys->i_font_size;
1542 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1543 &i_font_color, &i_karaoke_bg_color ) )
1546 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1551 static unsigned SetupText( filter_t *p_filter,
1552 uni_char_t *psz_text_out,
1553 text_style_t **pp_styles,
1554 uint32_t *pi_k_dates,
1556 const char *psz_text_in,
1557 text_style_t *p_style,
1560 size_t i_string_length;
1562 size_t i_string_bytes;
1563 uni_char_t *psz_tmp = ToCharset( FREETYPE_TO_UCS, psz_text_in, &i_string_bytes );
1566 memcpy( psz_text_out, psz_tmp, i_string_bytes );
1567 i_string_length = i_string_bytes / sizeof( *psz_tmp );
1572 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1573 i_string_length = 0;
1576 if( i_string_length > 0 )
1578 for( unsigned i = 0; i < i_string_length; i++ )
1579 pp_styles[i] = p_style;
1583 text_style_Delete( p_style );
1585 if( i_string_length > 0 && pi_k_dates )
1587 for( unsigned i = 0; i < i_string_length; i++ )
1588 pi_k_dates[i] = i_k_date;
1590 return i_string_length;
1593 static int ProcessNodes( filter_t *p_filter,
1594 uni_char_t *psz_text,
1595 text_style_t **pp_styles,
1596 uint32_t *pi_k_dates,
1598 xml_reader_t *p_xml_reader,
1599 text_style_t *p_font_style )
1601 int rv = VLC_SUCCESS;
1602 filter_sys_t *p_sys = p_filter->p_sys;
1603 int i_text_length = 0;
1604 font_stack_t *p_fonts = NULL;
1605 uint32_t i_k_date = 0;
1607 int i_style_flags = 0;
1611 rv = PushFont( &p_fonts,
1612 p_font_style->psz_fontname,
1613 p_font_style->i_font_size > 0 ? p_font_style->i_font_size
1614 : p_sys->i_font_size,
1615 (p_font_style->i_font_color & 0xffffff) |
1616 ((p_font_style->i_font_alpha & 0xff) << 24),
1617 (p_font_style->i_karaoke_background_color & 0xffffff) |
1618 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1620 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1627 rv = PushFont( &p_fonts,
1628 p_sys->psz_fontfamily,
1630 (p_sys->i_font_color & 0xffffff) |
1631 ((p_sys->i_font_opacity & 0xff) << 24),
1634 if( p_sys->b_font_bold )
1635 i_style_flags |= STYLE_BOLD;
1637 if( rv != VLC_SUCCESS )
1643 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1647 case XML_READER_ENDELEM:
1648 if( !strcasecmp( "font", node ) )
1649 PopFont( &p_fonts );
1650 else if( !strcasecmp( "b", node ) )
1651 i_style_flags &= ~STYLE_BOLD;
1652 else if( !strcasecmp( "i", node ) )
1653 i_style_flags &= ~STYLE_ITALIC;
1654 else if( !strcasecmp( "u", node ) )
1655 i_style_flags &= ~STYLE_UNDERLINE;
1656 else if( !strcasecmp( "s", node ) )
1657 i_style_flags &= ~STYLE_STRIKEOUT;
1660 case XML_READER_STARTELEM:
1661 if( !strcasecmp( "font", node ) )
1662 HandleFontAttributes( p_xml_reader, &p_fonts );
1663 else if( !strcasecmp( "b", node ) )
1664 i_style_flags |= STYLE_BOLD;
1665 else if( !strcasecmp( "i", node ) )
1666 i_style_flags |= STYLE_ITALIC;
1667 else if( !strcasecmp( "u", node ) )
1668 i_style_flags |= STYLE_UNDERLINE;
1669 else if( !strcasecmp( "s", node ) )
1670 i_style_flags |= STYLE_STRIKEOUT;
1671 else if( !strcasecmp( "br", node ) )
1673 i_text_length += SetupText( p_filter,
1674 &psz_text[i_text_length],
1675 &pp_styles[i_text_length],
1676 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1678 GetStyleFromFontStack( p_sys,
1683 else if( !strcasecmp( "k", node ) )
1686 const char *name, *value;
1687 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1689 if( !strcasecmp( "t", name ) && value )
1690 i_k_date += atoi( value );
1695 case XML_READER_TEXT:
1697 char *psz_node = strdup( node );
1698 if( unlikely(!psz_node) )
1701 HandleWhiteSpace( psz_node );
1702 resolve_xml_special_chars( psz_node );
1704 i_text_length += SetupText( p_filter,
1705 &psz_text[i_text_length],
1706 &pp_styles[i_text_length],
1707 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1709 GetStyleFromFontStack( p_sys,
1719 *pi_len = i_text_length;
1721 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1726 static void FreeLine( line_desc_t *p_line )
1728 for( int i = 0; i < p_line->i_character_count; i++ )
1730 line_character_t *ch = &p_line->p_character[i];
1731 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1733 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1735 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1738 free( p_line->p_character );
1742 static void FreeLines( line_desc_t *p_lines )
1744 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1746 line_desc_t *p_next = p_line->p_next;
1752 static line_desc_t *NewLine( int i_count )
1754 line_desc_t *p_line = malloc( sizeof(*p_line) );
1759 p_line->p_next = NULL;
1760 p_line->i_width = 0;
1761 p_line->i_base_line = 0;
1762 p_line->i_character_count = 0;
1764 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1765 if( !p_line->p_character )
1773 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1775 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1777 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1779 FT_Face p_face = NULL;
1781 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1789 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1790 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1791 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1792 (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC)) == i_style_received )
1795 FT_Done_Face( p_face );
1803 static FT_Face LoadFace( filter_t *p_filter,
1804 const text_style_t *p_style )
1806 filter_sys_t *p_sys = p_filter->p_sys;
1808 /* Look for a match amongst our attachments first */
1809 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1811 /* Load system wide font otheriwse */
1815 char *psz_fontfile = NULL;
1816 #ifdef HAVE_FONTCONFIG
1817 psz_fontfile = FontConfig_Select( NULL,
1818 p_style->psz_fontname,
1819 (p_style->i_style_flags & STYLE_BOLD) != 0,
1820 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1823 #elif defined( __APPLE__ )
1824 psz_fontfile = MacLegacy_Select( p_filter, p_style->psz_fontname, false, false, -1, &i_idx );
1825 #elif defined( WIN32 )
1826 psz_fontfile = Win32_Select( p_filter,
1827 p_style->psz_fontname,
1828 (p_style->i_style_flags & STYLE_BOLD) != 0,
1829 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1833 psz_fontfile = NULL;
1838 if( *psz_fontfile == '\0' )
1841 "We were not able to find a matching font: \"%s\" (%s %s),"
1842 " so using default font",
1843 p_style->psz_fontname,
1844 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1845 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1850 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1853 free( psz_fontfile );
1858 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1860 /* We've loaded a font face which is unhelpful for actually
1861 * rendering text - fallback to the default one.
1863 FT_Done_Face( p_face );
1869 static bool FaceStyleEquals( const text_style_t *p_style1,
1870 const text_style_t *p_style2 )
1872 if( !p_style1 || !p_style2 )
1874 if( p_style1 == p_style2 )
1877 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
1878 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
1879 !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
1882 static int GetGlyph( filter_t *p_filter,
1883 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1884 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1885 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1891 FT_Vector *p_pen_shadow )
1893 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1894 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1896 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1897 return VLC_EGENERIC;
1900 /* Do synthetic styling now that Freetype supports it;
1901 * ie. if the font we have loaded is NOT already in the
1902 * style that the tags want, then switch it on; if they
1903 * are then don't. */
1904 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1905 FT_GlyphSlot_Embolden( p_face->glyph );
1906 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1907 FT_GlyphSlot_Oblique( p_face->glyph );
1910 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1912 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1913 return VLC_EGENERIC;
1916 FT_Glyph outline = NULL;
1917 if( p_filter->p_sys->p_stroker )
1920 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1924 FT_Glyph shadow = NULL;
1925 if( p_filter->p_sys->i_shadow_opacity > 0 )
1927 shadow = outline ? outline : glyph;
1928 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1934 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1937 *pp_shadow = shadow;
1939 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1941 FT_Done_Glyph( glyph );
1943 FT_Done_Glyph( outline );
1945 FT_Done_Glyph( shadow );
1946 return VLC_EGENERIC;
1948 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1953 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1954 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1956 *pp_outline = outline;
1961 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1963 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1964 if( p_bbox->xMin >= p_bbox->xMax )
1966 p_bbox->xMin = FT_CEIL(p_pen->x);
1967 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1968 glyph_bmp->left = p_bbox->xMin;
1970 if( p_bbox->yMin >= p_bbox->yMax )
1972 p_bbox->yMax = FT_CEIL(p_pen->y);
1973 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1974 glyph_bmp->top = p_bbox->yMax;
1978 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1980 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1981 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1982 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1983 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1986 static int ProcessLines( filter_t *p_filter,
1987 line_desc_t **pp_lines,
1989 int *pi_max_face_height,
1991 uni_char_t *psz_text,
1992 text_style_t **pp_styles,
1993 uint32_t *pi_k_dates,
1996 filter_sys_t *p_sys = p_filter->p_sys;
1997 uni_char_t *p_fribidi_string = NULL;
1998 text_style_t **pp_fribidi_styles = NULL;
1999 int *p_new_positions = NULL;
2001 #if defined(HAVE_FRIBIDI)
2003 int *p_old_positions;
2004 int start_pos, pos = 0;
2006 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
2008 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
2009 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
2010 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
2012 if( ! pp_fribidi_styles ||
2013 ! p_fribidi_string ||
2014 ! p_old_positions ||
2017 free( p_old_positions );
2018 free( p_new_positions );
2019 free( p_fribidi_string );
2020 free( pp_fribidi_styles );
2024 /* Do bidi conversion line-by-line */
2027 while(pos < i_len) {
2028 if (psz_text[pos] != '\n')
2030 p_fribidi_string[pos] = psz_text[pos];
2031 pp_fribidi_styles[pos] = pp_styles[pos];
2032 p_new_positions[pos] = pos;
2036 while(pos < i_len) {
2037 if (psz_text[pos] == '\n')
2041 if (pos > start_pos)
2043 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
2044 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
2046 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
2048 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
2049 pos - start_pos, &base_dir,
2050 (FriBidiChar*)p_fribidi_string + start_pos,
2051 p_new_positions + start_pos,
2054 for( int j = start_pos; j < pos; j++ )
2056 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
2057 p_new_positions[ j ] += start_pos;
2061 p_fribidi_string[ i_len ] = 0;
2062 free( p_old_positions );
2064 pp_styles = pp_fribidi_styles;
2065 psz_text = p_fribidi_string;
2068 /* Work out the karaoke */
2069 uint8_t *pi_karaoke_bar = NULL;
2072 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
2073 if( pi_karaoke_bar )
2075 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
2076 for( int i = 0; i < i_len; i++ )
2078 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
2079 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
2083 free( p_new_positions );
2085 *pi_max_face_height = 0;
2087 line_desc_t **pp_line_next = pp_lines;
2095 int i_face_height_previous = 0;
2096 int i_base_line = 0;
2097 const text_style_t *p_previous_style = NULL;
2098 FT_Face p_face = NULL;
2099 for( int i_start = 0; i_start < i_len; )
2101 /* Compute the length of the current text line */
2103 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
2106 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
2107 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
2108 int i_index = i_start;
2113 int i_face_height = 0;
2114 FT_BBox line_bbox = {
2120 int i_ul_offset = 0;
2121 int i_ul_thickness = 0;
2130 break_point_t break_point;
2131 break_point_t break_point_fallback;
2133 #define SAVE_BP(dst) do { \
2134 dst.i_index = i_index; \
2136 dst.line_bbox = line_bbox; \
2137 dst.i_face_height = i_face_height; \
2138 dst.i_ul_offset = i_ul_offset; \
2139 dst.i_ul_thickness = i_ul_thickness; \
2142 SAVE_BP( break_point );
2143 SAVE_BP( break_point_fallback );
2145 while( i_index < i_start + i_length )
2147 /* Split by common FT_Face + Size */
2148 const text_style_t *p_current_style = pp_styles[i_index];
2149 int i_part_length = 0;
2150 while( i_index + i_part_length < i_start + i_length )
2152 const text_style_t *p_style = pp_styles[i_index + i_part_length];
2153 if( !FaceStyleEquals( p_style, p_current_style ) ||
2154 p_style->i_font_size != p_current_style->i_font_size )
2159 /* (Re)load/reconfigure the face if needed */
2160 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
2163 FT_Done_Face( p_face );
2164 p_previous_style = NULL;
2166 p_face = LoadFace( p_filter, p_current_style );
2168 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
2169 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
2171 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
2172 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
2173 if( p_sys->p_stroker )
2175 int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
2176 FT_Stroker_Set( p_sys->p_stroker,
2178 FT_STROKER_LINECAP_ROUND,
2179 FT_STROKER_LINEJOIN_ROUND, 0 );
2182 p_previous_style = p_current_style;
2184 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
2185 p_current_face->size->metrics.y_scale)));
2187 /* Render the part */
2188 bool b_break_line = false;
2189 int i_glyph_last = 0;
2190 while( i_part_length > 0 )
2192 const text_style_t *p_glyph_style = pp_styles[i_index];
2193 uni_char_t character = psz_text[i_index];
2194 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
2196 /* Get kerning vector */
2197 FT_Vector kerning = { .x = 0, .y = 0 };
2198 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
2199 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
2201 /* Get the glyph bitmap and its bounding box and all the associated properties */
2202 FT_Vector pen_new = {
2203 .x = pen.x + kerning.x,
2204 .y = pen.y + kerning.y,
2206 FT_Vector pen_shadow_new = {
2207 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
2208 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
2213 FT_BBox outline_bbox;
2215 FT_BBox shadow_bbox;
2217 if( GetGlyph( p_filter,
2218 &glyph, &glyph_bbox,
2219 &outline, &outline_bbox,
2220 &shadow, &shadow_bbox,
2221 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
2222 &pen_new, &pen_shadow_new ) )
2225 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
2227 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
2229 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
2231 /* FIXME and what about outline */
2233 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
2234 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
2235 (p_glyph_style->i_karaoke_background_alpha << 24))
2236 : (p_glyph_style->i_font_color |
2237 (p_glyph_style->i_font_alpha << 24));
2238 int i_line_offset = 0;
2239 int i_line_thickness = 0;
2240 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
2242 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
2243 p_current_face->size->metrics.y_scale)) );
2245 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
2246 p_current_face->size->metrics.y_scale)) );
2248 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
2250 /* Move the baseline to make it strikethrough instead of
2251 * underline. That means that strikethrough takes precedence
2253 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
2254 p_current_face->size->metrics.y_scale)) );
2256 else if( i_line_thickness > 0 )
2258 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
2260 /* The real underline thickness and position are
2261 * updated once the whole line has been parsed */
2262 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
2263 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
2264 i_line_thickness = -1;
2267 FT_BBox line_bbox_new = line_bbox;
2268 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
2270 BBoxEnlarge( &line_bbox_new, &outline_bbox );
2272 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
2274 b_break_line = i_index > i_start &&
2275 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
2278 FT_Done_Glyph( glyph );
2280 FT_Done_Glyph( outline );
2282 FT_Done_Glyph( shadow );
2284 break_point_t *p_bp = NULL;
2285 if( break_point.i_index > i_start )
2286 p_bp = &break_point;
2287 else if( break_point_fallback.i_index > i_start )
2288 p_bp = &break_point_fallback;
2292 msg_Dbg( p_filter, "Breaking line");
2293 for( int i = p_bp->i_index; i < i_index; i++ )
2295 line_character_t *ch = &p_line->p_character[i - i_start];
2296 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
2298 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
2300 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
2302 p_line->i_character_count = p_bp->i_index - i_start;
2304 i_index = p_bp->i_index;
2306 line_bbox = p_bp->line_bbox;
2307 i_face_height = p_bp->i_face_height;
2308 i_ul_offset = p_bp->i_ul_offset;
2309 i_ul_thickness = p_bp->i_ul_thickness;
2313 msg_Err( p_filter, "Breaking unbreakable line");
2318 assert( p_line->i_character_count == i_index - i_start);
2319 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2320 .p_glyph = (FT_BitmapGlyph)glyph,
2321 .p_outline = (FT_BitmapGlyph)outline,
2322 .p_shadow = (FT_BitmapGlyph)shadow,
2324 .i_line_offset = i_line_offset,
2325 .i_line_thickness = i_line_thickness,
2328 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2329 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2330 line_bbox = line_bbox_new;
2332 i_glyph_last = i_glyph_index;
2336 if( character == ' ' || character == '\t' )
2337 SAVE_BP( break_point );
2338 else if( character == 160 )
2339 SAVE_BP( break_point_fallback );
2345 /* Update our baseline */
2346 if( i_face_height_previous > 0 )
2347 i_base_line += __MAX(i_face_height, i_face_height_previous);
2348 if( i_face_height > 0 )
2349 i_face_height_previous = i_face_height;
2351 /* Update the line bbox with the actual base line */
2352 if (line_bbox.yMax > line_bbox.yMin) {
2353 line_bbox.yMin -= i_base_line;
2354 line_bbox.yMax -= i_base_line;
2356 BBoxEnlarge( &bbox, &line_bbox );
2358 /* Terminate and append the line */
2361 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2362 p_line->i_base_line = i_base_line;
2363 if( i_ul_thickness > 0 )
2365 for( int i = 0; i < p_line->i_character_count; i++ )
2367 line_character_t *ch = &p_line->p_character[i];
2368 if( ch->i_line_thickness < 0 )
2370 ch->i_line_offset = i_ul_offset;
2371 ch->i_line_thickness = i_ul_thickness;
2376 *pp_line_next = p_line;
2377 pp_line_next = &p_line->p_next;
2380 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2382 /* Skip what we have rendered and the line delimitor if present */
2384 if( i_start < i_len && psz_text[i_start] == '\n' )
2387 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
2389 msg_Err( p_filter, "Truncated too high subtitle" );
2394 FT_Done_Face( p_face );
2396 free( pp_fribidi_styles );
2397 free( p_fribidi_string );
2398 free( pi_karaoke_bar );
2405 * This function renders a text subpicture region into another one.
2406 * It also calculates the size needed for this string, and renders the
2407 * needed glyphs into memory. It is used as pf_add_string callback in
2408 * the vout method by this module
2410 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2411 subpicture_region_t *p_region_in, bool b_html,
2412 const vlc_fourcc_t *p_chroma_list )
2414 filter_sys_t *p_sys = p_filter->p_sys;
2417 return VLC_EGENERIC;
2418 if( b_html && !p_region_in->psz_html )
2419 return VLC_EGENERIC;
2420 if( !b_html && !p_region_in->psz_text )
2421 return VLC_EGENERIC;
2423 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2424 : p_region_in->psz_text );
2426 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2427 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2428 if( !psz_text || !pp_styles )
2432 return VLC_EGENERIC;
2435 /* Reset the default fontsize in case screen metrics have changed */
2436 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2439 int rv = VLC_SUCCESS;
2440 int i_text_length = 0;
2442 int i_max_face_height;
2443 line_desc_t *p_lines = NULL;
2445 uint32_t *pi_k_durations = NULL;
2449 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2450 (uint8_t *) p_region_in->psz_html,
2451 strlen( p_region_in->psz_html ),
2453 if( unlikely(p_sub == NULL) )
2456 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2458 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2460 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2461 p_filter->p_sys->p_xml = p_xml_reader;
2468 /* Look for Root Node */
2471 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2473 if( strcasecmp( "karaoke", node ) == 0 )
2475 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2477 else if( strcasecmp( "text", node ) != 0 )
2479 /* Only text and karaoke tags are supported */
2480 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2487 msg_Err( p_filter, "Malformed HTML subtitle" );
2493 rv = ProcessNodes( p_filter,
2494 psz_text, pp_styles, pi_k_durations, &i_text_length,
2495 p_xml_reader, p_region_in->p_style );
2499 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2501 stream_Delete( p_sub );
2505 text_style_t *p_style;
2506 if( p_region_in->p_style )
2507 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2508 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2509 : p_sys->i_font_size,
2510 (p_region_in->p_style->i_font_color & 0xffffff) |
2511 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2513 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2518 p_style = CreateStyle( p_sys->psz_fontfamily,
2520 (p_sys->i_font_color & 0xffffff) |
2521 ((p_sys->i_font_opacity & 0xff) << 24),
2523 if( p_sys->b_font_bold )
2524 p_style->i_style_flags |= STYLE_BOLD;
2526 i_text_length = SetupText( p_filter,
2530 p_region_in->psz_text, p_style, 0 );
2533 if( !rv && i_text_length > 0 )
2535 rv = ProcessLines( p_filter,
2536 &p_lines, &bbox, &i_max_face_height,
2537 psz_text, pp_styles, pi_k_durations, i_text_length );
2540 p_region_out->i_x = p_region_in->i_x;
2541 p_region_out->i_y = p_region_in->i_y;
2543 /* Don't attempt to render text that couldn't be layed out
2545 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2547 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2548 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2550 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2551 p_chroma_list = p_chroma_list_yuvp;
2552 else if( !p_chroma_list || *p_chroma_list == 0 )
2553 p_chroma_list = p_chroma_list_rgba;
2555 const int i_margin = p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2556 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2559 if( *p_chroma == VLC_CODEC_YUVP )
2560 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2561 else if( *p_chroma == VLC_CODEC_YUVA )
2562 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2567 else if( *p_chroma == VLC_CODEC_RGBA )
2568 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2577 /* With karaoke, we're going to have to render the text a number
2578 * of times to show the progress marker on the text.
2580 if( pi_k_durations )
2581 var_SetBool( p_filter, "text-rerender", true );
2584 FreeLines( p_lines );
2587 for( int i = 0; i < i_text_length; i++ )
2589 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2590 text_style_Delete( pp_styles[i] );
2593 free( pi_k_durations );
2598 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2599 subpicture_region_t *p_region_in,
2600 const vlc_fourcc_t *p_chroma_list )
2602 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2605 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2606 subpicture_region_t *p_region_in,
2607 const vlc_fourcc_t *p_chroma_list )
2609 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2612 /*****************************************************************************
2613 * Create: allocates osd-text video thread output method
2614 *****************************************************************************
2615 * This function allocates and initializes a Clone vout method.
2616 *****************************************************************************/
2617 static int Create( vlc_object_t *p_this )
2619 filter_t *p_filter = (filter_t *)p_this;
2620 filter_sys_t *p_sys;
2621 char *psz_fontfile = NULL;
2622 char *psz_fontfamily = NULL;
2623 int i_error = 0, fontindex = 0;
2625 /* Allocate structure */
2626 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2630 p_sys->psz_fontfamily = NULL;
2631 p_sys->p_xml = NULL;
2633 p_sys->p_library = 0;
2634 p_sys->i_font_size = 0;
2635 p_sys->i_display_height = 0;
2637 var_Create( p_filter, "freetype-rel-fontsize",
2638 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2640 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2641 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2642 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2643 p_sys->i_font_opacity = VLC_CLIP( p_sys->i_font_opacity, 0, 255 );
2644 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2645 p_sys->i_font_color = VLC_CLIP( p_sys->i_font_color, 0, 0xFFFFFF );
2646 p_sys->b_font_bold = var_InheritBool( p_filter, "freetype-bold" );
2648 p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
2649 p_sys->i_background_opacity = VLC_CLIP( p_sys->i_background_opacity, 0, 255 );
2650 p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
2651 p_sys->i_background_color = VLC_CLIP( p_sys->i_background_color, 0, 0xFFFFFF );
2653 p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2654 p_sys->f_outline_thickness = VLC_CLIP( p_sys->f_outline_thickness, 0.0, 0.5 );
2655 p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2656 p_sys->i_outline_opacity = VLC_CLIP( p_sys->i_outline_opacity, 0, 255 );
2657 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2658 p_sys->i_outline_color = VLC_CLIP( p_sys->i_outline_color, 0, 0xFFFFFF );
2660 p_sys->i_shadow_opacity = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2661 p_sys->i_shadow_opacity = VLC_CLIP( p_sys->i_shadow_opacity, 0, 255 );
2662 p_sys->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2663 p_sys->i_shadow_color = VLC_CLIP( p_sys->i_shadow_color, 0, 0xFFFFFF );
2664 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2665 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2666 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
2667 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2668 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2671 /* Get Windows Font folder */
2672 wchar_t wdir[MAX_PATH];
2673 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2675 GetWindowsDirectoryW( wdir, MAX_PATH );
2676 wcscat( wdir, L"\\fonts" );
2678 p_sys->psz_win_fonts_path = FromWide( wdir );
2681 /* Set default psz_fontfamily */
2682 if( !psz_fontfamily || !*psz_fontfamily )
2684 free( psz_fontfamily );
2686 psz_fontfamily = strdup( DEFAULT_FAMILY );
2689 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2692 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2694 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2698 /* Set the current font file */
2699 p_sys->psz_fontfamily = psz_fontfamily;
2701 #ifdef HAVE_FONTCONFIG
2702 FontConfig_BuildCache( p_filter );
2705 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2706 p_sys->i_default_font_size, &fontindex );
2707 #elif defined(__APPLE__)
2708 psz_fontfile = MacLegacy_Select( p_filter, psz_fontfamily, false, false, 0, &fontindex );
2709 #elif defined(WIN32)
2710 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2711 p_sys->i_default_font_size, &fontindex );
2714 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2716 /* If nothing is found, use the default family */
2718 psz_fontfile = strdup( psz_fontfamily );
2720 #else /* !HAVE_STYLES */
2721 /* Use the default file */
2722 psz_fontfile = psz_fontfamily;
2726 i_error = FT_Init_FreeType( &p_sys->p_library );
2729 msg_Err( p_filter, "couldn't initialize freetype" );
2733 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2734 fontindex, &p_sys->p_face );
2736 if( i_error == FT_Err_Unknown_File_Format )
2738 msg_Err( p_filter, "file %s have unknown format",
2739 psz_fontfile ? psz_fontfile : "(null)" );
2744 msg_Err( p_filter, "failed to load font file %s",
2745 psz_fontfile ? psz_fontfile : "(null)" );
2749 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2752 msg_Err( p_filter, "font has no unicode translation table" );
2756 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2758 p_sys->p_stroker = NULL;
2759 if( p_sys->f_outline_thickness > 0.001 )
2761 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2763 msg_Err( p_filter, "Failed to create stroker for outlining" );
2766 p_sys->pp_font_attachments = NULL;
2767 p_sys->i_font_attachments = 0;
2769 p_filter->pf_render_text = RenderText;
2770 p_filter->pf_render_html = RenderHtml;
2772 LoadFontsFromAttachments( p_filter );
2775 free( psz_fontfile );
2781 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2782 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2784 free( psz_fontfile );
2786 free( psz_fontfamily );
2788 return VLC_EGENERIC;
2791 /*****************************************************************************
2792 * Destroy: destroy Clone video thread output method
2793 *****************************************************************************
2794 * Clean up all data and library connections
2795 *****************************************************************************/
2796 static void Destroy( vlc_object_t *p_this )
2798 filter_t *p_filter = (filter_t *)p_this;
2799 filter_sys_t *p_sys = p_filter->p_sys;
2801 if( p_sys->pp_font_attachments )
2803 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2804 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2806 free( p_sys->pp_font_attachments );
2809 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2810 free( p_sys->psz_fontfamily );
2813 free( p_sys->psz_win_fonts_path );
2816 /* FcFini asserts calling the subfunction FcCacheFini()
2817 * even if no other library functions have been made since FcInit(),
2818 * so don't call it. */
2820 if( p_sys->p_stroker )
2821 FT_Stroker_Done( p_sys->p_stroker );
2822 FT_Done_Face( p_sys->p_face );
2823 FT_Done_FreeType( p_sys->p_library );