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*/
45 #include <vlc_memory.h> /* realloc_or_free */
49 # define DEFAULT_FONT_FILE "/Library/Fonts/Arial Black.ttf"
50 # define DEFAULT_FAMILY "Arial Black"
51 #elif defined( WIN32 )
52 # define DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
53 # define DEFAULT_FAMILY "Arial"
54 #elif defined( HAVE_MAEMO )
55 # define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf"
56 # define DEFAULT_FAMILY "Nokia Sans Bold"
58 # define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
59 # define DEFAULT_FAMILY "Serif Bold"
63 #include <freetype/ftsynth.h>
64 #include FT_FREETYPE_H
66 #define FT_FLOOR(X) ((X & -64) >> 6)
67 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
69 #define FT_MulFix(v, s) (((v)*(s))>>16)
73 #if defined(HAVE_FRIBIDI)
74 # include <fribidi/fribidi.h>
82 # undef HAVE_FONTCONFIG
86 #ifdef HAVE_FONTCONFIG
87 # include <fontconfig/fontconfig.h>
93 /*****************************************************************************
95 *****************************************************************************/
96 static int Create ( vlc_object_t * );
97 static void Destroy( vlc_object_t * );
99 #define FONT_TEXT N_("Font")
101 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
102 #define FONT_LONGTEXT N_("Font file for the font you want to use")
104 #define FONTSIZE_TEXT N_("Font size in pixels")
105 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
106 "that will be rendered on the video. " \
107 "If set to something different than 0 this option will override the " \
108 "relative font size." )
109 #define OPACITY_TEXT N_("Opacity")
110 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
111 "text that will be rendered on the video. 0 = transparent, " \
112 "255 = totally opaque. " )
113 #define COLOR_TEXT N_("Text default color")
114 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
115 "the video. This must be an hexadecimal (like HTML colors). The first two "\
116 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
117 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
118 #define FONTSIZER_TEXT N_("Relative font size")
119 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
120 "fonts that will be rendered on the video. If absolute font size is set, "\
121 "relative size will be overridden." )
123 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
124 static const char *const ppsz_sizes_text[] = {
125 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
126 #define YUVP_TEXT N_("Use YUVP renderer")
127 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
128 "This option is only needed if you want to encode into DVB subtitles" )
129 #define EFFECT_TEXT N_("Font Effect")
130 #define EFFECT_LONGTEXT N_("It is possible to apply effects to the rendered " \
131 "text to improve its readability." )
133 enum { EFFECT_BACKGROUND = 1,
135 EFFECT_OUTLINE_FAT = 3,
137 static int const pi_effects[] = { EFFECT_BACKGROUND, EFFECT_OUTLINE, EFFECT_OUTLINE_FAT };
138 static const char *const ppsz_effects_text[] = {
139 N_("Background"),N_("Outline"), N_("Fat Outline") };
141 static const int pi_color_values[] = {
142 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
143 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
144 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
146 static const char *const ppsz_color_descriptions[] = {
147 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
148 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
149 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
152 set_shortname( N_("Text renderer"))
153 set_description( N_("Freetype2 font renderer") )
154 set_category( CAT_VIDEO )
155 set_subcategory( SUBCAT_VIDEO_SUBPIC )
158 add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
160 add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
163 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
164 FONTSIZE_LONGTEXT, true )
167 /* opacity valid on 0..255, with default 255 = fully opaque */
168 add_integer_with_range( "freetype-opacity", 255, 0, 255,
169 OPACITY_TEXT, OPACITY_LONGTEXT, false )
172 /* hook to the color values list, with default 0x00ffffff = white */
173 add_integer( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
174 COLOR_LONGTEXT, false )
175 change_integer_list( pi_color_values, ppsz_color_descriptions )
178 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
179 FONTSIZER_LONGTEXT, false )
180 change_integer_list( pi_sizes, ppsz_sizes_text )
183 add_integer( "freetype-effect", 2, EFFECT_TEXT,
184 EFFECT_LONGTEXT, false )
185 change_integer_list( pi_effects, ppsz_effects_text )
188 add_bool( "freetype-yuvp", false, YUVP_TEXT,
189 YUVP_LONGTEXT, true )
190 set_capability( "text renderer", 100 )
191 add_shortcut( "text" )
192 set_callbacks( Create, Destroy )
196 /*****************************************************************************
198 *****************************************************************************/
200 /* The RenderText call maps to pf_render_string, defined in vlc_filter.h */
201 static int RenderText( filter_t *, subpicture_region_t *,
202 subpicture_region_t * );
205 static int RenderHtml( filter_t *, subpicture_region_t *,
206 subpicture_region_t * );
208 #ifdef HAVE_FONTCONFIG
209 static void FontConfig_BuildCache( filter_t * );
210 static char* FontConfig_Select( FcConfig *, const char *,
211 bool, bool, int, int * );
214 static char* Win32_Select( filter_t *, const char *,
215 bool, bool, int, int * );
218 static int LoadFontsFromAttachments( filter_t *p_filter );
220 static int GetFontSize( filter_t *p_filter );
221 static int SetFontSize( filter_t *, int );
222 static void YUVFromRGB( uint32_t i_argb,
223 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v );
225 typedef struct line_desc_t line_desc_t;
228 /** NULL-terminated list of glyphs making the string */
229 FT_BitmapGlyph *pp_glyphs;
230 /** list of relative positions for the glyphs */
231 FT_Vector *p_glyph_pos;
232 /** list of RGB information for styled text
233 * -- if the rendering mode supports it (RenderYUVA) and
234 * b_new_color_mode is set, then it becomes possible to
235 * have multicoloured text within the subtitles. */
238 uint8_t *p_fg_bg_ratio; /* 0x00=100% FG --> 0x7F=100% BG */
239 bool b_new_color_mode;
240 /** underline information -- only supplied if text should be underlined */
241 int *pi_underline_offset;
242 uint16_t *pi_underline_thickness;
246 int i_red, i_green, i_blue;
251 static line_desc_t *NewLine( int );
256 uint32_t i_font_color; /* ARGB */
257 uint32_t i_karaoke_bg_color; /* ARGB */
265 typedef struct font_stack_t font_stack_t;
270 uint32_t i_color; /* ARGB */
271 uint32_t i_karaoke_bg_color; /* ARGB */
273 font_stack_t *p_next;
276 static int RenderYUVP( filter_t *, subpicture_region_t *, line_desc_t *, int, int);
277 static void FreeLines( line_desc_t * );
278 static void FreeLine( line_desc_t * );
280 /*****************************************************************************
281 * filter_sys_t: freetype local data
282 *****************************************************************************
283 * This structure is part of the video output thread descriptor.
284 * It describes the freetype specific properties of an output thread.
285 *****************************************************************************/
288 FT_Library p_library; /* handle to library */
289 FT_Face p_face; /* handle to face object */
291 uint8_t i_font_opacity;
296 int i_default_font_size;
297 int i_display_height;
298 char* psz_fontfamily;
302 char* psz_win_fonts_path;
306 input_attachment_t **pp_font_attachments;
307 int i_font_attachments;
311 /*****************************************************************************
312 * Create: allocates osd-text video thread output method
313 *****************************************************************************
314 * This function allocates and initializes a Clone vout method.
315 *****************************************************************************/
316 static int Create( vlc_object_t *p_this )
318 filter_t *p_filter = (filter_t *)p_this;
320 char *psz_fontfile = NULL;
321 char *psz_fontfamily = NULL;
322 int i_error = 0, fontindex = 0;
324 /* Allocate structure */
325 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
329 p_sys->psz_fontfamily = NULL;
334 p_sys->p_library = 0;
335 p_sys->i_font_size = 0;
336 p_sys->i_display_height = 0;
338 var_Create( p_filter, "freetype-rel-fontsize",
339 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
341 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
342 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
343 p_sys->i_effect = var_InheritInteger( p_filter, "freetype-effect" );
344 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
345 p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
346 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
347 p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
350 /* Get Windows Font folder */
351 wchar_t wdir[MAX_PATH];
352 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
354 GetWindowsDirectoryW( wdir, MAX_PATH );
355 wcscat( wdir, L"\\fonts" );
357 p_sys->psz_win_fonts_path = FromWide( wdir );
360 /* Set default psz_fontfamily */
361 if( !psz_fontfamily || !*psz_fontfamily )
363 free( psz_fontfamily );
365 psz_fontfamily = strdup( DEFAULT_FAMILY );
368 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
371 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
373 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
377 /* Set the current font file */
378 p_sys->psz_fontfamily = psz_fontfamily;
380 #ifdef HAVE_FONTCONFIG
381 FontConfig_BuildCache( p_filter );
384 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
385 p_sys->i_default_font_size, &fontindex );
387 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
388 p_sys->i_default_font_size, &fontindex );
391 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
393 /* If nothing is found, use the default family */
395 psz_fontfile = strdup( psz_fontfamily );
397 #else /* !HAVE_STYLES */
398 /* Use the default file */
399 psz_fontfile = psz_fontfamily;
403 i_error = FT_Init_FreeType( &p_sys->p_library );
406 msg_Err( p_filter, "couldn't initialize freetype" );
410 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
411 fontindex, &p_sys->p_face );
413 if( i_error == FT_Err_Unknown_File_Format )
415 msg_Err( p_filter, "file %s have unknown format",
416 psz_fontfile ? psz_fontfile : "(null)" );
421 msg_Err( p_filter, "failed to load font file %s",
422 psz_fontfile ? psz_fontfile : "(null)" );
426 free( psz_fontfile );
429 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
432 msg_Err( p_filter, "font has no unicode translation table" );
436 p_sys->i_use_kerning = FT_HAS_KERNING( p_sys->p_face );
438 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
441 p_sys->pp_font_attachments = NULL;
442 p_sys->i_font_attachments = 0;
444 p_filter->pf_render_text = RenderText;
446 p_filter->pf_render_html = RenderHtml;
448 p_filter->pf_render_html = NULL;
451 LoadFontsFromAttachments( p_filter );
456 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
457 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
459 free( psz_fontfile );
461 free( psz_fontfamily );
466 /*****************************************************************************
467 * Destroy: destroy Clone video thread output method
468 *****************************************************************************
469 * Clean up all data and library connections
470 *****************************************************************************/
471 static void Destroy( vlc_object_t *p_this )
473 filter_t *p_filter = (filter_t *)p_this;
474 filter_sys_t *p_sys = p_filter->p_sys;
476 if( p_sys->pp_font_attachments )
478 for( int k = 0; k < p_sys->i_font_attachments; k++ )
479 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
481 free( p_sys->pp_font_attachments );
485 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
487 free( p_sys->psz_fontfamily );
489 /* FcFini asserts calling the subfunction FcCacheFini()
490 * even if no other library functions have been made since FcInit(),
491 * so don't call it. */
493 FT_Done_Face( p_sys->p_face );
494 FT_Done_FreeType( p_sys->p_library );
498 /*****************************************************************************
499 * Make any TTF/OTF fonts present in the attachments of the media file
500 * and store them for later use by the FreeType Engine
501 *****************************************************************************/
502 static int LoadFontsFromAttachments( filter_t *p_filter )
504 filter_sys_t *p_sys = p_filter->p_sys;
505 input_attachment_t **pp_attachments;
506 int i_attachments_cnt;
508 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
511 p_sys->i_font_attachments = 0;
512 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof( input_attachment_t * ));
513 if( !p_sys->pp_font_attachments )
516 for( int k = 0; k < i_attachments_cnt; k++ )
518 input_attachment_t *p_attach = pp_attachments[k];
520 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
521 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
522 p_attach->i_data > 0 && p_attach->p_data )
524 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
528 vlc_input_attachment_Delete( p_attach );
531 free( pp_attachments );
536 /*****************************************************************************
537 * RenderYUVP: place string in picture
538 *****************************************************************************
539 * This function merges the previously rendered freetype glyphs into a picture
540 *****************************************************************************/
541 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
542 line_desc_t *p_line, int i_width, int i_height )
544 VLC_UNUSED(p_filter);
545 static const uint8_t pi_gamma[16] =
546 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
547 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
551 int i, x, y, i_pitch;
552 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
554 /* Create a new subpicture region */
555 memset( &fmt, 0, sizeof(video_format_t) );
556 fmt.i_chroma = VLC_CODEC_YUVP;
557 fmt.i_width = fmt.i_visible_width = i_width + 4;
558 fmt.i_height = fmt.i_visible_height = i_height + 4;
559 if( p_region->fmt.i_visible_width > 0 )
560 fmt.i_visible_width = p_region->fmt.i_visible_width;
561 if( p_region->fmt.i_visible_height > 0 )
562 fmt.i_visible_height = p_region->fmt.i_visible_height;
563 fmt.i_x_offset = fmt.i_y_offset = 0;
567 assert( !p_region->p_picture );
568 p_region->p_picture = picture_NewFromFormat( &fmt );
569 if( !p_region->p_picture )
571 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
574 /* Calculate text color components */
575 YUVFromRGB( (p_line->i_red << 16) |
576 (p_line->i_green << 8) |
581 fmt.p_palette->i_entries = 16;
582 for( i = 0; i < 8; i++ )
584 fmt.p_palette->palette[i][0] = 0;
585 fmt.p_palette->palette[i][1] = 0x80;
586 fmt.p_palette->palette[i][2] = 0x80;
587 fmt.p_palette->palette[i][3] = pi_gamma[i];
588 fmt.p_palette->palette[i][3] =
589 (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
591 for( i = 8; i < fmt.p_palette->i_entries; i++ )
593 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
594 fmt.p_palette->palette[i][1] = i_u;
595 fmt.p_palette->palette[i][2] = i_v;
596 fmt.p_palette->palette[i][3] = pi_gamma[i];
597 fmt.p_palette->palette[i][3] =
598 (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
601 p_dst = p_region->p_picture->Y_PIXELS;
602 i_pitch = p_region->p_picture->Y_PITCH;
604 /* Initialize the region pixels */
605 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
607 for( ; p_line != NULL; p_line = p_line->p_next )
609 int i_glyph_tmax = 0;
610 int i_bitmap_offset, i_offset, i_align_offset = 0;
611 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
613 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
614 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
617 if( p_line->i_width < i_width )
619 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
621 i_align_offset = i_width - p_line->i_width;
623 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
625 i_align_offset = ( i_width - p_line->i_width ) / 2;
629 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
631 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
633 i_offset = ( p_line->p_glyph_pos[ i ].y +
634 i_glyph_tmax - p_glyph->top + 2 ) *
635 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 2 +
638 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
640 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
642 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
644 ((int)p_glyph->bitmap.buffer[i_bitmap_offset] + 8)/16;
651 /* Outlining (find something better than nearest neighbour filtering ?) */
654 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
655 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
656 uint8_t left, current;
658 for( y = 1; y < (int)fmt.i_height - 1; y++ )
660 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
661 p_dst += p_region->p_picture->Y_PITCH;
664 for( x = 1; x < (int)fmt.i_width - 1; x++ )
667 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
668 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;
672 memset( p_top, 0, fmt.i_width );
678 static void UnderlineGlyphYUVA( int i_line_thickness, int i_line_offset, bool b_ul_next_char,
679 FT_BitmapGlyph p_this_glyph, FT_Vector *p_this_glyph_pos,
680 FT_BitmapGlyph p_next_glyph, FT_Vector *p_next_glyph_pos,
681 int i_glyph_tmax, int i_align_offset,
682 uint8_t i_y, uint8_t i_u, uint8_t i_v,
683 subpicture_region_t *p_region)
686 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
688 p_dst_y = p_region->p_picture->Y_PIXELS;
689 p_dst_u = p_region->p_picture->U_PIXELS;
690 p_dst_v = p_region->p_picture->V_PIXELS;
691 p_dst_a = p_region->p_picture->A_PIXELS;
692 i_pitch = p_region->p_picture->A_PITCH;
694 int i_offset = ( p_this_glyph_pos->y + i_glyph_tmax + i_line_offset + 3 ) * i_pitch +
695 p_this_glyph_pos->x + p_this_glyph->left + 3 + i_align_offset;
697 for( int y = 0; y < i_line_thickness; y++ )
699 int i_extra = p_this_glyph->bitmap.width;
703 i_extra = (p_next_glyph_pos->x + p_next_glyph->left) -
704 (p_this_glyph_pos->x + p_this_glyph->left);
706 for( int x = 0; x < i_extra; x++ )
710 /* break the underline around the tails of any glyphs which cross it */
711 /* Strikethrough doesn't get broken */
712 for( int z = x - i_line_thickness;
713 z < x + i_line_thickness && b_ok && (i_line_offset >= 0);
716 if( p_next_glyph && ( z >= i_extra ) )
718 int i_row = i_line_offset + p_next_glyph->top + y;
720 if( ( p_next_glyph->bitmap.rows > i_row ) &&
721 p_next_glyph->bitmap.buffer[p_next_glyph->bitmap.width * i_row + z-i_extra] )
726 else if ((z > 0 ) && (z < p_this_glyph->bitmap.width))
728 int i_row = i_line_offset + p_this_glyph->top + y;
730 if( ( p_this_glyph->bitmap.rows > i_row ) &&
731 p_this_glyph->bitmap.buffer[p_this_glyph->bitmap.width * i_row + z] )
740 p_dst_y[i_offset+x] = (i_y * 255) >> 8;
741 p_dst_u[i_offset+x] = i_u;
742 p_dst_v[i_offset+x] = i_v;
743 p_dst_a[i_offset+x] = 255;
750 static void DrawBlack( line_desc_t *p_line, int i_width, subpicture_region_t *p_region, int xoffset, int yoffset )
752 uint8_t *p_dst = p_region->p_picture->A_PIXELS;
753 int i_pitch = p_region->p_picture->A_PITCH;
756 for( ; p_line != NULL; p_line = p_line->p_next )
758 int i_glyph_tmax=0, i = 0;
759 int i_bitmap_offset, i_offset, i_align_offset = 0;
760 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
762 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
763 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
766 if( p_line->i_width < i_width )
768 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
770 i_align_offset = i_width - p_line->i_width;
772 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
774 i_align_offset = ( i_width - p_line->i_width ) / 2;
778 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
780 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
782 i_offset = ( p_line->p_glyph_pos[ i ].y +
783 i_glyph_tmax - p_glyph->top + 3 + yoffset ) *
784 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
785 i_align_offset +xoffset;
787 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
789 for( int x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
791 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
792 if( p_dst[i_offset+x] <
793 ((int)p_glyph->bitmap.buffer[i_bitmap_offset]) )
795 ((int)p_glyph->bitmap.buffer[i_bitmap_offset]);
803 /*****************************************************************************
804 * RenderYUVA: place string in picture
805 *****************************************************************************
806 * This function merges the previously rendered freetype glyphs into a picture
807 *****************************************************************************/
808 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
809 line_desc_t *p_line, int i_width, int i_height )
811 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
813 int i, y, i_pitch, i_alpha;
814 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
816 if( i_width == 0 || i_height == 0 )
819 /* Create a new subpicture region */
820 memset( &fmt, 0, sizeof(video_format_t) );
821 fmt.i_chroma = VLC_CODEC_YUVA;
822 fmt.i_width = fmt.i_visible_width = i_width + 6;
823 fmt.i_height = fmt.i_visible_height = i_height + 6;
824 if( p_region->fmt.i_visible_width > 0 )
825 fmt.i_visible_width = p_region->fmt.i_visible_width;
826 if( p_region->fmt.i_visible_height > 0 )
827 fmt.i_visible_height = p_region->fmt.i_visible_height;
828 fmt.i_x_offset = fmt.i_y_offset = 0;
832 p_region->p_picture = picture_NewFromFormat( &fmt );
833 if( !p_region->p_picture )
837 /* Calculate text color components */
838 YUVFromRGB( (p_line->i_red << 16) |
839 (p_line->i_green << 8) |
842 i_alpha = p_line->i_alpha;
844 p_dst_y = p_region->p_picture->Y_PIXELS;
845 p_dst_u = p_region->p_picture->U_PIXELS;
846 p_dst_v = p_region->p_picture->V_PIXELS;
847 p_dst_a = p_region->p_picture->A_PIXELS;
848 i_pitch = p_region->p_picture->A_PITCH;
850 /* Initialize the region pixels */
851 if( p_filter->p_sys->i_effect != EFFECT_BACKGROUND )
853 memset( p_dst_y, 0x00, i_pitch * p_region->fmt.i_height );
854 memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
855 memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
856 memset( p_dst_a, 0, i_pitch * p_region->fmt.i_height );
860 memset( p_dst_y, 0x0, i_pitch * p_region->fmt.i_height );
861 memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
862 memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
863 memset( p_dst_a, 0x80, i_pitch * p_region->fmt.i_height );
865 if( p_filter->p_sys->i_effect == EFFECT_OUTLINE ||
866 p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
868 DrawBlack( p_line, i_width, p_region, 0, 0);
869 DrawBlack( p_line, i_width, p_region, -1, 0);
870 DrawBlack( p_line, i_width, p_region, 0, -1);
871 DrawBlack( p_line, i_width, p_region, 1, 0);
872 DrawBlack( p_line, i_width, p_region, 0, 1);
875 if( p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
877 DrawBlack( p_line, i_width, p_region, -1, -1);
878 DrawBlack( p_line, i_width, p_region, -1, 1);
879 DrawBlack( p_line, i_width, p_region, 1, -1);
880 DrawBlack( p_line, i_width, p_region, 1, 1);
882 DrawBlack( p_line, i_width, p_region, -2, 0);
883 DrawBlack( p_line, i_width, p_region, 0, -2);
884 DrawBlack( p_line, i_width, p_region, 2, 0);
885 DrawBlack( p_line, i_width, p_region, 0, 2);
887 DrawBlack( p_line, i_width, p_region, -2, -2);
888 DrawBlack( p_line, i_width, p_region, -2, 2);
889 DrawBlack( p_line, i_width, p_region, 2, -2);
890 DrawBlack( p_line, i_width, p_region, 2, 2);
892 DrawBlack( p_line, i_width, p_region, -3, 0);
893 DrawBlack( p_line, i_width, p_region, 0, -3);
894 DrawBlack( p_line, i_width, p_region, 3, 0);
895 DrawBlack( p_line, i_width, p_region, 0, 3);
898 for( ; p_line != NULL; p_line = p_line->p_next )
900 int i_glyph_tmax = 0;
901 int i_bitmap_offset, i_offset, i_align_offset = 0;
902 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
904 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
905 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
908 if( p_line->i_width < i_width )
910 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
912 i_align_offset = i_width - p_line->i_width;
914 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
916 i_align_offset = ( i_width - p_line->i_width ) / 2;
920 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
922 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
924 i_offset = ( p_line->p_glyph_pos[ i ].y +
925 i_glyph_tmax - p_glyph->top + 3 ) *
926 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
929 if( p_line->b_new_color_mode )
931 /* Every glyph can (and in fact must) have its own color */
932 YUVFromRGB( p_line->p_fg_rgb[ i ], &i_y, &i_u, &i_v );
935 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
937 for( int x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
939 uint8_t i_y_local = i_y;
940 uint8_t i_u_local = i_u;
941 uint8_t i_v_local = i_v;
943 if( p_line->p_fg_bg_ratio != 0x00 )
945 int i_split = p_glyph->bitmap.width *
946 p_line->p_fg_bg_ratio[ i ] / 0x7f;
950 YUVFromRGB( p_line->p_bg_rgb[ i ],
951 &i_y_local, &i_u_local, &i_v_local );
955 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
957 p_dst_y[i_offset+x] = ((p_dst_y[i_offset+x] *(255-(int)p_glyph->bitmap.buffer[i_bitmap_offset])) +
958 i_y * ((int)p_glyph->bitmap.buffer[i_bitmap_offset])) >> 8;
960 p_dst_u[i_offset+x] = i_u;
961 p_dst_v[i_offset+x] = i_v;
963 if( p_filter->p_sys->i_effect == EFFECT_BACKGROUND )
964 p_dst_a[i_offset+x] = 0xff;
970 if( p_line->pi_underline_thickness[ i ] )
972 UnderlineGlyphYUVA( p_line->pi_underline_thickness[ i ],
973 p_line->pi_underline_offset[ i ],
974 (p_line->pp_glyphs[i+1] && (p_line->pi_underline_thickness[ i + 1] > 0)),
975 p_line->pp_glyphs[i], &(p_line->p_glyph_pos[i]),
976 p_line->pp_glyphs[i+1], &(p_line->p_glyph_pos[i+1]),
977 i_glyph_tmax, i_align_offset,
984 /* Apply the alpha setting */
985 for( i = 0; i < (int)fmt.i_height * i_pitch; i++ )
986 p_dst_a[i] = p_dst_a[i] * (255 - i_alpha) / 255;
991 static ft_style_t *CreateStyle( char *psz_fontname, int i_font_size,
992 uint32_t i_font_color, uint32_t i_karaoke_bg_color, bool b_bold,
993 bool b_italic, bool b_uline, bool b_through )
995 ft_style_t *p_style = malloc( sizeof( *p_style ));
999 p_style->i_font_size = i_font_size;
1000 p_style->i_font_color = i_font_color;
1001 p_style->i_karaoke_bg_color = i_karaoke_bg_color;
1002 p_style->b_italic = b_italic;
1003 p_style->b_bold = b_bold;
1004 p_style->b_underline = b_uline;
1005 p_style->b_through = b_through;
1007 p_style->psz_fontname = strdup( psz_fontname );
1012 static void DeleteStyle( ft_style_t *p_style )
1017 free( p_style->psz_fontname );
1021 static bool StyleEquals( ft_style_t *s1, ft_style_t *s2 )
1028 if(( s1->i_font_size == s2->i_font_size ) &&
1029 ( s1->i_font_color == s2->i_font_color ) &&
1030 ( s1->b_italic == s2->b_italic ) &&
1031 ( s1->b_through == s2->b_through ) &&
1032 ( s1->b_bold == s2->b_bold ) &&
1033 ( s1->b_underline == s2->b_underline ) &&
1034 ( !strcmp( s1->psz_fontname, s2->psz_fontname )))
1041 static void IconvText( filter_t *p_filter, const char *psz_string,
1042 size_t *i_string_length, uint32_t *psz_unicode )
1044 *i_string_length = 0;
1045 if( psz_unicode == NULL )
1050 #if defined(WORDS_BIGENDIAN)
1051 ToCharset( "UCS-4BE", psz_string, &i_length );
1053 ToCharset( "UCS-4LE", psz_string, &i_length );
1057 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1060 memcpy( psz_unicode, psz_tmp, i_length );
1061 *i_string_length = i_length / 4;
1066 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1067 uint32_t i_color, uint32_t i_karaoke_bg_color )
1069 font_stack_t *p_new;
1072 return VLC_EGENERIC;
1074 p_new = malloc( sizeof( font_stack_t ) );
1078 p_new->p_next = NULL;
1081 p_new->psz_name = strdup( psz_name );
1083 p_new->psz_name = NULL;
1085 p_new->i_size = i_size;
1086 p_new->i_color = i_color;
1087 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1095 font_stack_t *p_last;
1097 for( p_last = *p_font;
1099 p_last = p_last->p_next )
1102 p_last->p_next = p_new;
1107 static int PopFont( font_stack_t **p_font )
1109 font_stack_t *p_last, *p_next_to_last;
1111 if( !p_font || !*p_font )
1112 return VLC_EGENERIC;
1114 p_next_to_last = NULL;
1115 for( p_last = *p_font;
1117 p_last = p_last->p_next )
1119 p_next_to_last = p_last;
1122 if( p_next_to_last )
1123 p_next_to_last->p_next = NULL;
1127 free( p_last->psz_name );
1133 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1134 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1136 font_stack_t *p_last;
1138 if( !p_font || !*p_font )
1139 return VLC_EGENERIC;
1141 for( p_last=*p_font;
1143 p_last=p_last->p_next )
1146 *psz_name = p_last->psz_name;
1147 *i_size = p_last->i_size;
1148 *i_color = p_last->i_color;
1149 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1154 static const struct {
1155 const char *psz_name;
1157 } p_html_colors[] = {
1158 /* Official html colors */
1159 { "Aqua", 0x00FFFF },
1160 { "Black", 0x000000 },
1161 { "Blue", 0x0000FF },
1162 { "Fuchsia", 0xFF00FF },
1163 { "Gray", 0x808080 },
1164 { "Green", 0x008000 },
1165 { "Lime", 0x00FF00 },
1166 { "Maroon", 0x800000 },
1167 { "Navy", 0x000080 },
1168 { "Olive", 0x808000 },
1169 { "Purple", 0x800080 },
1170 { "Red", 0xFF0000 },
1171 { "Silver", 0xC0C0C0 },
1172 { "Teal", 0x008080 },
1173 { "White", 0xFFFFFF },
1174 { "Yellow", 0xFFFF00 },
1177 { "AliceBlue", 0xF0F8FF },
1178 { "AntiqueWhite", 0xFAEBD7 },
1179 { "Aqua", 0x00FFFF },
1180 { "Aquamarine", 0x7FFFD4 },
1181 { "Azure", 0xF0FFFF },
1182 { "Beige", 0xF5F5DC },
1183 { "Bisque", 0xFFE4C4 },
1184 { "Black", 0x000000 },
1185 { "BlanchedAlmond", 0xFFEBCD },
1186 { "Blue", 0x0000FF },
1187 { "BlueViolet", 0x8A2BE2 },
1188 { "Brown", 0xA52A2A },
1189 { "BurlyWood", 0xDEB887 },
1190 { "CadetBlue", 0x5F9EA0 },
1191 { "Chartreuse", 0x7FFF00 },
1192 { "Chocolate", 0xD2691E },
1193 { "Coral", 0xFF7F50 },
1194 { "CornflowerBlue", 0x6495ED },
1195 { "Cornsilk", 0xFFF8DC },
1196 { "Crimson", 0xDC143C },
1197 { "Cyan", 0x00FFFF },
1198 { "DarkBlue", 0x00008B },
1199 { "DarkCyan", 0x008B8B },
1200 { "DarkGoldenRod", 0xB8860B },
1201 { "DarkGray", 0xA9A9A9 },
1202 { "DarkGrey", 0xA9A9A9 },
1203 { "DarkGreen", 0x006400 },
1204 { "DarkKhaki", 0xBDB76B },
1205 { "DarkMagenta", 0x8B008B },
1206 { "DarkOliveGreen", 0x556B2F },
1207 { "Darkorange", 0xFF8C00 },
1208 { "DarkOrchid", 0x9932CC },
1209 { "DarkRed", 0x8B0000 },
1210 { "DarkSalmon", 0xE9967A },
1211 { "DarkSeaGreen", 0x8FBC8F },
1212 { "DarkSlateBlue", 0x483D8B },
1213 { "DarkSlateGray", 0x2F4F4F },
1214 { "DarkSlateGrey", 0x2F4F4F },
1215 { "DarkTurquoise", 0x00CED1 },
1216 { "DarkViolet", 0x9400D3 },
1217 { "DeepPink", 0xFF1493 },
1218 { "DeepSkyBlue", 0x00BFFF },
1219 { "DimGray", 0x696969 },
1220 { "DimGrey", 0x696969 },
1221 { "DodgerBlue", 0x1E90FF },
1222 { "FireBrick", 0xB22222 },
1223 { "FloralWhite", 0xFFFAF0 },
1224 { "ForestGreen", 0x228B22 },
1225 { "Fuchsia", 0xFF00FF },
1226 { "Gainsboro", 0xDCDCDC },
1227 { "GhostWhite", 0xF8F8FF },
1228 { "Gold", 0xFFD700 },
1229 { "GoldenRod", 0xDAA520 },
1230 { "Gray", 0x808080 },
1231 { "Grey", 0x808080 },
1232 { "Green", 0x008000 },
1233 { "GreenYellow", 0xADFF2F },
1234 { "HoneyDew", 0xF0FFF0 },
1235 { "HotPink", 0xFF69B4 },
1236 { "IndianRed", 0xCD5C5C },
1237 { "Indigo", 0x4B0082 },
1238 { "Ivory", 0xFFFFF0 },
1239 { "Khaki", 0xF0E68C },
1240 { "Lavender", 0xE6E6FA },
1241 { "LavenderBlush", 0xFFF0F5 },
1242 { "LawnGreen", 0x7CFC00 },
1243 { "LemonChiffon", 0xFFFACD },
1244 { "LightBlue", 0xADD8E6 },
1245 { "LightCoral", 0xF08080 },
1246 { "LightCyan", 0xE0FFFF },
1247 { "LightGoldenRodYellow", 0xFAFAD2 },
1248 { "LightGray", 0xD3D3D3 },
1249 { "LightGrey", 0xD3D3D3 },
1250 { "LightGreen", 0x90EE90 },
1251 { "LightPink", 0xFFB6C1 },
1252 { "LightSalmon", 0xFFA07A },
1253 { "LightSeaGreen", 0x20B2AA },
1254 { "LightSkyBlue", 0x87CEFA },
1255 { "LightSlateGray", 0x778899 },
1256 { "LightSlateGrey", 0x778899 },
1257 { "LightSteelBlue", 0xB0C4DE },
1258 { "LightYellow", 0xFFFFE0 },
1259 { "Lime", 0x00FF00 },
1260 { "LimeGreen", 0x32CD32 },
1261 { "Linen", 0xFAF0E6 },
1262 { "Magenta", 0xFF00FF },
1263 { "Maroon", 0x800000 },
1264 { "MediumAquaMarine", 0x66CDAA },
1265 { "MediumBlue", 0x0000CD },
1266 { "MediumOrchid", 0xBA55D3 },
1267 { "MediumPurple", 0x9370D8 },
1268 { "MediumSeaGreen", 0x3CB371 },
1269 { "MediumSlateBlue", 0x7B68EE },
1270 { "MediumSpringGreen", 0x00FA9A },
1271 { "MediumTurquoise", 0x48D1CC },
1272 { "MediumVioletRed", 0xC71585 },
1273 { "MidnightBlue", 0x191970 },
1274 { "MintCream", 0xF5FFFA },
1275 { "MistyRose", 0xFFE4E1 },
1276 { "Moccasin", 0xFFE4B5 },
1277 { "NavajoWhite", 0xFFDEAD },
1278 { "Navy", 0x000080 },
1279 { "OldLace", 0xFDF5E6 },
1280 { "Olive", 0x808000 },
1281 { "OliveDrab", 0x6B8E23 },
1282 { "Orange", 0xFFA500 },
1283 { "OrangeRed", 0xFF4500 },
1284 { "Orchid", 0xDA70D6 },
1285 { "PaleGoldenRod", 0xEEE8AA },
1286 { "PaleGreen", 0x98FB98 },
1287 { "PaleTurquoise", 0xAFEEEE },
1288 { "PaleVioletRed", 0xD87093 },
1289 { "PapayaWhip", 0xFFEFD5 },
1290 { "PeachPuff", 0xFFDAB9 },
1291 { "Peru", 0xCD853F },
1292 { "Pink", 0xFFC0CB },
1293 { "Plum", 0xDDA0DD },
1294 { "PowderBlue", 0xB0E0E6 },
1295 { "Purple", 0x800080 },
1296 { "Red", 0xFF0000 },
1297 { "RosyBrown", 0xBC8F8F },
1298 { "RoyalBlue", 0x4169E1 },
1299 { "SaddleBrown", 0x8B4513 },
1300 { "Salmon", 0xFA8072 },
1301 { "SandyBrown", 0xF4A460 },
1302 { "SeaGreen", 0x2E8B57 },
1303 { "SeaShell", 0xFFF5EE },
1304 { "Sienna", 0xA0522D },
1305 { "Silver", 0xC0C0C0 },
1306 { "SkyBlue", 0x87CEEB },
1307 { "SlateBlue", 0x6A5ACD },
1308 { "SlateGray", 0x708090 },
1309 { "SlateGrey", 0x708090 },
1310 { "Snow", 0xFFFAFA },
1311 { "SpringGreen", 0x00FF7F },
1312 { "SteelBlue", 0x4682B4 },
1313 { "Tan", 0xD2B48C },
1314 { "Teal", 0x008080 },
1315 { "Thistle", 0xD8BFD8 },
1316 { "Tomato", 0xFF6347 },
1317 { "Turquoise", 0x40E0D0 },
1318 { "Violet", 0xEE82EE },
1319 { "Wheat", 0xF5DEB3 },
1320 { "White", 0xFFFFFF },
1321 { "WhiteSmoke", 0xF5F5F5 },
1322 { "Yellow", 0xFFFF00 },
1323 { "YellowGreen", 0x9ACD32 },
1328 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1329 font_stack_t **p_fonts, int i_scale )
1332 char *psz_fontname = NULL;
1333 uint32_t i_font_color = 0xffffff;
1334 int i_font_alpha = 0;
1335 uint32_t i_karaoke_bg_color = 0x00ffffff;
1336 int i_font_size = 24;
1338 /* Default all attributes to the top font in the stack -- in case not
1339 * all attributes are specified in the sub-font
1341 if( VLC_SUCCESS == PeekFont( p_fonts,
1345 &i_karaoke_bg_color ))
1347 psz_fontname = strdup( psz_fontname );
1348 i_font_size = i_font_size * 1000 / i_scale;
1350 i_font_alpha = (i_font_color >> 24) & 0xff;
1351 i_font_color &= 0x00ffffff;
1353 const char *name, *value;
1354 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1356 if( !strcasecmp( "face", name ) )
1358 free( psz_fontname );
1359 psz_fontname = strdup( value );
1361 else if( !strcasecmp( "size", name ) )
1363 if( ( *value == '+' ) || ( *value == '-' ) )
1365 int i_value = atoi( value );
1367 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1368 i_font_size += ( i_value * i_font_size ) / 10;
1369 else if( i_value < -5 )
1370 i_font_size = - i_value;
1371 else if( i_value > 5 )
1372 i_font_size = i_value;
1375 i_font_size = atoi( value );
1377 else if( !strcasecmp( "color", name ) )
1379 if( value[0] == '#' )
1381 i_font_color = strtol( value + 1, NULL, 16 );
1382 i_font_color &= 0x00ffffff;
1386 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1388 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1390 i_font_color = p_html_colors[i].i_value;
1396 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1398 i_font_alpha = strtol( value + 1, NULL, 16 );
1399 i_font_alpha &= 0xff;
1402 rv = PushFont( p_fonts,
1404 i_font_size * i_scale / 1000,
1405 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1406 i_karaoke_bg_color );
1408 free( psz_fontname );
1413 static void SetKaraokeLen( uint32_t i_runs, uint32_t *pi_run_lengths,
1414 uint32_t i_k_runs, uint32_t *pi_k_run_lengths )
1416 /* Karaoke tags _PRECEDE_ the text they specify a duration
1417 * for, therefore we are working out the length for the
1418 * previous tag, and first time through we have nothing
1420 if( pi_k_run_lengths )
1425 /* Work out how many characters are presently in the string
1427 for( i = 0; i < i_runs; i++ )
1428 i_chars += pi_run_lengths[ i ];
1430 /* Subtract away those we've already allocated to other
1433 for( i = 0; i < i_k_runs; i++ )
1434 i_chars -= pi_k_run_lengths[ i ];
1436 pi_k_run_lengths[ i_k_runs - 1 ] = i_chars;
1440 static void SetupKaraoke( xml_reader_t *p_xml_reader, uint32_t *pi_k_runs,
1441 uint32_t **ppi_k_run_lengths,
1442 uint32_t **ppi_k_durations )
1444 const char *name, *value;
1446 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1448 if( !strcasecmp( "t", name ) )
1450 if( ppi_k_durations && ppi_k_run_lengths )
1454 if( *ppi_k_durations )
1456 *ppi_k_durations = realloc_or_free( *ppi_k_durations,
1457 *pi_k_runs * sizeof( uint32_t ) );
1459 else if( *pi_k_runs == 1 )
1461 *ppi_k_durations = (uint32_t *)
1462 malloc( *pi_k_runs * sizeof( uint32_t ) );
1465 if( *ppi_k_run_lengths )
1467 *ppi_k_run_lengths = realloc_or_free( *ppi_k_run_lengths,
1468 *pi_k_runs * sizeof( uint32_t ) );
1470 else if( *pi_k_runs == 1 )
1472 *ppi_k_run_lengths = (uint32_t *)
1473 malloc( *pi_k_runs * sizeof( uint32_t ) );
1475 if( *ppi_k_durations )
1476 (*ppi_k_durations)[ *pi_k_runs - 1 ] = atoi( value );
1478 if( *ppi_k_run_lengths )
1479 (*ppi_k_run_lengths)[ *pi_k_runs - 1 ] = 0;
1485 /* Turn any multiple-whitespaces into single spaces */
1486 static void HandleWhiteSpace( char *psz_node )
1488 char *s = strpbrk( psz_node, "\t\r\n " );
1491 int i_whitespace = strspn( s, "\t\r\n " );
1493 if( i_whitespace > 1 )
1496 strlen( s ) - i_whitespace + 1 );
1499 s = strpbrk( s, "\t\r\n " );
1504 static ft_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1505 font_stack_t **p_fonts,
1506 bool b_bold, bool b_italic,
1507 bool b_uline, bool b_through )
1509 char *psz_fontname = NULL;
1510 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1511 uint32_t i_karaoke_bg_color = i_font_color;
1512 int i_font_size = p_sys->i_font_size;
1514 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1515 &i_font_color, &i_karaoke_bg_color ) )
1518 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1519 i_karaoke_bg_color, b_bold, b_italic, b_uline, b_through );
1522 static int RenderTag( filter_t *p_filter, FT_Face p_face, int i_font_color,
1523 bool b_uline, bool b_through, bool b_bold,
1524 bool b_italic, int i_karaoke_bgcolor,
1525 line_desc_t *p_line, uint32_t *psz_unicode,
1526 int *pi_pen_x, int i_pen_y, int *pi_start,
1527 FT_Vector *p_result )
1532 bool b_first_on_line = true;
1535 int i_pen_x_start = *pi_pen_x;
1537 uint32_t *psz_unicode_start = psz_unicode;
1539 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1541 /* Account for part of line already in position */
1542 for( i = 0; i<*pi_start; i++ )
1546 FT_Glyph_Get_CBox( (FT_Glyph) p_line->pp_glyphs[ i ],
1547 ft_glyph_bbox_pixels, &glyph_size );
1549 line.xMax = p_line->p_glyph_pos[ i ].x + glyph_size.xMax -
1550 glyph_size.xMin + p_line->pp_glyphs[ i ]->left;
1551 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1552 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1558 b_first_on_line = false;
1560 while( *psz_unicode && ( *psz_unicode != '\n' ) )
1566 int i_glyph_index = FT_Get_Char_Index( p_face, *psz_unicode++ );
1567 if( FT_HAS_KERNING( p_face ) && i_glyph_index
1571 FT_Get_Kerning( p_face, i_previous, i_glyph_index,
1572 ft_kerning_default, &delta );
1573 *pi_pen_x += delta.x >> 6;
1575 p_line->p_glyph_pos[ i ].x = *pi_pen_x;
1576 p_line->p_glyph_pos[ i ].y = i_pen_y;
1578 i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT );
1581 i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT );
1585 "unable to render text FT_Load_Glyph returned %d", i_error );
1586 p_line->pp_glyphs[ i ] = NULL;
1587 return VLC_EGENERIC;
1591 /* Do synthetic styling now that Freetype supports it;
1592 * ie. if the font we have loaded is NOT already in the
1593 * style that the tags want, then switch it on; if they
1594 * are then don't. */
1595 if (b_bold && !( p_face->style_flags & FT_STYLE_FLAG_BOLD ))
1596 FT_GlyphSlot_Embolden( p_face->glyph );
1597 if (b_italic && !( p_face->style_flags & FT_STYLE_FLAG_ITALIC ))
1598 FT_GlyphSlot_Oblique( p_face->glyph );
1600 i_error = FT_Get_Glyph( p_face->glyph, &tmp_glyph );
1604 "unable to render text FT_Get_Glyph returned %d", i_error );
1605 p_line->pp_glyphs[ i ] = NULL;
1606 return VLC_EGENERIC;
1608 FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
1609 i_error = FT_Glyph_To_Bitmap( &tmp_glyph, FT_RENDER_MODE_NORMAL, 0, 1);
1612 FT_Done_Glyph( tmp_glyph );
1615 if( b_uline || b_through )
1617 float aOffset = FT_FLOOR(FT_MulFix(p_face->underline_position,
1618 p_face->size->metrics.y_scale));
1619 float aSize = FT_CEIL(FT_MulFix(p_face->underline_thickness,
1620 p_face->size->metrics.y_scale));
1622 p_line->pi_underline_offset[ i ] =
1623 ( aOffset < 0 ) ? -aOffset : aOffset;
1624 p_line->pi_underline_thickness[ i ] =
1625 ( aSize < 0 ) ? -aSize : aSize;
1628 /* Move the baseline to make it strikethrough instead of
1629 * underline. That means that strikethrough takes precedence
1631 float aDescent = FT_FLOOR(FT_MulFix(p_face->descender*2,
1632 p_face->size->metrics.y_scale));
1634 p_line->pi_underline_offset[ i ] -=
1635 ( aDescent < 0 ) ? -aDescent : aDescent;
1639 p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
1640 p_line->p_fg_rgb[ i ] = i_font_color & 0x00ffffff;
1641 p_line->p_bg_rgb[ i ] = i_karaoke_bgcolor & 0x00ffffff;
1642 p_line->p_fg_bg_ratio[ i ] = 0x00;
1644 line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
1645 glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
1646 if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
1648 for( ; i >= *pi_start; i-- )
1649 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
1652 while( psz_unicode > psz_unicode_start && *psz_unicode != ' ' )
1656 if( psz_unicode == psz_unicode_start )
1658 if( b_first_on_line )
1660 msg_Warn( p_filter, "unbreakable string" );
1661 p_line->pp_glyphs[ i ] = NULL;
1662 return VLC_EGENERIC;
1664 *pi_pen_x = i_pen_x_start;
1666 p_line->i_width = line.xMax;
1667 p_line->i_height = __MAX( p_line->i_height,
1668 p_face->size->metrics.height >> 6 );
1669 p_line->pp_glyphs[ i ] = NULL;
1671 p_result->x = __MAX( p_result->x, line.xMax );
1672 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1673 i_yMax - i_yMin ) );
1678 *psz_unicode = '\n';
1680 psz_unicode = psz_unicode_start;
1681 *pi_pen_x = i_pen_x_start;
1689 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1690 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1692 i_previous = i_glyph_index;
1693 *pi_pen_x += p_face->glyph->advance.x >> 6;
1696 p_line->i_width = line.xMax;
1697 p_line->i_height = __MAX( p_line->i_height,
1698 p_face->size->metrics.height >> 6 );
1699 p_line->pp_glyphs[ i ] = NULL;
1701 p_result->x = __MAX( p_result->x, line.xMax );
1702 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1703 line.yMax - line.yMin ) );
1707 /* Get rid of any text processed - if necessary repositioning
1708 * at the start of a new line of text
1712 *psz_unicode_start = '\0';
1714 else if( psz_unicode > psz_unicode_start )
1716 for( i=0; psz_unicode[ i ]; i++ )
1717 psz_unicode_start[ i ] = psz_unicode[ i ];
1718 psz_unicode_start[ i ] = '\0';
1724 static void SetupLine( filter_t *p_filter, const char *psz_text_in,
1725 uint32_t **ppsz_text_out, uint32_t *pi_runs,
1726 uint32_t **ppi_run_lengths, ft_style_t ***ppp_styles,
1727 ft_style_t *p_style )
1729 size_t i_string_length;
1731 IconvText( p_filter, psz_text_in, &i_string_length, *ppsz_text_out );
1732 *ppsz_text_out += i_string_length;
1734 if( ppp_styles && ppi_run_lengths )
1738 /* XXX this logic looks somewhat broken */
1742 *ppp_styles = realloc_or_free( *ppp_styles,
1743 *pi_runs * sizeof( ft_style_t * ) );
1745 else if( *pi_runs == 1 )
1747 *ppp_styles = malloc( *pi_runs * sizeof( ft_style_t * ) );
1750 /* We have just malloc'ed this memory successfully -
1751 * *pi_runs HAS to be within the memory area of *ppp_styles */
1754 (*ppp_styles)[ *pi_runs - 1 ] = p_style;
1758 /* XXX more iffy logic */
1760 if( *ppi_run_lengths )
1762 *ppi_run_lengths = realloc_or_free( *ppi_run_lengths,
1763 *pi_runs * sizeof( uint32_t ) );
1765 else if( *pi_runs == 1 )
1767 *ppi_run_lengths = (uint32_t *)
1768 malloc( *pi_runs * sizeof( uint32_t ) );
1771 /* same remarks here */
1772 if( *ppi_run_lengths )
1774 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
1777 /* If we couldn't use the p_style argument due to memory allocation
1778 * problems above, release it here.
1780 DeleteStyle( p_style );
1783 static int CheckForEmbeddedFont( filter_sys_t *p_sys, FT_Face *pp_face, ft_style_t *p_style )
1785 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1787 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1789 FT_Face p_face = NULL;
1791 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1799 bool match = !strcasecmp( p_face->family_name,
1800 p_style->psz_fontname );
1802 if( p_face->style_flags & FT_STYLE_FLAG_BOLD )
1803 match = match && p_style->b_bold;
1805 match = match && !p_style->b_bold;
1807 if( p_face->style_flags & FT_STYLE_FLAG_ITALIC )
1808 match = match && p_style->b_italic;
1810 match = match && !p_style->b_italic;
1818 FT_Done_Face( p_face );
1823 return VLC_EGENERIC;
1826 static int ProcessNodes( filter_t *p_filter,
1827 xml_reader_t *p_xml_reader,
1828 text_style_t *p_font_style,
1833 uint32_t **ppi_run_lengths,
1834 ft_style_t * **ppp_styles,
1837 uint32_t *pi_k_runs,
1838 uint32_t **ppi_k_run_lengths,
1839 uint32_t **ppi_k_durations )
1841 int rv = VLC_SUCCESS;
1842 filter_sys_t *p_sys = p_filter->p_sys;
1843 uint32_t *psz_text_orig = psz_text;
1844 font_stack_t *p_fonts = NULL;
1848 bool b_italic = false;
1849 bool b_bold = false;
1850 bool b_uline = false;
1851 bool b_through = false;
1853 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
1854 i_scale = val.i_int;
1858 rv = PushFont( &p_fonts,
1859 p_font_style->psz_fontname,
1860 p_font_style->i_font_size * i_scale / 1000,
1861 (p_font_style->i_font_color & 0xffffff) |
1862 ((p_font_style->i_font_alpha & 0xff) << 24),
1863 (p_font_style->i_karaoke_background_color & 0xffffff) |
1864 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1866 if( p_font_style->i_style_flags & STYLE_BOLD )
1868 if( p_font_style->i_style_flags & STYLE_ITALIC )
1870 if( p_font_style->i_style_flags & STYLE_UNDERLINE )
1872 if( p_font_style->i_style_flags & STYLE_STRIKEOUT )
1878 rv = PushFont( &p_fonts,
1879 p_sys->psz_fontfamily,
1881 (p_sys->i_font_color & 0xffffff) |
1882 (((255-p_sys->i_font_opacity) & 0xff) << 24),
1887 if( rv != VLC_SUCCESS )
1893 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1897 case XML_READER_ENDELEM:
1898 if( !strcasecmp( "font", node ) )
1899 PopFont( &p_fonts );
1900 else if( !strcasecmp( "b", node ) )
1902 else if( !strcasecmp( "i", node ) )
1904 else if( !strcasecmp( "u", node ) )
1906 else if( !strcasecmp( "s", node ) )
1910 case XML_READER_STARTELEM:
1911 if( !strcasecmp( "font", node ) )
1912 rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
1913 else if( !strcasecmp( "b", node ) )
1915 else if( !strcasecmp( "i", node ) )
1917 else if( !strcasecmp( "u", node ) )
1919 else if( !strcasecmp( "s", node ) )
1921 else if( !strcasecmp( "br", node ) )
1923 SetupLine( p_filter, "\n", &psz_text,
1924 pi_runs, ppi_run_lengths, ppp_styles,
1925 GetStyleFromFontStack( p_sys,
1932 else if( !strcasecmp( "k", node ) )
1934 /* Only valid in karaoke */
1937 if( *pi_k_runs > 0 )
1938 SetKaraokeLen( *pi_runs, *ppi_run_lengths,
1939 *pi_k_runs, *ppi_k_run_lengths );
1940 SetupKaraoke( p_xml_reader, pi_k_runs,
1941 ppi_k_run_lengths, ppi_k_durations );
1946 case XML_READER_TEXT:
1948 char *psz_node = strdup( node );
1949 if( unlikely(!psz_node) )
1952 HandleWhiteSpace( psz_node );
1953 resolve_xml_special_chars( psz_node );
1955 SetupLine( p_filter, psz_node, &psz_text,
1956 pi_runs, ppi_run_lengths, ppp_styles,
1957 GetStyleFromFontStack( p_sys,
1967 if( rv != VLC_SUCCESS )
1969 psz_text = psz_text_orig;
1975 SetKaraokeLen( *pi_runs, *ppi_run_lengths,
1976 *pi_k_runs, *ppi_k_run_lengths );
1979 *pi_len = psz_text - psz_text_orig;
1981 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1987 static int ProcessLines( filter_t *p_filter,
1992 uint32_t *pi_run_lengths,
1993 ft_style_t **pp_styles,
1994 line_desc_t **pp_lines,
1996 FT_Vector *p_result,
2000 uint32_t *pi_k_run_lengths,
2001 uint32_t *pi_k_durations )
2003 filter_sys_t *p_sys = p_filter->p_sys;
2004 ft_style_t **pp_char_styles;
2005 int *p_new_positions = NULL;
2006 int8_t *p_levels = NULL;
2007 uint8_t *pi_karaoke_bar = NULL;
2011 /* Assign each character in the text string its style explicitly, so that
2012 * after the characters have been shuffled around by Fribidi, we can re-apply
2013 * the styles, and to simplify the calculation of runs within a line.
2015 pp_char_styles = (ft_style_t **) malloc( i_len * sizeof( ft_style_t * ));
2016 if( !pp_char_styles )
2021 pi_karaoke_bar = (uint8_t *) malloc( i_len * sizeof( uint8_t ));
2022 /* If we can't allocate sufficient memory for karaoke, continue anyway -
2023 * we just won't be able to display the progress bar; at least we'll
2029 for( j = 0; j < i_runs; j++ )
2030 for( k = 0; k < pi_run_lengths[ j ]; k++ )
2031 pp_char_styles[ i++ ] = pp_styles[ j ];
2033 #if defined(HAVE_FRIBIDI)
2035 ft_style_t **pp_char_styles_new;
2036 int *p_old_positions;
2037 uint32_t *p_fribidi_string;
2038 int start_pos, pos = 0;
2040 pp_char_styles_new = (ft_style_t **)
2041 malloc( i_len * sizeof( ft_style_t * ));
2043 p_fribidi_string = (uint32_t *)
2044 malloc( (i_len + 1) * sizeof(uint32_t) );
2045 p_old_positions = (int *)
2046 malloc( (i_len + 1) * sizeof( int ) );
2047 p_new_positions = (int *)
2048 malloc( (i_len + 1) * sizeof( int ) );
2049 p_levels = (int8_t *)
2050 malloc( (i_len + 1) * sizeof( int8_t ) );
2052 if( ! pp_char_styles_new ||
2053 ! p_fribidi_string ||
2054 ! p_old_positions ||
2055 ! p_new_positions ||
2059 free( p_old_positions );
2060 free( p_new_positions );
2061 free( p_fribidi_string );
2062 free( pp_char_styles_new );
2063 free( pi_karaoke_bar );
2065 free( pp_char_styles );
2069 /* Do bidi conversion line-by-line */
2072 while(pos < i_len) {
2073 if (psz_text[pos] != '\n')
2075 p_fribidi_string[pos] = psz_text[pos];
2076 pp_char_styles_new[pos] = pp_char_styles[pos];
2077 p_new_positions[pos] = pos;
2082 while(pos < i_len) {
2083 if (psz_text[pos] == '\n')
2087 if (pos > start_pos)
2089 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
2090 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
2092 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
2094 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
2095 pos - start_pos, &base_dir,
2096 (FriBidiChar*)p_fribidi_string + start_pos,
2097 p_new_positions + start_pos,
2099 p_levels + start_pos );
2100 for( j = (uint32_t) start_pos; j < (uint32_t) pos; j++ )
2102 pp_char_styles_new[ j ] = pp_char_styles[ start_pos +
2103 p_old_positions[ j - start_pos ] ];
2104 p_new_positions[ j ] += start_pos;
2108 free( p_old_positions );
2109 free( pp_char_styles );
2110 pp_char_styles = pp_char_styles_new;
2111 psz_text = p_fribidi_string;
2112 p_fribidi_string[ i_len ] = 0;
2115 /* Work out the karaoke */
2116 if( pi_karaoke_bar )
2118 int64_t i_last_duration = 0;
2119 int64_t i_duration = 0;
2120 int64_t i_start_pos = 0;
2121 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
2123 for( k = 0; k< i_k_runs; k++ )
2125 double fraction = 0.0;
2127 i_duration += pi_k_durations[ k ];
2129 if( i_duration < i_elapsed )
2131 /* Completely finished this run-length -
2132 * let it render normally */
2136 else if( i_elapsed < i_last_duration )
2138 /* Haven't got up to this segment yet -
2139 * render it completely in karaoke BG mode */
2145 /* Partway through this run */
2147 fraction = (double)(i_elapsed - i_last_duration) /
2148 (double)pi_k_durations[ k ];
2150 for( i = 0; i < pi_k_run_lengths[ k ]; i++ )
2152 double shade = pi_k_run_lengths[ k ] * fraction;
2154 if( p_new_positions )
2155 j = p_new_positions[ i_start_pos + i ];
2157 j = i_start_pos + i;
2159 if( i < (uint32_t)shade )
2160 pi_karaoke_bar[ j ] = 0xff;
2161 else if( (double)i > shade )
2162 pi_karaoke_bar[ j ] = 0x00;
2165 shade -= (int)shade;
2166 pi_karaoke_bar[ j ] = ((int)(shade * 128.0) & 0x7f) |
2167 ((p_levels ? (p_levels[ j ] % 2) : 0 ) << 7);
2171 i_last_duration = i_duration;
2172 i_start_pos += pi_k_run_lengths[ k ];
2176 free( p_new_positions );
2178 FT_Vector tmp_result;
2180 line_desc_t *p_line = NULL;
2181 line_desc_t *p_prev = NULL;
2187 p_result->x = p_result->y = 0;
2188 tmp_result.x = tmp_result.y = 0;
2191 for( k = 0; k <= (uint32_t) i_len; k++ )
2193 if( ( k == (uint32_t) i_len ) ||
2195 !StyleEquals( pp_char_styles[ k ], pp_char_styles[ k - 1] ) ) )
2197 ft_style_t *p_style = pp_char_styles[ k - 1 ];
2199 /* End of the current style run */
2200 FT_Face p_face = NULL;
2203 /* Look for a match amongst our attachments first */
2204 CheckForEmbeddedFont( p_sys, &p_face, p_style );
2210 #ifdef HAVE_FONTCONFIG
2211 psz_fontfile = FontConfig_Select( NULL,
2212 p_style->psz_fontname,
2217 #elif defined( WIN32 )
2218 psz_fontfile = Win32_Select( p_filter,
2219 p_style->psz_fontname,
2225 psz_fontfile = NULL;
2227 if( psz_fontfile && ! *psz_fontfile )
2229 msg_Warn( p_filter, "We were not able to find a matching font: \"%s\" %s,"
2230 " so using default font", p_style->psz_fontname,
2231 ((p_style->b_bold && p_style->b_italic) ? "(Bold,Italic)" :
2232 (p_style->b_bold ? "(Bold)" :
2233 (p_style->b_italic ? "(Italic)" : ""))) );
2234 free( psz_fontfile );
2235 psz_fontfile = NULL;
2240 if( FT_New_Face( p_sys->p_library,
2241 psz_fontfile, i_idx, &p_face ) )
2243 free( psz_fontfile );
2244 free( pp_char_styles );
2245 #if defined(HAVE_FRIBIDI)
2248 free( pi_karaoke_bar );
2249 return VLC_EGENERIC;
2251 free( psz_fontfile );
2255 FT_Select_Charmap( p_face, ft_encoding_unicode ) )
2257 /* We've loaded a font face which is unhelpful for actually
2258 * rendering text - fallback to the default one.
2260 FT_Done_Face( p_face );
2264 if( FT_Select_Charmap( p_face ? p_face : p_sys->p_face,
2265 ft_encoding_unicode ) ||
2266 FT_Set_Pixel_Sizes( p_face ? p_face : p_sys->p_face, 0,
2267 p_style->i_font_size ) )
2269 if( p_face ) FT_Done_Face( p_face );
2270 free( pp_char_styles );
2271 #if defined(HAVE_FRIBIDI)
2274 free( pi_karaoke_bar );
2275 return VLC_EGENERIC;
2277 p_sys->i_use_kerning =
2278 FT_HAS_KERNING( ( p_face ? p_face : p_sys->p_face ) );
2281 uint32_t *psz_unicode = (uint32_t *)
2282 malloc( (k - i_prev + 1) * sizeof( uint32_t ));
2285 if( p_face ) FT_Done_Face( p_face );
2286 free( pp_char_styles );
2287 free( psz_unicode );
2288 #if defined(HAVE_FRIBIDI)
2291 free( pi_karaoke_bar );
2294 memcpy( psz_unicode, psz_text + i_prev,
2295 sizeof( uint32_t ) * ( k - i_prev ) );
2296 psz_unicode[ k - i_prev ] = 0;
2297 while( *psz_unicode )
2301 if( !(p_line = NewLine( i_len - i_prev)) )
2303 if( p_face ) FT_Done_Face( p_face );
2304 free( pp_char_styles );
2305 free( psz_unicode );
2306 #if defined(HAVE_FRIBIDI)
2309 free( pi_karaoke_bar );
2312 /* New Color mode only works in YUVA rendering mode --
2313 * (RGB mode has palette constraints on it). We therefore
2314 * need to populate the legacy colour fields also.
2316 p_line->b_new_color_mode = true;
2317 p_line->i_alpha = ( p_style->i_font_color & 0xff000000 ) >> 24;
2318 p_line->i_red = ( p_style->i_font_color & 0x00ff0000 ) >> 16;
2319 p_line->i_green = ( p_style->i_font_color & 0x0000ff00 ) >> 8;
2320 p_line->i_blue = ( p_style->i_font_color & 0x000000ff );
2321 p_line->p_next = NULL;
2323 i_pen_y += tmp_result.y;
2327 if( p_prev ) p_prev->p_next = p_line;
2328 else *pp_lines = p_line;
2331 if( RenderTag( p_filter, p_face ? p_face : p_sys->p_face,
2332 p_style->i_font_color, p_style->b_underline,
2336 p_style->i_karaoke_bg_color,
2337 p_line, psz_unicode, &i_pen_x, i_pen_y, &i_posn,
2338 &tmp_result ) != VLC_SUCCESS )
2340 if( p_face ) FT_Done_Face( p_face );
2341 free( pp_char_styles );
2342 free( psz_unicode );
2343 #if defined(HAVE_FRIBIDI)
2346 free( pi_karaoke_bar );
2347 return VLC_EGENERIC;
2352 p_result->x = __MAX( p_result->x, tmp_result.x );
2353 p_result->y += tmp_result.y;
2358 if( *psz_unicode == '\n')
2362 for( c_ptr = psz_unicode; *c_ptr; c_ptr++ )
2364 *c_ptr = *(c_ptr+1);
2369 free( psz_unicode );
2370 if( p_face ) FT_Done_Face( p_face );
2374 free( pp_char_styles );
2375 #if defined(HAVE_FRIBIDI)
2380 p_result->x = __MAX( p_result->x, tmp_result.x );
2381 p_result->y += tmp_result.y;
2384 if( pi_karaoke_bar )
2387 for( p_line = *pp_lines; p_line; p_line=p_line->p_next )
2389 for( k = 0; p_line->pp_glyphs[ k ]; k++, i++ )
2391 if( (pi_karaoke_bar[ i ] & 0x7f) == 0x7f)
2395 else if( (pi_karaoke_bar[ i ] & 0x7f) == 0x00)
2397 /* 100% BG colour will render faster if we
2398 * instead make it 100% FG colour, so leave
2399 * the ratio alone and copy the value across
2401 p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2405 if( pi_karaoke_bar[ i ] & 0x80 )
2407 /* Swap Left and Right sides over for Right aligned
2408 * language text (eg. Arabic, Hebrew)
2410 uint32_t i_tmp = p_line->p_fg_rgb[ k ];
2412 p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2413 p_line->p_bg_rgb[ k ] = i_tmp;
2415 p_line->p_fg_bg_ratio[ k ] = (pi_karaoke_bar[ i ] & 0x7f);
2418 /* Jump over the '\n' at the line-end */
2421 free( pi_karaoke_bar );
2428 * This function renders a text subpicture region into another one.
2429 * It also calculates the size needed for this string, and renders the
2430 * needed glyphs into memory. It is used as pf_add_string callback in
2431 * the vout method by this module
2433 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2434 subpicture_region_t *p_region_in, bool b_html )
2436 filter_sys_t *p_sys = p_filter->p_sys;
2439 return VLC_EGENERIC;
2440 if( b_html && !p_region_in->psz_html )
2441 return VLC_EGENERIC;
2442 if( !b_html && !p_region_in->psz_text )
2443 return VLC_EGENERIC;
2445 uint32_t *psz_text = calloc( strlen( b_html ? p_region_in->psz_html
2446 : p_region_in->psz_text ),
2447 sizeof( *psz_text ) );
2449 return VLC_EGENERIC;
2452 /* Reset the default fontsize in case screen metrics have changed */
2453 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2456 int rv = VLC_SUCCESS;
2457 int i_text_length = 0;
2458 FT_Vector result = {0, 0};
2459 line_desc_t *p_lines = NULL;
2464 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2465 (uint8_t *) p_region_in->psz_html,
2466 strlen( p_region_in->psz_html ),
2468 if( unlikely(p_sub == NULL) )
2471 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2473 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2475 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2476 p_filter->p_sys->p_xml = p_xml_reader;
2481 bool b_karaoke = false;
2484 /* Look for Root Node */
2487 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2489 if( !strcasecmp( "karaoke", node ) )
2491 /* We're going to have to render the text a number
2492 * of times to show the progress marker on the text.
2494 var_SetBool( p_filter, "text-rerender", true );
2497 else if( !strcasecmp( "text", node ) )
2503 /* Only text and karaoke tags are supported */
2504 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2511 msg_Err( p_filter, "Malformed HTML subtitle" );
2517 uint32_t i_runs = 0;
2518 uint32_t i_k_runs = 0;
2519 uint32_t *pi_run_lengths = NULL;
2520 uint32_t *pi_k_run_lengths = NULL;
2521 uint32_t *pi_k_durations = NULL;
2522 ft_style_t **pp_styles = NULL;
2524 rv = ProcessNodes( p_filter, p_xml_reader,
2525 p_region_in->p_style, psz_text, &i_text_length,
2526 &i_runs, &pi_run_lengths, &pp_styles,
2527 b_karaoke, &i_k_runs, &pi_k_run_lengths,
2530 if( !rv && i_text_length > 0 )
2532 rv = ProcessLines( p_filter, psz_text, i_text_length, i_runs,
2533 pi_run_lengths, pp_styles, &p_lines,
2534 &result, b_karaoke, i_k_runs,
2535 pi_k_run_lengths, pi_k_durations );
2538 for( uint32_t k = 0; k < i_runs; k++ )
2539 DeleteStyle( pp_styles[k] );
2541 free( pi_run_lengths );
2546 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2548 stream_Delete( p_sub );
2554 size_t i_iconv_length;
2555 IconvText( p_filter, p_region_in->psz_text, &i_iconv_length, psz_text );
2556 i_text_length = i_iconv_length;
2560 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ) )
2561 i_scale = val.i_int;
2563 ft_style_t *p_style;
2564 if( p_region_in->p_style )
2565 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2566 p_region_in->p_style->i_font_size * i_scale / 1000,
2567 (p_region_in->p_style->i_font_color & 0xffffff) |
2568 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2570 p_region_in->p_style->i_style_flags & STYLE_BOLD,
2571 p_region_in->p_style->i_style_flags & STYLE_ITALIC,
2572 p_region_in->p_style->i_style_flags & STYLE_UNDERLINE,
2573 p_region_in->p_style->i_style_flags & STYLE_STRIKEOUT);
2575 p_style = CreateStyle( p_sys->psz_fontfamily,
2576 p_sys->i_font_size * i_scale / 1000,
2577 (p_sys->i_font_color & 0xffffff) |
2578 (((255-p_sys->i_font_opacity) & 0xff) << 24),
2580 false, false, false, false );
2581 uint32_t i_run_length = i_text_length;
2583 rv = ProcessLines( p_filter, psz_text, i_text_length,
2584 1, &i_run_length, &p_style,
2586 false, 0, NULL, NULL );
2587 DeleteStyle( p_style );
2591 p_region_out->i_x = p_region_in->i_x;
2592 p_region_out->i_y = p_region_in->i_y;
2594 /* Don't attempt to render text that couldn't be layed out
2596 if( !rv && i_text_length > 0 )
2598 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2599 RenderYUVP( p_filter, p_region_out, p_lines,
2600 result.x, result.y );
2602 RenderYUVA( p_filter, p_region_out, p_lines,
2603 result.x, result.y );
2606 FreeLines( p_lines );
2610 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2611 subpicture_region_t *p_region_in )
2613 return RenderCommon( p_filter, p_region_out, p_region_in, false );
2618 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2619 subpicture_region_t *p_region_in )
2621 return RenderCommon( p_filter, p_region_out, p_region_in, true );
2626 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
2628 static int GetFileFontByName( const char *font_name, char **psz_filename )
2631 wchar_t vbuffer[MAX_PATH];
2632 wchar_t dbuffer[256];
2634 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey) != ERROR_SUCCESS )
2637 for( int index = 0;; index++ )
2639 DWORD vbuflen = MAX_PATH - 1;
2640 DWORD dbuflen = 255;
2642 if( RegEnumValueW( hKey, index, vbuffer, &vbuflen,
2643 NULL, NULL, (LPBYTE)dbuffer, &dbuflen) != ERROR_SUCCESS )
2646 char *psz_value = FromWide( vbuffer );
2648 char *s = strchr( psz_value,'(' );
2649 if( s != NULL && s != psz_value ) s[-1] = '\0';
2651 /* Manage concatenated font names */
2652 if( strchr( psz_value, '&') ) {
2653 if( strcasestr( psz_value, font_name ) != NULL )
2657 if( strcasecmp( psz_value, font_name ) == 0 )
2662 *psz_filename = FromWide( dbuffer );
2667 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
2668 DWORD type, LPARAM lParam)
2670 VLC_UNUSED( metric );
2671 if( (type & RASTER_FONTTYPE) ) return 1;
2672 // if( lpelfe->elfScript ) FIXME
2674 return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
2677 static char* Win32_Select( filter_t *p_filter, const char* family,
2678 bool b_bold, bool b_italic, int i_size, int *i_idx )
2680 VLC_UNUSED( i_size );
2681 // msg_Dbg( p_filter, "Here in Win32_Select, asking for %s", family );
2685 lf.lfCharSet = DEFAULT_CHARSET;
2689 lf.lfWeight = FW_BOLD;
2690 strncpy( (LPSTR)&lf.lfFaceName, family, 32);
2693 char *psz_filename = NULL;
2694 HDC hDC = GetDC( NULL );
2695 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
2696 ReleaseDC(NULL, hDC);
2698 if( psz_filename == NULL )
2701 /* FIXME: increase i_idx, when concatenated strings */
2706 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
2712 #ifdef HAVE_FONTCONFIG
2713 static void FontConfig_BuildCache( filter_t *p_filter )
2716 msg_Dbg( p_filter, "Building font databases.");
2721 dialog_progress_bar_t *p_dialog = NULL;
2722 FcConfig *fcConfig = FcInitLoadConfig();
2724 p_dialog = dialog_ProgressCreate( p_filter,
2725 _("Building font cache"),
2726 _("Please wait while your font cache is rebuilt.\n"
2727 "This should take less than a few minutes."), NULL );
2730 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
2732 FcConfigBuildFonts( fcConfig );
2735 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
2736 dialog_ProgressDestroy( p_dialog );
2741 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
2745 * \brief Selects a font matching family, bold, italic provided
2747 static char* FontConfig_Select( FcConfig* config, const char* family,
2748 bool b_bold, bool b_italic, int i_size, int *i_idx )
2750 FcResult result = FcResultMatch;
2751 FcPattern *pat, *p_pat;
2755 /* Create a pattern and fills it */
2756 pat = FcPatternCreate();
2757 if (!pat) return NULL;
2760 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
2761 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
2762 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
2763 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
2767 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
2769 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
2770 free( psz_fontsize );
2775 FcDefaultSubstitute( pat );
2776 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
2778 FcPatternDestroy( pat );
2782 /* Find the best font for the pattern, destroy the pattern */
2783 p_pat = FcFontMatch( config, pat, &result );
2784 FcPatternDestroy( pat );
2785 if( !p_pat || result == FcResultNoMatch ) return NULL;
2787 /* Check the new pattern */
2788 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
2789 || ( val_b != FcTrue ) )
2791 FcPatternDestroy( p_pat );
2794 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
2799 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
2801 FcPatternDestroy( p_pat );
2805 /* if( strcasecmp((const char*)val_s, family ) != 0 )
2806 msg_Warn( p_filter, "fontconfig: selected font family is not"
2807 "the requested one: '%s' != '%s'\n",
2808 (const char*)val_s, family ); */
2810 if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
2812 FcPatternDestroy( p_pat );
2816 FcPatternDestroy( p_pat );
2817 return strdup( (const char*)val_s );
2822 static void FreeLine( line_desc_t *p_line )
2825 for( i = 0; p_line->pp_glyphs[ i ] != NULL; i++ )
2827 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
2829 free( p_line->pp_glyphs );
2830 free( p_line->p_glyph_pos );
2831 free( p_line->p_fg_rgb );
2832 free( p_line->p_bg_rgb );
2833 free( p_line->p_fg_bg_ratio );
2834 free( p_line->pi_underline_offset );
2835 free( p_line->pi_underline_thickness );
2839 static void FreeLines( line_desc_t *p_lines )
2841 line_desc_t *p_line, *p_next;
2843 if( !p_lines ) return;
2845 for( p_line = p_lines; p_line != NULL; p_line = p_next )
2847 p_next = p_line->p_next;
2852 static line_desc_t *NewLine( int i_count )
2854 line_desc_t *p_line = malloc( sizeof(line_desc_t) );
2856 if( !p_line ) return NULL;
2857 p_line->i_height = 0;
2858 p_line->i_width = 0;
2859 p_line->p_next = NULL;
2861 p_line->pp_glyphs = malloc( sizeof(FT_BitmapGlyph) * ( i_count + 1 ) );
2862 p_line->p_glyph_pos = malloc( sizeof( FT_Vector ) * ( i_count + 1 ) );
2863 p_line->p_fg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) );
2864 p_line->p_bg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) );
2865 p_line->p_fg_bg_ratio = calloc( i_count + 1, sizeof( uint8_t ) );
2866 p_line->pi_underline_offset = calloc( i_count + 1, sizeof( int ) );
2867 p_line->pi_underline_thickness = calloc( i_count + 1, sizeof( uint16_t ) );
2868 if( ( p_line->pp_glyphs == NULL ) ||
2869 ( p_line->p_glyph_pos == NULL ) ||
2870 ( p_line->p_fg_rgb == NULL ) ||
2871 ( p_line->p_bg_rgb == NULL ) ||
2872 ( p_line->p_fg_bg_ratio == NULL ) ||
2873 ( p_line->pi_underline_offset == NULL ) ||
2874 ( p_line->pi_underline_thickness == NULL ) )
2876 free( p_line->pi_underline_thickness );
2877 free( p_line->pi_underline_offset );
2878 free( p_line->p_fg_rgb );
2879 free( p_line->p_bg_rgb );
2880 free( p_line->p_fg_bg_ratio );
2881 free( p_line->p_glyph_pos );
2882 free( p_line->pp_glyphs );
2886 p_line->pp_glyphs[0] = NULL;
2887 p_line->b_new_color_mode = false;
2892 static int GetFontSize( filter_t *p_filter )
2894 filter_sys_t *p_sys = p_filter->p_sys;
2898 if( p_sys->i_default_font_size )
2900 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
2901 i_size = p_sys->i_default_font_size * val.i_int / 1000;
2903 i_size = p_sys->i_default_font_size;
2907 var_Get( p_filter, "freetype-rel-fontsize", &val );
2910 i_size = (int)p_filter->fmt_out.video.i_height / val.i_int;
2911 p_filter->p_sys->i_display_height =
2912 p_filter->fmt_out.video.i_height;
2917 msg_Warn( p_filter, "invalid fontsize, using 12" );
2918 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
2919 i_size = 12 * val.i_int / 1000;
2926 static int SetFontSize( filter_t *p_filter, int i_size )
2928 filter_sys_t *p_sys = p_filter->p_sys;
2932 i_size = GetFontSize( p_filter );
2934 msg_Dbg( p_filter, "using fontsize: %i", i_size );
2937 p_sys->i_font_size = i_size;
2939 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
2941 msg_Err( p_filter, "couldn't set font size to %d", i_size );
2942 return VLC_EGENERIC;
2948 static void YUVFromRGB( uint32_t i_argb,
2949 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
2951 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
2952 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
2953 int i_blue = ( i_argb & 0x000000ff );
2955 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
2956 802 * i_blue + 4096 + 131072 ) >> 13, 235);
2957 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
2958 3598 * i_blue + 4096 + 1048576) >> 13, 240);
2959 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
2960 -585 * i_blue + 4096 + 1048576) >> 13, 240);