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 );
369 /*****************************************************************************
370 * Make any TTF/OTF fonts present in the attachments of the media file
371 * and store them for later use by the FreeType Engine
372 *****************************************************************************/
373 static int LoadFontsFromAttachments( filter_t *p_filter )
375 filter_sys_t *p_sys = p_filter->p_sys;
376 input_attachment_t **pp_attachments;
377 int i_attachments_cnt;
379 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
382 p_sys->i_font_attachments = 0;
383 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
384 if( !p_sys->pp_font_attachments )
387 for( int k = 0; k < i_attachments_cnt; k++ )
389 input_attachment_t *p_attach = pp_attachments[k];
391 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
392 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
393 p_attach->i_data > 0 && p_attach->p_data )
395 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
399 vlc_input_attachment_Delete( p_attach );
402 free( pp_attachments );
407 static int GetFontSize( filter_t *p_filter )
409 filter_sys_t *p_sys = p_filter->p_sys;
412 if( p_sys->i_default_font_size )
414 i_size = p_sys->i_default_font_size;
418 int i_ratio = var_InheritInteger( p_filter, "freetype-rel-fontsize" );
421 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
426 msg_Warn( p_filter, "invalid fontsize, using 12" );
432 static int SetFontSize( filter_t *p_filter, int i_size )
434 filter_sys_t *p_sys = p_filter->p_sys;
438 i_size = GetFontSize( p_filter );
440 msg_Dbg( p_filter, "using fontsize: %i", i_size );
443 p_sys->style.i_font_size = i_size;
445 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
447 msg_Err( p_filter, "couldn't set font size to %d", i_size );
455 #ifdef HAVE_FONTCONFIG
456 static void FontConfig_BuildCache( filter_t *p_filter )
459 msg_Dbg( p_filter, "Building font databases.");
467 #if defined( _WIN32 ) || defined( __APPLE__ )
468 dialog_progress_bar_t *p_dialog = NULL;
469 FcConfig *fcConfig = FcInitLoadConfig();
471 p_dialog = dialog_ProgressCreate( p_filter,
472 _("Building font cache"),
473 _("Please wait while your font cache is rebuilt.\n"
474 "This should take less than a few minutes."), NULL );
477 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
479 FcConfigBuildFonts( fcConfig );
480 #if defined( __APPLE__ )
481 // By default, scan only the directory /System/Library/Fonts.
482 // So build the set of available fonts under another directories,
483 // and add the set to the current configuration.
484 FcConfigAppFontAddDir( NULL, "~/Library/Fonts" );
485 FcConfigAppFontAddDir( NULL, "/Library/Fonts" );
486 FcConfigAppFontAddDir( NULL, "/Network/Library/Fonts" );
487 //FcConfigAppFontAddDir( NULL, "/System/Library/Fonts" );
491 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
492 dialog_ProgressDestroy( p_dialog );
497 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
501 * \brief Selects a font matching family, bold, italic provided
503 static char* FontConfig_Select( FcConfig* config, const char* family,
504 bool b_bold, bool b_italic, int i_size, int *i_idx )
506 FcResult result = FcResultMatch;
507 FcPattern *pat, *p_pat;
512 /* Create a pattern and fills it */
513 pat = FcPatternCreate();
514 if (!pat) return NULL;
517 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
518 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
519 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
520 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
524 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
526 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
527 free( psz_fontsize );
532 FcDefaultSubstitute( pat );
533 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
535 FcPatternDestroy( pat );
539 /* Find the best font for the pattern, destroy the pattern */
540 p_pat = FcFontMatch( config, pat, &result );
541 FcPatternDestroy( pat );
542 if( !p_pat || result == FcResultNoMatch ) return NULL;
544 /* Check the new pattern */
545 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
546 || ( val_b != FcTrue ) )
548 FcPatternDestroy( p_pat );
551 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
556 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
558 FcPatternDestroy( p_pat );
562 /* if( strcasecmp((const char*)val_s, family ) != 0 )
563 msg_Warn( p_filter, "fontconfig: selected font family is not"
564 "the requested one: '%s' != '%s'\n",
565 (const char*)val_s, family ); */
567 if( FcResultMatch == FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
568 ret = strdup( (const char*)val_s );
570 FcPatternDestroy( p_pat );
576 #define FONT_DIR_NT _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts")
578 static int GetFileFontByName( LPCTSTR font_name, char **psz_filename )
581 TCHAR vbuffer[MAX_PATH];
584 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
588 char *font_name_temp = FromT( font_name );
589 size_t fontname_len = strlen( font_name_temp );
591 for( int index = 0;; index++ )
593 DWORD vbuflen = MAX_PATH - 1;
596 LONG i_result = RegEnumValue( hKey, index, vbuffer, &vbuflen,
597 NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
598 if( i_result != ERROR_SUCCESS )
604 char *psz_value = FromT( vbuffer );
606 char *s = strchr( psz_value,'(' );
607 if( s != NULL && s != psz_value ) s[-1] = '\0';
609 /* Manage concatenated font names */
610 if( strchr( psz_value, '&') ) {
611 if( strcasestr( psz_value, font_name_temp ) != NULL )
618 if( strncasecmp( psz_value, font_name_temp, fontname_len ) == 0 )
628 *psz_filename = FromT( dbuffer );
629 free( font_name_temp );
635 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
636 DWORD type, LPARAM lParam)
638 VLC_UNUSED( metric );
639 if( (type & RASTER_FONTTYPE) ) return 1;
640 // if( lpelfe->elfScript ) FIXME
642 return GetFileFontByName( (LPCTSTR)lpelfe->elfFullName, (char **)lParam );
645 static char* Win32_Select( filter_t *p_filter, const char* family,
646 bool b_bold, bool b_italic, int i_size, int *i_idx )
648 VLC_UNUSED( i_size );
650 if( !family || strlen( family ) < 1 )
655 lf.lfCharSet = DEFAULT_CHARSET;
659 lf.lfWeight = FW_BOLD;
661 LPTSTR psz_fbuffer = ToT( family );
662 _tcsncpy( (LPTSTR)&lf.lfFaceName, psz_fbuffer, LF_FACESIZE );
666 char *psz_filename = NULL;
667 HDC hDC = GetDC( NULL );
668 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
669 ReleaseDC(NULL, hDC);
672 if( psz_filename != NULL )
674 /* FIXME: increase i_idx, when concatenated strings */
677 /* Prepend the Windows Font path, when only a filename was provided */
678 if( strchr( psz_filename, DIR_SEP_CHAR ) )
683 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
685 free( psz_filename );
688 free( psz_filename );
692 else /* Let's take any font we can */
696 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, "arial.ttf" ) == -1 )
705 #if !TARGET_OS_IPHONE
706 static char* MacLegacy_Select( filter_t *p_filter, const char* psz_fontname,
707 bool b_bold, bool b_italic, int i_size, int *i_idx )
709 VLC_UNUSED( b_bold );
710 VLC_UNUSED( b_italic );
711 VLC_UNUSED( i_size );
713 unsigned char path[MAXPATHLEN];
716 CFStringRef cf_fontName;
717 ATSFontRef ats_font_id;
721 if( psz_fontname == NULL )
724 msg_Dbg( p_filter, "looking for %s", psz_fontname );
725 cf_fontName = CFStringCreateWithCString( kCFAllocatorDefault, psz_fontname, kCFStringEncodingUTF8 );
727 ats_font_id = ATSFontFindFromName( cf_fontName, kATSOptionFlagsIncludeDisabledMask );
729 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
731 msg_Dbg( p_filter, "ATS couldn't find %s by name, checking family", psz_fontname );
732 ats_font_id = ATSFontFamilyFindFromName( cf_fontName, kATSOptionFlagsDefault );
734 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
736 msg_Dbg( p_filter, "ATS couldn't find either %s nor its family, checking PS name", psz_fontname );
737 ats_font_id = ATSFontFindFromPostScriptName( cf_fontName, kATSOptionFlagsDefault );
739 if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
741 msg_Err( p_filter, "ATS couldn't find %s (no font name, family or PS name)", psz_fontname );
742 CFRelease( cf_fontName );
747 CFRelease( cf_fontName );
749 if ( noErr != ATSFontGetFileReference( ats_font_id, &ref ) )
751 msg_Err( p_filter, "ATS couldn't get file ref for %s", psz_fontname );
755 /* i_idx calculation by searching preceding fontIDs */
756 /* with same FSRef */
758 ATSFontRef id2 = ats_font_id - 1;
763 if ( noErr != ATSFontGetFileReference( id2, &ref2 ) )
765 if ( noErr != FSCompareFSRefs( &ref, &ref2 ) )
770 *i_idx = ats_font_id - ( id2 + 1 );
773 if ( noErr != FSRefMakePath( &ref, path, sizeof(path) ) )
775 msg_Err( p_filter, "failure when getting path from FSRef" );
778 msg_Dbg( p_filter, "found %s", path );
780 psz_path = strdup( (char *)path );
787 #endif /* HAVE_STYLES */
790 /*****************************************************************************
791 * RenderYUVP: place string in picture
792 *****************************************************************************
793 * This function merges the previously rendered freetype glyphs into a picture
794 *****************************************************************************/
795 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
799 VLC_UNUSED(p_filter);
800 static const uint8_t pi_gamma[16] =
801 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
802 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
806 int i, x, y, i_pitch;
807 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
809 /* Create a new subpicture region */
810 video_format_Init( &fmt, VLC_CODEC_YUVP );
812 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
814 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
816 assert( !p_region->p_picture );
817 p_region->p_picture = picture_NewFromFormat( &fmt );
818 if( !p_region->p_picture )
820 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
823 /* Calculate text color components
824 * Only use the first color */
825 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
826 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
829 fmt.p_palette->i_entries = 16;
830 for( i = 0; i < 8; i++ )
832 fmt.p_palette->palette[i][0] = 0;
833 fmt.p_palette->palette[i][1] = 0x80;
834 fmt.p_palette->palette[i][2] = 0x80;
835 fmt.p_palette->palette[i][3] = pi_gamma[i];
836 fmt.p_palette->palette[i][3] =
837 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
839 for( i = 8; i < fmt.p_palette->i_entries; i++ )
841 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
842 fmt.p_palette->palette[i][1] = i_u;
843 fmt.p_palette->palette[i][2] = i_v;
844 fmt.p_palette->palette[i][3] = pi_gamma[i];
845 fmt.p_palette->palette[i][3] =
846 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
849 p_dst = p_region->p_picture->Y_PIXELS;
850 i_pitch = p_region->p_picture->Y_PITCH;
852 /* Initialize the region pixels */
853 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
855 for( ; p_line != NULL; p_line = p_line->p_next )
857 int i_align_left = 0;
858 if( p_line->i_width < (int)fmt.i_visible_width )
860 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
861 i_align_left = ( fmt.i_visible_width - p_line->i_width );
862 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
863 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
867 for( i = 0; i < p_line->i_character_count; i++ )
869 const line_character_t *ch = &p_line->p_character[i];
870 FT_BitmapGlyph p_glyph = ch->p_glyph;
872 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
873 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
875 for( y = 0; y < p_glyph->bitmap.rows; y++ )
877 for( x = 0; x < p_glyph->bitmap.width; x++ )
879 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
880 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
881 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
887 /* Outlining (find something better than nearest neighbour filtering ?) */
890 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
891 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
892 uint8_t left, current;
894 for( y = 1; y < (int)fmt.i_height - 1; y++ )
896 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
897 p_dst += p_region->p_picture->Y_PITCH;
900 for( x = 1; x < (int)fmt.i_width - 1; x++ )
903 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
904 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;
908 memset( p_top, 0, fmt.i_width );
914 /*****************************************************************************
915 * RenderYUVA: place string in picture
916 *****************************************************************************
917 * This function merges the previously rendered freetype glyphs into a picture
918 *****************************************************************************/
919 static void FillYUVAPicture( picture_t *p_picture,
920 int i_a, int i_y, int i_u, int i_v )
922 memset( p_picture->p[0].p_pixels, i_y,
923 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
924 memset( p_picture->p[1].p_pixels, i_u,
925 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
926 memset( p_picture->p[2].p_pixels, i_v,
927 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
928 memset( p_picture->p[3].p_pixels, i_a,
929 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
932 static inline void BlendYUVAPixel( picture_t *p_picture,
933 int i_picture_x, int i_picture_y,
934 int i_a, int i_y, int i_u, int i_v,
937 int i_an = i_a * i_alpha / 255;
939 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
940 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
941 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
942 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
954 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
957 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
958 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
959 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
964 static void FillRGBAPicture( picture_t *p_picture,
965 int i_a, int i_r, int i_g, int i_b )
967 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
969 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
971 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
980 static inline void BlendRGBAPixel( picture_t *p_picture,
981 int i_picture_x, int i_picture_y,
982 int i_a, int i_r, int i_g, int i_b,
985 int i_an = i_a * i_alpha / 255;
987 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
989 int i_ao = p_rgba[3];
999 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
1000 if( p_rgba[3] != 0 )
1002 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
1003 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
1004 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
1009 static void FillARGBPicture(picture_t *pic, int a, int r, int g, int b)
1013 if (a == r && a == b && a == g)
1015 memset(pic->p->p_pixels, a, pic->p->i_visible_lines * pic->p->i_pitch);
1019 uint_fast32_t pixel = VLC_FOURCC(a, r, g, b);
1020 uint8_t *line = pic->p->p_pixels;
1022 for (unsigned lines = pic->p->i_visible_lines; lines > 0; lines--)
1024 uint32_t *pixels = (uint32_t *)line;
1025 for (unsigned cols = pic->p->i_visible_pitch; cols > 0; cols -= 4)
1026 *(pixels++) = pixel;
1027 line += pic->p->i_pitch;
1031 static inline void BlendARGBPixel(picture_t *pic, int pic_x, int pic_y,
1032 int a, int r, int g, int b, int alpha)
1034 uint8_t *rgba = &pic->p->p_pixels[pic_y * pic->p->i_pitch + 4 * pic_x];
1035 int an = a * alpha / 255;
1047 rgba[0] = 255 - (255 - rgba[0]) * (255 - an) / 255;
1050 rgba[1] = (rgba[1] * ao * (255 - an) / 255 + r * an ) / rgba[0];
1051 rgba[2] = (rgba[2] * ao * (255 - an) / 255 + g * an ) / rgba[0];
1052 rgba[3] = (rgba[3] * ao * (255 - an) / 255 + b * an ) / rgba[0];
1057 static inline void BlendAXYZGlyph( picture_t *p_picture,
1058 int i_picture_x, int i_picture_y,
1059 int i_a, int i_x, int i_y, int i_z,
1060 FT_BitmapGlyph p_glyph,
1061 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1064 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
1066 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
1067 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
1069 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
1073 static inline void BlendAXYZLine( picture_t *p_picture,
1074 int i_picture_x, int i_picture_y,
1075 int i_a, int i_x, int i_y, int i_z,
1076 const line_character_t *p_current,
1077 const line_character_t *p_next,
1078 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1080 int i_line_width = p_current->p_glyph->bitmap.width;
1082 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
1084 for( int dx = 0; dx < i_line_width; dx++ )
1086 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
1087 BlendPixel( p_picture,
1089 i_picture_y + p_current->i_line_offset + dy,
1090 i_a, i_x, i_y, i_z, 0xff );
1094 static inline void RenderBackground( subpicture_region_t *p_region,
1095 line_desc_t *p_line_head,
1098 picture_t *p_picture,
1100 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
1101 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1103 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
1105 int i_align_left = i_margin;
1106 int i_align_top = i_margin;
1109 unsigned line_top = 0;
1110 int line_bottom = 0;
1113 if( p_line->i_width < i_text_width )
1115 /* Left offset to take into account alignment */
1116 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1117 i_align_left += ( i_text_width - p_line->i_width );
1118 else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
1119 i_align_left = i_margin; /* Keep it the way it is */
1120 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1121 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1124 /* Find the tallest character in the line */
1125 for( int i = 0; i < p_line->i_character_count; i++ ) {
1126 const line_character_t *ch = &p_line->p_character[i];
1127 FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
1128 if (p_glyph->top > max_height)
1129 max_height = p_glyph->top;
1132 /* Compute the background for the line (identify leading/trailing space) */
1133 for( int i = 0; i < p_line->i_character_count; i++ ) {
1134 const line_character_t *ch = &p_line->p_character[i];
1135 FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
1136 if (p_glyph && p_glyph->bitmap.rows > 0) {
1137 // Found a non-whitespace character
1138 line_start = i_align_left + p_glyph->left - p_bbox->xMin;
1143 /* Fudge factor to make sure caption background edges are left aligned
1144 despite variable font width */
1145 if (line_start < 12)
1148 /* Find right boundary for bounding box for background */
1149 for( int i = p_line->i_character_count; i > 0; i-- ) {
1150 const line_character_t *ch = &p_line->p_character[i - 1];
1151 FT_BitmapGlyph p_glyph = ch->p_shadow ? ch->p_shadow : ch->p_glyph;
1152 if (p_glyph && p_glyph->bitmap.rows > 0) {
1153 // Found a non-whitespace character
1154 line_end = i_align_left + p_glyph->left - p_bbox->xMin + p_glyph->bitmap.width;
1159 /* Setup color for the background */
1160 uint8_t i_x, i_y, i_z;
1161 ExtractComponents( 0x000000, &i_x, &i_y, &i_z );
1163 /* Compute the upper boundary for the background */
1164 if ((i_align_top + p_line->i_base_line - max_height) < 0)
1165 line_top = i_align_top + p_line->i_base_line;
1167 line_top = i_align_top + p_line->i_base_line - max_height;
1169 /* Compute lower boundary for the background */
1170 line_bottom = __MIN(line_top + p_line->i_height, p_region->fmt.i_visible_height);
1172 /* Render the actual background */
1173 for( int dy = line_top; dy < line_bottom; dy++ )
1175 for( int dx = line_start; dx < line_end; dx++ )
1176 BlendPixel( p_picture, dx, dy, 0xff, i_x, i_y, i_z, 0xff );
1181 static inline int RenderAXYZ( filter_t *p_filter,
1182 subpicture_region_t *p_region,
1183 line_desc_t *p_line_head,
1186 vlc_fourcc_t i_chroma,
1187 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
1188 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
1189 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1191 filter_sys_t *p_sys = p_filter->p_sys;
1193 /* Create a new subpicture region */
1194 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
1195 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
1197 video_format_Init( &fmt, i_chroma );
1199 fmt.i_visible_width = i_text_width + 2 * i_margin;
1201 fmt.i_visible_height = i_text_height + 2 * i_margin;
1203 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
1204 if( !p_region->p_picture )
1205 return VLC_EGENERIC;
1206 p_region->fmt = fmt;
1208 /* Initialize the picture background */
1209 uint8_t i_a = var_InheritInteger( p_filter, "freetype-background-opacity" );
1210 i_a = VLC_CLIP( i_a, 0, 255 );
1211 uint8_t i_x, i_y, i_z;
1213 if (p_region->b_renderbg) {
1214 /* Render the background just under the text */
1215 FillPicture( p_picture, 0x00, 0x00, 0x00, 0x00 );
1216 RenderBackground(p_region, p_line_head, p_bbox, i_margin, p_picture, i_text_width,
1217 ExtractComponents, BlendPixel);
1219 /* Render background under entire subpicture block */
1220 int i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
1221 i_background_color = VLC_CLIP( i_background_color, 0, 0xFFFFFF );
1222 ExtractComponents( i_background_color, &i_x, &i_y, &i_z );
1223 FillPicture( p_picture, i_a, i_x, i_y, i_z );
1226 /* Render shadow then outline and then normal glyphs */
1227 for( int g = 0; g < 3; g++ )
1229 /* Render all lines */
1230 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
1232 int i_align_left = i_margin;
1233 if( p_line->i_width < i_text_width )
1235 /* Left offset to take into account alignment */
1236 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1237 i_align_left += ( i_text_width - p_line->i_width );
1238 else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
1239 i_align_left = i_margin; /* Keep it the way it is */
1240 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1241 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1243 int i_align_top = i_margin;
1245 /* Render all glyphs and underline/strikethrough */
1246 for( int i = 0; i < p_line->i_character_count; i++ )
1248 const line_character_t *ch = &p_line->p_character[i];
1249 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
1253 i_a = (ch->i_color >> 24) & 0xff;
1257 i_a = i_a * p_sys->style.i_shadow_alpha / 255;
1258 i_color = p_sys->style.i_shadow_color;
1261 i_a = i_a * p_sys->style.i_outline_alpha / 255;
1262 i_color = p_sys->style.i_outline_color;
1265 i_color = ch->i_color;
1268 ExtractComponents( i_color, &i_x, &i_y, &i_z );
1270 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
1271 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
1273 BlendAXYZGlyph( p_picture,
1274 i_glyph_x, i_glyph_y,
1279 /* underline/strikethrough are only rendered for the normal glyph */
1280 if( g == 2 && ch->i_line_thickness > 0 )
1281 BlendAXYZLine( p_picture,
1282 i_glyph_x, i_glyph_y + p_glyph->top,
1285 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
1296 static void FreeLine( line_desc_t *p_line )
1298 for( int i = 0; i < p_line->i_character_count; i++ )
1300 line_character_t *ch = &p_line->p_character[i];
1301 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1303 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1305 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1308 free( p_line->p_character );
1312 static void FreeLines( line_desc_t *p_lines )
1314 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1316 line_desc_t *p_next = p_line->p_next;
1322 static line_desc_t *NewLine( int i_count )
1324 line_desc_t *p_line = malloc( sizeof(*p_line) );
1329 p_line->p_next = NULL;
1330 p_line->i_width = 0;
1331 p_line->i_base_line = 0;
1332 p_line->i_character_count = 0;
1334 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1335 if( !p_line->p_character )
1343 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1345 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1347 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1349 FT_Face p_face = NULL;
1351 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1359 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1360 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1361 if( p_face->family_name != NULL
1362 && !strcasecmp( p_face->family_name, p_style->psz_fontname )
1363 && (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC))
1364 == i_style_received )
1367 FT_Done_Face( p_face );
1375 static FT_Face LoadFace( filter_t *p_filter,
1376 const text_style_t *p_style )
1378 filter_sys_t *p_sys = p_filter->p_sys;
1380 /* Look for a match amongst our attachments first */
1381 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1383 /* Load system wide font otheriwse */
1387 char *psz_fontfile = NULL;
1388 #ifdef HAVE_FONTCONFIG
1389 psz_fontfile = FontConfig_Select( NULL,
1390 p_style->psz_fontname,
1391 (p_style->i_style_flags & STYLE_BOLD) != 0,
1392 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1395 #elif defined( __APPLE__ )
1396 #if !TARGET_OS_IPHONE
1397 psz_fontfile = MacLegacy_Select( p_filter, p_style->psz_fontname, false, false, -1, &i_idx );
1399 #elif defined( _WIN32 )
1400 psz_fontfile = Win32_Select( p_filter,
1401 p_style->psz_fontname,
1402 (p_style->i_style_flags & STYLE_BOLD) != 0,
1403 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1407 psz_fontfile = NULL;
1412 if( *psz_fontfile == '\0' )
1415 "We were not able to find a matching font: \"%s\" (%s %s),"
1416 " so using default font",
1417 p_style->psz_fontname,
1418 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1419 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1424 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1427 free( psz_fontfile );
1432 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1434 /* We've loaded a font face which is unhelpful for actually
1435 * rendering text - fallback to the default one.
1437 FT_Done_Face( p_face );
1443 static int GetGlyph( filter_t *p_filter,
1444 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1445 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1446 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1452 FT_Vector *p_pen_shadow )
1454 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1455 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1457 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1458 return VLC_EGENERIC;
1461 /* Do synthetic styling now that Freetype supports it;
1462 * ie. if the font we have loaded is NOT already in the
1463 * style that the tags want, then switch it on; if they
1464 * are then don't. */
1465 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1466 FT_GlyphSlot_Embolden( p_face->glyph );
1467 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1468 FT_GlyphSlot_Oblique( p_face->glyph );
1471 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1473 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1474 return VLC_EGENERIC;
1477 FT_Glyph outline = NULL;
1478 if( p_filter->p_sys->p_stroker )
1481 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1485 FT_Glyph shadow = NULL;
1486 if( p_filter->p_sys->style.i_shadow_alpha > 0 )
1488 shadow = outline ? outline : glyph;
1489 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1495 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1498 *pp_shadow = shadow;
1500 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1502 FT_Done_Glyph( glyph );
1504 FT_Done_Glyph( outline );
1506 FT_Done_Glyph( shadow );
1507 return VLC_EGENERIC;
1509 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1514 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1515 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1517 *pp_outline = outline;
1522 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1524 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1525 if( p_bbox->xMin >= p_bbox->xMax )
1527 p_bbox->xMin = FT_CEIL(p_pen->x);
1528 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1529 glyph_bmp->left = p_bbox->xMin;
1531 if( p_bbox->yMin >= p_bbox->yMax )
1533 p_bbox->yMax = FT_CEIL(p_pen->y);
1534 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1535 glyph_bmp->top = p_bbox->yMax;
1539 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1541 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1542 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1543 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1544 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1547 static int ProcessLines( filter_t *p_filter,
1548 line_desc_t **pp_lines,
1550 int *pi_max_face_height,
1552 uni_char_t *psz_text,
1553 text_style_t **pp_styles,
1554 uint32_t *pi_k_dates,
1557 filter_sys_t *p_sys = p_filter->p_sys;
1558 uni_char_t *p_fribidi_string = NULL;
1559 text_style_t **pp_fribidi_styles = NULL;
1560 int *p_new_positions = NULL;
1562 #if defined(HAVE_FRIBIDI)
1564 int *p_old_positions;
1565 int start_pos, pos = 0;
1567 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1569 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1570 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1571 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1573 if( ! pp_fribidi_styles ||
1574 ! p_fribidi_string ||
1575 ! p_old_positions ||
1578 free( p_old_positions );
1579 free( p_new_positions );
1580 free( p_fribidi_string );
1581 free( pp_fribidi_styles );
1585 /* Do bidi conversion line-by-line */
1588 while(pos < i_len) {
1589 if (psz_text[pos] != '\n')
1591 p_fribidi_string[pos] = psz_text[pos];
1592 pp_fribidi_styles[pos] = pp_styles[pos];
1593 p_new_positions[pos] = pos;
1597 while(pos < i_len) {
1598 if (psz_text[pos] == '\n')
1602 if (pos > start_pos)
1604 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1605 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1607 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1609 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1610 pos - start_pos, &base_dir,
1611 (FriBidiChar*)p_fribidi_string + start_pos,
1612 p_new_positions + start_pos,
1615 for( int j = start_pos; j < pos; j++ )
1617 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1618 p_new_positions[ j ] += start_pos;
1622 p_fribidi_string[ i_len ] = 0;
1623 free( p_old_positions );
1625 pp_styles = pp_fribidi_styles;
1626 psz_text = p_fribidi_string;
1629 /* Work out the karaoke */
1630 uint8_t *pi_karaoke_bar = NULL;
1633 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1634 if( pi_karaoke_bar )
1636 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1637 for( int i = 0; i < i_len; i++ )
1639 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1640 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1644 free( p_new_positions );
1646 *pi_max_face_height = 0;
1648 line_desc_t **pp_line_next = pp_lines;
1656 int i_face_height_previous = 0;
1657 int i_base_line = 0;
1658 const text_style_t *p_previous_style = NULL;
1659 FT_Face p_face = NULL;
1660 for( int i_start = 0; i_start < i_len; )
1662 /* Compute the length of the current text line */
1664 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
1667 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1668 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
1669 int i_index = i_start;
1674 int i_face_height = 0;
1675 FT_BBox line_bbox = {
1681 int i_ul_offset = 0;
1682 int i_ul_thickness = 0;
1691 break_point_t break_point;
1692 break_point_t break_point_fallback;
1694 #define SAVE_BP(dst) do { \
1695 dst.i_index = i_index; \
1697 dst.line_bbox = line_bbox; \
1698 dst.i_face_height = i_face_height; \
1699 dst.i_ul_offset = i_ul_offset; \
1700 dst.i_ul_thickness = i_ul_thickness; \
1703 SAVE_BP( break_point );
1704 SAVE_BP( break_point_fallback );
1706 while( i_index < i_start + i_length )
1708 /* Split by common FT_Face + Size */
1709 const text_style_t *p_current_style = pp_styles[i_index];
1710 int i_part_length = 0;
1711 while( i_index + i_part_length < i_start + i_length )
1713 const text_style_t *p_style = pp_styles[i_index + i_part_length];
1714 if( !FaceStyleEquals( p_style, p_current_style ) ||
1715 p_style->i_font_size != p_current_style->i_font_size )
1720 /* (Re)load/reconfigure the face if needed */
1721 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
1724 FT_Done_Face( p_face );
1725 p_previous_style = NULL;
1727 p_face = LoadFace( p_filter, p_current_style );
1729 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
1730 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
1732 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
1733 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
1734 if( p_sys->p_stroker )
1736 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
1737 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
1738 int i_radius = (p_current_style->i_font_size << 6) * f_outline_thickness;
1739 FT_Stroker_Set( p_sys->p_stroker,
1741 FT_STROKER_LINECAP_ROUND,
1742 FT_STROKER_LINEJOIN_ROUND, 0 );
1745 p_previous_style = p_current_style;
1747 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
1748 p_current_face->size->metrics.y_scale)));
1750 /* Render the part */
1751 bool b_break_line = false;
1752 int i_glyph_last = 0;
1753 while( i_part_length > 0 )
1755 const text_style_t *p_glyph_style = pp_styles[i_index];
1756 uni_char_t character = psz_text[i_index];
1757 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
1759 /* Get kerning vector */
1760 FT_Vector kerning = { .x = 0, .y = 0 };
1761 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
1762 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
1764 /* Get the glyph bitmap and its bounding box and all the associated properties */
1765 FT_Vector pen_new = {
1766 .x = pen.x + kerning.x,
1767 .y = pen.y + kerning.y,
1769 FT_Vector pen_shadow_new = {
1770 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
1771 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
1776 FT_BBox outline_bbox;
1778 FT_BBox shadow_bbox;
1780 if( GetGlyph( p_filter,
1781 &glyph, &glyph_bbox,
1782 &outline, &outline_bbox,
1783 &shadow, &shadow_bbox,
1784 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
1785 &pen_new, &pen_shadow_new ) )
1788 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
1790 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
1792 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
1794 /* FIXME and what about outline */
1796 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
1797 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
1798 (p_glyph_style->i_karaoke_background_alpha << 24))
1799 : (p_glyph_style->i_font_color |
1800 (p_glyph_style->i_font_alpha << 24));
1801 int i_line_offset = 0;
1802 int i_line_thickness = 0;
1803 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1805 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
1806 p_current_face->size->metrics.y_scale)) );
1808 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
1809 p_current_face->size->metrics.y_scale)) );
1811 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
1813 /* Move the baseline to make it strikethrough instead of
1814 * underline. That means that strikethrough takes precedence
1816 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
1817 p_current_face->size->metrics.y_scale)) );
1819 else if( i_line_thickness > 0 )
1821 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
1823 /* The real underline thickness and position are
1824 * updated once the whole line has been parsed */
1825 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
1826 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
1827 i_line_thickness = -1;
1830 FT_BBox line_bbox_new = line_bbox;
1831 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
1833 BBoxEnlarge( &line_bbox_new, &outline_bbox );
1835 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
1837 b_break_line = i_index > i_start &&
1838 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
1841 FT_Done_Glyph( glyph );
1843 FT_Done_Glyph( outline );
1845 FT_Done_Glyph( shadow );
1847 break_point_t *p_bp = NULL;
1848 if( break_point.i_index > i_start )
1849 p_bp = &break_point;
1850 else if( break_point_fallback.i_index > i_start )
1851 p_bp = &break_point_fallback;
1855 msg_Dbg( p_filter, "Breaking line");
1856 for( int i = p_bp->i_index; i < i_index; i++ )
1858 line_character_t *ch = &p_line->p_character[i - i_start];
1859 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1861 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1863 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1865 p_line->i_character_count = p_bp->i_index - i_start;
1867 i_index = p_bp->i_index;
1869 line_bbox = p_bp->line_bbox;
1870 i_face_height = p_bp->i_face_height;
1871 i_ul_offset = p_bp->i_ul_offset;
1872 i_ul_thickness = p_bp->i_ul_thickness;
1876 msg_Err( p_filter, "Breaking unbreakable line");
1881 assert( p_line->i_character_count == i_index - i_start);
1882 p_line->p_character[p_line->i_character_count++] = (line_character_t){
1883 .p_glyph = (FT_BitmapGlyph)glyph,
1884 .p_outline = (FT_BitmapGlyph)outline,
1885 .p_shadow = (FT_BitmapGlyph)shadow,
1887 .i_line_offset = i_line_offset,
1888 .i_line_thickness = i_line_thickness,
1891 pen.x = pen_new.x + p_current_face->glyph->advance.x;
1892 pen.y = pen_new.y + p_current_face->glyph->advance.y;
1893 line_bbox = line_bbox_new;
1895 i_glyph_last = i_glyph_index;
1899 if( character == ' ' || character == '\t' )
1900 SAVE_BP( break_point );
1901 else if( character == 160 )
1902 SAVE_BP( break_point_fallback );
1908 /* Update our baseline */
1909 if( i_face_height_previous > 0 )
1910 i_base_line += __MAX(i_face_height, i_face_height_previous);
1911 if( i_face_height > 0 )
1912 i_face_height_previous = i_face_height;
1914 /* Update the line bbox with the actual base line */
1915 if (line_bbox.yMax > line_bbox.yMin) {
1916 line_bbox.yMin -= i_base_line;
1917 line_bbox.yMax -= i_base_line;
1919 BBoxEnlarge( &bbox, &line_bbox );
1921 /* Terminate and append the line */
1924 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
1925 p_line->i_base_line = i_base_line;
1926 p_line->i_height = __MAX(i_face_height, i_face_height_previous);
1927 if( i_ul_thickness > 0 )
1929 for( int i = 0; i < p_line->i_character_count; i++ )
1931 line_character_t *ch = &p_line->p_character[i];
1932 if( ch->i_line_thickness < 0 )
1934 ch->i_line_offset = i_ul_offset;
1935 ch->i_line_thickness = i_ul_thickness;
1940 *pp_line_next = p_line;
1941 pp_line_next = &p_line->p_next;
1944 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
1946 /* Skip what we have rendered and the line delimitor if present */
1948 if( i_start < i_len && psz_text[i_start] == '\n' )
1951 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
1953 msg_Err( p_filter, "Truncated too high subtitle" );
1958 FT_Done_Face( p_face );
1960 free( pp_fribidi_styles );
1961 free( p_fribidi_string );
1962 free( pi_karaoke_bar );
1969 * This function renders a text subpicture region into another one.
1970 * It also calculates the size needed for this string, and renders the
1971 * needed glyphs into memory. It is used as pf_add_string callback in
1972 * the vout method by this module
1974 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
1975 subpicture_region_t *p_region_in, bool b_html,
1976 const vlc_fourcc_t *p_chroma_list )
1978 filter_sys_t *p_sys = p_filter->p_sys;
1981 return VLC_EGENERIC;
1982 if( b_html && !p_region_in->psz_html )
1983 return VLC_EGENERIC;
1984 if( !b_html && !p_region_in->psz_text )
1985 return VLC_EGENERIC;
1987 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
1988 : p_region_in->psz_text );
1990 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
1991 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
1992 if( !psz_text || !pp_styles )
1996 return VLC_EGENERIC;
1999 /* Reset the default fontsize in case screen metrics have changed */
2000 p_filter->p_sys->style.i_font_size = GetFontSize( p_filter );
2003 int rv = VLC_SUCCESS;
2004 int i_text_length = 0;
2006 int i_max_face_height;
2007 line_desc_t *p_lines = NULL;
2009 uint32_t *pi_k_durations = NULL;
2013 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2014 (uint8_t *) p_region_in->psz_html,
2015 strlen( p_region_in->psz_html ),
2017 if( unlikely(p_sub == NULL) )
2020 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2022 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2024 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2025 p_filter->p_sys->p_xml = p_xml_reader;
2032 /* Look for Root Node */
2035 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2037 if( strcasecmp( "karaoke", node ) == 0 )
2039 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2041 else if( strcasecmp( "text", node ) != 0 )
2043 /* Only text and karaoke tags are supported */
2044 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2051 msg_Err( p_filter, "Malformed HTML subtitle" );
2057 rv = ProcessNodes( p_filter,
2058 psz_text, pp_styles, pi_k_durations, &i_text_length,
2059 p_xml_reader, p_region_in->p_style, &p_filter->p_sys->style );
2063 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2065 stream_Delete( p_sub );
2069 text_style_t *p_style;
2070 if( p_region_in->p_style )
2071 p_style = CreateStyle( p_region_in->p_style->psz_fontname ? p_region_in->p_style->psz_fontname
2072 : p_sys->style.psz_fontname,
2073 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2074 : p_sys->style.i_font_size,
2075 (p_region_in->p_style->i_font_color & 0xffffff) |
2076 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2078 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2084 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2085 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
2086 p_style = CreateStyle( p_sys->style.psz_fontname,
2087 p_sys->style.i_font_size,
2088 (i_font_color & 0xffffff) |
2089 ((p_sys->style.i_font_alpha & 0xff) << 24),
2092 if( p_sys->style.i_style_flags & STYLE_BOLD )
2093 p_style->i_style_flags |= STYLE_BOLD;
2095 i_text_length = SetupText( p_filter,
2099 p_region_in->psz_text, p_style, 0 );
2102 if( !rv && i_text_length > 0 )
2104 rv = ProcessLines( p_filter,
2105 &p_lines, &bbox, &i_max_face_height,
2106 psz_text, pp_styles, pi_k_durations, i_text_length );
2109 p_region_out->i_x = p_region_in->i_x;
2110 p_region_out->i_y = p_region_in->i_y;
2112 /* Don't attempt to render text that couldn't be layed out
2114 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2116 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2117 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2119 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2120 p_chroma_list = p_chroma_list_yuvp;
2121 else if( !p_chroma_list || *p_chroma_list == 0 )
2122 p_chroma_list = p_chroma_list_rgba;
2124 uint8_t i_background_opacity = var_InheritInteger( p_filter, "freetype-background-opacity" );
2125 i_background_opacity = VLC_CLIP( i_background_opacity, 0, 255 );
2126 const int i_margin = i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2127 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2130 if( *p_chroma == VLC_CODEC_YUVP )
2131 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2132 else if( *p_chroma == VLC_CODEC_YUVA )
2133 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2138 else if( *p_chroma == VLC_CODEC_RGBA )
2139 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2144 else if( *p_chroma == VLC_CODEC_ARGB )
2145 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox,
2146 i_margin, *p_chroma, RGBFromRGB,
2147 FillARGBPicture, BlendARGBPixel );
2153 /* With karaoke, we're going to have to render the text a number
2154 * of times to show the progress marker on the text.
2156 if( pi_k_durations )
2157 var_SetBool( p_filter, "text-rerender", true );
2160 FreeLines( p_lines );
2163 for( int i = 0; i < i_text_length; i++ )
2165 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2166 text_style_Delete( pp_styles[i] );
2169 free( pi_k_durations );
2174 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2175 subpicture_region_t *p_region_in,
2176 const vlc_fourcc_t *p_chroma_list )
2178 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2181 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2182 subpicture_region_t *p_region_in,
2183 const vlc_fourcc_t *p_chroma_list )
2185 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2188 /*****************************************************************************
2189 * Create: allocates osd-text video thread output method
2190 *****************************************************************************
2191 * This function allocates and initializes a Clone vout method.
2192 *****************************************************************************/
2193 static int Create( vlc_object_t *p_this )
2195 filter_t *p_filter = (filter_t *)p_this;
2196 filter_sys_t *p_sys;
2197 char *psz_fontfile = NULL;
2198 char *psz_fontname = NULL;
2199 char *psz_monofontfile = NULL;
2200 char *psz_monofontfamily = NULL;
2201 int i_error = 0, fontindex = 0, monofontindex = 0;
2203 /* Allocate structure */
2204 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2208 p_sys->style.psz_fontname = NULL;
2209 p_sys->p_xml = NULL;
2211 p_sys->p_library = 0;
2212 p_sys->style.i_font_size = 0;
2215 * The following variables should not be cached, as they might be changed on-the-fly:
2216 * freetype-rel-fontsize, freetype-background-opacity, freetype-background-color,
2217 * freetype-outline-thickness, freetype-color
2221 psz_fontname = var_InheritString( p_filter, "freetype-font" );
2222 psz_monofontfamily = var_InheritString( p_filter, "freetype-monofont" );
2223 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2224 p_sys->style.i_font_alpha = var_InheritInteger( p_filter,"freetype-opacity" );
2225 p_sys->style.i_font_alpha = VLC_CLIP( p_sys->style.i_font_alpha, 0, 255 );
2226 if( var_InheritBool( p_filter, "freetype-bold" ) )
2227 p_sys->style.i_style_flags |= STYLE_BOLD;
2229 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2230 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
2231 p_sys->style.i_outline_alpha = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2232 p_sys->style.i_outline_alpha = VLC_CLIP( p_sys->style.i_outline_alpha, 0, 255 );
2233 p_sys->style.i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2234 p_sys->style.i_outline_color = VLC_CLIP( p_sys->style.i_outline_color, 0, 0xFFFFFF );
2236 p_sys->style.i_shadow_alpha = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2237 p_sys->style.i_shadow_alpha = VLC_CLIP( p_sys->style.i_shadow_alpha, 0, 255 );
2238 p_sys->style.i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2239 p_sys->style.i_shadow_color = VLC_CLIP( p_sys->style.i_shadow_color, 0, 0xFFFFFF );
2240 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2241 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2242 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
2243 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2244 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2247 /* Get Windows Font folder */
2248 wchar_t wdir[MAX_PATH];
2249 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2251 GetWindowsDirectoryW( wdir, MAX_PATH );
2252 wcscat( wdir, L"\\fonts" );
2254 p_sys->psz_win_fonts_path = FromWide( wdir );
2257 /* Set default psz_fontname */
2258 if( !psz_fontname || !*psz_fontname )
2260 free( psz_fontname );
2262 psz_fontname = strdup( DEFAULT_FAMILY );
2265 if( asprintf( &psz_fontname, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2267 psz_fontname = NULL;
2271 psz_fontname = strdup( DEFAULT_FONT_FILE );
2273 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontname );
2277 /* Set the current font file */
2278 p_sys->style.psz_fontname = psz_fontname;
2280 #ifdef HAVE_FONTCONFIG
2281 FontConfig_BuildCache( p_filter );
2284 psz_fontfile = FontConfig_Select( NULL, psz_fontname, false, false,
2285 p_sys->i_default_font_size, &fontindex );
2286 psz_monofontfile = FontConfig_Select( NULL, psz_monofontfamily, false,
2287 false, p_sys->i_default_font_size,
2289 #elif defined(__APPLE__)
2290 #if !TARGET_OS_IPHONE
2291 psz_fontfile = MacLegacy_Select( p_filter, psz_fontname, false, false, -1, &fontindex );
2293 #elif defined(_WIN32)
2294 psz_fontfile = Win32_Select( p_filter, psz_fontname, false, false, -1, &fontindex );
2297 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontname, psz_fontfile );
2299 /* If nothing is found, use the default family */
2301 psz_fontfile = strdup( psz_fontname );
2302 if( !psz_monofontfile )
2303 psz_monofontfile = strdup( psz_monofontfamily );
2305 #else /* !HAVE_STYLES */
2306 /* Use the default file */
2307 psz_fontfile = psz_fontname;
2308 psz_monofontfile = psz_monofontfamily;
2310 p_sys->style.psz_monofontname = psz_monofontfamily;
2313 i_error = FT_Init_FreeType( &p_sys->p_library );
2316 msg_Err( p_filter, "couldn't initialize freetype" );
2320 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2321 fontindex, &p_sys->p_face );
2323 if( i_error == FT_Err_Unknown_File_Format )
2325 msg_Err( p_filter, "file %s have unknown format",
2326 psz_fontfile ? psz_fontfile : "(null)" );
2331 msg_Err( p_filter, "failed to load font file %s",
2332 psz_fontfile ? psz_fontfile : "(null)" );
2336 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2339 msg_Err( p_filter, "font has no unicode translation table" );
2343 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2345 p_sys->p_stroker = NULL;
2346 if( f_outline_thickness > 0.001 )
2348 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2350 msg_Err( p_filter, "Failed to create stroker for outlining" );
2353 p_sys->pp_font_attachments = NULL;
2354 p_sys->i_font_attachments = 0;
2356 p_filter->pf_render_text = RenderText;
2357 p_filter->pf_render_html = RenderHtml;
2359 LoadFontsFromAttachments( p_filter );
2362 free( psz_fontfile );
2363 free( psz_monofontfile );
2369 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2370 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2372 free( psz_fontfile );
2373 free( psz_monofontfile );
2375 free( psz_fontname );
2376 free( psz_monofontfamily );
2378 return VLC_EGENERIC;
2381 /*****************************************************************************
2382 * Destroy: destroy Clone video thread output method
2383 *****************************************************************************
2384 * Clean up all data and library connections
2385 *****************************************************************************/
2386 static void Destroy( vlc_object_t *p_this )
2388 filter_t *p_filter = (filter_t *)p_this;
2389 filter_sys_t *p_sys = p_filter->p_sys;
2391 if( p_sys->pp_font_attachments )
2393 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2394 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2396 free( p_sys->pp_font_attachments );
2399 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2400 free( p_sys->style.psz_fontname );
2401 free( p_sys->style.psz_monofontname );
2404 free( p_sys->psz_win_fonts_path );
2407 /* FcFini asserts calling the subfunction FcCacheFini()
2408 * even if no other library functions have been made since FcInit(),
2409 * so don't call it. */
2411 if( p_sys->p_stroker )
2412 FT_Stroker_Done( p_sys->p_stroker );
2413 FT_Done_Face( p_sys->p_face );
2414 FT_Done_FreeType( p_sys->p_library );