1 /*****************************************************************************
2 * freetype.c : Put text on the video, using freetype2
3 *****************************************************************************
4 * Copyright (C) 2002 - 2012 VLC authors and VideoLAN
7 * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8 * Gildas Bazin <gbazin@videolan.org>
9 * Bernie Purcell <bitmap@videolan.org>
10 * Jean-Baptiste Kempf <jb@videolan.org>
11 * Felix Paul Kühne <fkuehne@videolan.org>
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU Lesser General Public License as published by
15 * the Free Software Foundation; either version 2.1 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public License
24 * along with this program; if not, write to the Free Software Foundation, Inc.,
25 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 *****************************************************************************/
28 /*****************************************************************************
30 *****************************************************************************/
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
39 #include <vlc_stream.h> /* stream_MemoryNew */
40 #include <vlc_input.h> /* vlc_input_attachment_* */
41 #include <vlc_xml.h> /* xml_reader */
42 #include <vlc_strings.h> /* resolve_xml_special_chars */
43 #include <vlc_dialog.h> /* FcCache dialog */
44 #include <vlc_filter.h> /* filter_sys_t */
45 #include <vlc_text_style.h> /* text_style_t*/
49 # define SYSTEM_DEFAULT_FONT_FILE "/Library/Fonts/Arial Unicode.ttf"
50 # define SYSTEM_DEFAULT_FAMILY "Arial Unicode MS"
51 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/System/Library/Fonts/Monaco.dfont"
52 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monaco"
53 #elif defined( _WIN32 )
54 # define SYSTEM_DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
55 # define SYSTEM_DEFAULT_FAMILY "Arial"
56 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "cour.ttf"
57 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Courier New"
58 #elif defined( __OS2__ )
59 # define SYSTEM_DEFAULT_FONT_FILE "/psfonts/tnrwt_k.ttf"
60 # define SYSTEM_DEFAULT_FAMILY "Times New Roman WT K"
61 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/psfonts/mtsansdk.ttf"
62 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monotype Sans Duospace WT K"
63 #elif defined( __ANDROID__ )
64 # define SYSTEM_DEFAULT_FONT_FILE "/system/fonts/DroidSans-Bold.ttf"
65 # define SYSTEM_DEFAULT_FAMILY "Droid Sans Bold"
66 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/system/fonts/DroidSansMono.ttf"
67 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Droid Sans Mono"
69 # define SYSTEM_DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
70 # define SYSTEM_DEFAULT_FAMILY "Serif Bold"
71 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeMono.ttf"
72 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monospace"
75 #ifndef DEFAULT_FONT_FILE
76 #define DEFAULT_FONT_FILE SYSTEM_DEFAULT_FONT_FILE
79 #ifndef DEFAULT_FAMILY
80 #define DEFAULT_FAMILY SYSTEM_DEFAULT_FAMILY
83 #ifndef DEFAULT_MONOSPACE_FONT_FILE
84 #define DEFAULT_MONOSPACE_FONT_FILE SYSTEM_DEFAULT_MONOSPACE_FONT_FILE
87 #ifndef DEFAULT_MONOSPACE_FAMILY
88 #define DEFAULT_MONOSPACE_FAMILY SYSTEM_DEFAULT_MONOSPACE_FAMILY
92 #include <freetype/ftsynth.h>
93 #include FT_FREETYPE_H
97 #define FT_FLOOR(X) ((X & -64) >> 6)
98 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
100 #define FT_MulFix(v, s) (((v)*(s))>>16)
105 #include <TargetConditionals.h>
106 #if !TARGET_OS_IPHONE
107 #include <Carbon/Carbon.h>
109 #include <sys/param.h> /* for MAXPATHLEN */
110 #undef HAVE_FONTCONFIG
115 #if defined(HAVE_FRIBIDI)
116 # include <fribidi/fribidi.h>
121 # include <windows.h>
124 # undef HAVE_FONTCONFIG
125 # include <vlc_charset.h> /* FromT */
129 #ifdef HAVE_FONTCONFIG
130 # include <fontconfig/fontconfig.h>
136 #include "text_renderer.h"
138 /*****************************************************************************
140 *****************************************************************************/
141 static int Create ( vlc_object_t * );
142 static void Destroy( vlc_object_t * );
144 #define FONT_TEXT N_("Font")
145 #define MONOSPACE_FONT_TEXT N_("Monospace Font")
147 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
148 #define FONT_LONGTEXT N_("Font file for the font you want to use")
150 #define FONTSIZE_TEXT N_("Font size in pixels")
151 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
152 "that will be rendered on the video. " \
153 "If set to something different than 0 this option will override the " \
154 "relative font size." )
155 #define OPACITY_TEXT N_("Text opacity")
156 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
157 "text that will be rendered on the video. 0 = transparent, " \
158 "255 = totally opaque. " )
159 #define COLOR_TEXT N_("Text default color")
160 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
161 "the video. This must be an hexadecimal (like HTML colors). The first two "\
162 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
163 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
164 #define FONTSIZER_TEXT N_("Relative font size")
165 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
166 "fonts that will be rendered on the video. If absolute font size is set, "\
167 "relative size will be overridden." )
168 #define BOLD_TEXT N_("Force bold")
170 #define BG_OPACITY_TEXT N_("Background opacity")
171 #define BG_COLOR_TEXT N_("Background color")
173 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
174 #define OUTLINE_COLOR_TEXT N_("Outline color")
175 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
177 #define SHADOW_OPACITY_TEXT N_("Shadow opacity")
178 #define SHADOW_COLOR_TEXT N_("Shadow color")
179 #define SHADOW_ANGLE_TEXT N_("Shadow angle")
180 #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
183 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
184 static const char *const ppsz_sizes_text[] = {
185 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
186 #define YUVP_TEXT N_("Use YUVP renderer")
187 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
188 "This option is only needed if you want to encode into DVB subtitles" )
190 static const int pi_color_values[] = {
191 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
192 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
193 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
195 static const char *const ppsz_color_descriptions[] = {
196 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
197 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
198 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
200 static const int pi_outline_thickness[] = {
203 static const char *const ppsz_outline_thickness[] = {
204 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
208 set_shortname( N_("Text renderer"))
209 set_description( N_("Freetype2 font renderer") )
210 set_category( CAT_VIDEO )
211 set_subcategory( SUBCAT_VIDEO_SUBPIC )
214 add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
215 add_font( "freetype-monofont", DEFAULT_MONOSPACE_FAMILY, MONOSPACE_FONT_TEXT, FAMILY_LONGTEXT, false )
217 add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
218 add_loadfile( "freetype-monofont", DEFAULT_MONOSPACE_FONT_FILE, MONOSPACE_FONT_TEXT, FONT_LONGTEXT, false )
221 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
222 FONTSIZE_LONGTEXT, true )
223 change_integer_range( 0, 4096)
226 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
227 FONTSIZER_LONGTEXT, false )
228 change_integer_list( pi_sizes, ppsz_sizes_text )
231 /* opacity valid on 0..255, with default 255 = fully opaque */
232 add_integer_with_range( "freetype-opacity", 255, 0, 255,
233 OPACITY_TEXT, OPACITY_LONGTEXT, false )
236 /* hook to the color values list, with default 0x00ffffff = white */
237 add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
238 COLOR_LONGTEXT, false )
239 change_integer_list( pi_color_values, ppsz_color_descriptions )
242 add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
245 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
246 BG_OPACITY_TEXT, NULL, false )
248 add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
250 change_integer_list( pi_color_values, ppsz_color_descriptions )
253 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
254 OUTLINE_OPACITY_TEXT, NULL, false )
256 add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
258 change_integer_list( pi_color_values, ppsz_color_descriptions )
260 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
262 change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
265 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
266 SHADOW_OPACITY_TEXT, NULL, false )
268 add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
270 change_integer_list( pi_color_values, ppsz_color_descriptions )
272 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
273 SHADOW_ANGLE_TEXT, NULL, false )
275 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
276 SHADOW_DISTANCE_TEXT, NULL, false )
279 add_obsolete_integer( "freetype-effect" );
281 add_bool( "freetype-yuvp", false, YUVP_TEXT,
282 YUVP_LONGTEXT, true )
283 set_capability( "text renderer", 100 )
284 add_shortcut( "text" )
285 set_callbacks( Create, Destroy )
289 /*****************************************************************************
291 *****************************************************************************/
295 FT_BitmapGlyph p_glyph;
296 FT_BitmapGlyph p_outline;
297 FT_BitmapGlyph p_shadow;
298 uint32_t i_color; /* ARGB color */
299 int i_line_offset; /* underline/strikethrough offset */
300 int i_line_thickness; /* underline/strikethrough thickness */
303 typedef struct line_desc_t line_desc_t;
311 int i_character_count;
312 line_character_t *p_character;
315 /*****************************************************************************
316 * filter_sys_t: freetype local data
317 *****************************************************************************
318 * This structure is part of the video output thread descriptor.
319 * It describes the freetype specific properties of an output thread.
320 *****************************************************************************/
323 FT_Library p_library; /* handle to library */
324 FT_Face p_face; /* handle to face object */
325 FT_Stroker p_stroker;
334 float f_shadow_vector_x;
335 float f_shadow_vector_y;
339 int i_default_font_size;
341 char* psz_fontfamily;
342 char* psz_monofontfamily;
344 char* psz_win_fonts_path;
349 input_attachment_t **pp_font_attachments;
350 int i_font_attachments;
354 static void YUVFromRGB( uint32_t i_argb,
355 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
357 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
358 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
359 int i_blue = ( i_argb & 0x000000ff );
361 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
362 802 * i_blue + 4096 + 131072 ) >> 13, 235);
363 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
364 3598 * i_blue + 4096 + 1048576) >> 13, 240);
365 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
366 -585 * i_blue + 4096 + 1048576) >> 13, 240);
368 static void RGBFromRGB( uint32_t i_argb,
369 uint8_t *pi_r, uint8_t *pi_g, uint8_t *pi_b )
371 *pi_r = ( i_argb & 0x00ff0000 ) >> 16;
372 *pi_g = ( i_argb & 0x0000ff00 ) >> 8;
373 *pi_b = ( i_argb & 0x000000ff );
375 /*****************************************************************************
376 * Make any TTF/OTF fonts present in the attachments of the media file
377 * and store them for later use by the FreeType Engine
378 *****************************************************************************/
379 static int LoadFontsFromAttachments( filter_t *p_filter )
381 filter_sys_t *p_sys = p_filter->p_sys;
382 input_attachment_t **pp_attachments;
383 int i_attachments_cnt;
385 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
388 p_sys->i_font_attachments = 0;
389 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
390 if( !p_sys->pp_font_attachments )
393 for( int k = 0; k < i_attachments_cnt; k++ )
395 input_attachment_t *p_attach = pp_attachments[k];
397 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
398 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
399 p_attach->i_data > 0 && p_attach->p_data )
401 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
405 vlc_input_attachment_Delete( p_attach );
408 free( pp_attachments );
413 static int GetFontSize( filter_t *p_filter )
415 filter_sys_t *p_sys = p_filter->p_sys;
418 if( p_sys->i_default_font_size )
420 i_size = p_sys->i_default_font_size;
424 int i_ratio = var_InheritInteger( p_filter, "freetype-rel-fontsize" );
427 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
432 msg_Warn( p_filter, "invalid fontsize, using 12" );
438 static int SetFontSize( filter_t *p_filter, int i_size )
440 filter_sys_t *p_sys = p_filter->p_sys;
444 i_size = GetFontSize( p_filter );
446 msg_Dbg( p_filter, "using fontsize: %i", i_size );
449 p_sys->i_font_size = i_size;
451 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
453 msg_Err( p_filter, "couldn't set font size to %d", i_size );
461 #ifdef HAVE_FONTCONFIG
462 static void FontConfig_BuildCache( filter_t *p_filter )
465 msg_Dbg( p_filter, "Building font databases.");
473 #if defined( _WIN32 ) || defined( __APPLE__ )
474 dialog_progress_bar_t *p_dialog = NULL;
475 FcConfig *fcConfig = FcInitLoadConfig();
477 p_dialog = dialog_ProgressCreate( p_filter,
478 _("Building font cache"),
479 _("Please wait while your font cache is rebuilt.\n"
480 "This should take less than a few minutes."), NULL );
483 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
485 FcConfigBuildFonts( fcConfig );
486 #if defined( __APPLE__ )
487 // By default, scan only the directory /System/Library/Fonts.
488 // So build the set of available fonts under another directories,
489 // and add the set to the current configuration.
490 FcConfigAppFontAddDir( NULL, "~/Library/Fonts" );
491 FcConfigAppFontAddDir( NULL, "/Library/Fonts" );
492 FcConfigAppFontAddDir( NULL, "/Network/Library/Fonts" );
493 //FcConfigAppFontAddDir( NULL, "/System/Library/Fonts" );
497 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
498 dialog_ProgressDestroy( p_dialog );
503 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
507 * \brief Selects a font matching family, bold, italic provided
509 static char* FontConfig_Select( FcConfig* config, const char* family,
510 bool b_bold, bool b_italic, int i_size, int *i_idx )
512 FcResult result = FcResultMatch;
513 FcPattern *pat, *p_pat;
518 /* Create a pattern and fills it */
519 pat = FcPatternCreate();
520 if (!pat) return NULL;
523 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
524 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
525 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
526 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
530 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
532 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
533 free( psz_fontsize );
538 FcDefaultSubstitute( pat );
539 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
541 FcPatternDestroy( pat );
545 /* Find the best font for the pattern, destroy the pattern */
546 p_pat = FcFontMatch( config, pat, &result );
547 FcPatternDestroy( pat );
548 if( !p_pat || result == FcResultNoMatch ) return NULL;
550 /* Check the new pattern */
551 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
552 || ( val_b != FcTrue ) )
554 FcPatternDestroy( p_pat );
557 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
562 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
564 FcPatternDestroy( p_pat );
568 /* if( strcasecmp((const char*)val_s, family ) != 0 )
569 msg_Warn( p_filter, "fontconfig: selected font family is not"
570 "the requested one: '%s' != '%s'\n",
571 (const char*)val_s, family ); */
573 if( FcResultMatch == FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
574 ret = strdup( (const char*)val_s );
576 FcPatternDestroy( p_pat );
582 #define FONT_DIR_NT _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts")
584 static int GetFileFontByName( LPCTSTR font_name, char **psz_filename )
587 TCHAR vbuffer[MAX_PATH];
590 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
594 char *font_name_temp = FromT( font_name );
595 size_t fontname_len = strlen( font_name_temp );
597 for( int index = 0;; index++ )
599 DWORD vbuflen = MAX_PATH - 1;
602 LONG i_result = RegEnumValue( hKey, index, vbuffer, &vbuflen,
603 NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
604 if( i_result != ERROR_SUCCESS )
610 char *psz_value = FromT( vbuffer );
612 char *s = strchr( psz_value,'(' );
613 if( s != NULL && s != psz_value ) s[-1] = '\0';
615 /* Manage concatenated font names */
616 if( strchr( psz_value, '&') ) {
617 if( strcasestr( psz_value, font_name_temp ) != NULL )
624 if( strncasecmp( psz_value, font_name_temp, fontname_len ) == 0 )
634 *psz_filename = FromT( dbuffer );
635 free( font_name_temp );
641 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
642 DWORD type, LPARAM lParam)
644 VLC_UNUSED( metric );
645 if( (type & RASTER_FONTTYPE) ) return 1;
646 // if( lpelfe->elfScript ) FIXME
648 return GetFileFontByName( (LPCTSTR)lpelfe->elfFullName, (char **)lParam );
651 static char* Win32_Select( filter_t *p_filter, const char* family,
652 bool b_bold, bool b_italic, int i_size, int *i_idx )
654 VLC_UNUSED( i_size );
656 if( !family || strlen( family ) < 1 )
661 lf.lfCharSet = DEFAULT_CHARSET;
665 lf.lfWeight = FW_BOLD;
667 LPTSTR psz_fbuffer = ToT( family );
668 _tcsncpy( (LPTSTR)&lf.lfFaceName, psz_fbuffer, LF_FACESIZE );
672 char *psz_filename = NULL;
673 HDC hDC = GetDC( NULL );
674 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
675 ReleaseDC(NULL, hDC);
678 if( psz_filename != NULL )
680 /* FIXME: increase i_idx, when concatenated strings */
683 /* Prepend the Windows Font path, when only a filename was provided */
684 if( strchr( psz_filename, DIR_SEP_CHAR ) )
689 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
691 free( psz_filename );
694 free( psz_filename );
698 else /* Let's take any font we can */
702 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, "arial.ttf" ) == -1 )
711 #if !TARGET_OS_IPHONE
712 static char* MacLegacy_Select( filter_t *p_filter, const char* psz_fontname,
713 bool b_bold, bool b_italic, int i_size, int *i_idx )
715 VLC_UNUSED( b_bold );
716 VLC_UNUSED( b_italic );
717 VLC_UNUSED( i_size );
719 unsigned char path[MAXPATHLEN];
722 CFStringRef cf_fontName;
723 ATSFontRef ats_font_id;
727 if( psz_fontname == NULL )
730 msg_Dbg( p_filter, "looking for %s", psz_fontname );
731 cf_fontName = CFStringCreateWithCString( kCFAllocatorDefault, psz_fontname, kCFStringEncodingUTF8 );
733 ats_font_id = ATSFontFindFromName( cf_fontName, kATSOptionFlagsIncludeDisabledMask );
735 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
737 msg_Dbg( p_filter, "ATS couldn't find %s by name, checking family", psz_fontname );
738 ats_font_id = ATSFontFamilyFindFromName( cf_fontName, kATSOptionFlagsDefault );
740 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
742 msg_Dbg( p_filter, "ATS couldn't find either %s nor its family, checking PS name", psz_fontname );
743 ats_font_id = ATSFontFindFromPostScriptName( cf_fontName, kATSOptionFlagsDefault );
745 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
747 msg_Err( p_filter, "ATS couldn't find %s (no font name, family or PS name)", psz_fontname );
748 CFRelease( cf_fontName );
753 CFRelease( cf_fontName );
755 if ( noErr != ATSFontGetFileReference( ats_font_id, &ref ) )
757 msg_Err( p_filter, "ATS couldn't get file ref for %s", psz_fontname );
761 /* i_idx calculation by searching preceding fontIDs */
762 /* with same FSRef */
764 ATSFontRef id2 = ats_font_id - 1;
769 if ( noErr != ATSFontGetFileReference( id2, &ref2 ) )
771 if ( noErr != FSCompareFSRefs( &ref, &ref2 ) )
776 *i_idx = ats_font_id - ( id2 + 1 );
779 if ( noErr != FSRefMakePath( &ref, path, sizeof(path) ) )
781 msg_Err( p_filter, "failure when getting path from FSRef" );
784 msg_Dbg( p_filter, "found %s", path );
786 psz_path = strdup( (char *)path );
793 #endif /* HAVE_STYLES */
796 /*****************************************************************************
797 * RenderYUVP: place string in picture
798 *****************************************************************************
799 * This function merges the previously rendered freetype glyphs into a picture
800 *****************************************************************************/
801 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
805 VLC_UNUSED(p_filter);
806 static const uint8_t pi_gamma[16] =
807 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
808 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
812 int i, x, y, i_pitch;
813 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
815 /* Create a new subpicture region */
816 video_format_Init( &fmt, VLC_CODEC_YUVP );
818 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
820 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
822 assert( !p_region->p_picture );
823 p_region->p_picture = picture_NewFromFormat( &fmt );
824 if( !p_region->p_picture )
826 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
829 /* Calculate text color components
830 * Only use the first color */
831 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
832 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
835 fmt.p_palette->i_entries = 16;
836 for( i = 0; i < 8; i++ )
838 fmt.p_palette->palette[i][0] = 0;
839 fmt.p_palette->palette[i][1] = 0x80;
840 fmt.p_palette->palette[i][2] = 0x80;
841 fmt.p_palette->palette[i][3] = pi_gamma[i];
842 fmt.p_palette->palette[i][3] =
843 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
845 for( i = 8; i < fmt.p_palette->i_entries; i++ )
847 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
848 fmt.p_palette->palette[i][1] = i_u;
849 fmt.p_palette->palette[i][2] = i_v;
850 fmt.p_palette->palette[i][3] = pi_gamma[i];
851 fmt.p_palette->palette[i][3] =
852 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
855 p_dst = p_region->p_picture->Y_PIXELS;
856 i_pitch = p_region->p_picture->Y_PITCH;
858 /* Initialize the region pixels */
859 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
861 for( ; p_line != NULL; p_line = p_line->p_next )
863 int i_align_left = 0;
864 if( p_line->i_width < (int)fmt.i_visible_width )
866 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
867 i_align_left = ( fmt.i_visible_width - p_line->i_width );
868 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
869 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
873 for( i = 0; i < p_line->i_character_count; i++ )
875 const line_character_t *ch = &p_line->p_character[i];
876 FT_BitmapGlyph p_glyph = ch->p_glyph;
878 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
879 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
881 for( y = 0; y < p_glyph->bitmap.rows; y++ )
883 for( x = 0; x < p_glyph->bitmap.width; x++ )
885 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
886 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
887 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
893 /* Outlining (find something better than nearest neighbour filtering ?) */
896 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
897 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
898 uint8_t left, current;
900 for( y = 1; y < (int)fmt.i_height - 1; y++ )
902 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
903 p_dst += p_region->p_picture->Y_PITCH;
906 for( x = 1; x < (int)fmt.i_width - 1; x++ )
909 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
910 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;
914 memset( p_top, 0, fmt.i_width );
920 /*****************************************************************************
921 * RenderYUVA: place string in picture
922 *****************************************************************************
923 * This function merges the previously rendered freetype glyphs into a picture
924 *****************************************************************************/
925 static void FillYUVAPicture( picture_t *p_picture,
926 int i_a, int i_y, int i_u, int i_v )
928 memset( p_picture->p[0].p_pixels, i_y,
929 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
930 memset( p_picture->p[1].p_pixels, i_u,
931 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
932 memset( p_picture->p[2].p_pixels, i_v,
933 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
934 memset( p_picture->p[3].p_pixels, i_a,
935 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
938 static inline void BlendYUVAPixel( picture_t *p_picture,
939 int i_picture_x, int i_picture_y,
940 int i_a, int i_y, int i_u, int i_v,
943 int i_an = i_a * i_alpha / 255;
945 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
946 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
947 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
948 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
960 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
963 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
964 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
965 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
970 static void FillRGBAPicture( picture_t *p_picture,
971 int i_a, int i_r, int i_g, int i_b )
973 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
975 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
977 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
986 static inline void BlendRGBAPixel( picture_t *p_picture,
987 int i_picture_x, int i_picture_y,
988 int i_a, int i_r, int i_g, int i_b,
991 int i_an = i_a * i_alpha / 255;
993 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
995 int i_ao = p_rgba[3];
1005 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
1006 if( p_rgba[3] != 0 )
1008 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
1009 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
1010 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
1015 static inline void BlendAXYZGlyph( picture_t *p_picture,
1016 int i_picture_x, int i_picture_y,
1017 int i_a, int i_x, int i_y, int i_z,
1018 FT_BitmapGlyph p_glyph,
1019 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1022 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
1024 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
1025 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
1027 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
1031 static inline void BlendAXYZLine( picture_t *p_picture,
1032 int i_picture_x, int i_picture_y,
1033 int i_a, int i_x, int i_y, int i_z,
1034 const line_character_t *p_current,
1035 const line_character_t *p_next,
1036 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1038 int i_line_width = p_current->p_glyph->bitmap.width;
1040 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
1042 for( int dx = 0; dx < i_line_width; dx++ )
1044 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
1045 BlendPixel( p_picture,
1047 i_picture_y + p_current->i_line_offset + dy,
1048 i_a, i_x, i_y, i_z, 0xff );
1052 static inline void RenderBackground( subpicture_region_t *p_region,
1053 line_desc_t *p_line_head,
1056 picture_t *p_picture,
1058 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
1059 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1061 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
1063 int i_align_left = i_margin;
1064 int i_align_top = i_margin;
1067 unsigned line_top = 0;
1068 int line_bottom = 0;
1071 if( p_line->i_width < i_text_width )
1073 /* Left offset to take into account alignment */
1074 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1075 i_align_left += ( i_text_width - p_line->i_width );
1076 else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
1077 i_align_left = i_margin; /* Keep it the way it is */
1078 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1079 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1082 /* Find the tallest character in the line */
1083 for( int i = 0; i < p_line->i_character_count; i++ ) {
1084 const line_character_t *ch = &p_line->p_character[i];
1085 FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
1086 if (p_glyph->top > max_height)
1087 max_height = p_glyph->top;
1090 /* Compute the background for the line (identify leading/trailing space) */
1091 for( int i = 0; i < p_line->i_character_count; i++ ) {
1092 const line_character_t *ch = &p_line->p_character[i];
1093 FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
1094 if (p_glyph && p_glyph->bitmap.rows > 0) {
1095 // Found a non-whitespace character
1096 line_start = i_align_left + p_glyph->left - p_bbox->xMin;
1101 /* Fudge factor to make sure caption background edges are left aligned
1102 despite variable font width */
1103 if (line_start < 12)
1106 /* Find right boundary for bounding box for background */
1107 for( int i = p_line->i_character_count; i > 0; i-- ) {
1108 const line_character_t *ch = &p_line->p_character[i - 1];
1109 FT_BitmapGlyph p_glyph = ch->p_shadow ? ch->p_shadow : ch->p_glyph;
1110 if (p_glyph && p_glyph->bitmap.rows > 0) {
1111 // Found a non-whitespace character
1112 line_end = i_align_left + p_glyph->left - p_bbox->xMin + p_glyph->bitmap.width;
1117 /* Setup color for the background */
1118 uint8_t i_x, i_y, i_z;
1119 ExtractComponents( 0x000000, &i_x, &i_y, &i_z );
1121 /* Compute the upper boundary for the background */
1122 if ((i_align_top + p_line->i_base_line - max_height) < 0)
1123 line_top = i_align_top + p_line->i_base_line;
1125 line_top = i_align_top + p_line->i_base_line - max_height;
1127 /* Compute lower boundary for the background */
1128 line_bottom = __MIN(line_top + p_line->i_height, p_region->fmt.i_visible_height);
1130 /* Render the actual background */
1131 for( int dy = line_top; dy < line_bottom; dy++ )
1133 for( int dx = line_start; dx < line_end; dx++ )
1134 BlendPixel( p_picture, dx, dy, 0xff, i_x, i_y, i_z, 0xff );
1139 static inline int RenderAXYZ( filter_t *p_filter,
1140 subpicture_region_t *p_region,
1141 line_desc_t *p_line_head,
1144 vlc_fourcc_t i_chroma,
1145 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
1146 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
1147 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1149 filter_sys_t *p_sys = p_filter->p_sys;
1151 /* Create a new subpicture region */
1152 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
1153 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
1155 video_format_Init( &fmt, i_chroma );
1157 fmt.i_visible_width = i_text_width + 2 * i_margin;
1159 fmt.i_visible_height = i_text_height + 2 * i_margin;
1161 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
1162 if( !p_region->p_picture )
1163 return VLC_EGENERIC;
1164 p_region->fmt = fmt;
1166 /* Initialize the picture background */
1167 uint8_t i_a = var_InheritInteger( p_filter, "freetype-background-opacity" );
1168 i_a = VLC_CLIP( i_a, 0, 255 );
1169 uint8_t i_x, i_y, i_z;
1171 if (p_region->b_renderbg) {
1172 /* Render the background just under the text */
1173 FillPicture( p_picture, 0x00, 0x00, 0x00, 0x00 );
1174 RenderBackground(p_region, p_line_head, p_bbox, i_margin, p_picture, i_text_width,
1175 ExtractComponents, BlendPixel);
1177 /* Render background under entire subpicture block */
1178 int i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
1179 i_background_color = VLC_CLIP( i_background_color, 0, 0xFFFFFF );
1180 ExtractComponents( i_background_color, &i_x, &i_y, &i_z );
1181 FillPicture( p_picture, i_a, i_x, i_y, i_z );
1184 /* Render shadow then outline and then normal glyphs */
1185 for( int g = 0; g < 3; g++ )
1187 /* Render all lines */
1188 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
1190 int i_align_left = i_margin;
1191 if( p_line->i_width < i_text_width )
1193 /* Left offset to take into account alignment */
1194 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1195 i_align_left += ( i_text_width - p_line->i_width );
1196 else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
1197 i_align_left = i_margin; /* Keep it the way it is */
1198 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1199 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1201 int i_align_top = i_margin;
1203 /* Render all glyphs and underline/strikethrough */
1204 for( int i = 0; i < p_line->i_character_count; i++ )
1206 const line_character_t *ch = &p_line->p_character[i];
1207 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
1211 i_a = (ch->i_color >> 24) & 0xff;
1215 i_a = i_a * p_sys->i_shadow_alpha / 255;
1216 i_color = p_sys->i_shadow_color;
1219 i_a = i_a * p_sys->i_outline_alpha / 255;
1220 i_color = p_sys->i_outline_color;
1223 i_color = ch->i_color;
1226 ExtractComponents( i_color, &i_x, &i_y, &i_z );
1228 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
1229 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
1231 BlendAXYZGlyph( p_picture,
1232 i_glyph_x, i_glyph_y,
1237 /* underline/strikethrough are only rendered for the normal glyph */
1238 if( g == 2 && ch->i_line_thickness > 0 )
1239 BlendAXYZLine( p_picture,
1240 i_glyph_x, i_glyph_y + p_glyph->top,
1243 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
1253 static text_style_t *GetStyleFromFontStack( filter_t *p_filter,
1254 font_stack_t **p_fonts,
1257 char *psz_fontname = NULL;
1258 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
1259 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
1260 i_font_color = i_font_color & 0x00ffffff;
1261 int i_font_size = p_filter->p_sys->i_font_size;
1262 uint32_t i_karaoke_bg_color = i_font_color;
1264 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1265 &i_font_color, &i_karaoke_bg_color ) )
1268 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1274 static int ProcessNodes( filter_t *p_filter,
1275 uni_char_t *psz_text,
1276 text_style_t **pp_styles,
1277 uint32_t *pi_k_dates,
1279 xml_reader_t *p_xml_reader,
1280 text_style_t *p_font_style )
1282 int rv = VLC_SUCCESS;
1283 filter_sys_t *p_sys = p_filter->p_sys;
1284 int i_text_length = 0;
1285 font_stack_t *p_fonts = NULL;
1286 uint32_t i_k_date = 0;
1288 int i_style_flags = 0;
1292 /* If the font is not specified in the style, assume the system font */
1293 if(!p_font_style->psz_fontname)
1294 p_font_style->psz_fontname = strdup(p_sys->psz_fontfamily);
1296 rv = PushFont( &p_fonts,
1297 p_font_style->psz_fontname,
1298 p_font_style->i_font_size > 0 ? p_font_style->i_font_size
1299 : p_sys->i_font_size,
1300 (p_font_style->i_font_color & 0xffffff) |
1301 ((p_font_style->i_font_alpha & 0xff) << 24),
1302 (p_font_style->i_karaoke_background_color & 0xffffff) |
1303 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1305 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1312 uint32_t i_font_size = p_sys->i_font_size;
1313 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
1314 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
1315 int i_font_alpha = p_sys->i_font_alpha;
1316 rv = PushFont( &p_fonts,
1317 p_sys->psz_fontfamily,
1319 (i_font_color & 0xffffff) |
1320 ((i_font_alpha & 0xff) << 24),
1323 if( p_sys->i_style_flags & STYLE_BOLD )
1324 i_style_flags |= STYLE_BOLD;
1326 if( rv != VLC_SUCCESS )
1332 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1336 case XML_READER_ENDELEM:
1337 if( !strcasecmp( "font", node ) )
1338 PopFont( &p_fonts );
1339 else if( !strcasecmp( "tt", node ) )
1340 PopFont( &p_fonts );
1341 else if( !strcasecmp( "b", node ) )
1342 i_style_flags &= ~STYLE_BOLD;
1343 else if( !strcasecmp( "i", node ) )
1344 i_style_flags &= ~STYLE_ITALIC;
1345 else if( !strcasecmp( "u", node ) )
1346 i_style_flags &= ~STYLE_UNDERLINE;
1347 else if( !strcasecmp( "s", node ) )
1348 i_style_flags &= ~STYLE_STRIKEOUT;
1351 case XML_READER_STARTELEM:
1352 if( !strcasecmp( "font", node ) )
1353 HandleFontAttributes( p_xml_reader, &p_fonts );
1354 else if( !strcasecmp( "tt", node ) )
1355 HandleTT( &p_fonts, p_sys->psz_monofontfamily );
1356 else if( !strcasecmp( "b", node ) )
1357 i_style_flags |= STYLE_BOLD;
1358 else if( !strcasecmp( "i", node ) )
1359 i_style_flags |= STYLE_ITALIC;
1360 else if( !strcasecmp( "u", node ) )
1361 i_style_flags |= STYLE_UNDERLINE;
1362 else if( !strcasecmp( "s", node ) )
1363 i_style_flags |= STYLE_STRIKEOUT;
1364 else if( !strcasecmp( "br", node ) )
1366 i_text_length += SetupText( p_filter,
1367 &psz_text[i_text_length],
1368 &pp_styles[i_text_length],
1369 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1371 GetStyleFromFontStack( p_filter,
1376 else if( !strcasecmp( "k", node ) )
1379 const char *name, *value;
1380 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1382 if( !strcasecmp( "t", name ) && value )
1383 i_k_date += atoi( value );
1388 case XML_READER_TEXT:
1390 char *psz_node = strdup( node );
1391 if( unlikely(!psz_node) )
1394 HandleWhiteSpace( psz_node );
1395 resolve_xml_special_chars( psz_node );
1397 i_text_length += SetupText( p_filter,
1398 &psz_text[i_text_length],
1399 &pp_styles[i_text_length],
1400 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1402 GetStyleFromFontStack( p_filter,
1412 *pi_len = i_text_length;
1414 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1419 static void FreeLine( line_desc_t *p_line )
1421 for( int i = 0; i < p_line->i_character_count; i++ )
1423 line_character_t *ch = &p_line->p_character[i];
1424 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1426 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1428 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1431 free( p_line->p_character );
1435 static void FreeLines( line_desc_t *p_lines )
1437 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1439 line_desc_t *p_next = p_line->p_next;
1445 static line_desc_t *NewLine( int i_count )
1447 line_desc_t *p_line = malloc( sizeof(*p_line) );
1452 p_line->p_next = NULL;
1453 p_line->i_width = 0;
1454 p_line->i_base_line = 0;
1455 p_line->i_character_count = 0;
1457 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1458 if( !p_line->p_character )
1466 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1468 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1470 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1472 FT_Face p_face = NULL;
1474 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1482 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1483 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1484 if( p_face->family_name != NULL
1485 && !strcasecmp( p_face->family_name, p_style->psz_fontname )
1486 && (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC))
1487 == i_style_received )
1490 FT_Done_Face( p_face );
1498 static FT_Face LoadFace( filter_t *p_filter,
1499 const text_style_t *p_style )
1501 filter_sys_t *p_sys = p_filter->p_sys;
1503 /* Look for a match amongst our attachments first */
1504 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1506 /* Load system wide font otheriwse */
1510 char *psz_fontfile = NULL;
1511 #ifdef HAVE_FONTCONFIG
1512 psz_fontfile = FontConfig_Select( NULL,
1513 p_style->psz_fontname,
1514 (p_style->i_style_flags & STYLE_BOLD) != 0,
1515 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1518 #elif defined( __APPLE__ )
1519 #if !TARGET_OS_IPHONE
1520 psz_fontfile = MacLegacy_Select( p_filter, p_style->psz_fontname, false, false, -1, &i_idx );
1522 #elif defined( _WIN32 )
1523 psz_fontfile = Win32_Select( p_filter,
1524 p_style->psz_fontname,
1525 (p_style->i_style_flags & STYLE_BOLD) != 0,
1526 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1530 psz_fontfile = NULL;
1535 if( *psz_fontfile == '\0' )
1538 "We were not able to find a matching font: \"%s\" (%s %s),"
1539 " so using default font",
1540 p_style->psz_fontname,
1541 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1542 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1547 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1550 free( psz_fontfile );
1555 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1557 /* We've loaded a font face which is unhelpful for actually
1558 * rendering text - fallback to the default one.
1560 FT_Done_Face( p_face );
1566 static int GetGlyph( filter_t *p_filter,
1567 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1568 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1569 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1575 FT_Vector *p_pen_shadow )
1577 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1578 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1580 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1581 return VLC_EGENERIC;
1584 /* Do synthetic styling now that Freetype supports it;
1585 * ie. if the font we have loaded is NOT already in the
1586 * style that the tags want, then switch it on; if they
1587 * are then don't. */
1588 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1589 FT_GlyphSlot_Embolden( p_face->glyph );
1590 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1591 FT_GlyphSlot_Oblique( p_face->glyph );
1594 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1596 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1597 return VLC_EGENERIC;
1600 FT_Glyph outline = NULL;
1601 if( p_filter->p_sys->p_stroker )
1604 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1608 FT_Glyph shadow = NULL;
1609 if( p_filter->p_sys->i_shadow_alpha > 0 )
1611 shadow = outline ? outline : glyph;
1612 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1618 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1621 *pp_shadow = shadow;
1623 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1625 FT_Done_Glyph( glyph );
1627 FT_Done_Glyph( outline );
1629 FT_Done_Glyph( shadow );
1630 return VLC_EGENERIC;
1632 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1637 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1638 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1640 *pp_outline = outline;
1645 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1647 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1648 if( p_bbox->xMin >= p_bbox->xMax )
1650 p_bbox->xMin = FT_CEIL(p_pen->x);
1651 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1652 glyph_bmp->left = p_bbox->xMin;
1654 if( p_bbox->yMin >= p_bbox->yMax )
1656 p_bbox->yMax = FT_CEIL(p_pen->y);
1657 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1658 glyph_bmp->top = p_bbox->yMax;
1662 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1664 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1665 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1666 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1667 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1670 static int ProcessLines( filter_t *p_filter,
1671 line_desc_t **pp_lines,
1673 int *pi_max_face_height,
1675 uni_char_t *psz_text,
1676 text_style_t **pp_styles,
1677 uint32_t *pi_k_dates,
1680 filter_sys_t *p_sys = p_filter->p_sys;
1681 uni_char_t *p_fribidi_string = NULL;
1682 text_style_t **pp_fribidi_styles = NULL;
1683 int *p_new_positions = NULL;
1685 #if defined(HAVE_FRIBIDI)
1687 int *p_old_positions;
1688 int start_pos, pos = 0;
1690 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1692 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1693 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1694 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1696 if( ! pp_fribidi_styles ||
1697 ! p_fribidi_string ||
1698 ! p_old_positions ||
1701 free( p_old_positions );
1702 free( p_new_positions );
1703 free( p_fribidi_string );
1704 free( pp_fribidi_styles );
1708 /* Do bidi conversion line-by-line */
1711 while(pos < i_len) {
1712 if (psz_text[pos] != '\n')
1714 p_fribidi_string[pos] = psz_text[pos];
1715 pp_fribidi_styles[pos] = pp_styles[pos];
1716 p_new_positions[pos] = pos;
1720 while(pos < i_len) {
1721 if (psz_text[pos] == '\n')
1725 if (pos > start_pos)
1727 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1728 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1730 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1732 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1733 pos - start_pos, &base_dir,
1734 (FriBidiChar*)p_fribidi_string + start_pos,
1735 p_new_positions + start_pos,
1738 for( int j = start_pos; j < pos; j++ )
1740 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1741 p_new_positions[ j ] += start_pos;
1745 p_fribidi_string[ i_len ] = 0;
1746 free( p_old_positions );
1748 pp_styles = pp_fribidi_styles;
1749 psz_text = p_fribidi_string;
1752 /* Work out the karaoke */
1753 uint8_t *pi_karaoke_bar = NULL;
1756 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1757 if( pi_karaoke_bar )
1759 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1760 for( int i = 0; i < i_len; i++ )
1762 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1763 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1767 free( p_new_positions );
1769 *pi_max_face_height = 0;
1771 line_desc_t **pp_line_next = pp_lines;
1779 int i_face_height_previous = 0;
1780 int i_base_line = 0;
1781 const text_style_t *p_previous_style = NULL;
1782 FT_Face p_face = NULL;
1783 for( int i_start = 0; i_start < i_len; )
1785 /* Compute the length of the current text line */
1787 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
1790 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1791 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
1792 int i_index = i_start;
1797 int i_face_height = 0;
1798 FT_BBox line_bbox = {
1804 int i_ul_offset = 0;
1805 int i_ul_thickness = 0;
1814 break_point_t break_point;
1815 break_point_t break_point_fallback;
1817 #define SAVE_BP(dst) do { \
1818 dst.i_index = i_index; \
1820 dst.line_bbox = line_bbox; \
1821 dst.i_face_height = i_face_height; \
1822 dst.i_ul_offset = i_ul_offset; \
1823 dst.i_ul_thickness = i_ul_thickness; \
1826 SAVE_BP( break_point );
1827 SAVE_BP( break_point_fallback );
1829 while( i_index < i_start + i_length )
1831 /* Split by common FT_Face + Size */
1832 const text_style_t *p_current_style = pp_styles[i_index];
1833 int i_part_length = 0;
1834 while( i_index + i_part_length < i_start + i_length )
1836 const text_style_t *p_style = pp_styles[i_index + i_part_length];
1837 if( !FaceStyleEquals( p_style, p_current_style ) ||
1838 p_style->i_font_size != p_current_style->i_font_size )
1843 /* (Re)load/reconfigure the face if needed */
1844 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
1847 FT_Done_Face( p_face );
1848 p_previous_style = NULL;
1850 p_face = LoadFace( p_filter, p_current_style );
1852 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
1853 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
1855 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
1856 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
1857 if( p_sys->p_stroker )
1859 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
1860 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
1861 int i_radius = (p_current_style->i_font_size << 6) * f_outline_thickness;
1862 FT_Stroker_Set( p_sys->p_stroker,
1864 FT_STROKER_LINECAP_ROUND,
1865 FT_STROKER_LINEJOIN_ROUND, 0 );
1868 p_previous_style = p_current_style;
1870 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
1871 p_current_face->size->metrics.y_scale)));
1873 /* Render the part */
1874 bool b_break_line = false;
1875 int i_glyph_last = 0;
1876 while( i_part_length > 0 )
1878 const text_style_t *p_glyph_style = pp_styles[i_index];
1879 uni_char_t character = psz_text[i_index];
1880 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
1882 /* Get kerning vector */
1883 FT_Vector kerning = { .x = 0, .y = 0 };
1884 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
1885 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
1887 /* Get the glyph bitmap and its bounding box and all the associated properties */
1888 FT_Vector pen_new = {
1889 .x = pen.x + kerning.x,
1890 .y = pen.y + kerning.y,
1892 FT_Vector pen_shadow_new = {
1893 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
1894 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
1899 FT_BBox outline_bbox;
1901 FT_BBox shadow_bbox;
1903 if( GetGlyph( p_filter,
1904 &glyph, &glyph_bbox,
1905 &outline, &outline_bbox,
1906 &shadow, &shadow_bbox,
1907 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
1908 &pen_new, &pen_shadow_new ) )
1911 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
1913 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
1915 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
1917 /* FIXME and what about outline */
1919 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
1920 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
1921 (p_glyph_style->i_karaoke_background_alpha << 24))
1922 : (p_glyph_style->i_font_color |
1923 (p_glyph_style->i_font_alpha << 24));
1924 int i_line_offset = 0;
1925 int i_line_thickness = 0;
1926 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1928 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
1929 p_current_face->size->metrics.y_scale)) );
1931 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
1932 p_current_face->size->metrics.y_scale)) );
1934 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
1936 /* Move the baseline to make it strikethrough instead of
1937 * underline. That means that strikethrough takes precedence
1939 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
1940 p_current_face->size->metrics.y_scale)) );
1942 else if( i_line_thickness > 0 )
1944 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
1946 /* The real underline thickness and position are
1947 * updated once the whole line has been parsed */
1948 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
1949 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
1950 i_line_thickness = -1;
1953 FT_BBox line_bbox_new = line_bbox;
1954 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
1956 BBoxEnlarge( &line_bbox_new, &outline_bbox );
1958 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
1960 b_break_line = i_index > i_start &&
1961 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
1964 FT_Done_Glyph( glyph );
1966 FT_Done_Glyph( outline );
1968 FT_Done_Glyph( shadow );
1970 break_point_t *p_bp = NULL;
1971 if( break_point.i_index > i_start )
1972 p_bp = &break_point;
1973 else if( break_point_fallback.i_index > i_start )
1974 p_bp = &break_point_fallback;
1978 msg_Dbg( p_filter, "Breaking line");
1979 for( int i = p_bp->i_index; i < i_index; i++ )
1981 line_character_t *ch = &p_line->p_character[i - i_start];
1982 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1984 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1986 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1988 p_line->i_character_count = p_bp->i_index - i_start;
1990 i_index = p_bp->i_index;
1992 line_bbox = p_bp->line_bbox;
1993 i_face_height = p_bp->i_face_height;
1994 i_ul_offset = p_bp->i_ul_offset;
1995 i_ul_thickness = p_bp->i_ul_thickness;
1999 msg_Err( p_filter, "Breaking unbreakable line");
2004 assert( p_line->i_character_count == i_index - i_start);
2005 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2006 .p_glyph = (FT_BitmapGlyph)glyph,
2007 .p_outline = (FT_BitmapGlyph)outline,
2008 .p_shadow = (FT_BitmapGlyph)shadow,
2010 .i_line_offset = i_line_offset,
2011 .i_line_thickness = i_line_thickness,
2014 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2015 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2016 line_bbox = line_bbox_new;
2018 i_glyph_last = i_glyph_index;
2022 if( character == ' ' || character == '\t' )
2023 SAVE_BP( break_point );
2024 else if( character == 160 )
2025 SAVE_BP( break_point_fallback );
2031 /* Update our baseline */
2032 if( i_face_height_previous > 0 )
2033 i_base_line += __MAX(i_face_height, i_face_height_previous);
2034 if( i_face_height > 0 )
2035 i_face_height_previous = i_face_height;
2037 /* Update the line bbox with the actual base line */
2038 if (line_bbox.yMax > line_bbox.yMin) {
2039 line_bbox.yMin -= i_base_line;
2040 line_bbox.yMax -= i_base_line;
2042 BBoxEnlarge( &bbox, &line_bbox );
2044 /* Terminate and append the line */
2047 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2048 p_line->i_base_line = i_base_line;
2049 p_line->i_height = __MAX(i_face_height, i_face_height_previous);
2050 if( i_ul_thickness > 0 )
2052 for( int i = 0; i < p_line->i_character_count; i++ )
2054 line_character_t *ch = &p_line->p_character[i];
2055 if( ch->i_line_thickness < 0 )
2057 ch->i_line_offset = i_ul_offset;
2058 ch->i_line_thickness = i_ul_thickness;
2063 *pp_line_next = p_line;
2064 pp_line_next = &p_line->p_next;
2067 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2069 /* Skip what we have rendered and the line delimitor if present */
2071 if( i_start < i_len && psz_text[i_start] == '\n' )
2074 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
2076 msg_Err( p_filter, "Truncated too high subtitle" );
2081 FT_Done_Face( p_face );
2083 free( pp_fribidi_styles );
2084 free( p_fribidi_string );
2085 free( pi_karaoke_bar );
2092 * This function renders a text subpicture region into another one.
2093 * It also calculates the size needed for this string, and renders the
2094 * needed glyphs into memory. It is used as pf_add_string callback in
2095 * the vout method by this module
2097 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2098 subpicture_region_t *p_region_in, bool b_html,
2099 const vlc_fourcc_t *p_chroma_list )
2101 filter_sys_t *p_sys = p_filter->p_sys;
2104 return VLC_EGENERIC;
2105 if( b_html && !p_region_in->psz_html )
2106 return VLC_EGENERIC;
2107 if( !b_html && !p_region_in->psz_text )
2108 return VLC_EGENERIC;
2110 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2111 : p_region_in->psz_text );
2113 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2114 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2115 if( !psz_text || !pp_styles )
2119 return VLC_EGENERIC;
2122 /* Reset the default fontsize in case screen metrics have changed */
2123 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2126 int rv = VLC_SUCCESS;
2127 int i_text_length = 0;
2129 int i_max_face_height;
2130 line_desc_t *p_lines = NULL;
2132 uint32_t *pi_k_durations = NULL;
2136 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2137 (uint8_t *) p_region_in->psz_html,
2138 strlen( p_region_in->psz_html ),
2140 if( unlikely(p_sub == NULL) )
2143 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2145 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2147 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2148 p_filter->p_sys->p_xml = p_xml_reader;
2155 /* Look for Root Node */
2158 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2160 if( strcasecmp( "karaoke", node ) == 0 )
2162 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2164 else if( strcasecmp( "text", node ) != 0 )
2166 /* Only text and karaoke tags are supported */
2167 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2174 msg_Err( p_filter, "Malformed HTML subtitle" );
2180 rv = ProcessNodes( p_filter,
2181 psz_text, pp_styles, pi_k_durations, &i_text_length,
2182 p_xml_reader, p_region_in->p_style );
2186 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2188 stream_Delete( p_sub );
2192 text_style_t *p_style;
2193 if( p_region_in->p_style )
2194 p_style = CreateStyle( p_region_in->p_style->psz_fontname ? p_region_in->p_style->psz_fontname
2195 : p_sys->psz_fontfamily,
2196 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2197 : p_sys->i_font_size,
2198 (p_region_in->p_style->i_font_color & 0xffffff) |
2199 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2201 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2207 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2208 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
2209 p_style = CreateStyle( p_sys->psz_fontfamily,
2211 (i_font_color & 0xffffff) |
2212 ((p_sys->i_font_alpha & 0xff) << 24),
2215 if( p_sys->i_style_flags & STYLE_BOLD )
2216 p_style->i_style_flags |= STYLE_BOLD;
2218 i_text_length = SetupText( p_filter,
2222 p_region_in->psz_text, p_style, 0 );
2225 if( !rv && i_text_length > 0 )
2227 rv = ProcessLines( p_filter,
2228 &p_lines, &bbox, &i_max_face_height,
2229 psz_text, pp_styles, pi_k_durations, i_text_length );
2232 p_region_out->i_x = p_region_in->i_x;
2233 p_region_out->i_y = p_region_in->i_y;
2235 /* Don't attempt to render text that couldn't be layed out
2237 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2239 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2240 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2242 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2243 p_chroma_list = p_chroma_list_yuvp;
2244 else if( !p_chroma_list || *p_chroma_list == 0 )
2245 p_chroma_list = p_chroma_list_rgba;
2247 uint8_t i_background_opacity = var_InheritInteger( p_filter, "freetype-background-opacity" );
2248 i_background_opacity = VLC_CLIP( i_background_opacity, 0, 255 );
2249 const int i_margin = i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2250 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2253 if( *p_chroma == VLC_CODEC_YUVP )
2254 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2255 else if( *p_chroma == VLC_CODEC_YUVA )
2256 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2261 else if( *p_chroma == VLC_CODEC_RGBA )
2262 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2271 /* With karaoke, we're going to have to render the text a number
2272 * of times to show the progress marker on the text.
2274 if( pi_k_durations )
2275 var_SetBool( p_filter, "text-rerender", true );
2278 FreeLines( p_lines );
2281 for( int i = 0; i < i_text_length; i++ )
2283 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2284 text_style_Delete( pp_styles[i] );
2287 free( pi_k_durations );
2292 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2293 subpicture_region_t *p_region_in,
2294 const vlc_fourcc_t *p_chroma_list )
2296 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2299 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2300 subpicture_region_t *p_region_in,
2301 const vlc_fourcc_t *p_chroma_list )
2303 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2306 /*****************************************************************************
2307 * Create: allocates osd-text video thread output method
2308 *****************************************************************************
2309 * This function allocates and initializes a Clone vout method.
2310 *****************************************************************************/
2311 static int Create( vlc_object_t *p_this )
2313 filter_t *p_filter = (filter_t *)p_this;
2314 filter_sys_t *p_sys;
2315 char *psz_fontfile = NULL;
2316 char *psz_fontfamily = NULL;
2317 char *psz_monofontfile = NULL;
2318 char *psz_monofontfamily = NULL;
2319 int i_error = 0, fontindex = 0, monofontindex = 0;
2321 /* Allocate structure */
2322 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2326 p_sys->psz_fontfamily = NULL;
2327 p_sys->p_xml = NULL;
2329 p_sys->p_library = 0;
2330 p_sys->i_font_size = 0;
2333 * The following variables should not be cached, as they might be changed on-the-fly:
2334 * freetype-rel-fontsize, freetype-background-opacity, freetype-background-color,
2335 * freetype-outline-thickness, freetype-color
2339 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2340 psz_monofontfamily = var_InheritString( p_filter, "freetype-monofont" );
2341 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2342 p_sys->i_font_alpha = var_InheritInteger( p_filter,"freetype-opacity" );
2343 p_sys->i_font_alpha = VLC_CLIP( p_sys->i_font_alpha, 0, 255 );
2344 if( var_InheritBool( p_filter, "freetype-bold" ) )
2345 p_sys->i_style_flags |= STYLE_BOLD;
2347 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2348 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
2349 p_sys->i_outline_alpha = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2350 p_sys->i_outline_alpha = VLC_CLIP( p_sys->i_outline_alpha, 0, 255 );
2351 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2352 p_sys->i_outline_color = VLC_CLIP( p_sys->i_outline_color, 0, 0xFFFFFF );
2354 p_sys->i_shadow_alpha = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2355 p_sys->i_shadow_alpha = VLC_CLIP( p_sys->i_shadow_alpha, 0, 255 );
2356 p_sys->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2357 p_sys->i_shadow_color = VLC_CLIP( p_sys->i_shadow_color, 0, 0xFFFFFF );
2358 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2359 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2360 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
2361 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2362 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2365 /* Get Windows Font folder */
2366 wchar_t wdir[MAX_PATH];
2367 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2369 GetWindowsDirectoryW( wdir, MAX_PATH );
2370 wcscat( wdir, L"\\fonts" );
2372 p_sys->psz_win_fonts_path = FromWide( wdir );
2375 /* Set default psz_fontfamily */
2376 if( !psz_fontfamily || !*psz_fontfamily )
2378 free( psz_fontfamily );
2380 psz_fontfamily = strdup( DEFAULT_FAMILY );
2383 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2385 psz_fontfamily = NULL;
2389 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2391 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2395 /* Set the current font file */
2396 p_sys->psz_fontfamily = psz_fontfamily;
2398 #ifdef HAVE_FONTCONFIG
2399 FontConfig_BuildCache( p_filter );
2402 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2403 p_sys->i_default_font_size, &fontindex );
2404 psz_monofontfile = FontConfig_Select( NULL, psz_monofontfamily, false,
2405 false, p_sys->i_default_font_size,
2407 #elif defined(__APPLE__)
2408 #if !TARGET_OS_IPHONE
2409 psz_fontfile = MacLegacy_Select( p_filter, psz_fontfamily, false, false, 0, &fontindex );
2411 #elif defined(_WIN32)
2412 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2413 p_sys->i_default_font_size, &fontindex );
2416 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2418 /* If nothing is found, use the default family */
2420 psz_fontfile = strdup( psz_fontfamily );
2421 if( !psz_monofontfile )
2422 psz_monofontfile = strdup( psz_monofontfamily );
2424 #else /* !HAVE_STYLES */
2425 /* Use the default file */
2426 psz_fontfile = psz_fontfamily;
2427 psz_monofontfile = psz_monofontfamily;
2429 p_sys->psz_monofontfamily = psz_monofontfamily;
2432 i_error = FT_Init_FreeType( &p_sys->p_library );
2435 msg_Err( p_filter, "couldn't initialize freetype" );
2439 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2440 fontindex, &p_sys->p_face );
2442 if( i_error == FT_Err_Unknown_File_Format )
2444 msg_Err( p_filter, "file %s have unknown format",
2445 psz_fontfile ? psz_fontfile : "(null)" );
2450 msg_Err( p_filter, "failed to load font file %s",
2451 psz_fontfile ? psz_fontfile : "(null)" );
2455 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2458 msg_Err( p_filter, "font has no unicode translation table" );
2462 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2464 p_sys->p_stroker = NULL;
2465 if( f_outline_thickness > 0.001 )
2467 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2469 msg_Err( p_filter, "Failed to create stroker for outlining" );
2472 p_sys->pp_font_attachments = NULL;
2473 p_sys->i_font_attachments = 0;
2475 p_filter->pf_render_text = RenderText;
2476 p_filter->pf_render_html = RenderHtml;
2478 LoadFontsFromAttachments( p_filter );
2481 free( psz_fontfile );
2482 free( psz_monofontfile );
2488 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2489 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2491 free( psz_fontfile );
2492 free( psz_monofontfile );
2494 free( psz_fontfamily );
2495 free( psz_monofontfamily );
2497 return VLC_EGENERIC;
2500 /*****************************************************************************
2501 * Destroy: destroy Clone video thread output method
2502 *****************************************************************************
2503 * Clean up all data and library connections
2504 *****************************************************************************/
2505 static void Destroy( vlc_object_t *p_this )
2507 filter_t *p_filter = (filter_t *)p_this;
2508 filter_sys_t *p_sys = p_filter->p_sys;
2510 if( p_sys->pp_font_attachments )
2512 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2513 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2515 free( p_sys->pp_font_attachments );
2518 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2519 free( p_sys->psz_fontfamily );
2520 free( p_sys->psz_monofontfamily );
2523 free( p_sys->psz_win_fonts_path );
2526 /* FcFini asserts calling the subfunction FcCacheFini()
2527 * even if no other library functions have been made since FcInit(),
2528 * so don't call it. */
2530 if( p_sys->p_stroker )
2531 FT_Stroker_Done( p_sys->p_stroker );
2532 FT_Done_Face( p_sys->p_face );
2533 FT_Done_FreeType( p_sys->p_library );