1 /*****************************************************************************
2 * freetype.c : Put text on the video, using freetype2
3 *****************************************************************************
4 * Copyright (C) 2002 - 2011 the VideoLAN team
7 * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8 * Gildas Bazin <gbazin@videolan.org>
9 * Bernie Purcell <bitmap@videolan.org>
10 * Jean-Baptiste Kempf <jb@videolan.org>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 /*****************************************************************************
29 *****************************************************************************/
36 #include <vlc_common.h>
37 #include <vlc_plugin.h>
38 #include <vlc_stream.h> /* stream_MemoryNew */
39 #include <vlc_input.h> /* vlc_input_attachment_* */
40 #include <vlc_xml.h> /* xml_reader */
41 #include <vlc_strings.h> /* resolve_xml_special_chars */
42 #include <vlc_charset.h> /* ToCharset */
43 #include <vlc_dialog.h> /* FcCache dialog */
44 #include <vlc_filter.h> /* filter_sys_t */
45 #include <vlc_text_style.h> /* text_style_t*/
49 # define DEFAULT_FONT_FILE "/Library/Fonts/Arial Black.ttf"
50 # define DEFAULT_FAMILY "Arial Black"
51 #elif defined( WIN32 )
52 # define DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
53 # define DEFAULT_FAMILY "Arial"
54 #elif defined( __OS2__ )
55 # define DEFAULT_FONT_FILE "/psfonts/tnrwt_k.ttf"
56 # define DEFAULT_FAMILY "Times New Roman WT K"
57 #elif defined( HAVE_MAEMO )
58 # define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf"
59 # define DEFAULT_FAMILY "Nokia Sans Bold"
61 # define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
62 # define DEFAULT_FAMILY "Serif Bold"
66 #include <freetype/ftsynth.h>
67 #include FT_FREETYPE_H
71 #define FT_FLOOR(X) ((X & -64) >> 6)
72 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
74 #define FT_MulFix(v, s) (((v)*(s))>>16)
78 #if defined(HAVE_FRIBIDI)
79 # include <fribidi/fribidi.h>
87 # undef HAVE_FONTCONFIG
91 #ifdef HAVE_FONTCONFIG
92 # include <fontconfig/fontconfig.h>
99 typedef uint16_t uni_char_t;
100 # define FREETYPE_TO_UCS "UCS-2LE"
102 typedef uint32_t uni_char_t;
103 # if defined(WORDS_BIGENDIAN)
104 # define FREETYPE_TO_UCS "UCS-4BE"
106 # define FREETYPE_TO_UCS "UCS-4LE"
110 /*****************************************************************************
112 *****************************************************************************/
113 static int Create ( vlc_object_t * );
114 static void Destroy( vlc_object_t * );
116 #define FONT_TEXT N_("Font")
118 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
119 #define FONT_LONGTEXT N_("Font file for the font you want to use")
121 #define FONTSIZE_TEXT N_("Font size in pixels")
122 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
123 "that will be rendered on the video. " \
124 "If set to something different than 0 this option will override the " \
125 "relative font size." )
126 #define OPACITY_TEXT N_("Text opacity")
127 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
128 "text that will be rendered on the video. 0 = transparent, " \
129 "255 = totally opaque. " )
130 #define COLOR_TEXT N_("Text default color")
131 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
132 "the video. This must be an hexadecimal (like HTML colors). The first two "\
133 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
134 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
135 #define FONTSIZER_TEXT N_("Relative font size")
136 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
137 "fonts that will be rendered on the video. If absolute font size is set, "\
138 "relative size will be overridden." )
139 #define BOLD_TEXT N_("Force bold")
141 #define BG_OPACITY_TEXT N_("Background opacity")
142 #define BG_COLOR_TEXT N_("Background color")
144 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
145 #define OUTLINE_COLOR_TEXT N_("Outline color")
146 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
148 #define SHADOW_OPACITY_TEXT N_("Shadow opacity")
149 #define SHADOW_COLOR_TEXT N_("Shadow color")
150 #define SHADOW_ANGLE_TEXT N_("Shadow angle")
151 #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
154 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
155 static const char *const ppsz_sizes_text[] = {
156 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
157 #define YUVP_TEXT N_("Use YUVP renderer")
158 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
159 "This option is only needed if you want to encode into DVB subtitles" )
161 static const int pi_color_values[] = {
162 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
163 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
164 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
166 static const char *const ppsz_color_descriptions[] = {
167 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
168 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
169 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
171 static const int pi_outline_thickness[] = {
174 static const char *const ppsz_outline_thickness[] = {
175 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
179 set_shortname( N_("Text renderer"))
180 set_description( N_("Freetype2 font renderer") )
181 set_category( CAT_VIDEO )
182 set_subcategory( SUBCAT_VIDEO_SUBPIC )
185 add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
187 add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
190 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
191 FONTSIZE_LONGTEXT, true )
194 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
195 FONTSIZER_LONGTEXT, false )
196 change_integer_list( pi_sizes, ppsz_sizes_text )
199 /* opacity valid on 0..255, with default 255 = fully opaque */
200 add_integer_with_range( "freetype-opacity", 255, 0, 255,
201 OPACITY_TEXT, OPACITY_LONGTEXT, false )
204 /* hook to the color values list, with default 0x00ffffff = white */
205 add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
206 COLOR_LONGTEXT, false )
207 change_integer_list( pi_color_values, ppsz_color_descriptions )
210 add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
213 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
214 BG_OPACITY_TEXT, NULL, false )
216 add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
218 change_integer_list( pi_color_values, ppsz_color_descriptions )
221 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
222 OUTLINE_OPACITY_TEXT, NULL, false )
224 add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
226 change_integer_list( pi_color_values, ppsz_color_descriptions )
228 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
230 change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
233 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
234 SHADOW_OPACITY_TEXT, NULL, false )
236 add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
238 change_integer_list( pi_color_values, ppsz_color_descriptions )
240 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
241 SHADOW_ANGLE_TEXT, NULL, false )
243 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
244 SHADOW_DISTANCE_TEXT, NULL, false )
247 add_obsolete_integer( "freetype-effect" );
249 add_bool( "freetype-yuvp", false, YUVP_TEXT,
250 YUVP_LONGTEXT, true )
251 set_capability( "text renderer", 100 )
252 add_shortcut( "text" )
253 set_callbacks( Create, Destroy )
257 /*****************************************************************************
259 *****************************************************************************/
263 FT_BitmapGlyph p_glyph;
264 FT_BitmapGlyph p_outline;
265 FT_BitmapGlyph p_shadow;
266 uint32_t i_color; /* ARGB color */
267 int i_line_offset; /* underline/strikethrough offset */
268 int i_line_thickness; /* underline/strikethrough thickness */
271 typedef struct line_desc_t line_desc_t;
278 int i_character_count;
279 line_character_t *p_character;
282 typedef struct font_stack_t font_stack_t;
287 uint32_t i_color; /* ARGB */
288 uint32_t i_karaoke_bg_color; /* ARGB */
290 font_stack_t *p_next;
293 /*****************************************************************************
294 * filter_sys_t: freetype local data
295 *****************************************************************************
296 * This structure is part of the video output thread descriptor.
297 * It describes the freetype specific properties of an output thread.
298 *****************************************************************************/
301 FT_Library p_library; /* handle to library */
302 FT_Face p_face; /* handle to face object */
303 FT_Stroker p_stroker;
304 uint8_t i_font_opacity;
309 uint8_t i_background_opacity;
310 int i_background_color;
312 double f_outline_thickness;
313 uint8_t i_outline_opacity;
316 float f_shadow_vector_x;
317 float f_shadow_vector_y;
318 uint8_t i_shadow_opacity;
321 int i_default_font_size;
322 int i_display_height;
323 char* psz_fontfamily;
327 char* psz_win_fonts_path;
331 input_attachment_t **pp_font_attachments;
332 int i_font_attachments;
336 static void YUVFromRGB( uint32_t i_argb,
337 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
339 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
340 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
341 int i_blue = ( i_argb & 0x000000ff );
343 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
344 802 * i_blue + 4096 + 131072 ) >> 13, 235);
345 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
346 3598 * i_blue + 4096 + 1048576) >> 13, 240);
347 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
348 -585 * i_blue + 4096 + 1048576) >> 13, 240);
350 static void RGBFromRGB( uint32_t i_argb,
351 uint8_t *pi_r, uint8_t *pi_g, uint8_t *pi_b )
353 *pi_r = ( i_argb & 0x00ff0000 ) >> 16;
354 *pi_g = ( i_argb & 0x0000ff00 ) >> 8;
355 *pi_b = ( i_argb & 0x000000ff );
357 /*****************************************************************************
358 * Make any TTF/OTF fonts present in the attachments of the media file
359 * and store them for later use by the FreeType Engine
360 *****************************************************************************/
361 static int LoadFontsFromAttachments( filter_t *p_filter )
363 filter_sys_t *p_sys = p_filter->p_sys;
364 input_attachment_t **pp_attachments;
365 int i_attachments_cnt;
367 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
370 p_sys->i_font_attachments = 0;
371 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
372 if( !p_sys->pp_font_attachments )
375 for( int k = 0; k < i_attachments_cnt; k++ )
377 input_attachment_t *p_attach = pp_attachments[k];
379 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
380 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
381 p_attach->i_data > 0 && p_attach->p_data )
383 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
387 vlc_input_attachment_Delete( p_attach );
390 free( pp_attachments );
395 static int GetFontSize( filter_t *p_filter )
397 filter_sys_t *p_sys = p_filter->p_sys;
400 if( p_sys->i_default_font_size )
402 i_size = p_sys->i_default_font_size;
406 int i_ratio = var_GetInteger( p_filter, "freetype-rel-fontsize" );
409 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
410 p_filter->p_sys->i_display_height = p_filter->fmt_out.video.i_height;
415 msg_Warn( p_filter, "invalid fontsize, using 12" );
421 static int SetFontSize( filter_t *p_filter, int i_size )
423 filter_sys_t *p_sys = p_filter->p_sys;
427 i_size = GetFontSize( p_filter );
429 msg_Dbg( p_filter, "using fontsize: %i", i_size );
432 p_sys->i_font_size = i_size;
434 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
436 msg_Err( p_filter, "couldn't set font size to %d", i_size );
444 #ifdef HAVE_FONTCONFIG
445 static void FontConfig_BuildCache( filter_t *p_filter )
448 msg_Dbg( p_filter, "Building font databases.");
456 #if defined( WIN32 ) || defined( __APPLE__ )
457 dialog_progress_bar_t *p_dialog = NULL;
458 FcConfig *fcConfig = FcInitLoadConfig();
460 p_dialog = dialog_ProgressCreate( p_filter,
461 _("Building font cache"),
462 _("Please wait while your font cache is rebuilt.\n"
463 "This should take less than a few minutes."), NULL );
466 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
468 FcConfigBuildFonts( fcConfig );
469 #if defined( __APPLE__ )
470 // By default, scan only the directory /System/Library/Fonts.
471 // So build the set of available fonts under another directories,
472 // and add the set to the current configuration.
473 FcConfigAppFontAddDir( NULL, "~/Library/Fonts" );
474 FcConfigAppFontAddDir( NULL, "/Library/Fonts" );
475 FcConfigAppFontAddDir( NULL, "/Network/Library/Fonts" );
476 //FcConfigAppFontAddDir( NULL, "/System/Library/Fonts" );
480 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
481 dialog_ProgressDestroy( p_dialog );
486 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
490 * \brief Selects a font matching family, bold, italic provided
492 static char* FontConfig_Select( FcConfig* config, const char* family,
493 bool b_bold, bool b_italic, int i_size, int *i_idx )
495 FcResult result = FcResultMatch;
496 FcPattern *pat, *p_pat;
500 /* Create a pattern and fills it */
501 pat = FcPatternCreate();
502 if (!pat) return NULL;
505 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
506 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
507 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
508 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
512 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
514 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
515 free( psz_fontsize );
520 FcDefaultSubstitute( pat );
521 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
523 FcPatternDestroy( pat );
527 /* Find the best font for the pattern, destroy the pattern */
528 p_pat = FcFontMatch( config, pat, &result );
529 FcPatternDestroy( pat );
530 if( !p_pat || result == FcResultNoMatch ) return NULL;
532 /* Check the new pattern */
533 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
534 || ( val_b != FcTrue ) )
536 FcPatternDestroy( p_pat );
539 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
544 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
546 FcPatternDestroy( p_pat );
550 /* if( strcasecmp((const char*)val_s, family ) != 0 )
551 msg_Warn( p_filter, "fontconfig: selected font family is not"
552 "the requested one: '%s' != '%s'\n",
553 (const char*)val_s, family ); */
555 if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
557 FcPatternDestroy( p_pat );
561 FcPatternDestroy( p_pat );
562 return strdup( (const char*)val_s );
568 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
570 static int GetFileFontByName( const char *font_name, char **psz_filename )
573 wchar_t vbuffer[MAX_PATH];
574 wchar_t dbuffer[256];
576 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
580 MultiByteToWideChar( CP_ACP, 0, font_name, -1, dbuffer, 256 );
581 char *font_name_temp = FromWide( dbuffer );
582 size_t fontname_len = strlen( font_name_temp );
584 for( int index = 0;; index++ )
586 DWORD vbuflen = MAX_PATH - 1;
589 LONG i_result = RegEnumValueW( hKey, index, vbuffer, &vbuflen,
590 NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
591 if( i_result != ERROR_SUCCESS )
594 char *psz_value = FromWide( vbuffer );
596 char *s = strchr( psz_value,'(' );
597 if( s != NULL && s != psz_value ) s[-1] = '\0';
599 /* Manage concatenated font names */
600 if( strchr( psz_value, '&') ) {
601 if( strcasestr( psz_value, font_name_temp ) != NULL )
608 if( strncasecmp( psz_value, font_name_temp, fontname_len ) == 0 )
618 *psz_filename = FromWide( dbuffer );
619 free( font_name_temp );
624 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
625 DWORD type, LPARAM lParam)
627 VLC_UNUSED( metric );
628 if( (type & RASTER_FONTTYPE) ) return 1;
629 // if( lpelfe->elfScript ) FIXME
631 return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
634 static char* Win32_Select( filter_t *p_filter, const char* family,
635 bool b_bold, bool b_italic, int i_size, int *i_idx )
637 VLC_UNUSED( i_size );
639 if( !family || strlen( family ) < 1 )
644 lf.lfCharSet = DEFAULT_CHARSET;
648 lf.lfWeight = FW_BOLD;
651 wchar_t* psz_fbuffer = ToWide( family );
652 WideCharToMultiByte( CP_ACP, 0, psz_fbuffer, -1, facename, 32, " ", 0 );
653 strncpy( (LPSTR)&lf.lfFaceName, facename, 32 );
657 char *psz_filename = NULL;
658 HDC hDC = GetDC( NULL );
659 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
660 ReleaseDC(NULL, hDC);
663 if( psz_filename != NULL )
665 /* FIXME: increase i_idx, when concatenated strings */
668 /* Prepend the Windows Font path, when only a filename was provided */
669 if( strchr( psz_filename, DIR_SEP_CHAR ) )
674 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
676 free( psz_filename );
679 free( psz_filename );
683 else /* Let's take any font we can */
687 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, "arial.ttf" ) == -1 )
693 #endif /* HAVE_WIN32 */
695 #endif /* HAVE_STYLES */
698 /*****************************************************************************
699 * RenderYUVP: place string in picture
700 *****************************************************************************
701 * This function merges the previously rendered freetype glyphs into a picture
702 *****************************************************************************/
703 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
707 VLC_UNUSED(p_filter);
708 static const uint8_t pi_gamma[16] =
709 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
710 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
714 int i, x, y, i_pitch;
715 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
717 /* Create a new subpicture region */
718 video_format_Init( &fmt, VLC_CODEC_YUVP );
720 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
722 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
724 assert( !p_region->p_picture );
725 p_region->p_picture = picture_NewFromFormat( &fmt );
726 if( !p_region->p_picture )
728 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
731 /* Calculate text color components
732 * Only use the first color */
733 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
734 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
737 fmt.p_palette->i_entries = 16;
738 for( i = 0; i < 8; i++ )
740 fmt.p_palette->palette[i][0] = 0;
741 fmt.p_palette->palette[i][1] = 0x80;
742 fmt.p_palette->palette[i][2] = 0x80;
743 fmt.p_palette->palette[i][3] = pi_gamma[i];
744 fmt.p_palette->palette[i][3] =
745 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
747 for( i = 8; i < fmt.p_palette->i_entries; i++ )
749 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
750 fmt.p_palette->palette[i][1] = i_u;
751 fmt.p_palette->palette[i][2] = i_v;
752 fmt.p_palette->palette[i][3] = pi_gamma[i];
753 fmt.p_palette->palette[i][3] =
754 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
757 p_dst = p_region->p_picture->Y_PIXELS;
758 i_pitch = p_region->p_picture->Y_PITCH;
760 /* Initialize the region pixels */
761 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
763 for( ; p_line != NULL; p_line = p_line->p_next )
765 int i_align_left = 0;
766 if( p_line->i_width < (int)fmt.i_visible_width )
768 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
769 i_align_left = ( fmt.i_visible_width - p_line->i_width );
770 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
771 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
775 for( i = 0; i < p_line->i_character_count; i++ )
777 const line_character_t *ch = &p_line->p_character[i];
778 FT_BitmapGlyph p_glyph = ch->p_glyph;
780 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
781 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
783 for( y = 0; y < p_glyph->bitmap.rows; y++ )
785 for( x = 0; x < p_glyph->bitmap.width; x++ )
787 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
788 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
789 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
795 /* Outlining (find something better than nearest neighbour filtering ?) */
798 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
799 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
800 uint8_t left, current;
802 for( y = 1; y < (int)fmt.i_height - 1; y++ )
804 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
805 p_dst += p_region->p_picture->Y_PITCH;
808 for( x = 1; x < (int)fmt.i_width - 1; x++ )
811 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
812 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;
816 memset( p_top, 0, fmt.i_width );
822 /*****************************************************************************
823 * RenderYUVA: place string in picture
824 *****************************************************************************
825 * This function merges the previously rendered freetype glyphs into a picture
826 *****************************************************************************/
827 static void FillYUVAPicture( picture_t *p_picture,
828 int i_a, int i_y, int i_u, int i_v )
830 memset( p_picture->p[0].p_pixels, i_y,
831 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
832 memset( p_picture->p[1].p_pixels, i_u,
833 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
834 memset( p_picture->p[2].p_pixels, i_v,
835 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
836 memset( p_picture->p[3].p_pixels, i_a,
837 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
840 static inline void BlendYUVAPixel( picture_t *p_picture,
841 int i_picture_x, int i_picture_y,
842 int i_a, int i_y, int i_u, int i_v,
845 int i_an = i_a * i_alpha / 255;
847 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
848 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
849 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
850 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
862 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
865 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
866 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
867 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
872 static void FillRGBAPicture( picture_t *p_picture,
873 int i_a, int i_r, int i_g, int i_b )
875 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
877 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
879 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
888 static inline void BlendRGBAPixel( picture_t *p_picture,
889 int i_picture_x, int i_picture_y,
890 int i_a, int i_r, int i_g, int i_b,
893 int i_an = i_a * i_alpha / 255;
895 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
897 int i_ao = p_rgba[3];
907 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
910 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
911 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
912 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
917 static inline void BlendAXYZGlyph( picture_t *p_picture,
918 int i_picture_x, int i_picture_y,
919 int i_a, int i_x, int i_y, int i_z,
920 FT_BitmapGlyph p_glyph,
921 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
924 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
926 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
927 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
929 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
933 static inline void BlendAXYZLine( picture_t *p_picture,
934 int i_picture_x, int i_picture_y,
935 int i_a, int i_x, int i_y, int i_z,
936 const line_character_t *p_current,
937 const line_character_t *p_next,
938 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
940 int i_line_width = p_current->p_glyph->bitmap.width;
942 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
944 for( int dx = 0; dx < i_line_width; dx++ )
946 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
947 BlendPixel( p_picture,
949 i_picture_y + p_current->i_line_offset + dy,
950 i_a, i_x, i_y, i_z, 0xff );
954 static inline int RenderAXYZ( filter_t *p_filter,
955 subpicture_region_t *p_region,
956 line_desc_t *p_line_head,
959 vlc_fourcc_t i_chroma,
960 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
961 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
962 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
964 filter_sys_t *p_sys = p_filter->p_sys;
966 /* Create a new subpicture region */
967 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
968 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
970 video_format_Init( &fmt, i_chroma );
972 fmt.i_visible_width = i_text_width + 2 * i_margin;
974 fmt.i_visible_height = i_text_height + 2 * i_margin;
976 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
977 if( !p_region->p_picture )
981 /* Initialize the picture background */
982 uint8_t i_a = p_sys->i_background_opacity;
983 uint8_t i_x, i_y, i_z;
984 ExtractComponents( p_sys->i_background_color, &i_x, &i_y, &i_z );
986 FillPicture( p_picture, i_a, i_x, i_y, i_z );
988 /* Render shadow then outline and then normal glyphs */
989 for( int g = 0; g < 3; g++ )
991 /* Render all lines */
992 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
994 int i_align_left = i_margin;
995 if( p_line->i_width < i_text_width )
997 /* Left offset to take into account alignment */
998 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
999 i_align_left += ( i_text_width - p_line->i_width );
1000 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1001 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1003 int i_align_top = i_margin;
1005 /* Render all glyphs and underline/strikethrough */
1006 for( int i = 0; i < p_line->i_character_count; i++ )
1008 const line_character_t *ch = &p_line->p_character[i];
1009 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
1013 i_a = (ch->i_color >> 24) & 0xff;
1017 i_a = i_a * p_sys->i_shadow_opacity / 255;
1018 i_color = p_sys->i_shadow_color;
1021 i_a = i_a * p_sys->i_outline_opacity / 255;
1022 i_color = p_sys->i_outline_color;
1025 i_color = ch->i_color;
1028 ExtractComponents( i_color, &i_x, &i_y, &i_z );
1030 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
1031 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
1033 BlendAXYZGlyph( p_picture,
1034 i_glyph_x, i_glyph_y,
1039 /* underline/strikethrough are only rendered for the normal glyph */
1040 if( g == 2 && ch->i_line_thickness > 0 )
1041 BlendAXYZLine( p_picture,
1042 i_glyph_x, i_glyph_y + p_glyph->top,
1045 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
1054 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
1055 uint32_t i_font_color, uint32_t i_karaoke_bg_color,
1058 text_style_t *p_style = text_style_New();
1062 p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
1063 p_style->i_font_size = i_font_size;
1064 p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0;
1065 p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
1066 p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0;
1067 p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
1068 p_style->i_style_flags |= i_style_flags;
1072 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1073 uint32_t i_color, uint32_t i_karaoke_bg_color )
1076 return VLC_EGENERIC;
1078 font_stack_t *p_new = malloc( sizeof(*p_new) );
1082 p_new->p_next = NULL;
1085 p_new->psz_name = strdup( psz_name );
1087 p_new->psz_name = NULL;
1089 p_new->i_size = i_size;
1090 p_new->i_color = i_color;
1091 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1099 font_stack_t *p_last;
1101 for( p_last = *p_font;
1103 p_last = p_last->p_next )
1106 p_last->p_next = p_new;
1111 static int PopFont( font_stack_t **p_font )
1113 font_stack_t *p_last, *p_next_to_last;
1115 if( !p_font || !*p_font )
1116 return VLC_EGENERIC;
1118 p_next_to_last = NULL;
1119 for( p_last = *p_font;
1121 p_last = p_last->p_next )
1123 p_next_to_last = p_last;
1126 if( p_next_to_last )
1127 p_next_to_last->p_next = NULL;
1131 free( p_last->psz_name );
1137 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1138 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1140 font_stack_t *p_last;
1142 if( !p_font || !*p_font )
1143 return VLC_EGENERIC;
1145 for( p_last=*p_font;
1147 p_last=p_last->p_next )
1150 *psz_name = p_last->psz_name;
1151 *i_size = p_last->i_size;
1152 *i_color = p_last->i_color;
1153 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1158 static const struct {
1159 const char *psz_name;
1161 } p_html_colors[] = {
1162 /* Official html colors */
1163 { "Aqua", 0x00FFFF },
1164 { "Black", 0x000000 },
1165 { "Blue", 0x0000FF },
1166 { "Fuchsia", 0xFF00FF },
1167 { "Gray", 0x808080 },
1168 { "Green", 0x008000 },
1169 { "Lime", 0x00FF00 },
1170 { "Maroon", 0x800000 },
1171 { "Navy", 0x000080 },
1172 { "Olive", 0x808000 },
1173 { "Purple", 0x800080 },
1174 { "Red", 0xFF0000 },
1175 { "Silver", 0xC0C0C0 },
1176 { "Teal", 0x008080 },
1177 { "White", 0xFFFFFF },
1178 { "Yellow", 0xFFFF00 },
1181 { "AliceBlue", 0xF0F8FF },
1182 { "AntiqueWhite", 0xFAEBD7 },
1183 { "Aqua", 0x00FFFF },
1184 { "Aquamarine", 0x7FFFD4 },
1185 { "Azure", 0xF0FFFF },
1186 { "Beige", 0xF5F5DC },
1187 { "Bisque", 0xFFE4C4 },
1188 { "Black", 0x000000 },
1189 { "BlanchedAlmond", 0xFFEBCD },
1190 { "Blue", 0x0000FF },
1191 { "BlueViolet", 0x8A2BE2 },
1192 { "Brown", 0xA52A2A },
1193 { "BurlyWood", 0xDEB887 },
1194 { "CadetBlue", 0x5F9EA0 },
1195 { "Chartreuse", 0x7FFF00 },
1196 { "Chocolate", 0xD2691E },
1197 { "Coral", 0xFF7F50 },
1198 { "CornflowerBlue", 0x6495ED },
1199 { "Cornsilk", 0xFFF8DC },
1200 { "Crimson", 0xDC143C },
1201 { "Cyan", 0x00FFFF },
1202 { "DarkBlue", 0x00008B },
1203 { "DarkCyan", 0x008B8B },
1204 { "DarkGoldenRod", 0xB8860B },
1205 { "DarkGray", 0xA9A9A9 },
1206 { "DarkGrey", 0xA9A9A9 },
1207 { "DarkGreen", 0x006400 },
1208 { "DarkKhaki", 0xBDB76B },
1209 { "DarkMagenta", 0x8B008B },
1210 { "DarkOliveGreen", 0x556B2F },
1211 { "Darkorange", 0xFF8C00 },
1212 { "DarkOrchid", 0x9932CC },
1213 { "DarkRed", 0x8B0000 },
1214 { "DarkSalmon", 0xE9967A },
1215 { "DarkSeaGreen", 0x8FBC8F },
1216 { "DarkSlateBlue", 0x483D8B },
1217 { "DarkSlateGray", 0x2F4F4F },
1218 { "DarkSlateGrey", 0x2F4F4F },
1219 { "DarkTurquoise", 0x00CED1 },
1220 { "DarkViolet", 0x9400D3 },
1221 { "DeepPink", 0xFF1493 },
1222 { "DeepSkyBlue", 0x00BFFF },
1223 { "DimGray", 0x696969 },
1224 { "DimGrey", 0x696969 },
1225 { "DodgerBlue", 0x1E90FF },
1226 { "FireBrick", 0xB22222 },
1227 { "FloralWhite", 0xFFFAF0 },
1228 { "ForestGreen", 0x228B22 },
1229 { "Fuchsia", 0xFF00FF },
1230 { "Gainsboro", 0xDCDCDC },
1231 { "GhostWhite", 0xF8F8FF },
1232 { "Gold", 0xFFD700 },
1233 { "GoldenRod", 0xDAA520 },
1234 { "Gray", 0x808080 },
1235 { "Grey", 0x808080 },
1236 { "Green", 0x008000 },
1237 { "GreenYellow", 0xADFF2F },
1238 { "HoneyDew", 0xF0FFF0 },
1239 { "HotPink", 0xFF69B4 },
1240 { "IndianRed", 0xCD5C5C },
1241 { "Indigo", 0x4B0082 },
1242 { "Ivory", 0xFFFFF0 },
1243 { "Khaki", 0xF0E68C },
1244 { "Lavender", 0xE6E6FA },
1245 { "LavenderBlush", 0xFFF0F5 },
1246 { "LawnGreen", 0x7CFC00 },
1247 { "LemonChiffon", 0xFFFACD },
1248 { "LightBlue", 0xADD8E6 },
1249 { "LightCoral", 0xF08080 },
1250 { "LightCyan", 0xE0FFFF },
1251 { "LightGoldenRodYellow", 0xFAFAD2 },
1252 { "LightGray", 0xD3D3D3 },
1253 { "LightGrey", 0xD3D3D3 },
1254 { "LightGreen", 0x90EE90 },
1255 { "LightPink", 0xFFB6C1 },
1256 { "LightSalmon", 0xFFA07A },
1257 { "LightSeaGreen", 0x20B2AA },
1258 { "LightSkyBlue", 0x87CEFA },
1259 { "LightSlateGray", 0x778899 },
1260 { "LightSlateGrey", 0x778899 },
1261 { "LightSteelBlue", 0xB0C4DE },
1262 { "LightYellow", 0xFFFFE0 },
1263 { "Lime", 0x00FF00 },
1264 { "LimeGreen", 0x32CD32 },
1265 { "Linen", 0xFAF0E6 },
1266 { "Magenta", 0xFF00FF },
1267 { "Maroon", 0x800000 },
1268 { "MediumAquaMarine", 0x66CDAA },
1269 { "MediumBlue", 0x0000CD },
1270 { "MediumOrchid", 0xBA55D3 },
1271 { "MediumPurple", 0x9370D8 },
1272 { "MediumSeaGreen", 0x3CB371 },
1273 { "MediumSlateBlue", 0x7B68EE },
1274 { "MediumSpringGreen", 0x00FA9A },
1275 { "MediumTurquoise", 0x48D1CC },
1276 { "MediumVioletRed", 0xC71585 },
1277 { "MidnightBlue", 0x191970 },
1278 { "MintCream", 0xF5FFFA },
1279 { "MistyRose", 0xFFE4E1 },
1280 { "Moccasin", 0xFFE4B5 },
1281 { "NavajoWhite", 0xFFDEAD },
1282 { "Navy", 0x000080 },
1283 { "OldLace", 0xFDF5E6 },
1284 { "Olive", 0x808000 },
1285 { "OliveDrab", 0x6B8E23 },
1286 { "Orange", 0xFFA500 },
1287 { "OrangeRed", 0xFF4500 },
1288 { "Orchid", 0xDA70D6 },
1289 { "PaleGoldenRod", 0xEEE8AA },
1290 { "PaleGreen", 0x98FB98 },
1291 { "PaleTurquoise", 0xAFEEEE },
1292 { "PaleVioletRed", 0xD87093 },
1293 { "PapayaWhip", 0xFFEFD5 },
1294 { "PeachPuff", 0xFFDAB9 },
1295 { "Peru", 0xCD853F },
1296 { "Pink", 0xFFC0CB },
1297 { "Plum", 0xDDA0DD },
1298 { "PowderBlue", 0xB0E0E6 },
1299 { "Purple", 0x800080 },
1300 { "Red", 0xFF0000 },
1301 { "RosyBrown", 0xBC8F8F },
1302 { "RoyalBlue", 0x4169E1 },
1303 { "SaddleBrown", 0x8B4513 },
1304 { "Salmon", 0xFA8072 },
1305 { "SandyBrown", 0xF4A460 },
1306 { "SeaGreen", 0x2E8B57 },
1307 { "SeaShell", 0xFFF5EE },
1308 { "Sienna", 0xA0522D },
1309 { "Silver", 0xC0C0C0 },
1310 { "SkyBlue", 0x87CEEB },
1311 { "SlateBlue", 0x6A5ACD },
1312 { "SlateGray", 0x708090 },
1313 { "SlateGrey", 0x708090 },
1314 { "Snow", 0xFFFAFA },
1315 { "SpringGreen", 0x00FF7F },
1316 { "SteelBlue", 0x4682B4 },
1317 { "Tan", 0xD2B48C },
1318 { "Teal", 0x008080 },
1319 { "Thistle", 0xD8BFD8 },
1320 { "Tomato", 0xFF6347 },
1321 { "Turquoise", 0x40E0D0 },
1322 { "Violet", 0xEE82EE },
1323 { "Wheat", 0xF5DEB3 },
1324 { "White", 0xFFFFFF },
1325 { "WhiteSmoke", 0xF5F5F5 },
1326 { "Yellow", 0xFFFF00 },
1327 { "YellowGreen", 0x9ACD32 },
1332 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1333 font_stack_t **p_fonts )
1336 char *psz_fontname = NULL;
1337 uint32_t i_font_color = 0xffffff;
1338 int i_font_alpha = 255;
1339 uint32_t i_karaoke_bg_color = 0x00ffffff;
1340 int i_font_size = 24;
1342 /* Default all attributes to the top font in the stack -- in case not
1343 * all attributes are specified in the sub-font
1345 if( VLC_SUCCESS == PeekFont( p_fonts,
1349 &i_karaoke_bg_color ))
1351 psz_fontname = strdup( psz_fontname );
1352 i_font_size = i_font_size;
1354 i_font_alpha = (i_font_color >> 24) & 0xff;
1355 i_font_color &= 0x00ffffff;
1357 const char *name, *value;
1358 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1360 if( !strcasecmp( "face", name ) )
1362 free( psz_fontname );
1363 psz_fontname = strdup( value );
1365 else if( !strcasecmp( "size", name ) )
1367 if( ( *value == '+' ) || ( *value == '-' ) )
1369 int i_value = atoi( value );
1371 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1372 i_font_size += ( i_value * i_font_size ) / 10;
1373 else if( i_value < -5 )
1374 i_font_size = - i_value;
1375 else if( i_value > 5 )
1376 i_font_size = i_value;
1379 i_font_size = atoi( value );
1381 else if( !strcasecmp( "color", name ) )
1383 if( value[0] == '#' )
1385 i_font_color = strtol( value + 1, NULL, 16 );
1386 i_font_color &= 0x00ffffff;
1391 uint32_t i_value = strtol( value, &end, 16 );
1392 if( *end == '\0' || *end == ' ' )
1393 i_font_color = i_value & 0x00ffffff;
1395 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1397 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1399 i_font_color = p_html_colors[i].i_value;
1405 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1407 i_font_alpha = strtol( value + 1, NULL, 16 );
1408 i_font_alpha &= 0xff;
1411 rv = PushFont( p_fonts,
1414 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1415 i_karaoke_bg_color );
1417 free( psz_fontname );
1422 /* Turn any multiple-whitespaces into single spaces */
1423 static void HandleWhiteSpace( char *psz_node )
1425 char *s = strpbrk( psz_node, "\t\r\n " );
1428 int i_whitespace = strspn( s, "\t\r\n " );
1430 if( i_whitespace > 1 )
1433 strlen( s ) - i_whitespace + 1 );
1436 s = strpbrk( s, "\t\r\n " );
1441 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1442 font_stack_t **p_fonts,
1445 char *psz_fontname = NULL;
1446 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1447 uint32_t i_karaoke_bg_color = i_font_color;
1448 int i_font_size = p_sys->i_font_size;
1450 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1451 &i_font_color, &i_karaoke_bg_color ) )
1454 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1459 static unsigned SetupText( filter_t *p_filter,
1460 uni_char_t *psz_text_out,
1461 text_style_t **pp_styles,
1462 uint32_t *pi_k_dates,
1464 const char *psz_text_in,
1465 text_style_t *p_style,
1468 size_t i_string_length;
1470 size_t i_string_bytes;
1471 uni_char_t *psz_tmp = ToCharset( FREETYPE_TO_UCS, psz_text_in, &i_string_bytes );
1474 memcpy( psz_text_out, psz_tmp, i_string_bytes );
1475 i_string_length = i_string_bytes / sizeof( *psz_tmp );
1480 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1481 i_string_length = 0;
1484 if( i_string_length > 0 )
1486 for( unsigned i = 0; i < i_string_length; i++ )
1487 pp_styles[i] = p_style;
1491 text_style_Delete( p_style );
1493 if( i_string_length > 0 && pi_k_dates )
1495 for( unsigned i = 0; i < i_string_length; i++ )
1496 pi_k_dates[i] = i_k_date;
1498 return i_string_length;
1501 static int ProcessNodes( filter_t *p_filter,
1502 uni_char_t *psz_text,
1503 text_style_t **pp_styles,
1504 uint32_t *pi_k_dates,
1506 xml_reader_t *p_xml_reader,
1507 text_style_t *p_font_style )
1509 int rv = VLC_SUCCESS;
1510 filter_sys_t *p_sys = p_filter->p_sys;
1511 int i_text_length = 0;
1512 font_stack_t *p_fonts = NULL;
1513 uint32_t i_k_date = 0;
1515 int i_style_flags = 0;
1519 rv = PushFont( &p_fonts,
1520 p_font_style->psz_fontname,
1521 p_font_style->i_font_size > 0 ? p_font_style->i_font_size
1522 : p_sys->i_font_size,
1523 (p_font_style->i_font_color & 0xffffff) |
1524 ((p_font_style->i_font_alpha & 0xff) << 24),
1525 (p_font_style->i_karaoke_background_color & 0xffffff) |
1526 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1528 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1536 rv = PushFont( &p_fonts,
1537 p_sys->psz_fontfamily,
1539 (p_sys->i_font_color & 0xffffff) |
1540 ((p_sys->i_font_opacity & 0xff) << 24),
1544 if( p_sys->b_font_bold )
1545 i_style_flags |= STYLE_BOLD;
1547 if( rv != VLC_SUCCESS )
1553 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1557 case XML_READER_ENDELEM:
1558 if( !strcasecmp( "font", node ) )
1559 PopFont( &p_fonts );
1560 else if( !strcasecmp( "b", node ) )
1561 i_style_flags &= ~STYLE_BOLD;
1562 else if( !strcasecmp( "i", node ) )
1563 i_style_flags &= ~STYLE_ITALIC;
1564 else if( !strcasecmp( "u", node ) )
1565 i_style_flags &= ~STYLE_UNDERLINE;
1566 else if( !strcasecmp( "s", node ) )
1567 i_style_flags &= ~STYLE_STRIKEOUT;
1570 case XML_READER_STARTELEM:
1571 if( !strcasecmp( "font", node ) )
1572 HandleFontAttributes( p_xml_reader, &p_fonts );
1573 else if( !strcasecmp( "b", node ) )
1574 i_style_flags |= STYLE_BOLD;
1575 else if( !strcasecmp( "i", node ) )
1576 i_style_flags |= STYLE_ITALIC;
1577 else if( !strcasecmp( "u", node ) )
1578 i_style_flags |= STYLE_UNDERLINE;
1579 else if( !strcasecmp( "s", node ) )
1580 i_style_flags |= STYLE_STRIKEOUT;
1581 else if( !strcasecmp( "br", node ) )
1583 i_text_length += SetupText( p_filter,
1584 &psz_text[i_text_length],
1585 &pp_styles[i_text_length],
1586 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1588 GetStyleFromFontStack( p_sys,
1593 else if( !strcasecmp( "k", node ) )
1596 const char *name, *value;
1597 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1599 if( !strcasecmp( "t", name ) && value )
1600 i_k_date += atoi( value );
1605 case XML_READER_TEXT:
1607 char *psz_node = strdup( node );
1608 if( unlikely(!psz_node) )
1611 HandleWhiteSpace( psz_node );
1612 resolve_xml_special_chars( psz_node );
1614 i_text_length += SetupText( p_filter,
1615 &psz_text[i_text_length],
1616 &pp_styles[i_text_length],
1617 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1619 GetStyleFromFontStack( p_sys,
1629 *pi_len = i_text_length;
1631 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1636 static void FreeLine( line_desc_t *p_line )
1638 for( int i = 0; i < p_line->i_character_count; i++ )
1640 line_character_t *ch = &p_line->p_character[i];
1641 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1643 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1645 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1648 free( p_line->p_character );
1652 static void FreeLines( line_desc_t *p_lines )
1654 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1656 line_desc_t *p_next = p_line->p_next;
1662 static line_desc_t *NewLine( int i_count )
1664 line_desc_t *p_line = malloc( sizeof(*p_line) );
1669 p_line->p_next = NULL;
1670 p_line->i_width = 0;
1671 p_line->i_base_line = 0;
1672 p_line->i_character_count = 0;
1674 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1675 if( !p_line->p_character )
1683 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1685 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1687 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1689 FT_Face p_face = NULL;
1691 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1699 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1700 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1701 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1702 (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC)) == i_style_received )
1705 FT_Done_Face( p_face );
1713 static FT_Face LoadFace( filter_t *p_filter,
1714 const text_style_t *p_style )
1716 filter_sys_t *p_sys = p_filter->p_sys;
1718 /* Look for a match amongst our attachments first */
1719 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1721 /* Load system wide font otheriwse */
1726 #ifdef HAVE_FONTCONFIG
1727 psz_fontfile = FontConfig_Select( NULL,
1728 p_style->psz_fontname,
1729 (p_style->i_style_flags & STYLE_BOLD) != 0,
1730 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1733 #elif defined( WIN32 )
1734 psz_fontfile = Win32_Select( p_filter,
1735 p_style->psz_fontname,
1736 (p_style->i_style_flags & STYLE_BOLD) != 0,
1737 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1741 psz_fontfile = NULL;
1746 if( *psz_fontfile == '\0' )
1749 "We were not able to find a matching font: \"%s\" (%s %s),"
1750 " so using default font",
1751 p_style->psz_fontname,
1752 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1753 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1758 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1761 free( psz_fontfile );
1766 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1768 /* We've loaded a font face which is unhelpful for actually
1769 * rendering text - fallback to the default one.
1771 FT_Done_Face( p_face );
1777 static bool FaceStyleEquals( const text_style_t *p_style1,
1778 const text_style_t *p_style2 )
1780 if( !p_style1 || !p_style2 )
1782 if( p_style1 == p_style2 )
1785 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
1786 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
1787 !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
1790 static int GetGlyph( filter_t *p_filter,
1791 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1792 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1793 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1799 FT_Vector *p_pen_shadow )
1801 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1802 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1804 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1805 return VLC_EGENERIC;
1808 /* Do synthetic styling now that Freetype supports it;
1809 * ie. if the font we have loaded is NOT already in the
1810 * style that the tags want, then switch it on; if they
1811 * are then don't. */
1812 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1813 FT_GlyphSlot_Embolden( p_face->glyph );
1814 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1815 FT_GlyphSlot_Oblique( p_face->glyph );
1818 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1820 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1821 return VLC_EGENERIC;
1824 FT_Glyph outline = NULL;
1825 if( p_filter->p_sys->p_stroker )
1828 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1832 FT_Glyph shadow = NULL;
1833 if( p_filter->p_sys->i_shadow_opacity > 0 )
1835 shadow = outline ? outline : glyph;
1836 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1842 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1845 *pp_shadow = shadow;
1847 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1849 FT_Done_Glyph( glyph );
1851 FT_Done_Glyph( outline );
1853 FT_Done_Glyph( shadow );
1854 return VLC_EGENERIC;
1856 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1861 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1862 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1864 *pp_outline = outline;
1869 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1871 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1872 if( p_bbox->xMin >= p_bbox->xMax )
1874 p_bbox->xMin = FT_CEIL(p_pen->x);
1875 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1876 glyph_bmp->left = p_bbox->xMin;
1878 if( p_bbox->yMin >= p_bbox->yMax )
1880 p_bbox->yMax = FT_CEIL(p_pen->y);
1881 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1882 glyph_bmp->top = p_bbox->yMax;
1886 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1888 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1889 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1890 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1891 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1894 static int ProcessLines( filter_t *p_filter,
1895 line_desc_t **pp_lines,
1897 int *pi_max_face_height,
1899 uni_char_t *psz_text,
1900 text_style_t **pp_styles,
1901 uint32_t *pi_k_dates,
1904 filter_sys_t *p_sys = p_filter->p_sys;
1905 uni_char_t *p_fribidi_string = NULL;
1906 text_style_t **pp_fribidi_styles = NULL;
1907 int *p_new_positions = NULL;
1909 #if defined(HAVE_FRIBIDI)
1911 int *p_old_positions;
1912 int start_pos, pos = 0;
1914 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1916 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1917 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1918 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1920 if( ! pp_fribidi_styles ||
1921 ! p_fribidi_string ||
1922 ! p_old_positions ||
1925 free( p_old_positions );
1926 free( p_new_positions );
1927 free( p_fribidi_string );
1928 free( pp_fribidi_styles );
1932 /* Do bidi conversion line-by-line */
1935 while(pos < i_len) {
1936 if (psz_text[pos] != '\n')
1938 p_fribidi_string[pos] = psz_text[pos];
1939 pp_fribidi_styles[pos] = pp_styles[pos];
1940 p_new_positions[pos] = pos;
1944 while(pos < i_len) {
1945 if (psz_text[pos] == '\n')
1949 if (pos > start_pos)
1951 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1952 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1954 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1956 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1957 pos - start_pos, &base_dir,
1958 (FriBidiChar*)p_fribidi_string + start_pos,
1959 p_new_positions + start_pos,
1962 for( int j = start_pos; j < pos; j++ )
1964 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1965 p_new_positions[ j ] += start_pos;
1969 p_fribidi_string[ i_len ] = 0;
1970 free( p_old_positions );
1972 pp_styles = pp_fribidi_styles;
1973 psz_text = p_fribidi_string;
1976 /* Work out the karaoke */
1977 uint8_t *pi_karaoke_bar = NULL;
1980 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1981 if( pi_karaoke_bar )
1983 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1984 for( int i = 0; i < i_len; i++ )
1986 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1987 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1991 free( p_new_positions );
1993 *pi_max_face_height = 0;
1995 line_desc_t **pp_line_next = pp_lines;
2003 int i_face_height_previous = 0;
2004 int i_base_line = 0;
2005 const text_style_t *p_previous_style = NULL;
2006 FT_Face p_face = NULL;
2007 for( int i_start = 0; i_start < i_len; )
2009 /* Compute the length of the current text line */
2011 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
2014 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
2015 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
2016 int i_index = i_start;
2021 int i_face_height = 0;
2022 FT_BBox line_bbox = {
2028 int i_ul_offset = 0;
2029 int i_ul_thickness = 0;
2038 break_point_t break_point;
2039 break_point_t break_point_fallback;
2041 #define SAVE_BP(dst) do { \
2042 dst.i_index = i_index; \
2044 dst.line_bbox = line_bbox; \
2045 dst.i_face_height = i_face_height; \
2046 dst.i_ul_offset = i_ul_offset; \
2047 dst.i_ul_thickness = i_ul_thickness; \
2050 SAVE_BP( break_point );
2051 SAVE_BP( break_point_fallback );
2053 while( i_index < i_start + i_length )
2055 /* Split by common FT_Face + Size */
2056 const text_style_t *p_current_style = pp_styles[i_index];
2057 int i_part_length = 0;
2058 while( i_index + i_part_length < i_start + i_length )
2060 const text_style_t *p_style = pp_styles[i_index + i_part_length];
2061 if( !FaceStyleEquals( p_style, p_current_style ) ||
2062 p_style->i_font_size != p_current_style->i_font_size )
2067 /* (Re)load/reconfigure the face if needed */
2068 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
2071 FT_Done_Face( p_face );
2072 p_previous_style = NULL;
2074 p_face = LoadFace( p_filter, p_current_style );
2076 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
2077 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
2079 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
2080 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
2081 if( p_sys->p_stroker )
2083 int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
2084 FT_Stroker_Set( p_sys->p_stroker,
2086 FT_STROKER_LINECAP_ROUND,
2087 FT_STROKER_LINEJOIN_ROUND, 0 );
2090 p_previous_style = p_current_style;
2092 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
2093 p_current_face->size->metrics.y_scale)));
2095 /* Render the part */
2096 bool b_break_line = false;
2097 int i_glyph_last = 0;
2098 while( i_part_length > 0 )
2100 const text_style_t *p_glyph_style = pp_styles[i_index];
2101 uni_char_t character = psz_text[i_index];
2102 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
2104 /* Get kerning vector */
2105 FT_Vector kerning = { .x = 0, .y = 0 };
2106 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
2107 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
2109 /* Get the glyph bitmap and its bounding box and all the associated properties */
2110 FT_Vector pen_new = {
2111 .x = pen.x + kerning.x,
2112 .y = pen.y + kerning.y,
2114 FT_Vector pen_shadow_new = {
2115 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
2116 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
2121 FT_BBox outline_bbox;
2123 FT_BBox shadow_bbox;
2125 if( GetGlyph( p_filter,
2126 &glyph, &glyph_bbox,
2127 &outline, &outline_bbox,
2128 &shadow, &shadow_bbox,
2129 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
2130 &pen_new, &pen_shadow_new ) )
2133 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
2135 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
2137 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
2139 /* FIXME and what about outline */
2141 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
2142 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
2143 (p_glyph_style->i_karaoke_background_alpha << 24))
2144 : (p_glyph_style->i_font_color |
2145 (p_glyph_style->i_font_alpha << 24));
2146 int i_line_offset = 0;
2147 int i_line_thickness = 0;
2148 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
2150 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
2151 p_current_face->size->metrics.y_scale)) );
2153 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
2154 p_current_face->size->metrics.y_scale)) );
2156 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
2158 /* Move the baseline to make it strikethrough instead of
2159 * underline. That means that strikethrough takes precedence
2161 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
2162 p_current_face->size->metrics.y_scale)) );
2164 else if( i_line_thickness > 0 )
2166 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
2168 /* The real underline thickness and position are
2169 * updated once the whole line has been parsed */
2170 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
2171 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
2172 i_line_thickness = -1;
2175 FT_BBox line_bbox_new = line_bbox;
2176 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
2178 BBoxEnlarge( &line_bbox_new, &outline_bbox );
2180 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
2182 b_break_line = i_index > i_start &&
2183 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
2186 FT_Done_Glyph( glyph );
2188 FT_Done_Glyph( outline );
2190 FT_Done_Glyph( shadow );
2192 break_point_t *p_bp = NULL;
2193 if( break_point.i_index > i_start )
2194 p_bp = &break_point;
2195 else if( break_point_fallback.i_index > i_start )
2196 p_bp = &break_point_fallback;
2200 msg_Dbg( p_filter, "Breaking line");
2201 for( int i = p_bp->i_index; i < i_index; i++ )
2203 line_character_t *ch = &p_line->p_character[i - i_start];
2204 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
2206 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
2208 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
2210 p_line->i_character_count = p_bp->i_index - i_start;
2212 i_index = p_bp->i_index;
2214 line_bbox = p_bp->line_bbox;
2215 i_face_height = p_bp->i_face_height;
2216 i_ul_offset = p_bp->i_ul_offset;
2217 i_ul_thickness = p_bp->i_ul_thickness;
2221 msg_Err( p_filter, "Breaking unbreakable line");
2226 assert( p_line->i_character_count == i_index - i_start);
2227 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2228 .p_glyph = (FT_BitmapGlyph)glyph,
2229 .p_outline = (FT_BitmapGlyph)outline,
2230 .p_shadow = (FT_BitmapGlyph)shadow,
2232 .i_line_offset = i_line_offset,
2233 .i_line_thickness = i_line_thickness,
2236 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2237 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2238 line_bbox = line_bbox_new;
2240 i_glyph_last = i_glyph_index;
2244 if( character == ' ' || character == '\t' )
2245 SAVE_BP( break_point );
2246 else if( character == 160 )
2247 SAVE_BP( break_point_fallback );
2253 /* Update our baseline */
2254 if( i_face_height_previous > 0 )
2255 i_base_line += __MAX(i_face_height, i_face_height_previous);
2256 if( i_face_height > 0 )
2257 i_face_height_previous = i_face_height;
2259 /* Update the line bbox with the actual base line */
2260 if (line_bbox.yMax > line_bbox.yMin) {
2261 line_bbox.yMin -= i_base_line;
2262 line_bbox.yMax -= i_base_line;
2264 BBoxEnlarge( &bbox, &line_bbox );
2266 /* Terminate and append the line */
2269 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2270 p_line->i_base_line = i_base_line;
2271 if( i_ul_thickness > 0 )
2273 for( int i = 0; i < p_line->i_character_count; i++ )
2275 line_character_t *ch = &p_line->p_character[i];
2276 if( ch->i_line_thickness < 0 )
2278 ch->i_line_offset = i_ul_offset;
2279 ch->i_line_thickness = i_ul_thickness;
2284 *pp_line_next = p_line;
2285 pp_line_next = &p_line->p_next;
2288 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2290 /* Skip what we have rendered and the line delimitor if present */
2292 if( i_start < i_len && psz_text[i_start] == '\n' )
2295 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
2297 msg_Err( p_filter, "Truncated too high subtitle" );
2302 FT_Done_Face( p_face );
2304 free( pp_fribidi_styles );
2305 free( p_fribidi_string );
2306 free( pi_karaoke_bar );
2313 * This function renders a text subpicture region into another one.
2314 * It also calculates the size needed for this string, and renders the
2315 * needed glyphs into memory. It is used as pf_add_string callback in
2316 * the vout method by this module
2318 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2319 subpicture_region_t *p_region_in, bool b_html,
2320 const vlc_fourcc_t *p_chroma_list )
2322 filter_sys_t *p_sys = p_filter->p_sys;
2325 return VLC_EGENERIC;
2326 if( b_html && !p_region_in->psz_html )
2327 return VLC_EGENERIC;
2328 if( !b_html && !p_region_in->psz_text )
2329 return VLC_EGENERIC;
2331 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2332 : p_region_in->psz_text );
2334 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2335 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2336 if( !psz_text || !pp_styles )
2340 return VLC_EGENERIC;
2343 /* Reset the default fontsize in case screen metrics have changed */
2344 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2347 int rv = VLC_SUCCESS;
2348 int i_text_length = 0;
2350 int i_max_face_height;
2351 line_desc_t *p_lines = NULL;
2353 uint32_t *pi_k_durations = NULL;
2358 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2359 (uint8_t *) p_region_in->psz_html,
2360 strlen( p_region_in->psz_html ),
2362 if( unlikely(p_sub == NULL) )
2365 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2367 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2369 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2370 p_filter->p_sys->p_xml = p_xml_reader;
2377 /* Look for Root Node */
2380 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2382 if( strcasecmp( "karaoke", node ) == 0 )
2384 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2386 else if( strcasecmp( "text", node ) != 0 )
2388 /* Only text and karaoke tags are supported */
2389 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2396 msg_Err( p_filter, "Malformed HTML subtitle" );
2402 rv = ProcessNodes( p_filter,
2403 psz_text, pp_styles, pi_k_durations, &i_text_length,
2404 p_xml_reader, p_region_in->p_style );
2408 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2410 stream_Delete( p_sub );
2415 text_style_t *p_style;
2416 if( p_region_in->p_style )
2417 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2418 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2419 : p_sys->i_font_size,
2420 (p_region_in->p_style->i_font_color & 0xffffff) |
2421 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2423 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2428 p_style = CreateStyle( p_sys->psz_fontfamily,
2430 (p_sys->i_font_color & 0xffffff) |
2431 ((p_sys->i_font_opacity & 0xff) << 24),
2433 if( p_sys->b_font_bold )
2434 p_style->i_style_flags |= STYLE_BOLD;
2436 i_text_length = SetupText( p_filter,
2440 p_region_in->psz_text, p_style, 0 );
2443 if( !rv && i_text_length > 0 )
2445 rv = ProcessLines( p_filter,
2446 &p_lines, &bbox, &i_max_face_height,
2447 psz_text, pp_styles, pi_k_durations, i_text_length );
2450 p_region_out->i_x = p_region_in->i_x;
2451 p_region_out->i_y = p_region_in->i_y;
2453 /* Don't attempt to render text that couldn't be layed out
2455 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2457 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2458 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2460 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2461 p_chroma_list = p_chroma_list_yuvp;
2462 else if( !p_chroma_list || *p_chroma_list == 0 )
2463 p_chroma_list = p_chroma_list_rgba;
2465 const int i_margin = p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2466 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2469 if( *p_chroma == VLC_CODEC_YUVP )
2470 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2471 else if( *p_chroma == VLC_CODEC_YUVA )
2472 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2477 else if( *p_chroma == VLC_CODEC_RGBA )
2478 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2487 /* With karaoke, we're going to have to render the text a number
2488 * of times to show the progress marker on the text.
2490 if( pi_k_durations )
2491 var_SetBool( p_filter, "text-rerender", true );
2494 FreeLines( p_lines );
2497 for( int i = 0; i < i_text_length; i++ )
2499 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2500 text_style_Delete( pp_styles[i] );
2503 free( pi_k_durations );
2508 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2509 subpicture_region_t *p_region_in,
2510 const vlc_fourcc_t *p_chroma_list )
2512 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2517 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2518 subpicture_region_t *p_region_in,
2519 const vlc_fourcc_t *p_chroma_list )
2521 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2526 /*****************************************************************************
2527 * Create: allocates osd-text video thread output method
2528 *****************************************************************************
2529 * This function allocates and initializes a Clone vout method.
2530 *****************************************************************************/
2531 static int Create( vlc_object_t *p_this )
2533 filter_t *p_filter = (filter_t *)p_this;
2534 filter_sys_t *p_sys;
2535 char *psz_fontfile = NULL;
2536 char *psz_fontfamily = NULL;
2537 int i_error = 0, fontindex = 0;
2539 /* Allocate structure */
2540 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2544 p_sys->psz_fontfamily = NULL;
2546 p_sys->p_xml = NULL;
2549 p_sys->p_library = 0;
2550 p_sys->i_font_size = 0;
2551 p_sys->i_display_height = 0;
2553 var_Create( p_filter, "freetype-rel-fontsize",
2554 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2556 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2557 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2558 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2559 p_sys->i_font_opacity = VLC_CLIP( p_sys->i_font_opacity, 0, 255 );
2560 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2561 p_sys->i_font_color = VLC_CLIP( p_sys->i_font_color, 0, 0xFFFFFF );
2562 p_sys->b_font_bold = var_InheritBool( p_filter, "freetype-bold" );
2564 p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
2565 p_sys->i_background_opacity = VLC_CLIP( p_sys->i_background_opacity, 0, 255 );
2566 p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
2567 p_sys->i_background_color = VLC_CLIP( p_sys->i_background_color, 0, 0xFFFFFF );
2569 p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2570 p_sys->f_outline_thickness = VLC_CLIP( p_sys->f_outline_thickness, 0.0, 0.5 );
2571 p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2572 p_sys->i_outline_opacity = VLC_CLIP( p_sys->i_outline_opacity, 0, 255 );
2573 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2574 p_sys->i_outline_color = VLC_CLIP( p_sys->i_outline_color, 0, 0xFFFFFF );
2576 p_sys->i_shadow_opacity = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2577 p_sys->i_shadow_opacity = VLC_CLIP( p_sys->i_shadow_opacity, 0, 255 );
2578 p_sys->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2579 p_sys->i_shadow_color = VLC_CLIP( p_sys->i_shadow_color, 0, 0xFFFFFF );
2580 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2581 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2582 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
2583 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2584 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2587 /* Get Windows Font folder */
2588 wchar_t wdir[MAX_PATH];
2589 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2591 GetWindowsDirectoryW( wdir, MAX_PATH );
2592 wcscat( wdir, L"\\fonts" );
2594 p_sys->psz_win_fonts_path = FromWide( wdir );
2597 /* Set default psz_fontfamily */
2598 if( !psz_fontfamily || !*psz_fontfamily )
2600 free( psz_fontfamily );
2602 psz_fontfamily = strdup( DEFAULT_FAMILY );
2605 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2608 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2610 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2614 /* Set the current font file */
2615 p_sys->psz_fontfamily = psz_fontfamily;
2617 #ifdef HAVE_FONTCONFIG
2618 FontConfig_BuildCache( p_filter );
2621 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2622 p_sys->i_default_font_size, &fontindex );
2623 #elif defined(WIN32)
2624 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2625 p_sys->i_default_font_size, &fontindex );
2628 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2630 /* If nothing is found, use the default family */
2632 psz_fontfile = strdup( psz_fontfamily );
2634 #else /* !HAVE_STYLES */
2635 /* Use the default file */
2636 psz_fontfile = psz_fontfamily;
2640 i_error = FT_Init_FreeType( &p_sys->p_library );
2643 msg_Err( p_filter, "couldn't initialize freetype" );
2647 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2648 fontindex, &p_sys->p_face );
2650 if( i_error == FT_Err_Unknown_File_Format )
2652 msg_Err( p_filter, "file %s have unknown format",
2653 psz_fontfile ? psz_fontfile : "(null)" );
2658 msg_Err( p_filter, "failed to load font file %s",
2659 psz_fontfile ? psz_fontfile : "(null)" );
2663 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2666 msg_Err( p_filter, "font has no unicode translation table" );
2670 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2672 p_sys->p_stroker = NULL;
2673 if( p_sys->f_outline_thickness > 0.001 )
2675 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2677 msg_Err( p_filter, "Failed to create stroker for outlining" );
2680 p_sys->pp_font_attachments = NULL;
2681 p_sys->i_font_attachments = 0;
2683 p_filter->pf_render_text = RenderText;
2685 p_filter->pf_render_html = RenderHtml;
2687 p_filter->pf_render_html = NULL;
2690 LoadFontsFromAttachments( p_filter );
2693 free( psz_fontfile );
2699 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2700 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2702 free( psz_fontfile );
2704 free( psz_fontfamily );
2706 return VLC_EGENERIC;
2709 /*****************************************************************************
2710 * Destroy: destroy Clone video thread output method
2711 *****************************************************************************
2712 * Clean up all data and library connections
2713 *****************************************************************************/
2714 static void Destroy( vlc_object_t *p_this )
2716 filter_t *p_filter = (filter_t *)p_this;
2717 filter_sys_t *p_sys = p_filter->p_sys;
2719 if( p_sys->pp_font_attachments )
2721 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2722 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2724 free( p_sys->pp_font_attachments );
2728 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2730 free( p_sys->psz_fontfamily );
2732 /* FcFini asserts calling the subfunction FcCacheFini()
2733 * even if no other library functions have been made since FcInit(),
2734 * so don't call it. */
2736 if( p_sys->p_stroker )
2737 FT_Stroker_Done( p_sys->p_stroker );
2738 FT_Done_Face( p_sys->p_face );
2739 FT_Done_FreeType( p_sys->p_library );