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 size_t fontname_len = strlen( font_name );
578 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
582 for( int index = 0;; index++ )
584 DWORD vbuflen = MAX_PATH - 1;
587 LONG i_result = RegEnumValueW( hKey, index, vbuffer, &vbuflen,
588 NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
589 if( i_result != ERROR_SUCCESS )
592 char *psz_value = FromWide( vbuffer );
594 char *s = strchr( psz_value,'(' );
595 if( s != NULL && s != psz_value ) s[-1] = '\0';
597 /* Manage concatenated font names */
598 if( strchr( psz_value, '&') ) {
599 if( strcasestr( psz_value, font_name ) != NULL )
603 if( strncasecmp( psz_value, font_name, fontname_len ) == 0 )
608 *psz_filename = FromWide( dbuffer );
613 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
614 DWORD type, LPARAM lParam)
616 VLC_UNUSED( metric );
617 if( (type & RASTER_FONTTYPE) ) return 1;
618 // if( lpelfe->elfScript ) FIXME
620 return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
623 static char* Win32_Select( filter_t *p_filter, const char* family,
624 bool b_bold, bool b_italic, int i_size, int *i_idx )
626 VLC_UNUSED( i_size );
628 if( strlen( family ) < 1 )
633 lf.lfCharSet = DEFAULT_CHARSET;
637 lf.lfWeight = FW_BOLD;
638 strncpy( (LPSTR)&lf.lfFaceName, family, 32);
641 char *psz_filename = NULL;
642 HDC hDC = GetDC( NULL );
643 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
644 ReleaseDC(NULL, hDC);
647 if( psz_filename != NULL )
649 /* FIXME: increase i_idx, when concatenated strings */
652 /* Prepend the Windows Font path, when only a filename was provided */
653 if( strchr( psz_filename, DIR_SEP_CHAR ) )
658 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
660 free( psz_filename );
663 free( psz_filename );
667 else /* Let's take any font we can */
671 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, "arial.ttf" ) == -1 )
677 #endif /* HAVE_WIN32 */
679 #endif /* HAVE_STYLES */
682 /*****************************************************************************
683 * RenderYUVP: place string in picture
684 *****************************************************************************
685 * This function merges the previously rendered freetype glyphs into a picture
686 *****************************************************************************/
687 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
691 VLC_UNUSED(p_filter);
692 static const uint8_t pi_gamma[16] =
693 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
694 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
698 int i, x, y, i_pitch;
699 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
701 /* Create a new subpicture region */
702 video_format_Init( &fmt, VLC_CODEC_YUVP );
704 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
706 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
708 assert( !p_region->p_picture );
709 p_region->p_picture = picture_NewFromFormat( &fmt );
710 if( !p_region->p_picture )
712 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
715 /* Calculate text color components
716 * Only use the first color */
717 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
718 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
721 fmt.p_palette->i_entries = 16;
722 for( i = 0; i < 8; i++ )
724 fmt.p_palette->palette[i][0] = 0;
725 fmt.p_palette->palette[i][1] = 0x80;
726 fmt.p_palette->palette[i][2] = 0x80;
727 fmt.p_palette->palette[i][3] = pi_gamma[i];
728 fmt.p_palette->palette[i][3] =
729 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
731 for( i = 8; i < fmt.p_palette->i_entries; i++ )
733 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
734 fmt.p_palette->palette[i][1] = i_u;
735 fmt.p_palette->palette[i][2] = i_v;
736 fmt.p_palette->palette[i][3] = pi_gamma[i];
737 fmt.p_palette->palette[i][3] =
738 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
741 p_dst = p_region->p_picture->Y_PIXELS;
742 i_pitch = p_region->p_picture->Y_PITCH;
744 /* Initialize the region pixels */
745 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
747 for( ; p_line != NULL; p_line = p_line->p_next )
749 int i_align_left = 0;
750 if( p_line->i_width < (int)fmt.i_visible_width )
752 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
753 i_align_left = ( fmt.i_visible_width - p_line->i_width );
754 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
755 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
759 for( i = 0; i < p_line->i_character_count; i++ )
761 const line_character_t *ch = &p_line->p_character[i];
762 FT_BitmapGlyph p_glyph = ch->p_glyph;
764 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
765 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
767 for( y = 0; y < p_glyph->bitmap.rows; y++ )
769 for( x = 0; x < p_glyph->bitmap.width; x++ )
771 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
772 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
773 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
779 /* Outlining (find something better than nearest neighbour filtering ?) */
782 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
783 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
784 uint8_t left, current;
786 for( y = 1; y < (int)fmt.i_height - 1; y++ )
788 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
789 p_dst += p_region->p_picture->Y_PITCH;
792 for( x = 1; x < (int)fmt.i_width - 1; x++ )
795 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
796 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;
800 memset( p_top, 0, fmt.i_width );
806 /*****************************************************************************
807 * RenderYUVA: place string in picture
808 *****************************************************************************
809 * This function merges the previously rendered freetype glyphs into a picture
810 *****************************************************************************/
811 static void FillYUVAPicture( picture_t *p_picture,
812 int i_a, int i_y, int i_u, int i_v )
814 memset( p_picture->p[0].p_pixels, i_y,
815 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
816 memset( p_picture->p[1].p_pixels, i_u,
817 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
818 memset( p_picture->p[2].p_pixels, i_v,
819 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
820 memset( p_picture->p[3].p_pixels, i_a,
821 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
824 static inline void BlendYUVAPixel( picture_t *p_picture,
825 int i_picture_x, int i_picture_y,
826 int i_a, int i_y, int i_u, int i_v,
829 int i_an = i_a * i_alpha / 255;
831 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
832 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
833 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
834 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
846 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
849 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
850 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
851 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
856 static void FillRGBAPicture( picture_t *p_picture,
857 int i_a, int i_r, int i_g, int i_b )
859 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
861 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
863 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
872 static inline void BlendRGBAPixel( picture_t *p_picture,
873 int i_picture_x, int i_picture_y,
874 int i_a, int i_r, int i_g, int i_b,
877 int i_an = i_a * i_alpha / 255;
879 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
881 int i_ao = p_rgba[3];
891 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
894 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
895 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
896 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
901 static inline void BlendAXYZGlyph( picture_t *p_picture,
902 int i_picture_x, int i_picture_y,
903 int i_a, int i_x, int i_y, int i_z,
904 FT_BitmapGlyph p_glyph,
905 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
908 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
910 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
911 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
913 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
917 static inline void BlendAXYZLine( 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 const line_character_t *p_current,
921 const line_character_t *p_next,
922 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
924 int i_line_width = p_current->p_glyph->bitmap.width;
926 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
928 for( int dx = 0; dx < i_line_width; dx++ )
930 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
931 BlendPixel( p_picture,
933 i_picture_y + p_current->i_line_offset + dy,
934 i_a, i_x, i_y, i_z, 0xff );
938 static inline int RenderAXYZ( filter_t *p_filter,
939 subpicture_region_t *p_region,
940 line_desc_t *p_line_head,
943 vlc_fourcc_t i_chroma,
944 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
945 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
946 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
948 filter_sys_t *p_sys = p_filter->p_sys;
950 /* Create a new subpicture region */
951 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
952 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
954 video_format_Init( &fmt, i_chroma );
956 fmt.i_visible_width = i_text_width + 2 * i_margin;
958 fmt.i_visible_height = i_text_height + 2 * i_margin;
960 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
961 if( !p_region->p_picture )
965 /* Initialize the picture background */
966 uint8_t i_a = p_sys->i_background_opacity;
967 uint8_t i_x, i_y, i_z;
968 ExtractComponents( p_sys->i_background_color, &i_x, &i_y, &i_z );
970 FillPicture( p_picture, i_a, i_x, i_y, i_z );
972 /* Render shadow then outline and then normal glyphs */
973 for( int g = 0; g < 3; g++ )
975 /* Render all lines */
976 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
978 int i_align_left = i_margin;
979 if( p_line->i_width < i_text_width )
981 /* Left offset to take into account alignment */
982 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
983 i_align_left += ( i_text_width - p_line->i_width );
984 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
985 i_align_left += ( i_text_width - p_line->i_width ) / 2;
987 int i_align_top = i_margin;
989 /* Render all glyphs and underline/strikethrough */
990 for( int i = 0; i < p_line->i_character_count; i++ )
992 const line_character_t *ch = &p_line->p_character[i];
993 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
997 i_a = (ch->i_color >> 24) & 0xff;
1001 i_a = i_a * p_sys->i_shadow_opacity / 255;
1002 i_color = p_sys->i_shadow_color;
1005 i_a = i_a * p_sys->i_outline_opacity / 255;
1006 i_color = p_sys->i_outline_color;
1009 i_color = ch->i_color;
1012 ExtractComponents( i_color, &i_x, &i_y, &i_z );
1014 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
1015 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
1017 BlendAXYZGlyph( p_picture,
1018 i_glyph_x, i_glyph_y,
1023 /* underline/strikethrough are only rendered for the normal glyph */
1024 if( g == 2 && ch->i_line_thickness > 0 )
1025 BlendAXYZLine( p_picture,
1026 i_glyph_x, i_glyph_y + p_glyph->top,
1029 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
1038 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
1039 uint32_t i_font_color, uint32_t i_karaoke_bg_color,
1042 text_style_t *p_style = text_style_New();
1046 p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
1047 p_style->i_font_size = i_font_size;
1048 p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0;
1049 p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
1050 p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0;
1051 p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
1052 p_style->i_style_flags |= i_style_flags;
1056 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1057 uint32_t i_color, uint32_t i_karaoke_bg_color )
1060 return VLC_EGENERIC;
1062 font_stack_t *p_new = malloc( sizeof(*p_new) );
1066 p_new->p_next = NULL;
1069 p_new->psz_name = strdup( psz_name );
1071 p_new->psz_name = NULL;
1073 p_new->i_size = i_size;
1074 p_new->i_color = i_color;
1075 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1083 font_stack_t *p_last;
1085 for( p_last = *p_font;
1087 p_last = p_last->p_next )
1090 p_last->p_next = p_new;
1095 static int PopFont( font_stack_t **p_font )
1097 font_stack_t *p_last, *p_next_to_last;
1099 if( !p_font || !*p_font )
1100 return VLC_EGENERIC;
1102 p_next_to_last = NULL;
1103 for( p_last = *p_font;
1105 p_last = p_last->p_next )
1107 p_next_to_last = p_last;
1110 if( p_next_to_last )
1111 p_next_to_last->p_next = NULL;
1115 free( p_last->psz_name );
1121 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1122 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1124 font_stack_t *p_last;
1126 if( !p_font || !*p_font )
1127 return VLC_EGENERIC;
1129 for( p_last=*p_font;
1131 p_last=p_last->p_next )
1134 *psz_name = p_last->psz_name;
1135 *i_size = p_last->i_size;
1136 *i_color = p_last->i_color;
1137 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1142 static const struct {
1143 const char *psz_name;
1145 } p_html_colors[] = {
1146 /* Official html colors */
1147 { "Aqua", 0x00FFFF },
1148 { "Black", 0x000000 },
1149 { "Blue", 0x0000FF },
1150 { "Fuchsia", 0xFF00FF },
1151 { "Gray", 0x808080 },
1152 { "Green", 0x008000 },
1153 { "Lime", 0x00FF00 },
1154 { "Maroon", 0x800000 },
1155 { "Navy", 0x000080 },
1156 { "Olive", 0x808000 },
1157 { "Purple", 0x800080 },
1158 { "Red", 0xFF0000 },
1159 { "Silver", 0xC0C0C0 },
1160 { "Teal", 0x008080 },
1161 { "White", 0xFFFFFF },
1162 { "Yellow", 0xFFFF00 },
1165 { "AliceBlue", 0xF0F8FF },
1166 { "AntiqueWhite", 0xFAEBD7 },
1167 { "Aqua", 0x00FFFF },
1168 { "Aquamarine", 0x7FFFD4 },
1169 { "Azure", 0xF0FFFF },
1170 { "Beige", 0xF5F5DC },
1171 { "Bisque", 0xFFE4C4 },
1172 { "Black", 0x000000 },
1173 { "BlanchedAlmond", 0xFFEBCD },
1174 { "Blue", 0x0000FF },
1175 { "BlueViolet", 0x8A2BE2 },
1176 { "Brown", 0xA52A2A },
1177 { "BurlyWood", 0xDEB887 },
1178 { "CadetBlue", 0x5F9EA0 },
1179 { "Chartreuse", 0x7FFF00 },
1180 { "Chocolate", 0xD2691E },
1181 { "Coral", 0xFF7F50 },
1182 { "CornflowerBlue", 0x6495ED },
1183 { "Cornsilk", 0xFFF8DC },
1184 { "Crimson", 0xDC143C },
1185 { "Cyan", 0x00FFFF },
1186 { "DarkBlue", 0x00008B },
1187 { "DarkCyan", 0x008B8B },
1188 { "DarkGoldenRod", 0xB8860B },
1189 { "DarkGray", 0xA9A9A9 },
1190 { "DarkGrey", 0xA9A9A9 },
1191 { "DarkGreen", 0x006400 },
1192 { "DarkKhaki", 0xBDB76B },
1193 { "DarkMagenta", 0x8B008B },
1194 { "DarkOliveGreen", 0x556B2F },
1195 { "Darkorange", 0xFF8C00 },
1196 { "DarkOrchid", 0x9932CC },
1197 { "DarkRed", 0x8B0000 },
1198 { "DarkSalmon", 0xE9967A },
1199 { "DarkSeaGreen", 0x8FBC8F },
1200 { "DarkSlateBlue", 0x483D8B },
1201 { "DarkSlateGray", 0x2F4F4F },
1202 { "DarkSlateGrey", 0x2F4F4F },
1203 { "DarkTurquoise", 0x00CED1 },
1204 { "DarkViolet", 0x9400D3 },
1205 { "DeepPink", 0xFF1493 },
1206 { "DeepSkyBlue", 0x00BFFF },
1207 { "DimGray", 0x696969 },
1208 { "DimGrey", 0x696969 },
1209 { "DodgerBlue", 0x1E90FF },
1210 { "FireBrick", 0xB22222 },
1211 { "FloralWhite", 0xFFFAF0 },
1212 { "ForestGreen", 0x228B22 },
1213 { "Fuchsia", 0xFF00FF },
1214 { "Gainsboro", 0xDCDCDC },
1215 { "GhostWhite", 0xF8F8FF },
1216 { "Gold", 0xFFD700 },
1217 { "GoldenRod", 0xDAA520 },
1218 { "Gray", 0x808080 },
1219 { "Grey", 0x808080 },
1220 { "Green", 0x008000 },
1221 { "GreenYellow", 0xADFF2F },
1222 { "HoneyDew", 0xF0FFF0 },
1223 { "HotPink", 0xFF69B4 },
1224 { "IndianRed", 0xCD5C5C },
1225 { "Indigo", 0x4B0082 },
1226 { "Ivory", 0xFFFFF0 },
1227 { "Khaki", 0xF0E68C },
1228 { "Lavender", 0xE6E6FA },
1229 { "LavenderBlush", 0xFFF0F5 },
1230 { "LawnGreen", 0x7CFC00 },
1231 { "LemonChiffon", 0xFFFACD },
1232 { "LightBlue", 0xADD8E6 },
1233 { "LightCoral", 0xF08080 },
1234 { "LightCyan", 0xE0FFFF },
1235 { "LightGoldenRodYellow", 0xFAFAD2 },
1236 { "LightGray", 0xD3D3D3 },
1237 { "LightGrey", 0xD3D3D3 },
1238 { "LightGreen", 0x90EE90 },
1239 { "LightPink", 0xFFB6C1 },
1240 { "LightSalmon", 0xFFA07A },
1241 { "LightSeaGreen", 0x20B2AA },
1242 { "LightSkyBlue", 0x87CEFA },
1243 { "LightSlateGray", 0x778899 },
1244 { "LightSlateGrey", 0x778899 },
1245 { "LightSteelBlue", 0xB0C4DE },
1246 { "LightYellow", 0xFFFFE0 },
1247 { "Lime", 0x00FF00 },
1248 { "LimeGreen", 0x32CD32 },
1249 { "Linen", 0xFAF0E6 },
1250 { "Magenta", 0xFF00FF },
1251 { "Maroon", 0x800000 },
1252 { "MediumAquaMarine", 0x66CDAA },
1253 { "MediumBlue", 0x0000CD },
1254 { "MediumOrchid", 0xBA55D3 },
1255 { "MediumPurple", 0x9370D8 },
1256 { "MediumSeaGreen", 0x3CB371 },
1257 { "MediumSlateBlue", 0x7B68EE },
1258 { "MediumSpringGreen", 0x00FA9A },
1259 { "MediumTurquoise", 0x48D1CC },
1260 { "MediumVioletRed", 0xC71585 },
1261 { "MidnightBlue", 0x191970 },
1262 { "MintCream", 0xF5FFFA },
1263 { "MistyRose", 0xFFE4E1 },
1264 { "Moccasin", 0xFFE4B5 },
1265 { "NavajoWhite", 0xFFDEAD },
1266 { "Navy", 0x000080 },
1267 { "OldLace", 0xFDF5E6 },
1268 { "Olive", 0x808000 },
1269 { "OliveDrab", 0x6B8E23 },
1270 { "Orange", 0xFFA500 },
1271 { "OrangeRed", 0xFF4500 },
1272 { "Orchid", 0xDA70D6 },
1273 { "PaleGoldenRod", 0xEEE8AA },
1274 { "PaleGreen", 0x98FB98 },
1275 { "PaleTurquoise", 0xAFEEEE },
1276 { "PaleVioletRed", 0xD87093 },
1277 { "PapayaWhip", 0xFFEFD5 },
1278 { "PeachPuff", 0xFFDAB9 },
1279 { "Peru", 0xCD853F },
1280 { "Pink", 0xFFC0CB },
1281 { "Plum", 0xDDA0DD },
1282 { "PowderBlue", 0xB0E0E6 },
1283 { "Purple", 0x800080 },
1284 { "Red", 0xFF0000 },
1285 { "RosyBrown", 0xBC8F8F },
1286 { "RoyalBlue", 0x4169E1 },
1287 { "SaddleBrown", 0x8B4513 },
1288 { "Salmon", 0xFA8072 },
1289 { "SandyBrown", 0xF4A460 },
1290 { "SeaGreen", 0x2E8B57 },
1291 { "SeaShell", 0xFFF5EE },
1292 { "Sienna", 0xA0522D },
1293 { "Silver", 0xC0C0C0 },
1294 { "SkyBlue", 0x87CEEB },
1295 { "SlateBlue", 0x6A5ACD },
1296 { "SlateGray", 0x708090 },
1297 { "SlateGrey", 0x708090 },
1298 { "Snow", 0xFFFAFA },
1299 { "SpringGreen", 0x00FF7F },
1300 { "SteelBlue", 0x4682B4 },
1301 { "Tan", 0xD2B48C },
1302 { "Teal", 0x008080 },
1303 { "Thistle", 0xD8BFD8 },
1304 { "Tomato", 0xFF6347 },
1305 { "Turquoise", 0x40E0D0 },
1306 { "Violet", 0xEE82EE },
1307 { "Wheat", 0xF5DEB3 },
1308 { "White", 0xFFFFFF },
1309 { "WhiteSmoke", 0xF5F5F5 },
1310 { "Yellow", 0xFFFF00 },
1311 { "YellowGreen", 0x9ACD32 },
1316 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1317 font_stack_t **p_fonts )
1320 char *psz_fontname = NULL;
1321 uint32_t i_font_color = 0xffffff;
1322 int i_font_alpha = 255;
1323 uint32_t i_karaoke_bg_color = 0x00ffffff;
1324 int i_font_size = 24;
1326 /* Default all attributes to the top font in the stack -- in case not
1327 * all attributes are specified in the sub-font
1329 if( VLC_SUCCESS == PeekFont( p_fonts,
1333 &i_karaoke_bg_color ))
1335 psz_fontname = strdup( psz_fontname );
1336 i_font_size = i_font_size;
1338 i_font_alpha = (i_font_color >> 24) & 0xff;
1339 i_font_color &= 0x00ffffff;
1341 const char *name, *value;
1342 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1344 if( !strcasecmp( "face", name ) )
1346 free( psz_fontname );
1347 psz_fontname = strdup( value );
1349 else if( !strcasecmp( "size", name ) )
1351 if( ( *value == '+' ) || ( *value == '-' ) )
1353 int i_value = atoi( value );
1355 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1356 i_font_size += ( i_value * i_font_size ) / 10;
1357 else if( i_value < -5 )
1358 i_font_size = - i_value;
1359 else if( i_value > 5 )
1360 i_font_size = i_value;
1363 i_font_size = atoi( value );
1365 else if( !strcasecmp( "color", name ) )
1367 if( value[0] == '#' )
1369 i_font_color = strtol( value + 1, NULL, 16 );
1370 i_font_color &= 0x00ffffff;
1375 uint32_t i_value = strtol( value, &end, 16 );
1376 if( *end == '\0' || *end == ' ' )
1377 i_font_color = i_value & 0x00ffffff;
1379 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1381 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1383 i_font_color = p_html_colors[i].i_value;
1389 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1391 i_font_alpha = strtol( value + 1, NULL, 16 );
1392 i_font_alpha &= 0xff;
1395 rv = PushFont( p_fonts,
1398 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1399 i_karaoke_bg_color );
1401 free( psz_fontname );
1406 /* Turn any multiple-whitespaces into single spaces */
1407 static void HandleWhiteSpace( char *psz_node )
1409 char *s = strpbrk( psz_node, "\t\r\n " );
1412 int i_whitespace = strspn( s, "\t\r\n " );
1414 if( i_whitespace > 1 )
1417 strlen( s ) - i_whitespace + 1 );
1420 s = strpbrk( s, "\t\r\n " );
1425 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1426 font_stack_t **p_fonts,
1429 char *psz_fontname = NULL;
1430 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1431 uint32_t i_karaoke_bg_color = i_font_color;
1432 int i_font_size = p_sys->i_font_size;
1434 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1435 &i_font_color, &i_karaoke_bg_color ) )
1438 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1443 static unsigned SetupText( filter_t *p_filter,
1444 uni_char_t *psz_text_out,
1445 text_style_t **pp_styles,
1446 uint32_t *pi_k_dates,
1448 const char *psz_text_in,
1449 text_style_t *p_style,
1452 size_t i_string_length;
1454 size_t i_string_bytes;
1455 uni_char_t *psz_tmp = ToCharset( FREETYPE_TO_UCS, psz_text_in, &i_string_bytes );
1458 memcpy( psz_text_out, psz_tmp, i_string_bytes );
1459 i_string_length = i_string_bytes / sizeof( *psz_tmp );
1464 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1465 i_string_length = 0;
1468 if( i_string_length > 0 )
1470 for( unsigned i = 0; i < i_string_length; i++ )
1471 pp_styles[i] = p_style;
1475 text_style_Delete( p_style );
1477 if( i_string_length > 0 && pi_k_dates )
1479 for( unsigned i = 0; i < i_string_length; i++ )
1480 pi_k_dates[i] = i_k_date;
1482 return i_string_length;
1485 static int ProcessNodes( filter_t *p_filter,
1486 uni_char_t *psz_text,
1487 text_style_t **pp_styles,
1488 uint32_t *pi_k_dates,
1490 xml_reader_t *p_xml_reader,
1491 text_style_t *p_font_style )
1493 int rv = VLC_SUCCESS;
1494 filter_sys_t *p_sys = p_filter->p_sys;
1495 int i_text_length = 0;
1496 font_stack_t *p_fonts = NULL;
1497 uint32_t i_k_date = 0;
1499 int i_style_flags = 0;
1503 rv = PushFont( &p_fonts,
1504 p_font_style->psz_fontname,
1505 p_font_style->i_font_size > 0 ? p_font_style->i_font_size
1506 : p_sys->i_font_size,
1507 (p_font_style->i_font_color & 0xffffff) |
1508 ((p_font_style->i_font_alpha & 0xff) << 24),
1509 (p_font_style->i_karaoke_background_color & 0xffffff) |
1510 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1512 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1520 rv = PushFont( &p_fonts,
1521 p_sys->psz_fontfamily,
1523 (p_sys->i_font_color & 0xffffff) |
1524 ((p_sys->i_font_opacity & 0xff) << 24),
1528 if( p_sys->b_font_bold )
1529 i_style_flags |= STYLE_BOLD;
1531 if( rv != VLC_SUCCESS )
1537 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1541 case XML_READER_ENDELEM:
1542 if( !strcasecmp( "font", node ) )
1543 PopFont( &p_fonts );
1544 else if( !strcasecmp( "b", node ) )
1545 i_style_flags &= ~STYLE_BOLD;
1546 else if( !strcasecmp( "i", node ) )
1547 i_style_flags &= ~STYLE_ITALIC;
1548 else if( !strcasecmp( "u", node ) )
1549 i_style_flags &= ~STYLE_UNDERLINE;
1550 else if( !strcasecmp( "s", node ) )
1551 i_style_flags &= ~STYLE_STRIKEOUT;
1554 case XML_READER_STARTELEM:
1555 if( !strcasecmp( "font", node ) )
1556 HandleFontAttributes( p_xml_reader, &p_fonts );
1557 else if( !strcasecmp( "b", node ) )
1558 i_style_flags |= STYLE_BOLD;
1559 else if( !strcasecmp( "i", node ) )
1560 i_style_flags |= STYLE_ITALIC;
1561 else if( !strcasecmp( "u", node ) )
1562 i_style_flags |= STYLE_UNDERLINE;
1563 else if( !strcasecmp( "s", node ) )
1564 i_style_flags |= STYLE_STRIKEOUT;
1565 else if( !strcasecmp( "br", node ) )
1567 i_text_length += SetupText( p_filter,
1568 &psz_text[i_text_length],
1569 &pp_styles[i_text_length],
1570 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1572 GetStyleFromFontStack( p_sys,
1577 else if( !strcasecmp( "k", node ) )
1580 const char *name, *value;
1581 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1583 if( !strcasecmp( "t", name ) && value )
1584 i_k_date += atoi( value );
1589 case XML_READER_TEXT:
1591 char *psz_node = strdup( node );
1592 if( unlikely(!psz_node) )
1595 HandleWhiteSpace( psz_node );
1596 resolve_xml_special_chars( psz_node );
1598 i_text_length += SetupText( p_filter,
1599 &psz_text[i_text_length],
1600 &pp_styles[i_text_length],
1601 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1603 GetStyleFromFontStack( p_sys,
1613 *pi_len = i_text_length;
1615 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1620 static void FreeLine( line_desc_t *p_line )
1622 for( int i = 0; i < p_line->i_character_count; i++ )
1624 line_character_t *ch = &p_line->p_character[i];
1625 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1627 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1629 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1632 free( p_line->p_character );
1636 static void FreeLines( line_desc_t *p_lines )
1638 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1640 line_desc_t *p_next = p_line->p_next;
1646 static line_desc_t *NewLine( int i_count )
1648 line_desc_t *p_line = malloc( sizeof(*p_line) );
1653 p_line->p_next = NULL;
1654 p_line->i_width = 0;
1655 p_line->i_base_line = 0;
1656 p_line->i_character_count = 0;
1658 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1659 if( !p_line->p_character )
1667 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1669 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1671 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1673 FT_Face p_face = NULL;
1675 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1683 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1684 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1685 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1686 (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC)) == i_style_received )
1689 FT_Done_Face( p_face );
1697 static FT_Face LoadFace( filter_t *p_filter,
1698 const text_style_t *p_style )
1700 filter_sys_t *p_sys = p_filter->p_sys;
1702 /* Look for a match amongst our attachments first */
1703 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1705 /* Load system wide font otheriwse */
1710 #ifdef HAVE_FONTCONFIG
1711 psz_fontfile = FontConfig_Select( NULL,
1712 p_style->psz_fontname,
1713 (p_style->i_style_flags & STYLE_BOLD) != 0,
1714 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1717 #elif defined( WIN32 )
1718 psz_fontfile = Win32_Select( p_filter,
1719 p_style->psz_fontname,
1720 (p_style->i_style_flags & STYLE_BOLD) != 0,
1721 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1725 psz_fontfile = NULL;
1730 if( *psz_fontfile == '\0' )
1733 "We were not able to find a matching font: \"%s\" (%s %s),"
1734 " so using default font",
1735 p_style->psz_fontname,
1736 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1737 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1742 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1745 free( psz_fontfile );
1750 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1752 /* We've loaded a font face which is unhelpful for actually
1753 * rendering text - fallback to the default one.
1755 FT_Done_Face( p_face );
1761 static bool FaceStyleEquals( const text_style_t *p_style1,
1762 const text_style_t *p_style2 )
1764 if( !p_style1 || !p_style2 )
1766 if( p_style1 == p_style2 )
1769 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
1770 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
1771 !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
1774 static int GetGlyph( filter_t *p_filter,
1775 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1776 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1777 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1783 FT_Vector *p_pen_shadow )
1785 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1786 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1788 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1789 return VLC_EGENERIC;
1792 /* Do synthetic styling now that Freetype supports it;
1793 * ie. if the font we have loaded is NOT already in the
1794 * style that the tags want, then switch it on; if they
1795 * are then don't. */
1796 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1797 FT_GlyphSlot_Embolden( p_face->glyph );
1798 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1799 FT_GlyphSlot_Oblique( p_face->glyph );
1802 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1804 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1805 return VLC_EGENERIC;
1808 FT_Glyph outline = NULL;
1809 if( p_filter->p_sys->p_stroker )
1812 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1816 FT_Glyph shadow = NULL;
1817 if( p_filter->p_sys->i_shadow_opacity > 0 )
1819 shadow = outline ? outline : glyph;
1820 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1826 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1829 *pp_shadow = shadow;
1831 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1833 FT_Done_Glyph( glyph );
1835 FT_Done_Glyph( outline );
1837 FT_Done_Glyph( shadow );
1838 return VLC_EGENERIC;
1840 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1845 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1846 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1848 *pp_outline = outline;
1853 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1855 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1856 if( p_bbox->xMin >= p_bbox->xMax )
1858 p_bbox->xMin = FT_CEIL(p_pen->x);
1859 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1860 glyph_bmp->left = p_bbox->xMin;
1862 if( p_bbox->yMin >= p_bbox->yMax )
1864 p_bbox->yMax = FT_CEIL(p_pen->y);
1865 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1866 glyph_bmp->top = p_bbox->yMax;
1870 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1872 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1873 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1874 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1875 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1878 static int ProcessLines( filter_t *p_filter,
1879 line_desc_t **pp_lines,
1881 int *pi_max_face_height,
1883 uni_char_t *psz_text,
1884 text_style_t **pp_styles,
1885 uint32_t *pi_k_dates,
1888 filter_sys_t *p_sys = p_filter->p_sys;
1889 uni_char_t *p_fribidi_string = NULL;
1890 text_style_t **pp_fribidi_styles = NULL;
1891 int *p_new_positions = NULL;
1893 #if defined(HAVE_FRIBIDI)
1895 int *p_old_positions;
1896 int start_pos, pos = 0;
1898 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1900 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1901 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1902 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1904 if( ! pp_fribidi_styles ||
1905 ! p_fribidi_string ||
1906 ! p_old_positions ||
1909 free( p_old_positions );
1910 free( p_new_positions );
1911 free( p_fribidi_string );
1912 free( pp_fribidi_styles );
1916 /* Do bidi conversion line-by-line */
1919 while(pos < i_len) {
1920 if (psz_text[pos] != '\n')
1922 p_fribidi_string[pos] = psz_text[pos];
1923 pp_fribidi_styles[pos] = pp_styles[pos];
1924 p_new_positions[pos] = pos;
1928 while(pos < i_len) {
1929 if (psz_text[pos] == '\n')
1933 if (pos > start_pos)
1935 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1936 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1938 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1940 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1941 pos - start_pos, &base_dir,
1942 (FriBidiChar*)p_fribidi_string + start_pos,
1943 p_new_positions + start_pos,
1946 for( int j = start_pos; j < pos; j++ )
1948 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1949 p_new_positions[ j ] += start_pos;
1953 p_fribidi_string[ i_len ] = 0;
1954 free( p_old_positions );
1956 pp_styles = pp_fribidi_styles;
1957 psz_text = p_fribidi_string;
1960 /* Work out the karaoke */
1961 uint8_t *pi_karaoke_bar = NULL;
1964 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1965 if( pi_karaoke_bar )
1967 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1968 for( int i = 0; i < i_len; i++ )
1970 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1971 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1975 free( p_new_positions );
1977 *pi_max_face_height = 0;
1979 line_desc_t **pp_line_next = pp_lines;
1987 int i_face_height_previous = 0;
1988 int i_base_line = 0;
1989 const text_style_t *p_previous_style = NULL;
1990 FT_Face p_face = NULL;
1991 for( int i_start = 0; i_start < i_len; )
1993 /* Compute the length of the current text line */
1995 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
1998 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1999 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
2000 int i_index = i_start;
2005 int i_face_height = 0;
2006 FT_BBox line_bbox = {
2012 int i_ul_offset = 0;
2013 int i_ul_thickness = 0;
2022 break_point_t break_point;
2023 break_point_t break_point_fallback;
2025 #define SAVE_BP(dst) do { \
2026 dst.i_index = i_index; \
2028 dst.line_bbox = line_bbox; \
2029 dst.i_face_height = i_face_height; \
2030 dst.i_ul_offset = i_ul_offset; \
2031 dst.i_ul_thickness = i_ul_thickness; \
2034 SAVE_BP( break_point );
2035 SAVE_BP( break_point_fallback );
2037 while( i_index < i_start + i_length )
2039 /* Split by common FT_Face + Size */
2040 const text_style_t *p_current_style = pp_styles[i_index];
2041 int i_part_length = 0;
2042 while( i_index + i_part_length < i_start + i_length )
2044 const text_style_t *p_style = pp_styles[i_index + i_part_length];
2045 if( !FaceStyleEquals( p_style, p_current_style ) ||
2046 p_style->i_font_size != p_current_style->i_font_size )
2051 /* (Re)load/reconfigure the face if needed */
2052 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
2055 FT_Done_Face( p_face );
2056 p_previous_style = NULL;
2058 p_face = LoadFace( p_filter, p_current_style );
2060 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
2061 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
2063 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
2064 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
2065 if( p_sys->p_stroker )
2067 int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
2068 FT_Stroker_Set( p_sys->p_stroker,
2070 FT_STROKER_LINECAP_ROUND,
2071 FT_STROKER_LINEJOIN_ROUND, 0 );
2074 p_previous_style = p_current_style;
2076 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
2077 p_current_face->size->metrics.y_scale)));
2079 /* Render the part */
2080 bool b_break_line = false;
2081 int i_glyph_last = 0;
2082 while( i_part_length > 0 )
2084 const text_style_t *p_glyph_style = pp_styles[i_index];
2085 uni_char_t character = psz_text[i_index];
2086 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
2088 /* Get kerning vector */
2089 FT_Vector kerning = { .x = 0, .y = 0 };
2090 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
2091 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
2093 /* Get the glyph bitmap and its bounding box and all the associated properties */
2094 FT_Vector pen_new = {
2095 .x = pen.x + kerning.x,
2096 .y = pen.y + kerning.y,
2098 FT_Vector pen_shadow_new = {
2099 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
2100 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
2105 FT_BBox outline_bbox;
2107 FT_BBox shadow_bbox;
2109 if( GetGlyph( p_filter,
2110 &glyph, &glyph_bbox,
2111 &outline, &outline_bbox,
2112 &shadow, &shadow_bbox,
2113 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
2114 &pen_new, &pen_shadow_new ) )
2117 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
2119 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
2121 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
2123 /* FIXME and what about outline */
2125 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
2126 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
2127 (p_glyph_style->i_karaoke_background_alpha << 24))
2128 : (p_glyph_style->i_font_color |
2129 (p_glyph_style->i_font_alpha << 24));
2130 int i_line_offset = 0;
2131 int i_line_thickness = 0;
2132 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
2134 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
2135 p_current_face->size->metrics.y_scale)) );
2137 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
2138 p_current_face->size->metrics.y_scale)) );
2140 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
2142 /* Move the baseline to make it strikethrough instead of
2143 * underline. That means that strikethrough takes precedence
2145 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
2146 p_current_face->size->metrics.y_scale)) );
2148 else if( i_line_thickness > 0 )
2150 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
2152 /* The real underline thickness and position are
2153 * updated once the whole line has been parsed */
2154 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
2155 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
2156 i_line_thickness = -1;
2159 FT_BBox line_bbox_new = line_bbox;
2160 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
2162 BBoxEnlarge( &line_bbox_new, &outline_bbox );
2164 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
2166 b_break_line = i_index > i_start &&
2167 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
2170 FT_Done_Glyph( glyph );
2172 FT_Done_Glyph( outline );
2174 FT_Done_Glyph( shadow );
2176 break_point_t *p_bp = NULL;
2177 if( break_point.i_index > i_start )
2178 p_bp = &break_point;
2179 else if( break_point_fallback.i_index > i_start )
2180 p_bp = &break_point_fallback;
2184 msg_Dbg( p_filter, "Breaking line");
2185 for( int i = p_bp->i_index; i < i_index; i++ )
2187 line_character_t *ch = &p_line->p_character[i - i_start];
2188 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
2190 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
2192 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
2194 p_line->i_character_count = p_bp->i_index - i_start;
2196 i_index = p_bp->i_index;
2198 line_bbox = p_bp->line_bbox;
2199 i_face_height = p_bp->i_face_height;
2200 i_ul_offset = p_bp->i_ul_offset;
2201 i_ul_thickness = p_bp->i_ul_thickness;
2205 msg_Err( p_filter, "Breaking unbreakable line");
2210 assert( p_line->i_character_count == i_index - i_start);
2211 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2212 .p_glyph = (FT_BitmapGlyph)glyph,
2213 .p_outline = (FT_BitmapGlyph)outline,
2214 .p_shadow = (FT_BitmapGlyph)shadow,
2216 .i_line_offset = i_line_offset,
2217 .i_line_thickness = i_line_thickness,
2220 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2221 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2222 line_bbox = line_bbox_new;
2224 i_glyph_last = i_glyph_index;
2228 if( character == ' ' || character == '\t' )
2229 SAVE_BP( break_point );
2230 else if( character == 160 )
2231 SAVE_BP( break_point_fallback );
2237 /* Update our baseline */
2238 if( i_face_height_previous > 0 )
2239 i_base_line += __MAX(i_face_height, i_face_height_previous);
2240 if( i_face_height > 0 )
2241 i_face_height_previous = i_face_height;
2243 /* Update the line bbox with the actual base line */
2244 if (line_bbox.yMax > line_bbox.yMin) {
2245 line_bbox.yMin -= i_base_line;
2246 line_bbox.yMax -= i_base_line;
2248 BBoxEnlarge( &bbox, &line_bbox );
2250 /* Terminate and append the line */
2253 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2254 p_line->i_base_line = i_base_line;
2255 if( i_ul_thickness > 0 )
2257 for( int i = 0; i < p_line->i_character_count; i++ )
2259 line_character_t *ch = &p_line->p_character[i];
2260 if( ch->i_line_thickness < 0 )
2262 ch->i_line_offset = i_ul_offset;
2263 ch->i_line_thickness = i_ul_thickness;
2268 *pp_line_next = p_line;
2269 pp_line_next = &p_line->p_next;
2272 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2274 /* Skip what we have rendered and the line delimitor if present */
2276 if( i_start < i_len && psz_text[i_start] == '\n' )
2279 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
2281 msg_Err( p_filter, "Truncated too high subtitle" );
2286 FT_Done_Face( p_face );
2288 free( pp_fribidi_styles );
2289 free( p_fribidi_string );
2290 free( pi_karaoke_bar );
2297 * This function renders a text subpicture region into another one.
2298 * It also calculates the size needed for this string, and renders the
2299 * needed glyphs into memory. It is used as pf_add_string callback in
2300 * the vout method by this module
2302 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2303 subpicture_region_t *p_region_in, bool b_html,
2304 const vlc_fourcc_t *p_chroma_list )
2306 filter_sys_t *p_sys = p_filter->p_sys;
2309 return VLC_EGENERIC;
2310 if( b_html && !p_region_in->psz_html )
2311 return VLC_EGENERIC;
2312 if( !b_html && !p_region_in->psz_text )
2313 return VLC_EGENERIC;
2315 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2316 : p_region_in->psz_text );
2318 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2319 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2320 if( !psz_text || !pp_styles )
2324 return VLC_EGENERIC;
2327 /* Reset the default fontsize in case screen metrics have changed */
2328 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2331 int rv = VLC_SUCCESS;
2332 int i_text_length = 0;
2334 int i_max_face_height;
2335 line_desc_t *p_lines = NULL;
2337 uint32_t *pi_k_durations = NULL;
2342 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2343 (uint8_t *) p_region_in->psz_html,
2344 strlen( p_region_in->psz_html ),
2346 if( unlikely(p_sub == NULL) )
2349 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2351 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2353 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2354 p_filter->p_sys->p_xml = p_xml_reader;
2361 /* Look for Root Node */
2364 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2366 if( strcasecmp( "karaoke", node ) == 0 )
2368 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2370 else if( strcasecmp( "text", node ) != 0 )
2372 /* Only text and karaoke tags are supported */
2373 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2380 msg_Err( p_filter, "Malformed HTML subtitle" );
2386 rv = ProcessNodes( p_filter,
2387 psz_text, pp_styles, pi_k_durations, &i_text_length,
2388 p_xml_reader, p_region_in->p_style );
2392 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2394 stream_Delete( p_sub );
2399 text_style_t *p_style;
2400 if( p_region_in->p_style )
2401 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2402 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2403 : p_sys->i_font_size,
2404 (p_region_in->p_style->i_font_color & 0xffffff) |
2405 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2407 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2412 p_style = CreateStyle( p_sys->psz_fontfamily,
2414 (p_sys->i_font_color & 0xffffff) |
2415 ((p_sys->i_font_opacity & 0xff) << 24),
2417 if( p_sys->b_font_bold )
2418 p_style->i_style_flags |= STYLE_BOLD;
2420 i_text_length = SetupText( p_filter,
2424 p_region_in->psz_text, p_style, 0 );
2427 if( !rv && i_text_length > 0 )
2429 rv = ProcessLines( p_filter,
2430 &p_lines, &bbox, &i_max_face_height,
2431 psz_text, pp_styles, pi_k_durations, i_text_length );
2434 p_region_out->i_x = p_region_in->i_x;
2435 p_region_out->i_y = p_region_in->i_y;
2437 /* Don't attempt to render text that couldn't be layed out
2439 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2441 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2442 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2444 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2445 p_chroma_list = p_chroma_list_yuvp;
2446 else if( !p_chroma_list || *p_chroma_list == 0 )
2447 p_chroma_list = p_chroma_list_rgba;
2449 const int i_margin = p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2450 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2453 if( *p_chroma == VLC_CODEC_YUVP )
2454 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2455 else if( *p_chroma == VLC_CODEC_YUVA )
2456 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2461 else if( *p_chroma == VLC_CODEC_RGBA )
2462 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2471 /* With karaoke, we're going to have to render the text a number
2472 * of times to show the progress marker on the text.
2474 if( pi_k_durations )
2475 var_SetBool( p_filter, "text-rerender", true );
2478 FreeLines( p_lines );
2481 for( int i = 0; i < i_text_length; i++ )
2483 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2484 text_style_Delete( pp_styles[i] );
2487 free( pi_k_durations );
2492 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2493 subpicture_region_t *p_region_in,
2494 const vlc_fourcc_t *p_chroma_list )
2496 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2501 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2502 subpicture_region_t *p_region_in,
2503 const vlc_fourcc_t *p_chroma_list )
2505 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2510 /*****************************************************************************
2511 * Create: allocates osd-text video thread output method
2512 *****************************************************************************
2513 * This function allocates and initializes a Clone vout method.
2514 *****************************************************************************/
2515 static int Create( vlc_object_t *p_this )
2517 filter_t *p_filter = (filter_t *)p_this;
2518 filter_sys_t *p_sys;
2519 char *psz_fontfile = NULL;
2520 char *psz_fontfamily = NULL;
2521 int i_error = 0, fontindex = 0;
2523 /* Allocate structure */
2524 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2528 p_sys->psz_fontfamily = NULL;
2530 p_sys->p_xml = NULL;
2533 p_sys->p_library = 0;
2534 p_sys->i_font_size = 0;
2535 p_sys->i_display_height = 0;
2537 var_Create( p_filter, "freetype-rel-fontsize",
2538 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2540 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2541 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2542 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2543 p_sys->i_font_opacity = VLC_CLIP( p_sys->i_font_opacity, 0, 255 );
2544 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2545 p_sys->i_font_color = VLC_CLIP( p_sys->i_font_color, 0, 0xFFFFFF );
2546 p_sys->b_font_bold = var_InheritBool( p_filter, "freetype-bold" );
2548 p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
2549 p_sys->i_background_opacity = VLC_CLIP( p_sys->i_background_opacity, 0, 255 );
2550 p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
2551 p_sys->i_background_color = VLC_CLIP( p_sys->i_background_color, 0, 0xFFFFFF );
2553 p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2554 p_sys->f_outline_thickness = VLC_CLIP( p_sys->f_outline_thickness, 0.0, 0.5 );
2555 p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2556 p_sys->i_outline_opacity = VLC_CLIP( p_sys->i_outline_opacity, 0, 255 );
2557 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2558 p_sys->i_outline_color = VLC_CLIP( p_sys->i_outline_color, 0, 0xFFFFFF );
2560 p_sys->i_shadow_opacity = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2561 p_sys->i_shadow_opacity = VLC_CLIP( p_sys->i_shadow_opacity, 0, 255 );
2562 p_sys->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2563 p_sys->i_shadow_color = VLC_CLIP( p_sys->i_shadow_color, 0, 0xFFFFFF );
2564 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2565 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2566 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
2567 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2568 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2571 /* Get Windows Font folder */
2572 wchar_t wdir[MAX_PATH];
2573 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2575 GetWindowsDirectoryW( wdir, MAX_PATH );
2576 wcscat( wdir, L"\\fonts" );
2578 p_sys->psz_win_fonts_path = FromWide( wdir );
2581 /* Set default psz_fontfamily */
2582 if( !psz_fontfamily || !*psz_fontfamily )
2584 free( psz_fontfamily );
2586 psz_fontfamily = strdup( DEFAULT_FAMILY );
2589 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2592 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2594 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2598 /* Set the current font file */
2599 p_sys->psz_fontfamily = psz_fontfamily;
2601 #ifdef HAVE_FONTCONFIG
2602 FontConfig_BuildCache( p_filter );
2605 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2606 p_sys->i_default_font_size, &fontindex );
2607 #elif defined(WIN32)
2608 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2609 p_sys->i_default_font_size, &fontindex );
2612 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2614 /* If nothing is found, use the default family */
2616 psz_fontfile = strdup( psz_fontfamily );
2618 #else /* !HAVE_STYLES */
2619 /* Use the default file */
2620 psz_fontfile = psz_fontfamily;
2624 i_error = FT_Init_FreeType( &p_sys->p_library );
2627 msg_Err( p_filter, "couldn't initialize freetype" );
2631 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2632 fontindex, &p_sys->p_face );
2634 if( i_error == FT_Err_Unknown_File_Format )
2636 msg_Err( p_filter, "file %s have unknown format",
2637 psz_fontfile ? psz_fontfile : "(null)" );
2642 msg_Err( p_filter, "failed to load font file %s",
2643 psz_fontfile ? psz_fontfile : "(null)" );
2647 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2650 msg_Err( p_filter, "font has no unicode translation table" );
2654 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2656 p_sys->p_stroker = NULL;
2657 if( p_sys->f_outline_thickness > 0.001 )
2659 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2661 msg_Err( p_filter, "Failed to create stroker for outlining" );
2664 p_sys->pp_font_attachments = NULL;
2665 p_sys->i_font_attachments = 0;
2667 p_filter->pf_render_text = RenderText;
2669 p_filter->pf_render_html = RenderHtml;
2671 p_filter->pf_render_html = NULL;
2674 LoadFontsFromAttachments( p_filter );
2677 free( psz_fontfile );
2683 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2684 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2686 free( psz_fontfile );
2688 free( psz_fontfamily );
2690 return VLC_EGENERIC;
2693 /*****************************************************************************
2694 * Destroy: destroy Clone video thread output method
2695 *****************************************************************************
2696 * Clean up all data and library connections
2697 *****************************************************************************/
2698 static void Destroy( vlc_object_t *p_this )
2700 filter_t *p_filter = (filter_t *)p_this;
2701 filter_sys_t *p_sys = p_filter->p_sys;
2703 if( p_sys->pp_font_attachments )
2705 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2706 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2708 free( p_sys->pp_font_attachments );
2712 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2714 free( p_sys->psz_fontfamily );
2716 /* FcFini asserts calling the subfunction FcCacheFini()
2717 * even if no other library functions have been made since FcInit(),
2718 * so don't call it. */
2720 if( p_sys->p_stroker )
2721 FT_Stroker_Done( p_sys->p_stroker );
2722 FT_Done_Face( p_sys->p_face );
2723 FT_Done_FreeType( p_sys->p_library );