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( HAVE_MAEMO )
55 # define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf"
56 # define DEFAULT_FAMILY "Nokia Sans Bold"
58 # define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
59 # define DEFAULT_FAMILY "Serif Bold"
63 #include <freetype/ftsynth.h>
64 #include FT_FREETYPE_H
68 #define FT_FLOOR(X) ((X & -64) >> 6)
69 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
71 #define FT_MulFix(v, s) (((v)*(s))>>16)
75 #if defined(HAVE_FRIBIDI)
76 # include <fribidi/fribidi.h>
84 # undef HAVE_FONTCONFIG
88 #ifdef HAVE_FONTCONFIG
89 # include <fontconfig/fontconfig.h>
95 /*****************************************************************************
97 *****************************************************************************/
98 static int Create ( vlc_object_t * );
99 static void Destroy( vlc_object_t * );
101 #define FONT_TEXT N_("Font")
103 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
104 #define FONT_LONGTEXT N_("Font file for the font you want to use")
106 #define FONTSIZE_TEXT N_("Font size in pixels")
107 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
108 "that will be rendered on the video. " \
109 "If set to something different than 0 this option will override the " \
110 "relative font size." )
111 #define OPACITY_TEXT N_("Opacity")
112 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
113 "text that will be rendered on the video. 0 = transparent, " \
114 "255 = totally opaque. " )
115 #define COLOR_TEXT N_("Text default color")
116 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
117 "the video. This must be an hexadecimal (like HTML colors). The first two "\
118 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
119 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
120 #define FONTSIZER_TEXT N_("Relative font size")
121 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
122 "fonts that will be rendered on the video. If absolute font size is set, "\
123 "relative size will be overridden." )
124 #define BOLD_TEXT N_("Force bold")
126 #define BG_OPACITY_TEXT N_("Background opacity")
127 #define BG_COLOR_TEXT N_("Background color")
129 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
130 #define OUTLINE_COLOR_TEXT N_("Outline color")
131 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
133 #define SHADOW_OPACITY_TEXT N_("Shadow opacity")
134 #define SHADOW_COLOR_TEXT N_("Shadow color")
135 #define SHADOW_ANGLE_TEXT N_("Shadow angle")
136 #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
139 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
140 static const char *const ppsz_sizes_text[] = {
141 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
142 #define YUVP_TEXT N_("Use YUVP renderer")
143 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
144 "This option is only needed if you want to encode into DVB subtitles" )
146 static const int pi_color_values[] = {
147 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
148 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
149 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
151 static const char *const ppsz_color_descriptions[] = {
152 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
153 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
154 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
156 static const int pi_outline_thickness[] = {
159 static const char *const ppsz_outline_thickness[] = {
160 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
164 set_shortname( N_("Text renderer"))
165 set_description( N_("Freetype2 font renderer") )
166 set_category( CAT_VIDEO )
167 set_subcategory( SUBCAT_VIDEO_SUBPIC )
170 add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
172 add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
175 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
176 FONTSIZE_LONGTEXT, true )
179 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
180 FONTSIZER_LONGTEXT, false )
181 change_integer_list( pi_sizes, ppsz_sizes_text )
184 /* opacity valid on 0..255, with default 255 = fully opaque */
185 add_integer_with_range( "freetype-opacity", 255, 0, 255,
186 OPACITY_TEXT, OPACITY_LONGTEXT, false )
189 /* hook to the color values list, with default 0x00ffffff = white */
190 add_integer( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
191 COLOR_LONGTEXT, false )
192 change_integer_list( pi_color_values, ppsz_color_descriptions )
195 add_bool( "freetype-bold", false, BOLD_TEXT, "", false )
198 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
199 BG_OPACITY_TEXT, "", false )
201 add_integer( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
203 change_integer_list( pi_color_values, ppsz_color_descriptions )
206 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
207 OUTLINE_OPACITY_TEXT, "", false )
209 add_integer( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
211 change_integer_list( pi_color_values, ppsz_color_descriptions )
213 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
215 change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
218 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
219 SHADOW_OPACITY_TEXT, "", false )
221 add_integer( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
223 change_integer_list( pi_color_values, ppsz_color_descriptions )
225 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
226 SHADOW_ANGLE_TEXT, "", false )
228 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
229 SHADOW_DISTANCE_TEXT, "", false )
232 add_obsolete_integer( "freetype-effect" );
234 add_bool( "freetype-yuvp", false, YUVP_TEXT,
235 YUVP_LONGTEXT, true )
236 set_capability( "text renderer", 100 )
237 add_shortcut( "text" )
238 set_callbacks( Create, Destroy )
242 /*****************************************************************************
244 *****************************************************************************/
248 FT_BitmapGlyph p_glyph;
249 FT_BitmapGlyph p_outline;
250 FT_BitmapGlyph p_shadow;
251 uint32_t i_color; /* ARGB color */
252 int i_line_offset; /* underline/strikethrough offset */
253 int i_line_thickness; /* underline/strikethrough thickness */
256 typedef struct line_desc_t line_desc_t;
263 int i_character_count;
264 line_character_t *p_character;
267 typedef struct font_stack_t font_stack_t;
272 uint32_t i_color; /* ARGB */
273 uint32_t i_karaoke_bg_color; /* ARGB */
275 font_stack_t *p_next;
278 /*****************************************************************************
279 * filter_sys_t: freetype local data
280 *****************************************************************************
281 * This structure is part of the video output thread descriptor.
282 * It describes the freetype specific properties of an output thread.
283 *****************************************************************************/
286 FT_Library p_library; /* handle to library */
287 FT_Face p_face; /* handle to face object */
288 FT_Stroker p_stroker;
289 uint8_t i_font_opacity;
294 uint8_t i_background_opacity;
295 int i_background_color;
297 double f_outline_thickness;
298 uint8_t i_outline_opacity;
301 float f_shadow_vector_x;
302 float f_shadow_vector_y;
303 uint8_t i_shadow_opacity;
306 int i_default_font_size;
307 int i_display_height;
308 char* psz_fontfamily;
312 char* psz_win_fonts_path;
316 input_attachment_t **pp_font_attachments;
317 int i_font_attachments;
321 static void YUVFromRGB( uint32_t i_argb,
322 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
324 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
325 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
326 int i_blue = ( i_argb & 0x000000ff );
328 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
329 802 * i_blue + 4096 + 131072 ) >> 13, 235);
330 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
331 3598 * i_blue + 4096 + 1048576) >> 13, 240);
332 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
333 -585 * i_blue + 4096 + 1048576) >> 13, 240);
335 static void RGBFromRGB( uint32_t i_argb,
336 uint8_t *pi_r, uint8_t *pi_g, uint8_t *pi_b )
338 *pi_r = ( i_argb & 0x00ff0000 ) >> 16;
339 *pi_g = ( i_argb & 0x0000ff00 ) >> 8;
340 *pi_b = ( i_argb & 0x000000ff );
342 /*****************************************************************************
343 * Make any TTF/OTF fonts present in the attachments of the media file
344 * and store them for later use by the FreeType Engine
345 *****************************************************************************/
346 static int LoadFontsFromAttachments( filter_t *p_filter )
348 filter_sys_t *p_sys = p_filter->p_sys;
349 input_attachment_t **pp_attachments;
350 int i_attachments_cnt;
352 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
355 p_sys->i_font_attachments = 0;
356 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
357 if( !p_sys->pp_font_attachments )
360 for( int k = 0; k < i_attachments_cnt; k++ )
362 input_attachment_t *p_attach = pp_attachments[k];
364 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
365 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
366 p_attach->i_data > 0 && p_attach->p_data )
368 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
372 vlc_input_attachment_Delete( p_attach );
375 free( pp_attachments );
380 static int GetFontSize( filter_t *p_filter )
382 filter_sys_t *p_sys = p_filter->p_sys;
385 if( p_sys->i_default_font_size )
387 i_size = p_sys->i_default_font_size;
391 int i_ratio = var_GetInteger( p_filter, "freetype-rel-fontsize" );
394 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
395 p_filter->p_sys->i_display_height = p_filter->fmt_out.video.i_height;
400 msg_Warn( p_filter, "invalid fontsize, using 12" );
406 static int SetFontSize( filter_t *p_filter, int i_size )
408 filter_sys_t *p_sys = p_filter->p_sys;
412 i_size = GetFontSize( p_filter );
414 msg_Dbg( p_filter, "using fontsize: %i", i_size );
417 p_sys->i_font_size = i_size;
419 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
421 msg_Err( p_filter, "couldn't set font size to %d", i_size );
429 #ifdef HAVE_FONTCONFIG
430 static void FontConfig_BuildCache( filter_t *p_filter )
433 msg_Dbg( p_filter, "Building font databases.");
438 dialog_progress_bar_t *p_dialog = NULL;
439 FcConfig *fcConfig = FcInitLoadConfig();
441 p_dialog = dialog_ProgressCreate( p_filter,
442 _("Building font cache"),
443 _("Please wait while your font cache is rebuilt.\n"
444 "This should take less than a few minutes."), NULL );
447 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
449 FcConfigBuildFonts( fcConfig );
452 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
453 dialog_ProgressDestroy( p_dialog );
458 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
462 * \brief Selects a font matching family, bold, italic provided
464 static char* FontConfig_Select( FcConfig* config, const char* family,
465 bool b_bold, bool b_italic, int i_size, int *i_idx )
467 FcResult result = FcResultMatch;
468 FcPattern *pat, *p_pat;
472 /* Create a pattern and fills it */
473 pat = FcPatternCreate();
474 if (!pat) return NULL;
477 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
478 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
479 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
480 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
484 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
486 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
487 free( psz_fontsize );
492 FcDefaultSubstitute( pat );
493 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
495 FcPatternDestroy( pat );
499 /* Find the best font for the pattern, destroy the pattern */
500 p_pat = FcFontMatch( config, pat, &result );
501 FcPatternDestroy( pat );
502 if( !p_pat || result == FcResultNoMatch ) return NULL;
504 /* Check the new pattern */
505 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
506 || ( val_b != FcTrue ) )
508 FcPatternDestroy( p_pat );
511 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
516 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
518 FcPatternDestroy( p_pat );
522 /* if( strcasecmp((const char*)val_s, family ) != 0 )
523 msg_Warn( p_filter, "fontconfig: selected font family is not"
524 "the requested one: '%s' != '%s'\n",
525 (const char*)val_s, family ); */
527 if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
529 FcPatternDestroy( p_pat );
533 FcPatternDestroy( p_pat );
534 return strdup( (const char*)val_s );
540 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
542 static int GetFileFontByName( const char *font_name, char **psz_filename )
545 wchar_t vbuffer[MAX_PATH];
546 wchar_t dbuffer[256];
548 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey) != ERROR_SUCCESS )
551 for( int index = 0;; index++ )
553 DWORD vbuflen = MAX_PATH - 1;
556 if( RegEnumValueW( hKey, index, vbuffer, &vbuflen,
557 NULL, NULL, (LPBYTE)dbuffer, &dbuflen) != ERROR_SUCCESS )
560 char *psz_value = FromWide( vbuffer );
562 char *s = strchr( psz_value,'(' );
563 if( s != NULL && s != psz_value ) s[-1] = '\0';
565 /* Manage concatenated font names */
566 if( strchr( psz_value, '&') ) {
567 if( strcasestr( psz_value, font_name ) != NULL )
571 if( strcasecmp( psz_value, font_name ) == 0 )
576 *psz_filename = FromWide( dbuffer );
581 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
582 DWORD type, LPARAM lParam)
584 VLC_UNUSED( metric );
585 if( (type & RASTER_FONTTYPE) ) return 1;
586 // if( lpelfe->elfScript ) FIXME
588 return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
591 static char* Win32_Select( filter_t *p_filter, const char* family,
592 bool b_bold, bool b_italic, int i_size, int *i_idx )
594 VLC_UNUSED( i_size );
595 // msg_Dbg( p_filter, "Here in Win32_Select, asking for %s", family );
599 lf.lfCharSet = DEFAULT_CHARSET;
603 lf.lfWeight = FW_BOLD;
604 strncpy( (LPSTR)&lf.lfFaceName, family, 32);
607 char *psz_filename = NULL;
608 HDC hDC = GetDC( NULL );
609 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
610 ReleaseDC(NULL, hDC);
612 if( psz_filename == NULL )
615 /* FIXME: increase i_idx, when concatenated strings */
620 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
629 /*****************************************************************************
630 * RenderYUVP: place string in picture
631 *****************************************************************************
632 * This function merges the previously rendered freetype glyphs into a picture
633 *****************************************************************************/
634 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
638 VLC_UNUSED(p_filter);
639 static const uint8_t pi_gamma[16] =
640 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
641 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
645 int i, x, y, i_pitch;
646 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
648 /* Create a new subpicture region */
649 video_format_Init( &fmt, VLC_CODEC_YUVP );
651 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
653 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
655 assert( !p_region->p_picture );
656 p_region->p_picture = picture_NewFromFormat( &fmt );
657 if( !p_region->p_picture )
659 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
662 /* Calculate text color components
663 * Only use the first color */
664 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
665 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
668 fmt.p_palette->i_entries = 16;
669 for( i = 0; i < 8; i++ )
671 fmt.p_palette->palette[i][0] = 0;
672 fmt.p_palette->palette[i][1] = 0x80;
673 fmt.p_palette->palette[i][2] = 0x80;
674 fmt.p_palette->palette[i][3] = pi_gamma[i];
675 fmt.p_palette->palette[i][3] =
676 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
678 for( i = 8; i < fmt.p_palette->i_entries; i++ )
680 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
681 fmt.p_palette->palette[i][1] = i_u;
682 fmt.p_palette->palette[i][2] = i_v;
683 fmt.p_palette->palette[i][3] = pi_gamma[i];
684 fmt.p_palette->palette[i][3] =
685 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
688 p_dst = p_region->p_picture->Y_PIXELS;
689 i_pitch = p_region->p_picture->Y_PITCH;
691 /* Initialize the region pixels */
692 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
694 for( ; p_line != NULL; p_line = p_line->p_next )
696 int i_align_left = 0;
697 if( p_line->i_width < (int)fmt.i_visible_width )
699 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
700 i_align_left = ( fmt.i_visible_width - p_line->i_width );
701 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
702 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
706 for( i = 0; i < p_line->i_character_count; i++ )
708 const line_character_t *ch = &p_line->p_character[i];
709 FT_BitmapGlyph p_glyph = ch->p_glyph;
711 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
712 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
714 for( y = 0; y < p_glyph->bitmap.rows; y++ )
716 for( x = 0; x < p_glyph->bitmap.width; x++ )
718 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
719 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
720 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
726 /* Outlining (find something better than nearest neighbour filtering ?) */
729 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
730 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
731 uint8_t left, current;
733 for( y = 1; y < (int)fmt.i_height - 1; y++ )
735 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
736 p_dst += p_region->p_picture->Y_PITCH;
739 for( x = 1; x < (int)fmt.i_width - 1; x++ )
742 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
743 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;
747 memset( p_top, 0, fmt.i_width );
753 /*****************************************************************************
754 * RenderYUVA: place string in picture
755 *****************************************************************************
756 * This function merges the previously rendered freetype glyphs into a picture
757 *****************************************************************************/
758 static void FillYUVAPicture( picture_t *p_picture,
759 int i_a, int i_y, int i_u, int i_v )
761 memset( p_picture->p[0].p_pixels, i_y,
762 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
763 memset( p_picture->p[1].p_pixels, i_u,
764 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
765 memset( p_picture->p[2].p_pixels, i_v,
766 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
767 memset( p_picture->p[3].p_pixels, i_a,
768 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
771 static inline void BlendYUVAPixel( picture_t *p_picture,
772 int i_picture_x, int i_picture_y,
773 int i_a, int i_y, int i_u, int i_v,
776 int i_an = i_a * i_alpha / 255;
778 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
779 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
780 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
781 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
793 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
796 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
797 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
798 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
803 static void FillRGBAPicture( picture_t *p_picture,
804 int i_a, int i_r, int i_g, int i_b )
806 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
808 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
810 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
819 static inline void BlendRGBAPixel( picture_t *p_picture,
820 int i_picture_x, int i_picture_y,
821 int i_a, int i_r, int i_g, int i_b,
824 int i_an = i_a * i_alpha / 255;
826 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
828 int i_ao = p_rgba[3];
838 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
841 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
842 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
843 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
848 static inline void BlendAXYZGlyph( picture_t *p_picture,
849 int i_picture_x, int i_picture_y,
850 int i_a, int i_x, int i_y, int i_z,
851 FT_BitmapGlyph p_glyph,
852 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
855 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
857 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
858 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
860 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
864 static inline void BlendAXYZLine( picture_t *p_picture,
865 int i_picture_x, int i_picture_y,
866 int i_a, int i_x, int i_y, int i_z,
867 const line_character_t *p_current,
868 const line_character_t *p_next,
869 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
871 int i_line_width = p_current->p_glyph->bitmap.width;
873 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
875 for( int dx = 0; dx < i_line_width; dx++ )
877 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
878 BlendPixel( p_picture,
880 i_picture_y + p_current->i_line_offset + dy,
881 i_a, i_x, i_y, i_z, 0xff );
885 static inline int RenderAXYZ( filter_t *p_filter,
886 subpicture_region_t *p_region,
887 line_desc_t *p_line_head,
890 vlc_fourcc_t i_chroma,
891 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
892 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
893 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
895 filter_sys_t *p_sys = p_filter->p_sys;
897 /* Create a new subpicture region */
898 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
899 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
901 video_format_Init( &fmt, i_chroma );
903 fmt.i_visible_width = i_text_width + 2 * i_margin;
905 fmt.i_visible_height = i_text_height + 2 * i_margin;
907 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
908 if( !p_region->p_picture )
912 /* Initialize the picture background */
913 uint8_t i_a = p_sys->i_background_opacity;
914 uint8_t i_x, i_y, i_z;
915 ExtractComponents( p_sys->i_background_color, &i_x, &i_y, &i_z );
917 FillPicture( p_picture, i_a, i_x, i_y, i_z );
919 /* Render shadow then outline and then normal glyphs */
920 for( int g = 0; g < 3; g++ )
922 /* Render all lines */
923 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
925 int i_align_left = i_margin;
926 if( p_line->i_width < i_text_width )
928 /* Left offset to take into account alignment */
929 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
930 i_align_left += ( i_text_width - p_line->i_width );
931 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
932 i_align_left += ( i_text_width - p_line->i_width ) / 2;
934 int i_align_top = i_margin;
936 /* Render all glyphs and underline/strikethrough */
937 for( int i = 0; i < p_line->i_character_count; i++ )
939 const line_character_t *ch = &p_line->p_character[i];
940 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
944 i_a = (ch->i_color >> 24) & 0xff;
948 i_a = i_a * p_sys->i_shadow_opacity / 255;
949 i_color = p_sys->i_shadow_color;
952 i_a = i_a * p_sys->i_outline_opacity / 255;
953 i_color = p_sys->i_outline_color;
956 i_color = ch->i_color;
959 ExtractComponents( i_color, &i_x, &i_y, &i_z );
961 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
962 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
964 BlendAXYZGlyph( p_picture,
965 i_glyph_x, i_glyph_y,
970 /* underline/strikethrough are only rendered for the normal glyph */
971 if( g == 2 && ch->i_line_thickness > 0 )
972 BlendAXYZLine( p_picture,
973 i_glyph_x, i_glyph_y + p_glyph->top,
976 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
985 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
986 uint32_t i_font_color, uint32_t i_karaoke_bg_color,
989 text_style_t *p_style = text_style_New();
993 p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
994 p_style->i_font_size = i_font_size;
995 p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0;
996 p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
997 p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0;
998 p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
999 p_style->i_style_flags |= i_style_flags;
1003 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1004 uint32_t i_color, uint32_t i_karaoke_bg_color )
1007 return VLC_EGENERIC;
1009 font_stack_t *p_new = malloc( sizeof(*p_new) );
1013 p_new->p_next = NULL;
1016 p_new->psz_name = strdup( psz_name );
1018 p_new->psz_name = NULL;
1020 p_new->i_size = i_size;
1021 p_new->i_color = i_color;
1022 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1030 font_stack_t *p_last;
1032 for( p_last = *p_font;
1034 p_last = p_last->p_next )
1037 p_last->p_next = p_new;
1042 static int PopFont( font_stack_t **p_font )
1044 font_stack_t *p_last, *p_next_to_last;
1046 if( !p_font || !*p_font )
1047 return VLC_EGENERIC;
1049 p_next_to_last = NULL;
1050 for( p_last = *p_font;
1052 p_last = p_last->p_next )
1054 p_next_to_last = p_last;
1057 if( p_next_to_last )
1058 p_next_to_last->p_next = NULL;
1062 free( p_last->psz_name );
1068 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1069 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1071 font_stack_t *p_last;
1073 if( !p_font || !*p_font )
1074 return VLC_EGENERIC;
1076 for( p_last=*p_font;
1078 p_last=p_last->p_next )
1081 *psz_name = p_last->psz_name;
1082 *i_size = p_last->i_size;
1083 *i_color = p_last->i_color;
1084 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1089 static const struct {
1090 const char *psz_name;
1092 } p_html_colors[] = {
1093 /* Official html colors */
1094 { "Aqua", 0x00FFFF },
1095 { "Black", 0x000000 },
1096 { "Blue", 0x0000FF },
1097 { "Fuchsia", 0xFF00FF },
1098 { "Gray", 0x808080 },
1099 { "Green", 0x008000 },
1100 { "Lime", 0x00FF00 },
1101 { "Maroon", 0x800000 },
1102 { "Navy", 0x000080 },
1103 { "Olive", 0x808000 },
1104 { "Purple", 0x800080 },
1105 { "Red", 0xFF0000 },
1106 { "Silver", 0xC0C0C0 },
1107 { "Teal", 0x008080 },
1108 { "White", 0xFFFFFF },
1109 { "Yellow", 0xFFFF00 },
1112 { "AliceBlue", 0xF0F8FF },
1113 { "AntiqueWhite", 0xFAEBD7 },
1114 { "Aqua", 0x00FFFF },
1115 { "Aquamarine", 0x7FFFD4 },
1116 { "Azure", 0xF0FFFF },
1117 { "Beige", 0xF5F5DC },
1118 { "Bisque", 0xFFE4C4 },
1119 { "Black", 0x000000 },
1120 { "BlanchedAlmond", 0xFFEBCD },
1121 { "Blue", 0x0000FF },
1122 { "BlueViolet", 0x8A2BE2 },
1123 { "Brown", 0xA52A2A },
1124 { "BurlyWood", 0xDEB887 },
1125 { "CadetBlue", 0x5F9EA0 },
1126 { "Chartreuse", 0x7FFF00 },
1127 { "Chocolate", 0xD2691E },
1128 { "Coral", 0xFF7F50 },
1129 { "CornflowerBlue", 0x6495ED },
1130 { "Cornsilk", 0xFFF8DC },
1131 { "Crimson", 0xDC143C },
1132 { "Cyan", 0x00FFFF },
1133 { "DarkBlue", 0x00008B },
1134 { "DarkCyan", 0x008B8B },
1135 { "DarkGoldenRod", 0xB8860B },
1136 { "DarkGray", 0xA9A9A9 },
1137 { "DarkGrey", 0xA9A9A9 },
1138 { "DarkGreen", 0x006400 },
1139 { "DarkKhaki", 0xBDB76B },
1140 { "DarkMagenta", 0x8B008B },
1141 { "DarkOliveGreen", 0x556B2F },
1142 { "Darkorange", 0xFF8C00 },
1143 { "DarkOrchid", 0x9932CC },
1144 { "DarkRed", 0x8B0000 },
1145 { "DarkSalmon", 0xE9967A },
1146 { "DarkSeaGreen", 0x8FBC8F },
1147 { "DarkSlateBlue", 0x483D8B },
1148 { "DarkSlateGray", 0x2F4F4F },
1149 { "DarkSlateGrey", 0x2F4F4F },
1150 { "DarkTurquoise", 0x00CED1 },
1151 { "DarkViolet", 0x9400D3 },
1152 { "DeepPink", 0xFF1493 },
1153 { "DeepSkyBlue", 0x00BFFF },
1154 { "DimGray", 0x696969 },
1155 { "DimGrey", 0x696969 },
1156 { "DodgerBlue", 0x1E90FF },
1157 { "FireBrick", 0xB22222 },
1158 { "FloralWhite", 0xFFFAF0 },
1159 { "ForestGreen", 0x228B22 },
1160 { "Fuchsia", 0xFF00FF },
1161 { "Gainsboro", 0xDCDCDC },
1162 { "GhostWhite", 0xF8F8FF },
1163 { "Gold", 0xFFD700 },
1164 { "GoldenRod", 0xDAA520 },
1165 { "Gray", 0x808080 },
1166 { "Grey", 0x808080 },
1167 { "Green", 0x008000 },
1168 { "GreenYellow", 0xADFF2F },
1169 { "HoneyDew", 0xF0FFF0 },
1170 { "HotPink", 0xFF69B4 },
1171 { "IndianRed", 0xCD5C5C },
1172 { "Indigo", 0x4B0082 },
1173 { "Ivory", 0xFFFFF0 },
1174 { "Khaki", 0xF0E68C },
1175 { "Lavender", 0xE6E6FA },
1176 { "LavenderBlush", 0xFFF0F5 },
1177 { "LawnGreen", 0x7CFC00 },
1178 { "LemonChiffon", 0xFFFACD },
1179 { "LightBlue", 0xADD8E6 },
1180 { "LightCoral", 0xF08080 },
1181 { "LightCyan", 0xE0FFFF },
1182 { "LightGoldenRodYellow", 0xFAFAD2 },
1183 { "LightGray", 0xD3D3D3 },
1184 { "LightGrey", 0xD3D3D3 },
1185 { "LightGreen", 0x90EE90 },
1186 { "LightPink", 0xFFB6C1 },
1187 { "LightSalmon", 0xFFA07A },
1188 { "LightSeaGreen", 0x20B2AA },
1189 { "LightSkyBlue", 0x87CEFA },
1190 { "LightSlateGray", 0x778899 },
1191 { "LightSlateGrey", 0x778899 },
1192 { "LightSteelBlue", 0xB0C4DE },
1193 { "LightYellow", 0xFFFFE0 },
1194 { "Lime", 0x00FF00 },
1195 { "LimeGreen", 0x32CD32 },
1196 { "Linen", 0xFAF0E6 },
1197 { "Magenta", 0xFF00FF },
1198 { "Maroon", 0x800000 },
1199 { "MediumAquaMarine", 0x66CDAA },
1200 { "MediumBlue", 0x0000CD },
1201 { "MediumOrchid", 0xBA55D3 },
1202 { "MediumPurple", 0x9370D8 },
1203 { "MediumSeaGreen", 0x3CB371 },
1204 { "MediumSlateBlue", 0x7B68EE },
1205 { "MediumSpringGreen", 0x00FA9A },
1206 { "MediumTurquoise", 0x48D1CC },
1207 { "MediumVioletRed", 0xC71585 },
1208 { "MidnightBlue", 0x191970 },
1209 { "MintCream", 0xF5FFFA },
1210 { "MistyRose", 0xFFE4E1 },
1211 { "Moccasin", 0xFFE4B5 },
1212 { "NavajoWhite", 0xFFDEAD },
1213 { "Navy", 0x000080 },
1214 { "OldLace", 0xFDF5E6 },
1215 { "Olive", 0x808000 },
1216 { "OliveDrab", 0x6B8E23 },
1217 { "Orange", 0xFFA500 },
1218 { "OrangeRed", 0xFF4500 },
1219 { "Orchid", 0xDA70D6 },
1220 { "PaleGoldenRod", 0xEEE8AA },
1221 { "PaleGreen", 0x98FB98 },
1222 { "PaleTurquoise", 0xAFEEEE },
1223 { "PaleVioletRed", 0xD87093 },
1224 { "PapayaWhip", 0xFFEFD5 },
1225 { "PeachPuff", 0xFFDAB9 },
1226 { "Peru", 0xCD853F },
1227 { "Pink", 0xFFC0CB },
1228 { "Plum", 0xDDA0DD },
1229 { "PowderBlue", 0xB0E0E6 },
1230 { "Purple", 0x800080 },
1231 { "Red", 0xFF0000 },
1232 { "RosyBrown", 0xBC8F8F },
1233 { "RoyalBlue", 0x4169E1 },
1234 { "SaddleBrown", 0x8B4513 },
1235 { "Salmon", 0xFA8072 },
1236 { "SandyBrown", 0xF4A460 },
1237 { "SeaGreen", 0x2E8B57 },
1238 { "SeaShell", 0xFFF5EE },
1239 { "Sienna", 0xA0522D },
1240 { "Silver", 0xC0C0C0 },
1241 { "SkyBlue", 0x87CEEB },
1242 { "SlateBlue", 0x6A5ACD },
1243 { "SlateGray", 0x708090 },
1244 { "SlateGrey", 0x708090 },
1245 { "Snow", 0xFFFAFA },
1246 { "SpringGreen", 0x00FF7F },
1247 { "SteelBlue", 0x4682B4 },
1248 { "Tan", 0xD2B48C },
1249 { "Teal", 0x008080 },
1250 { "Thistle", 0xD8BFD8 },
1251 { "Tomato", 0xFF6347 },
1252 { "Turquoise", 0x40E0D0 },
1253 { "Violet", 0xEE82EE },
1254 { "Wheat", 0xF5DEB3 },
1255 { "White", 0xFFFFFF },
1256 { "WhiteSmoke", 0xF5F5F5 },
1257 { "Yellow", 0xFFFF00 },
1258 { "YellowGreen", 0x9ACD32 },
1263 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1264 font_stack_t **p_fonts )
1267 char *psz_fontname = NULL;
1268 uint32_t i_font_color = 0xffffff;
1269 int i_font_alpha = 255;
1270 uint32_t i_karaoke_bg_color = 0x00ffffff;
1271 int i_font_size = 24;
1273 /* Default all attributes to the top font in the stack -- in case not
1274 * all attributes are specified in the sub-font
1276 if( VLC_SUCCESS == PeekFont( p_fonts,
1280 &i_karaoke_bg_color ))
1282 psz_fontname = strdup( psz_fontname );
1283 i_font_size = i_font_size;
1285 i_font_alpha = (i_font_color >> 24) & 0xff;
1286 i_font_color &= 0x00ffffff;
1288 const char *name, *value;
1289 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1291 if( !strcasecmp( "face", name ) )
1293 free( psz_fontname );
1294 psz_fontname = strdup( value );
1296 else if( !strcasecmp( "size", name ) )
1298 if( ( *value == '+' ) || ( *value == '-' ) )
1300 int i_value = atoi( value );
1302 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1303 i_font_size += ( i_value * i_font_size ) / 10;
1304 else if( i_value < -5 )
1305 i_font_size = - i_value;
1306 else if( i_value > 5 )
1307 i_font_size = i_value;
1310 i_font_size = atoi( value );
1312 else if( !strcasecmp( "color", name ) )
1314 if( value[0] == '#' )
1316 i_font_color = strtol( value + 1, NULL, 16 );
1317 i_font_color &= 0x00ffffff;
1322 uint32_t i_value = strtol( value, &end, 16 );
1323 if( *end == '\0' || *end == ' ' )
1324 i_font_color = i_value & 0x00ffffff;
1326 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1328 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1330 i_font_color = p_html_colors[i].i_value;
1336 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1338 i_font_alpha = strtol( value + 1, NULL, 16 );
1339 i_font_alpha &= 0xff;
1342 rv = PushFont( p_fonts,
1345 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1346 i_karaoke_bg_color );
1348 free( psz_fontname );
1353 /* Turn any multiple-whitespaces into single spaces */
1354 static void HandleWhiteSpace( char *psz_node )
1356 char *s = strpbrk( psz_node, "\t\r\n " );
1359 int i_whitespace = strspn( s, "\t\r\n " );
1361 if( i_whitespace > 1 )
1364 strlen( s ) - i_whitespace + 1 );
1367 s = strpbrk( s, "\t\r\n " );
1372 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1373 font_stack_t **p_fonts,
1376 char *psz_fontname = NULL;
1377 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1378 uint32_t i_karaoke_bg_color = i_font_color;
1379 int i_font_size = p_sys->i_font_size;
1381 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1382 &i_font_color, &i_karaoke_bg_color ) )
1385 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1390 static unsigned SetupText( filter_t *p_filter,
1391 uint32_t *psz_text_out,
1392 text_style_t **pp_styles,
1393 uint32_t *pi_k_dates,
1395 const char *psz_text_in,
1396 text_style_t *p_style,
1399 size_t i_string_length;
1401 size_t i_string_bytes;
1402 #if defined(WORDS_BIGENDIAN)
1403 uint32_t *psz_tmp = ToCharset( "UCS-4BE", psz_text_in, &i_string_bytes );
1405 uint32_t *psz_tmp = ToCharset( "UCS-4LE", psz_text_in, &i_string_bytes );
1409 memcpy( psz_text_out, psz_tmp, i_string_bytes );
1410 i_string_length = i_string_bytes / 4;
1415 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1416 i_string_length = 0;
1419 if( i_string_length > 0 )
1421 for( unsigned i = 0; i < i_string_length; i++ )
1422 pp_styles[i] = p_style;
1426 text_style_Delete( p_style );
1428 if( i_string_length > 0 && pi_k_dates )
1430 for( unsigned i = 0; i < i_string_length; i++ )
1431 pi_k_dates[i] = i_k_date;
1433 return i_string_length;
1436 static int ProcessNodes( filter_t *p_filter,
1438 text_style_t **pp_styles,
1439 uint32_t *pi_k_dates,
1441 xml_reader_t *p_xml_reader,
1442 text_style_t *p_font_style )
1444 int rv = VLC_SUCCESS;
1445 filter_sys_t *p_sys = p_filter->p_sys;
1446 int i_text_length = 0;
1447 font_stack_t *p_fonts = NULL;
1448 uint32_t i_k_date = 0;
1450 int i_style_flags = 0;
1454 rv = PushFont( &p_fonts,
1455 p_font_style->psz_fontname,
1456 p_font_style->i_font_size,
1457 (p_font_style->i_font_color & 0xffffff) |
1458 ((p_font_style->i_font_alpha & 0xff) << 24),
1459 (p_font_style->i_karaoke_background_color & 0xffffff) |
1460 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1462 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1470 rv = PushFont( &p_fonts,
1471 p_sys->psz_fontfamily,
1473 (p_sys->i_font_color & 0xffffff) |
1474 ((p_sys->i_font_opacity & 0xff) << 24),
1478 if( p_sys->b_font_bold )
1479 i_style_flags |= STYLE_BOLD;
1481 if( rv != VLC_SUCCESS )
1487 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1491 case XML_READER_ENDELEM:
1492 if( !strcasecmp( "font", node ) )
1493 PopFont( &p_fonts );
1494 else if( !strcasecmp( "b", node ) )
1495 i_style_flags &= ~STYLE_BOLD;
1496 else if( !strcasecmp( "i", node ) )
1497 i_style_flags &= ~STYLE_ITALIC;
1498 else if( !strcasecmp( "u", node ) )
1499 i_style_flags &= ~STYLE_UNDERLINE;
1500 else if( !strcasecmp( "s", node ) )
1501 i_style_flags &= ~STYLE_STRIKEOUT;
1504 case XML_READER_STARTELEM:
1505 if( !strcasecmp( "font", node ) )
1506 HandleFontAttributes( p_xml_reader, &p_fonts );
1507 else if( !strcasecmp( "b", node ) )
1508 i_style_flags |= STYLE_BOLD;
1509 else if( !strcasecmp( "i", node ) )
1510 i_style_flags |= STYLE_ITALIC;
1511 else if( !strcasecmp( "u", node ) )
1512 i_style_flags |= STYLE_UNDERLINE;
1513 else if( !strcasecmp( "s", node ) )
1514 i_style_flags |= STYLE_STRIKEOUT;
1515 else if( !strcasecmp( "br", node ) )
1517 i_text_length += SetupText( p_filter,
1518 &psz_text[i_text_length],
1519 &pp_styles[i_text_length],
1520 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1522 GetStyleFromFontStack( p_sys,
1527 else if( !strcasecmp( "k", node ) )
1530 const char *name, *value;
1531 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1533 if( !strcasecmp( "t", name ) && value )
1534 i_k_date += atoi( value );
1539 case XML_READER_TEXT:
1541 char *psz_node = strdup( node );
1542 if( unlikely(!psz_node) )
1545 HandleWhiteSpace( psz_node );
1546 resolve_xml_special_chars( psz_node );
1548 i_text_length += SetupText( p_filter,
1549 &psz_text[i_text_length],
1550 &pp_styles[i_text_length],
1551 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1553 GetStyleFromFontStack( p_sys,
1563 *pi_len = i_text_length;
1565 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1570 static void FreeLine( line_desc_t *p_line )
1572 for( int i = 0; i < p_line->i_character_count; i++ )
1574 line_character_t *ch = &p_line->p_character[i];
1575 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1577 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1579 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1582 free( p_line->p_character );
1586 static void FreeLines( line_desc_t *p_lines )
1588 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1590 line_desc_t *p_next = p_line->p_next;
1596 static line_desc_t *NewLine( int i_count )
1598 line_desc_t *p_line = malloc( sizeof(*p_line) );
1603 p_line->p_next = NULL;
1604 p_line->i_width = 0;
1605 p_line->i_base_line = 0;
1606 p_line->i_character_count = 0;
1608 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1609 if( !p_line->p_character )
1617 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1619 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1621 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1623 FT_Face p_face = NULL;
1625 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1633 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1634 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1635 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1636 (p_style->i_style_flags & (STYLE_BOLD | STYLE_BOLD)) == i_style_received )
1639 FT_Done_Face( p_face );
1647 static FT_Face LoadFace( filter_t *p_filter,
1648 const text_style_t *p_style )
1650 filter_sys_t *p_sys = p_filter->p_sys;
1652 /* Look for a match amongst our attachments first */
1653 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1655 /* Load system wide font otheriwse */
1660 #ifdef HAVE_FONTCONFIG
1661 psz_fontfile = FontConfig_Select( NULL,
1662 p_style->psz_fontname,
1663 (p_style->i_style_flags & STYLE_BOLD) != 0,
1664 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1667 #elif defined( WIN32 )
1668 psz_fontfile = Win32_Select( p_filter,
1669 p_style->psz_fontname,
1670 (p_style->i_style_flags & STYLE_BOLD) != 0,
1671 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1675 psz_fontfile = NULL;
1680 if( *psz_fontfile == '\0' )
1683 "We were not able to find a matching font: \"%s\" (%s %s),"
1684 " so using default font",
1685 p_style->psz_fontname,
1686 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1687 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1692 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1695 free( psz_fontfile );
1700 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1702 /* We've loaded a font face which is unhelpful for actually
1703 * rendering text - fallback to the default one.
1705 FT_Done_Face( p_face );
1711 static bool FaceStyleEquals( const text_style_t *p_style1,
1712 const text_style_t *p_style2 )
1714 if( !p_style1 || !p_style2 )
1716 if( p_style1 == p_style2 )
1719 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
1720 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
1721 !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
1724 static int GetGlyph( filter_t *p_filter,
1725 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1726 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1727 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1733 FT_Vector *p_pen_shadow )
1735 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1736 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1738 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1739 return VLC_EGENERIC;
1742 /* Do synthetic styling now that Freetype supports it;
1743 * ie. if the font we have loaded is NOT already in the
1744 * style that the tags want, then switch it on; if they
1745 * are then don't. */
1746 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1747 FT_GlyphSlot_Embolden( p_face->glyph );
1748 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1749 FT_GlyphSlot_Oblique( p_face->glyph );
1752 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1754 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1755 return VLC_EGENERIC;
1758 FT_Glyph outline = NULL;
1759 if( p_filter->p_sys->p_stroker )
1762 FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 );
1765 FT_Glyph shadow = NULL;
1766 if( p_filter->p_sys->i_shadow_opacity > 0 )
1768 shadow = outline ? outline : glyph;
1769 FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 );
1770 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1772 *pp_shadow = shadow;
1774 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1776 FT_Done_Glyph( glyph );
1778 FT_Done_Glyph( outline );
1780 FT_Done_Glyph( shadow );
1781 return VLC_EGENERIC;
1783 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1788 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1789 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1791 *pp_outline = outline;
1796 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1798 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1799 if( p_bbox->xMin >= p_bbox->xMax )
1801 p_bbox->xMin = FT_CEIL(p_pen->x);
1802 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1803 glyph_bmp->left = p_bbox->xMin;
1805 if( p_bbox->yMin >= p_bbox->yMax )
1807 p_bbox->yMax = FT_CEIL(p_pen->y);
1808 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1809 glyph_bmp->top = p_bbox->yMax;
1813 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1815 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1816 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1817 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1818 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1821 static int ProcessLines( filter_t *p_filter,
1822 line_desc_t **pp_lines,
1824 int *pi_max_face_height,
1827 text_style_t **pp_styles,
1828 uint32_t *pi_k_dates,
1831 filter_sys_t *p_sys = p_filter->p_sys;
1832 uint32_t *p_fribidi_string = NULL;
1833 text_style_t **pp_fribidi_styles = NULL;
1834 int *p_new_positions = NULL;
1836 #if defined(HAVE_FRIBIDI)
1838 int *p_old_positions;
1839 int start_pos, pos = 0;
1841 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1843 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1844 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1845 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1847 if( ! pp_fribidi_styles ||
1848 ! p_fribidi_string ||
1849 ! p_old_positions ||
1852 free( p_old_positions );
1853 free( p_new_positions );
1854 free( p_fribidi_string );
1855 free( pp_fribidi_styles );
1859 /* Do bidi conversion line-by-line */
1862 while(pos < i_len) {
1863 if (psz_text[pos] != '\n')
1865 p_fribidi_string[pos] = psz_text[pos];
1866 pp_fribidi_styles[pos] = pp_styles[pos];
1867 p_new_positions[pos] = pos;
1871 while(pos < i_len) {
1872 if (psz_text[pos] == '\n')
1876 if (pos > start_pos)
1878 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1879 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1881 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1883 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1884 pos - start_pos, &base_dir,
1885 (FriBidiChar*)p_fribidi_string + start_pos,
1886 p_new_positions + start_pos,
1889 for( int j = start_pos; j < pos; j++ )
1891 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1892 p_new_positions[ j ] += start_pos;
1896 p_fribidi_string[ i_len ] = 0;
1897 free( p_old_positions );
1899 pp_styles = pp_fribidi_styles;
1900 psz_text = p_fribidi_string;
1903 /* Work out the karaoke */
1904 uint8_t *pi_karaoke_bar = NULL;
1907 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1908 if( pi_karaoke_bar )
1910 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1911 for( int i = 0; i < i_len; i++ )
1913 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1914 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1918 free( p_new_positions );
1920 *pi_max_face_height = 0;
1922 line_desc_t **pp_line_next = pp_lines;
1930 int i_face_height_previous = 0;
1931 int i_base_line = 0;
1932 const text_style_t *p_previous_style = NULL;
1933 FT_Face p_face = NULL;
1934 for( int i_start = 0; i_start < i_len; )
1936 /* Compute the length of the current text line */
1938 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
1941 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1942 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
1943 int i_index = i_start;
1948 int i_face_height = 0;
1949 FT_BBox line_bbox = {
1955 int i_ul_offset = 0;
1956 int i_ul_thickness = 0;
1965 break_point_t break_point;
1966 break_point_t break_point_fallback;
1968 #define SAVE_BP(dst) do { \
1969 dst.i_index = i_index; \
1971 dst.line_bbox = line_bbox; \
1972 dst.i_face_height = i_face_height; \
1973 dst.i_ul_offset = i_ul_offset; \
1974 dst.i_ul_thickness = i_ul_thickness; \
1977 SAVE_BP( break_point );
1978 SAVE_BP( break_point_fallback );
1980 while( i_index < i_start + i_length )
1982 /* Split by common FT_Face + Size */
1983 const text_style_t *p_current_style = pp_styles[i_index];
1984 int i_part_length = 0;
1985 while( i_index + i_part_length < i_start + i_length )
1987 const text_style_t *p_style = pp_styles[i_index + i_part_length];
1988 if( !FaceStyleEquals( p_style, p_current_style ) ||
1989 p_style->i_font_size != p_current_style->i_font_size )
1994 /* (Re)load/reconfigure the face if needed */
1995 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
1998 FT_Done_Face( p_face );
1999 p_previous_style = NULL;
2001 p_face = LoadFace( p_filter, p_current_style );
2003 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
2004 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
2006 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
2007 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
2008 if( p_sys->p_stroker )
2010 int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
2011 FT_Stroker_Set( p_sys->p_stroker,
2013 FT_STROKER_LINECAP_ROUND,
2014 FT_STROKER_LINEJOIN_ROUND, 0 );
2017 p_previous_style = p_current_style;
2019 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
2020 p_current_face->size->metrics.y_scale)));
2022 /* Render the part */
2023 bool b_break_line = false;
2024 int i_glyph_last = 0;
2025 while( i_part_length > 0 )
2027 const text_style_t *p_glyph_style = pp_styles[i_index];
2028 uint32_t character = psz_text[i_index];
2029 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
2031 /* Get kerning vector */
2032 FT_Vector kerning = { .x = 0, .y = 0 };
2033 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
2034 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
2036 /* Get the glyph bitmap and its bounding box and all the associated properties */
2037 FT_Vector pen_new = {
2038 .x = pen.x + kerning.x,
2039 .y = pen.y + kerning.y,
2041 FT_Vector pen_shadow_new = {
2042 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
2043 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
2048 FT_BBox outline_bbox;
2050 FT_BBox shadow_bbox;
2052 if( GetGlyph( p_filter,
2053 &glyph, &glyph_bbox,
2054 &outline, &outline_bbox,
2055 &shadow, &shadow_bbox,
2056 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
2057 &pen_new, &pen_shadow_new ) )
2060 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
2062 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
2064 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
2066 /* FIXME and what about outline */
2068 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
2069 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
2070 (p_glyph_style->i_karaoke_background_alpha << 24))
2071 : (p_glyph_style->i_font_color |
2072 (p_glyph_style->i_font_alpha << 24));
2073 int i_line_offset = 0;
2074 int i_line_thickness = 0;
2075 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
2077 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
2078 p_current_face->size->metrics.y_scale)) );
2080 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
2081 p_current_face->size->metrics.y_scale)) );
2083 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
2085 /* Move the baseline to make it strikethrough instead of
2086 * underline. That means that strikethrough takes precedence
2088 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
2089 p_current_face->size->metrics.y_scale)) );
2091 else if( i_line_thickness > 0 )
2093 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
2095 /* The real underline thickness and position are
2096 * updated once the whole line has been parsed */
2097 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
2098 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
2099 i_line_thickness = -1;
2102 FT_BBox line_bbox_new = line_bbox;
2103 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
2105 BBoxEnlarge( &line_bbox_new, &outline_bbox );
2107 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
2109 b_break_line = i_index > i_start &&
2110 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
2113 FT_Done_Glyph( glyph );
2115 FT_Done_Glyph( outline );
2117 FT_Done_Glyph( shadow );
2119 break_point_t *p_bp = NULL;
2120 if( break_point.i_index > i_start )
2121 p_bp = &break_point;
2122 else if( break_point_fallback.i_index > i_start )
2123 p_bp = &break_point_fallback;
2127 msg_Dbg( p_filter, "Breaking line");
2128 for( int i = p_bp->i_index; i < i_index; i++ )
2130 line_character_t *ch = &p_line->p_character[i - i_start];
2131 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
2133 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
2135 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
2137 p_line->i_character_count = p_bp->i_index - i_start;
2139 i_index = p_bp->i_index;
2141 line_bbox = p_bp->line_bbox;
2142 i_face_height = p_bp->i_face_height;
2143 i_ul_offset = p_bp->i_ul_offset;
2144 i_ul_thickness = p_bp->i_ul_thickness;
2148 msg_Err( p_filter, "Breaking unbreakable line");
2153 assert( p_line->i_character_count == i_index - i_start);
2154 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2155 .p_glyph = (FT_BitmapGlyph)glyph,
2156 .p_outline = (FT_BitmapGlyph)outline,
2157 .p_shadow = (FT_BitmapGlyph)shadow,
2159 .i_line_offset = i_line_offset,
2160 .i_line_thickness = i_line_thickness,
2163 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2164 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2165 line_bbox = line_bbox_new;
2167 i_glyph_last = i_glyph_index;
2171 if( character == ' ' || character == '\t' )
2172 SAVE_BP( break_point );
2173 else if( character == 160 )
2174 SAVE_BP( break_point_fallback );
2180 /* Update our baseline */
2181 if( i_face_height_previous > 0 )
2182 i_base_line += __MAX(i_face_height, i_face_height_previous);
2183 i_face_height_previous = i_face_height;
2185 /* Update the line bbox with the actual base line */
2186 if (line_bbox.yMax > line_bbox.yMin) {
2187 line_bbox.yMin -= i_base_line;
2188 line_bbox.yMax -= i_base_line;
2190 BBoxEnlarge( &bbox, &line_bbox );
2192 /* Terminate and append the line */
2195 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2196 p_line->i_base_line = i_base_line;
2197 if( i_ul_thickness > 0 )
2199 for( int i = 0; i < p_line->i_character_count; i++ )
2201 line_character_t *ch = &p_line->p_character[i];
2202 if( ch->i_line_thickness < 0 )
2204 ch->i_line_offset = i_ul_offset;
2205 ch->i_line_thickness = i_ul_thickness;
2210 *pp_line_next = p_line;
2211 pp_line_next = &p_line->p_next;
2214 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2216 /* Skip what we have rendered and the line delimitor if present */
2218 if( i_start < i_len && psz_text[i_start] == '\n' )
2221 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
2223 msg_Err( p_filter, "Truncated too high subtitle" );
2228 FT_Done_Face( p_face );
2230 free( pp_fribidi_styles );
2231 free( p_fribidi_string );
2232 free( pi_karaoke_bar );
2239 * This function renders a text subpicture region into another one.
2240 * It also calculates the size needed for this string, and renders the
2241 * needed glyphs into memory. It is used as pf_add_string callback in
2242 * the vout method by this module
2244 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2245 subpicture_region_t *p_region_in, bool b_html,
2246 const vlc_fourcc_t *p_chroma_list )
2248 filter_sys_t *p_sys = p_filter->p_sys;
2251 return VLC_EGENERIC;
2252 if( b_html && !p_region_in->psz_html )
2253 return VLC_EGENERIC;
2254 if( !b_html && !p_region_in->psz_text )
2255 return VLC_EGENERIC;
2257 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2258 : p_region_in->psz_text );
2260 uint32_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2261 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2262 if( !psz_text || !pp_styles )
2266 return VLC_EGENERIC;
2269 /* Reset the default fontsize in case screen metrics have changed */
2270 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2273 int rv = VLC_SUCCESS;
2274 int i_text_length = 0;
2276 int i_max_face_height;
2277 line_desc_t *p_lines = NULL;
2279 uint32_t *pi_k_durations = NULL;
2284 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2285 (uint8_t *) p_region_in->psz_html,
2286 strlen( p_region_in->psz_html ),
2288 if( unlikely(p_sub == NULL) )
2291 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2293 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2295 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2296 p_filter->p_sys->p_xml = p_xml_reader;
2303 /* Look for Root Node */
2306 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2308 if( strcasecmp( "karaoke", node ) == 0 )
2310 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2312 else if( strcasecmp( "text", node ) != 0 )
2314 /* Only text and karaoke tags are supported */
2315 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2322 msg_Err( p_filter, "Malformed HTML subtitle" );
2328 rv = ProcessNodes( p_filter,
2329 psz_text, pp_styles, pi_k_durations, &i_text_length,
2330 p_xml_reader, p_region_in->p_style );
2334 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2336 stream_Delete( p_sub );
2341 text_style_t *p_style;
2342 if( p_region_in->p_style )
2343 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2344 p_region_in->p_style->i_font_size,
2345 (p_region_in->p_style->i_font_color & 0xffffff) |
2346 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2348 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2353 p_style = CreateStyle( p_sys->psz_fontfamily,
2355 (p_sys->i_font_color & 0xffffff) |
2356 ((p_sys->i_font_opacity & 0xff) << 24),
2358 if( p_sys->b_font_bold )
2359 p_style->i_style_flags |= STYLE_BOLD;
2361 i_text_length = SetupText( p_filter,
2365 p_region_in->psz_text, p_style, 0 );
2368 if( !rv && i_text_length > 0 )
2370 rv = ProcessLines( p_filter,
2371 &p_lines, &bbox, &i_max_face_height,
2372 psz_text, pp_styles, pi_k_durations, i_text_length );
2375 p_region_out->i_x = p_region_in->i_x;
2376 p_region_out->i_y = p_region_in->i_y;
2378 /* Don't attempt to render text that couldn't be layed out
2380 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2382 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2383 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2385 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2386 p_chroma_list = p_chroma_list_yuvp;
2387 else if( !p_chroma_list || *p_chroma_list == 0 )
2388 p_chroma_list = p_chroma_list_rgba;
2390 const int i_margin = p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2391 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2394 if( *p_chroma == VLC_CODEC_YUVP )
2395 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2396 else if( *p_chroma == VLC_CODEC_YUVA )
2397 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2402 else if( *p_chroma == VLC_CODEC_RGBA )
2403 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2412 /* With karaoke, we're going to have to render the text a number
2413 * of times to show the progress marker on the text.
2415 if( pi_k_durations )
2416 var_SetBool( p_filter, "text-rerender", true );
2419 FreeLines( p_lines );
2422 for( int i = 0; i < i_text_length; i++ )
2424 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2425 text_style_Delete( pp_styles[i] );
2428 free( pi_k_durations );
2433 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2434 subpicture_region_t *p_region_in,
2435 const vlc_fourcc_t *p_chroma_list )
2437 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2442 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2443 subpicture_region_t *p_region_in,
2444 const vlc_fourcc_t *p_chroma_list )
2446 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2451 /*****************************************************************************
2452 * Create: allocates osd-text video thread output method
2453 *****************************************************************************
2454 * This function allocates and initializes a Clone vout method.
2455 *****************************************************************************/
2456 static int Create( vlc_object_t *p_this )
2458 filter_t *p_filter = (filter_t *)p_this;
2459 filter_sys_t *p_sys;
2460 char *psz_fontfile = NULL;
2461 char *psz_fontfamily = NULL;
2462 int i_error = 0, fontindex = 0;
2464 /* Allocate structure */
2465 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2469 p_sys->psz_fontfamily = NULL;
2471 p_sys->p_xml = NULL;
2474 p_sys->p_library = 0;
2475 p_sys->i_font_size = 0;
2476 p_sys->i_display_height = 0;
2478 var_Create( p_filter, "freetype-rel-fontsize",
2479 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2481 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2482 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2483 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2484 p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
2485 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2486 p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
2487 p_sys->b_font_bold = var_InheritBool( p_filter, "freetype-bold" );
2489 p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
2490 p_sys->i_background_opacity = __MAX( __MIN( p_sys->i_background_opacity, 255 ), 0 );
2491 p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
2492 p_sys->i_background_color = __MAX( __MIN( p_sys->i_background_color, 0xFFFFFF ), 0 );
2494 p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2495 p_sys->f_outline_thickness = __MAX( __MIN( p_sys->f_outline_thickness, 0.5 ), 0.0 );
2496 p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2497 p_sys->i_outline_opacity = __MAX( __MIN( p_sys->i_outline_opacity, 255 ), 0 );
2498 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2499 p_sys->i_outline_color = __MAX( __MIN( p_sys->i_outline_color, 0xFFFFFF ), 0 );
2501 p_sys->i_shadow_opacity = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2502 p_sys->i_shadow_opacity = __MAX( __MIN( p_sys->i_shadow_opacity, 255 ), 0 );
2503 p_sys->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2504 p_sys->i_shadow_color = __MAX( __MIN( p_sys->i_shadow_color, 0xFFFFFF ), 0 );
2505 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2506 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2507 f_shadow_distance = __MAX( __MIN( f_shadow_distance, 1 ), 0 );
2508 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2509 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2512 /* Get Windows Font folder */
2513 wchar_t wdir[MAX_PATH];
2514 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2516 GetWindowsDirectoryW( wdir, MAX_PATH );
2517 wcscat( wdir, L"\\fonts" );
2519 p_sys->psz_win_fonts_path = FromWide( wdir );
2522 /* Set default psz_fontfamily */
2523 if( !psz_fontfamily || !*psz_fontfamily )
2525 free( psz_fontfamily );
2527 psz_fontfamily = strdup( DEFAULT_FAMILY );
2530 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2533 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2535 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2539 /* Set the current font file */
2540 p_sys->psz_fontfamily = psz_fontfamily;
2542 #ifdef HAVE_FONTCONFIG
2543 FontConfig_BuildCache( p_filter );
2546 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2547 p_sys->i_default_font_size, &fontindex );
2548 #elif defined(WIN32)
2549 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2550 p_sys->i_default_font_size, &fontindex );
2553 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2555 /* If nothing is found, use the default family */
2557 psz_fontfile = strdup( psz_fontfamily );
2559 #else /* !HAVE_STYLES */
2560 /* Use the default file */
2561 psz_fontfile = psz_fontfamily;
2565 i_error = FT_Init_FreeType( &p_sys->p_library );
2568 msg_Err( p_filter, "couldn't initialize freetype" );
2572 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2573 fontindex, &p_sys->p_face );
2575 if( i_error == FT_Err_Unknown_File_Format )
2577 msg_Err( p_filter, "file %s have unknown format",
2578 psz_fontfile ? psz_fontfile : "(null)" );
2583 msg_Err( p_filter, "failed to load font file %s",
2584 psz_fontfile ? psz_fontfile : "(null)" );
2588 free( psz_fontfile );
2591 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2594 msg_Err( p_filter, "font has no unicode translation table" );
2598 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2600 p_sys->p_stroker = NULL;
2601 if( p_sys->f_outline_thickness > 0.001 )
2603 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2605 msg_Err( p_filter, "Failed to create stroker for outlining" );
2608 p_sys->pp_font_attachments = NULL;
2609 p_sys->i_font_attachments = 0;
2611 p_filter->pf_render_text = RenderText;
2613 p_filter->pf_render_html = RenderHtml;
2615 p_filter->pf_render_html = NULL;
2618 LoadFontsFromAttachments( p_filter );
2623 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2624 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2626 free( psz_fontfile );
2628 free( psz_fontfamily );
2630 return VLC_EGENERIC;
2633 /*****************************************************************************
2634 * Destroy: destroy Clone video thread output method
2635 *****************************************************************************
2636 * Clean up all data and library connections
2637 *****************************************************************************/
2638 static void Destroy( vlc_object_t *p_this )
2640 filter_t *p_filter = (filter_t *)p_this;
2641 filter_sys_t *p_sys = p_filter->p_sys;
2643 if( p_sys->pp_font_attachments )
2645 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2646 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2648 free( p_sys->pp_font_attachments );
2652 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2654 free( p_sys->psz_fontfamily );
2656 /* FcFini asserts calling the subfunction FcCacheFini()
2657 * even if no other library functions have been made since FcInit(),
2658 * so don't call it. */
2660 if( p_sys->p_stroker )
2661 FT_Stroker_Done( p_sys->p_stroker );
2662 FT_Done_Face( p_sys->p_face );
2663 FT_Done_FreeType( p_sys->p_library );