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_dialog.h> /* FcCache dialog */
43 #include <vlc_filter.h> /* filter_sys_t */
44 #include <vlc_text_style.h> /* text_style_t*/
48 # define SYSTEM_DEFAULT_FONT_FILE "/Library/Fonts/Arial Unicode.ttf"
49 # define SYSTEM_DEFAULT_FAMILY "Arial Unicode MS"
50 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/System/Library/Fonts/Monaco.dfont"
51 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monaco"
52 #elif defined( _WIN32 )
53 # define SYSTEM_DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
54 # define SYSTEM_DEFAULT_FAMILY "Arial"
55 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "cour.ttf"
56 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Courier New"
57 #elif defined( __OS2__ )
58 # define SYSTEM_DEFAULT_FONT_FILE "/psfonts/tnrwt_k.ttf"
59 # define SYSTEM_DEFAULT_FAMILY "Times New Roman WT K"
60 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/psfonts/mtsansdk.ttf"
61 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monotype Sans Duospace WT K"
62 #elif defined( __ANDROID__ )
63 # define SYSTEM_DEFAULT_FONT_FILE "/system/fonts/DroidSans-Bold.ttf"
64 # define SYSTEM_DEFAULT_FAMILY "Droid Sans Bold"
65 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/system/fonts/DroidSansMono.ttf"
66 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Droid Sans Mono"
68 # define SYSTEM_DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
69 # define SYSTEM_DEFAULT_FAMILY "Serif Bold"
70 # define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeMono.ttf"
71 # define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monospace"
74 #ifndef DEFAULT_FONT_FILE
75 #define DEFAULT_FONT_FILE SYSTEM_DEFAULT_FONT_FILE
78 #ifndef DEFAULT_FAMILY
79 #define DEFAULT_FAMILY SYSTEM_DEFAULT_FAMILY
82 #ifndef DEFAULT_MONOSPACE_FONT_FILE
83 #define DEFAULT_MONOSPACE_FONT_FILE SYSTEM_DEFAULT_MONOSPACE_FONT_FILE
86 #ifndef DEFAULT_MONOSPACE_FAMILY
87 #define DEFAULT_MONOSPACE_FAMILY SYSTEM_DEFAULT_MONOSPACE_FAMILY
91 #include <freetype/ftsynth.h>
92 #include FT_FREETYPE_H
96 #define FT_FLOOR(X) ((X & -64) >> 6)
97 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
99 #define FT_MulFix(v, s) (((v)*(s))>>16)
104 #include <TargetConditionals.h>
105 #if !TARGET_OS_IPHONE
106 #include <Carbon/Carbon.h>
108 #include <sys/param.h> /* for MAXPATHLEN */
109 #undef HAVE_FONTCONFIG
114 #if defined(HAVE_FRIBIDI)
115 # include <fribidi/fribidi.h>
120 # include <windows.h>
123 # undef HAVE_FONTCONFIG
124 # include <vlc_charset.h> /* FromT */
128 #ifdef HAVE_FONTCONFIG
129 # include <fontconfig/fontconfig.h>
135 #include "text_renderer.h"
137 /*****************************************************************************
139 *****************************************************************************/
140 static int Create ( vlc_object_t * );
141 static void Destroy( vlc_object_t * );
143 #define FONT_TEXT N_("Font")
144 #define MONOSPACE_FONT_TEXT N_("Monospace Font")
146 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
147 #define FONT_LONGTEXT N_("Font file for the font you want to use")
149 #define FONTSIZE_TEXT N_("Font size in pixels")
150 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
151 "that will be rendered on the video. " \
152 "If set to something different than 0 this option will override the " \
153 "relative font size." )
154 #define OPACITY_TEXT N_("Text opacity")
155 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
156 "text that will be rendered on the video. 0 = transparent, " \
157 "255 = totally opaque. " )
158 #define COLOR_TEXT N_("Text default color")
159 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
160 "the video. This must be an hexadecimal (like HTML colors). The first two "\
161 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
162 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
163 #define FONTSIZER_TEXT N_("Relative font size")
164 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
165 "fonts that will be rendered on the video. If absolute font size is set, "\
166 "relative size will be overridden." )
167 #define BOLD_TEXT N_("Force bold")
169 #define BG_OPACITY_TEXT N_("Background opacity")
170 #define BG_COLOR_TEXT N_("Background color")
172 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
173 #define OUTLINE_COLOR_TEXT N_("Outline color")
174 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
176 #define SHADOW_OPACITY_TEXT N_("Shadow opacity")
177 #define SHADOW_COLOR_TEXT N_("Shadow color")
178 #define SHADOW_ANGLE_TEXT N_("Shadow angle")
179 #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
182 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
183 static const char *const ppsz_sizes_text[] = {
184 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
185 #define YUVP_TEXT N_("Use YUVP renderer")
186 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
187 "This option is only needed if you want to encode into DVB subtitles" )
189 static const int pi_color_values[] = {
190 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
191 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
192 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
194 static const char *const ppsz_color_descriptions[] = {
195 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
196 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
197 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
199 static const int pi_outline_thickness[] = {
202 static const char *const ppsz_outline_thickness[] = {
203 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
207 set_shortname( N_("Text renderer"))
208 set_description( N_("Freetype2 font renderer") )
209 set_category( CAT_VIDEO )
210 set_subcategory( SUBCAT_VIDEO_SUBPIC )
213 add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
214 add_font( "freetype-monofont", DEFAULT_MONOSPACE_FAMILY, MONOSPACE_FONT_TEXT, FAMILY_LONGTEXT, false )
216 add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
217 add_loadfile( "freetype-monofont", DEFAULT_MONOSPACE_FONT_FILE, MONOSPACE_FONT_TEXT, FONT_LONGTEXT, false )
220 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
221 FONTSIZE_LONGTEXT, true )
222 change_integer_range( 0, 4096)
225 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
226 FONTSIZER_LONGTEXT, false )
227 change_integer_list( pi_sizes, ppsz_sizes_text )
230 /* opacity valid on 0..255, with default 255 = fully opaque */
231 add_integer_with_range( "freetype-opacity", 255, 0, 255,
232 OPACITY_TEXT, OPACITY_LONGTEXT, false )
235 /* hook to the color values list, with default 0x00ffffff = white */
236 add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
237 COLOR_LONGTEXT, false )
238 change_integer_list( pi_color_values, ppsz_color_descriptions )
241 add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
244 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
245 BG_OPACITY_TEXT, NULL, false )
247 add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
249 change_integer_list( pi_color_values, ppsz_color_descriptions )
252 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
253 OUTLINE_OPACITY_TEXT, NULL, false )
255 add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
257 change_integer_list( pi_color_values, ppsz_color_descriptions )
259 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
261 change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
264 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
265 SHADOW_OPACITY_TEXT, NULL, false )
267 add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
269 change_integer_list( pi_color_values, ppsz_color_descriptions )
271 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
272 SHADOW_ANGLE_TEXT, NULL, false )
274 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
275 SHADOW_DISTANCE_TEXT, NULL, false )
278 add_obsolete_integer( "freetype-effect" );
280 add_bool( "freetype-yuvp", false, YUVP_TEXT,
281 YUVP_LONGTEXT, true )
282 set_capability( "text renderer", 100 )
283 add_shortcut( "text" )
284 set_callbacks( Create, Destroy )
288 /*****************************************************************************
290 *****************************************************************************/
294 FT_BitmapGlyph p_glyph;
295 FT_BitmapGlyph p_outline;
296 FT_BitmapGlyph p_shadow;
297 uint32_t i_color; /* ARGB color */
298 int i_line_offset; /* underline/strikethrough offset */
299 int i_line_thickness; /* underline/strikethrough thickness */
302 typedef struct line_desc_t line_desc_t;
310 int i_character_count;
311 line_character_t *p_character;
314 /*****************************************************************************
315 * filter_sys_t: freetype local data
316 *****************************************************************************
317 * This structure is part of the video output thread descriptor.
318 * It describes the freetype specific properties of an output thread.
319 *****************************************************************************/
322 FT_Library p_library; /* handle to library */
323 FT_Face p_face; /* handle to face object */
324 FT_Stroker p_stroker; /* handle to path stroker object */
326 xml_reader_t *p_xml; /* vlc xml parser */
328 text_style_t style; /* Current Style */
331 float f_shadow_vector_x;
332 float f_shadow_vector_y;
333 int i_default_font_size;
336 input_attachment_t **pp_font_attachments;
337 int i_font_attachments;
339 /* Cache the Win32 font folder */
341 char* psz_win_fonts_path;
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_InheritInteger( p_filter, "freetype-rel-fontsize" );
420 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
425 msg_Warn( p_filter, "invalid fontsize, using 12" );
431 static int SetFontSize( filter_t *p_filter, int i_size )
433 filter_sys_t *p_sys = p_filter->p_sys;
437 i_size = GetFontSize( p_filter );
439 msg_Dbg( p_filter, "using fontsize: %i", i_size );
442 p_sys->style.i_font_size = i_size;
444 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
446 msg_Err( p_filter, "couldn't set font size to %d", i_size );
454 #ifdef HAVE_FONTCONFIG
455 static void FontConfig_BuildCache( filter_t *p_filter )
458 msg_Dbg( p_filter, "Building font databases.");
466 #if defined( _WIN32 ) || defined( __APPLE__ )
467 dialog_progress_bar_t *p_dialog = NULL;
468 FcConfig *fcConfig = FcInitLoadConfig();
470 p_dialog = dialog_ProgressCreate( p_filter,
471 _("Building font cache"),
472 _("Please wait while your font cache is rebuilt.\n"
473 "This should take less than a few minutes."), NULL );
476 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
478 FcConfigBuildFonts( fcConfig );
479 #if defined( __APPLE__ )
480 // By default, scan only the directory /System/Library/Fonts.
481 // So build the set of available fonts under another directories,
482 // and add the set to the current configuration.
483 FcConfigAppFontAddDir( NULL, "~/Library/Fonts" );
484 FcConfigAppFontAddDir( NULL, "/Library/Fonts" );
485 FcConfigAppFontAddDir( NULL, "/Network/Library/Fonts" );
486 //FcConfigAppFontAddDir( NULL, "/System/Library/Fonts" );
490 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
491 dialog_ProgressDestroy( p_dialog );
496 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
500 * \brief Selects a font matching family, bold, italic provided
502 static char* FontConfig_Select( FcConfig* config, const char* family,
503 bool b_bold, bool b_italic, int i_size, int *i_idx )
505 FcResult result = FcResultMatch;
506 FcPattern *pat, *p_pat;
511 /* Create a pattern and fills it */
512 pat = FcPatternCreate();
513 if (!pat) return NULL;
516 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
517 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
518 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
519 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
523 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
525 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
526 free( psz_fontsize );
531 FcDefaultSubstitute( pat );
532 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
534 FcPatternDestroy( pat );
538 /* Find the best font for the pattern, destroy the pattern */
539 p_pat = FcFontMatch( config, pat, &result );
540 FcPatternDestroy( pat );
541 if( !p_pat || result == FcResultNoMatch ) return NULL;
543 /* Check the new pattern */
544 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
545 || ( val_b != FcTrue ) )
547 FcPatternDestroy( p_pat );
550 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
555 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
557 FcPatternDestroy( p_pat );
561 /* if( strcasecmp((const char*)val_s, family ) != 0 )
562 msg_Warn( p_filter, "fontconfig: selected font family is not"
563 "the requested one: '%s' != '%s'\n",
564 (const char*)val_s, family ); */
566 if( FcResultMatch == FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
567 ret = strdup( (const char*)val_s );
569 FcPatternDestroy( p_pat );
575 #define FONT_DIR_NT _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts")
577 static int GetFileFontByName( LPCTSTR font_name, char **psz_filename )
580 TCHAR vbuffer[MAX_PATH];
583 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
587 char *font_name_temp = FromT( font_name );
588 size_t fontname_len = strlen( font_name_temp );
590 for( int index = 0;; index++ )
592 DWORD vbuflen = MAX_PATH - 1;
595 LONG i_result = RegEnumValue( hKey, index, vbuffer, &vbuflen,
596 NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
597 if( i_result != ERROR_SUCCESS )
603 char *psz_value = FromT( vbuffer );
605 char *s = strchr( psz_value,'(' );
606 if( s != NULL && s != psz_value ) s[-1] = '\0';
608 /* Manage concatenated font names */
609 if( strchr( psz_value, '&') ) {
610 if( strcasestr( psz_value, font_name_temp ) != NULL )
617 if( strncasecmp( psz_value, font_name_temp, fontname_len ) == 0 )
627 *psz_filename = FromT( dbuffer );
628 free( font_name_temp );
634 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
635 DWORD type, LPARAM lParam)
637 VLC_UNUSED( metric );
638 if( (type & RASTER_FONTTYPE) ) return 1;
639 // if( lpelfe->elfScript ) FIXME
641 return GetFileFontByName( (LPCTSTR)lpelfe->elfFullName, (char **)lParam );
644 static char* Win32_Select( filter_t *p_filter, const char* family,
645 bool b_bold, bool b_italic, int i_size, int *i_idx )
647 VLC_UNUSED( i_size );
649 if( !family || strlen( family ) < 1 )
654 lf.lfCharSet = DEFAULT_CHARSET;
658 lf.lfWeight = FW_BOLD;
660 LPTSTR psz_fbuffer = ToT( family );
661 _tcsncpy( (LPTSTR)&lf.lfFaceName, psz_fbuffer, LF_FACESIZE );
665 char *psz_filename = NULL;
666 HDC hDC = GetDC( NULL );
667 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
668 ReleaseDC(NULL, hDC);
671 if( psz_filename != NULL )
673 /* FIXME: increase i_idx, when concatenated strings */
676 /* Prepend the Windows Font path, when only a filename was provided */
677 if( strchr( psz_filename, DIR_SEP_CHAR ) )
682 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
684 free( psz_filename );
687 free( psz_filename );
691 else /* Let's take any font we can */
695 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, "arial.ttf" ) == -1 )
704 #if !TARGET_OS_IPHONE
705 static char* MacLegacy_Select( filter_t *p_filter, const char* psz_fontname,
706 bool b_bold, bool b_italic, int i_size, int *i_idx )
708 VLC_UNUSED( b_bold );
709 VLC_UNUSED( b_italic );
710 VLC_UNUSED( i_size );
712 unsigned char path[MAXPATHLEN];
715 CFStringRef cf_fontName;
716 ATSFontRef ats_font_id;
720 if( psz_fontname == NULL )
723 msg_Dbg( p_filter, "looking for %s", psz_fontname );
724 cf_fontName = CFStringCreateWithCString( kCFAllocatorDefault, psz_fontname, kCFStringEncodingUTF8 );
726 ats_font_id = ATSFontFindFromName( cf_fontName, kATSOptionFlagsIncludeDisabledMask );
728 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
730 msg_Dbg( p_filter, "ATS couldn't find %s by name, checking family", psz_fontname );
731 ats_font_id = ATSFontFamilyFindFromName( cf_fontName, kATSOptionFlagsDefault );
733 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
735 msg_Dbg( p_filter, "ATS couldn't find either %s nor its family, checking PS name", psz_fontname );
736 ats_font_id = ATSFontFindFromPostScriptName( cf_fontName, kATSOptionFlagsDefault );
738 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
740 msg_Err( p_filter, "ATS couldn't find %s (no font name, family or PS name)", psz_fontname );
741 CFRelease( cf_fontName );
746 CFRelease( cf_fontName );
748 if ( noErr != ATSFontGetFileReference( ats_font_id, &ref ) )
750 msg_Err( p_filter, "ATS couldn't get file ref for %s", psz_fontname );
754 /* i_idx calculation by searching preceding fontIDs */
755 /* with same FSRef */
757 ATSFontRef id2 = ats_font_id - 1;
762 if ( noErr != ATSFontGetFileReference( id2, &ref2 ) )
764 if ( noErr != FSCompareFSRefs( &ref, &ref2 ) )
769 *i_idx = ats_font_id - ( id2 + 1 );
772 if ( noErr != FSRefMakePath( &ref, path, sizeof(path) ) )
774 msg_Err( p_filter, "failure when getting path from FSRef" );
777 msg_Dbg( p_filter, "found %s", path );
779 psz_path = strdup( (char *)path );
786 #endif /* HAVE_STYLES */
789 /*****************************************************************************
790 * RenderYUVP: place string in picture
791 *****************************************************************************
792 * This function merges the previously rendered freetype glyphs into a picture
793 *****************************************************************************/
794 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
798 VLC_UNUSED(p_filter);
799 static const uint8_t pi_gamma[16] =
800 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
801 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
805 int i, x, y, i_pitch;
806 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
808 /* Create a new subpicture region */
809 video_format_Init( &fmt, VLC_CODEC_YUVP );
811 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
813 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
815 assert( !p_region->p_picture );
816 p_region->p_picture = picture_NewFromFormat( &fmt );
817 if( !p_region->p_picture )
819 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
822 /* Calculate text color components
823 * Only use the first color */
824 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
825 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
828 fmt.p_palette->i_entries = 16;
829 for( i = 0; i < 8; i++ )
831 fmt.p_palette->palette[i][0] = 0;
832 fmt.p_palette->palette[i][1] = 0x80;
833 fmt.p_palette->palette[i][2] = 0x80;
834 fmt.p_palette->palette[i][3] = pi_gamma[i];
835 fmt.p_palette->palette[i][3] =
836 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
838 for( i = 8; i < fmt.p_palette->i_entries; i++ )
840 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
841 fmt.p_palette->palette[i][1] = i_u;
842 fmt.p_palette->palette[i][2] = i_v;
843 fmt.p_palette->palette[i][3] = pi_gamma[i];
844 fmt.p_palette->palette[i][3] =
845 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
848 p_dst = p_region->p_picture->Y_PIXELS;
849 i_pitch = p_region->p_picture->Y_PITCH;
851 /* Initialize the region pixels */
852 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
854 for( ; p_line != NULL; p_line = p_line->p_next )
856 int i_align_left = 0;
857 if( p_line->i_width < (int)fmt.i_visible_width )
859 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
860 i_align_left = ( fmt.i_visible_width - p_line->i_width );
861 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
862 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
866 for( i = 0; i < p_line->i_character_count; i++ )
868 const line_character_t *ch = &p_line->p_character[i];
869 FT_BitmapGlyph p_glyph = ch->p_glyph;
871 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
872 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
874 for( y = 0; y < p_glyph->bitmap.rows; y++ )
876 for( x = 0; x < p_glyph->bitmap.width; x++ )
878 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
879 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
880 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
886 /* Outlining (find something better than nearest neighbour filtering ?) */
889 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
890 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
891 uint8_t left, current;
893 for( y = 1; y < (int)fmt.i_height - 1; y++ )
895 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
896 p_dst += p_region->p_picture->Y_PITCH;
899 for( x = 1; x < (int)fmt.i_width - 1; x++ )
902 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
903 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;
907 memset( p_top, 0, fmt.i_width );
913 /*****************************************************************************
914 * RenderYUVA: place string in picture
915 *****************************************************************************
916 * This function merges the previously rendered freetype glyphs into a picture
917 *****************************************************************************/
918 static void FillYUVAPicture( picture_t *p_picture,
919 int i_a, int i_y, int i_u, int i_v )
921 memset( p_picture->p[0].p_pixels, i_y,
922 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
923 memset( p_picture->p[1].p_pixels, i_u,
924 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
925 memset( p_picture->p[2].p_pixels, i_v,
926 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
927 memset( p_picture->p[3].p_pixels, i_a,
928 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
931 static inline void BlendYUVAPixel( picture_t *p_picture,
932 int i_picture_x, int i_picture_y,
933 int i_a, int i_y, int i_u, int i_v,
936 int i_an = i_a * i_alpha / 255;
938 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
939 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
940 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
941 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
953 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
956 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
957 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
958 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
963 static void FillRGBAPicture( picture_t *p_picture,
964 int i_a, int i_r, int i_g, int i_b )
966 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
968 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
970 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
979 static inline void BlendRGBAPixel( picture_t *p_picture,
980 int i_picture_x, int i_picture_y,
981 int i_a, int i_r, int i_g, int i_b,
984 int i_an = i_a * i_alpha / 255;
986 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
988 int i_ao = p_rgba[3];
998 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
1001 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
1002 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
1003 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
1008 static inline void BlendAXYZGlyph( picture_t *p_picture,
1009 int i_picture_x, int i_picture_y,
1010 int i_a, int i_x, int i_y, int i_z,
1011 FT_BitmapGlyph p_glyph,
1012 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1015 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
1017 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
1018 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
1020 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
1024 static inline void BlendAXYZLine( picture_t *p_picture,
1025 int i_picture_x, int i_picture_y,
1026 int i_a, int i_x, int i_y, int i_z,
1027 const line_character_t *p_current,
1028 const line_character_t *p_next,
1029 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1031 int i_line_width = p_current->p_glyph->bitmap.width;
1033 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
1035 for( int dx = 0; dx < i_line_width; dx++ )
1037 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
1038 BlendPixel( p_picture,
1040 i_picture_y + p_current->i_line_offset + dy,
1041 i_a, i_x, i_y, i_z, 0xff );
1045 static inline void RenderBackground( subpicture_region_t *p_region,
1046 line_desc_t *p_line_head,
1049 picture_t *p_picture,
1051 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
1052 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1054 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
1056 int i_align_left = i_margin;
1057 int i_align_top = i_margin;
1060 unsigned line_top = 0;
1061 int line_bottom = 0;
1064 if( p_line->i_width < i_text_width )
1066 /* Left offset to take into account alignment */
1067 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1068 i_align_left += ( i_text_width - p_line->i_width );
1069 else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
1070 i_align_left = i_margin; /* Keep it the way it is */
1071 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1072 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1075 /* Find the tallest character in the line */
1076 for( int i = 0; i < p_line->i_character_count; i++ ) {
1077 const line_character_t *ch = &p_line->p_character[i];
1078 FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
1079 if (p_glyph->top > max_height)
1080 max_height = p_glyph->top;
1083 /* Compute the background for the line (identify leading/trailing space) */
1084 for( int i = 0; i < p_line->i_character_count; i++ ) {
1085 const line_character_t *ch = &p_line->p_character[i];
1086 FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
1087 if (p_glyph && p_glyph->bitmap.rows > 0) {
1088 // Found a non-whitespace character
1089 line_start = i_align_left + p_glyph->left - p_bbox->xMin;
1094 /* Fudge factor to make sure caption background edges are left aligned
1095 despite variable font width */
1096 if (line_start < 12)
1099 /* Find right boundary for bounding box for background */
1100 for( int i = p_line->i_character_count; i > 0; i-- ) {
1101 const line_character_t *ch = &p_line->p_character[i - 1];
1102 FT_BitmapGlyph p_glyph = ch->p_shadow ? ch->p_shadow : ch->p_glyph;
1103 if (p_glyph && p_glyph->bitmap.rows > 0) {
1104 // Found a non-whitespace character
1105 line_end = i_align_left + p_glyph->left - p_bbox->xMin + p_glyph->bitmap.width;
1110 /* Setup color for the background */
1111 uint8_t i_x, i_y, i_z;
1112 ExtractComponents( 0x000000, &i_x, &i_y, &i_z );
1114 /* Compute the upper boundary for the background */
1115 if ((i_align_top + p_line->i_base_line - max_height) < 0)
1116 line_top = i_align_top + p_line->i_base_line;
1118 line_top = i_align_top + p_line->i_base_line - max_height;
1120 /* Compute lower boundary for the background */
1121 line_bottom = __MIN(line_top + p_line->i_height, p_region->fmt.i_visible_height);
1123 /* Render the actual background */
1124 for( int dy = line_top; dy < line_bottom; dy++ )
1126 for( int dx = line_start; dx < line_end; dx++ )
1127 BlendPixel( p_picture, dx, dy, 0xff, i_x, i_y, i_z, 0xff );
1132 static inline int RenderAXYZ( filter_t *p_filter,
1133 subpicture_region_t *p_region,
1134 line_desc_t *p_line_head,
1137 vlc_fourcc_t i_chroma,
1138 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
1139 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
1140 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1142 filter_sys_t *p_sys = p_filter->p_sys;
1144 /* Create a new subpicture region */
1145 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
1146 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
1148 video_format_Init( &fmt, i_chroma );
1150 fmt.i_visible_width = i_text_width + 2 * i_margin;
1152 fmt.i_visible_height = i_text_height + 2 * i_margin;
1154 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
1155 if( !p_region->p_picture )
1156 return VLC_EGENERIC;
1157 p_region->fmt = fmt;
1159 /* Initialize the picture background */
1160 uint8_t i_a = var_InheritInteger( p_filter, "freetype-background-opacity" );
1161 i_a = VLC_CLIP( i_a, 0, 255 );
1162 uint8_t i_x, i_y, i_z;
1164 if (p_region->b_renderbg) {
1165 /* Render the background just under the text */
1166 FillPicture( p_picture, 0x00, 0x00, 0x00, 0x00 );
1167 RenderBackground(p_region, p_line_head, p_bbox, i_margin, p_picture, i_text_width,
1168 ExtractComponents, BlendPixel);
1170 /* Render background under entire subpicture block */
1171 int i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
1172 i_background_color = VLC_CLIP( i_background_color, 0, 0xFFFFFF );
1173 ExtractComponents( i_background_color, &i_x, &i_y, &i_z );
1174 FillPicture( p_picture, i_a, i_x, i_y, i_z );
1177 /* Render shadow then outline and then normal glyphs */
1178 for( int g = 0; g < 3; g++ )
1180 /* Render all lines */
1181 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
1183 int i_align_left = i_margin;
1184 if( p_line->i_width < i_text_width )
1186 /* Left offset to take into account alignment */
1187 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1188 i_align_left += ( i_text_width - p_line->i_width );
1189 else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
1190 i_align_left = i_margin; /* Keep it the way it is */
1191 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1192 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1194 int i_align_top = i_margin;
1196 /* Render all glyphs and underline/strikethrough */
1197 for( int i = 0; i < p_line->i_character_count; i++ )
1199 const line_character_t *ch = &p_line->p_character[i];
1200 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
1204 i_a = (ch->i_color >> 24) & 0xff;
1208 i_a = i_a * p_sys->style.i_shadow_alpha / 255;
1209 i_color = p_sys->style.i_shadow_color;
1212 i_a = i_a * p_sys->style.i_outline_alpha / 255;
1213 i_color = p_sys->style.i_outline_color;
1216 i_color = ch->i_color;
1219 ExtractComponents( i_color, &i_x, &i_y, &i_z );
1221 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
1222 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
1224 BlendAXYZGlyph( p_picture,
1225 i_glyph_x, i_glyph_y,
1230 /* underline/strikethrough are only rendered for the normal glyph */
1231 if( g == 2 && ch->i_line_thickness > 0 )
1232 BlendAXYZLine( p_picture,
1233 i_glyph_x, i_glyph_y + p_glyph->top,
1236 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
1247 static void FreeLine( line_desc_t *p_line )
1249 for( int i = 0; i < p_line->i_character_count; i++ )
1251 line_character_t *ch = &p_line->p_character[i];
1252 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1254 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1256 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1259 free( p_line->p_character );
1263 static void FreeLines( line_desc_t *p_lines )
1265 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1267 line_desc_t *p_next = p_line->p_next;
1273 static line_desc_t *NewLine( int i_count )
1275 line_desc_t *p_line = malloc( sizeof(*p_line) );
1280 p_line->p_next = NULL;
1281 p_line->i_width = 0;
1282 p_line->i_base_line = 0;
1283 p_line->i_character_count = 0;
1285 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1286 if( !p_line->p_character )
1294 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1296 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1298 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1300 FT_Face p_face = NULL;
1302 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1310 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1311 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1312 if( p_face->family_name != NULL
1313 && !strcasecmp( p_face->family_name, p_style->psz_fontname )
1314 && (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC))
1315 == i_style_received )
1318 FT_Done_Face( p_face );
1326 static FT_Face LoadFace( filter_t *p_filter,
1327 const text_style_t *p_style )
1329 filter_sys_t *p_sys = p_filter->p_sys;
1331 /* Look for a match amongst our attachments first */
1332 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1334 /* Load system wide font otheriwse */
1338 char *psz_fontfile = NULL;
1339 #ifdef HAVE_FONTCONFIG
1340 psz_fontfile = FontConfig_Select( NULL,
1341 p_style->psz_fontname,
1342 (p_style->i_style_flags & STYLE_BOLD) != 0,
1343 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1346 #elif defined( __APPLE__ )
1347 #if !TARGET_OS_IPHONE
1348 psz_fontfile = MacLegacy_Select( p_filter, p_style->psz_fontname, false, false, -1, &i_idx );
1350 #elif defined( _WIN32 )
1351 psz_fontfile = Win32_Select( p_filter,
1352 p_style->psz_fontname,
1353 (p_style->i_style_flags & STYLE_BOLD) != 0,
1354 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1358 psz_fontfile = NULL;
1363 if( *psz_fontfile == '\0' )
1366 "We were not able to find a matching font: \"%s\" (%s %s),"
1367 " so using default font",
1368 p_style->psz_fontname,
1369 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1370 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1375 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1378 free( psz_fontfile );
1383 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1385 /* We've loaded a font face which is unhelpful for actually
1386 * rendering text - fallback to the default one.
1388 FT_Done_Face( p_face );
1394 static int GetGlyph( filter_t *p_filter,
1395 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1396 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1397 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1403 FT_Vector *p_pen_shadow )
1405 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1406 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1408 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1409 return VLC_EGENERIC;
1412 /* Do synthetic styling now that Freetype supports it;
1413 * ie. if the font we have loaded is NOT already in the
1414 * style that the tags want, then switch it on; if they
1415 * are then don't. */
1416 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1417 FT_GlyphSlot_Embolden( p_face->glyph );
1418 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1419 FT_GlyphSlot_Oblique( p_face->glyph );
1422 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1424 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1425 return VLC_EGENERIC;
1428 FT_Glyph outline = NULL;
1429 if( p_filter->p_sys->p_stroker )
1432 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1436 FT_Glyph shadow = NULL;
1437 if( p_filter->p_sys->style.i_shadow_alpha > 0 )
1439 shadow = outline ? outline : glyph;
1440 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1446 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1449 *pp_shadow = shadow;
1451 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1453 FT_Done_Glyph( glyph );
1455 FT_Done_Glyph( outline );
1457 FT_Done_Glyph( shadow );
1458 return VLC_EGENERIC;
1460 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1465 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1466 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1468 *pp_outline = outline;
1473 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1475 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1476 if( p_bbox->xMin >= p_bbox->xMax )
1478 p_bbox->xMin = FT_CEIL(p_pen->x);
1479 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1480 glyph_bmp->left = p_bbox->xMin;
1482 if( p_bbox->yMin >= p_bbox->yMax )
1484 p_bbox->yMax = FT_CEIL(p_pen->y);
1485 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1486 glyph_bmp->top = p_bbox->yMax;
1490 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1492 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1493 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1494 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1495 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1498 static int ProcessLines( filter_t *p_filter,
1499 line_desc_t **pp_lines,
1501 int *pi_max_face_height,
1503 uni_char_t *psz_text,
1504 text_style_t **pp_styles,
1505 uint32_t *pi_k_dates,
1508 filter_sys_t *p_sys = p_filter->p_sys;
1509 uni_char_t *p_fribidi_string = NULL;
1510 text_style_t **pp_fribidi_styles = NULL;
1511 int *p_new_positions = NULL;
1513 #if defined(HAVE_FRIBIDI)
1515 int *p_old_positions;
1516 int start_pos, pos = 0;
1518 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1520 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1521 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1522 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1524 if( ! pp_fribidi_styles ||
1525 ! p_fribidi_string ||
1526 ! p_old_positions ||
1529 free( p_old_positions );
1530 free( p_new_positions );
1531 free( p_fribidi_string );
1532 free( pp_fribidi_styles );
1536 /* Do bidi conversion line-by-line */
1539 while(pos < i_len) {
1540 if (psz_text[pos] != '\n')
1542 p_fribidi_string[pos] = psz_text[pos];
1543 pp_fribidi_styles[pos] = pp_styles[pos];
1544 p_new_positions[pos] = pos;
1548 while(pos < i_len) {
1549 if (psz_text[pos] == '\n')
1553 if (pos > start_pos)
1555 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1556 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1558 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1560 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1561 pos - start_pos, &base_dir,
1562 (FriBidiChar*)p_fribidi_string + start_pos,
1563 p_new_positions + start_pos,
1566 for( int j = start_pos; j < pos; j++ )
1568 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1569 p_new_positions[ j ] += start_pos;
1573 p_fribidi_string[ i_len ] = 0;
1574 free( p_old_positions );
1576 pp_styles = pp_fribidi_styles;
1577 psz_text = p_fribidi_string;
1580 /* Work out the karaoke */
1581 uint8_t *pi_karaoke_bar = NULL;
1584 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1585 if( pi_karaoke_bar )
1587 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1588 for( int i = 0; i < i_len; i++ )
1590 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1591 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1595 free( p_new_positions );
1597 *pi_max_face_height = 0;
1599 line_desc_t **pp_line_next = pp_lines;
1607 int i_face_height_previous = 0;
1608 int i_base_line = 0;
1609 const text_style_t *p_previous_style = NULL;
1610 FT_Face p_face = NULL;
1611 for( int i_start = 0; i_start < i_len; )
1613 /* Compute the length of the current text line */
1615 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
1618 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1619 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
1620 int i_index = i_start;
1625 int i_face_height = 0;
1626 FT_BBox line_bbox = {
1632 int i_ul_offset = 0;
1633 int i_ul_thickness = 0;
1642 break_point_t break_point;
1643 break_point_t break_point_fallback;
1645 #define SAVE_BP(dst) do { \
1646 dst.i_index = i_index; \
1648 dst.line_bbox = line_bbox; \
1649 dst.i_face_height = i_face_height; \
1650 dst.i_ul_offset = i_ul_offset; \
1651 dst.i_ul_thickness = i_ul_thickness; \
1654 SAVE_BP( break_point );
1655 SAVE_BP( break_point_fallback );
1657 while( i_index < i_start + i_length )
1659 /* Split by common FT_Face + Size */
1660 const text_style_t *p_current_style = pp_styles[i_index];
1661 int i_part_length = 0;
1662 while( i_index + i_part_length < i_start + i_length )
1664 const text_style_t *p_style = pp_styles[i_index + i_part_length];
1665 if( !FaceStyleEquals( p_style, p_current_style ) ||
1666 p_style->i_font_size != p_current_style->i_font_size )
1671 /* (Re)load/reconfigure the face if needed */
1672 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
1675 FT_Done_Face( p_face );
1676 p_previous_style = NULL;
1678 p_face = LoadFace( p_filter, p_current_style );
1680 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
1681 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
1683 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
1684 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
1685 if( p_sys->p_stroker )
1687 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
1688 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
1689 int i_radius = (p_current_style->i_font_size << 6) * f_outline_thickness;
1690 FT_Stroker_Set( p_sys->p_stroker,
1692 FT_STROKER_LINECAP_ROUND,
1693 FT_STROKER_LINEJOIN_ROUND, 0 );
1696 p_previous_style = p_current_style;
1698 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
1699 p_current_face->size->metrics.y_scale)));
1701 /* Render the part */
1702 bool b_break_line = false;
1703 int i_glyph_last = 0;
1704 while( i_part_length > 0 )
1706 const text_style_t *p_glyph_style = pp_styles[i_index];
1707 uni_char_t character = psz_text[i_index];
1708 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
1710 /* Get kerning vector */
1711 FT_Vector kerning = { .x = 0, .y = 0 };
1712 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
1713 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
1715 /* Get the glyph bitmap and its bounding box and all the associated properties */
1716 FT_Vector pen_new = {
1717 .x = pen.x + kerning.x,
1718 .y = pen.y + kerning.y,
1720 FT_Vector pen_shadow_new = {
1721 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
1722 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
1727 FT_BBox outline_bbox;
1729 FT_BBox shadow_bbox;
1731 if( GetGlyph( p_filter,
1732 &glyph, &glyph_bbox,
1733 &outline, &outline_bbox,
1734 &shadow, &shadow_bbox,
1735 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
1736 &pen_new, &pen_shadow_new ) )
1739 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
1741 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
1743 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
1745 /* FIXME and what about outline */
1747 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
1748 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
1749 (p_glyph_style->i_karaoke_background_alpha << 24))
1750 : (p_glyph_style->i_font_color |
1751 (p_glyph_style->i_font_alpha << 24));
1752 int i_line_offset = 0;
1753 int i_line_thickness = 0;
1754 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1756 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
1757 p_current_face->size->metrics.y_scale)) );
1759 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
1760 p_current_face->size->metrics.y_scale)) );
1762 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
1764 /* Move the baseline to make it strikethrough instead of
1765 * underline. That means that strikethrough takes precedence
1767 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
1768 p_current_face->size->metrics.y_scale)) );
1770 else if( i_line_thickness > 0 )
1772 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
1774 /* The real underline thickness and position are
1775 * updated once the whole line has been parsed */
1776 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
1777 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
1778 i_line_thickness = -1;
1781 FT_BBox line_bbox_new = line_bbox;
1782 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
1784 BBoxEnlarge( &line_bbox_new, &outline_bbox );
1786 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
1788 b_break_line = i_index > i_start &&
1789 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
1792 FT_Done_Glyph( glyph );
1794 FT_Done_Glyph( outline );
1796 FT_Done_Glyph( shadow );
1798 break_point_t *p_bp = NULL;
1799 if( break_point.i_index > i_start )
1800 p_bp = &break_point;
1801 else if( break_point_fallback.i_index > i_start )
1802 p_bp = &break_point_fallback;
1806 msg_Dbg( p_filter, "Breaking line");
1807 for( int i = p_bp->i_index; i < i_index; i++ )
1809 line_character_t *ch = &p_line->p_character[i - i_start];
1810 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1812 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1814 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1816 p_line->i_character_count = p_bp->i_index - i_start;
1818 i_index = p_bp->i_index;
1820 line_bbox = p_bp->line_bbox;
1821 i_face_height = p_bp->i_face_height;
1822 i_ul_offset = p_bp->i_ul_offset;
1823 i_ul_thickness = p_bp->i_ul_thickness;
1827 msg_Err( p_filter, "Breaking unbreakable line");
1832 assert( p_line->i_character_count == i_index - i_start);
1833 p_line->p_character[p_line->i_character_count++] = (line_character_t){
1834 .p_glyph = (FT_BitmapGlyph)glyph,
1835 .p_outline = (FT_BitmapGlyph)outline,
1836 .p_shadow = (FT_BitmapGlyph)shadow,
1838 .i_line_offset = i_line_offset,
1839 .i_line_thickness = i_line_thickness,
1842 pen.x = pen_new.x + p_current_face->glyph->advance.x;
1843 pen.y = pen_new.y + p_current_face->glyph->advance.y;
1844 line_bbox = line_bbox_new;
1846 i_glyph_last = i_glyph_index;
1850 if( character == ' ' || character == '\t' )
1851 SAVE_BP( break_point );
1852 else if( character == 160 )
1853 SAVE_BP( break_point_fallback );
1859 /* Update our baseline */
1860 if( i_face_height_previous > 0 )
1861 i_base_line += __MAX(i_face_height, i_face_height_previous);
1862 if( i_face_height > 0 )
1863 i_face_height_previous = i_face_height;
1865 /* Update the line bbox with the actual base line */
1866 if (line_bbox.yMax > line_bbox.yMin) {
1867 line_bbox.yMin -= i_base_line;
1868 line_bbox.yMax -= i_base_line;
1870 BBoxEnlarge( &bbox, &line_bbox );
1872 /* Terminate and append the line */
1875 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
1876 p_line->i_base_line = i_base_line;
1877 p_line->i_height = __MAX(i_face_height, i_face_height_previous);
1878 if( i_ul_thickness > 0 )
1880 for( int i = 0; i < p_line->i_character_count; i++ )
1882 line_character_t *ch = &p_line->p_character[i];
1883 if( ch->i_line_thickness < 0 )
1885 ch->i_line_offset = i_ul_offset;
1886 ch->i_line_thickness = i_ul_thickness;
1891 *pp_line_next = p_line;
1892 pp_line_next = &p_line->p_next;
1895 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
1897 /* Skip what we have rendered and the line delimitor if present */
1899 if( i_start < i_len && psz_text[i_start] == '\n' )
1902 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
1904 msg_Err( p_filter, "Truncated too high subtitle" );
1909 FT_Done_Face( p_face );
1911 free( pp_fribidi_styles );
1912 free( p_fribidi_string );
1913 free( pi_karaoke_bar );
1920 * This function renders a text subpicture region into another one.
1921 * It also calculates the size needed for this string, and renders the
1922 * needed glyphs into memory. It is used as pf_add_string callback in
1923 * the vout method by this module
1925 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
1926 subpicture_region_t *p_region_in, bool b_html,
1927 const vlc_fourcc_t *p_chroma_list )
1929 filter_sys_t *p_sys = p_filter->p_sys;
1932 return VLC_EGENERIC;
1933 if( b_html && !p_region_in->psz_html )
1934 return VLC_EGENERIC;
1935 if( !b_html && !p_region_in->psz_text )
1936 return VLC_EGENERIC;
1938 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
1939 : p_region_in->psz_text );
1941 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
1942 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
1943 if( !psz_text || !pp_styles )
1947 return VLC_EGENERIC;
1950 /* Reset the default fontsize in case screen metrics have changed */
1951 p_filter->p_sys->style.i_font_size = GetFontSize( p_filter );
1954 int rv = VLC_SUCCESS;
1955 int i_text_length = 0;
1957 int i_max_face_height;
1958 line_desc_t *p_lines = NULL;
1960 uint32_t *pi_k_durations = NULL;
1964 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
1965 (uint8_t *) p_region_in->psz_html,
1966 strlen( p_region_in->psz_html ),
1968 if( unlikely(p_sub == NULL) )
1971 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
1973 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
1975 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
1976 p_filter->p_sys->p_xml = p_xml_reader;
1983 /* Look for Root Node */
1986 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
1988 if( strcasecmp( "karaoke", node ) == 0 )
1990 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
1992 else if( strcasecmp( "text", node ) != 0 )
1994 /* Only text and karaoke tags are supported */
1995 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2002 msg_Err( p_filter, "Malformed HTML subtitle" );
2008 rv = ProcessNodes( p_filter,
2009 psz_text, pp_styles, pi_k_durations, &i_text_length,
2010 p_xml_reader, p_region_in->p_style, &p_filter->p_sys->style );
2014 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2016 stream_Delete( p_sub );
2020 text_style_t *p_style;
2021 if( p_region_in->p_style )
2022 p_style = CreateStyle( p_region_in->p_style->psz_fontname ? p_region_in->p_style->psz_fontname
2023 : p_sys->style.psz_fontname,
2024 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2025 : p_sys->style.i_font_size,
2026 (p_region_in->p_style->i_font_color & 0xffffff) |
2027 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2029 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2035 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2036 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
2037 p_style = CreateStyle( p_sys->style.psz_fontname,
2038 p_sys->style.i_font_size,
2039 (i_font_color & 0xffffff) |
2040 ((p_sys->style.i_font_alpha & 0xff) << 24),
2043 if( p_sys->style.i_style_flags & STYLE_BOLD )
2044 p_style->i_style_flags |= STYLE_BOLD;
2046 i_text_length = SetupText( p_filter,
2050 p_region_in->psz_text, p_style, 0 );
2053 if( !rv && i_text_length > 0 )
2055 rv = ProcessLines( p_filter,
2056 &p_lines, &bbox, &i_max_face_height,
2057 psz_text, pp_styles, pi_k_durations, i_text_length );
2060 p_region_out->i_x = p_region_in->i_x;
2061 p_region_out->i_y = p_region_in->i_y;
2063 /* Don't attempt to render text that couldn't be layed out
2065 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2067 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2068 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2070 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2071 p_chroma_list = p_chroma_list_yuvp;
2072 else if( !p_chroma_list || *p_chroma_list == 0 )
2073 p_chroma_list = p_chroma_list_rgba;
2075 uint8_t i_background_opacity = var_InheritInteger( p_filter, "freetype-background-opacity" );
2076 i_background_opacity = VLC_CLIP( i_background_opacity, 0, 255 );
2077 const int i_margin = i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2078 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2081 if( *p_chroma == VLC_CODEC_YUVP )
2082 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2083 else if( *p_chroma == VLC_CODEC_YUVA )
2084 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2089 else if( *p_chroma == VLC_CODEC_RGBA )
2090 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2099 /* With karaoke, we're going to have to render the text a number
2100 * of times to show the progress marker on the text.
2102 if( pi_k_durations )
2103 var_SetBool( p_filter, "text-rerender", true );
2106 FreeLines( p_lines );
2109 for( int i = 0; i < i_text_length; i++ )
2111 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2112 text_style_Delete( pp_styles[i] );
2115 free( pi_k_durations );
2120 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2121 subpicture_region_t *p_region_in,
2122 const vlc_fourcc_t *p_chroma_list )
2124 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2127 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2128 subpicture_region_t *p_region_in,
2129 const vlc_fourcc_t *p_chroma_list )
2131 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2134 /*****************************************************************************
2135 * Create: allocates osd-text video thread output method
2136 *****************************************************************************
2137 * This function allocates and initializes a Clone vout method.
2138 *****************************************************************************/
2139 static int Create( vlc_object_t *p_this )
2141 filter_t *p_filter = (filter_t *)p_this;
2142 filter_sys_t *p_sys;
2143 char *psz_fontfile = NULL;
2144 char *psz_fontname = NULL;
2145 char *psz_monofontfile = NULL;
2146 char *psz_monofontfamily = NULL;
2147 int i_error = 0, fontindex = 0, monofontindex = 0;
2149 /* Allocate structure */
2150 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2154 p_sys->style.psz_fontname = NULL;
2155 p_sys->p_xml = NULL;
2157 p_sys->p_library = 0;
2158 p_sys->style.i_font_size = 0;
2161 * The following variables should not be cached, as they might be changed on-the-fly:
2162 * freetype-rel-fontsize, freetype-background-opacity, freetype-background-color,
2163 * freetype-outline-thickness, freetype-color
2167 psz_fontname = var_InheritString( p_filter, "freetype-font" );
2168 psz_monofontfamily = var_InheritString( p_filter, "freetype-monofont" );
2169 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2170 p_sys->style.i_font_alpha = var_InheritInteger( p_filter,"freetype-opacity" );
2171 p_sys->style.i_font_alpha = VLC_CLIP( p_sys->style.i_font_alpha, 0, 255 );
2172 if( var_InheritBool( p_filter, "freetype-bold" ) )
2173 p_sys->style.i_style_flags |= STYLE_BOLD;
2175 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2176 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
2177 p_sys->style.i_outline_alpha = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2178 p_sys->style.i_outline_alpha = VLC_CLIP( p_sys->style.i_outline_alpha, 0, 255 );
2179 p_sys->style.i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2180 p_sys->style.i_outline_color = VLC_CLIP( p_sys->style.i_outline_color, 0, 0xFFFFFF );
2182 p_sys->style.i_shadow_alpha = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2183 p_sys->style.i_shadow_alpha = VLC_CLIP( p_sys->style.i_shadow_alpha, 0, 255 );
2184 p_sys->style.i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2185 p_sys->style.i_shadow_color = VLC_CLIP( p_sys->style.i_shadow_color, 0, 0xFFFFFF );
2186 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2187 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2188 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
2189 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2190 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2193 /* Get Windows Font folder */
2194 wchar_t wdir[MAX_PATH];
2195 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2197 GetWindowsDirectoryW( wdir, MAX_PATH );
2198 wcscat( wdir, L"\\fonts" );
2200 p_sys->psz_win_fonts_path = FromWide( wdir );
2203 /* Set default psz_fontname */
2204 if( !psz_fontname || !*psz_fontname )
2206 free( psz_fontname );
2208 psz_fontname = strdup( DEFAULT_FAMILY );
2211 if( asprintf( &psz_fontname, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2213 psz_fontname = NULL;
2217 psz_fontname = strdup( DEFAULT_FONT_FILE );
2219 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontname );
2223 /* Set the current font file */
2224 p_sys->style.psz_fontname = psz_fontname;
2226 #ifdef HAVE_FONTCONFIG
2227 FontConfig_BuildCache( p_filter );
2230 psz_fontfile = FontConfig_Select( NULL, psz_fontname, false, false,
2231 p_sys->i_default_font_size, &fontindex );
2232 psz_monofontfile = FontConfig_Select( NULL, psz_monofontfamily, false,
2233 false, p_sys->i_default_font_size,
2235 #elif defined(__APPLE__)
2236 #if !TARGET_OS_IPHONE
2237 psz_fontfile = MacLegacy_Select( p_filter, psz_fontname, false, false, -1, &fontindex );
2239 #elif defined(_WIN32)
2240 psz_fontfile = Win32_Select( p_filter, psz_fontname, false, false, -1, &fontindex );
2243 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontname, psz_fontfile );
2245 /* If nothing is found, use the default family */
2247 psz_fontfile = strdup( psz_fontname );
2248 if( !psz_monofontfile )
2249 psz_monofontfile = strdup( psz_monofontfamily );
2251 #else /* !HAVE_STYLES */
2252 /* Use the default file */
2253 psz_fontfile = psz_fontname;
2254 psz_monofontfile = psz_monofontfamily;
2256 p_sys->style.psz_monofontname = psz_monofontfamily;
2259 i_error = FT_Init_FreeType( &p_sys->p_library );
2262 msg_Err( p_filter, "couldn't initialize freetype" );
2266 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2267 fontindex, &p_sys->p_face );
2269 if( i_error == FT_Err_Unknown_File_Format )
2271 msg_Err( p_filter, "file %s have unknown format",
2272 psz_fontfile ? psz_fontfile : "(null)" );
2277 msg_Err( p_filter, "failed to load font file %s",
2278 psz_fontfile ? psz_fontfile : "(null)" );
2282 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2285 msg_Err( p_filter, "font has no unicode translation table" );
2289 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2291 p_sys->p_stroker = NULL;
2292 if( f_outline_thickness > 0.001 )
2294 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2296 msg_Err( p_filter, "Failed to create stroker for outlining" );
2299 p_sys->pp_font_attachments = NULL;
2300 p_sys->i_font_attachments = 0;
2302 p_filter->pf_render_text = RenderText;
2303 p_filter->pf_render_html = RenderHtml;
2305 LoadFontsFromAttachments( p_filter );
2308 free( psz_fontfile );
2309 free( psz_monofontfile );
2315 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2316 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2318 free( psz_fontfile );
2319 free( psz_monofontfile );
2321 free( psz_fontname );
2322 free( psz_monofontfamily );
2324 return VLC_EGENERIC;
2327 /*****************************************************************************
2328 * Destroy: destroy Clone video thread output method
2329 *****************************************************************************
2330 * Clean up all data and library connections
2331 *****************************************************************************/
2332 static void Destroy( vlc_object_t *p_this )
2334 filter_t *p_filter = (filter_t *)p_this;
2335 filter_sys_t *p_sys = p_filter->p_sys;
2337 if( p_sys->pp_font_attachments )
2339 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2340 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2342 free( p_sys->pp_font_attachments );
2345 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2346 free( p_sys->style.psz_fontname );
2347 free( p_sys->style.psz_monofontname );
2350 free( p_sys->psz_win_fonts_path );
2353 /* FcFini asserts calling the subfunction FcCacheFini()
2354 * even if no other library functions have been made since FcInit(),
2355 * so don't call it. */
2357 if( p_sys->p_stroker )
2358 FT_Stroker_Done( p_sys->p_stroker );
2359 FT_Done_Face( p_sys->p_face );
2360 FT_Done_FreeType( p_sys->p_library );