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_("Text 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_rgb( "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, NULL, false )
198 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
199 BG_OPACITY_TEXT, NULL, false )
201 add_rgb( "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, NULL, false )
209 add_rgb( "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, NULL, false )
221 add_rgb( "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, NULL, false )
228 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
229 SHADOW_DISTANCE_TEXT, NULL, 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 */
619 if( strchr( psz_filename, DIR_SEP_CHAR ) )
624 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
626 free( psz_filename );
629 free( psz_filename );
638 /*****************************************************************************
639 * RenderYUVP: place string in picture
640 *****************************************************************************
641 * This function merges the previously rendered freetype glyphs into a picture
642 *****************************************************************************/
643 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
647 VLC_UNUSED(p_filter);
648 static const uint8_t pi_gamma[16] =
649 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
650 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
654 int i, x, y, i_pitch;
655 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
657 /* Create a new subpicture region */
658 video_format_Init( &fmt, VLC_CODEC_YUVP );
660 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
662 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
664 assert( !p_region->p_picture );
665 p_region->p_picture = picture_NewFromFormat( &fmt );
666 if( !p_region->p_picture )
668 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
671 /* Calculate text color components
672 * Only use the first color */
673 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
674 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
677 fmt.p_palette->i_entries = 16;
678 for( i = 0; i < 8; i++ )
680 fmt.p_palette->palette[i][0] = 0;
681 fmt.p_palette->palette[i][1] = 0x80;
682 fmt.p_palette->palette[i][2] = 0x80;
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;
687 for( i = 8; i < fmt.p_palette->i_entries; i++ )
689 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
690 fmt.p_palette->palette[i][1] = i_u;
691 fmt.p_palette->palette[i][2] = i_v;
692 fmt.p_palette->palette[i][3] = pi_gamma[i];
693 fmt.p_palette->palette[i][3] =
694 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
697 p_dst = p_region->p_picture->Y_PIXELS;
698 i_pitch = p_region->p_picture->Y_PITCH;
700 /* Initialize the region pixels */
701 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
703 for( ; p_line != NULL; p_line = p_line->p_next )
705 int i_align_left = 0;
706 if( p_line->i_width < (int)fmt.i_visible_width )
708 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
709 i_align_left = ( fmt.i_visible_width - p_line->i_width );
710 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
711 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
715 for( i = 0; i < p_line->i_character_count; i++ )
717 const line_character_t *ch = &p_line->p_character[i];
718 FT_BitmapGlyph p_glyph = ch->p_glyph;
720 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
721 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
723 for( y = 0; y < p_glyph->bitmap.rows; y++ )
725 for( x = 0; x < p_glyph->bitmap.width; x++ )
727 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
728 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
729 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
735 /* Outlining (find something better than nearest neighbour filtering ?) */
738 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
739 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
740 uint8_t left, current;
742 for( y = 1; y < (int)fmt.i_height - 1; y++ )
744 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
745 p_dst += p_region->p_picture->Y_PITCH;
748 for( x = 1; x < (int)fmt.i_width - 1; x++ )
751 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
752 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;
756 memset( p_top, 0, fmt.i_width );
762 /*****************************************************************************
763 * RenderYUVA: place string in picture
764 *****************************************************************************
765 * This function merges the previously rendered freetype glyphs into a picture
766 *****************************************************************************/
767 static void FillYUVAPicture( picture_t *p_picture,
768 int i_a, int i_y, int i_u, int i_v )
770 memset( p_picture->p[0].p_pixels, i_y,
771 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
772 memset( p_picture->p[1].p_pixels, i_u,
773 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
774 memset( p_picture->p[2].p_pixels, i_v,
775 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
776 memset( p_picture->p[3].p_pixels, i_a,
777 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
780 static inline void BlendYUVAPixel( picture_t *p_picture,
781 int i_picture_x, int i_picture_y,
782 int i_a, int i_y, int i_u, int i_v,
785 int i_an = i_a * i_alpha / 255;
787 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
788 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
789 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
790 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
802 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
805 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
806 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
807 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
812 static void FillRGBAPicture( picture_t *p_picture,
813 int i_a, int i_r, int i_g, int i_b )
815 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
817 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
819 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
828 static inline void BlendRGBAPixel( picture_t *p_picture,
829 int i_picture_x, int i_picture_y,
830 int i_a, int i_r, int i_g, int i_b,
833 int i_an = i_a * i_alpha / 255;
835 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
837 int i_ao = p_rgba[3];
847 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
850 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
851 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
852 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
857 static inline void BlendAXYZGlyph( picture_t *p_picture,
858 int i_picture_x, int i_picture_y,
859 int i_a, int i_x, int i_y, int i_z,
860 FT_BitmapGlyph p_glyph,
861 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
864 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
866 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
867 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
869 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
873 static inline void BlendAXYZLine( picture_t *p_picture,
874 int i_picture_x, int i_picture_y,
875 int i_a, int i_x, int i_y, int i_z,
876 const line_character_t *p_current,
877 const line_character_t *p_next,
878 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
880 int i_line_width = p_current->p_glyph->bitmap.width;
882 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
884 for( int dx = 0; dx < i_line_width; dx++ )
886 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
887 BlendPixel( p_picture,
889 i_picture_y + p_current->i_line_offset + dy,
890 i_a, i_x, i_y, i_z, 0xff );
894 static inline int RenderAXYZ( filter_t *p_filter,
895 subpicture_region_t *p_region,
896 line_desc_t *p_line_head,
899 vlc_fourcc_t i_chroma,
900 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
901 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
902 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
904 filter_sys_t *p_sys = p_filter->p_sys;
906 /* Create a new subpicture region */
907 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
908 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
910 video_format_Init( &fmt, i_chroma );
912 fmt.i_visible_width = i_text_width + 2 * i_margin;
914 fmt.i_visible_height = i_text_height + 2 * i_margin;
916 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
917 if( !p_region->p_picture )
921 /* Initialize the picture background */
922 uint8_t i_a = p_sys->i_background_opacity;
923 uint8_t i_x, i_y, i_z;
924 ExtractComponents( p_sys->i_background_color, &i_x, &i_y, &i_z );
926 FillPicture( p_picture, i_a, i_x, i_y, i_z );
928 /* Render shadow then outline and then normal glyphs */
929 for( int g = 0; g < 3; g++ )
931 /* Render all lines */
932 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
934 int i_align_left = i_margin;
935 if( p_line->i_width < i_text_width )
937 /* Left offset to take into account alignment */
938 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
939 i_align_left += ( i_text_width - p_line->i_width );
940 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
941 i_align_left += ( i_text_width - p_line->i_width ) / 2;
943 int i_align_top = i_margin;
945 /* Render all glyphs and underline/strikethrough */
946 for( int i = 0; i < p_line->i_character_count; i++ )
948 const line_character_t *ch = &p_line->p_character[i];
949 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
953 i_a = (ch->i_color >> 24) & 0xff;
957 i_a = i_a * p_sys->i_shadow_opacity / 255;
958 i_color = p_sys->i_shadow_color;
961 i_a = i_a * p_sys->i_outline_opacity / 255;
962 i_color = p_sys->i_outline_color;
965 i_color = ch->i_color;
968 ExtractComponents( i_color, &i_x, &i_y, &i_z );
970 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
971 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
973 BlendAXYZGlyph( p_picture,
974 i_glyph_x, i_glyph_y,
979 /* underline/strikethrough are only rendered for the normal glyph */
980 if( g == 2 && ch->i_line_thickness > 0 )
981 BlendAXYZLine( p_picture,
982 i_glyph_x, i_glyph_y + p_glyph->top,
985 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
994 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
995 uint32_t i_font_color, uint32_t i_karaoke_bg_color,
998 text_style_t *p_style = text_style_New();
1002 p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
1003 p_style->i_font_size = i_font_size;
1004 p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0;
1005 p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
1006 p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0;
1007 p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
1008 p_style->i_style_flags |= i_style_flags;
1012 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1013 uint32_t i_color, uint32_t i_karaoke_bg_color )
1016 return VLC_EGENERIC;
1018 font_stack_t *p_new = malloc( sizeof(*p_new) );
1022 p_new->p_next = NULL;
1025 p_new->psz_name = strdup( psz_name );
1027 p_new->psz_name = NULL;
1029 p_new->i_size = i_size;
1030 p_new->i_color = i_color;
1031 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1039 font_stack_t *p_last;
1041 for( p_last = *p_font;
1043 p_last = p_last->p_next )
1046 p_last->p_next = p_new;
1051 static int PopFont( font_stack_t **p_font )
1053 font_stack_t *p_last, *p_next_to_last;
1055 if( !p_font || !*p_font )
1056 return VLC_EGENERIC;
1058 p_next_to_last = NULL;
1059 for( p_last = *p_font;
1061 p_last = p_last->p_next )
1063 p_next_to_last = p_last;
1066 if( p_next_to_last )
1067 p_next_to_last->p_next = NULL;
1071 free( p_last->psz_name );
1077 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1078 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1080 font_stack_t *p_last;
1082 if( !p_font || !*p_font )
1083 return VLC_EGENERIC;
1085 for( p_last=*p_font;
1087 p_last=p_last->p_next )
1090 *psz_name = p_last->psz_name;
1091 *i_size = p_last->i_size;
1092 *i_color = p_last->i_color;
1093 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1098 static const struct {
1099 const char *psz_name;
1101 } p_html_colors[] = {
1102 /* Official html colors */
1103 { "Aqua", 0x00FFFF },
1104 { "Black", 0x000000 },
1105 { "Blue", 0x0000FF },
1106 { "Fuchsia", 0xFF00FF },
1107 { "Gray", 0x808080 },
1108 { "Green", 0x008000 },
1109 { "Lime", 0x00FF00 },
1110 { "Maroon", 0x800000 },
1111 { "Navy", 0x000080 },
1112 { "Olive", 0x808000 },
1113 { "Purple", 0x800080 },
1114 { "Red", 0xFF0000 },
1115 { "Silver", 0xC0C0C0 },
1116 { "Teal", 0x008080 },
1117 { "White", 0xFFFFFF },
1118 { "Yellow", 0xFFFF00 },
1121 { "AliceBlue", 0xF0F8FF },
1122 { "AntiqueWhite", 0xFAEBD7 },
1123 { "Aqua", 0x00FFFF },
1124 { "Aquamarine", 0x7FFFD4 },
1125 { "Azure", 0xF0FFFF },
1126 { "Beige", 0xF5F5DC },
1127 { "Bisque", 0xFFE4C4 },
1128 { "Black", 0x000000 },
1129 { "BlanchedAlmond", 0xFFEBCD },
1130 { "Blue", 0x0000FF },
1131 { "BlueViolet", 0x8A2BE2 },
1132 { "Brown", 0xA52A2A },
1133 { "BurlyWood", 0xDEB887 },
1134 { "CadetBlue", 0x5F9EA0 },
1135 { "Chartreuse", 0x7FFF00 },
1136 { "Chocolate", 0xD2691E },
1137 { "Coral", 0xFF7F50 },
1138 { "CornflowerBlue", 0x6495ED },
1139 { "Cornsilk", 0xFFF8DC },
1140 { "Crimson", 0xDC143C },
1141 { "Cyan", 0x00FFFF },
1142 { "DarkBlue", 0x00008B },
1143 { "DarkCyan", 0x008B8B },
1144 { "DarkGoldenRod", 0xB8860B },
1145 { "DarkGray", 0xA9A9A9 },
1146 { "DarkGrey", 0xA9A9A9 },
1147 { "DarkGreen", 0x006400 },
1148 { "DarkKhaki", 0xBDB76B },
1149 { "DarkMagenta", 0x8B008B },
1150 { "DarkOliveGreen", 0x556B2F },
1151 { "Darkorange", 0xFF8C00 },
1152 { "DarkOrchid", 0x9932CC },
1153 { "DarkRed", 0x8B0000 },
1154 { "DarkSalmon", 0xE9967A },
1155 { "DarkSeaGreen", 0x8FBC8F },
1156 { "DarkSlateBlue", 0x483D8B },
1157 { "DarkSlateGray", 0x2F4F4F },
1158 { "DarkSlateGrey", 0x2F4F4F },
1159 { "DarkTurquoise", 0x00CED1 },
1160 { "DarkViolet", 0x9400D3 },
1161 { "DeepPink", 0xFF1493 },
1162 { "DeepSkyBlue", 0x00BFFF },
1163 { "DimGray", 0x696969 },
1164 { "DimGrey", 0x696969 },
1165 { "DodgerBlue", 0x1E90FF },
1166 { "FireBrick", 0xB22222 },
1167 { "FloralWhite", 0xFFFAF0 },
1168 { "ForestGreen", 0x228B22 },
1169 { "Fuchsia", 0xFF00FF },
1170 { "Gainsboro", 0xDCDCDC },
1171 { "GhostWhite", 0xF8F8FF },
1172 { "Gold", 0xFFD700 },
1173 { "GoldenRod", 0xDAA520 },
1174 { "Gray", 0x808080 },
1175 { "Grey", 0x808080 },
1176 { "Green", 0x008000 },
1177 { "GreenYellow", 0xADFF2F },
1178 { "HoneyDew", 0xF0FFF0 },
1179 { "HotPink", 0xFF69B4 },
1180 { "IndianRed", 0xCD5C5C },
1181 { "Indigo", 0x4B0082 },
1182 { "Ivory", 0xFFFFF0 },
1183 { "Khaki", 0xF0E68C },
1184 { "Lavender", 0xE6E6FA },
1185 { "LavenderBlush", 0xFFF0F5 },
1186 { "LawnGreen", 0x7CFC00 },
1187 { "LemonChiffon", 0xFFFACD },
1188 { "LightBlue", 0xADD8E6 },
1189 { "LightCoral", 0xF08080 },
1190 { "LightCyan", 0xE0FFFF },
1191 { "LightGoldenRodYellow", 0xFAFAD2 },
1192 { "LightGray", 0xD3D3D3 },
1193 { "LightGrey", 0xD3D3D3 },
1194 { "LightGreen", 0x90EE90 },
1195 { "LightPink", 0xFFB6C1 },
1196 { "LightSalmon", 0xFFA07A },
1197 { "LightSeaGreen", 0x20B2AA },
1198 { "LightSkyBlue", 0x87CEFA },
1199 { "LightSlateGray", 0x778899 },
1200 { "LightSlateGrey", 0x778899 },
1201 { "LightSteelBlue", 0xB0C4DE },
1202 { "LightYellow", 0xFFFFE0 },
1203 { "Lime", 0x00FF00 },
1204 { "LimeGreen", 0x32CD32 },
1205 { "Linen", 0xFAF0E6 },
1206 { "Magenta", 0xFF00FF },
1207 { "Maroon", 0x800000 },
1208 { "MediumAquaMarine", 0x66CDAA },
1209 { "MediumBlue", 0x0000CD },
1210 { "MediumOrchid", 0xBA55D3 },
1211 { "MediumPurple", 0x9370D8 },
1212 { "MediumSeaGreen", 0x3CB371 },
1213 { "MediumSlateBlue", 0x7B68EE },
1214 { "MediumSpringGreen", 0x00FA9A },
1215 { "MediumTurquoise", 0x48D1CC },
1216 { "MediumVioletRed", 0xC71585 },
1217 { "MidnightBlue", 0x191970 },
1218 { "MintCream", 0xF5FFFA },
1219 { "MistyRose", 0xFFE4E1 },
1220 { "Moccasin", 0xFFE4B5 },
1221 { "NavajoWhite", 0xFFDEAD },
1222 { "Navy", 0x000080 },
1223 { "OldLace", 0xFDF5E6 },
1224 { "Olive", 0x808000 },
1225 { "OliveDrab", 0x6B8E23 },
1226 { "Orange", 0xFFA500 },
1227 { "OrangeRed", 0xFF4500 },
1228 { "Orchid", 0xDA70D6 },
1229 { "PaleGoldenRod", 0xEEE8AA },
1230 { "PaleGreen", 0x98FB98 },
1231 { "PaleTurquoise", 0xAFEEEE },
1232 { "PaleVioletRed", 0xD87093 },
1233 { "PapayaWhip", 0xFFEFD5 },
1234 { "PeachPuff", 0xFFDAB9 },
1235 { "Peru", 0xCD853F },
1236 { "Pink", 0xFFC0CB },
1237 { "Plum", 0xDDA0DD },
1238 { "PowderBlue", 0xB0E0E6 },
1239 { "Purple", 0x800080 },
1240 { "Red", 0xFF0000 },
1241 { "RosyBrown", 0xBC8F8F },
1242 { "RoyalBlue", 0x4169E1 },
1243 { "SaddleBrown", 0x8B4513 },
1244 { "Salmon", 0xFA8072 },
1245 { "SandyBrown", 0xF4A460 },
1246 { "SeaGreen", 0x2E8B57 },
1247 { "SeaShell", 0xFFF5EE },
1248 { "Sienna", 0xA0522D },
1249 { "Silver", 0xC0C0C0 },
1250 { "SkyBlue", 0x87CEEB },
1251 { "SlateBlue", 0x6A5ACD },
1252 { "SlateGray", 0x708090 },
1253 { "SlateGrey", 0x708090 },
1254 { "Snow", 0xFFFAFA },
1255 { "SpringGreen", 0x00FF7F },
1256 { "SteelBlue", 0x4682B4 },
1257 { "Tan", 0xD2B48C },
1258 { "Teal", 0x008080 },
1259 { "Thistle", 0xD8BFD8 },
1260 { "Tomato", 0xFF6347 },
1261 { "Turquoise", 0x40E0D0 },
1262 { "Violet", 0xEE82EE },
1263 { "Wheat", 0xF5DEB3 },
1264 { "White", 0xFFFFFF },
1265 { "WhiteSmoke", 0xF5F5F5 },
1266 { "Yellow", 0xFFFF00 },
1267 { "YellowGreen", 0x9ACD32 },
1272 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1273 font_stack_t **p_fonts )
1276 char *psz_fontname = NULL;
1277 uint32_t i_font_color = 0xffffff;
1278 int i_font_alpha = 255;
1279 uint32_t i_karaoke_bg_color = 0x00ffffff;
1280 int i_font_size = 24;
1282 /* Default all attributes to the top font in the stack -- in case not
1283 * all attributes are specified in the sub-font
1285 if( VLC_SUCCESS == PeekFont( p_fonts,
1289 &i_karaoke_bg_color ))
1291 psz_fontname = strdup( psz_fontname );
1292 i_font_size = i_font_size;
1294 i_font_alpha = (i_font_color >> 24) & 0xff;
1295 i_font_color &= 0x00ffffff;
1297 const char *name, *value;
1298 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1300 if( !strcasecmp( "face", name ) )
1302 free( psz_fontname );
1303 psz_fontname = strdup( value );
1305 else if( !strcasecmp( "size", name ) )
1307 if( ( *value == '+' ) || ( *value == '-' ) )
1309 int i_value = atoi( value );
1311 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1312 i_font_size += ( i_value * i_font_size ) / 10;
1313 else if( i_value < -5 )
1314 i_font_size = - i_value;
1315 else if( i_value > 5 )
1316 i_font_size = i_value;
1319 i_font_size = atoi( value );
1321 else if( !strcasecmp( "color", name ) )
1323 if( value[0] == '#' )
1325 i_font_color = strtol( value + 1, NULL, 16 );
1326 i_font_color &= 0x00ffffff;
1331 uint32_t i_value = strtol( value, &end, 16 );
1332 if( *end == '\0' || *end == ' ' )
1333 i_font_color = i_value & 0x00ffffff;
1335 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1337 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1339 i_font_color = p_html_colors[i].i_value;
1345 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1347 i_font_alpha = strtol( value + 1, NULL, 16 );
1348 i_font_alpha &= 0xff;
1351 rv = PushFont( p_fonts,
1354 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1355 i_karaoke_bg_color );
1357 free( psz_fontname );
1362 /* Turn any multiple-whitespaces into single spaces */
1363 static void HandleWhiteSpace( char *psz_node )
1365 char *s = strpbrk( psz_node, "\t\r\n " );
1368 int i_whitespace = strspn( s, "\t\r\n " );
1370 if( i_whitespace > 1 )
1373 strlen( s ) - i_whitespace + 1 );
1376 s = strpbrk( s, "\t\r\n " );
1381 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1382 font_stack_t **p_fonts,
1385 char *psz_fontname = NULL;
1386 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1387 uint32_t i_karaoke_bg_color = i_font_color;
1388 int i_font_size = p_sys->i_font_size;
1390 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1391 &i_font_color, &i_karaoke_bg_color ) )
1394 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1399 static unsigned SetupText( filter_t *p_filter,
1400 uint32_t *psz_text_out,
1401 text_style_t **pp_styles,
1402 uint32_t *pi_k_dates,
1404 const char *psz_text_in,
1405 text_style_t *p_style,
1408 size_t i_string_length;
1410 size_t i_string_bytes;
1411 #if defined(WORDS_BIGENDIAN)
1412 uint32_t *psz_tmp = ToCharset( "UCS-4BE", psz_text_in, &i_string_bytes );
1414 uint32_t *psz_tmp = ToCharset( "UCS-4LE", psz_text_in, &i_string_bytes );
1418 memcpy( psz_text_out, psz_tmp, i_string_bytes );
1419 i_string_length = i_string_bytes / 4;
1424 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1425 i_string_length = 0;
1428 if( i_string_length > 0 )
1430 for( unsigned i = 0; i < i_string_length; i++ )
1431 pp_styles[i] = p_style;
1435 text_style_Delete( p_style );
1437 if( i_string_length > 0 && pi_k_dates )
1439 for( unsigned i = 0; i < i_string_length; i++ )
1440 pi_k_dates[i] = i_k_date;
1442 return i_string_length;
1445 static int ProcessNodes( filter_t *p_filter,
1447 text_style_t **pp_styles,
1448 uint32_t *pi_k_dates,
1450 xml_reader_t *p_xml_reader,
1451 text_style_t *p_font_style )
1453 int rv = VLC_SUCCESS;
1454 filter_sys_t *p_sys = p_filter->p_sys;
1455 int i_text_length = 0;
1456 font_stack_t *p_fonts = NULL;
1457 uint32_t i_k_date = 0;
1459 int i_style_flags = 0;
1463 rv = PushFont( &p_fonts,
1464 p_font_style->psz_fontname,
1465 p_font_style->i_font_size > 0 ? p_font_style->i_font_size
1466 : p_sys->i_font_size,
1467 (p_font_style->i_font_color & 0xffffff) |
1468 ((p_font_style->i_font_alpha & 0xff) << 24),
1469 (p_font_style->i_karaoke_background_color & 0xffffff) |
1470 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1472 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1480 rv = PushFont( &p_fonts,
1481 p_sys->psz_fontfamily,
1483 (p_sys->i_font_color & 0xffffff) |
1484 ((p_sys->i_font_opacity & 0xff) << 24),
1488 if( p_sys->b_font_bold )
1489 i_style_flags |= STYLE_BOLD;
1491 if( rv != VLC_SUCCESS )
1497 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1501 case XML_READER_ENDELEM:
1502 if( !strcasecmp( "font", node ) )
1503 PopFont( &p_fonts );
1504 else if( !strcasecmp( "b", node ) )
1505 i_style_flags &= ~STYLE_BOLD;
1506 else if( !strcasecmp( "i", node ) )
1507 i_style_flags &= ~STYLE_ITALIC;
1508 else if( !strcasecmp( "u", node ) )
1509 i_style_flags &= ~STYLE_UNDERLINE;
1510 else if( !strcasecmp( "s", node ) )
1511 i_style_flags &= ~STYLE_STRIKEOUT;
1514 case XML_READER_STARTELEM:
1515 if( !strcasecmp( "font", node ) )
1516 HandleFontAttributes( p_xml_reader, &p_fonts );
1517 else if( !strcasecmp( "b", node ) )
1518 i_style_flags |= STYLE_BOLD;
1519 else if( !strcasecmp( "i", node ) )
1520 i_style_flags |= STYLE_ITALIC;
1521 else if( !strcasecmp( "u", node ) )
1522 i_style_flags |= STYLE_UNDERLINE;
1523 else if( !strcasecmp( "s", node ) )
1524 i_style_flags |= STYLE_STRIKEOUT;
1525 else if( !strcasecmp( "br", node ) )
1527 i_text_length += SetupText( p_filter,
1528 &psz_text[i_text_length],
1529 &pp_styles[i_text_length],
1530 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1532 GetStyleFromFontStack( p_sys,
1537 else if( !strcasecmp( "k", node ) )
1540 const char *name, *value;
1541 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1543 if( !strcasecmp( "t", name ) && value )
1544 i_k_date += atoi( value );
1549 case XML_READER_TEXT:
1551 char *psz_node = strdup( node );
1552 if( unlikely(!psz_node) )
1555 HandleWhiteSpace( psz_node );
1556 resolve_xml_special_chars( psz_node );
1558 i_text_length += SetupText( p_filter,
1559 &psz_text[i_text_length],
1560 &pp_styles[i_text_length],
1561 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1563 GetStyleFromFontStack( p_sys,
1573 *pi_len = i_text_length;
1575 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1580 static void FreeLine( line_desc_t *p_line )
1582 for( int i = 0; i < p_line->i_character_count; i++ )
1584 line_character_t *ch = &p_line->p_character[i];
1585 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1587 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1589 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1592 free( p_line->p_character );
1596 static void FreeLines( line_desc_t *p_lines )
1598 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1600 line_desc_t *p_next = p_line->p_next;
1606 static line_desc_t *NewLine( int i_count )
1608 line_desc_t *p_line = malloc( sizeof(*p_line) );
1613 p_line->p_next = NULL;
1614 p_line->i_width = 0;
1615 p_line->i_base_line = 0;
1616 p_line->i_character_count = 0;
1618 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1619 if( !p_line->p_character )
1627 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1629 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1631 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1633 FT_Face p_face = NULL;
1635 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1643 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1644 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1645 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1646 (p_style->i_style_flags & (STYLE_BOLD | STYLE_BOLD)) == i_style_received )
1649 FT_Done_Face( p_face );
1657 static FT_Face LoadFace( filter_t *p_filter,
1658 const text_style_t *p_style )
1660 filter_sys_t *p_sys = p_filter->p_sys;
1662 /* Look for a match amongst our attachments first */
1663 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1665 /* Load system wide font otheriwse */
1670 #ifdef HAVE_FONTCONFIG
1671 psz_fontfile = FontConfig_Select( NULL,
1672 p_style->psz_fontname,
1673 (p_style->i_style_flags & STYLE_BOLD) != 0,
1674 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1677 #elif defined( WIN32 )
1678 psz_fontfile = Win32_Select( p_filter,
1679 p_style->psz_fontname,
1680 (p_style->i_style_flags & STYLE_BOLD) != 0,
1681 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1685 psz_fontfile = NULL;
1690 if( *psz_fontfile == '\0' )
1693 "We were not able to find a matching font: \"%s\" (%s %s),"
1694 " so using default font",
1695 p_style->psz_fontname,
1696 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1697 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1702 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1705 free( psz_fontfile );
1710 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1712 /* We've loaded a font face which is unhelpful for actually
1713 * rendering text - fallback to the default one.
1715 FT_Done_Face( p_face );
1721 static bool FaceStyleEquals( const text_style_t *p_style1,
1722 const text_style_t *p_style2 )
1724 if( !p_style1 || !p_style2 )
1726 if( p_style1 == p_style2 )
1729 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
1730 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
1731 !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
1734 static int GetGlyph( filter_t *p_filter,
1735 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1736 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1737 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1743 FT_Vector *p_pen_shadow )
1745 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1746 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1748 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1749 return VLC_EGENERIC;
1752 /* Do synthetic styling now that Freetype supports it;
1753 * ie. if the font we have loaded is NOT already in the
1754 * style that the tags want, then switch it on; if they
1755 * are then don't. */
1756 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1757 FT_GlyphSlot_Embolden( p_face->glyph );
1758 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1759 FT_GlyphSlot_Oblique( p_face->glyph );
1762 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1764 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1765 return VLC_EGENERIC;
1768 FT_Glyph outline = NULL;
1769 if( p_filter->p_sys->p_stroker )
1772 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1776 FT_Glyph shadow = NULL;
1777 if( p_filter->p_sys->i_shadow_opacity > 0 )
1779 shadow = outline ? outline : glyph;
1780 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1786 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1789 *pp_shadow = shadow;
1791 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1793 FT_Done_Glyph( glyph );
1795 FT_Done_Glyph( outline );
1797 FT_Done_Glyph( shadow );
1798 return VLC_EGENERIC;
1800 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1805 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1806 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1808 *pp_outline = outline;
1813 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1815 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1816 if( p_bbox->xMin >= p_bbox->xMax )
1818 p_bbox->xMin = FT_CEIL(p_pen->x);
1819 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1820 glyph_bmp->left = p_bbox->xMin;
1822 if( p_bbox->yMin >= p_bbox->yMax )
1824 p_bbox->yMax = FT_CEIL(p_pen->y);
1825 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1826 glyph_bmp->top = p_bbox->yMax;
1830 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1832 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1833 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1834 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1835 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1838 static int ProcessLines( filter_t *p_filter,
1839 line_desc_t **pp_lines,
1841 int *pi_max_face_height,
1844 text_style_t **pp_styles,
1845 uint32_t *pi_k_dates,
1848 filter_sys_t *p_sys = p_filter->p_sys;
1849 uint32_t *p_fribidi_string = NULL;
1850 text_style_t **pp_fribidi_styles = NULL;
1851 int *p_new_positions = NULL;
1853 #if defined(HAVE_FRIBIDI)
1855 int *p_old_positions;
1856 int start_pos, pos = 0;
1858 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1860 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1861 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1862 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1864 if( ! pp_fribidi_styles ||
1865 ! p_fribidi_string ||
1866 ! p_old_positions ||
1869 free( p_old_positions );
1870 free( p_new_positions );
1871 free( p_fribidi_string );
1872 free( pp_fribidi_styles );
1876 /* Do bidi conversion line-by-line */
1879 while(pos < i_len) {
1880 if (psz_text[pos] != '\n')
1882 p_fribidi_string[pos] = psz_text[pos];
1883 pp_fribidi_styles[pos] = pp_styles[pos];
1884 p_new_positions[pos] = pos;
1888 while(pos < i_len) {
1889 if (psz_text[pos] == '\n')
1893 if (pos > start_pos)
1895 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1896 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1898 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1900 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1901 pos - start_pos, &base_dir,
1902 (FriBidiChar*)p_fribidi_string + start_pos,
1903 p_new_positions + start_pos,
1906 for( int j = start_pos; j < pos; j++ )
1908 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1909 p_new_positions[ j ] += start_pos;
1913 p_fribidi_string[ i_len ] = 0;
1914 free( p_old_positions );
1916 pp_styles = pp_fribidi_styles;
1917 psz_text = p_fribidi_string;
1920 /* Work out the karaoke */
1921 uint8_t *pi_karaoke_bar = NULL;
1924 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1925 if( pi_karaoke_bar )
1927 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1928 for( int i = 0; i < i_len; i++ )
1930 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1931 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1935 free( p_new_positions );
1937 *pi_max_face_height = 0;
1939 line_desc_t **pp_line_next = pp_lines;
1947 int i_face_height_previous = 0;
1948 int i_base_line = 0;
1949 const text_style_t *p_previous_style = NULL;
1950 FT_Face p_face = NULL;
1951 for( int i_start = 0; i_start < i_len; )
1953 /* Compute the length of the current text line */
1955 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
1958 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1959 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
1960 int i_index = i_start;
1965 int i_face_height = 0;
1966 FT_BBox line_bbox = {
1972 int i_ul_offset = 0;
1973 int i_ul_thickness = 0;
1982 break_point_t break_point;
1983 break_point_t break_point_fallback;
1985 #define SAVE_BP(dst) do { \
1986 dst.i_index = i_index; \
1988 dst.line_bbox = line_bbox; \
1989 dst.i_face_height = i_face_height; \
1990 dst.i_ul_offset = i_ul_offset; \
1991 dst.i_ul_thickness = i_ul_thickness; \
1994 SAVE_BP( break_point );
1995 SAVE_BP( break_point_fallback );
1997 while( i_index < i_start + i_length )
1999 /* Split by common FT_Face + Size */
2000 const text_style_t *p_current_style = pp_styles[i_index];
2001 int i_part_length = 0;
2002 while( i_index + i_part_length < i_start + i_length )
2004 const text_style_t *p_style = pp_styles[i_index + i_part_length];
2005 if( !FaceStyleEquals( p_style, p_current_style ) ||
2006 p_style->i_font_size != p_current_style->i_font_size )
2011 /* (Re)load/reconfigure the face if needed */
2012 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
2015 FT_Done_Face( p_face );
2016 p_previous_style = NULL;
2018 p_face = LoadFace( p_filter, p_current_style );
2020 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
2021 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
2023 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
2024 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
2025 if( p_sys->p_stroker )
2027 int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
2028 FT_Stroker_Set( p_sys->p_stroker,
2030 FT_STROKER_LINECAP_ROUND,
2031 FT_STROKER_LINEJOIN_ROUND, 0 );
2034 p_previous_style = p_current_style;
2036 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
2037 p_current_face->size->metrics.y_scale)));
2039 /* Render the part */
2040 bool b_break_line = false;
2041 int i_glyph_last = 0;
2042 while( i_part_length > 0 )
2044 const text_style_t *p_glyph_style = pp_styles[i_index];
2045 uint32_t character = psz_text[i_index];
2046 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
2048 /* Get kerning vector */
2049 FT_Vector kerning = { .x = 0, .y = 0 };
2050 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
2051 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
2053 /* Get the glyph bitmap and its bounding box and all the associated properties */
2054 FT_Vector pen_new = {
2055 .x = pen.x + kerning.x,
2056 .y = pen.y + kerning.y,
2058 FT_Vector pen_shadow_new = {
2059 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
2060 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
2065 FT_BBox outline_bbox;
2067 FT_BBox shadow_bbox;
2069 if( GetGlyph( p_filter,
2070 &glyph, &glyph_bbox,
2071 &outline, &outline_bbox,
2072 &shadow, &shadow_bbox,
2073 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
2074 &pen_new, &pen_shadow_new ) )
2077 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
2079 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
2081 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
2083 /* FIXME and what about outline */
2085 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
2086 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
2087 (p_glyph_style->i_karaoke_background_alpha << 24))
2088 : (p_glyph_style->i_font_color |
2089 (p_glyph_style->i_font_alpha << 24));
2090 int i_line_offset = 0;
2091 int i_line_thickness = 0;
2092 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
2094 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
2095 p_current_face->size->metrics.y_scale)) );
2097 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
2098 p_current_face->size->metrics.y_scale)) );
2100 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
2102 /* Move the baseline to make it strikethrough instead of
2103 * underline. That means that strikethrough takes precedence
2105 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
2106 p_current_face->size->metrics.y_scale)) );
2108 else if( i_line_thickness > 0 )
2110 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
2112 /* The real underline thickness and position are
2113 * updated once the whole line has been parsed */
2114 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
2115 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
2116 i_line_thickness = -1;
2119 FT_BBox line_bbox_new = line_bbox;
2120 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
2122 BBoxEnlarge( &line_bbox_new, &outline_bbox );
2124 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
2126 b_break_line = i_index > i_start &&
2127 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
2130 FT_Done_Glyph( glyph );
2132 FT_Done_Glyph( outline );
2134 FT_Done_Glyph( shadow );
2136 break_point_t *p_bp = NULL;
2137 if( break_point.i_index > i_start )
2138 p_bp = &break_point;
2139 else if( break_point_fallback.i_index > i_start )
2140 p_bp = &break_point_fallback;
2144 msg_Dbg( p_filter, "Breaking line");
2145 for( int i = p_bp->i_index; i < i_index; i++ )
2147 line_character_t *ch = &p_line->p_character[i - i_start];
2148 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
2150 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
2152 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
2154 p_line->i_character_count = p_bp->i_index - i_start;
2156 i_index = p_bp->i_index;
2158 line_bbox = p_bp->line_bbox;
2159 i_face_height = p_bp->i_face_height;
2160 i_ul_offset = p_bp->i_ul_offset;
2161 i_ul_thickness = p_bp->i_ul_thickness;
2165 msg_Err( p_filter, "Breaking unbreakable line");
2170 assert( p_line->i_character_count == i_index - i_start);
2171 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2172 .p_glyph = (FT_BitmapGlyph)glyph,
2173 .p_outline = (FT_BitmapGlyph)outline,
2174 .p_shadow = (FT_BitmapGlyph)shadow,
2176 .i_line_offset = i_line_offset,
2177 .i_line_thickness = i_line_thickness,
2180 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2181 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2182 line_bbox = line_bbox_new;
2184 i_glyph_last = i_glyph_index;
2188 if( character == ' ' || character == '\t' )
2189 SAVE_BP( break_point );
2190 else if( character == 160 )
2191 SAVE_BP( break_point_fallback );
2197 /* Update our baseline */
2198 if( i_face_height_previous > 0 )
2199 i_base_line += __MAX(i_face_height, i_face_height_previous);
2200 if( i_face_height > 0 )
2201 i_face_height_previous = i_face_height;
2203 /* Update the line bbox with the actual base line */
2204 if (line_bbox.yMax > line_bbox.yMin) {
2205 line_bbox.yMin -= i_base_line;
2206 line_bbox.yMax -= i_base_line;
2208 BBoxEnlarge( &bbox, &line_bbox );
2210 /* Terminate and append the line */
2213 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2214 p_line->i_base_line = i_base_line;
2215 if( i_ul_thickness > 0 )
2217 for( int i = 0; i < p_line->i_character_count; i++ )
2219 line_character_t *ch = &p_line->p_character[i];
2220 if( ch->i_line_thickness < 0 )
2222 ch->i_line_offset = i_ul_offset;
2223 ch->i_line_thickness = i_ul_thickness;
2228 *pp_line_next = p_line;
2229 pp_line_next = &p_line->p_next;
2232 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2234 /* Skip what we have rendered and the line delimitor if present */
2236 if( i_start < i_len && psz_text[i_start] == '\n' )
2239 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
2241 msg_Err( p_filter, "Truncated too high subtitle" );
2246 FT_Done_Face( p_face );
2248 free( pp_fribidi_styles );
2249 free( p_fribidi_string );
2250 free( pi_karaoke_bar );
2257 * This function renders a text subpicture region into another one.
2258 * It also calculates the size needed for this string, and renders the
2259 * needed glyphs into memory. It is used as pf_add_string callback in
2260 * the vout method by this module
2262 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2263 subpicture_region_t *p_region_in, bool b_html,
2264 const vlc_fourcc_t *p_chroma_list )
2266 filter_sys_t *p_sys = p_filter->p_sys;
2269 return VLC_EGENERIC;
2270 if( b_html && !p_region_in->psz_html )
2271 return VLC_EGENERIC;
2272 if( !b_html && !p_region_in->psz_text )
2273 return VLC_EGENERIC;
2275 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2276 : p_region_in->psz_text );
2278 uint32_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2279 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2280 if( !psz_text || !pp_styles )
2284 return VLC_EGENERIC;
2287 /* Reset the default fontsize in case screen metrics have changed */
2288 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2291 int rv = VLC_SUCCESS;
2292 int i_text_length = 0;
2294 int i_max_face_height;
2295 line_desc_t *p_lines = NULL;
2297 uint32_t *pi_k_durations = NULL;
2302 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2303 (uint8_t *) p_region_in->psz_html,
2304 strlen( p_region_in->psz_html ),
2306 if( unlikely(p_sub == NULL) )
2309 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2311 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2313 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2314 p_filter->p_sys->p_xml = p_xml_reader;
2321 /* Look for Root Node */
2324 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2326 if( strcasecmp( "karaoke", node ) == 0 )
2328 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2330 else if( strcasecmp( "text", node ) != 0 )
2332 /* Only text and karaoke tags are supported */
2333 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2340 msg_Err( p_filter, "Malformed HTML subtitle" );
2346 rv = ProcessNodes( p_filter,
2347 psz_text, pp_styles, pi_k_durations, &i_text_length,
2348 p_xml_reader, p_region_in->p_style );
2352 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2354 stream_Delete( p_sub );
2359 text_style_t *p_style;
2360 if( p_region_in->p_style )
2361 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2362 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2363 : p_sys->i_font_size,
2364 (p_region_in->p_style->i_font_color & 0xffffff) |
2365 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2367 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2372 p_style = CreateStyle( p_sys->psz_fontfamily,
2374 (p_sys->i_font_color & 0xffffff) |
2375 ((p_sys->i_font_opacity & 0xff) << 24),
2377 if( p_sys->b_font_bold )
2378 p_style->i_style_flags |= STYLE_BOLD;
2380 i_text_length = SetupText( p_filter,
2384 p_region_in->psz_text, p_style, 0 );
2387 if( !rv && i_text_length > 0 )
2389 rv = ProcessLines( p_filter,
2390 &p_lines, &bbox, &i_max_face_height,
2391 psz_text, pp_styles, pi_k_durations, i_text_length );
2394 p_region_out->i_x = p_region_in->i_x;
2395 p_region_out->i_y = p_region_in->i_y;
2397 /* Don't attempt to render text that couldn't be layed out
2399 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2401 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2402 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2404 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2405 p_chroma_list = p_chroma_list_yuvp;
2406 else if( !p_chroma_list || *p_chroma_list == 0 )
2407 p_chroma_list = p_chroma_list_rgba;
2409 const int i_margin = p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2410 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2413 if( *p_chroma == VLC_CODEC_YUVP )
2414 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2415 else if( *p_chroma == VLC_CODEC_YUVA )
2416 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2421 else if( *p_chroma == VLC_CODEC_RGBA )
2422 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2431 /* With karaoke, we're going to have to render the text a number
2432 * of times to show the progress marker on the text.
2434 if( pi_k_durations )
2435 var_SetBool( p_filter, "text-rerender", true );
2438 FreeLines( p_lines );
2441 for( int i = 0; i < i_text_length; i++ )
2443 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2444 text_style_Delete( pp_styles[i] );
2447 free( pi_k_durations );
2452 static int RenderText( 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, false, p_chroma_list );
2461 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2462 subpicture_region_t *p_region_in,
2463 const vlc_fourcc_t *p_chroma_list )
2465 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2470 /*****************************************************************************
2471 * Create: allocates osd-text video thread output method
2472 *****************************************************************************
2473 * This function allocates and initializes a Clone vout method.
2474 *****************************************************************************/
2475 static int Create( vlc_object_t *p_this )
2477 filter_t *p_filter = (filter_t *)p_this;
2478 filter_sys_t *p_sys;
2479 char *psz_fontfile = NULL;
2480 char *psz_fontfamily = NULL;
2481 int i_error = 0, fontindex = 0;
2483 /* Allocate structure */
2484 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2488 p_sys->psz_fontfamily = NULL;
2490 p_sys->p_xml = NULL;
2493 p_sys->p_library = 0;
2494 p_sys->i_font_size = 0;
2495 p_sys->i_display_height = 0;
2497 var_Create( p_filter, "freetype-rel-fontsize",
2498 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2500 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2501 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2502 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2503 p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
2504 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2505 p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
2506 p_sys->b_font_bold = var_InheritBool( p_filter, "freetype-bold" );
2508 p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
2509 p_sys->i_background_opacity = __MAX( __MIN( p_sys->i_background_opacity, 255 ), 0 );
2510 p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
2511 p_sys->i_background_color = __MAX( __MIN( p_sys->i_background_color, 0xFFFFFF ), 0 );
2513 p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2514 p_sys->f_outline_thickness = __MAX( __MIN( p_sys->f_outline_thickness, 0.5 ), 0.0 );
2515 p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2516 p_sys->i_outline_opacity = __MAX( __MIN( p_sys->i_outline_opacity, 255 ), 0 );
2517 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2518 p_sys->i_outline_color = __MAX( __MIN( p_sys->i_outline_color, 0xFFFFFF ), 0 );
2520 p_sys->i_shadow_opacity = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2521 p_sys->i_shadow_opacity = __MAX( __MIN( p_sys->i_shadow_opacity, 255 ), 0 );
2522 p_sys->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2523 p_sys->i_shadow_color = __MAX( __MIN( p_sys->i_shadow_color, 0xFFFFFF ), 0 );
2524 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2525 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2526 f_shadow_distance = __MAX( __MIN( f_shadow_distance, 1 ), 0 );
2527 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2528 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2531 /* Get Windows Font folder */
2532 wchar_t wdir[MAX_PATH];
2533 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2535 GetWindowsDirectoryW( wdir, MAX_PATH );
2536 wcscat( wdir, L"\\fonts" );
2538 p_sys->psz_win_fonts_path = FromWide( wdir );
2541 /* Set default psz_fontfamily */
2542 if( !psz_fontfamily || !*psz_fontfamily )
2544 free( psz_fontfamily );
2546 psz_fontfamily = strdup( DEFAULT_FAMILY );
2549 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2552 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2554 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2558 /* Set the current font file */
2559 p_sys->psz_fontfamily = psz_fontfamily;
2561 #ifdef HAVE_FONTCONFIG
2562 FontConfig_BuildCache( p_filter );
2565 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2566 p_sys->i_default_font_size, &fontindex );
2567 #elif defined(WIN32)
2568 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2569 p_sys->i_default_font_size, &fontindex );
2572 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2574 /* If nothing is found, use the default family */
2576 psz_fontfile = strdup( psz_fontfamily );
2578 #else /* !HAVE_STYLES */
2579 /* Use the default file */
2580 psz_fontfile = psz_fontfamily;
2584 i_error = FT_Init_FreeType( &p_sys->p_library );
2587 msg_Err( p_filter, "couldn't initialize freetype" );
2591 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2592 fontindex, &p_sys->p_face );
2594 if( i_error == FT_Err_Unknown_File_Format )
2596 msg_Err( p_filter, "file %s have unknown format",
2597 psz_fontfile ? psz_fontfile : "(null)" );
2602 msg_Err( p_filter, "failed to load font file %s",
2603 psz_fontfile ? psz_fontfile : "(null)" );
2607 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2610 msg_Err( p_filter, "font has no unicode translation table" );
2614 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2616 p_sys->p_stroker = NULL;
2617 if( p_sys->f_outline_thickness > 0.001 )
2619 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2621 msg_Err( p_filter, "Failed to create stroker for outlining" );
2624 p_sys->pp_font_attachments = NULL;
2625 p_sys->i_font_attachments = 0;
2627 p_filter->pf_render_text = RenderText;
2629 p_filter->pf_render_html = RenderHtml;
2631 p_filter->pf_render_html = NULL;
2634 LoadFontsFromAttachments( p_filter );
2637 free( psz_fontfile );
2643 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2644 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2646 free( psz_fontfile );
2648 free( psz_fontfamily );
2650 return VLC_EGENERIC;
2653 /*****************************************************************************
2654 * Destroy: destroy Clone video thread output method
2655 *****************************************************************************
2656 * Clean up all data and library connections
2657 *****************************************************************************/
2658 static void Destroy( vlc_object_t *p_this )
2660 filter_t *p_filter = (filter_t *)p_this;
2661 filter_sys_t *p_sys = p_filter->p_sys;
2663 if( p_sys->pp_font_attachments )
2665 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2666 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2668 free( p_sys->pp_font_attachments );
2672 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2674 free( p_sys->psz_fontfamily );
2676 /* FcFini asserts calling the subfunction FcCacheFini()
2677 * even if no other library functions have been made since FcInit(),
2678 * so don't call it. */
2680 if( p_sys->p_stroker )
2681 FT_Stroker_Done( p_sys->p_stroker );
2682 FT_Done_Face( p_sys->p_face );
2683 FT_Done_FreeType( p_sys->p_library );