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 Unicode.ttf"
50 # define DEFAULT_FAMILY "Arial Unicode MS"
51 #elif defined( WIN32 )
52 # define DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
53 # define DEFAULT_FAMILY "Arial"
54 #elif defined( __OS2__ )
55 # define DEFAULT_FONT_FILE "/psfonts/tnrwt_k.ttf"
56 # define DEFAULT_FAMILY "Times New Roman WT K"
57 #elif defined( HAVE_MAEMO )
58 # define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf"
59 # define DEFAULT_FAMILY "Nokia Sans Bold"
61 # define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
62 # define DEFAULT_FAMILY "Serif Bold"
66 #include <freetype/ftsynth.h>
67 #include FT_FREETYPE_H
71 #define FT_FLOOR(X) ((X & -64) >> 6)
72 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
74 #define FT_MulFix(v, s) (((v)*(s))>>16)
78 #if defined(HAVE_FRIBIDI)
79 # include <fribidi/fribidi.h>
87 # undef HAVE_FONTCONFIG
91 #ifdef HAVE_FONTCONFIG
92 # include <fontconfig/fontconfig.h>
99 typedef uint16_t uni_char_t;
100 # define FREETYPE_TO_UCS "UCS-2LE"
102 typedef uint32_t uni_char_t;
103 # if defined(WORDS_BIGENDIAN)
104 # define FREETYPE_TO_UCS "UCS-4BE"
106 # define FREETYPE_TO_UCS "UCS-4LE"
110 /*****************************************************************************
112 *****************************************************************************/
113 static int Create ( vlc_object_t * );
114 static void Destroy( vlc_object_t * );
116 #define FONT_TEXT N_("Font")
118 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
119 #define FONT_LONGTEXT N_("Font file for the font you want to use")
121 #define FONTSIZE_TEXT N_("Font size in pixels")
122 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
123 "that will be rendered on the video. " \
124 "If set to something different than 0 this option will override the " \
125 "relative font size." )
126 #define OPACITY_TEXT N_("Text opacity")
127 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
128 "text that will be rendered on the video. 0 = transparent, " \
129 "255 = totally opaque. " )
130 #define COLOR_TEXT N_("Text default color")
131 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
132 "the video. This must be an hexadecimal (like HTML colors). The first two "\
133 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
134 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
135 #define FONTSIZER_TEXT N_("Relative font size")
136 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
137 "fonts that will be rendered on the video. If absolute font size is set, "\
138 "relative size will be overridden." )
139 #define BOLD_TEXT N_("Force bold")
141 #define BG_OPACITY_TEXT N_("Background opacity")
142 #define BG_COLOR_TEXT N_("Background color")
144 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
145 #define OUTLINE_COLOR_TEXT N_("Outline color")
146 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
148 #define SHADOW_OPACITY_TEXT N_("Shadow opacity")
149 #define SHADOW_COLOR_TEXT N_("Shadow color")
150 #define SHADOW_ANGLE_TEXT N_("Shadow angle")
151 #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
154 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
155 static const char *const ppsz_sizes_text[] = {
156 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
157 #define YUVP_TEXT N_("Use YUVP renderer")
158 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
159 "This option is only needed if you want to encode into DVB subtitles" )
161 static const int pi_color_values[] = {
162 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
163 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
164 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
166 static const char *const ppsz_color_descriptions[] = {
167 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
168 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
169 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
171 static const int pi_outline_thickness[] = {
174 static const char *const ppsz_outline_thickness[] = {
175 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
179 set_shortname( N_("Text renderer"))
180 set_description( N_("Freetype2 font renderer") )
181 set_category( CAT_VIDEO )
182 set_subcategory( SUBCAT_VIDEO_SUBPIC )
185 add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
187 add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
190 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
191 FONTSIZE_LONGTEXT, true )
194 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
195 FONTSIZER_LONGTEXT, false )
196 change_integer_list( pi_sizes, ppsz_sizes_text )
199 /* opacity valid on 0..255, with default 255 = fully opaque */
200 add_integer_with_range( "freetype-opacity", 255, 0, 255,
201 OPACITY_TEXT, OPACITY_LONGTEXT, false )
204 /* hook to the color values list, with default 0x00ffffff = white */
205 add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
206 COLOR_LONGTEXT, false )
207 change_integer_list( pi_color_values, ppsz_color_descriptions )
210 add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
213 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
214 BG_OPACITY_TEXT, NULL, false )
216 add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
218 change_integer_list( pi_color_values, ppsz_color_descriptions )
221 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
222 OUTLINE_OPACITY_TEXT, NULL, false )
224 add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
226 change_integer_list( pi_color_values, ppsz_color_descriptions )
228 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
230 change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
233 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
234 SHADOW_OPACITY_TEXT, NULL, false )
236 add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
238 change_integer_list( pi_color_values, ppsz_color_descriptions )
240 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
241 SHADOW_ANGLE_TEXT, NULL, false )
243 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
244 SHADOW_DISTANCE_TEXT, NULL, false )
247 add_obsolete_integer( "freetype-effect" );
249 add_bool( "freetype-yuvp", false, YUVP_TEXT,
250 YUVP_LONGTEXT, true )
251 set_capability( "text renderer", 100 )
252 add_shortcut( "text" )
253 set_callbacks( Create, Destroy )
257 /*****************************************************************************
259 *****************************************************************************/
263 FT_BitmapGlyph p_glyph;
264 FT_BitmapGlyph p_outline;
265 FT_BitmapGlyph p_shadow;
266 uint32_t i_color; /* ARGB color */
267 int i_line_offset; /* underline/strikethrough offset */
268 int i_line_thickness; /* underline/strikethrough thickness */
271 typedef struct line_desc_t line_desc_t;
278 int i_character_count;
279 line_character_t *p_character;
282 typedef struct font_stack_t font_stack_t;
287 uint32_t i_color; /* ARGB */
288 uint32_t i_karaoke_bg_color; /* ARGB */
290 font_stack_t *p_next;
293 /*****************************************************************************
294 * filter_sys_t: freetype local data
295 *****************************************************************************
296 * This structure is part of the video output thread descriptor.
297 * It describes the freetype specific properties of an output thread.
298 *****************************************************************************/
301 FT_Library p_library; /* handle to library */
302 FT_Face p_face; /* handle to face object */
303 FT_Stroker p_stroker;
304 uint8_t i_font_opacity;
309 uint8_t i_background_opacity;
310 int i_background_color;
312 double f_outline_thickness;
313 uint8_t i_outline_opacity;
316 float f_shadow_vector_x;
317 float f_shadow_vector_y;
318 uint8_t i_shadow_opacity;
321 int i_default_font_size;
322 int i_display_height;
323 char* psz_fontfamily;
327 char* psz_win_fonts_path;
331 input_attachment_t **pp_font_attachments;
332 int i_font_attachments;
336 static void YUVFromRGB( uint32_t i_argb,
337 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
339 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
340 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
341 int i_blue = ( i_argb & 0x000000ff );
343 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
344 802 * i_blue + 4096 + 131072 ) >> 13, 235);
345 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
346 3598 * i_blue + 4096 + 1048576) >> 13, 240);
347 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
348 -585 * i_blue + 4096 + 1048576) >> 13, 240);
350 static void RGBFromRGB( uint32_t i_argb,
351 uint8_t *pi_r, uint8_t *pi_g, uint8_t *pi_b )
353 *pi_r = ( i_argb & 0x00ff0000 ) >> 16;
354 *pi_g = ( i_argb & 0x0000ff00 ) >> 8;
355 *pi_b = ( i_argb & 0x000000ff );
357 /*****************************************************************************
358 * Make any TTF/OTF fonts present in the attachments of the media file
359 * and store them for later use by the FreeType Engine
360 *****************************************************************************/
361 static int LoadFontsFromAttachments( filter_t *p_filter )
363 filter_sys_t *p_sys = p_filter->p_sys;
364 input_attachment_t **pp_attachments;
365 int i_attachments_cnt;
367 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
370 p_sys->i_font_attachments = 0;
371 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
372 if( !p_sys->pp_font_attachments )
375 for( int k = 0; k < i_attachments_cnt; k++ )
377 input_attachment_t *p_attach = pp_attachments[k];
379 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
380 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
381 p_attach->i_data > 0 && p_attach->p_data )
383 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
387 vlc_input_attachment_Delete( p_attach );
390 free( pp_attachments );
395 static int GetFontSize( filter_t *p_filter )
397 filter_sys_t *p_sys = p_filter->p_sys;
400 if( p_sys->i_default_font_size )
402 i_size = p_sys->i_default_font_size;
406 int i_ratio = var_GetInteger( p_filter, "freetype-rel-fontsize" );
409 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
410 p_filter->p_sys->i_display_height = p_filter->fmt_out.video.i_height;
415 msg_Warn( p_filter, "invalid fontsize, using 12" );
421 static int SetFontSize( filter_t *p_filter, int i_size )
423 filter_sys_t *p_sys = p_filter->p_sys;
427 i_size = GetFontSize( p_filter );
429 msg_Dbg( p_filter, "using fontsize: %i", i_size );
432 p_sys->i_font_size = i_size;
434 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
436 msg_Err( p_filter, "couldn't set font size to %d", i_size );
444 #ifdef HAVE_FONTCONFIG
445 static void FontConfig_BuildCache( filter_t *p_filter )
448 msg_Dbg( p_filter, "Building font databases.");
456 #if defined( WIN32 ) || defined( __APPLE__ )
457 dialog_progress_bar_t *p_dialog = NULL;
458 FcConfig *fcConfig = FcInitLoadConfig();
460 p_dialog = dialog_ProgressCreate( p_filter,
461 _("Building font cache"),
462 _("Please wait while your font cache is rebuilt.\n"
463 "This should take less than a few minutes."), NULL );
466 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
468 FcConfigBuildFonts( fcConfig );
469 #if defined( __APPLE__ )
470 // By default, scan only the directory /System/Library/Fonts.
471 // So build the set of available fonts under another directories,
472 // and add the set to the current configuration.
473 FcConfigAppFontAddDir( NULL, "~/Library/Fonts" );
474 FcConfigAppFontAddDir( NULL, "/Library/Fonts" );
475 FcConfigAppFontAddDir( NULL, "/Network/Library/Fonts" );
476 //FcConfigAppFontAddDir( NULL, "/System/Library/Fonts" );
480 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
481 dialog_ProgressDestroy( p_dialog );
486 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
490 * \brief Selects a font matching family, bold, italic provided
492 static char* FontConfig_Select( FcConfig* config, const char* family,
493 bool b_bold, bool b_italic, int i_size, int *i_idx )
495 FcResult result = FcResultMatch;
496 FcPattern *pat, *p_pat;
500 /* Create a pattern and fills it */
501 pat = FcPatternCreate();
502 if (!pat) return NULL;
505 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
506 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
507 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
508 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
512 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
514 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
515 free( psz_fontsize );
520 FcDefaultSubstitute( pat );
521 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
523 FcPatternDestroy( pat );
527 /* Find the best font for the pattern, destroy the pattern */
528 p_pat = FcFontMatch( config, pat, &result );
529 FcPatternDestroy( pat );
530 if( !p_pat || result == FcResultNoMatch ) return NULL;
532 /* Check the new pattern */
533 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
534 || ( val_b != FcTrue ) )
536 FcPatternDestroy( p_pat );
539 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
544 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
546 FcPatternDestroy( p_pat );
550 /* if( strcasecmp((const char*)val_s, family ) != 0 )
551 msg_Warn( p_filter, "fontconfig: selected font family is not"
552 "the requested one: '%s' != '%s'\n",
553 (const char*)val_s, family ); */
555 if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
557 FcPatternDestroy( p_pat );
561 FcPatternDestroy( p_pat );
562 return strdup( (const char*)val_s );
567 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
569 static int GetFileFontByName( const char *font_name, char **psz_filename )
572 wchar_t vbuffer[MAX_PATH];
573 wchar_t dbuffer[256];
575 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
579 MultiByteToWideChar( CP_ACP, 0, font_name, -1, dbuffer, 256 );
580 char *font_name_temp = FromWide( dbuffer );
581 size_t fontname_len = strlen( font_name_temp );
583 for( int index = 0;; index++ )
585 DWORD vbuflen = MAX_PATH - 1;
588 LONG i_result = RegEnumValueW( hKey, index, vbuffer, &vbuflen,
589 NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
590 if( i_result != ERROR_SUCCESS )
596 char *psz_value = FromWide( vbuffer );
598 char *s = strchr( psz_value,'(' );
599 if( s != NULL && s != psz_value ) s[-1] = '\0';
601 /* Manage concatenated font names */
602 if( strchr( psz_value, '&') ) {
603 if( strcasestr( psz_value, font_name_temp ) != NULL )
610 if( strncasecmp( psz_value, font_name_temp, fontname_len ) == 0 )
620 *psz_filename = FromWide( dbuffer );
621 free( font_name_temp );
627 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
628 DWORD type, LPARAM lParam)
630 VLC_UNUSED( metric );
631 if( (type & RASTER_FONTTYPE) ) return 1;
632 // if( lpelfe->elfScript ) FIXME
634 return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
637 static char* Win32_Select( filter_t *p_filter, const char* family,
638 bool b_bold, bool b_italic, int i_size, int *i_idx )
640 VLC_UNUSED( i_size );
642 if( !family || strlen( family ) < 1 )
647 lf.lfCharSet = DEFAULT_CHARSET;
651 lf.lfWeight = FW_BOLD;
654 wchar_t* psz_fbuffer = ToWide( family );
655 WideCharToMultiByte( CP_ACP, 0, psz_fbuffer, -1, facename, 32, " ", 0 );
656 strncpy( (LPSTR)&lf.lfFaceName, facename, 32 );
660 char *psz_filename = NULL;
661 HDC hDC = GetDC( NULL );
662 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
663 ReleaseDC(NULL, hDC);
666 if( psz_filename != NULL )
668 /* FIXME: increase i_idx, when concatenated strings */
671 /* Prepend the Windows Font path, when only a filename was provided */
672 if( strchr( psz_filename, DIR_SEP_CHAR ) )
677 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
679 free( psz_filename );
682 free( psz_filename );
686 else /* Let's take any font we can */
690 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, "arial.ttf" ) == -1 )
696 #endif /* HAVE_WIN32 */
698 #endif /* HAVE_STYLES */
701 /*****************************************************************************
702 * RenderYUVP: place string in picture
703 *****************************************************************************
704 * This function merges the previously rendered freetype glyphs into a picture
705 *****************************************************************************/
706 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
710 VLC_UNUSED(p_filter);
711 static const uint8_t pi_gamma[16] =
712 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
713 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
717 int i, x, y, i_pitch;
718 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
720 /* Create a new subpicture region */
721 video_format_Init( &fmt, VLC_CODEC_YUVP );
723 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
725 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
727 assert( !p_region->p_picture );
728 p_region->p_picture = picture_NewFromFormat( &fmt );
729 if( !p_region->p_picture )
731 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
734 /* Calculate text color components
735 * Only use the first color */
736 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
737 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
740 fmt.p_palette->i_entries = 16;
741 for( i = 0; i < 8; i++ )
743 fmt.p_palette->palette[i][0] = 0;
744 fmt.p_palette->palette[i][1] = 0x80;
745 fmt.p_palette->palette[i][2] = 0x80;
746 fmt.p_palette->palette[i][3] = pi_gamma[i];
747 fmt.p_palette->palette[i][3] =
748 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
750 for( i = 8; i < fmt.p_palette->i_entries; i++ )
752 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
753 fmt.p_palette->palette[i][1] = i_u;
754 fmt.p_palette->palette[i][2] = i_v;
755 fmt.p_palette->palette[i][3] = pi_gamma[i];
756 fmt.p_palette->palette[i][3] =
757 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
760 p_dst = p_region->p_picture->Y_PIXELS;
761 i_pitch = p_region->p_picture->Y_PITCH;
763 /* Initialize the region pixels */
764 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
766 for( ; p_line != NULL; p_line = p_line->p_next )
768 int i_align_left = 0;
769 if( p_line->i_width < (int)fmt.i_visible_width )
771 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
772 i_align_left = ( fmt.i_visible_width - p_line->i_width );
773 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
774 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
778 for( i = 0; i < p_line->i_character_count; i++ )
780 const line_character_t *ch = &p_line->p_character[i];
781 FT_BitmapGlyph p_glyph = ch->p_glyph;
783 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
784 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
786 for( y = 0; y < p_glyph->bitmap.rows; y++ )
788 for( x = 0; x < p_glyph->bitmap.width; x++ )
790 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
791 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
792 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
798 /* Outlining (find something better than nearest neighbour filtering ?) */
801 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
802 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
803 uint8_t left, current;
805 for( y = 1; y < (int)fmt.i_height - 1; y++ )
807 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
808 p_dst += p_region->p_picture->Y_PITCH;
811 for( x = 1; x < (int)fmt.i_width - 1; x++ )
814 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
815 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;
819 memset( p_top, 0, fmt.i_width );
825 /*****************************************************************************
826 * RenderYUVA: place string in picture
827 *****************************************************************************
828 * This function merges the previously rendered freetype glyphs into a picture
829 *****************************************************************************/
830 static void FillYUVAPicture( picture_t *p_picture,
831 int i_a, int i_y, int i_u, int i_v )
833 memset( p_picture->p[0].p_pixels, i_y,
834 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
835 memset( p_picture->p[1].p_pixels, i_u,
836 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
837 memset( p_picture->p[2].p_pixels, i_v,
838 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
839 memset( p_picture->p[3].p_pixels, i_a,
840 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
843 static inline void BlendYUVAPixel( picture_t *p_picture,
844 int i_picture_x, int i_picture_y,
845 int i_a, int i_y, int i_u, int i_v,
848 int i_an = i_a * i_alpha / 255;
850 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
851 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
852 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
853 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
865 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
868 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
869 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
870 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
875 static void FillRGBAPicture( picture_t *p_picture,
876 int i_a, int i_r, int i_g, int i_b )
878 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
880 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
882 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
891 static inline void BlendRGBAPixel( picture_t *p_picture,
892 int i_picture_x, int i_picture_y,
893 int i_a, int i_r, int i_g, int i_b,
896 int i_an = i_a * i_alpha / 255;
898 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
900 int i_ao = p_rgba[3];
910 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
913 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
914 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
915 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
920 static inline void BlendAXYZGlyph( picture_t *p_picture,
921 int i_picture_x, int i_picture_y,
922 int i_a, int i_x, int i_y, int i_z,
923 FT_BitmapGlyph p_glyph,
924 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
927 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
929 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
930 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
932 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
936 static inline void BlendAXYZLine( picture_t *p_picture,
937 int i_picture_x, int i_picture_y,
938 int i_a, int i_x, int i_y, int i_z,
939 const line_character_t *p_current,
940 const line_character_t *p_next,
941 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
943 int i_line_width = p_current->p_glyph->bitmap.width;
945 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
947 for( int dx = 0; dx < i_line_width; dx++ )
949 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
950 BlendPixel( p_picture,
952 i_picture_y + p_current->i_line_offset + dy,
953 i_a, i_x, i_y, i_z, 0xff );
957 static inline int RenderAXYZ( filter_t *p_filter,
958 subpicture_region_t *p_region,
959 line_desc_t *p_line_head,
962 vlc_fourcc_t i_chroma,
963 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
964 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
965 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
967 filter_sys_t *p_sys = p_filter->p_sys;
969 /* Create a new subpicture region */
970 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
971 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
973 video_format_Init( &fmt, i_chroma );
975 fmt.i_visible_width = i_text_width + 2 * i_margin;
977 fmt.i_visible_height = i_text_height + 2 * i_margin;
979 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
980 if( !p_region->p_picture )
984 /* Initialize the picture background */
985 uint8_t i_a = p_sys->i_background_opacity;
986 uint8_t i_x, i_y, i_z;
987 ExtractComponents( p_sys->i_background_color, &i_x, &i_y, &i_z );
989 FillPicture( p_picture, i_a, i_x, i_y, i_z );
991 /* Render shadow then outline and then normal glyphs */
992 for( int g = 0; g < 3; g++ )
994 /* Render all lines */
995 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
997 int i_align_left = i_margin;
998 if( p_line->i_width < i_text_width )
1000 /* Left offset to take into account alignment */
1001 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
1002 i_align_left += ( i_text_width - p_line->i_width );
1003 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
1004 i_align_left += ( i_text_width - p_line->i_width ) / 2;
1006 int i_align_top = i_margin;
1008 /* Render all glyphs and underline/strikethrough */
1009 for( int i = 0; i < p_line->i_character_count; i++ )
1011 const line_character_t *ch = &p_line->p_character[i];
1012 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
1016 i_a = (ch->i_color >> 24) & 0xff;
1020 i_a = i_a * p_sys->i_shadow_opacity / 255;
1021 i_color = p_sys->i_shadow_color;
1024 i_a = i_a * p_sys->i_outline_opacity / 255;
1025 i_color = p_sys->i_outline_color;
1028 i_color = ch->i_color;
1031 ExtractComponents( i_color, &i_x, &i_y, &i_z );
1033 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
1034 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
1036 BlendAXYZGlyph( p_picture,
1037 i_glyph_x, i_glyph_y,
1042 /* underline/strikethrough are only rendered for the normal glyph */
1043 if( g == 2 && ch->i_line_thickness > 0 )
1044 BlendAXYZLine( p_picture,
1045 i_glyph_x, i_glyph_y + p_glyph->top,
1048 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
1057 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
1058 uint32_t i_font_color, uint32_t i_karaoke_bg_color,
1061 text_style_t *p_style = text_style_New();
1065 p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
1066 p_style->i_font_size = i_font_size;
1067 p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0;
1068 p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
1069 p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0;
1070 p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
1071 p_style->i_style_flags |= i_style_flags;
1075 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1076 uint32_t i_color, uint32_t i_karaoke_bg_color )
1079 return VLC_EGENERIC;
1081 font_stack_t *p_new = malloc( sizeof(*p_new) );
1085 p_new->p_next = NULL;
1088 p_new->psz_name = strdup( psz_name );
1090 p_new->psz_name = NULL;
1092 p_new->i_size = i_size;
1093 p_new->i_color = i_color;
1094 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1102 font_stack_t *p_last;
1104 for( p_last = *p_font;
1106 p_last = p_last->p_next )
1109 p_last->p_next = p_new;
1114 static int PopFont( font_stack_t **p_font )
1116 font_stack_t *p_last, *p_next_to_last;
1118 if( !p_font || !*p_font )
1119 return VLC_EGENERIC;
1121 p_next_to_last = NULL;
1122 for( p_last = *p_font;
1124 p_last = p_last->p_next )
1126 p_next_to_last = p_last;
1129 if( p_next_to_last )
1130 p_next_to_last->p_next = NULL;
1134 free( p_last->psz_name );
1140 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1141 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1143 font_stack_t *p_last;
1145 if( !p_font || !*p_font )
1146 return VLC_EGENERIC;
1148 for( p_last=*p_font;
1150 p_last=p_last->p_next )
1153 *psz_name = p_last->psz_name;
1154 *i_size = p_last->i_size;
1155 *i_color = p_last->i_color;
1156 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1161 static const struct {
1162 const char *psz_name;
1164 } p_html_colors[] = {
1165 /* Official html colors */
1166 { "Aqua", 0x00FFFF },
1167 { "Black", 0x000000 },
1168 { "Blue", 0x0000FF },
1169 { "Fuchsia", 0xFF00FF },
1170 { "Gray", 0x808080 },
1171 { "Green", 0x008000 },
1172 { "Lime", 0x00FF00 },
1173 { "Maroon", 0x800000 },
1174 { "Navy", 0x000080 },
1175 { "Olive", 0x808000 },
1176 { "Purple", 0x800080 },
1177 { "Red", 0xFF0000 },
1178 { "Silver", 0xC0C0C0 },
1179 { "Teal", 0x008080 },
1180 { "White", 0xFFFFFF },
1181 { "Yellow", 0xFFFF00 },
1184 { "AliceBlue", 0xF0F8FF },
1185 { "AntiqueWhite", 0xFAEBD7 },
1186 { "Aqua", 0x00FFFF },
1187 { "Aquamarine", 0x7FFFD4 },
1188 { "Azure", 0xF0FFFF },
1189 { "Beige", 0xF5F5DC },
1190 { "Bisque", 0xFFE4C4 },
1191 { "Black", 0x000000 },
1192 { "BlanchedAlmond", 0xFFEBCD },
1193 { "Blue", 0x0000FF },
1194 { "BlueViolet", 0x8A2BE2 },
1195 { "Brown", 0xA52A2A },
1196 { "BurlyWood", 0xDEB887 },
1197 { "CadetBlue", 0x5F9EA0 },
1198 { "Chartreuse", 0x7FFF00 },
1199 { "Chocolate", 0xD2691E },
1200 { "Coral", 0xFF7F50 },
1201 { "CornflowerBlue", 0x6495ED },
1202 { "Cornsilk", 0xFFF8DC },
1203 { "Crimson", 0xDC143C },
1204 { "Cyan", 0x00FFFF },
1205 { "DarkBlue", 0x00008B },
1206 { "DarkCyan", 0x008B8B },
1207 { "DarkGoldenRod", 0xB8860B },
1208 { "DarkGray", 0xA9A9A9 },
1209 { "DarkGrey", 0xA9A9A9 },
1210 { "DarkGreen", 0x006400 },
1211 { "DarkKhaki", 0xBDB76B },
1212 { "DarkMagenta", 0x8B008B },
1213 { "DarkOliveGreen", 0x556B2F },
1214 { "Darkorange", 0xFF8C00 },
1215 { "DarkOrchid", 0x9932CC },
1216 { "DarkRed", 0x8B0000 },
1217 { "DarkSalmon", 0xE9967A },
1218 { "DarkSeaGreen", 0x8FBC8F },
1219 { "DarkSlateBlue", 0x483D8B },
1220 { "DarkSlateGray", 0x2F4F4F },
1221 { "DarkSlateGrey", 0x2F4F4F },
1222 { "DarkTurquoise", 0x00CED1 },
1223 { "DarkViolet", 0x9400D3 },
1224 { "DeepPink", 0xFF1493 },
1225 { "DeepSkyBlue", 0x00BFFF },
1226 { "DimGray", 0x696969 },
1227 { "DimGrey", 0x696969 },
1228 { "DodgerBlue", 0x1E90FF },
1229 { "FireBrick", 0xB22222 },
1230 { "FloralWhite", 0xFFFAF0 },
1231 { "ForestGreen", 0x228B22 },
1232 { "Fuchsia", 0xFF00FF },
1233 { "Gainsboro", 0xDCDCDC },
1234 { "GhostWhite", 0xF8F8FF },
1235 { "Gold", 0xFFD700 },
1236 { "GoldenRod", 0xDAA520 },
1237 { "Gray", 0x808080 },
1238 { "Grey", 0x808080 },
1239 { "Green", 0x008000 },
1240 { "GreenYellow", 0xADFF2F },
1241 { "HoneyDew", 0xF0FFF0 },
1242 { "HotPink", 0xFF69B4 },
1243 { "IndianRed", 0xCD5C5C },
1244 { "Indigo", 0x4B0082 },
1245 { "Ivory", 0xFFFFF0 },
1246 { "Khaki", 0xF0E68C },
1247 { "Lavender", 0xE6E6FA },
1248 { "LavenderBlush", 0xFFF0F5 },
1249 { "LawnGreen", 0x7CFC00 },
1250 { "LemonChiffon", 0xFFFACD },
1251 { "LightBlue", 0xADD8E6 },
1252 { "LightCoral", 0xF08080 },
1253 { "LightCyan", 0xE0FFFF },
1254 { "LightGoldenRodYellow", 0xFAFAD2 },
1255 { "LightGray", 0xD3D3D3 },
1256 { "LightGrey", 0xD3D3D3 },
1257 { "LightGreen", 0x90EE90 },
1258 { "LightPink", 0xFFB6C1 },
1259 { "LightSalmon", 0xFFA07A },
1260 { "LightSeaGreen", 0x20B2AA },
1261 { "LightSkyBlue", 0x87CEFA },
1262 { "LightSlateGray", 0x778899 },
1263 { "LightSlateGrey", 0x778899 },
1264 { "LightSteelBlue", 0xB0C4DE },
1265 { "LightYellow", 0xFFFFE0 },
1266 { "Lime", 0x00FF00 },
1267 { "LimeGreen", 0x32CD32 },
1268 { "Linen", 0xFAF0E6 },
1269 { "Magenta", 0xFF00FF },
1270 { "Maroon", 0x800000 },
1271 { "MediumAquaMarine", 0x66CDAA },
1272 { "MediumBlue", 0x0000CD },
1273 { "MediumOrchid", 0xBA55D3 },
1274 { "MediumPurple", 0x9370D8 },
1275 { "MediumSeaGreen", 0x3CB371 },
1276 { "MediumSlateBlue", 0x7B68EE },
1277 { "MediumSpringGreen", 0x00FA9A },
1278 { "MediumTurquoise", 0x48D1CC },
1279 { "MediumVioletRed", 0xC71585 },
1280 { "MidnightBlue", 0x191970 },
1281 { "MintCream", 0xF5FFFA },
1282 { "MistyRose", 0xFFE4E1 },
1283 { "Moccasin", 0xFFE4B5 },
1284 { "NavajoWhite", 0xFFDEAD },
1285 { "Navy", 0x000080 },
1286 { "OldLace", 0xFDF5E6 },
1287 { "Olive", 0x808000 },
1288 { "OliveDrab", 0x6B8E23 },
1289 { "Orange", 0xFFA500 },
1290 { "OrangeRed", 0xFF4500 },
1291 { "Orchid", 0xDA70D6 },
1292 { "PaleGoldenRod", 0xEEE8AA },
1293 { "PaleGreen", 0x98FB98 },
1294 { "PaleTurquoise", 0xAFEEEE },
1295 { "PaleVioletRed", 0xD87093 },
1296 { "PapayaWhip", 0xFFEFD5 },
1297 { "PeachPuff", 0xFFDAB9 },
1298 { "Peru", 0xCD853F },
1299 { "Pink", 0xFFC0CB },
1300 { "Plum", 0xDDA0DD },
1301 { "PowderBlue", 0xB0E0E6 },
1302 { "Purple", 0x800080 },
1303 { "Red", 0xFF0000 },
1304 { "RosyBrown", 0xBC8F8F },
1305 { "RoyalBlue", 0x4169E1 },
1306 { "SaddleBrown", 0x8B4513 },
1307 { "Salmon", 0xFA8072 },
1308 { "SandyBrown", 0xF4A460 },
1309 { "SeaGreen", 0x2E8B57 },
1310 { "SeaShell", 0xFFF5EE },
1311 { "Sienna", 0xA0522D },
1312 { "Silver", 0xC0C0C0 },
1313 { "SkyBlue", 0x87CEEB },
1314 { "SlateBlue", 0x6A5ACD },
1315 { "SlateGray", 0x708090 },
1316 { "SlateGrey", 0x708090 },
1317 { "Snow", 0xFFFAFA },
1318 { "SpringGreen", 0x00FF7F },
1319 { "SteelBlue", 0x4682B4 },
1320 { "Tan", 0xD2B48C },
1321 { "Teal", 0x008080 },
1322 { "Thistle", 0xD8BFD8 },
1323 { "Tomato", 0xFF6347 },
1324 { "Turquoise", 0x40E0D0 },
1325 { "Violet", 0xEE82EE },
1326 { "Wheat", 0xF5DEB3 },
1327 { "White", 0xFFFFFF },
1328 { "WhiteSmoke", 0xF5F5F5 },
1329 { "Yellow", 0xFFFF00 },
1330 { "YellowGreen", 0x9ACD32 },
1335 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1336 font_stack_t **p_fonts )
1339 char *psz_fontname = NULL;
1340 uint32_t i_font_color = 0xffffff;
1341 int i_font_alpha = 255;
1342 uint32_t i_karaoke_bg_color = 0x00ffffff;
1343 int i_font_size = 24;
1345 /* Default all attributes to the top font in the stack -- in case not
1346 * all attributes are specified in the sub-font
1348 if( VLC_SUCCESS == PeekFont( p_fonts,
1352 &i_karaoke_bg_color ))
1354 psz_fontname = strdup( psz_fontname );
1355 i_font_size = i_font_size;
1357 i_font_alpha = (i_font_color >> 24) & 0xff;
1358 i_font_color &= 0x00ffffff;
1360 const char *name, *value;
1361 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1363 if( !strcasecmp( "face", name ) )
1365 free( psz_fontname );
1366 psz_fontname = strdup( value );
1368 else if( !strcasecmp( "size", name ) )
1370 if( ( *value == '+' ) || ( *value == '-' ) )
1372 int i_value = atoi( value );
1374 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1375 i_font_size += ( i_value * i_font_size ) / 10;
1376 else if( i_value < -5 )
1377 i_font_size = - i_value;
1378 else if( i_value > 5 )
1379 i_font_size = i_value;
1382 i_font_size = atoi( value );
1384 else if( !strcasecmp( "color", name ) )
1386 if( value[0] == '#' )
1388 i_font_color = strtol( value + 1, NULL, 16 );
1389 i_font_color &= 0x00ffffff;
1394 uint32_t i_value = strtol( value, &end, 16 );
1395 if( *end == '\0' || *end == ' ' )
1396 i_font_color = i_value & 0x00ffffff;
1398 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1400 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1402 i_font_color = p_html_colors[i].i_value;
1408 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1410 i_font_alpha = strtol( value + 1, NULL, 16 );
1411 i_font_alpha &= 0xff;
1414 rv = PushFont( p_fonts,
1417 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1418 i_karaoke_bg_color );
1420 free( psz_fontname );
1425 /* Turn any multiple-whitespaces into single spaces */
1426 static void HandleWhiteSpace( char *psz_node )
1428 char *s = strpbrk( psz_node, "\t\r\n " );
1431 int i_whitespace = strspn( s, "\t\r\n " );
1433 if( i_whitespace > 1 )
1436 strlen( s ) - i_whitespace + 1 );
1439 s = strpbrk( s, "\t\r\n " );
1444 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1445 font_stack_t **p_fonts,
1448 char *psz_fontname = NULL;
1449 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1450 uint32_t i_karaoke_bg_color = i_font_color;
1451 int i_font_size = p_sys->i_font_size;
1453 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1454 &i_font_color, &i_karaoke_bg_color ) )
1457 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1462 static unsigned SetupText( filter_t *p_filter,
1463 uni_char_t *psz_text_out,
1464 text_style_t **pp_styles,
1465 uint32_t *pi_k_dates,
1467 const char *psz_text_in,
1468 text_style_t *p_style,
1471 size_t i_string_length;
1473 size_t i_string_bytes;
1474 uni_char_t *psz_tmp = ToCharset( FREETYPE_TO_UCS, psz_text_in, &i_string_bytes );
1477 memcpy( psz_text_out, psz_tmp, i_string_bytes );
1478 i_string_length = i_string_bytes / sizeof( *psz_tmp );
1483 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1484 i_string_length = 0;
1487 if( i_string_length > 0 )
1489 for( unsigned i = 0; i < i_string_length; i++ )
1490 pp_styles[i] = p_style;
1494 text_style_Delete( p_style );
1496 if( i_string_length > 0 && pi_k_dates )
1498 for( unsigned i = 0; i < i_string_length; i++ )
1499 pi_k_dates[i] = i_k_date;
1501 return i_string_length;
1504 static int ProcessNodes( filter_t *p_filter,
1505 uni_char_t *psz_text,
1506 text_style_t **pp_styles,
1507 uint32_t *pi_k_dates,
1509 xml_reader_t *p_xml_reader,
1510 text_style_t *p_font_style )
1512 int rv = VLC_SUCCESS;
1513 filter_sys_t *p_sys = p_filter->p_sys;
1514 int i_text_length = 0;
1515 font_stack_t *p_fonts = NULL;
1516 uint32_t i_k_date = 0;
1518 int i_style_flags = 0;
1522 rv = PushFont( &p_fonts,
1523 p_font_style->psz_fontname,
1524 p_font_style->i_font_size > 0 ? p_font_style->i_font_size
1525 : p_sys->i_font_size,
1526 (p_font_style->i_font_color & 0xffffff) |
1527 ((p_font_style->i_font_alpha & 0xff) << 24),
1528 (p_font_style->i_karaoke_background_color & 0xffffff) |
1529 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1531 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1539 rv = PushFont( &p_fonts,
1540 p_sys->psz_fontfamily,
1542 (p_sys->i_font_color & 0xffffff) |
1543 ((p_sys->i_font_opacity & 0xff) << 24),
1547 if( p_sys->b_font_bold )
1548 i_style_flags |= STYLE_BOLD;
1550 if( rv != VLC_SUCCESS )
1556 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1560 case XML_READER_ENDELEM:
1561 if( !strcasecmp( "font", node ) )
1562 PopFont( &p_fonts );
1563 else if( !strcasecmp( "b", node ) )
1564 i_style_flags &= ~STYLE_BOLD;
1565 else if( !strcasecmp( "i", node ) )
1566 i_style_flags &= ~STYLE_ITALIC;
1567 else if( !strcasecmp( "u", node ) )
1568 i_style_flags &= ~STYLE_UNDERLINE;
1569 else if( !strcasecmp( "s", node ) )
1570 i_style_flags &= ~STYLE_STRIKEOUT;
1573 case XML_READER_STARTELEM:
1574 if( !strcasecmp( "font", node ) )
1575 HandleFontAttributes( p_xml_reader, &p_fonts );
1576 else if( !strcasecmp( "b", node ) )
1577 i_style_flags |= STYLE_BOLD;
1578 else if( !strcasecmp( "i", node ) )
1579 i_style_flags |= STYLE_ITALIC;
1580 else if( !strcasecmp( "u", node ) )
1581 i_style_flags |= STYLE_UNDERLINE;
1582 else if( !strcasecmp( "s", node ) )
1583 i_style_flags |= STYLE_STRIKEOUT;
1584 else if( !strcasecmp( "br", node ) )
1586 i_text_length += SetupText( p_filter,
1587 &psz_text[i_text_length],
1588 &pp_styles[i_text_length],
1589 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1591 GetStyleFromFontStack( p_sys,
1596 else if( !strcasecmp( "k", node ) )
1599 const char *name, *value;
1600 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1602 if( !strcasecmp( "t", name ) && value )
1603 i_k_date += atoi( value );
1608 case XML_READER_TEXT:
1610 char *psz_node = strdup( node );
1611 if( unlikely(!psz_node) )
1614 HandleWhiteSpace( psz_node );
1615 resolve_xml_special_chars( psz_node );
1617 i_text_length += SetupText( p_filter,
1618 &psz_text[i_text_length],
1619 &pp_styles[i_text_length],
1620 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1622 GetStyleFromFontStack( p_sys,
1632 *pi_len = i_text_length;
1634 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1639 static void FreeLine( line_desc_t *p_line )
1641 for( int i = 0; i < p_line->i_character_count; i++ )
1643 line_character_t *ch = &p_line->p_character[i];
1644 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1646 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1648 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1651 free( p_line->p_character );
1655 static void FreeLines( line_desc_t *p_lines )
1657 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1659 line_desc_t *p_next = p_line->p_next;
1665 static line_desc_t *NewLine( int i_count )
1667 line_desc_t *p_line = malloc( sizeof(*p_line) );
1672 p_line->p_next = NULL;
1673 p_line->i_width = 0;
1674 p_line->i_base_line = 0;
1675 p_line->i_character_count = 0;
1677 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1678 if( !p_line->p_character )
1686 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1688 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1690 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1692 FT_Face p_face = NULL;
1694 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1702 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1703 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1704 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1705 (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC)) == i_style_received )
1708 FT_Done_Face( p_face );
1716 static FT_Face LoadFace( filter_t *p_filter,
1717 const text_style_t *p_style )
1719 filter_sys_t *p_sys = p_filter->p_sys;
1721 /* Look for a match amongst our attachments first */
1722 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1724 /* Load system wide font otheriwse */
1729 #ifdef HAVE_FONTCONFIG
1730 psz_fontfile = FontConfig_Select( NULL,
1731 p_style->psz_fontname,
1732 (p_style->i_style_flags & STYLE_BOLD) != 0,
1733 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1736 #elif defined( WIN32 )
1737 psz_fontfile = Win32_Select( p_filter,
1738 p_style->psz_fontname,
1739 (p_style->i_style_flags & STYLE_BOLD) != 0,
1740 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1744 psz_fontfile = NULL;
1749 if( *psz_fontfile == '\0' )
1752 "We were not able to find a matching font: \"%s\" (%s %s),"
1753 " so using default font",
1754 p_style->psz_fontname,
1755 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1756 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1761 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1764 free( psz_fontfile );
1769 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1771 /* We've loaded a font face which is unhelpful for actually
1772 * rendering text - fallback to the default one.
1774 FT_Done_Face( p_face );
1780 static bool FaceStyleEquals( const text_style_t *p_style1,
1781 const text_style_t *p_style2 )
1783 if( !p_style1 || !p_style2 )
1785 if( p_style1 == p_style2 )
1788 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
1789 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
1790 !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
1793 static int GetGlyph( filter_t *p_filter,
1794 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1795 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1796 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1802 FT_Vector *p_pen_shadow )
1804 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1805 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1807 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1808 return VLC_EGENERIC;
1811 /* Do synthetic styling now that Freetype supports it;
1812 * ie. if the font we have loaded is NOT already in the
1813 * style that the tags want, then switch it on; if they
1814 * are then don't. */
1815 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1816 FT_GlyphSlot_Embolden( p_face->glyph );
1817 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1818 FT_GlyphSlot_Oblique( p_face->glyph );
1821 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1823 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1824 return VLC_EGENERIC;
1827 FT_Glyph outline = NULL;
1828 if( p_filter->p_sys->p_stroker )
1831 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1835 FT_Glyph shadow = NULL;
1836 if( p_filter->p_sys->i_shadow_opacity > 0 )
1838 shadow = outline ? outline : glyph;
1839 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1845 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1848 *pp_shadow = shadow;
1850 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1852 FT_Done_Glyph( glyph );
1854 FT_Done_Glyph( outline );
1856 FT_Done_Glyph( shadow );
1857 return VLC_EGENERIC;
1859 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1864 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1865 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1867 *pp_outline = outline;
1872 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1874 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1875 if( p_bbox->xMin >= p_bbox->xMax )
1877 p_bbox->xMin = FT_CEIL(p_pen->x);
1878 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1879 glyph_bmp->left = p_bbox->xMin;
1881 if( p_bbox->yMin >= p_bbox->yMax )
1883 p_bbox->yMax = FT_CEIL(p_pen->y);
1884 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1885 glyph_bmp->top = p_bbox->yMax;
1889 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1891 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1892 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1893 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1894 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1897 static int ProcessLines( filter_t *p_filter,
1898 line_desc_t **pp_lines,
1900 int *pi_max_face_height,
1902 uni_char_t *psz_text,
1903 text_style_t **pp_styles,
1904 uint32_t *pi_k_dates,
1907 filter_sys_t *p_sys = p_filter->p_sys;
1908 uni_char_t *p_fribidi_string = NULL;
1909 text_style_t **pp_fribidi_styles = NULL;
1910 int *p_new_positions = NULL;
1912 #if defined(HAVE_FRIBIDI)
1914 int *p_old_positions;
1915 int start_pos, pos = 0;
1917 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1919 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1920 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1921 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1923 if( ! pp_fribidi_styles ||
1924 ! p_fribidi_string ||
1925 ! p_old_positions ||
1928 free( p_old_positions );
1929 free( p_new_positions );
1930 free( p_fribidi_string );
1931 free( pp_fribidi_styles );
1935 /* Do bidi conversion line-by-line */
1938 while(pos < i_len) {
1939 if (psz_text[pos] != '\n')
1941 p_fribidi_string[pos] = psz_text[pos];
1942 pp_fribidi_styles[pos] = pp_styles[pos];
1943 p_new_positions[pos] = pos;
1947 while(pos < i_len) {
1948 if (psz_text[pos] == '\n')
1952 if (pos > start_pos)
1954 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1955 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1957 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1959 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1960 pos - start_pos, &base_dir,
1961 (FriBidiChar*)p_fribidi_string + start_pos,
1962 p_new_positions + start_pos,
1965 for( int j = start_pos; j < pos; j++ )
1967 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1968 p_new_positions[ j ] += start_pos;
1972 p_fribidi_string[ i_len ] = 0;
1973 free( p_old_positions );
1975 pp_styles = pp_fribidi_styles;
1976 psz_text = p_fribidi_string;
1979 /* Work out the karaoke */
1980 uint8_t *pi_karaoke_bar = NULL;
1983 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1984 if( pi_karaoke_bar )
1986 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1987 for( int i = 0; i < i_len; i++ )
1989 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1990 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1994 free( p_new_positions );
1996 *pi_max_face_height = 0;
1998 line_desc_t **pp_line_next = pp_lines;
2006 int i_face_height_previous = 0;
2007 int i_base_line = 0;
2008 const text_style_t *p_previous_style = NULL;
2009 FT_Face p_face = NULL;
2010 for( int i_start = 0; i_start < i_len; )
2012 /* Compute the length of the current text line */
2014 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
2017 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
2018 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
2019 int i_index = i_start;
2024 int i_face_height = 0;
2025 FT_BBox line_bbox = {
2031 int i_ul_offset = 0;
2032 int i_ul_thickness = 0;
2041 break_point_t break_point;
2042 break_point_t break_point_fallback;
2044 #define SAVE_BP(dst) do { \
2045 dst.i_index = i_index; \
2047 dst.line_bbox = line_bbox; \
2048 dst.i_face_height = i_face_height; \
2049 dst.i_ul_offset = i_ul_offset; \
2050 dst.i_ul_thickness = i_ul_thickness; \
2053 SAVE_BP( break_point );
2054 SAVE_BP( break_point_fallback );
2056 while( i_index < i_start + i_length )
2058 /* Split by common FT_Face + Size */
2059 const text_style_t *p_current_style = pp_styles[i_index];
2060 int i_part_length = 0;
2061 while( i_index + i_part_length < i_start + i_length )
2063 const text_style_t *p_style = pp_styles[i_index + i_part_length];
2064 if( !FaceStyleEquals( p_style, p_current_style ) ||
2065 p_style->i_font_size != p_current_style->i_font_size )
2070 /* (Re)load/reconfigure the face if needed */
2071 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
2074 FT_Done_Face( p_face );
2075 p_previous_style = NULL;
2077 p_face = LoadFace( p_filter, p_current_style );
2079 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
2080 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
2082 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
2083 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
2084 if( p_sys->p_stroker )
2086 int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
2087 FT_Stroker_Set( p_sys->p_stroker,
2089 FT_STROKER_LINECAP_ROUND,
2090 FT_STROKER_LINEJOIN_ROUND, 0 );
2093 p_previous_style = p_current_style;
2095 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
2096 p_current_face->size->metrics.y_scale)));
2098 /* Render the part */
2099 bool b_break_line = false;
2100 int i_glyph_last = 0;
2101 while( i_part_length > 0 )
2103 const text_style_t *p_glyph_style = pp_styles[i_index];
2104 uni_char_t character = psz_text[i_index];
2105 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
2107 /* Get kerning vector */
2108 FT_Vector kerning = { .x = 0, .y = 0 };
2109 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
2110 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
2112 /* Get the glyph bitmap and its bounding box and all the associated properties */
2113 FT_Vector pen_new = {
2114 .x = pen.x + kerning.x,
2115 .y = pen.y + kerning.y,
2117 FT_Vector pen_shadow_new = {
2118 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
2119 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
2124 FT_BBox outline_bbox;
2126 FT_BBox shadow_bbox;
2128 if( GetGlyph( p_filter,
2129 &glyph, &glyph_bbox,
2130 &outline, &outline_bbox,
2131 &shadow, &shadow_bbox,
2132 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
2133 &pen_new, &pen_shadow_new ) )
2136 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
2138 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
2140 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
2142 /* FIXME and what about outline */
2144 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
2145 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
2146 (p_glyph_style->i_karaoke_background_alpha << 24))
2147 : (p_glyph_style->i_font_color |
2148 (p_glyph_style->i_font_alpha << 24));
2149 int i_line_offset = 0;
2150 int i_line_thickness = 0;
2151 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
2153 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
2154 p_current_face->size->metrics.y_scale)) );
2156 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
2157 p_current_face->size->metrics.y_scale)) );
2159 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
2161 /* Move the baseline to make it strikethrough instead of
2162 * underline. That means that strikethrough takes precedence
2164 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
2165 p_current_face->size->metrics.y_scale)) );
2167 else if( i_line_thickness > 0 )
2169 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
2171 /* The real underline thickness and position are
2172 * updated once the whole line has been parsed */
2173 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
2174 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
2175 i_line_thickness = -1;
2178 FT_BBox line_bbox_new = line_bbox;
2179 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
2181 BBoxEnlarge( &line_bbox_new, &outline_bbox );
2183 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
2185 b_break_line = i_index > i_start &&
2186 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
2189 FT_Done_Glyph( glyph );
2191 FT_Done_Glyph( outline );
2193 FT_Done_Glyph( shadow );
2195 break_point_t *p_bp = NULL;
2196 if( break_point.i_index > i_start )
2197 p_bp = &break_point;
2198 else if( break_point_fallback.i_index > i_start )
2199 p_bp = &break_point_fallback;
2203 msg_Dbg( p_filter, "Breaking line");
2204 for( int i = p_bp->i_index; i < i_index; i++ )
2206 line_character_t *ch = &p_line->p_character[i - i_start];
2207 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
2209 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
2211 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
2213 p_line->i_character_count = p_bp->i_index - i_start;
2215 i_index = p_bp->i_index;
2217 line_bbox = p_bp->line_bbox;
2218 i_face_height = p_bp->i_face_height;
2219 i_ul_offset = p_bp->i_ul_offset;
2220 i_ul_thickness = p_bp->i_ul_thickness;
2224 msg_Err( p_filter, "Breaking unbreakable line");
2229 assert( p_line->i_character_count == i_index - i_start);
2230 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2231 .p_glyph = (FT_BitmapGlyph)glyph,
2232 .p_outline = (FT_BitmapGlyph)outline,
2233 .p_shadow = (FT_BitmapGlyph)shadow,
2235 .i_line_offset = i_line_offset,
2236 .i_line_thickness = i_line_thickness,
2239 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2240 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2241 line_bbox = line_bbox_new;
2243 i_glyph_last = i_glyph_index;
2247 if( character == ' ' || character == '\t' )
2248 SAVE_BP( break_point );
2249 else if( character == 160 )
2250 SAVE_BP( break_point_fallback );
2256 /* Update our baseline */
2257 if( i_face_height_previous > 0 )
2258 i_base_line += __MAX(i_face_height, i_face_height_previous);
2259 if( i_face_height > 0 )
2260 i_face_height_previous = i_face_height;
2262 /* Update the line bbox with the actual base line */
2263 if (line_bbox.yMax > line_bbox.yMin) {
2264 line_bbox.yMin -= i_base_line;
2265 line_bbox.yMax -= i_base_line;
2267 BBoxEnlarge( &bbox, &line_bbox );
2269 /* Terminate and append the line */
2272 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2273 p_line->i_base_line = i_base_line;
2274 if( i_ul_thickness > 0 )
2276 for( int i = 0; i < p_line->i_character_count; i++ )
2278 line_character_t *ch = &p_line->p_character[i];
2279 if( ch->i_line_thickness < 0 )
2281 ch->i_line_offset = i_ul_offset;
2282 ch->i_line_thickness = i_ul_thickness;
2287 *pp_line_next = p_line;
2288 pp_line_next = &p_line->p_next;
2291 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2293 /* Skip what we have rendered and the line delimitor if present */
2295 if( i_start < i_len && psz_text[i_start] == '\n' )
2298 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
2300 msg_Err( p_filter, "Truncated too high subtitle" );
2305 FT_Done_Face( p_face );
2307 free( pp_fribidi_styles );
2308 free( p_fribidi_string );
2309 free( pi_karaoke_bar );
2316 * This function renders a text subpicture region into another one.
2317 * It also calculates the size needed for this string, and renders the
2318 * needed glyphs into memory. It is used as pf_add_string callback in
2319 * the vout method by this module
2321 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2322 subpicture_region_t *p_region_in, bool b_html,
2323 const vlc_fourcc_t *p_chroma_list )
2325 filter_sys_t *p_sys = p_filter->p_sys;
2328 return VLC_EGENERIC;
2329 if( b_html && !p_region_in->psz_html )
2330 return VLC_EGENERIC;
2331 if( !b_html && !p_region_in->psz_text )
2332 return VLC_EGENERIC;
2334 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2335 : p_region_in->psz_text );
2337 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2338 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2339 if( !psz_text || !pp_styles )
2343 return VLC_EGENERIC;
2346 /* Reset the default fontsize in case screen metrics have changed */
2347 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2350 int rv = VLC_SUCCESS;
2351 int i_text_length = 0;
2353 int i_max_face_height;
2354 line_desc_t *p_lines = NULL;
2356 uint32_t *pi_k_durations = NULL;
2361 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2362 (uint8_t *) p_region_in->psz_html,
2363 strlen( p_region_in->psz_html ),
2365 if( unlikely(p_sub == NULL) )
2368 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2370 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2372 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2373 p_filter->p_sys->p_xml = p_xml_reader;
2380 /* Look for Root Node */
2383 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2385 if( strcasecmp( "karaoke", node ) == 0 )
2387 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2389 else if( strcasecmp( "text", node ) != 0 )
2391 /* Only text and karaoke tags are supported */
2392 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2399 msg_Err( p_filter, "Malformed HTML subtitle" );
2405 rv = ProcessNodes( p_filter,
2406 psz_text, pp_styles, pi_k_durations, &i_text_length,
2407 p_xml_reader, p_region_in->p_style );
2411 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2413 stream_Delete( p_sub );
2418 text_style_t *p_style;
2419 if( p_region_in->p_style )
2420 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2421 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
2422 : p_sys->i_font_size,
2423 (p_region_in->p_style->i_font_color & 0xffffff) |
2424 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2426 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2431 p_style = CreateStyle( p_sys->psz_fontfamily,
2433 (p_sys->i_font_color & 0xffffff) |
2434 ((p_sys->i_font_opacity & 0xff) << 24),
2436 if( p_sys->b_font_bold )
2437 p_style->i_style_flags |= STYLE_BOLD;
2439 i_text_length = SetupText( p_filter,
2443 p_region_in->psz_text, p_style, 0 );
2446 if( !rv && i_text_length > 0 )
2448 rv = ProcessLines( p_filter,
2449 &p_lines, &bbox, &i_max_face_height,
2450 psz_text, pp_styles, pi_k_durations, i_text_length );
2453 p_region_out->i_x = p_region_in->i_x;
2454 p_region_out->i_y = p_region_in->i_y;
2456 /* Don't attempt to render text that couldn't be layed out
2458 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2460 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
2461 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
2463 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2464 p_chroma_list = p_chroma_list_yuvp;
2465 else if( !p_chroma_list || *p_chroma_list == 0 )
2466 p_chroma_list = p_chroma_list_rgba;
2468 const int i_margin = p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0;
2469 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
2472 if( *p_chroma == VLC_CODEC_YUVP )
2473 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2474 else if( *p_chroma == VLC_CODEC_YUVA )
2475 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2480 else if( *p_chroma == VLC_CODEC_RGBA )
2481 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
2490 /* With karaoke, we're going to have to render the text a number
2491 * of times to show the progress marker on the text.
2493 if( pi_k_durations )
2494 var_SetBool( p_filter, "text-rerender", true );
2497 FreeLines( p_lines );
2500 for( int i = 0; i < i_text_length; i++ )
2502 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2503 text_style_Delete( pp_styles[i] );
2506 free( pi_k_durations );
2511 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2512 subpicture_region_t *p_region_in,
2513 const vlc_fourcc_t *p_chroma_list )
2515 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
2520 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2521 subpicture_region_t *p_region_in,
2522 const vlc_fourcc_t *p_chroma_list )
2524 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
2529 /*****************************************************************************
2530 * Create: allocates osd-text video thread output method
2531 *****************************************************************************
2532 * This function allocates and initializes a Clone vout method.
2533 *****************************************************************************/
2534 static int Create( vlc_object_t *p_this )
2536 filter_t *p_filter = (filter_t *)p_this;
2537 filter_sys_t *p_sys;
2538 char *psz_fontfile = NULL;
2539 char *psz_fontfamily = NULL;
2540 int i_error = 0, fontindex = 0;
2542 /* Allocate structure */
2543 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2547 p_sys->psz_fontfamily = NULL;
2549 p_sys->p_xml = NULL;
2552 p_sys->p_library = 0;
2553 p_sys->i_font_size = 0;
2554 p_sys->i_display_height = 0;
2556 var_Create( p_filter, "freetype-rel-fontsize",
2557 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2559 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2560 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2561 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2562 p_sys->i_font_opacity = VLC_CLIP( p_sys->i_font_opacity, 0, 255 );
2563 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2564 p_sys->i_font_color = VLC_CLIP( p_sys->i_font_color, 0, 0xFFFFFF );
2565 p_sys->b_font_bold = var_InheritBool( p_filter, "freetype-bold" );
2567 p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
2568 p_sys->i_background_opacity = VLC_CLIP( p_sys->i_background_opacity, 0, 255 );
2569 p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
2570 p_sys->i_background_color = VLC_CLIP( p_sys->i_background_color, 0, 0xFFFFFF );
2572 p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2573 p_sys->f_outline_thickness = VLC_CLIP( p_sys->f_outline_thickness, 0.0, 0.5 );
2574 p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2575 p_sys->i_outline_opacity = VLC_CLIP( p_sys->i_outline_opacity, 0, 255 );
2576 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2577 p_sys->i_outline_color = VLC_CLIP( p_sys->i_outline_color, 0, 0xFFFFFF );
2579 p_sys->i_shadow_opacity = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
2580 p_sys->i_shadow_opacity = VLC_CLIP( p_sys->i_shadow_opacity, 0, 255 );
2581 p_sys->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
2582 p_sys->i_shadow_color = VLC_CLIP( p_sys->i_shadow_color, 0, 0xFFFFFF );
2583 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
2584 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
2585 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
2586 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
2587 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
2590 /* Get Windows Font folder */
2591 wchar_t wdir[MAX_PATH];
2592 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2594 GetWindowsDirectoryW( wdir, MAX_PATH );
2595 wcscat( wdir, L"\\fonts" );
2597 p_sys->psz_win_fonts_path = FromWide( wdir );
2600 /* Set default psz_fontfamily */
2601 if( !psz_fontfamily || !*psz_fontfamily )
2603 free( psz_fontfamily );
2605 psz_fontfamily = strdup( DEFAULT_FAMILY );
2608 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2611 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2613 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2617 /* Set the current font file */
2618 p_sys->psz_fontfamily = psz_fontfamily;
2620 #ifdef HAVE_FONTCONFIG
2621 FontConfig_BuildCache( p_filter );
2624 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2625 p_sys->i_default_font_size, &fontindex );
2626 #elif defined(WIN32)
2627 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2628 p_sys->i_default_font_size, &fontindex );
2631 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2633 /* If nothing is found, use the default family */
2635 psz_fontfile = strdup( psz_fontfamily );
2637 #else /* !HAVE_STYLES */
2638 /* Use the default file */
2639 psz_fontfile = psz_fontfamily;
2643 i_error = FT_Init_FreeType( &p_sys->p_library );
2646 msg_Err( p_filter, "couldn't initialize freetype" );
2650 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2651 fontindex, &p_sys->p_face );
2653 if( i_error == FT_Err_Unknown_File_Format )
2655 msg_Err( p_filter, "file %s have unknown format",
2656 psz_fontfile ? psz_fontfile : "(null)" );
2661 msg_Err( p_filter, "failed to load font file %s",
2662 psz_fontfile ? psz_fontfile : "(null)" );
2666 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2669 msg_Err( p_filter, "font has no unicode translation table" );
2673 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2675 p_sys->p_stroker = NULL;
2676 if( p_sys->f_outline_thickness > 0.001 )
2678 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2680 msg_Err( p_filter, "Failed to create stroker for outlining" );
2683 p_sys->pp_font_attachments = NULL;
2684 p_sys->i_font_attachments = 0;
2686 p_filter->pf_render_text = RenderText;
2688 p_filter->pf_render_html = RenderHtml;
2690 p_filter->pf_render_html = NULL;
2693 LoadFontsFromAttachments( p_filter );
2696 free( psz_fontfile );
2702 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2703 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2705 free( psz_fontfile );
2707 free( psz_fontfamily );
2709 return VLC_EGENERIC;
2712 /*****************************************************************************
2713 * Destroy: destroy Clone video thread output method
2714 *****************************************************************************
2715 * Clean up all data and library connections
2716 *****************************************************************************/
2717 static void Destroy( vlc_object_t *p_this )
2719 filter_t *p_filter = (filter_t *)p_this;
2720 filter_sys_t *p_sys = p_filter->p_sys;
2722 if( p_sys->pp_font_attachments )
2724 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2725 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2727 free( p_sys->pp_font_attachments );
2731 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2733 free( p_sys->psz_fontfamily );
2735 /* FcFini asserts calling the subfunction FcCacheFini()
2736 * even if no other library functions have been made since FcInit(),
2737 * so don't call it. */
2739 if( p_sys->p_stroker )
2740 FT_Stroker_Done( p_sys->p_stroker );
2741 FT_Done_Face( p_sys->p_face );
2742 FT_Done_FreeType( p_sys->p_library );