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 typedef struct line_desc_t line_desc_t;
203 /** NULL-terminated list of glyphs making the string */
204 FT_BitmapGlyph *pp_glyphs;
205 /** list of relative positions for the glyphs */
206 FT_Vector *p_glyph_pos;
207 /** list of RGB information for styled text */
210 uint8_t *p_fg_bg_ratio; /* 0x00=100% FG --> 0x7F=100% BG */
211 /** underline information -- only supplied if text should be underlined */
212 int *pi_underline_offset;
213 uint16_t *pi_underline_thickness;
223 typedef struct font_stack_t font_stack_t;
228 uint32_t i_color; /* ARGB */
229 uint32_t i_karaoke_bg_color; /* ARGB */
231 font_stack_t *p_next;
234 /*****************************************************************************
235 * filter_sys_t: freetype local data
236 *****************************************************************************
237 * This structure is part of the video output thread descriptor.
238 * It describes the freetype specific properties of an output thread.
239 *****************************************************************************/
242 FT_Library p_library; /* handle to library */
243 FT_Face p_face; /* handle to face object */
245 uint8_t i_font_opacity;
250 int i_default_font_size;
251 int i_display_height;
252 char* psz_fontfamily;
256 char* psz_win_fonts_path;
260 input_attachment_t **pp_font_attachments;
261 int i_font_attachments;
265 static void YUVFromRGB( uint32_t i_argb,
266 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
268 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
269 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
270 int i_blue = ( i_argb & 0x000000ff );
272 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
273 802 * i_blue + 4096 + 131072 ) >> 13, 235);
274 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
275 3598 * i_blue + 4096 + 1048576) >> 13, 240);
276 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
277 -585 * i_blue + 4096 + 1048576) >> 13, 240);
280 /*****************************************************************************
281 * Make any TTF/OTF fonts present in the attachments of the media file
282 * and store them for later use by the FreeType Engine
283 *****************************************************************************/
284 static int LoadFontsFromAttachments( filter_t *p_filter )
286 filter_sys_t *p_sys = p_filter->p_sys;
287 input_attachment_t **pp_attachments;
288 int i_attachments_cnt;
290 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
293 p_sys->i_font_attachments = 0;
294 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
295 if( !p_sys->pp_font_attachments )
298 for( int k = 0; k < i_attachments_cnt; k++ )
300 input_attachment_t *p_attach = pp_attachments[k];
302 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
303 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
304 p_attach->i_data > 0 && p_attach->p_data )
306 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
310 vlc_input_attachment_Delete( p_attach );
313 free( pp_attachments );
318 static int GetFontSize( filter_t *p_filter )
320 filter_sys_t *p_sys = p_filter->p_sys;
323 if( p_sys->i_default_font_size )
325 i_size = p_sys->i_default_font_size;
329 int i_ratio = var_GetInteger( p_filter, "freetype-rel-fontsize" );
332 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
333 p_filter->p_sys->i_display_height = p_filter->fmt_out.video.i_height;
338 msg_Warn( p_filter, "invalid fontsize, using 12" );
344 static int SetFontSize( filter_t *p_filter, int i_size )
346 filter_sys_t *p_sys = p_filter->p_sys;
350 i_size = GetFontSize( p_filter );
352 msg_Dbg( p_filter, "using fontsize: %i", i_size );
355 p_sys->i_font_size = i_size;
357 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
359 msg_Err( p_filter, "couldn't set font size to %d", i_size );
367 #ifdef HAVE_FONTCONFIG
368 static void FontConfig_BuildCache( filter_t *p_filter )
371 msg_Dbg( p_filter, "Building font databases.");
376 dialog_progress_bar_t *p_dialog = NULL;
377 FcConfig *fcConfig = FcInitLoadConfig();
379 p_dialog = dialog_ProgressCreate( p_filter,
380 _("Building font cache"),
381 _("Please wait while your font cache is rebuilt.\n"
382 "This should take less than a few minutes."), NULL );
385 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
387 FcConfigBuildFonts( fcConfig );
390 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
391 dialog_ProgressDestroy( p_dialog );
396 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
400 * \brief Selects a font matching family, bold, italic provided
402 static char* FontConfig_Select( FcConfig* config, const char* family,
403 bool b_bold, bool b_italic, int i_size, int *i_idx )
405 FcResult result = FcResultMatch;
406 FcPattern *pat, *p_pat;
410 /* Create a pattern and fills it */
411 pat = FcPatternCreate();
412 if (!pat) return NULL;
415 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
416 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
417 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
418 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
422 if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
424 FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
425 free( psz_fontsize );
430 FcDefaultSubstitute( pat );
431 if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
433 FcPatternDestroy( pat );
437 /* Find the best font for the pattern, destroy the pattern */
438 p_pat = FcFontMatch( config, pat, &result );
439 FcPatternDestroy( pat );
440 if( !p_pat || result == FcResultNoMatch ) return NULL;
442 /* Check the new pattern */
443 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
444 || ( val_b != FcTrue ) )
446 FcPatternDestroy( p_pat );
449 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
454 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
456 FcPatternDestroy( p_pat );
460 /* if( strcasecmp((const char*)val_s, family ) != 0 )
461 msg_Warn( p_filter, "fontconfig: selected font family is not"
462 "the requested one: '%s' != '%s'\n",
463 (const char*)val_s, family ); */
465 if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
467 FcPatternDestroy( p_pat );
471 FcPatternDestroy( p_pat );
472 return strdup( (const char*)val_s );
478 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
480 static int GetFileFontByName( const char *font_name, char **psz_filename )
483 wchar_t vbuffer[MAX_PATH];
484 wchar_t dbuffer[256];
486 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey) != ERROR_SUCCESS )
489 for( int index = 0;; index++ )
491 DWORD vbuflen = MAX_PATH - 1;
494 if( RegEnumValueW( hKey, index, vbuffer, &vbuflen,
495 NULL, NULL, (LPBYTE)dbuffer, &dbuflen) != ERROR_SUCCESS )
498 char *psz_value = FromWide( vbuffer );
500 char *s = strchr( psz_value,'(' );
501 if( s != NULL && s != psz_value ) s[-1] = '\0';
503 /* Manage concatenated font names */
504 if( strchr( psz_value, '&') ) {
505 if( strcasestr( psz_value, font_name ) != NULL )
509 if( strcasecmp( psz_value, font_name ) == 0 )
514 *psz_filename = FromWide( dbuffer );
519 static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
520 DWORD type, LPARAM lParam)
522 VLC_UNUSED( metric );
523 if( (type & RASTER_FONTTYPE) ) return 1;
524 // if( lpelfe->elfScript ) FIXME
526 return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
529 static char* Win32_Select( filter_t *p_filter, const char* family,
530 bool b_bold, bool b_italic, int i_size, int *i_idx )
532 VLC_UNUSED( i_size );
533 // msg_Dbg( p_filter, "Here in Win32_Select, asking for %s", family );
537 lf.lfCharSet = DEFAULT_CHARSET;
541 lf.lfWeight = FW_BOLD;
542 strncpy( (LPSTR)&lf.lfFaceName, family, 32);
545 char *psz_filename = NULL;
546 HDC hDC = GetDC( NULL );
547 EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
548 ReleaseDC(NULL, hDC);
550 if( psz_filename == NULL )
553 /* FIXME: increase i_idx, when concatenated strings */
558 if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
567 /*****************************************************************************
568 * RenderYUVP: place string in picture
569 *****************************************************************************
570 * This function merges the previously rendered freetype glyphs into a picture
571 *****************************************************************************/
572 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
573 line_desc_t *p_line, int i_width, int i_height )
575 VLC_UNUSED(p_filter);
576 static const uint8_t pi_gamma[16] =
577 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
578 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
582 int i, x, y, i_pitch;
583 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
585 /* Create a new subpicture region */
586 memset( &fmt, 0, sizeof(video_format_t) );
587 fmt.i_chroma = VLC_CODEC_YUVP;
588 fmt.i_width = fmt.i_visible_width = i_width + 4;
589 fmt.i_height = fmt.i_visible_height = i_height + 4;
590 if( p_region->fmt.i_visible_width > 0 )
591 fmt.i_visible_width = p_region->fmt.i_visible_width;
592 if( p_region->fmt.i_visible_height > 0 )
593 fmt.i_visible_height = p_region->fmt.i_visible_height;
594 fmt.i_x_offset = fmt.i_y_offset = 0;
598 assert( !p_region->p_picture );
599 p_region->p_picture = picture_NewFromFormat( &fmt );
600 if( !p_region->p_picture )
602 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
605 /* Calculate text color components
606 * Only use the first color */
607 YUVFromRGB( p_line->p_fg_rgb[ 0 ], &i_y, &i_u, &i_v );
610 fmt.p_palette->i_entries = 16;
611 for( i = 0; i < 8; i++ )
613 fmt.p_palette->palette[i][0] = 0;
614 fmt.p_palette->palette[i][1] = 0x80;
615 fmt.p_palette->palette[i][2] = 0x80;
616 fmt.p_palette->palette[i][3] = pi_gamma[i];
617 fmt.p_palette->palette[i][3] =
618 (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
620 for( i = 8; i < fmt.p_palette->i_entries; i++ )
622 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
623 fmt.p_palette->palette[i][1] = i_u;
624 fmt.p_palette->palette[i][2] = i_v;
625 fmt.p_palette->palette[i][3] = pi_gamma[i];
626 fmt.p_palette->palette[i][3] =
627 (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
630 p_dst = p_region->p_picture->Y_PIXELS;
631 i_pitch = p_region->p_picture->Y_PITCH;
633 /* Initialize the region pixels */
634 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
636 for( ; p_line != NULL; p_line = p_line->p_next )
638 int i_glyph_tmax = 0;
639 int i_bitmap_offset, i_offset, i_align_offset = 0;
640 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
642 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
643 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
646 if( p_line->i_width < i_width )
648 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
650 i_align_offset = i_width - p_line->i_width;
652 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
654 i_align_offset = ( i_width - p_line->i_width ) / 2;
658 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
660 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
662 i_offset = ( p_line->p_glyph_pos[ i ].y +
663 i_glyph_tmax - p_glyph->top + 2 ) *
664 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 2 +
667 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
669 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
671 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
673 ((int)p_glyph->bitmap.buffer[i_bitmap_offset] + 8)/16;
680 /* Outlining (find something better than nearest neighbour filtering ?) */
683 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
684 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
685 uint8_t left, current;
687 for( y = 1; y < (int)fmt.i_height - 1; y++ )
689 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
690 p_dst += p_region->p_picture->Y_PITCH;
693 for( x = 1; x < (int)fmt.i_width - 1; x++ )
696 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
697 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;
701 memset( p_top, 0, fmt.i_width );
707 static void UnderlineGlyphYUVA( int i_line_thickness, int i_line_offset, bool b_ul_next_char,
708 FT_BitmapGlyph p_this_glyph, FT_Vector *p_this_glyph_pos,
709 FT_BitmapGlyph p_next_glyph, FT_Vector *p_next_glyph_pos,
710 int i_glyph_tmax, int i_align_offset,
711 uint8_t i_y, uint8_t i_u, uint8_t i_v,
712 subpicture_region_t *p_region)
715 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
717 p_dst_y = p_region->p_picture->Y_PIXELS;
718 p_dst_u = p_region->p_picture->U_PIXELS;
719 p_dst_v = p_region->p_picture->V_PIXELS;
720 p_dst_a = p_region->p_picture->A_PIXELS;
721 i_pitch = p_region->p_picture->A_PITCH;
723 int i_offset = ( p_this_glyph_pos->y + i_glyph_tmax + i_line_offset + 3 ) * i_pitch +
724 p_this_glyph_pos->x + p_this_glyph->left + 3 + i_align_offset;
726 for( int y = 0; y < i_line_thickness; y++ )
728 int i_extra = p_this_glyph->bitmap.width;
732 i_extra = (p_next_glyph_pos->x + p_next_glyph->left) -
733 (p_this_glyph_pos->x + p_this_glyph->left);
735 for( int x = 0; x < i_extra; x++ )
739 /* break the underline around the tails of any glyphs which cross it */
740 /* Strikethrough doesn't get broken */
741 for( int z = x - i_line_thickness;
742 z < x + i_line_thickness && b_ok && (i_line_offset >= 0);
745 if( p_next_glyph && ( z >= i_extra ) )
747 int i_row = i_line_offset + p_next_glyph->top + y;
749 if( ( p_next_glyph->bitmap.rows > i_row ) &&
750 p_next_glyph->bitmap.buffer[p_next_glyph->bitmap.width * i_row + z-i_extra] )
755 else if ((z > 0 ) && (z < p_this_glyph->bitmap.width))
757 int i_row = i_line_offset + p_this_glyph->top + y;
759 if( ( p_this_glyph->bitmap.rows > i_row ) &&
760 p_this_glyph->bitmap.buffer[p_this_glyph->bitmap.width * i_row + z] )
769 p_dst_y[i_offset+x] = (i_y * 255) >> 8;
770 p_dst_u[i_offset+x] = i_u;
771 p_dst_v[i_offset+x] = i_v;
772 p_dst_a[i_offset+x] = 255;
779 static void DrawBlack( line_desc_t *p_line, int i_width, subpicture_region_t *p_region, int xoffset, int yoffset )
781 uint8_t *p_dst = p_region->p_picture->A_PIXELS;
782 int i_pitch = p_region->p_picture->A_PITCH;
785 for( ; p_line != NULL; p_line = p_line->p_next )
787 int i_glyph_tmax=0, i = 0;
788 int i_bitmap_offset, i_offset, i_align_offset = 0;
789 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
791 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
792 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
795 if( p_line->i_width < i_width )
797 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
799 i_align_offset = i_width - p_line->i_width;
801 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
803 i_align_offset = ( i_width - p_line->i_width ) / 2;
807 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
809 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
811 i_offset = ( p_line->p_glyph_pos[ i ].y +
812 i_glyph_tmax - p_glyph->top + 3 + yoffset ) *
813 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
814 i_align_offset +xoffset;
816 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
818 for( int x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
820 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
821 if( p_dst[i_offset+x] <
822 ((int)p_glyph->bitmap.buffer[i_bitmap_offset]) )
824 ((int)p_glyph->bitmap.buffer[i_bitmap_offset]);
832 /*****************************************************************************
833 * RenderYUVA: place string in picture
834 *****************************************************************************
835 * This function merges the previously rendered freetype glyphs into a picture
836 *****************************************************************************/
837 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
838 line_desc_t *p_line, int i_width, int i_height )
840 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
842 int i, y, i_pitch, i_alpha;
844 if( i_width == 0 || i_height == 0 )
847 /* Create a new subpicture region */
848 memset( &fmt, 0, sizeof(video_format_t) );
849 fmt.i_chroma = VLC_CODEC_YUVA;
850 fmt.i_width = fmt.i_visible_width = i_width + 6;
851 fmt.i_height = fmt.i_visible_height = i_height + 6;
852 if( p_region->fmt.i_visible_width > 0 )
853 fmt.i_visible_width = p_region->fmt.i_visible_width;
854 if( p_region->fmt.i_visible_height > 0 )
855 fmt.i_visible_height = p_region->fmt.i_visible_height;
856 fmt.i_x_offset = fmt.i_y_offset = 0;
860 p_region->p_picture = picture_NewFromFormat( &fmt );
861 if( !p_region->p_picture )
865 /* Save the alpha value */
866 i_alpha = p_line->i_alpha;
868 p_dst_y = p_region->p_picture->Y_PIXELS;
869 p_dst_u = p_region->p_picture->U_PIXELS;
870 p_dst_v = p_region->p_picture->V_PIXELS;
871 p_dst_a = p_region->p_picture->A_PIXELS;
872 i_pitch = p_region->p_picture->A_PITCH;
874 /* Initialize the region pixels */
875 memset( p_dst_y, 0x00, i_pitch * p_region->fmt.i_height );
876 memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
877 memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
879 if( p_filter->p_sys->i_effect != EFFECT_BACKGROUND )
880 memset( p_dst_a, 0x00, i_pitch * p_region->fmt.i_height );
882 memset( p_dst_a, 0x80, i_pitch * p_region->fmt.i_height );
884 if( p_filter->p_sys->i_effect == EFFECT_OUTLINE ||
885 p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
887 DrawBlack( p_line, i_width, p_region, 0, 0);
888 DrawBlack( p_line, i_width, p_region, -1, 0);
889 DrawBlack( p_line, i_width, p_region, 0, -1);
890 DrawBlack( p_line, i_width, p_region, 1, 0);
891 DrawBlack( p_line, i_width, p_region, 0, 1);
894 if( p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
896 DrawBlack( p_line, i_width, p_region, -1, -1);
897 DrawBlack( p_line, i_width, p_region, -1, 1);
898 DrawBlack( p_line, i_width, p_region, 1, -1);
899 DrawBlack( p_line, i_width, p_region, 1, 1);
901 DrawBlack( p_line, i_width, p_region, -2, 0);
902 DrawBlack( p_line, i_width, p_region, 0, -2);
903 DrawBlack( p_line, i_width, p_region, 2, 0);
904 DrawBlack( p_line, i_width, p_region, 0, 2);
906 DrawBlack( p_line, i_width, p_region, -2, -2);
907 DrawBlack( p_line, i_width, p_region, -2, 2);
908 DrawBlack( p_line, i_width, p_region, 2, -2);
909 DrawBlack( p_line, i_width, p_region, 2, 2);
911 DrawBlack( p_line, i_width, p_region, -3, 0);
912 DrawBlack( p_line, i_width, p_region, 0, -3);
913 DrawBlack( p_line, i_width, p_region, 3, 0);
914 DrawBlack( p_line, i_width, p_region, 0, 3);
917 for( ; p_line != NULL; p_line = p_line->p_next )
919 int i_glyph_tmax = 0;
920 int i_bitmap_offset, i_offset, i_align_offset = 0;
921 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
923 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
924 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
927 if( p_line->i_width < i_width )
929 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
931 i_align_offset = i_width - p_line->i_width;
933 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
935 i_align_offset = ( i_width - p_line->i_width ) / 2;
939 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
941 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
943 i_offset = ( p_line->p_glyph_pos[ i ].y +
944 i_glyph_tmax - p_glyph->top + 3 ) *
945 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
948 /* Every glyph can (and in fact must) have its own color */
949 uint8_t i_y, i_u, i_v;
950 YUVFromRGB( p_line->p_fg_rgb[ i ], &i_y, &i_u, &i_v );
952 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
954 for( int x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
956 uint8_t i_y_local = i_y;
957 uint8_t i_u_local = i_u;
958 uint8_t i_v_local = i_v;
960 if( p_line->p_fg_bg_ratio != 0x00 )
962 int i_split = p_glyph->bitmap.width *
963 p_line->p_fg_bg_ratio[ i ] / 0x7f;
967 YUVFromRGB( p_line->p_bg_rgb[ i ],
968 &i_y_local, &i_u_local, &i_v_local );
972 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
974 p_dst_y[i_offset+x] = ((p_dst_y[i_offset+x] *(255-(int)p_glyph->bitmap.buffer[i_bitmap_offset])) +
975 i_y * ((int)p_glyph->bitmap.buffer[i_bitmap_offset])) >> 8;
977 p_dst_u[i_offset+x] = i_u;
978 p_dst_v[i_offset+x] = i_v;
980 if( p_filter->p_sys->i_effect == EFFECT_BACKGROUND )
981 p_dst_a[i_offset+x] = 0xff;
987 if( p_line->pi_underline_thickness[ i ] )
989 UnderlineGlyphYUVA( p_line->pi_underline_thickness[ i ],
990 p_line->pi_underline_offset[ i ],
991 (p_line->pp_glyphs[i+1] && (p_line->pi_underline_thickness[ i + 1] > 0)),
992 p_line->pp_glyphs[i], &(p_line->p_glyph_pos[i]),
993 p_line->pp_glyphs[i+1], &(p_line->p_glyph_pos[i+1]),
994 i_glyph_tmax, i_align_offset,
1001 /* Apply the alpha setting */
1002 for( i = 0; i < (int)fmt.i_height * i_pitch; i++ )
1003 p_dst_a[i] = p_dst_a[i] * (255 - i_alpha) / 255;
1008 static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
1009 uint32_t i_font_color, uint32_t i_karaoke_bg_color,
1012 text_style_t *p_style = text_style_New();
1016 p_style->psz_fontname = strdup( psz_fontname );
1017 p_style->i_font_size = i_font_size;
1018 p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0;
1019 p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
1020 p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0;
1021 p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
1022 p_style->i_style_flags |= i_style_flags;
1026 static bool StyleEquals( text_style_t *s1, text_style_t *s2 )
1033 return s1->i_font_size == s2->i_font_size &&
1034 s1->i_font_color == s2->i_font_color &&
1035 s1->i_font_alpha == s2->i_font_alpha &&
1036 s1->i_style_flags == s2->i_style_flags &&
1037 !strcmp( s1->psz_fontname, s2->psz_fontname );
1040 static void IconvText( filter_t *p_filter, const char *psz_string,
1041 size_t *i_string_length, uint32_t *psz_unicode )
1043 *i_string_length = 0;
1044 if( psz_unicode == NULL )
1049 #if defined(WORDS_BIGENDIAN)
1050 ToCharset( "UCS-4BE", psz_string, &i_length );
1052 ToCharset( "UCS-4LE", psz_string, &i_length );
1056 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1059 memcpy( psz_unicode, psz_tmp, i_length );
1060 *i_string_length = i_length / 4;
1065 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1066 uint32_t i_color, uint32_t i_karaoke_bg_color )
1069 return VLC_EGENERIC;
1071 font_stack_t *p_new = malloc( sizeof(*p_new) );
1075 p_new->p_next = NULL;
1078 p_new->psz_name = strdup( psz_name );
1080 p_new->psz_name = NULL;
1082 p_new->i_size = i_size;
1083 p_new->i_color = i_color;
1084 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1092 font_stack_t *p_last;
1094 for( p_last = *p_font;
1096 p_last = p_last->p_next )
1099 p_last->p_next = p_new;
1104 static int PopFont( font_stack_t **p_font )
1106 font_stack_t *p_last, *p_next_to_last;
1108 if( !p_font || !*p_font )
1109 return VLC_EGENERIC;
1111 p_next_to_last = NULL;
1112 for( p_last = *p_font;
1114 p_last = p_last->p_next )
1116 p_next_to_last = p_last;
1119 if( p_next_to_last )
1120 p_next_to_last->p_next = NULL;
1124 free( p_last->psz_name );
1130 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1131 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1133 font_stack_t *p_last;
1135 if( !p_font || !*p_font )
1136 return VLC_EGENERIC;
1138 for( p_last=*p_font;
1140 p_last=p_last->p_next )
1143 *psz_name = p_last->psz_name;
1144 *i_size = p_last->i_size;
1145 *i_color = p_last->i_color;
1146 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1151 static const struct {
1152 const char *psz_name;
1154 } p_html_colors[] = {
1155 /* Official html colors */
1156 { "Aqua", 0x00FFFF },
1157 { "Black", 0x000000 },
1158 { "Blue", 0x0000FF },
1159 { "Fuchsia", 0xFF00FF },
1160 { "Gray", 0x808080 },
1161 { "Green", 0x008000 },
1162 { "Lime", 0x00FF00 },
1163 { "Maroon", 0x800000 },
1164 { "Navy", 0x000080 },
1165 { "Olive", 0x808000 },
1166 { "Purple", 0x800080 },
1167 { "Red", 0xFF0000 },
1168 { "Silver", 0xC0C0C0 },
1169 { "Teal", 0x008080 },
1170 { "White", 0xFFFFFF },
1171 { "Yellow", 0xFFFF00 },
1174 { "AliceBlue", 0xF0F8FF },
1175 { "AntiqueWhite", 0xFAEBD7 },
1176 { "Aqua", 0x00FFFF },
1177 { "Aquamarine", 0x7FFFD4 },
1178 { "Azure", 0xF0FFFF },
1179 { "Beige", 0xF5F5DC },
1180 { "Bisque", 0xFFE4C4 },
1181 { "Black", 0x000000 },
1182 { "BlanchedAlmond", 0xFFEBCD },
1183 { "Blue", 0x0000FF },
1184 { "BlueViolet", 0x8A2BE2 },
1185 { "Brown", 0xA52A2A },
1186 { "BurlyWood", 0xDEB887 },
1187 { "CadetBlue", 0x5F9EA0 },
1188 { "Chartreuse", 0x7FFF00 },
1189 { "Chocolate", 0xD2691E },
1190 { "Coral", 0xFF7F50 },
1191 { "CornflowerBlue", 0x6495ED },
1192 { "Cornsilk", 0xFFF8DC },
1193 { "Crimson", 0xDC143C },
1194 { "Cyan", 0x00FFFF },
1195 { "DarkBlue", 0x00008B },
1196 { "DarkCyan", 0x008B8B },
1197 { "DarkGoldenRod", 0xB8860B },
1198 { "DarkGray", 0xA9A9A9 },
1199 { "DarkGrey", 0xA9A9A9 },
1200 { "DarkGreen", 0x006400 },
1201 { "DarkKhaki", 0xBDB76B },
1202 { "DarkMagenta", 0x8B008B },
1203 { "DarkOliveGreen", 0x556B2F },
1204 { "Darkorange", 0xFF8C00 },
1205 { "DarkOrchid", 0x9932CC },
1206 { "DarkRed", 0x8B0000 },
1207 { "DarkSalmon", 0xE9967A },
1208 { "DarkSeaGreen", 0x8FBC8F },
1209 { "DarkSlateBlue", 0x483D8B },
1210 { "DarkSlateGray", 0x2F4F4F },
1211 { "DarkSlateGrey", 0x2F4F4F },
1212 { "DarkTurquoise", 0x00CED1 },
1213 { "DarkViolet", 0x9400D3 },
1214 { "DeepPink", 0xFF1493 },
1215 { "DeepSkyBlue", 0x00BFFF },
1216 { "DimGray", 0x696969 },
1217 { "DimGrey", 0x696969 },
1218 { "DodgerBlue", 0x1E90FF },
1219 { "FireBrick", 0xB22222 },
1220 { "FloralWhite", 0xFFFAF0 },
1221 { "ForestGreen", 0x228B22 },
1222 { "Fuchsia", 0xFF00FF },
1223 { "Gainsboro", 0xDCDCDC },
1224 { "GhostWhite", 0xF8F8FF },
1225 { "Gold", 0xFFD700 },
1226 { "GoldenRod", 0xDAA520 },
1227 { "Gray", 0x808080 },
1228 { "Grey", 0x808080 },
1229 { "Green", 0x008000 },
1230 { "GreenYellow", 0xADFF2F },
1231 { "HoneyDew", 0xF0FFF0 },
1232 { "HotPink", 0xFF69B4 },
1233 { "IndianRed", 0xCD5C5C },
1234 { "Indigo", 0x4B0082 },
1235 { "Ivory", 0xFFFFF0 },
1236 { "Khaki", 0xF0E68C },
1237 { "Lavender", 0xE6E6FA },
1238 { "LavenderBlush", 0xFFF0F5 },
1239 { "LawnGreen", 0x7CFC00 },
1240 { "LemonChiffon", 0xFFFACD },
1241 { "LightBlue", 0xADD8E6 },
1242 { "LightCoral", 0xF08080 },
1243 { "LightCyan", 0xE0FFFF },
1244 { "LightGoldenRodYellow", 0xFAFAD2 },
1245 { "LightGray", 0xD3D3D3 },
1246 { "LightGrey", 0xD3D3D3 },
1247 { "LightGreen", 0x90EE90 },
1248 { "LightPink", 0xFFB6C1 },
1249 { "LightSalmon", 0xFFA07A },
1250 { "LightSeaGreen", 0x20B2AA },
1251 { "LightSkyBlue", 0x87CEFA },
1252 { "LightSlateGray", 0x778899 },
1253 { "LightSlateGrey", 0x778899 },
1254 { "LightSteelBlue", 0xB0C4DE },
1255 { "LightYellow", 0xFFFFE0 },
1256 { "Lime", 0x00FF00 },
1257 { "LimeGreen", 0x32CD32 },
1258 { "Linen", 0xFAF0E6 },
1259 { "Magenta", 0xFF00FF },
1260 { "Maroon", 0x800000 },
1261 { "MediumAquaMarine", 0x66CDAA },
1262 { "MediumBlue", 0x0000CD },
1263 { "MediumOrchid", 0xBA55D3 },
1264 { "MediumPurple", 0x9370D8 },
1265 { "MediumSeaGreen", 0x3CB371 },
1266 { "MediumSlateBlue", 0x7B68EE },
1267 { "MediumSpringGreen", 0x00FA9A },
1268 { "MediumTurquoise", 0x48D1CC },
1269 { "MediumVioletRed", 0xC71585 },
1270 { "MidnightBlue", 0x191970 },
1271 { "MintCream", 0xF5FFFA },
1272 { "MistyRose", 0xFFE4E1 },
1273 { "Moccasin", 0xFFE4B5 },
1274 { "NavajoWhite", 0xFFDEAD },
1275 { "Navy", 0x000080 },
1276 { "OldLace", 0xFDF5E6 },
1277 { "Olive", 0x808000 },
1278 { "OliveDrab", 0x6B8E23 },
1279 { "Orange", 0xFFA500 },
1280 { "OrangeRed", 0xFF4500 },
1281 { "Orchid", 0xDA70D6 },
1282 { "PaleGoldenRod", 0xEEE8AA },
1283 { "PaleGreen", 0x98FB98 },
1284 { "PaleTurquoise", 0xAFEEEE },
1285 { "PaleVioletRed", 0xD87093 },
1286 { "PapayaWhip", 0xFFEFD5 },
1287 { "PeachPuff", 0xFFDAB9 },
1288 { "Peru", 0xCD853F },
1289 { "Pink", 0xFFC0CB },
1290 { "Plum", 0xDDA0DD },
1291 { "PowderBlue", 0xB0E0E6 },
1292 { "Purple", 0x800080 },
1293 { "Red", 0xFF0000 },
1294 { "RosyBrown", 0xBC8F8F },
1295 { "RoyalBlue", 0x4169E1 },
1296 { "SaddleBrown", 0x8B4513 },
1297 { "Salmon", 0xFA8072 },
1298 { "SandyBrown", 0xF4A460 },
1299 { "SeaGreen", 0x2E8B57 },
1300 { "SeaShell", 0xFFF5EE },
1301 { "Sienna", 0xA0522D },
1302 { "Silver", 0xC0C0C0 },
1303 { "SkyBlue", 0x87CEEB },
1304 { "SlateBlue", 0x6A5ACD },
1305 { "SlateGray", 0x708090 },
1306 { "SlateGrey", 0x708090 },
1307 { "Snow", 0xFFFAFA },
1308 { "SpringGreen", 0x00FF7F },
1309 { "SteelBlue", 0x4682B4 },
1310 { "Tan", 0xD2B48C },
1311 { "Teal", 0x008080 },
1312 { "Thistle", 0xD8BFD8 },
1313 { "Tomato", 0xFF6347 },
1314 { "Turquoise", 0x40E0D0 },
1315 { "Violet", 0xEE82EE },
1316 { "Wheat", 0xF5DEB3 },
1317 { "White", 0xFFFFFF },
1318 { "WhiteSmoke", 0xF5F5F5 },
1319 { "Yellow", 0xFFFF00 },
1320 { "YellowGreen", 0x9ACD32 },
1325 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1326 font_stack_t **p_fonts )
1329 char *psz_fontname = NULL;
1330 uint32_t i_font_color = 0xffffff;
1331 int i_font_alpha = 0;
1332 uint32_t i_karaoke_bg_color = 0x00ffffff;
1333 int i_font_size = 24;
1335 /* Default all attributes to the top font in the stack -- in case not
1336 * all attributes are specified in the sub-font
1338 if( VLC_SUCCESS == PeekFont( p_fonts,
1342 &i_karaoke_bg_color ))
1344 psz_fontname = strdup( psz_fontname );
1345 i_font_size = i_font_size;
1347 i_font_alpha = (i_font_color >> 24) & 0xff;
1348 i_font_color &= 0x00ffffff;
1350 const char *name, *value;
1351 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1353 if( !strcasecmp( "face", name ) )
1355 free( psz_fontname );
1356 psz_fontname = strdup( value );
1358 else if( !strcasecmp( "size", name ) )
1360 if( ( *value == '+' ) || ( *value == '-' ) )
1362 int i_value = atoi( value );
1364 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1365 i_font_size += ( i_value * i_font_size ) / 10;
1366 else if( i_value < -5 )
1367 i_font_size = - i_value;
1368 else if( i_value > 5 )
1369 i_font_size = i_value;
1372 i_font_size = atoi( value );
1374 else if( !strcasecmp( "color", name ) )
1376 if( value[0] == '#' )
1378 i_font_color = strtol( value + 1, NULL, 16 );
1379 i_font_color &= 0x00ffffff;
1383 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1385 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1387 i_font_color = p_html_colors[i].i_value;
1393 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1395 i_font_alpha = strtol( value + 1, NULL, 16 );
1396 i_font_alpha &= 0xff;
1399 rv = PushFont( p_fonts,
1402 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1403 i_karaoke_bg_color );
1405 free( psz_fontname );
1410 static void SetKaraokeLen( uint32_t i_runs, uint32_t *pi_run_lengths,
1411 uint32_t i_k_runs, uint32_t *pi_k_run_lengths )
1413 /* Karaoke tags _PRECEDE_ the text they specify a duration
1414 * for, therefore we are working out the length for the
1415 * previous tag, and first time through we have nothing
1417 if( pi_k_run_lengths )
1422 /* Work out how many characters are presently in the string
1424 for( i = 0; i < i_runs; i++ )
1425 i_chars += pi_run_lengths[ i ];
1427 /* Subtract away those we've already allocated to other
1430 for( i = 0; i < i_k_runs; i++ )
1431 i_chars -= pi_k_run_lengths[ i ];
1433 pi_k_run_lengths[ i_k_runs - 1 ] = i_chars;
1437 static void SetupKaraoke( xml_reader_t *p_xml_reader, uint32_t *pi_k_runs,
1438 uint32_t **ppi_k_run_lengths,
1439 uint32_t **ppi_k_durations )
1441 const char *name, *value;
1443 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1445 if( !strcasecmp( "t", name ) )
1447 if( ppi_k_durations && ppi_k_run_lengths )
1451 if( *ppi_k_durations )
1453 *ppi_k_durations = realloc_or_free( *ppi_k_durations,
1454 *pi_k_runs * sizeof(**ppi_k_durations) );
1456 else if( *pi_k_runs == 1 )
1458 *ppi_k_durations = malloc( *pi_k_runs * sizeof(**ppi_k_durations) );
1461 if( *ppi_k_run_lengths )
1463 *ppi_k_run_lengths = realloc_or_free( *ppi_k_run_lengths,
1464 *pi_k_runs * sizeof(**ppi_k_run_lengths) );
1466 else if( *pi_k_runs == 1 )
1468 *ppi_k_run_lengths = malloc( *pi_k_runs * sizeof(**ppi_k_run_lengths) );
1470 if( *ppi_k_durations )
1471 (*ppi_k_durations)[ *pi_k_runs - 1 ] = atoi( value );
1473 if( *ppi_k_run_lengths )
1474 (*ppi_k_run_lengths)[ *pi_k_runs - 1 ] = 0;
1480 /* Turn any multiple-whitespaces into single spaces */
1481 static void HandleWhiteSpace( char *psz_node )
1483 char *s = strpbrk( psz_node, "\t\r\n " );
1486 int i_whitespace = strspn( s, "\t\r\n " );
1488 if( i_whitespace > 1 )
1491 strlen( s ) - i_whitespace + 1 );
1494 s = strpbrk( s, "\t\r\n " );
1499 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1500 font_stack_t **p_fonts,
1503 char *psz_fontname = NULL;
1504 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1505 uint32_t i_karaoke_bg_color = i_font_color;
1506 int i_font_size = p_sys->i_font_size;
1508 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1509 &i_font_color, &i_karaoke_bg_color ) )
1512 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1517 static int RenderTag( filter_t *p_filter, FT_Face p_face,
1520 int i_karaoke_bgcolor,
1521 line_desc_t *p_line, uint32_t *psz_unicode,
1522 int *pi_pen_x, int i_pen_y, int *pi_start,
1523 FT_Vector *p_result )
1528 bool b_first_on_line = true;
1531 int i_pen_x_start = *pi_pen_x;
1533 uint32_t *psz_unicode_start = psz_unicode;
1535 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1537 /* Account for part of line already in position */
1538 for( i = 0; i<*pi_start; i++ )
1542 FT_Glyph_Get_CBox( (FT_Glyph) p_line->pp_glyphs[ i ],
1543 ft_glyph_bbox_pixels, &glyph_size );
1545 line.xMax = p_line->p_glyph_pos[ i ].x + glyph_size.xMax -
1546 glyph_size.xMin + p_line->pp_glyphs[ i ]->left;
1547 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1548 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1554 b_first_on_line = false;
1556 while( *psz_unicode && ( *psz_unicode != '\n' ) )
1562 int i_glyph_index = FT_Get_Char_Index( p_face, *psz_unicode++ );
1563 if( FT_HAS_KERNING( p_face ) && i_glyph_index
1567 FT_Get_Kerning( p_face, i_previous, i_glyph_index,
1568 ft_kerning_default, &delta );
1569 *pi_pen_x += delta.x >> 6;
1571 p_line->p_glyph_pos[ i ].x = *pi_pen_x;
1572 p_line->p_glyph_pos[ i ].y = i_pen_y;
1574 i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT );
1577 i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT );
1581 "unable to render text FT_Load_Glyph returned %d", i_error );
1582 p_line->pp_glyphs[ i ] = NULL;
1583 return VLC_EGENERIC;
1587 /* Do synthetic styling now that Freetype supports it;
1588 * ie. if the font we have loaded is NOT already in the
1589 * style that the tags want, then switch it on; if they
1590 * are then don't. */
1591 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1592 FT_GlyphSlot_Embolden( p_face->glyph );
1593 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1594 FT_GlyphSlot_Oblique( p_face->glyph );
1596 i_error = FT_Get_Glyph( p_face->glyph, &tmp_glyph );
1600 "unable to render text FT_Get_Glyph returned %d", i_error );
1601 p_line->pp_glyphs[ i ] = NULL;
1602 return VLC_EGENERIC;
1604 FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
1605 i_error = FT_Glyph_To_Bitmap( &tmp_glyph, FT_RENDER_MODE_NORMAL, 0, 1);
1608 FT_Done_Glyph( tmp_glyph );
1611 if( i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1613 float aOffset = FT_FLOOR(FT_MulFix(p_face->underline_position,
1614 p_face->size->metrics.y_scale));
1615 float aSize = FT_CEIL(FT_MulFix(p_face->underline_thickness,
1616 p_face->size->metrics.y_scale));
1618 p_line->pi_underline_offset[ i ] =
1619 ( aOffset < 0 ) ? -aOffset : aOffset;
1620 p_line->pi_underline_thickness[ i ] =
1621 ( aSize < 0 ) ? -aSize : aSize;
1622 if (i_style_flags & STYLE_STRIKEOUT)
1624 /* Move the baseline to make it strikethrough instead of
1625 * underline. That means that strikethrough takes precedence
1627 float aDescent = FT_FLOOR(FT_MulFix(p_face->descender*2,
1628 p_face->size->metrics.y_scale));
1630 p_line->pi_underline_offset[ i ] -=
1631 ( aDescent < 0 ) ? -aDescent : aDescent;
1635 p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
1636 p_line->p_fg_rgb[ i ] = i_font_color;
1637 p_line->p_bg_rgb[ i ] = i_karaoke_bgcolor;
1638 p_line->p_fg_bg_ratio[ i ] = 0x00;
1640 line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
1641 glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
1642 if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
1644 for( ; i >= *pi_start; i-- )
1645 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
1648 while( psz_unicode > psz_unicode_start && *psz_unicode != ' ' )
1652 if( psz_unicode == psz_unicode_start )
1654 if( b_first_on_line )
1656 msg_Warn( p_filter, "unbreakable string" );
1657 p_line->pp_glyphs[ i ] = NULL;
1658 return VLC_EGENERIC;
1660 *pi_pen_x = i_pen_x_start;
1662 p_line->i_width = line.xMax;
1663 p_line->i_height = __MAX( p_line->i_height,
1664 p_face->size->metrics.height >> 6 );
1665 p_line->pp_glyphs[ i ] = NULL;
1667 p_result->x = __MAX( p_result->x, line.xMax );
1668 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1669 i_yMax - i_yMin ) );
1674 *psz_unicode = '\n';
1676 psz_unicode = psz_unicode_start;
1677 *pi_pen_x = i_pen_x_start;
1685 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1686 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1688 i_previous = i_glyph_index;
1689 *pi_pen_x += p_face->glyph->advance.x >> 6;
1692 p_line->i_width = line.xMax;
1693 p_line->i_height = __MAX( p_line->i_height,
1694 p_face->size->metrics.height >> 6 );
1695 p_line->pp_glyphs[ i ] = NULL;
1697 p_result->x = __MAX( p_result->x, line.xMax );
1698 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1699 line.yMax - line.yMin ) );
1703 /* Get rid of any text processed - if necessary repositioning
1704 * at the start of a new line of text
1708 *psz_unicode_start = '\0';
1710 else if( psz_unicode > psz_unicode_start )
1712 for( i=0; psz_unicode[ i ]; i++ )
1713 psz_unicode_start[ i ] = psz_unicode[ i ];
1714 psz_unicode_start[ i ] = '\0';
1720 static void SetupLine( filter_t *p_filter, const char *psz_text_in,
1721 uint32_t **ppsz_text_out, uint32_t *pi_runs,
1722 uint32_t **ppi_run_lengths, text_style_t ***ppp_styles,
1723 text_style_t *p_style )
1725 size_t i_string_length;
1727 IconvText( p_filter, psz_text_in, &i_string_length, *ppsz_text_out );
1728 *ppsz_text_out += i_string_length;
1730 if( ppp_styles && ppi_run_lengths )
1734 /* XXX this logic looks somewhat broken */
1738 *ppp_styles = realloc_or_free( *ppp_styles,
1739 *pi_runs * sizeof(**ppp_styles) );
1741 else if( *pi_runs == 1 )
1743 *ppp_styles = malloc( *pi_runs * sizeof(**ppp_styles) );
1746 /* We have just malloc'ed this memory successfully -
1747 * *pi_runs HAS to be within the memory area of *ppp_styles */
1750 (*ppp_styles)[ *pi_runs - 1 ] = p_style;
1754 /* XXX more iffy logic */
1756 if( *ppi_run_lengths )
1758 *ppi_run_lengths = realloc_or_free( *ppi_run_lengths,
1759 *pi_runs * sizeof(**ppi_run_lengths) );
1761 else if( *pi_runs == 1 )
1763 *ppi_run_lengths = malloc( *pi_runs * sizeof(**ppi_run_lengths) );
1766 /* same remarks here */
1767 if( *ppi_run_lengths )
1769 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
1772 /* If we couldn't use the p_style argument due to memory allocation
1773 * problems above, release it here.
1775 text_style_Delete( p_style );
1778 static int CheckForEmbeddedFont( filter_sys_t *p_sys, FT_Face *pp_face, text_style_t *p_style )
1780 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1782 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1784 FT_Face p_face = NULL;
1786 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1794 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1795 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1796 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1797 (p_style->i_style_flags & (STYLE_BOLD | STYLE_BOLD)) == i_style_received )
1802 FT_Done_Face( p_face );
1807 return VLC_EGENERIC;
1810 static int ProcessNodes( filter_t *p_filter,
1811 xml_reader_t *p_xml_reader,
1812 text_style_t *p_font_style,
1817 uint32_t **ppi_run_lengths,
1818 text_style_t * **ppp_styles,
1821 uint32_t *pi_k_runs,
1822 uint32_t **ppi_k_run_lengths,
1823 uint32_t **ppi_k_durations )
1825 int rv = VLC_SUCCESS;
1826 filter_sys_t *p_sys = p_filter->p_sys;
1827 uint32_t *psz_text_orig = psz_text;
1828 font_stack_t *p_fonts = NULL;
1830 int i_style_flags = 0;
1834 rv = PushFont( &p_fonts,
1835 p_font_style->psz_fontname,
1836 p_font_style->i_font_size,
1837 (p_font_style->i_font_color & 0xffffff) |
1838 ((p_font_style->i_font_alpha & 0xff) << 24),
1839 (p_font_style->i_karaoke_background_color & 0xffffff) |
1840 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1842 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1850 rv = PushFont( &p_fonts,
1851 p_sys->psz_fontfamily,
1853 (p_sys->i_font_color & 0xffffff) |
1854 (((255-p_sys->i_font_opacity) & 0xff) << 24),
1859 if( rv != VLC_SUCCESS )
1865 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1869 case XML_READER_ENDELEM:
1870 if( !strcasecmp( "font", node ) )
1871 PopFont( &p_fonts );
1872 else if( !strcasecmp( "b", node ) )
1873 i_style_flags &= ~STYLE_BOLD;
1874 else if( !strcasecmp( "i", node ) )
1875 i_style_flags &= ~STYLE_ITALIC;
1876 else if( !strcasecmp( "u", node ) )
1877 i_style_flags &= ~STYLE_UNDERLINE;
1878 else if( !strcasecmp( "s", node ) )
1879 i_style_flags &= ~STYLE_STRIKEOUT;
1882 case XML_READER_STARTELEM:
1883 if( !strcasecmp( "font", node ) )
1884 rv = HandleFontAttributes( p_xml_reader, &p_fonts );
1885 else if( !strcasecmp( "b", node ) )
1886 i_style_flags |= STYLE_BOLD;
1887 else if( !strcasecmp( "i", node ) )
1888 i_style_flags |= STYLE_ITALIC;
1889 else if( !strcasecmp( "u", node ) )
1890 i_style_flags |= STYLE_UNDERLINE;
1891 else if( !strcasecmp( "s", node ) )
1892 i_style_flags |= STYLE_STRIKEOUT;
1893 else if( !strcasecmp( "br", node ) )
1895 SetupLine( p_filter, "\n", &psz_text,
1896 pi_runs, ppi_run_lengths, ppp_styles,
1897 GetStyleFromFontStack( p_sys,
1901 else if( !strcasecmp( "k", node ) )
1903 /* Only valid in karaoke */
1906 if( *pi_k_runs > 0 )
1907 SetKaraokeLen( *pi_runs, *ppi_run_lengths,
1908 *pi_k_runs, *ppi_k_run_lengths );
1909 SetupKaraoke( p_xml_reader, pi_k_runs,
1910 ppi_k_run_lengths, ppi_k_durations );
1915 case XML_READER_TEXT:
1917 char *psz_node = strdup( node );
1918 if( unlikely(!psz_node) )
1921 HandleWhiteSpace( psz_node );
1922 resolve_xml_special_chars( psz_node );
1924 SetupLine( p_filter, psz_node, &psz_text,
1925 pi_runs, ppi_run_lengths, ppp_styles,
1926 GetStyleFromFontStack( p_sys,
1933 if( rv != VLC_SUCCESS )
1935 psz_text = psz_text_orig;
1941 SetKaraokeLen( *pi_runs, *ppi_run_lengths,
1942 *pi_k_runs, *ppi_k_run_lengths );
1945 *pi_len = psz_text - psz_text_orig;
1947 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1952 static void FreeLine( line_desc_t *p_line )
1954 for( int i = 0; p_line->pp_glyphs && p_line->pp_glyphs[i] != NULL; i++ )
1955 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[i] );
1957 free( p_line->pp_glyphs );
1958 free( p_line->p_glyph_pos );
1959 free( p_line->p_fg_rgb );
1960 free( p_line->p_bg_rgb );
1961 free( p_line->p_fg_bg_ratio );
1962 free( p_line->pi_underline_offset );
1963 free( p_line->pi_underline_thickness );
1967 static void FreeLines( line_desc_t *p_lines )
1969 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1971 line_desc_t *p_next = p_line->p_next;
1977 static line_desc_t *NewLine( int i_count )
1979 line_desc_t *p_line = malloc( sizeof(*p_line) );
1984 p_line->i_width = 0;
1985 p_line->i_height = 0;
1986 p_line->i_alpha = 0xff;
1988 p_line->p_next = NULL;
1990 p_line->pp_glyphs = calloc( i_count + 1, sizeof(*p_line->pp_glyphs) );
1991 p_line->p_glyph_pos = calloc( i_count + 1, sizeof(*p_line->p_glyph_pos) );
1992 p_line->p_fg_rgb = calloc( i_count + 1, sizeof(*p_line->p_fg_rgb) );
1993 p_line->p_bg_rgb = calloc( i_count + 1, sizeof(*p_line->p_bg_rgb) );
1994 p_line->p_fg_bg_ratio = calloc( i_count + 1, sizeof(*p_line->p_fg_bg_ratio) );
1995 p_line->pi_underline_offset = calloc( i_count + 1, sizeof(*p_line->pi_underline_offset) );
1996 p_line->pi_underline_thickness = calloc( i_count + 1, sizeof(*p_line->pi_underline_thickness) );
1998 if( !p_line->pp_glyphs || !p_line->p_glyph_pos ||
1999 !p_line->p_fg_rgb || !p_line->p_bg_rgb || !p_line->p_fg_bg_ratio ||
2000 !p_line->pi_underline_offset || !p_line->pi_underline_thickness )
2005 p_line->pp_glyphs[0] = NULL;
2010 static int ProcessLines( filter_t *p_filter,
2015 uint32_t *pi_run_lengths,
2016 text_style_t **pp_styles,
2017 line_desc_t **pp_lines,
2019 FT_Vector *p_result,
2023 uint32_t *pi_k_run_lengths,
2024 uint32_t *pi_k_durations )
2026 filter_sys_t *p_sys = p_filter->p_sys;
2027 text_style_t **pp_char_styles;
2028 int *p_new_positions = NULL;
2029 int8_t *p_levels = NULL;
2030 uint8_t *pi_karaoke_bar = NULL;
2034 /* Assign each character in the text string its style explicitly, so that
2035 * after the characters have been shuffled around by Fribidi, we can re-apply
2036 * the styles, and to simplify the calculation of runs within a line.
2038 pp_char_styles = malloc( i_len * sizeof(*pp_char_styles));
2039 if( !pp_char_styles )
2044 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
2045 /* If we can't allocate sufficient memory for karaoke, continue anyway -
2046 * we just won't be able to display the progress bar; at least we'll
2052 for( j = 0; j < i_runs; j++ )
2053 for( k = 0; k < pi_run_lengths[ j ]; k++ )
2054 pp_char_styles[ i++ ] = pp_styles[ j ];
2056 #if defined(HAVE_FRIBIDI)
2058 text_style_t **pp_char_styles_new;
2059 int *p_old_positions;
2060 uint32_t *p_fribidi_string;
2061 int start_pos, pos = 0;
2063 pp_char_styles_new = malloc( i_len * sizeof(*pp_char_styles_new));
2065 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
2066 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
2067 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
2068 p_levels = malloc( (i_len + 1) * sizeof(*p_levels) );
2070 if( ! pp_char_styles_new ||
2071 ! p_fribidi_string ||
2072 ! p_old_positions ||
2073 ! p_new_positions ||
2077 free( p_old_positions );
2078 free( p_new_positions );
2079 free( p_fribidi_string );
2080 free( pp_char_styles_new );
2081 free( pi_karaoke_bar );
2083 free( pp_char_styles );
2087 /* Do bidi conversion line-by-line */
2090 while(pos < i_len) {
2091 if (psz_text[pos] != '\n')
2093 p_fribidi_string[pos] = psz_text[pos];
2094 pp_char_styles_new[pos] = pp_char_styles[pos];
2095 p_new_positions[pos] = pos;
2100 while(pos < i_len) {
2101 if (psz_text[pos] == '\n')
2105 if (pos > start_pos)
2107 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
2108 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
2110 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
2112 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
2113 pos - start_pos, &base_dir,
2114 (FriBidiChar*)p_fribidi_string + start_pos,
2115 p_new_positions + start_pos,
2117 p_levels + start_pos );
2118 for( j = (uint32_t) start_pos; j < (uint32_t) pos; j++ )
2120 pp_char_styles_new[ j ] = pp_char_styles[ start_pos +
2121 p_old_positions[ j - start_pos ] ];
2122 p_new_positions[ j ] += start_pos;
2126 free( p_old_positions );
2127 free( pp_char_styles );
2128 pp_char_styles = pp_char_styles_new;
2129 psz_text = p_fribidi_string;
2130 p_fribidi_string[ i_len ] = 0;
2133 /* Work out the karaoke */
2134 if( pi_karaoke_bar )
2136 int64_t i_last_duration = 0;
2137 int64_t i_duration = 0;
2138 int64_t i_start_pos = 0;
2139 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
2141 for( k = 0; k< i_k_runs; k++ )
2143 double fraction = 0.0;
2145 i_duration += pi_k_durations[ k ];
2147 if( i_duration < i_elapsed )
2149 /* Completely finished this run-length -
2150 * let it render normally */
2154 else if( i_elapsed < i_last_duration )
2156 /* Haven't got up to this segment yet -
2157 * render it completely in karaoke BG mode */
2163 /* Partway through this run */
2165 fraction = (double)(i_elapsed - i_last_duration) /
2166 (double)pi_k_durations[ k ];
2168 for( i = 0; i < pi_k_run_lengths[ k ]; i++ )
2170 double shade = pi_k_run_lengths[ k ] * fraction;
2172 if( p_new_positions )
2173 j = p_new_positions[ i_start_pos + i ];
2175 j = i_start_pos + i;
2177 if( i < (uint32_t)shade )
2178 pi_karaoke_bar[ j ] = 0xff;
2179 else if( (double)i > shade )
2180 pi_karaoke_bar[ j ] = 0x00;
2183 shade -= (int)shade;
2184 pi_karaoke_bar[ j ] = ((int)(shade * 128.0) & 0x7f) |
2185 ((p_levels ? (p_levels[ j ] % 2) : 0 ) << 7);
2189 i_last_duration = i_duration;
2190 i_start_pos += pi_k_run_lengths[ k ];
2194 free( p_new_positions );
2196 FT_Vector tmp_result;
2198 line_desc_t *p_line = NULL;
2199 line_desc_t *p_prev = NULL;
2205 p_result->x = p_result->y = 0;
2206 tmp_result.x = tmp_result.y = 0;
2209 for( k = 0; k <= (uint32_t) i_len; k++ )
2211 if( ( k == (uint32_t) i_len ) ||
2213 !StyleEquals( pp_char_styles[ k ], pp_char_styles[ k - 1] ) ) )
2215 text_style_t *p_style = pp_char_styles[ k - 1 ];
2217 /* End of the current style run */
2218 FT_Face p_face = NULL;
2221 /* Look for a match amongst our attachments first */
2222 CheckForEmbeddedFont( p_sys, &p_face, p_style );
2228 #ifdef HAVE_FONTCONFIG
2229 psz_fontfile = FontConfig_Select( NULL,
2230 p_style->psz_fontname,
2231 (p_style->i_style_flags & STYLE_BOLD) != 0,
2232 (p_style->i_style_flags & STYLE_ITALIC) != 0,
2235 #elif defined( WIN32 )
2236 psz_fontfile = Win32_Select( p_filter,
2237 p_style->psz_fontname,
2243 psz_fontfile = NULL;
2245 if( psz_fontfile && ! *psz_fontfile )
2248 "We were not able to find a matching font: \"%s\" (%s %s),"
2249 " so using default font",
2250 p_style->psz_fontname,
2251 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
2252 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
2253 free( psz_fontfile );
2254 psz_fontfile = NULL;
2259 if( FT_New_Face( p_sys->p_library,
2260 psz_fontfile, i_idx, &p_face ) )
2262 free( psz_fontfile );
2263 free( pp_char_styles );
2264 #if defined(HAVE_FRIBIDI)
2267 free( pi_karaoke_bar );
2268 return VLC_EGENERIC;
2270 free( psz_fontfile );
2274 FT_Select_Charmap( p_face, ft_encoding_unicode ) )
2276 /* We've loaded a font face which is unhelpful for actually
2277 * rendering text - fallback to the default one.
2279 FT_Done_Face( p_face );
2283 if( FT_Select_Charmap( p_face ? p_face : p_sys->p_face,
2284 ft_encoding_unicode ) ||
2285 FT_Set_Pixel_Sizes( p_face ? p_face : p_sys->p_face, 0,
2286 p_style->i_font_size ) )
2288 if( p_face ) FT_Done_Face( p_face );
2289 free( pp_char_styles );
2290 #if defined(HAVE_FRIBIDI)
2293 free( pi_karaoke_bar );
2294 return VLC_EGENERIC;
2296 p_sys->i_use_kerning =
2297 FT_HAS_KERNING( ( p_face ? p_face : p_sys->p_face ) );
2300 uint32_t *psz_unicode = malloc( (k - i_prev + 1) * sizeof(*psz_unicode) );
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 memcpy( psz_unicode, psz_text + i_prev,
2313 sizeof( uint32_t ) * ( k - i_prev ) );
2314 psz_unicode[ k - i_prev ] = 0;
2315 while( *psz_unicode )
2319 if( !(p_line = NewLine( i_len - i_prev)) )
2321 if( p_face ) FT_Done_Face( p_face );
2322 free( pp_char_styles );
2323 free( psz_unicode );
2324 #if defined(HAVE_FRIBIDI)
2327 free( pi_karaoke_bar );
2330 p_line->i_alpha = p_style->i_font_alpha & 0xff;
2332 i_pen_y += tmp_result.y;
2336 if( p_prev ) p_prev->p_next = p_line;
2337 else *pp_lines = p_line;
2340 if( RenderTag( p_filter, p_face ? p_face : p_sys->p_face,
2341 p_style->i_font_color,
2342 p_style->i_style_flags,
2343 p_style->i_karaoke_background_color,
2344 p_line, psz_unicode, &i_pen_x, i_pen_y, &i_posn,
2345 &tmp_result ) != VLC_SUCCESS )
2347 if( p_face ) FT_Done_Face( p_face );
2348 free( pp_char_styles );
2349 free( psz_unicode );
2350 #if defined(HAVE_FRIBIDI)
2353 free( pi_karaoke_bar );
2354 return VLC_EGENERIC;
2359 p_result->x = __MAX( p_result->x, tmp_result.x );
2360 p_result->y += tmp_result.y;
2365 if( *psz_unicode == '\n')
2369 for( c_ptr = psz_unicode; *c_ptr; c_ptr++ )
2371 *c_ptr = *(c_ptr+1);
2376 free( psz_unicode );
2377 if( p_face ) FT_Done_Face( p_face );
2381 free( pp_char_styles );
2382 #if defined(HAVE_FRIBIDI)
2387 p_result->x = __MAX( p_result->x, tmp_result.x );
2388 p_result->y += tmp_result.y;
2391 if( pi_karaoke_bar )
2394 for( p_line = *pp_lines; p_line; p_line=p_line->p_next )
2396 for( k = 0; p_line->pp_glyphs[ k ]; k++, i++ )
2398 if( (pi_karaoke_bar[ i ] & 0x7f) == 0x7f)
2402 else if( (pi_karaoke_bar[ i ] & 0x7f) == 0x00)
2404 /* 100% BG colour will render faster if we
2405 * instead make it 100% FG colour, so leave
2406 * the ratio alone and copy the value across
2408 p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2412 if( pi_karaoke_bar[ i ] & 0x80 )
2414 /* Swap Left and Right sides over for Right aligned
2415 * language text (eg. Arabic, Hebrew)
2417 uint32_t i_tmp = p_line->p_fg_rgb[ k ];
2419 p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2420 p_line->p_bg_rgb[ k ] = i_tmp;
2422 p_line->p_fg_bg_ratio[ k ] = (pi_karaoke_bar[ i ] & 0x7f);
2425 /* Jump over the '\n' at the line-end */
2428 free( pi_karaoke_bar );
2435 * This function renders a text subpicture region into another one.
2436 * It also calculates the size needed for this string, and renders the
2437 * needed glyphs into memory. It is used as pf_add_string callback in
2438 * the vout method by this module
2440 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2441 subpicture_region_t *p_region_in, bool b_html )
2443 filter_sys_t *p_sys = p_filter->p_sys;
2446 return VLC_EGENERIC;
2447 if( b_html && !p_region_in->psz_html )
2448 return VLC_EGENERIC;
2449 if( !b_html && !p_region_in->psz_text )
2450 return VLC_EGENERIC;
2452 uint32_t *psz_text = calloc( strlen( b_html ? p_region_in->psz_html
2453 : p_region_in->psz_text ),
2454 sizeof( *psz_text ) );
2456 return VLC_EGENERIC;
2459 /* Reset the default fontsize in case screen metrics have changed */
2460 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2463 int rv = VLC_SUCCESS;
2464 int i_text_length = 0;
2465 FT_Vector result = {0, 0};
2466 line_desc_t *p_lines = NULL;
2471 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2472 (uint8_t *) p_region_in->psz_html,
2473 strlen( p_region_in->psz_html ),
2475 if( unlikely(p_sub == NULL) )
2478 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2480 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2482 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2483 p_filter->p_sys->p_xml = p_xml_reader;
2488 bool b_karaoke = false;
2491 /* Look for Root Node */
2494 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2496 if( !strcasecmp( "karaoke", node ) )
2498 /* We're going to have to render the text a number
2499 * of times to show the progress marker on the text.
2501 var_SetBool( p_filter, "text-rerender", true );
2504 else if( !strcasecmp( "text", node ) )
2510 /* Only text and karaoke tags are supported */
2511 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2518 msg_Err( p_filter, "Malformed HTML subtitle" );
2524 uint32_t i_runs = 0;
2525 uint32_t i_k_runs = 0;
2526 uint32_t *pi_run_lengths = NULL;
2527 uint32_t *pi_k_run_lengths = NULL;
2528 uint32_t *pi_k_durations = NULL;
2529 text_style_t **pp_styles = NULL;
2531 rv = ProcessNodes( p_filter, p_xml_reader,
2532 p_region_in->p_style, psz_text, &i_text_length,
2533 &i_runs, &pi_run_lengths, &pp_styles,
2534 b_karaoke, &i_k_runs, &pi_k_run_lengths,
2537 if( !rv && i_text_length > 0 )
2539 rv = ProcessLines( p_filter, psz_text, i_text_length, i_runs,
2540 pi_run_lengths, pp_styles, &p_lines,
2541 &result, b_karaoke, i_k_runs,
2542 pi_k_run_lengths, pi_k_durations );
2545 for( uint32_t k = 0; k < i_runs; k++ )
2546 text_style_Delete( pp_styles[k] );
2548 free( pi_run_lengths );
2553 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2555 stream_Delete( p_sub );
2561 size_t i_iconv_length;
2562 IconvText( p_filter, p_region_in->psz_text, &i_iconv_length, psz_text );
2563 i_text_length = i_iconv_length;
2565 text_style_t *p_style;
2566 if( p_region_in->p_style )
2567 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2568 p_region_in->p_style->i_font_size,
2569 (p_region_in->p_style->i_font_color & 0xffffff) |
2570 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2572 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2577 p_style = CreateStyle( p_sys->psz_fontfamily,
2579 (p_sys->i_font_color & 0xffffff) |
2580 (((255-p_sys->i_font_opacity) & 0xff) << 24),
2582 uint32_t i_run_length = i_text_length;
2584 rv = ProcessLines( p_filter, psz_text, i_text_length,
2585 1, &i_run_length, &p_style,
2587 false, 0, NULL, NULL );
2588 text_style_Delete( p_style );
2592 p_region_out->i_x = p_region_in->i_x;
2593 p_region_out->i_y = p_region_in->i_y;
2595 /* Don't attempt to render text that couldn't be layed out
2597 if( !rv && i_text_length > 0 )
2599 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2600 RenderYUVP( p_filter, p_region_out, p_lines,
2601 result.x, result.y );
2603 RenderYUVA( p_filter, p_region_out, p_lines,
2604 result.x, result.y );
2607 FreeLines( p_lines );
2611 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2612 subpicture_region_t *p_region_in )
2614 return RenderCommon( p_filter, p_region_out, p_region_in, false );
2619 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2620 subpicture_region_t *p_region_in )
2622 return RenderCommon( p_filter, p_region_out, p_region_in, true );
2627 /*****************************************************************************
2628 * Create: allocates osd-text video thread output method
2629 *****************************************************************************
2630 * This function allocates and initializes a Clone vout method.
2631 *****************************************************************************/
2632 static int Create( vlc_object_t *p_this )
2634 filter_t *p_filter = (filter_t *)p_this;
2635 filter_sys_t *p_sys;
2636 char *psz_fontfile = NULL;
2637 char *psz_fontfamily = NULL;
2638 int i_error = 0, fontindex = 0;
2640 /* Allocate structure */
2641 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2645 p_sys->psz_fontfamily = NULL;
2647 p_sys->p_xml = NULL;
2650 p_sys->p_library = 0;
2651 p_sys->i_font_size = 0;
2652 p_sys->i_display_height = 0;
2654 var_Create( p_filter, "freetype-rel-fontsize",
2655 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2657 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2658 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2659 p_sys->i_effect = var_InheritInteger( p_filter, "freetype-effect" );
2660 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2661 p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
2662 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2663 p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
2666 /* Get Windows Font folder */
2667 wchar_t wdir[MAX_PATH];
2668 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2670 GetWindowsDirectoryW( wdir, MAX_PATH );
2671 wcscat( wdir, L"\\fonts" );
2673 p_sys->psz_win_fonts_path = FromWide( wdir );
2676 /* Set default psz_fontfamily */
2677 if( !psz_fontfamily || !*psz_fontfamily )
2679 free( psz_fontfamily );
2681 psz_fontfamily = strdup( DEFAULT_FAMILY );
2684 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2687 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2689 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2693 /* Set the current font file */
2694 p_sys->psz_fontfamily = psz_fontfamily;
2696 #ifdef HAVE_FONTCONFIG
2697 FontConfig_BuildCache( p_filter );
2700 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2701 p_sys->i_default_font_size, &fontindex );
2702 #elif defined(WIN32)
2703 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2704 p_sys->i_default_font_size, &fontindex );
2707 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2709 /* If nothing is found, use the default family */
2711 psz_fontfile = strdup( psz_fontfamily );
2713 #else /* !HAVE_STYLES */
2714 /* Use the default file */
2715 psz_fontfile = psz_fontfamily;
2719 i_error = FT_Init_FreeType( &p_sys->p_library );
2722 msg_Err( p_filter, "couldn't initialize freetype" );
2726 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2727 fontindex, &p_sys->p_face );
2729 if( i_error == FT_Err_Unknown_File_Format )
2731 msg_Err( p_filter, "file %s have unknown format",
2732 psz_fontfile ? psz_fontfile : "(null)" );
2737 msg_Err( p_filter, "failed to load font file %s",
2738 psz_fontfile ? psz_fontfile : "(null)" );
2742 free( psz_fontfile );
2745 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2748 msg_Err( p_filter, "font has no unicode translation table" );
2752 p_sys->i_use_kerning = FT_HAS_KERNING( p_sys->p_face );
2754 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2757 p_sys->pp_font_attachments = NULL;
2758 p_sys->i_font_attachments = 0;
2760 p_filter->pf_render_text = RenderText;
2762 p_filter->pf_render_html = RenderHtml;
2764 p_filter->pf_render_html = NULL;
2767 LoadFontsFromAttachments( p_filter );
2772 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2773 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2775 free( psz_fontfile );
2777 free( psz_fontfamily );
2779 return VLC_EGENERIC;
2782 /*****************************************************************************
2783 * Destroy: destroy Clone video thread output method
2784 *****************************************************************************
2785 * Clean up all data and library connections
2786 *****************************************************************************/
2787 static void Destroy( vlc_object_t *p_this )
2789 filter_t *p_filter = (filter_t *)p_this;
2790 filter_sys_t *p_sys = p_filter->p_sys;
2792 if( p_sys->pp_font_attachments )
2794 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2795 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2797 free( p_sys->pp_font_attachments );
2801 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2803 free( p_sys->psz_fontfamily );
2805 /* FcFini asserts calling the subfunction FcCacheFini()
2806 * even if no other library functions have been made since FcInit(),
2807 * so don't call it. */
2809 FT_Done_Face( p_sys->p_face );
2810 FT_Done_FreeType( p_sys->p_library );