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 > 0 ? p_font_style->i_font_size
1457 : p_sys->i_font_size,
1458 (p_font_style->i_font_color & 0xffffff) |
1459 ((p_font_style->i_font_alpha & 0xff) << 24),
1460 (p_font_style->i_karaoke_background_color & 0xffffff) |
1461 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1463 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1471 rv = PushFont( &p_fonts,
1472 p_sys->psz_fontfamily,
1474 (p_sys->i_font_color & 0xffffff) |
1475 ((p_sys->i_font_opacity & 0xff) << 24),
1479 if( p_sys->b_font_bold )
1480 i_style_flags |= STYLE_BOLD;
1482 if( rv != VLC_SUCCESS )
1488 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1492 case XML_READER_ENDELEM:
1493 if( !strcasecmp( "font", node ) )
1494 PopFont( &p_fonts );
1495 else if( !strcasecmp( "b", node ) )
1496 i_style_flags &= ~STYLE_BOLD;
1497 else if( !strcasecmp( "i", node ) )
1498 i_style_flags &= ~STYLE_ITALIC;
1499 else if( !strcasecmp( "u", node ) )
1500 i_style_flags &= ~STYLE_UNDERLINE;
1501 else if( !strcasecmp( "s", node ) )
1502 i_style_flags &= ~STYLE_STRIKEOUT;
1505 case XML_READER_STARTELEM:
1506 if( !strcasecmp( "font", node ) )
1507 HandleFontAttributes( p_xml_reader, &p_fonts );
1508 else if( !strcasecmp( "b", node ) )
1509 i_style_flags |= STYLE_BOLD;
1510 else if( !strcasecmp( "i", node ) )
1511 i_style_flags |= STYLE_ITALIC;
1512 else if( !strcasecmp( "u", node ) )
1513 i_style_flags |= STYLE_UNDERLINE;
1514 else if( !strcasecmp( "s", node ) )
1515 i_style_flags |= STYLE_STRIKEOUT;
1516 else if( !strcasecmp( "br", node ) )
1518 i_text_length += SetupText( p_filter,
1519 &psz_text[i_text_length],
1520 &pp_styles[i_text_length],
1521 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1523 GetStyleFromFontStack( p_sys,
1528 else if( !strcasecmp( "k", node ) )
1531 const char *name, *value;
1532 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1534 if( !strcasecmp( "t", name ) && value )
1535 i_k_date += atoi( value );
1540 case XML_READER_TEXT:
1542 char *psz_node = strdup( node );
1543 if( unlikely(!psz_node) )
1546 HandleWhiteSpace( psz_node );
1547 resolve_xml_special_chars( psz_node );
1549 i_text_length += SetupText( p_filter,
1550 &psz_text[i_text_length],
1551 &pp_styles[i_text_length],
1552 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1554 GetStyleFromFontStack( p_sys,
1564 *pi_len = i_text_length;
1566 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1571 static void FreeLine( line_desc_t *p_line )
1573 for( int i = 0; i < p_line->i_character_count; i++ )
1575 line_character_t *ch = &p_line->p_character[i];
1576 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1578 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1580 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1583 free( p_line->p_character );
1587 static void FreeLines( line_desc_t *p_lines )
1589 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1591 line_desc_t *p_next = p_line->p_next;
1597 static line_desc_t *NewLine( int i_count )
1599 line_desc_t *p_line = malloc( sizeof(*p_line) );
1604 p_line->p_next = NULL;
1605 p_line->i_width = 0;
1606 p_line->i_base_line = 0;
1607 p_line->i_character_count = 0;
1609 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1610 if( !p_line->p_character )
1618 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1620 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1622 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1624 FT_Face p_face = NULL;
1626 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1634 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1635 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1636 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1637 (p_style->i_style_flags & (STYLE_BOLD | STYLE_BOLD)) == i_style_received )
1640 FT_Done_Face( p_face );
1648 static FT_Face LoadFace( filter_t *p_filter,
1649 const text_style_t *p_style )
1651 filter_sys_t *p_sys = p_filter->p_sys;
1653 /* Look for a match amongst our attachments first */
1654 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1656 /* Load system wide font otheriwse */
1661 #ifdef HAVE_FONTCONFIG
1662 psz_fontfile = FontConfig_Select( NULL,
1663 p_style->psz_fontname,
1664 (p_style->i_style_flags & STYLE_BOLD) != 0,
1665 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1668 #elif defined( WIN32 )
1669 psz_fontfile = Win32_Select( p_filter,
1670 p_style->psz_fontname,
1671 (p_style->i_style_flags & STYLE_BOLD) != 0,
1672 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1676 psz_fontfile = NULL;
1681 if( *psz_fontfile == '\0' )
1684 "We were not able to find a matching font: \"%s\" (%s %s),"
1685 " so using default font",
1686 p_style->psz_fontname,
1687 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1688 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1693 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1696 free( psz_fontfile );
1701 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1703 /* We've loaded a font face which is unhelpful for actually
1704 * rendering text - fallback to the default one.
1706 FT_Done_Face( p_face );
1712 static bool FaceStyleEquals( const text_style_t *p_style1,
1713 const text_style_t *p_style2 )
1715 if( !p_style1 || !p_style2 )
1717 if( p_style1 == p_style2 )
1720 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
1721 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
1722 !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
1725 static int GetGlyph( filter_t *p_filter,
1726 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1727 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1728 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1734 FT_Vector *p_pen_shadow )
1736 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1737 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1739 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1740 return VLC_EGENERIC;
1743 /* Do synthetic styling now that Freetype supports it;
1744 * ie. if the font we have loaded is NOT already in the
1745 * style that the tags want, then switch it on; if they
1746 * are then don't. */
1747 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1748 FT_GlyphSlot_Embolden( p_face->glyph );
1749 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1750 FT_GlyphSlot_Oblique( p_face->glyph );
1753 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1755 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1756 return VLC_EGENERIC;
1759 FT_Glyph outline = NULL;
1760 if( p_filter->p_sys->p_stroker )
1763 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1767 FT_Glyph shadow = NULL;
1768 if( p_filter->p_sys->i_shadow_opacity > 0 )
1770 shadow = outline ? outline : glyph;
1771 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1777 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1780 *pp_shadow = shadow;
1782 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1784 FT_Done_Glyph( glyph );
1786 FT_Done_Glyph( outline );
1788 FT_Done_Glyph( shadow );
1789 return VLC_EGENERIC;
1791 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1796 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1797 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1799 *pp_outline = outline;
1804 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1806 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1807 if( p_bbox->xMin >= p_bbox->xMax )
1809 p_bbox->xMin = FT_CEIL(p_pen->x);
1810 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1811 glyph_bmp->left = p_bbox->xMin;
1813 if( p_bbox->yMin >= p_bbox->yMax )
1815 p_bbox->yMax = FT_CEIL(p_pen->y);
1816 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1817 glyph_bmp->top = p_bbox->yMax;
1821 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1823 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1824 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1825 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1826 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1829 static int ProcessLines( filter_t *p_filter,
1830 line_desc_t **pp_lines,
1832 int *pi_max_face_height,
1835 text_style_t **pp_styles,
1836 uint32_t *pi_k_dates,
1839 filter_sys_t *p_sys = p_filter->p_sys;
1840 uint32_t *p_fribidi_string = NULL;
1841 text_style_t **pp_fribidi_styles = NULL;
1842 int *p_new_positions = NULL;
1844 #if defined(HAVE_FRIBIDI)
1846 int *p_old_positions;
1847 int start_pos, pos = 0;
1849 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1851 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1852 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1853 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1855 if( ! pp_fribidi_styles ||
1856 ! p_fribidi_string ||
1857 ! p_old_positions ||
1860 free( p_old_positions );
1861 free( p_new_positions );
1862 free( p_fribidi_string );
1863 free( pp_fribidi_styles );
1867 /* Do bidi conversion line-by-line */
1870 while(pos < i_len) {
1871 if (psz_text[pos] != '\n')
1873 p_fribidi_string[pos] = psz_text[pos];
1874 pp_fribidi_styles[pos] = pp_styles[pos];
1875 p_new_positions[pos] = pos;
1879 while(pos < i_len) {
1880 if (psz_text[pos] == '\n')
1884 if (pos > start_pos)
1886 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1887 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1889 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1891 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1892 pos - start_pos, &base_dir,
1893 (FriBidiChar*)p_fribidi_string + start_pos,
1894 p_new_positions + start_pos,
1897 for( int j = start_pos; j < pos; j++ )
1899 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1900 p_new_positions[ j ] += start_pos;
1904 p_fribidi_string[ i_len ] = 0;
1905 free( p_old_positions );
1907 pp_styles = pp_fribidi_styles;
1908 psz_text = p_fribidi_string;
1911 /* Work out the karaoke */
1912 uint8_t *pi_karaoke_bar = NULL;
1915 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1916 if( pi_karaoke_bar )
1918 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1919 for( int i = 0; i < i_len; i++ )
1921 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1922 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1926 free( p_new_positions );
1928 *pi_max_face_height = 0;
1930 line_desc_t **pp_line_next = pp_lines;
1938 int i_face_height_previous = 0;
1939 int i_base_line = 0;
1940 const text_style_t *p_previous_style = NULL;
1941 FT_Face p_face = NULL;
1942 for( int i_start = 0; i_start < i_len; )
1944 /* Compute the length of the current text line */
1946 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
1949 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1950 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
1951 int i_index = i_start;
1956 int i_face_height = 0;
1957 FT_BBox line_bbox = {
1963 int i_ul_offset = 0;
1964 int i_ul_thickness = 0;
1973 break_point_t break_point;
1974 break_point_t break_point_fallback;
1976 #define SAVE_BP(dst) do { \
1977 dst.i_index = i_index; \
1979 dst.line_bbox = line_bbox; \
1980 dst.i_face_height = i_face_height; \
1981 dst.i_ul_offset = i_ul_offset; \
1982 dst.i_ul_thickness = i_ul_thickness; \
1985 SAVE_BP( break_point );
1986 SAVE_BP( break_point_fallback );
1988 while( i_index < i_start + i_length )
1990 /* Split by common FT_Face + Size */
1991 const text_style_t *p_current_style = pp_styles[i_index];
1992 int i_part_length = 0;
1993 while( i_index + i_part_length < i_start + i_length )
1995 const text_style_t *p_style = pp_styles[i_index + i_part_length];
1996 if( !FaceStyleEquals( p_style, p_current_style ) ||
1997 p_style->i_font_size != p_current_style->i_font_size )
2002 /* (Re)load/reconfigure the face if needed */
2003 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
2006 FT_Done_Face( p_face );
2007 p_previous_style = NULL;
2009 p_face = LoadFace( p_filter, p_current_style );
2011 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
2012 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
2014 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
2015 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
2016 if( p_sys->p_stroker )
2018 int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
2019 FT_Stroker_Set( p_sys->p_stroker,
2021 FT_STROKER_LINECAP_ROUND,
2022 FT_STROKER_LINEJOIN_ROUND, 0 );
2025 p_previous_style = p_current_style;
2027 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
2028 p_current_face->size->metrics.y_scale)));
2030 /* Render the part */
2031 bool b_break_line = false;
2032 int i_glyph_last = 0;
2033 while( i_part_length > 0 )
2035 const text_style_t *p_glyph_style = pp_styles[i_index];
2036 uint32_t character = psz_text[i_index];
2037 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
2039 /* Get kerning vector */
2040 FT_Vector kerning = { .x = 0, .y = 0 };
2041 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
2042 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
2044 /* Get the glyph bitmap and its bounding box and all the associated properties */
2045 FT_Vector pen_new = {
2046 .x = pen.x + kerning.x,
2047 .y = pen.y + kerning.y,
2049 FT_Vector pen_shadow_new = {
2050 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
2051 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
2056 FT_BBox outline_bbox;
2058 FT_BBox shadow_bbox;
2060 if( GetGlyph( p_filter,
2061 &glyph, &glyph_bbox,
2062 &outline, &outline_bbox,
2063 &shadow, &shadow_bbox,
2064 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
2065 &pen_new, &pen_shadow_new ) )
2068 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
2070 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
2072 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
2074 /* FIXME and what about outline */
2076 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
2077 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
2078 (p_glyph_style->i_karaoke_background_alpha << 24))
2079 : (p_glyph_style->i_font_color |
2080 (p_glyph_style->i_font_alpha << 24));
2081 int i_line_offset = 0;
2082 int i_line_thickness = 0;
2083 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
2085 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
2086 p_current_face->size->metrics.y_scale)) );
2088 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
2089 p_current_face->size->metrics.y_scale)) );
2091 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
2093 /* Move the baseline to make it strikethrough instead of
2094 * underline. That means that strikethrough takes precedence
2096 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
2097 p_current_face->size->metrics.y_scale)) );
2099 else if( i_line_thickness > 0 )
2101 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
2103 /* The real underline thickness and position are
2104 * updated once the whole line has been parsed */
2105 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
2106 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
2107 i_line_thickness = -1;
2110 FT_BBox line_bbox_new = line_bbox;
2111 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
2113 BBoxEnlarge( &line_bbox_new, &outline_bbox );
2115 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
2117 b_break_line = i_index > i_start &&
2118 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
2121 FT_Done_Glyph( glyph );
2123 FT_Done_Glyph( outline );
2125 FT_Done_Glyph( shadow );
2127 break_point_t *p_bp = NULL;
2128 if( break_point.i_index > i_start )
2129 p_bp = &break_point;
2130 else if( break_point_fallback.i_index > i_start )
2131 p_bp = &break_point_fallback;
2135 msg_Dbg( p_filter, "Breaking line");
2136 for( int i = p_bp->i_index; i < i_index; i++ )
2138 line_character_t *ch = &p_line->p_character[i - i_start];
2139 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
2141 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
2143 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
2145 p_line->i_character_count = p_bp->i_index - i_start;
2147 i_index = p_bp->i_index;
2149 line_bbox = p_bp->line_bbox;
2150 i_face_height = p_bp->i_face_height;
2151 i_ul_offset = p_bp->i_ul_offset;
2152 i_ul_thickness = p_bp->i_ul_thickness;
2156 msg_Err( p_filter, "Breaking unbreakable line");
2161 assert( p_line->i_character_count == i_index - i_start);
2162 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2163 .p_glyph = (FT_BitmapGlyph)glyph,
2164 .p_outline = (FT_BitmapGlyph)outline,
2165 .p_shadow = (FT_BitmapGlyph)shadow,
2167 .i_line_offset = i_line_offset,
2168 .i_line_thickness = i_line_thickness,
2171 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2172 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2173 line_bbox = line_bbox_new;
2175 i_glyph_last = i_glyph_index;
2179 if( character == ' ' || character == '\t' )
2180 SAVE_BP( break_point );
2181 else if( character == 160 )
2182 SAVE_BP( break_point_fallback );
2188 /* Update our baseline */
2189 if( i_face_height_previous > 0 )
2190 i_base_line += __MAX(i_face_height, i_face_height_previous);
2191 if( i_face_height > 0 )
2192 i_face_height_previous = i_face_height;
2194 /* Update the line bbox with the actual base line */
2195 if (line_bbox.yMax > line_bbox.yMin) {
2196 line_bbox.yMin -= i_base_line;
2197 line_bbox.yMax -= i_base_line;
2199 BBoxEnlarge( &bbox, &line_bbox );
2201 /* Terminate and append the line */
2204 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2205 p_line->i_base_line = i_base_line;
2206 if( i_ul_thickness > 0 )
2208 for( int i = 0; i < p_line->i_character_count; i++ )
2210 line_character_t *ch = &p_line->p_character[i];
2211 if( ch->i_line_thickness < 0 )
2213 ch->i_line_offset = i_ul_offset;
2214 ch->i_line_thickness = i_ul_thickness;
2219 *pp_line_next = p_line;
2220 pp_line_next = &p_line->p_next;
2223 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2225 /* Skip what we have rendered and the line delimitor if present */
2227 if( i_start < i_len && psz_text[i_start] == '\n' )
2230 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
2232 msg_Err( p_filter, "Truncated too high subtitle" );
2237 FT_Done_Face( p_face );
2239 free( pp_fribidi_styles );
2240 free( p_fribidi_string );
2241 free( pi_karaoke_bar );
2248 * This function renders a text subpicture region into another one.
2249 * It also calculates the size needed for this string, and renders the
2250 * needed glyphs into memory. It is used as pf_add_string callback in
2251 * the vout method by this module
2253 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2254 subpicture_region_t *p_region_in, bool b_html,
2255 const vlc_fourcc_t *p_chroma_list )
2257 filter_sys_t *p_sys = p_filter->p_sys;
2260 return VLC_EGENERIC;
2261 if( b_html && !p_region_in->psz_html )
2262 return VLC_EGENERIC;
2263 if( !b_html && !p_region_in->psz_text )
2264 return VLC_EGENERIC;
2266 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2267 : p_region_in->psz_text );
2269 uint32_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2270 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2271 if( !psz_text || !pp_styles )
2275 return VLC_EGENERIC;
2278 /* Reset the default fontsize in case screen metrics have changed */
2279 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2282 int rv = VLC_SUCCESS;
2283 int i_text_length = 0;
2285 int i_max_face_height;
2286 line_desc_t *p_lines = NULL;
2288 uint32_t *pi_k_durations = NULL;
2293 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2294 (uint8_t *) p_region_in->psz_html,
2295 strlen( p_region_in->psz_html ),
2297 if( unlikely(p_sub == NULL) )
2300 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2302 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2304 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2305 p_filter->p_sys->p_xml = p_xml_reader;
2312 /* Look for Root Node */
2315 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2317 if( strcasecmp( "karaoke", node ) == 0 )
2319 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2321 else if( strcasecmp( "text", node ) != 0 )
2323 /* Only text and karaoke tags are supported */
2324 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2331 msg_Err( p_filter, "Malformed HTML subtitle" );
2337 rv = ProcessNodes( p_filter,
2338 psz_text, pp_styles, pi_k_durations, &i_text_length,
2339 p_xml_reader, p_region_in->p_style );
2343 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2345 stream_Delete( p_sub );
2350 text_style_t *p_style;
2351 if( p_region_in->p_style )
2352 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2353 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2354 : p_sys->i_font_size,
2355 (p_region_in->p_style->i_font_color & 0xffffff) |
2356 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2358 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2363 p_style = CreateStyle( p_sys->psz_fontfamily,
2365 (p_sys->i_font_color & 0xffffff) |
2366 ((p_sys->i_font_opacity & 0xff) << 24),
2368 if( p_sys->b_font_bold )
2369 p_style->i_style_flags |= STYLE_BOLD;
2371 i_text_length = SetupText( p_filter,
2375 p_region_in->psz_text, p_style, 0 );
2378 if( !rv && i_text_length > 0 )
2380 rv = ProcessLines( p_filter,
2381 &p_lines, &bbox, &i_max_face_height,
2382 psz_text, pp_styles, pi_k_durations, i_text_length );
2385 p_region_out->i_x = p_region_in->i_x;
2386 p_region_out->i_y = p_region_in->i_y;
2388 /* Don't attempt to render text that couldn't be layed out
2390 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2392 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2393 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2395 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2396 p_chroma_list = p_chroma_list_yuvp;
2397 else if( !p_chroma_list || *p_chroma_list == 0 )
2398 p_chroma_list = p_chroma_list_rgba;
2400 const int i_margin = p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2401 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2404 if( *p_chroma == VLC_CODEC_YUVP )
2405 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2406 else if( *p_chroma == VLC_CODEC_YUVA )
2407 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2412 else if( *p_chroma == VLC_CODEC_RGBA )
2413 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2422 /* With karaoke, we're going to have to render the text a number
2423 * of times to show the progress marker on the text.
2425 if( pi_k_durations )
2426 var_SetBool( p_filter, "text-rerender", true );
2429 FreeLines( p_lines );
2432 for( int i = 0; i < i_text_length; i++ )
2434 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2435 text_style_Delete( pp_styles[i] );
2438 free( pi_k_durations );
2443 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2444 subpicture_region_t *p_region_in,
2445 const vlc_fourcc_t *p_chroma_list )
2447 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2452 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2453 subpicture_region_t *p_region_in,
2454 const vlc_fourcc_t *p_chroma_list )
2456 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2461 /*****************************************************************************
2462 * Create: allocates osd-text video thread output method
2463 *****************************************************************************
2464 * This function allocates and initializes a Clone vout method.
2465 *****************************************************************************/
2466 static int Create( vlc_object_t *p_this )
2468 filter_t *p_filter = (filter_t *)p_this;
2469 filter_sys_t *p_sys;
2470 char *psz_fontfile = NULL;
2471 char *psz_fontfamily = NULL;
2472 int i_error = 0, fontindex = 0;
2474 /* Allocate structure */
2475 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2479 p_sys->psz_fontfamily = NULL;
2481 p_sys->p_xml = NULL;
2484 p_sys->p_library = 0;
2485 p_sys->i_font_size = 0;
2486 p_sys->i_display_height = 0;
2488 var_Create( p_filter, "freetype-rel-fontsize",
2489 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2491 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2492 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2493 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2494 p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
2495 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2496 p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
2497 p_sys->b_font_bold = var_InheritBool( p_filter, "freetype-bold" );
2499 p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
2500 p_sys->i_background_opacity = __MAX( __MIN( p_sys->i_background_opacity, 255 ), 0 );
2501 p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
2502 p_sys->i_background_color = __MAX( __MIN( p_sys->i_background_color, 0xFFFFFF ), 0 );
2504 p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2505 p_sys->f_outline_thickness = __MAX( __MIN( p_sys->f_outline_thickness, 0.5 ), 0.0 );
2506 p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2507 p_sys->i_outline_opacity = __MAX( __MIN( p_sys->i_outline_opacity, 255 ), 0 );
2508 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2509 p_sys->i_outline_color = __MAX( __MIN( p_sys->i_outline_color, 0xFFFFFF ), 0 );
2511 p_sys->i_shadow_opacity = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2512 p_sys->i_shadow_opacity = __MAX( __MIN( p_sys->i_shadow_opacity, 255 ), 0 );
2513 p_sys->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2514 p_sys->i_shadow_color = __MAX( __MIN( p_sys->i_shadow_color, 0xFFFFFF ), 0 );
2515 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2516 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2517 f_shadow_distance = __MAX( __MIN( f_shadow_distance, 1 ), 0 );
2518 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2519 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2522 /* Get Windows Font folder */
2523 wchar_t wdir[MAX_PATH];
2524 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2526 GetWindowsDirectoryW( wdir, MAX_PATH );
2527 wcscat( wdir, L"\\fonts" );
2529 p_sys->psz_win_fonts_path = FromWide( wdir );
2532 /* Set default psz_fontfamily */
2533 if( !psz_fontfamily || !*psz_fontfamily )
2535 free( psz_fontfamily );
2537 psz_fontfamily = strdup( DEFAULT_FAMILY );
2540 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2543 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2545 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2549 /* Set the current font file */
2550 p_sys->psz_fontfamily = psz_fontfamily;
2552 #ifdef HAVE_FONTCONFIG
2553 FontConfig_BuildCache( p_filter );
2556 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2557 p_sys->i_default_font_size, &fontindex );
2558 #elif defined(WIN32)
2559 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2560 p_sys->i_default_font_size, &fontindex );
2563 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2565 /* If nothing is found, use the default family */
2567 psz_fontfile = strdup( psz_fontfamily );
2569 #else /* !HAVE_STYLES */
2570 /* Use the default file */
2571 psz_fontfile = psz_fontfamily;
2575 i_error = FT_Init_FreeType( &p_sys->p_library );
2578 msg_Err( p_filter, "couldn't initialize freetype" );
2582 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2583 fontindex, &p_sys->p_face );
2585 if( i_error == FT_Err_Unknown_File_Format )
2587 msg_Err( p_filter, "file %s have unknown format",
2588 psz_fontfile ? psz_fontfile : "(null)" );
2593 msg_Err( p_filter, "failed to load font file %s",
2594 psz_fontfile ? psz_fontfile : "(null)" );
2598 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2601 msg_Err( p_filter, "font has no unicode translation table" );
2605 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2607 p_sys->p_stroker = NULL;
2608 if( p_sys->f_outline_thickness > 0.001 )
2610 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2612 msg_Err( p_filter, "Failed to create stroker for outlining" );
2615 p_sys->pp_font_attachments = NULL;
2616 p_sys->i_font_attachments = 0;
2618 p_filter->pf_render_text = RenderText;
2620 p_filter->pf_render_html = RenderHtml;
2622 p_filter->pf_render_html = NULL;
2625 LoadFontsFromAttachments( p_filter );
2628 free( psz_fontfile );
2634 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2635 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2637 free( psz_fontfile );
2639 free( psz_fontfamily );
2641 return VLC_EGENERIC;
2644 /*****************************************************************************
2645 * Destroy: destroy Clone video thread output method
2646 *****************************************************************************
2647 * Clean up all data and library connections
2648 *****************************************************************************/
2649 static void Destroy( vlc_object_t *p_this )
2651 filter_t *p_filter = (filter_t *)p_this;
2652 filter_sys_t *p_sys = p_filter->p_sys;
2654 if( p_sys->pp_font_attachments )
2656 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2657 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2659 free( p_sys->pp_font_attachments );
2663 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2665 free( p_sys->psz_fontfamily );
2667 /* FcFini asserts calling the subfunction FcCacheFini()
2668 * even if no other library functions have been made since FcInit(),
2669 * so don't call it. */
2671 if( p_sys->p_stroker )
2672 FT_Stroker_Done( p_sys->p_stroker );
2673 FT_Done_Face( p_sys->p_face );
2674 FT_Done_FreeType( p_sys->p_library );