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 *****************************************************************************/
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
37 #include <vlc_stream.h> /* stream_MemoryNew */
38 #include <vlc_input.h> /* vlc_input_attachment_* */
39 #include <vlc_xml.h> /* xml_reader */
40 #include <vlc_strings.h> /* resolve_xml_special_chars */
41 #include <vlc_charset.h> /* ToCharset */
42 #include <vlc_dialog.h> /* FcCache dialog */
43 #include <vlc_filter.h> /* filter_sys_t */
44 #include <vlc_text_style.h> /* text_style_t*/
48 # define DEFAULT_FONT_FILE "/Library/Fonts/Arial Black.ttf"
49 # define DEFAULT_FAMILY "Arial Black"
50 #elif defined( WIN32 )
51 # define DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
52 # define DEFAULT_FAMILY "Arial"
53 #elif defined( HAVE_MAEMO )
54 # define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf"
55 # define DEFAULT_FAMILY "Nokia Sans Bold"
57 # define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
58 # define DEFAULT_FAMILY "Serif Bold"
62 #include <freetype/ftsynth.h>
63 #include FT_FREETYPE_H
67 #define FT_FLOOR(X) ((X & -64) >> 6)
68 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
70 #define FT_MulFix(v, s) (((v)*(s))>>16)
74 #if defined(HAVE_FRIBIDI)
75 # include <fribidi/fribidi.h>
83 # undef HAVE_FONTCONFIG
87 #ifdef HAVE_FONTCONFIG
88 # include <fontconfig/fontconfig.h>
94 /*****************************************************************************
96 *****************************************************************************/
97 static int Create ( vlc_object_t * );
98 static void Destroy( vlc_object_t * );
100 #define FONT_TEXT N_("Font")
102 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
103 #define FONT_LONGTEXT N_("Font file for the font you want to use")
105 #define FONTSIZE_TEXT N_("Font size in pixels")
106 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
107 "that will be rendered on the video. " \
108 "If set to something different than 0 this option will override the " \
109 "relative font size." )
110 #define OPACITY_TEXT N_("Opacity")
111 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
112 "text that will be rendered on the video. 0 = transparent, " \
113 "255 = totally opaque. " )
114 #define COLOR_TEXT N_("Text default color")
115 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
116 "the video. This must be an hexadecimal (like HTML colors). The first two "\
117 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
118 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
119 #define FONTSIZER_TEXT N_("Relative font size")
120 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
121 "fonts that will be rendered on the video. If absolute font size is set, "\
122 "relative size will be overridden." )
124 #define BG_OPACITY_TEXT N_("Background opacity")
125 #define BG_COLOR_TEXT N_("Background color")
127 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
128 #define OUTLINE_COLOR_TEXT N_("Outline color")
129 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
131 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
132 static const char *const ppsz_sizes_text[] = {
133 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
134 #define YUVP_TEXT N_("Use YUVP renderer")
135 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
136 "This option is only needed if you want to encode into DVB subtitles" )
138 static const int pi_color_values[] = {
139 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
140 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
141 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
143 static const char *const ppsz_color_descriptions[] = {
144 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
145 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
146 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
148 static const int pi_outline_thickness[] = {
151 static const char *const ppsz_outline_thickness[] = {
152 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
156 set_shortname( N_("Text renderer"))
157 set_description( N_("Freetype2 font renderer") )
158 set_category( CAT_VIDEO )
159 set_subcategory( SUBCAT_VIDEO_SUBPIC )
162 add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
164 add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
167 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
168 FONTSIZE_LONGTEXT, true )
171 /* opacity valid on 0..255, with default 255 = fully opaque */
172 add_integer_with_range( "freetype-opacity", 255, 0, 255,
173 OPACITY_TEXT, OPACITY_LONGTEXT, false )
176 /* hook to the color values list, with default 0x00ffffff = white */
177 add_integer( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
178 COLOR_LONGTEXT, false )
179 change_integer_list( pi_color_values, ppsz_color_descriptions )
182 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
183 BG_OPACITY_TEXT, "", false )
185 add_integer( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
187 change_integer_list( pi_color_values, ppsz_color_descriptions )
190 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
191 OUTLINE_OPACITY_TEXT, "", false )
193 add_integer( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
195 change_integer_list( pi_color_values, ppsz_color_descriptions )
197 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
199 change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
202 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
203 FONTSIZER_LONGTEXT, false )
204 change_integer_list( pi_sizes, ppsz_sizes_text )
207 add_obsolete_integer( "freetype-effect" );
209 add_bool( "freetype-yuvp", false, YUVP_TEXT,
210 YUVP_LONGTEXT, true )
211 set_capability( "text renderer", 100 )
212 add_shortcut( "text" )
213 set_callbacks( Create, Destroy )
217 /*****************************************************************************
219 *****************************************************************************/
223 FT_BitmapGlyph p_glyph;
224 FT_BitmapGlyph p_outline;
225 uint32_t i_color; /* ARGB color */
226 int i_line_offset; /* underline/strikethrough offset */
227 int i_line_thickness; /* underline/strikethrough thickness */
230 typedef struct line_desc_t line_desc_t;
237 int i_character_count;
238 line_character_t *p_character;
241 typedef struct font_stack_t font_stack_t;
246 uint32_t i_color; /* ARGB */
247 uint32_t i_karaoke_bg_color; /* ARGB */
249 font_stack_t *p_next;
252 /*****************************************************************************
253 * filter_sys_t: freetype local data
254 *****************************************************************************
255 * This structure is part of the video output thread descriptor.
256 * It describes the freetype specific properties of an output thread.
257 *****************************************************************************/
260 FT_Library p_library; /* handle to library */
261 FT_Face p_face; /* handle to face object */
262 FT_Stroker p_stroker;
263 uint8_t i_font_opacity;
267 uint8_t i_background_opacity;
268 int i_background_color;
270 double f_outline_thickness;
271 uint8_t i_outline_opacity;
274 int i_default_font_size;
275 int i_display_height;
276 char* psz_fontfamily;
280 char* psz_win_fonts_path;
284 input_attachment_t **pp_font_attachments;
285 int i_font_attachments;
289 static void YUVFromRGB( uint32_t i_argb,
290 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
292 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
293 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
294 int i_blue = ( i_argb & 0x000000ff );
296 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
297 802 * i_blue + 4096 + 131072 ) >> 13, 235);
298 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
299 3598 * i_blue + 4096 + 1048576) >> 13, 240);
300 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
301 -585 * i_blue + 4096 + 1048576) >> 13, 240);
304 /*****************************************************************************
305 * Make any TTF/OTF fonts present in the attachments of the media file
306 * and store them for later use by the FreeType Engine
307 *****************************************************************************/
308 static int LoadFontsFromAttachments( filter_t *p_filter )
310 filter_sys_t *p_sys = p_filter->p_sys;
311 input_attachment_t **pp_attachments;
312 int i_attachments_cnt;
314 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
317 p_sys->i_font_attachments = 0;
318 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
319 if( !p_sys->pp_font_attachments )
322 for( int k = 0; k < i_attachments_cnt; k++ )
324 input_attachment_t *p_attach = pp_attachments[k];
326 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
327 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
328 p_attach->i_data > 0 && p_attach->p_data )
330 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
334 vlc_input_attachment_Delete( p_attach );
337 free( pp_attachments );
342 static int GetFontSize( filter_t *p_filter )
344 filter_sys_t *p_sys = p_filter->p_sys;
347 if( p_sys->i_default_font_size )
349 i_size = p_sys->i_default_font_size;
353 int i_ratio = var_GetInteger( p_filter, "freetype-rel-fontsize" );
356 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
357 p_filter->p_sys->i_display_height = p_filter->fmt_out.video.i_height;
362 msg_Warn( p_filter, "invalid fontsize, using 12" );
368 static int SetFontSize( filter_t *p_filter, int i_size )
370 filter_sys_t *p_sys = p_filter->p_sys;
374 i_size = GetFontSize( p_filter );
376 msg_Dbg( p_filter, "using fontsize: %i", i_size );
379 p_sys->i_font_size = i_size;
381 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
383 msg_Err( p_filter, "couldn't set font size to %d", i_size );
391 #ifdef HAVE_FONTCONFIG
392 static void FontConfig_BuildCache( filter_t *p_filter )
395 msg_Dbg( p_filter, "Building font databases.");
400 dialog_progress_bar_t *p_dialog = NULL;
401 FcConfig *fcConfig = FcInitLoadConfig();
403 p_dialog = dialog_ProgressCreate( p_filter,
404 _("Building font cache"),
405 _("Please wait while your font cache is rebuilt.\n"
406 "This should take less than a few minutes."), NULL );
409 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
411 FcConfigBuildFonts( fcConfig );
414 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
415 dialog_ProgressDestroy( p_dialog );
420 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
424 * \brief Selects a font matching family, bold, italic provided
426 static char* FontConfig_Select( FcConfig* config, const char* family,
427 bool b_bold, bool b_italic, int i_size, int *i_idx )
429 FcResult result = FcResultMatch;
430 FcPattern *pat, *p_pat;
434 /* Create a pattern and fills it */
435 pat = FcPatternCreate();
436 if (!pat) return NULL;
439 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
440 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
441 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
442 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
446 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
448 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
449 free( psz_fontsize );
454 FcDefaultSubstitute( pat );
455 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
457 FcPatternDestroy( pat );
461 /* Find the best font for the pattern, destroy the pattern */
462 p_pat = FcFontMatch( config, pat, &result );
463 FcPatternDestroy( pat );
464 if( !p_pat || result == FcResultNoMatch ) return NULL;
466 /* Check the new pattern */
467 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
468 || ( val_b != FcTrue ) )
470 FcPatternDestroy( p_pat );
473 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
478 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
480 FcPatternDestroy( p_pat );
484 /* if( strcasecmp((const char*)val_s, family ) != 0 )
485 msg_Warn( p_filter, "fontconfig: selected font family is not"
486 "the requested one: '%s' != '%s'\n",
487 (const char*)val_s, family ); */
489 if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
491 FcPatternDestroy( p_pat );
495 FcPatternDestroy( p_pat );
496 return strdup( (const char*)val_s );
502 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
504 static int GetFileFontByName( const char *font_name, char **psz_filename )
507 wchar_t vbuffer[MAX_PATH];
508 wchar_t dbuffer[256];
510 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey) != ERROR_SUCCESS )
513 for( int index = 0;; index++ )
515 DWORD vbuflen = MAX_PATH - 1;
518 if( RegEnumValueW( hKey, index, vbuffer, &vbuflen,
519 NULL, NULL, (LPBYTE)dbuffer, &dbuflen) != ERROR_SUCCESS )
522 char *psz_value = FromWide( vbuffer );
524 char *s = strchr( psz_value,'(' );
525 if( s != NULL && s != psz_value ) s[-1] = '\0';
527 /* Manage concatenated font names */
528 if( strchr( psz_value, '&') ) {
529 if( strcasestr( psz_value, font_name ) != NULL )
533 if( strcasecmp( psz_value, font_name ) == 0 )
538 *psz_filename = FromWide( dbuffer );
543 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
544 DWORD type, LPARAM lParam)
546 VLC_UNUSED( metric );
547 if( (type & RASTER_FONTTYPE) ) return 1;
548 // if( lpelfe->elfScript ) FIXME
550 return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
553 static char* Win32_Select( filter_t *p_filter, const char* family,
554 bool b_bold, bool b_italic, int i_size, int *i_idx )
556 VLC_UNUSED( i_size );
557 // msg_Dbg( p_filter, "Here in Win32_Select, asking for %s", family );
561 lf.lfCharSet = DEFAULT_CHARSET;
565 lf.lfWeight = FW_BOLD;
566 strncpy( (LPSTR)&lf.lfFaceName, family, 32);
569 char *psz_filename = NULL;
570 HDC hDC = GetDC( NULL );
571 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
572 ReleaseDC(NULL, hDC);
574 if( psz_filename == NULL )
577 /* FIXME: increase i_idx, when concatenated strings */
582 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
591 /*****************************************************************************
592 * RenderYUVP: place string in picture
593 *****************************************************************************
594 * This function merges the previously rendered freetype glyphs into a picture
595 *****************************************************************************/
596 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
600 VLC_UNUSED(p_filter);
601 static const uint8_t pi_gamma[16] =
602 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
603 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
607 int i, x, y, i_pitch;
608 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
610 /* Create a new subpicture region */
611 video_format_Init( &fmt, VLC_CODEC_YUVP );
613 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
615 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
617 assert( !p_region->p_picture );
618 p_region->p_picture = picture_NewFromFormat( &fmt );
619 if( !p_region->p_picture )
621 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
624 /* Calculate text color components
625 * Only use the first color */
626 int i_alpha = 0xff - ((p_line->p_character[0].i_color >> 24) & 0xff);
627 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
630 fmt.p_palette->i_entries = 16;
631 for( i = 0; i < 8; i++ )
633 fmt.p_palette->palette[i][0] = 0;
634 fmt.p_palette->palette[i][1] = 0x80;
635 fmt.p_palette->palette[i][2] = 0x80;
636 fmt.p_palette->palette[i][3] = pi_gamma[i];
637 fmt.p_palette->palette[i][3] =
638 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
640 for( i = 8; i < fmt.p_palette->i_entries; i++ )
642 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
643 fmt.p_palette->palette[i][1] = i_u;
644 fmt.p_palette->palette[i][2] = i_v;
645 fmt.p_palette->palette[i][3] = pi_gamma[i];
646 fmt.p_palette->palette[i][3] =
647 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
650 p_dst = p_region->p_picture->Y_PIXELS;
651 i_pitch = p_region->p_picture->Y_PITCH;
653 /* Initialize the region pixels */
654 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
656 for( ; p_line != NULL; p_line = p_line->p_next )
658 int i_align_left = 0;
659 if( p_line->i_width < fmt.i_visible_width )
661 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
662 i_align_left = ( fmt.i_visible_width - p_line->i_width );
663 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
664 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
668 for( i = 0; i < p_line->i_character_count; i++ )
670 const line_character_t *ch = &p_line->p_character[i];
671 FT_BitmapGlyph p_glyph = ch->p_glyph;
673 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
674 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
676 for( y = 0; y < p_glyph->bitmap.rows; y++ )
678 for( x = 0; x < p_glyph->bitmap.width; x++ )
680 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
681 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
682 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
688 /* Outlining (find something better than nearest neighbour filtering ?) */
691 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
692 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
693 uint8_t left, current;
695 for( y = 1; y < (int)fmt.i_height - 1; y++ )
697 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
698 p_dst += p_region->p_picture->Y_PITCH;
701 for( x = 1; x < (int)fmt.i_width - 1; x++ )
704 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
705 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;
709 memset( p_top, 0, fmt.i_width );
715 /*****************************************************************************
716 * RenderYUVA: place string in picture
717 *****************************************************************************
718 * This function merges the previously rendered freetype glyphs into a picture
719 *****************************************************************************/
720 static inline void BlendYUVAPixel( picture_t *p_picture,
721 int i_picture_x, int i_picture_y,
722 int i_a, int i_y, int i_u, int i_v,
725 int i_an = i_a * i_alpha / 255;
727 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
728 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
729 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
730 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
742 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
745 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
746 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
747 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
752 static inline void BlendYUVAGlyph( picture_t *p_picture,
753 int i_picture_x, int i_picture_y,
754 int i_a, int i_y, int i_u, int i_v,
755 FT_BitmapGlyph p_glyph )
757 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
759 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
760 BlendYUVAPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
762 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
766 static inline void BlendYUVALine( picture_t *p_picture,
767 int i_picture_x, int i_picture_y,
768 int i_a, int i_y, int i_u, int i_v,
769 const line_character_t *p_current,
770 const line_character_t *p_next )
772 int i_line_width = p_current->p_glyph->bitmap.width;
774 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
776 for( int dx = 0; dx < i_line_width; dx++ )
778 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
779 BlendYUVAPixel( p_picture,
781 i_picture_y + p_current->i_line_offset + dy,
782 i_a, i_y, i_u, i_v, 0xff );
786 static int RenderYUVA( filter_t *p_filter,
787 subpicture_region_t *p_region,
788 line_desc_t *p_line_head,
792 filter_sys_t *p_sys = p_filter->p_sys;
794 /* Create a new subpicture region */
795 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
796 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
798 video_format_Init( &fmt, VLC_CODEC_YUVA );
800 fmt.i_visible_width = i_text_width + 2 * i_margin;
802 fmt.i_visible_height = i_text_height + 2 * i_margin;
804 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
805 if( !p_region->p_picture )
809 /* Initialize the picture background */
810 uint8_t i_a = p_sys->i_background_opacity;
811 uint8_t i_y, i_u, i_v;
812 YUVFromRGB( p_sys->i_background_color, &i_y, &i_u, &i_v );
814 memset( p_picture->p[0].p_pixels, i_y,
815 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
816 memset( p_picture->p[1].p_pixels, i_u,
817 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
818 memset( p_picture->p[2].p_pixels, i_v,
819 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
820 memset( p_picture->p[3].p_pixels, i_a,
821 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
823 /* Render outline glyphs in the first pass, and then the normal glyphs */
824 for( int g = 0; g < 2; g++ )
826 /* Render all lines */
827 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
829 int i_align_left = i_margin;
830 if( p_line->i_width < i_text_width )
832 /* Left offset to take into account alignment */
833 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
834 i_align_left += ( i_text_width - p_line->i_width );
835 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
836 i_align_left += ( i_text_width - p_line->i_width ) / 2;
838 int i_align_top = i_margin;
840 /* Render all glyphs and underline/strikethrough */
841 for( int i = 0; i < p_line->i_character_count; i++ )
843 const line_character_t *ch = &p_line->p_character[i];
844 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_outline : ch->p_glyph;
848 uint32_t i_color = ch->i_color;
849 i_a = 0xff - ((i_color >> 24) & 0xff);
852 i_a = i_a * p_sys->i_outline_opacity / 255;
853 i_color = p_sys->i_outline_color;
855 YUVFromRGB( i_color, &i_y, &i_u, &i_v );
857 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
858 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
860 BlendYUVAGlyph( p_picture,
861 i_glyph_x, i_glyph_y,
865 /* underline/strikethrough are only rendered for the normal glyph */
866 if( g == 1 && ch->i_line_thickness > 0 )
867 BlendYUVALine( p_picture,
868 i_glyph_x, i_glyph_y + p_glyph->top,
871 i + 1 < p_line->i_character_count ? &ch[1] : NULL );
879 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
880 uint32_t i_font_color, uint32_t i_karaoke_bg_color,
883 text_style_t *p_style = text_style_New();
887 p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
888 p_style->i_font_size = i_font_size;
889 p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0;
890 p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
891 p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0;
892 p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
893 p_style->i_style_flags |= i_style_flags;
897 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
898 uint32_t i_color, uint32_t i_karaoke_bg_color )
903 font_stack_t *p_new = malloc( sizeof(*p_new) );
907 p_new->p_next = NULL;
910 p_new->psz_name = strdup( psz_name );
912 p_new->psz_name = NULL;
914 p_new->i_size = i_size;
915 p_new->i_color = i_color;
916 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
924 font_stack_t *p_last;
926 for( p_last = *p_font;
928 p_last = p_last->p_next )
931 p_last->p_next = p_new;
936 static int PopFont( font_stack_t **p_font )
938 font_stack_t *p_last, *p_next_to_last;
940 if( !p_font || !*p_font )
943 p_next_to_last = NULL;
944 for( p_last = *p_font;
946 p_last = p_last->p_next )
948 p_next_to_last = p_last;
952 p_next_to_last->p_next = NULL;
956 free( p_last->psz_name );
962 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
963 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
965 font_stack_t *p_last;
967 if( !p_font || !*p_font )
972 p_last=p_last->p_next )
975 *psz_name = p_last->psz_name;
976 *i_size = p_last->i_size;
977 *i_color = p_last->i_color;
978 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
983 static const struct {
984 const char *psz_name;
986 } p_html_colors[] = {
987 /* Official html colors */
988 { "Aqua", 0x00FFFF },
989 { "Black", 0x000000 },
990 { "Blue", 0x0000FF },
991 { "Fuchsia", 0xFF00FF },
992 { "Gray", 0x808080 },
993 { "Green", 0x008000 },
994 { "Lime", 0x00FF00 },
995 { "Maroon", 0x800000 },
996 { "Navy", 0x000080 },
997 { "Olive", 0x808000 },
998 { "Purple", 0x800080 },
1000 { "Silver", 0xC0C0C0 },
1001 { "Teal", 0x008080 },
1002 { "White", 0xFFFFFF },
1003 { "Yellow", 0xFFFF00 },
1006 { "AliceBlue", 0xF0F8FF },
1007 { "AntiqueWhite", 0xFAEBD7 },
1008 { "Aqua", 0x00FFFF },
1009 { "Aquamarine", 0x7FFFD4 },
1010 { "Azure", 0xF0FFFF },
1011 { "Beige", 0xF5F5DC },
1012 { "Bisque", 0xFFE4C4 },
1013 { "Black", 0x000000 },
1014 { "BlanchedAlmond", 0xFFEBCD },
1015 { "Blue", 0x0000FF },
1016 { "BlueViolet", 0x8A2BE2 },
1017 { "Brown", 0xA52A2A },
1018 { "BurlyWood", 0xDEB887 },
1019 { "CadetBlue", 0x5F9EA0 },
1020 { "Chartreuse", 0x7FFF00 },
1021 { "Chocolate", 0xD2691E },
1022 { "Coral", 0xFF7F50 },
1023 { "CornflowerBlue", 0x6495ED },
1024 { "Cornsilk", 0xFFF8DC },
1025 { "Crimson", 0xDC143C },
1026 { "Cyan", 0x00FFFF },
1027 { "DarkBlue", 0x00008B },
1028 { "DarkCyan", 0x008B8B },
1029 { "DarkGoldenRod", 0xB8860B },
1030 { "DarkGray", 0xA9A9A9 },
1031 { "DarkGrey", 0xA9A9A9 },
1032 { "DarkGreen", 0x006400 },
1033 { "DarkKhaki", 0xBDB76B },
1034 { "DarkMagenta", 0x8B008B },
1035 { "DarkOliveGreen", 0x556B2F },
1036 { "Darkorange", 0xFF8C00 },
1037 { "DarkOrchid", 0x9932CC },
1038 { "DarkRed", 0x8B0000 },
1039 { "DarkSalmon", 0xE9967A },
1040 { "DarkSeaGreen", 0x8FBC8F },
1041 { "DarkSlateBlue", 0x483D8B },
1042 { "DarkSlateGray", 0x2F4F4F },
1043 { "DarkSlateGrey", 0x2F4F4F },
1044 { "DarkTurquoise", 0x00CED1 },
1045 { "DarkViolet", 0x9400D3 },
1046 { "DeepPink", 0xFF1493 },
1047 { "DeepSkyBlue", 0x00BFFF },
1048 { "DimGray", 0x696969 },
1049 { "DimGrey", 0x696969 },
1050 { "DodgerBlue", 0x1E90FF },
1051 { "FireBrick", 0xB22222 },
1052 { "FloralWhite", 0xFFFAF0 },
1053 { "ForestGreen", 0x228B22 },
1054 { "Fuchsia", 0xFF00FF },
1055 { "Gainsboro", 0xDCDCDC },
1056 { "GhostWhite", 0xF8F8FF },
1057 { "Gold", 0xFFD700 },
1058 { "GoldenRod", 0xDAA520 },
1059 { "Gray", 0x808080 },
1060 { "Grey", 0x808080 },
1061 { "Green", 0x008000 },
1062 { "GreenYellow", 0xADFF2F },
1063 { "HoneyDew", 0xF0FFF0 },
1064 { "HotPink", 0xFF69B4 },
1065 { "IndianRed", 0xCD5C5C },
1066 { "Indigo", 0x4B0082 },
1067 { "Ivory", 0xFFFFF0 },
1068 { "Khaki", 0xF0E68C },
1069 { "Lavender", 0xE6E6FA },
1070 { "LavenderBlush", 0xFFF0F5 },
1071 { "LawnGreen", 0x7CFC00 },
1072 { "LemonChiffon", 0xFFFACD },
1073 { "LightBlue", 0xADD8E6 },
1074 { "LightCoral", 0xF08080 },
1075 { "LightCyan", 0xE0FFFF },
1076 { "LightGoldenRodYellow", 0xFAFAD2 },
1077 { "LightGray", 0xD3D3D3 },
1078 { "LightGrey", 0xD3D3D3 },
1079 { "LightGreen", 0x90EE90 },
1080 { "LightPink", 0xFFB6C1 },
1081 { "LightSalmon", 0xFFA07A },
1082 { "LightSeaGreen", 0x20B2AA },
1083 { "LightSkyBlue", 0x87CEFA },
1084 { "LightSlateGray", 0x778899 },
1085 { "LightSlateGrey", 0x778899 },
1086 { "LightSteelBlue", 0xB0C4DE },
1087 { "LightYellow", 0xFFFFE0 },
1088 { "Lime", 0x00FF00 },
1089 { "LimeGreen", 0x32CD32 },
1090 { "Linen", 0xFAF0E6 },
1091 { "Magenta", 0xFF00FF },
1092 { "Maroon", 0x800000 },
1093 { "MediumAquaMarine", 0x66CDAA },
1094 { "MediumBlue", 0x0000CD },
1095 { "MediumOrchid", 0xBA55D3 },
1096 { "MediumPurple", 0x9370D8 },
1097 { "MediumSeaGreen", 0x3CB371 },
1098 { "MediumSlateBlue", 0x7B68EE },
1099 { "MediumSpringGreen", 0x00FA9A },
1100 { "MediumTurquoise", 0x48D1CC },
1101 { "MediumVioletRed", 0xC71585 },
1102 { "MidnightBlue", 0x191970 },
1103 { "MintCream", 0xF5FFFA },
1104 { "MistyRose", 0xFFE4E1 },
1105 { "Moccasin", 0xFFE4B5 },
1106 { "NavajoWhite", 0xFFDEAD },
1107 { "Navy", 0x000080 },
1108 { "OldLace", 0xFDF5E6 },
1109 { "Olive", 0x808000 },
1110 { "OliveDrab", 0x6B8E23 },
1111 { "Orange", 0xFFA500 },
1112 { "OrangeRed", 0xFF4500 },
1113 { "Orchid", 0xDA70D6 },
1114 { "PaleGoldenRod", 0xEEE8AA },
1115 { "PaleGreen", 0x98FB98 },
1116 { "PaleTurquoise", 0xAFEEEE },
1117 { "PaleVioletRed", 0xD87093 },
1118 { "PapayaWhip", 0xFFEFD5 },
1119 { "PeachPuff", 0xFFDAB9 },
1120 { "Peru", 0xCD853F },
1121 { "Pink", 0xFFC0CB },
1122 { "Plum", 0xDDA0DD },
1123 { "PowderBlue", 0xB0E0E6 },
1124 { "Purple", 0x800080 },
1125 { "Red", 0xFF0000 },
1126 { "RosyBrown", 0xBC8F8F },
1127 { "RoyalBlue", 0x4169E1 },
1128 { "SaddleBrown", 0x8B4513 },
1129 { "Salmon", 0xFA8072 },
1130 { "SandyBrown", 0xF4A460 },
1131 { "SeaGreen", 0x2E8B57 },
1132 { "SeaShell", 0xFFF5EE },
1133 { "Sienna", 0xA0522D },
1134 { "Silver", 0xC0C0C0 },
1135 { "SkyBlue", 0x87CEEB },
1136 { "SlateBlue", 0x6A5ACD },
1137 { "SlateGray", 0x708090 },
1138 { "SlateGrey", 0x708090 },
1139 { "Snow", 0xFFFAFA },
1140 { "SpringGreen", 0x00FF7F },
1141 { "SteelBlue", 0x4682B4 },
1142 { "Tan", 0xD2B48C },
1143 { "Teal", 0x008080 },
1144 { "Thistle", 0xD8BFD8 },
1145 { "Tomato", 0xFF6347 },
1146 { "Turquoise", 0x40E0D0 },
1147 { "Violet", 0xEE82EE },
1148 { "Wheat", 0xF5DEB3 },
1149 { "White", 0xFFFFFF },
1150 { "WhiteSmoke", 0xF5F5F5 },
1151 { "Yellow", 0xFFFF00 },
1152 { "YellowGreen", 0x9ACD32 },
1157 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1158 font_stack_t **p_fonts )
1161 char *psz_fontname = NULL;
1162 uint32_t i_font_color = 0xffffff;
1163 int i_font_alpha = 0;
1164 uint32_t i_karaoke_bg_color = 0x00ffffff;
1165 int i_font_size = 24;
1167 /* Default all attributes to the top font in the stack -- in case not
1168 * all attributes are specified in the sub-font
1170 if( VLC_SUCCESS == PeekFont( p_fonts,
1174 &i_karaoke_bg_color ))
1176 psz_fontname = strdup( psz_fontname );
1177 i_font_size = i_font_size;
1179 i_font_alpha = (i_font_color >> 24) & 0xff;
1180 i_font_color &= 0x00ffffff;
1182 const char *name, *value;
1183 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1185 if( !strcasecmp( "face", name ) )
1187 free( psz_fontname );
1188 psz_fontname = strdup( value );
1190 else if( !strcasecmp( "size", name ) )
1192 if( ( *value == '+' ) || ( *value == '-' ) )
1194 int i_value = atoi( value );
1196 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1197 i_font_size += ( i_value * i_font_size ) / 10;
1198 else if( i_value < -5 )
1199 i_font_size = - i_value;
1200 else if( i_value > 5 )
1201 i_font_size = i_value;
1204 i_font_size = atoi( value );
1206 else if( !strcasecmp( "color", name ) )
1208 if( value[0] == '#' )
1210 i_font_color = strtol( value + 1, NULL, 16 );
1211 i_font_color &= 0x00ffffff;
1215 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1217 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1219 i_font_color = p_html_colors[i].i_value;
1225 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1227 i_font_alpha = strtol( value + 1, NULL, 16 );
1228 i_font_alpha &= 0xff;
1231 rv = PushFont( p_fonts,
1234 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1235 i_karaoke_bg_color );
1237 free( psz_fontname );
1242 /* Turn any multiple-whitespaces into single spaces */
1243 static void HandleWhiteSpace( char *psz_node )
1245 char *s = strpbrk( psz_node, "\t\r\n " );
1248 int i_whitespace = strspn( s, "\t\r\n " );
1250 if( i_whitespace > 1 )
1253 strlen( s ) - i_whitespace + 1 );
1256 s = strpbrk( s, "\t\r\n " );
1261 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1262 font_stack_t **p_fonts,
1265 char *psz_fontname = NULL;
1266 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1267 uint32_t i_karaoke_bg_color = i_font_color;
1268 int i_font_size = p_sys->i_font_size;
1270 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1271 &i_font_color, &i_karaoke_bg_color ) )
1274 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1279 static unsigned SetupText( filter_t *p_filter,
1280 uint32_t *psz_text_out,
1281 text_style_t **pp_styles,
1282 uint32_t *pi_k_dates,
1284 const char *psz_text_in,
1285 text_style_t *p_style,
1288 size_t i_string_length;
1290 size_t i_string_bytes;
1291 #if defined(WORDS_BIGENDIAN)
1292 uint32_t *psz_tmp = ToCharset( "UCS-4BE", psz_text_in, &i_string_bytes );
1294 uint32_t *psz_tmp = ToCharset( "UCS-4LE", psz_text_in, &i_string_bytes );
1298 memcpy( psz_text_out, psz_tmp, i_string_bytes );
1299 i_string_length = i_string_bytes / 4;
1304 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1305 i_string_length = 0;
1308 if( i_string_length > 0 )
1310 for( unsigned i = 0; i < i_string_length; i++ )
1311 pp_styles[i] = p_style;
1315 text_style_Delete( p_style );
1317 if( i_string_length > 0 && pi_k_dates )
1319 for( unsigned i = 0; i < i_string_length; i++ )
1320 pi_k_dates[i] = i_k_date;
1322 return i_string_length;
1325 static int ProcessNodes( filter_t *p_filter,
1327 text_style_t **pp_styles,
1328 uint32_t *pi_k_dates,
1330 xml_reader_t *p_xml_reader,
1331 text_style_t *p_font_style )
1333 int rv = VLC_SUCCESS;
1334 filter_sys_t *p_sys = p_filter->p_sys;
1335 int i_text_length = 0;
1336 font_stack_t *p_fonts = NULL;
1337 uint32_t i_k_date = 0;
1339 int i_style_flags = 0;
1343 rv = PushFont( &p_fonts,
1344 p_font_style->psz_fontname,
1345 p_font_style->i_font_size,
1346 (p_font_style->i_font_color & 0xffffff) |
1347 ((p_font_style->i_font_alpha & 0xff) << 24),
1348 (p_font_style->i_karaoke_background_color & 0xffffff) |
1349 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1351 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1359 rv = PushFont( &p_fonts,
1360 p_sys->psz_fontfamily,
1362 (p_sys->i_font_color & 0xffffff) |
1363 (((255-p_sys->i_font_opacity) & 0xff) << 24),
1368 if( rv != VLC_SUCCESS )
1374 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1378 case XML_READER_ENDELEM:
1379 if( !strcasecmp( "font", node ) )
1380 PopFont( &p_fonts );
1381 else if( !strcasecmp( "b", node ) )
1382 i_style_flags &= ~STYLE_BOLD;
1383 else if( !strcasecmp( "i", node ) )
1384 i_style_flags &= ~STYLE_ITALIC;
1385 else if( !strcasecmp( "u", node ) )
1386 i_style_flags &= ~STYLE_UNDERLINE;
1387 else if( !strcasecmp( "s", node ) )
1388 i_style_flags &= ~STYLE_STRIKEOUT;
1391 case XML_READER_STARTELEM:
1392 if( !strcasecmp( "font", node ) )
1393 HandleFontAttributes( p_xml_reader, &p_fonts );
1394 else if( !strcasecmp( "b", node ) )
1395 i_style_flags |= STYLE_BOLD;
1396 else if( !strcasecmp( "i", node ) )
1397 i_style_flags |= STYLE_ITALIC;
1398 else if( !strcasecmp( "u", node ) )
1399 i_style_flags |= STYLE_UNDERLINE;
1400 else if( !strcasecmp( "s", node ) )
1401 i_style_flags |= STYLE_STRIKEOUT;
1402 else if( !strcasecmp( "br", node ) )
1404 i_text_length += SetupText( p_filter,
1405 &psz_text[i_text_length],
1406 &pp_styles[i_text_length],
1407 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1409 GetStyleFromFontStack( p_sys,
1414 else if( !strcasecmp( "k", node ) )
1417 const char *name, *value;
1418 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1420 if( !strcasecmp( "t", name ) && value )
1421 i_k_date += atoi( value );
1426 case XML_READER_TEXT:
1428 char *psz_node = strdup( node );
1429 if( unlikely(!psz_node) )
1432 HandleWhiteSpace( psz_node );
1433 resolve_xml_special_chars( psz_node );
1435 i_text_length += SetupText( p_filter,
1436 &psz_text[i_text_length],
1437 &pp_styles[i_text_length],
1438 pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
1440 GetStyleFromFontStack( p_sys,
1450 *pi_len = i_text_length;
1452 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1457 static void FreeLine( line_desc_t *p_line )
1459 for( int i = 0; i < p_line->i_character_count; i++ )
1461 line_character_t *ch = &p_line->p_character[i];
1462 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1464 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1467 free( p_line->p_character );
1471 static void FreeLines( line_desc_t *p_lines )
1473 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1475 line_desc_t *p_next = p_line->p_next;
1481 static line_desc_t *NewLine( int i_count )
1483 line_desc_t *p_line = malloc( sizeof(*p_line) );
1488 p_line->p_next = NULL;
1489 p_line->i_width = 0;
1490 p_line->i_base_line = 0;
1491 p_line->i_character_count = 0;
1493 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
1494 if( !p_line->p_character )
1502 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
1504 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1506 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1508 FT_Face p_face = NULL;
1510 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1518 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1519 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1520 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1521 (p_style->i_style_flags & (STYLE_BOLD | STYLE_BOLD)) == i_style_received )
1524 FT_Done_Face( p_face );
1532 static FT_Face LoadFace( filter_t *p_filter,
1533 const text_style_t *p_style )
1535 filter_sys_t *p_sys = p_filter->p_sys;
1537 /* Look for a match amongst our attachments first */
1538 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
1540 /* Load system wide font otheriwse */
1545 #ifdef HAVE_FONTCONFIG
1546 psz_fontfile = FontConfig_Select( NULL,
1547 p_style->psz_fontname,
1548 (p_style->i_style_flags & STYLE_BOLD) != 0,
1549 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1552 #elif defined( WIN32 )
1553 psz_fontfile = Win32_Select( p_filter,
1554 p_style->psz_fontname,
1555 (p_style->i_style_flags & STYLE_BOLD) != 0,
1556 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1560 psz_fontfile = NULL;
1565 if( *psz_fontfile == '\0' )
1568 "We were not able to find a matching font: \"%s\" (%s %s),"
1569 " so using default font",
1570 p_style->psz_fontname,
1571 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1572 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1577 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1580 free( psz_fontfile );
1585 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1587 /* We've loaded a font face which is unhelpful for actually
1588 * rendering text - fallback to the default one.
1590 FT_Done_Face( p_face );
1596 static bool FaceStyleEquals( const text_style_t *p_style1,
1597 const text_style_t *p_style2 )
1599 if( !p_style1 || !p_style2 )
1601 if( p_style1 == p_style2 )
1604 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
1605 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
1606 !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
1609 static int GetGlyph( filter_t *p_filter,
1610 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1611 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1618 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1619 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1621 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1622 return VLC_EGENERIC;
1625 /* Do synthetic styling now that Freetype supports it;
1626 * ie. if the font we have loaded is NOT already in the
1627 * style that the tags want, then switch it on; if they
1628 * are then don't. */
1629 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1630 FT_GlyphSlot_Embolden( p_face->glyph );
1631 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1632 FT_GlyphSlot_Oblique( p_face->glyph );
1635 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1637 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1638 return VLC_EGENERIC;
1641 FT_Glyph outline = NULL;
1642 if( p_filter->p_sys->p_stroker )
1645 FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 );
1646 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1648 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1650 *pp_outline = outline;
1652 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1654 FT_Done_Glyph( glyph );
1655 return VLC_EGENERIC;
1658 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1664 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1666 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1667 if( p_bbox->xMin >= p_bbox->xMax )
1669 p_bbox->xMin = FT_CEIL(p_pen->x);
1670 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1671 glyph_bmp->left = p_bbox->xMin;
1673 if( p_bbox->yMin >= p_bbox->yMax )
1675 p_bbox->yMax = FT_CEIL(p_pen->y);
1676 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1677 glyph_bmp->top = p_bbox->yMax;
1681 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1683 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1684 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1685 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1686 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1689 static int ProcessLines( filter_t *p_filter,
1690 line_desc_t **pp_lines,
1692 int *pi_max_face_height,
1695 text_style_t **pp_styles,
1696 uint32_t *pi_k_dates,
1699 filter_sys_t *p_sys = p_filter->p_sys;
1700 uint32_t *p_fribidi_string = NULL;
1701 text_style_t **pp_fribidi_styles = NULL;
1702 int *p_new_positions = NULL;
1704 #if defined(HAVE_FRIBIDI)
1706 int *p_old_positions;
1707 int start_pos, pos = 0;
1709 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1711 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1712 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1713 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1715 if( ! pp_fribidi_styles ||
1716 ! p_fribidi_string ||
1717 ! p_old_positions ||
1720 free( p_old_positions );
1721 free( p_new_positions );
1722 free( p_fribidi_string );
1723 free( pp_fribidi_styles );
1727 /* Do bidi conversion line-by-line */
1730 while(pos < i_len) {
1731 if (psz_text[pos] != '\n')
1733 p_fribidi_string[pos] = psz_text[pos];
1734 pp_fribidi_styles[pos] = pp_styles[pos];
1735 p_new_positions[pos] = pos;
1739 while(pos < i_len) {
1740 if (psz_text[pos] == '\n')
1744 if (pos > start_pos)
1746 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1747 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1749 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1751 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1752 pos - start_pos, &base_dir,
1753 (FriBidiChar*)p_fribidi_string + start_pos,
1754 p_new_positions + start_pos,
1757 for( int j = start_pos; j < pos; j++ )
1759 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1760 p_new_positions[ j ] += start_pos;
1764 p_fribidi_string[ i_len ] = 0;
1765 free( p_old_positions );
1767 pp_styles = pp_fribidi_styles;
1768 psz_text = p_fribidi_string;
1771 /* Work out the karaoke */
1772 uint8_t *pi_karaoke_bar = NULL;
1775 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1776 if( pi_karaoke_bar )
1778 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1779 for( int i = 0; i < i_len; i++ )
1781 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1782 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1786 free( p_new_positions );
1788 *pi_max_face_height = 0;
1790 line_desc_t **pp_line_next = pp_lines;
1798 int i_face_height_previous = 0;
1799 int i_base_line = 0;
1800 const text_style_t *p_previous_style = NULL;
1801 FT_Face p_face = NULL;
1802 for( int i_start = 0; i_start < i_len; )
1804 /* Compute the length of the current text line */
1806 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
1809 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1810 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
1811 int i_index = i_start;
1816 int i_face_height = 0;
1817 FT_BBox line_bbox = {
1823 int i_ul_offset = 0;
1824 int i_ul_thickness = 0;
1833 break_point_t break_point;
1834 break_point_t break_point_fallback;
1836 #define SAVE_BP(dst) do { \
1837 dst.i_index = i_index; \
1839 dst.line_bbox = line_bbox; \
1840 dst.i_face_height = i_face_height; \
1841 dst.i_ul_offset = i_ul_offset; \
1842 dst.i_ul_thickness = i_ul_thickness; \
1845 SAVE_BP( break_point );
1846 SAVE_BP( break_point_fallback );
1848 while( i_index < i_start + i_length )
1850 /* Split by common FT_Face + Size */
1851 const text_style_t *p_current_style = pp_styles[i_index];
1852 int i_part_length = 0;
1853 while( i_index + i_part_length < i_start + i_length )
1855 const text_style_t *p_style = pp_styles[i_index + i_part_length];
1856 if( !FaceStyleEquals( p_style, p_current_style ) ||
1857 p_style->i_font_size != p_current_style->i_font_size )
1862 /* (Re)load/reconfigure the face if needed */
1863 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
1866 FT_Done_Face( p_face );
1867 p_previous_style = NULL;
1869 p_face = LoadFace( p_filter, p_current_style );
1871 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
1872 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
1874 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
1875 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
1876 if( p_sys->p_stroker )
1878 int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
1879 FT_Stroker_Set( p_sys->p_stroker,
1881 FT_STROKER_LINECAP_ROUND,
1882 FT_STROKER_LINEJOIN_ROUND, 0 );
1885 p_previous_style = p_current_style;
1887 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
1888 p_current_face->size->metrics.y_scale)));
1890 /* Render the part */
1891 bool b_break_line = false;
1892 int i_glyph_last = 0;
1893 while( i_part_length > 0 )
1895 const text_style_t *p_glyph_style = pp_styles[i_index];
1896 uint32_t character = psz_text[i_index];
1897 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
1899 /* Get kerning vector */
1900 FT_Vector kerning = { .x = 0, .y = 0 };
1901 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
1902 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
1904 /* Get the glyph bitmap and its bounding box and all the associated properties */
1905 FT_Vector pen_new = {
1906 .x = pen.x + kerning.x,
1907 .y = pen.y + kerning.y,
1912 FT_BBox outline_bbox;
1913 if( GetGlyph( p_filter,
1914 &glyph, &glyph_bbox,
1915 &outline, &outline_bbox,
1916 p_current_face, i_glyph_index, p_glyph_style->i_style_flags, &pen_new ) )
1919 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
1921 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
1922 /* FIXME and what about outline */
1924 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
1925 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
1926 (p_glyph_style->i_karaoke_background_alpha << 24))
1927 : (p_glyph_style->i_font_color |
1928 (p_glyph_style->i_font_alpha << 24));
1929 int i_line_offset = 0;
1930 int i_line_thickness = 0;
1931 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1933 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
1934 p_current_face->size->metrics.y_scale)) );
1936 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
1937 p_current_face->size->metrics.y_scale)) );
1939 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
1941 /* Move the baseline to make it strikethrough instead of
1942 * underline. That means that strikethrough takes precedence
1944 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
1945 p_current_face->size->metrics.y_scale)) );
1947 else if( i_line_thickness > 0 )
1949 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
1951 /* The real underline thickness and position are
1952 * updated once the whole line has been parsed */
1953 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
1954 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
1955 i_line_thickness = -1;
1958 FT_BBox line_bbox_new = line_bbox;
1959 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
1961 BBoxEnlarge( &line_bbox_new, &outline_bbox );
1963 b_break_line = i_index > i_start &&
1964 line_bbox_new.xMax - line_bbox_new.xMin >= p_filter->fmt_out.video.i_visible_width;
1967 FT_Done_Glyph( glyph );
1969 FT_Done_Glyph( outline );
1971 break_point_t *p_bp = NULL;
1972 if( break_point.i_index > i_start )
1973 p_bp = &break_point;
1974 else if( break_point_fallback.i_index > i_start )
1975 p_bp = &break_point_fallback;
1979 msg_Dbg( p_filter, "Breaking line");
1980 for( int i = p_bp->i_index; i < i_index; i++ )
1982 line_character_t *ch = &p_line->p_character[i - i_start];
1983 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1985 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1987 p_line->i_character_count = p_bp->i_index - i_start;
1989 i_index = p_bp->i_index;
1991 line_bbox = p_bp->line_bbox;
1992 i_face_height = p_bp->i_face_height;
1993 i_ul_offset = p_bp->i_ul_offset;
1994 i_ul_thickness = p_bp->i_ul_thickness;
1998 msg_Err( p_filter, "Breaking unbreakable line");
2003 assert( p_line->i_character_count == i_index - i_start);
2004 p_line->p_character[p_line->i_character_count++] = (line_character_t){
2005 .p_glyph = (FT_BitmapGlyph)glyph,
2006 .p_outline = (FT_BitmapGlyph)outline,
2008 .i_line_offset = i_line_offset,
2009 .i_line_thickness = i_line_thickness,
2012 pen.x = pen_new.x + p_current_face->glyph->advance.x;
2013 pen.y = pen_new.y + p_current_face->glyph->advance.y;
2014 line_bbox = line_bbox_new;
2016 i_glyph_last = i_glyph_index;
2020 if( character == ' ' || character == '\t' )
2021 SAVE_BP( break_point );
2022 else if( character == 160 )
2023 SAVE_BP( break_point_fallback );
2029 /* Update our baseline */
2030 if( i_face_height_previous > 0 )
2031 i_base_line += __MAX(i_face_height, i_face_height_previous);
2032 i_face_height_previous = i_face_height;
2034 /* Update the line bbox with the actual base line */
2035 if (line_bbox.yMax > line_bbox.yMin) {
2036 line_bbox.yMin -= i_base_line;
2037 line_bbox.yMax -= i_base_line;
2039 BBoxEnlarge( &bbox, &line_bbox );
2041 /* Terminate and append the line */
2044 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
2045 p_line->i_base_line = i_base_line;
2046 if( i_ul_thickness > 0 )
2048 for( int i = 0; i < p_line->i_character_count; i++ )
2050 line_character_t *ch = &p_line->p_character[i];
2051 if( ch->i_line_thickness < 0 )
2053 ch->i_line_offset = i_ul_offset;
2054 ch->i_line_thickness = i_ul_thickness;
2059 *pp_line_next = p_line;
2060 pp_line_next = &p_line->p_next;
2063 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
2065 /* Skip what we have rendered and the line delimitor if present */
2067 if( i_start < i_len && psz_text[i_start] == '\n' )
2070 if( bbox.yMax - bbox.yMin >= p_filter->fmt_out.video.i_visible_height )
2072 msg_Err( p_filter, "Truncated too high subtitle" );
2077 FT_Done_Face( p_face );
2079 free( pp_fribidi_styles );
2080 free( p_fribidi_string );
2081 free( pi_karaoke_bar );
2088 * This function renders a text subpicture region into another one.
2089 * It also calculates the size needed for this string, and renders the
2090 * needed glyphs into memory. It is used as pf_add_string callback in
2091 * the vout method by this module
2093 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2094 subpicture_region_t *p_region_in, bool b_html )
2096 filter_sys_t *p_sys = p_filter->p_sys;
2099 return VLC_EGENERIC;
2100 if( b_html && !p_region_in->psz_html )
2101 return VLC_EGENERIC;
2102 if( !b_html && !p_region_in->psz_text )
2103 return VLC_EGENERIC;
2105 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
2106 : p_region_in->psz_text );
2108 uint32_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
2109 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
2110 if( !psz_text || !pp_styles )
2114 return VLC_EGENERIC;
2117 /* Reset the default fontsize in case screen metrics have changed */
2118 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2121 int rv = VLC_SUCCESS;
2122 int i_text_length = 0;
2124 int i_max_face_height;
2125 line_desc_t *p_lines = NULL;
2127 uint32_t *pi_k_durations = NULL;
2132 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2133 (uint8_t *) p_region_in->psz_html,
2134 strlen( p_region_in->psz_html ),
2136 if( unlikely(p_sub == NULL) )
2139 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2141 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2143 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2144 p_filter->p_sys->p_xml = p_xml_reader;
2151 /* Look for Root Node */
2154 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2156 if( strcasecmp( "karaoke", node ) == 0 )
2158 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
2160 else if( strcasecmp( "text", node ) != 0 )
2162 /* Only text and karaoke tags are supported */
2163 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2170 msg_Err( p_filter, "Malformed HTML subtitle" );
2176 rv = ProcessNodes( p_filter,
2177 psz_text, pp_styles, pi_k_durations, &i_text_length,
2178 p_xml_reader, p_region_in->p_style );
2182 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2184 stream_Delete( p_sub );
2189 text_style_t *p_style;
2190 if( p_region_in->p_style )
2191 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2192 p_region_in->p_style->i_font_size,
2193 (p_region_in->p_style->i_font_color & 0xffffff) |
2194 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2196 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2201 p_style = CreateStyle( p_sys->psz_fontfamily,
2203 (p_sys->i_font_color & 0xffffff) |
2204 (((255-p_sys->i_font_opacity) & 0xff) << 24),
2207 i_text_length = SetupText( p_filter,
2211 p_region_in->psz_text, p_style, 0 );
2214 if( !rv && i_text_length > 0 )
2216 rv = ProcessLines( p_filter,
2217 &p_lines, &bbox, &i_max_face_height,
2218 psz_text, pp_styles, pi_k_durations, i_text_length );
2221 p_region_out->i_x = p_region_in->i_x;
2222 p_region_out->i_y = p_region_in->i_y;
2224 /* Don't attempt to render text that couldn't be layed out
2226 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
2228 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2229 RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
2231 RenderYUVA( p_filter, p_region_out,
2234 p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0 );
2236 /* With karaoke, we're going to have to render the text a number
2237 * of times to show the progress marker on the text.
2239 if( pi_k_durations )
2240 var_SetBool( p_filter, "text-rerender", true );
2243 FreeLines( p_lines );
2246 for( int i = 0; i < i_text_length; i++ )
2248 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
2249 text_style_Delete( pp_styles[i] );
2252 free( pi_k_durations );
2257 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2258 subpicture_region_t *p_region_in )
2260 return RenderCommon( p_filter, p_region_out, p_region_in, false );
2265 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2266 subpicture_region_t *p_region_in )
2268 return RenderCommon( p_filter, p_region_out, p_region_in, true );
2273 /*****************************************************************************
2274 * Create: allocates osd-text video thread output method
2275 *****************************************************************************
2276 * This function allocates and initializes a Clone vout method.
2277 *****************************************************************************/
2278 static int Create( vlc_object_t *p_this )
2280 filter_t *p_filter = (filter_t *)p_this;
2281 filter_sys_t *p_sys;
2282 char *psz_fontfile = NULL;
2283 char *psz_fontfamily = NULL;
2284 int i_error = 0, fontindex = 0;
2286 /* Allocate structure */
2287 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2291 p_sys->psz_fontfamily = NULL;
2293 p_sys->p_xml = NULL;
2296 p_sys->p_library = 0;
2297 p_sys->i_font_size = 0;
2298 p_sys->i_display_height = 0;
2300 var_Create( p_filter, "freetype-rel-fontsize",
2301 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2303 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2304 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2305 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2306 p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
2307 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2308 p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
2310 p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
2311 p_sys->i_background_opacity = __MAX( __MIN( p_sys->i_background_opacity, 255 ), 0 );
2312 p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
2313 p_sys->i_background_color = __MAX( __MIN( p_sys->i_background_color, 0xFFFFFF ), 0 );
2315 p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
2316 p_sys->f_outline_thickness = __MAX( __MIN( p_sys->f_outline_thickness, 0.5 ), 0.0 );
2317 p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
2318 p_sys->i_outline_opacity = __MAX( __MIN( p_sys->i_outline_opacity, 255 ), 0 );
2319 p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
2320 p_sys->i_outline_color = __MAX( __MIN( p_sys->i_outline_color, 0xFFFFFF ), 0 );
2323 /* Get Windows Font folder */
2324 wchar_t wdir[MAX_PATH];
2325 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2327 GetWindowsDirectoryW( wdir, MAX_PATH );
2328 wcscat( wdir, L"\\fonts" );
2330 p_sys->psz_win_fonts_path = FromWide( wdir );
2333 /* Set default psz_fontfamily */
2334 if( !psz_fontfamily || !*psz_fontfamily )
2336 free( psz_fontfamily );
2338 psz_fontfamily = strdup( DEFAULT_FAMILY );
2341 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2344 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2346 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2350 /* Set the current font file */
2351 p_sys->psz_fontfamily = psz_fontfamily;
2353 #ifdef HAVE_FONTCONFIG
2354 FontConfig_BuildCache( p_filter );
2357 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2358 p_sys->i_default_font_size, &fontindex );
2359 #elif defined(WIN32)
2360 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2361 p_sys->i_default_font_size, &fontindex );
2364 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2366 /* If nothing is found, use the default family */
2368 psz_fontfile = strdup( psz_fontfamily );
2370 #else /* !HAVE_STYLES */
2371 /* Use the default file */
2372 psz_fontfile = psz_fontfamily;
2376 i_error = FT_Init_FreeType( &p_sys->p_library );
2379 msg_Err( p_filter, "couldn't initialize freetype" );
2383 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2384 fontindex, &p_sys->p_face );
2386 if( i_error == FT_Err_Unknown_File_Format )
2388 msg_Err( p_filter, "file %s have unknown format",
2389 psz_fontfile ? psz_fontfile : "(null)" );
2394 msg_Err( p_filter, "failed to load font file %s",
2395 psz_fontfile ? psz_fontfile : "(null)" );
2399 free( psz_fontfile );
2402 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2405 msg_Err( p_filter, "font has no unicode translation table" );
2409 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2411 p_sys->p_stroker = NULL;
2412 if( p_sys->f_outline_thickness > 0.001 )
2414 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
2416 msg_Err( p_filter, "Failed to create stroker for outlining" );
2419 p_sys->pp_font_attachments = NULL;
2420 p_sys->i_font_attachments = 0;
2422 p_filter->pf_render_text = RenderText;
2424 p_filter->pf_render_html = RenderHtml;
2426 p_filter->pf_render_html = NULL;
2429 LoadFontsFromAttachments( p_filter );
2434 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2435 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2437 free( psz_fontfile );
2439 free( psz_fontfamily );
2441 return VLC_EGENERIC;
2444 /*****************************************************************************
2445 * Destroy: destroy Clone video thread output method
2446 *****************************************************************************
2447 * Clean up all data and library connections
2448 *****************************************************************************/
2449 static void Destroy( vlc_object_t *p_this )
2451 filter_t *p_filter = (filter_t *)p_this;
2452 filter_sys_t *p_sys = p_filter->p_sys;
2454 if( p_sys->pp_font_attachments )
2456 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2457 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2459 free( p_sys->pp_font_attachments );
2463 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2465 free( p_sys->psz_fontfamily );
2467 /* FcFini asserts calling the subfunction FcCacheFini()
2468 * even if no other library functions have been made since FcInit(),
2469 * so don't call it. */
2471 if( p_sys->p_stroker )
2472 FT_Stroker_Done( p_sys->p_stroker );
2473 FT_Done_Face( p_sys->p_face );
2474 FT_Done_FreeType( p_sys->p_library );