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 int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1041 uint32_t i_color, uint32_t i_karaoke_bg_color )
1044 return VLC_EGENERIC;
1046 font_stack_t *p_new = malloc( sizeof(*p_new) );
1050 p_new->p_next = NULL;
1053 p_new->psz_name = strdup( psz_name );
1055 p_new->psz_name = NULL;
1057 p_new->i_size = i_size;
1058 p_new->i_color = i_color;
1059 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1067 font_stack_t *p_last;
1069 for( p_last = *p_font;
1071 p_last = p_last->p_next )
1074 p_last->p_next = p_new;
1079 static int PopFont( font_stack_t **p_font )
1081 font_stack_t *p_last, *p_next_to_last;
1083 if( !p_font || !*p_font )
1084 return VLC_EGENERIC;
1086 p_next_to_last = NULL;
1087 for( p_last = *p_font;
1089 p_last = p_last->p_next )
1091 p_next_to_last = p_last;
1094 if( p_next_to_last )
1095 p_next_to_last->p_next = NULL;
1099 free( p_last->psz_name );
1105 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1106 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1108 font_stack_t *p_last;
1110 if( !p_font || !*p_font )
1111 return VLC_EGENERIC;
1113 for( p_last=*p_font;
1115 p_last=p_last->p_next )
1118 *psz_name = p_last->psz_name;
1119 *i_size = p_last->i_size;
1120 *i_color = p_last->i_color;
1121 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1126 static const struct {
1127 const char *psz_name;
1129 } p_html_colors[] = {
1130 /* Official html colors */
1131 { "Aqua", 0x00FFFF },
1132 { "Black", 0x000000 },
1133 { "Blue", 0x0000FF },
1134 { "Fuchsia", 0xFF00FF },
1135 { "Gray", 0x808080 },
1136 { "Green", 0x008000 },
1137 { "Lime", 0x00FF00 },
1138 { "Maroon", 0x800000 },
1139 { "Navy", 0x000080 },
1140 { "Olive", 0x808000 },
1141 { "Purple", 0x800080 },
1142 { "Red", 0xFF0000 },
1143 { "Silver", 0xC0C0C0 },
1144 { "Teal", 0x008080 },
1145 { "White", 0xFFFFFF },
1146 { "Yellow", 0xFFFF00 },
1149 { "AliceBlue", 0xF0F8FF },
1150 { "AntiqueWhite", 0xFAEBD7 },
1151 { "Aqua", 0x00FFFF },
1152 { "Aquamarine", 0x7FFFD4 },
1153 { "Azure", 0xF0FFFF },
1154 { "Beige", 0xF5F5DC },
1155 { "Bisque", 0xFFE4C4 },
1156 { "Black", 0x000000 },
1157 { "BlanchedAlmond", 0xFFEBCD },
1158 { "Blue", 0x0000FF },
1159 { "BlueViolet", 0x8A2BE2 },
1160 { "Brown", 0xA52A2A },
1161 { "BurlyWood", 0xDEB887 },
1162 { "CadetBlue", 0x5F9EA0 },
1163 { "Chartreuse", 0x7FFF00 },
1164 { "Chocolate", 0xD2691E },
1165 { "Coral", 0xFF7F50 },
1166 { "CornflowerBlue", 0x6495ED },
1167 { "Cornsilk", 0xFFF8DC },
1168 { "Crimson", 0xDC143C },
1169 { "Cyan", 0x00FFFF },
1170 { "DarkBlue", 0x00008B },
1171 { "DarkCyan", 0x008B8B },
1172 { "DarkGoldenRod", 0xB8860B },
1173 { "DarkGray", 0xA9A9A9 },
1174 { "DarkGrey", 0xA9A9A9 },
1175 { "DarkGreen", 0x006400 },
1176 { "DarkKhaki", 0xBDB76B },
1177 { "DarkMagenta", 0x8B008B },
1178 { "DarkOliveGreen", 0x556B2F },
1179 { "Darkorange", 0xFF8C00 },
1180 { "DarkOrchid", 0x9932CC },
1181 { "DarkRed", 0x8B0000 },
1182 { "DarkSalmon", 0xE9967A },
1183 { "DarkSeaGreen", 0x8FBC8F },
1184 { "DarkSlateBlue", 0x483D8B },
1185 { "DarkSlateGray", 0x2F4F4F },
1186 { "DarkSlateGrey", 0x2F4F4F },
1187 { "DarkTurquoise", 0x00CED1 },
1188 { "DarkViolet", 0x9400D3 },
1189 { "DeepPink", 0xFF1493 },
1190 { "DeepSkyBlue", 0x00BFFF },
1191 { "DimGray", 0x696969 },
1192 { "DimGrey", 0x696969 },
1193 { "DodgerBlue", 0x1E90FF },
1194 { "FireBrick", 0xB22222 },
1195 { "FloralWhite", 0xFFFAF0 },
1196 { "ForestGreen", 0x228B22 },
1197 { "Fuchsia", 0xFF00FF },
1198 { "Gainsboro", 0xDCDCDC },
1199 { "GhostWhite", 0xF8F8FF },
1200 { "Gold", 0xFFD700 },
1201 { "GoldenRod", 0xDAA520 },
1202 { "Gray", 0x808080 },
1203 { "Grey", 0x808080 },
1204 { "Green", 0x008000 },
1205 { "GreenYellow", 0xADFF2F },
1206 { "HoneyDew", 0xF0FFF0 },
1207 { "HotPink", 0xFF69B4 },
1208 { "IndianRed", 0xCD5C5C },
1209 { "Indigo", 0x4B0082 },
1210 { "Ivory", 0xFFFFF0 },
1211 { "Khaki", 0xF0E68C },
1212 { "Lavender", 0xE6E6FA },
1213 { "LavenderBlush", 0xFFF0F5 },
1214 { "LawnGreen", 0x7CFC00 },
1215 { "LemonChiffon", 0xFFFACD },
1216 { "LightBlue", 0xADD8E6 },
1217 { "LightCoral", 0xF08080 },
1218 { "LightCyan", 0xE0FFFF },
1219 { "LightGoldenRodYellow", 0xFAFAD2 },
1220 { "LightGray", 0xD3D3D3 },
1221 { "LightGrey", 0xD3D3D3 },
1222 { "LightGreen", 0x90EE90 },
1223 { "LightPink", 0xFFB6C1 },
1224 { "LightSalmon", 0xFFA07A },
1225 { "LightSeaGreen", 0x20B2AA },
1226 { "LightSkyBlue", 0x87CEFA },
1227 { "LightSlateGray", 0x778899 },
1228 { "LightSlateGrey", 0x778899 },
1229 { "LightSteelBlue", 0xB0C4DE },
1230 { "LightYellow", 0xFFFFE0 },
1231 { "Lime", 0x00FF00 },
1232 { "LimeGreen", 0x32CD32 },
1233 { "Linen", 0xFAF0E6 },
1234 { "Magenta", 0xFF00FF },
1235 { "Maroon", 0x800000 },
1236 { "MediumAquaMarine", 0x66CDAA },
1237 { "MediumBlue", 0x0000CD },
1238 { "MediumOrchid", 0xBA55D3 },
1239 { "MediumPurple", 0x9370D8 },
1240 { "MediumSeaGreen", 0x3CB371 },
1241 { "MediumSlateBlue", 0x7B68EE },
1242 { "MediumSpringGreen", 0x00FA9A },
1243 { "MediumTurquoise", 0x48D1CC },
1244 { "MediumVioletRed", 0xC71585 },
1245 { "MidnightBlue", 0x191970 },
1246 { "MintCream", 0xF5FFFA },
1247 { "MistyRose", 0xFFE4E1 },
1248 { "Moccasin", 0xFFE4B5 },
1249 { "NavajoWhite", 0xFFDEAD },
1250 { "Navy", 0x000080 },
1251 { "OldLace", 0xFDF5E6 },
1252 { "Olive", 0x808000 },
1253 { "OliveDrab", 0x6B8E23 },
1254 { "Orange", 0xFFA500 },
1255 { "OrangeRed", 0xFF4500 },
1256 { "Orchid", 0xDA70D6 },
1257 { "PaleGoldenRod", 0xEEE8AA },
1258 { "PaleGreen", 0x98FB98 },
1259 { "PaleTurquoise", 0xAFEEEE },
1260 { "PaleVioletRed", 0xD87093 },
1261 { "PapayaWhip", 0xFFEFD5 },
1262 { "PeachPuff", 0xFFDAB9 },
1263 { "Peru", 0xCD853F },
1264 { "Pink", 0xFFC0CB },
1265 { "Plum", 0xDDA0DD },
1266 { "PowderBlue", 0xB0E0E6 },
1267 { "Purple", 0x800080 },
1268 { "Red", 0xFF0000 },
1269 { "RosyBrown", 0xBC8F8F },
1270 { "RoyalBlue", 0x4169E1 },
1271 { "SaddleBrown", 0x8B4513 },
1272 { "Salmon", 0xFA8072 },
1273 { "SandyBrown", 0xF4A460 },
1274 { "SeaGreen", 0x2E8B57 },
1275 { "SeaShell", 0xFFF5EE },
1276 { "Sienna", 0xA0522D },
1277 { "Silver", 0xC0C0C0 },
1278 { "SkyBlue", 0x87CEEB },
1279 { "SlateBlue", 0x6A5ACD },
1280 { "SlateGray", 0x708090 },
1281 { "SlateGrey", 0x708090 },
1282 { "Snow", 0xFFFAFA },
1283 { "SpringGreen", 0x00FF7F },
1284 { "SteelBlue", 0x4682B4 },
1285 { "Tan", 0xD2B48C },
1286 { "Teal", 0x008080 },
1287 { "Thistle", 0xD8BFD8 },
1288 { "Tomato", 0xFF6347 },
1289 { "Turquoise", 0x40E0D0 },
1290 { "Violet", 0xEE82EE },
1291 { "Wheat", 0xF5DEB3 },
1292 { "White", 0xFFFFFF },
1293 { "WhiteSmoke", 0xF5F5F5 },
1294 { "Yellow", 0xFFFF00 },
1295 { "YellowGreen", 0x9ACD32 },
1300 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1301 font_stack_t **p_fonts )
1304 char *psz_fontname = NULL;
1305 uint32_t i_font_color = 0xffffff;
1306 int i_font_alpha = 0;
1307 uint32_t i_karaoke_bg_color = 0x00ffffff;
1308 int i_font_size = 24;
1310 /* Default all attributes to the top font in the stack -- in case not
1311 * all attributes are specified in the sub-font
1313 if( VLC_SUCCESS == PeekFont( p_fonts,
1317 &i_karaoke_bg_color ))
1319 psz_fontname = strdup( psz_fontname );
1320 i_font_size = i_font_size;
1322 i_font_alpha = (i_font_color >> 24) & 0xff;
1323 i_font_color &= 0x00ffffff;
1325 const char *name, *value;
1326 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1328 if( !strcasecmp( "face", name ) )
1330 free( psz_fontname );
1331 psz_fontname = strdup( value );
1333 else if( !strcasecmp( "size", name ) )
1335 if( ( *value == '+' ) || ( *value == '-' ) )
1337 int i_value = atoi( value );
1339 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1340 i_font_size += ( i_value * i_font_size ) / 10;
1341 else if( i_value < -5 )
1342 i_font_size = - i_value;
1343 else if( i_value > 5 )
1344 i_font_size = i_value;
1347 i_font_size = atoi( value );
1349 else if( !strcasecmp( "color", name ) )
1351 if( value[0] == '#' )
1353 i_font_color = strtol( value + 1, NULL, 16 );
1354 i_font_color &= 0x00ffffff;
1358 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
1360 if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
1362 i_font_color = p_html_colors[i].i_value;
1368 else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
1370 i_font_alpha = strtol( value + 1, NULL, 16 );
1371 i_font_alpha &= 0xff;
1374 rv = PushFont( p_fonts,
1377 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1378 i_karaoke_bg_color );
1380 free( psz_fontname );
1385 static void SetKaraokeLen( uint32_t i_runs, uint32_t *pi_run_lengths,
1386 uint32_t i_k_runs, uint32_t *pi_k_run_lengths )
1388 /* Karaoke tags _PRECEDE_ the text they specify a duration
1389 * for, therefore we are working out the length for the
1390 * previous tag, and first time through we have nothing
1392 if( pi_k_run_lengths )
1397 /* Work out how many characters are presently in the string
1399 for( i = 0; i < i_runs; i++ )
1400 i_chars += pi_run_lengths[ i ];
1402 /* Subtract away those we've already allocated to other
1405 for( i = 0; i < i_k_runs; i++ )
1406 i_chars -= pi_k_run_lengths[ i ];
1408 pi_k_run_lengths[ i_k_runs - 1 ] = i_chars;
1412 static void SetupKaraoke( xml_reader_t *p_xml_reader, uint32_t *pi_k_runs,
1413 uint32_t **ppi_k_run_lengths,
1414 uint32_t **ppi_k_durations )
1416 const char *name, *value;
1418 while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
1420 if( !strcasecmp( "t", name ) )
1422 if( ppi_k_durations && ppi_k_run_lengths )
1426 if( *ppi_k_durations )
1428 *ppi_k_durations = realloc_or_free( *ppi_k_durations,
1429 *pi_k_runs * sizeof(**ppi_k_durations) );
1431 else if( *pi_k_runs == 1 )
1433 *ppi_k_durations = malloc( *pi_k_runs * sizeof(**ppi_k_durations) );
1436 if( *ppi_k_run_lengths )
1438 *ppi_k_run_lengths = realloc_or_free( *ppi_k_run_lengths,
1439 *pi_k_runs * sizeof(**ppi_k_run_lengths) );
1441 else if( *pi_k_runs == 1 )
1443 *ppi_k_run_lengths = malloc( *pi_k_runs * sizeof(**ppi_k_run_lengths) );
1445 if( *ppi_k_durations )
1446 (*ppi_k_durations)[ *pi_k_runs - 1 ] = atoi( value );
1448 if( *ppi_k_run_lengths )
1449 (*ppi_k_run_lengths)[ *pi_k_runs - 1 ] = 0;
1455 /* Turn any multiple-whitespaces into single spaces */
1456 static void HandleWhiteSpace( char *psz_node )
1458 char *s = strpbrk( psz_node, "\t\r\n " );
1461 int i_whitespace = strspn( s, "\t\r\n " );
1463 if( i_whitespace > 1 )
1466 strlen( s ) - i_whitespace + 1 );
1469 s = strpbrk( s, "\t\r\n " );
1474 static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1475 font_stack_t **p_fonts,
1478 char *psz_fontname = NULL;
1479 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1480 uint32_t i_karaoke_bg_color = i_font_color;
1481 int i_font_size = p_sys->i_font_size;
1483 if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
1484 &i_font_color, &i_karaoke_bg_color ) )
1487 return CreateStyle( psz_fontname, i_font_size, i_font_color,
1492 static int RenderTag( filter_t *p_filter, FT_Face p_face,
1493 const text_style_t *p_style,
1494 line_desc_t *p_line, uint32_t *psz_unicode,
1495 int *pi_pen_x, int i_pen_y, int *pi_start,
1496 FT_Vector *p_result )
1501 bool b_first_on_line = true;
1504 int i_pen_x_start = *pi_pen_x;
1506 uint32_t *psz_unicode_start = psz_unicode;
1508 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1510 /* Account for part of line already in position */
1511 for( i = 0; i<*pi_start; i++ )
1515 FT_Glyph_Get_CBox( (FT_Glyph) p_line->pp_glyphs[ i ],
1516 ft_glyph_bbox_pixels, &glyph_size );
1518 line.xMax = p_line->p_glyph_pos[ i ].x + glyph_size.xMax -
1519 glyph_size.xMin + p_line->pp_glyphs[ i ]->left;
1520 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1521 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1527 b_first_on_line = false;
1529 while( *psz_unicode && ( *psz_unicode != '\n' ) )
1535 int i_glyph_index = FT_Get_Char_Index( p_face, *psz_unicode++ );
1536 if( FT_HAS_KERNING( p_face ) && i_glyph_index
1540 FT_Get_Kerning( p_face, i_previous, i_glyph_index,
1541 ft_kerning_default, &delta );
1542 *pi_pen_x += delta.x >> 6;
1544 p_line->p_glyph_pos[ i ].x = *pi_pen_x;
1545 p_line->p_glyph_pos[ i ].y = i_pen_y;
1547 i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT );
1550 i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT );
1554 "unable to render text FT_Load_Glyph returned %d", i_error );
1555 p_line->pp_glyphs[ i ] = NULL;
1556 return VLC_EGENERIC;
1560 /* Do synthetic styling now that Freetype supports it;
1561 * ie. if the font we have loaded is NOT already in the
1562 * style that the tags want, then switch it on; if they
1563 * are then don't. */
1564 if ((p_style->i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1565 FT_GlyphSlot_Embolden( p_face->glyph );
1566 if ((p_style->i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1567 FT_GlyphSlot_Oblique( p_face->glyph );
1569 i_error = FT_Get_Glyph( p_face->glyph, &tmp_glyph );
1573 "unable to render text FT_Get_Glyph returned %d", i_error );
1574 p_line->pp_glyphs[ i ] = NULL;
1575 return VLC_EGENERIC;
1577 FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
1578 i_error = FT_Glyph_To_Bitmap( &tmp_glyph, FT_RENDER_MODE_NORMAL, 0, 1);
1581 FT_Done_Glyph( tmp_glyph );
1584 if( p_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1586 float aOffset = FT_FLOOR(FT_MulFix(p_face->underline_position,
1587 p_face->size->metrics.y_scale));
1588 float aSize = FT_CEIL(FT_MulFix(p_face->underline_thickness,
1589 p_face->size->metrics.y_scale));
1591 p_line->pi_underline_offset[ i ] =
1592 ( aOffset < 0 ) ? -aOffset : aOffset;
1593 p_line->pi_underline_thickness[ i ] =
1594 ( aSize < 0 ) ? -aSize : aSize;
1595 if( p_style->i_style_flags & STYLE_STRIKEOUT )
1597 /* Move the baseline to make it strikethrough instead of
1598 * underline. That means that strikethrough takes precedence
1600 float aDescent = FT_FLOOR(FT_MulFix(p_face->descender*2,
1601 p_face->size->metrics.y_scale));
1603 p_line->pi_underline_offset[ i ] -=
1604 ( aDescent < 0 ) ? -aDescent : aDescent;
1608 p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
1609 p_line->p_fg_rgb[ i ] = p_style->i_font_color;
1610 p_line->p_bg_rgb[ i ] = p_style->i_karaoke_background_color;
1611 p_line->p_fg_bg_ratio[ i ] = 0x00;
1613 line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
1614 glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
1615 if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
1617 for( ; i >= *pi_start; i-- )
1618 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
1621 while( psz_unicode > psz_unicode_start && *psz_unicode != ' ' )
1625 if( psz_unicode == psz_unicode_start )
1627 if( b_first_on_line )
1629 msg_Warn( p_filter, "unbreakable string" );
1630 p_line->pp_glyphs[ i ] = NULL;
1631 return VLC_EGENERIC;
1633 *pi_pen_x = i_pen_x_start;
1635 p_line->i_width = line.xMax;
1636 p_line->i_height = __MAX( p_line->i_height,
1637 p_face->size->metrics.height >> 6 );
1638 p_line->pp_glyphs[ i ] = NULL;
1640 p_result->x = __MAX( p_result->x, line.xMax );
1641 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1642 i_yMax - i_yMin ) );
1647 *psz_unicode = '\n';
1649 psz_unicode = psz_unicode_start;
1650 *pi_pen_x = i_pen_x_start;
1658 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1659 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1661 i_previous = i_glyph_index;
1662 *pi_pen_x += p_face->glyph->advance.x >> 6;
1665 p_line->i_width = line.xMax;
1666 p_line->i_height = __MAX( p_line->i_height,
1667 p_face->size->metrics.height >> 6 );
1668 p_line->pp_glyphs[ i ] = NULL;
1670 p_result->x = __MAX( p_result->x, line.xMax );
1671 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1672 line.yMax - line.yMin ) );
1676 /* Get rid of any text processed - if necessary repositioning
1677 * at the start of a new line of text
1681 *psz_unicode_start = '\0';
1683 else if( psz_unicode > psz_unicode_start )
1685 for( i=0; psz_unicode[ i ]; i++ )
1686 psz_unicode_start[ i ] = psz_unicode[ i ];
1687 psz_unicode_start[ i ] = '\0';
1693 static unsigned SetupText( filter_t *p_filter,
1694 uint32_t *psz_text_out,
1696 uint32_t **ppi_run_lengths,
1697 text_style_t ***ppp_styles,
1699 const char *psz_text_in,
1700 text_style_t *p_style )
1702 size_t i_string_length;
1704 size_t i_string_bytes;
1705 #if defined(WORDS_BIGENDIAN)
1706 uint32_t *psz_tmp = ToCharset( "UCS-4BE", psz_text_in, &i_string_bytes );
1708 uint32_t *psz_tmp = ToCharset( "UCS-4LE", psz_text_in, &i_string_bytes );
1712 memcpy( psz_text_out, psz_tmp, i_string_bytes );
1713 i_string_length = i_string_bytes / 4;
1718 msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
1719 i_string_length = 0;
1722 if( i_string_length > 0 && ppp_styles && ppi_run_lengths )
1726 /* XXX this logic looks somewhat broken */
1730 *ppp_styles = realloc_or_free( *ppp_styles,
1731 *pi_runs * sizeof(**ppp_styles) );
1733 else if( *pi_runs == 1 )
1735 *ppp_styles = malloc( *pi_runs * sizeof(**ppp_styles) );
1738 /* We have just malloc'ed this memory successfully -
1739 * *pi_runs HAS to be within the memory area of *ppp_styles */
1742 (*ppp_styles)[ *pi_runs - 1 ] = p_style;
1746 /* XXX more iffy logic */
1748 if( *ppi_run_lengths )
1750 *ppi_run_lengths = realloc_or_free( *ppi_run_lengths,
1751 *pi_runs * sizeof(**ppi_run_lengths) );
1753 else if( *pi_runs == 1 )
1755 *ppi_run_lengths = malloc( *pi_runs * sizeof(**ppi_run_lengths) );
1758 /* same remarks here */
1759 if( *ppi_run_lengths )
1761 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
1764 /* If we couldn't use the p_style argument due to memory allocation
1765 * problems above, release it here.
1767 text_style_Delete( p_style );
1768 return i_string_length;
1771 static int CheckForEmbeddedFont( filter_sys_t *p_sys, FT_Face *pp_face, text_style_t *p_style )
1773 for( int k = 0; k < p_sys->i_font_attachments; k++ )
1775 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1777 FT_Face p_face = NULL;
1779 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1787 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
1788 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
1789 if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
1790 (p_style->i_style_flags & (STYLE_BOLD | STYLE_BOLD)) == i_style_received )
1795 FT_Done_Face( p_face );
1800 return VLC_EGENERIC;
1803 static int ProcessNodes( filter_t *p_filter,
1804 xml_reader_t *p_xml_reader,
1805 text_style_t *p_font_style,
1810 uint32_t **ppi_run_lengths,
1811 text_style_t * **ppp_styles,
1814 uint32_t *pi_k_runs,
1815 uint32_t **ppi_k_run_lengths,
1816 uint32_t **ppi_k_durations )
1818 int rv = VLC_SUCCESS;
1819 filter_sys_t *p_sys = p_filter->p_sys;
1820 int i_text_length = 0;
1821 font_stack_t *p_fonts = NULL;
1823 int i_style_flags = 0;
1827 rv = PushFont( &p_fonts,
1828 p_font_style->psz_fontname,
1829 p_font_style->i_font_size,
1830 (p_font_style->i_font_color & 0xffffff) |
1831 ((p_font_style->i_font_alpha & 0xff) << 24),
1832 (p_font_style->i_karaoke_background_color & 0xffffff) |
1833 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
1835 i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
1843 rv = PushFont( &p_fonts,
1844 p_sys->psz_fontfamily,
1846 (p_sys->i_font_color & 0xffffff) |
1847 (((255-p_sys->i_font_opacity) & 0xff) << 24),
1852 if( rv != VLC_SUCCESS )
1858 while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
1862 case XML_READER_ENDELEM:
1863 if( !strcasecmp( "font", node ) )
1864 PopFont( &p_fonts );
1865 else if( !strcasecmp( "b", node ) )
1866 i_style_flags &= ~STYLE_BOLD;
1867 else if( !strcasecmp( "i", node ) )
1868 i_style_flags &= ~STYLE_ITALIC;
1869 else if( !strcasecmp( "u", node ) )
1870 i_style_flags &= ~STYLE_UNDERLINE;
1871 else if( !strcasecmp( "s", node ) )
1872 i_style_flags &= ~STYLE_STRIKEOUT;
1875 case XML_READER_STARTELEM:
1876 if( !strcasecmp( "font", node ) )
1877 rv = HandleFontAttributes( p_xml_reader, &p_fonts );
1878 else if( !strcasecmp( "b", node ) )
1879 i_style_flags |= STYLE_BOLD;
1880 else if( !strcasecmp( "i", node ) )
1881 i_style_flags |= STYLE_ITALIC;
1882 else if( !strcasecmp( "u", node ) )
1883 i_style_flags |= STYLE_UNDERLINE;
1884 else if( !strcasecmp( "s", node ) )
1885 i_style_flags |= STYLE_STRIKEOUT;
1886 else if( !strcasecmp( "br", node ) )
1888 i_text_length += SetupText( p_filter,
1889 &psz_text[i_text_length],
1890 pi_runs, ppi_run_lengths, ppp_styles,
1892 GetStyleFromFontStack( p_sys,
1896 else if( !strcasecmp( "k", node ) )
1898 /* Only valid in karaoke */
1901 if( *pi_k_runs > 0 )
1902 SetKaraokeLen( *pi_runs, *ppi_run_lengths,
1903 *pi_k_runs, *ppi_k_run_lengths );
1904 SetupKaraoke( p_xml_reader, pi_k_runs,
1905 ppi_k_run_lengths, ppi_k_durations );
1910 case XML_READER_TEXT:
1912 char *psz_node = strdup( node );
1913 if( unlikely(!psz_node) )
1916 HandleWhiteSpace( psz_node );
1917 resolve_xml_special_chars( psz_node );
1919 i_text_length += SetupText( p_filter,
1920 &psz_text[i_text_length],
1921 pi_runs, ppi_run_lengths, ppp_styles,
1923 GetStyleFromFontStack( p_sys,
1930 if( rv != VLC_SUCCESS )
1938 SetKaraokeLen( *pi_runs, *ppi_run_lengths,
1939 *pi_k_runs, *ppi_k_run_lengths );
1942 *pi_len = i_text_length;
1944 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1949 static void FreeLine( line_desc_t *p_line )
1951 for( int i = 0; p_line->pp_glyphs && p_line->pp_glyphs[i] != NULL; i++ )
1952 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[i] );
1954 free( p_line->pp_glyphs );
1955 free( p_line->p_glyph_pos );
1956 free( p_line->p_fg_rgb );
1957 free( p_line->p_bg_rgb );
1958 free( p_line->p_fg_bg_ratio );
1959 free( p_line->pi_underline_offset );
1960 free( p_line->pi_underline_thickness );
1964 static void FreeLines( line_desc_t *p_lines )
1966 for( line_desc_t *p_line = p_lines; p_line != NULL; )
1968 line_desc_t *p_next = p_line->p_next;
1974 static line_desc_t *NewLine( int i_count )
1976 line_desc_t *p_line = malloc( sizeof(*p_line) );
1981 p_line->i_width = 0;
1982 p_line->i_height = 0;
1983 p_line->i_alpha = 0xff;
1985 p_line->p_next = NULL;
1987 p_line->pp_glyphs = calloc( i_count + 1, sizeof(*p_line->pp_glyphs) );
1988 p_line->p_glyph_pos = calloc( i_count + 1, sizeof(*p_line->p_glyph_pos) );
1989 p_line->p_fg_rgb = calloc( i_count + 1, sizeof(*p_line->p_fg_rgb) );
1990 p_line->p_bg_rgb = calloc( i_count + 1, sizeof(*p_line->p_bg_rgb) );
1991 p_line->p_fg_bg_ratio = calloc( i_count + 1, sizeof(*p_line->p_fg_bg_ratio) );
1992 p_line->pi_underline_offset = calloc( i_count + 1, sizeof(*p_line->pi_underline_offset) );
1993 p_line->pi_underline_thickness = calloc( i_count + 1, sizeof(*p_line->pi_underline_thickness) );
1995 if( !p_line->pp_glyphs || !p_line->p_glyph_pos ||
1996 !p_line->p_fg_rgb || !p_line->p_bg_rgb || !p_line->p_fg_bg_ratio ||
1997 !p_line->pi_underline_offset || !p_line->pi_underline_thickness )
2002 p_line->pp_glyphs[0] = NULL;
2007 static int ProcessLines( filter_t *p_filter,
2012 uint32_t *pi_run_lengths,
2013 text_style_t **pp_styles,
2014 line_desc_t **pp_lines,
2016 FT_Vector *p_result,
2020 uint32_t *pi_k_run_lengths,
2021 uint32_t *pi_k_durations )
2023 filter_sys_t *p_sys = p_filter->p_sys;
2024 text_style_t **pp_char_styles;
2025 int *p_new_positions = NULL;
2026 int8_t *p_levels = NULL;
2027 uint8_t *pi_karaoke_bar = NULL;
2031 /* Assign each character in the text string its style explicitly, so that
2032 * after the characters have been shuffled around by Fribidi, we can re-apply
2033 * the styles, and to simplify the calculation of runs within a line.
2035 pp_char_styles = malloc( i_len * sizeof(*pp_char_styles));
2036 if( !pp_char_styles )
2041 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
2042 /* If we can't allocate sufficient memory for karaoke, continue anyway -
2043 * we just won't be able to display the progress bar; at least we'll
2049 for( j = 0; j < i_runs; j++ )
2050 for( k = 0; k < pi_run_lengths[ j ]; k++ )
2051 pp_char_styles[ i++ ] = pp_styles[ j ];
2053 #if defined(HAVE_FRIBIDI)
2055 text_style_t **pp_char_styles_new;
2056 int *p_old_positions;
2057 uint32_t *p_fribidi_string;
2058 int start_pos, pos = 0;
2060 pp_char_styles_new = malloc( i_len * sizeof(*pp_char_styles_new));
2062 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
2063 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
2064 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
2065 p_levels = malloc( (i_len + 1) * sizeof(*p_levels) );
2067 if( ! pp_char_styles_new ||
2068 ! p_fribidi_string ||
2069 ! p_old_positions ||
2070 ! p_new_positions ||
2074 free( p_old_positions );
2075 free( p_new_positions );
2076 free( p_fribidi_string );
2077 free( pp_char_styles_new );
2078 free( pi_karaoke_bar );
2080 free( pp_char_styles );
2084 /* Do bidi conversion line-by-line */
2087 while(pos < i_len) {
2088 if (psz_text[pos] != '\n')
2090 p_fribidi_string[pos] = psz_text[pos];
2091 pp_char_styles_new[pos] = pp_char_styles[pos];
2092 p_new_positions[pos] = pos;
2097 while(pos < i_len) {
2098 if (psz_text[pos] == '\n')
2102 if (pos > start_pos)
2104 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
2105 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
2107 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
2109 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
2110 pos - start_pos, &base_dir,
2111 (FriBidiChar*)p_fribidi_string + start_pos,
2112 p_new_positions + start_pos,
2114 p_levels + start_pos );
2115 for( j = (uint32_t) start_pos; j < (uint32_t) pos; j++ )
2117 pp_char_styles_new[ j ] = pp_char_styles[ start_pos +
2118 p_old_positions[ j - start_pos ] ];
2119 p_new_positions[ j ] += start_pos;
2123 free( p_old_positions );
2124 free( pp_char_styles );
2125 pp_char_styles = pp_char_styles_new;
2126 psz_text = p_fribidi_string;
2127 p_fribidi_string[ i_len ] = 0;
2130 /* Work out the karaoke */
2131 if( pi_karaoke_bar )
2133 int64_t i_last_duration = 0;
2134 int64_t i_duration = 0;
2135 int64_t i_start_pos = 0;
2136 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
2138 for( k = 0; k< i_k_runs; k++ )
2140 double fraction = 0.0;
2142 i_duration += pi_k_durations[ k ];
2144 if( i_duration < i_elapsed )
2146 /* Completely finished this run-length -
2147 * let it render normally */
2151 else if( i_elapsed < i_last_duration )
2153 /* Haven't got up to this segment yet -
2154 * render it completely in karaoke BG mode */
2160 /* Partway through this run */
2162 fraction = (double)(i_elapsed - i_last_duration) /
2163 (double)pi_k_durations[ k ];
2165 for( i = 0; i < pi_k_run_lengths[ k ]; i++ )
2167 double shade = pi_k_run_lengths[ k ] * fraction;
2169 if( p_new_positions )
2170 j = p_new_positions[ i_start_pos + i ];
2172 j = i_start_pos + i;
2174 if( i < (uint32_t)shade )
2175 pi_karaoke_bar[ j ] = 0xff;
2176 else if( (double)i > shade )
2177 pi_karaoke_bar[ j ] = 0x00;
2180 shade -= (int)shade;
2181 pi_karaoke_bar[ j ] = ((int)(shade * 128.0) & 0x7f) |
2182 ((p_levels ? (p_levels[ j ] % 2) : 0 ) << 7);
2186 i_last_duration = i_duration;
2187 i_start_pos += pi_k_run_lengths[ k ];
2191 free( p_new_positions );
2193 FT_Vector tmp_result;
2195 line_desc_t *p_line = NULL;
2196 line_desc_t *p_prev = NULL;
2202 p_result->x = p_result->y = 0;
2203 tmp_result.x = tmp_result.y = 0;
2206 for( k = 0; k <= (uint32_t) i_len; k++ )
2208 if( ( k == (uint32_t) i_len ) ||
2210 !StyleEquals( pp_char_styles[ k ], pp_char_styles[ k - 1] ) ) )
2212 text_style_t *p_style = pp_char_styles[ k - 1 ];
2214 /* End of the current style run */
2215 FT_Face p_face = NULL;
2218 /* Look for a match amongst our attachments first */
2219 CheckForEmbeddedFont( p_sys, &p_face, p_style );
2225 #ifdef HAVE_FONTCONFIG
2226 psz_fontfile = FontConfig_Select( NULL,
2227 p_style->psz_fontname,
2228 (p_style->i_style_flags & STYLE_BOLD) != 0,
2229 (p_style->i_style_flags & STYLE_ITALIC) != 0,
2232 #elif defined( WIN32 )
2233 psz_fontfile = Win32_Select( p_filter,
2234 p_style->psz_fontname,
2235 (p_style->i_style_flags & STYLE_BOLD) != 0,
2236 (p_style->i_style_flags & STYLE_ITALIC) != 0,
2240 psz_fontfile = NULL;
2242 if( psz_fontfile && ! *psz_fontfile )
2245 "We were not able to find a matching font: \"%s\" (%s %s),"
2246 " so using default font",
2247 p_style->psz_fontname,
2248 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
2249 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
2250 free( psz_fontfile );
2251 psz_fontfile = NULL;
2256 if( FT_New_Face( p_sys->p_library,
2257 psz_fontfile, i_idx, &p_face ) )
2259 free( psz_fontfile );
2260 free( pp_char_styles );
2261 #if defined(HAVE_FRIBIDI)
2264 free( pi_karaoke_bar );
2265 return VLC_EGENERIC;
2267 free( psz_fontfile );
2271 FT_Select_Charmap( p_face, ft_encoding_unicode ) )
2273 /* We've loaded a font face which is unhelpful for actually
2274 * rendering text - fallback to the default one.
2276 FT_Done_Face( p_face );
2280 if( FT_Select_Charmap( p_face ? p_face : p_sys->p_face,
2281 ft_encoding_unicode ) ||
2282 FT_Set_Pixel_Sizes( p_face ? p_face : p_sys->p_face, 0,
2283 p_style->i_font_size ) )
2285 if( p_face ) FT_Done_Face( p_face );
2286 free( pp_char_styles );
2287 #if defined(HAVE_FRIBIDI)
2290 free( pi_karaoke_bar );
2291 return VLC_EGENERIC;
2293 p_sys->i_use_kerning =
2294 FT_HAS_KERNING( ( p_face ? p_face : p_sys->p_face ) );
2297 uint32_t *psz_unicode = malloc( (k - i_prev + 1) * sizeof(*psz_unicode) );
2300 if( p_face ) FT_Done_Face( p_face );
2301 free( pp_char_styles );
2302 free( psz_unicode );
2303 #if defined(HAVE_FRIBIDI)
2306 free( pi_karaoke_bar );
2309 memcpy( psz_unicode, psz_text + i_prev,
2310 sizeof( uint32_t ) * ( k - i_prev ) );
2311 psz_unicode[ k - i_prev ] = 0;
2312 while( *psz_unicode )
2316 if( !(p_line = NewLine( i_len - i_prev)) )
2318 if( p_face ) FT_Done_Face( p_face );
2319 free( pp_char_styles );
2320 free( psz_unicode );
2321 #if defined(HAVE_FRIBIDI)
2324 free( pi_karaoke_bar );
2327 p_line->i_alpha = p_style->i_font_alpha & 0xff;
2329 i_pen_y += tmp_result.y;
2333 if( p_prev ) p_prev->p_next = p_line;
2334 else *pp_lines = p_line;
2337 if( RenderTag( p_filter, p_face ? p_face : p_sys->p_face,
2339 p_line, psz_unicode, &i_pen_x, i_pen_y, &i_posn,
2340 &tmp_result ) != VLC_SUCCESS )
2342 if( p_face ) FT_Done_Face( p_face );
2343 free( pp_char_styles );
2344 free( psz_unicode );
2345 #if defined(HAVE_FRIBIDI)
2348 free( pi_karaoke_bar );
2349 return VLC_EGENERIC;
2354 p_result->x = __MAX( p_result->x, tmp_result.x );
2355 p_result->y += tmp_result.y;
2360 if( *psz_unicode == '\n')
2364 for( c_ptr = psz_unicode; *c_ptr; c_ptr++ )
2366 *c_ptr = *(c_ptr+1);
2371 free( psz_unicode );
2372 if( p_face ) FT_Done_Face( p_face );
2376 free( pp_char_styles );
2377 #if defined(HAVE_FRIBIDI)
2382 p_result->x = __MAX( p_result->x, tmp_result.x );
2383 p_result->y += tmp_result.y;
2386 if( pi_karaoke_bar )
2389 for( p_line = *pp_lines; p_line; p_line=p_line->p_next )
2391 for( k = 0; p_line->pp_glyphs[ k ]; k++, i++ )
2393 if( (pi_karaoke_bar[ i ] & 0x7f) == 0x7f)
2397 else if( (pi_karaoke_bar[ i ] & 0x7f) == 0x00)
2399 /* 100% BG colour will render faster if we
2400 * instead make it 100% FG colour, so leave
2401 * the ratio alone and copy the value across
2403 p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2407 if( pi_karaoke_bar[ i ] & 0x80 )
2409 /* Swap Left and Right sides over for Right aligned
2410 * language text (eg. Arabic, Hebrew)
2412 uint32_t i_tmp = p_line->p_fg_rgb[ k ];
2414 p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2415 p_line->p_bg_rgb[ k ] = i_tmp;
2417 p_line->p_fg_bg_ratio[ k ] = (pi_karaoke_bar[ i ] & 0x7f);
2420 /* Jump over the '\n' at the line-end */
2423 free( pi_karaoke_bar );
2430 * This function renders a text subpicture region into another one.
2431 * It also calculates the size needed for this string, and renders the
2432 * needed glyphs into memory. It is used as pf_add_string callback in
2433 * the vout method by this module
2435 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
2436 subpicture_region_t *p_region_in, bool b_html )
2438 filter_sys_t *p_sys = p_filter->p_sys;
2441 return VLC_EGENERIC;
2442 if( b_html && !p_region_in->psz_html )
2443 return VLC_EGENERIC;
2444 if( !b_html && !p_region_in->psz_text )
2445 return VLC_EGENERIC;
2447 uint32_t *psz_text = calloc( strlen( b_html ? p_region_in->psz_html
2448 : p_region_in->psz_text ),
2449 sizeof( *psz_text ) );
2451 return VLC_EGENERIC;
2454 /* Reset the default fontsize in case screen metrics have changed */
2455 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2458 int rv = VLC_SUCCESS;
2459 int i_text_length = 0;
2460 FT_Vector result = {0, 0};
2461 line_desc_t *p_lines = NULL;
2463 uint32_t i_runs = 0;
2464 uint32_t i_k_runs = 0;
2465 uint32_t *pi_run_lengths = NULL;
2466 uint32_t *pi_k_run_lengths = NULL;
2467 uint32_t *pi_k_durations = NULL;
2468 text_style_t **pp_styles = NULL;
2469 bool b_karaoke = false;
2474 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2475 (uint8_t *) p_region_in->psz_html,
2476 strlen( p_region_in->psz_html ),
2478 if( unlikely(p_sub == NULL) )
2481 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
2483 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
2485 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
2486 p_filter->p_sys->p_xml = p_xml_reader;
2493 /* Look for Root Node */
2496 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
2498 if( !strcasecmp( "karaoke", node ) )
2502 else if( !strcasecmp( "text", node ) )
2508 /* Only text and karaoke tags are supported */
2509 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
2516 msg_Err( p_filter, "Malformed HTML subtitle" );
2522 rv = ProcessNodes( p_filter, p_xml_reader,
2523 p_region_in->p_style, psz_text, &i_text_length,
2524 &i_runs, &pi_run_lengths, &pp_styles,
2525 b_karaoke, &i_k_runs, &pi_k_run_lengths,
2530 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
2532 stream_Delete( p_sub );
2537 text_style_t *p_style;
2538 if( p_region_in->p_style )
2539 p_style = CreateStyle( p_region_in->p_style->psz_fontname,
2540 p_region_in->p_style->i_font_size,
2541 (p_region_in->p_style->i_font_color & 0xffffff) |
2542 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
2544 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
2549 p_style = CreateStyle( p_sys->psz_fontfamily,
2551 (p_sys->i_font_color & 0xffffff) |
2552 (((255-p_sys->i_font_opacity) & 0xff) << 24),
2555 i_text_length = SetupText( p_filter,
2557 &i_runs, &pi_run_lengths, &pp_styles,
2558 p_region_in->psz_text, p_style );
2561 if( !rv && i_text_length > 0 )
2563 rv = ProcessLines( p_filter, psz_text, i_text_length, i_runs,
2564 pi_run_lengths, pp_styles, &p_lines,
2565 &result, b_karaoke, i_k_runs,
2566 pi_k_run_lengths, pi_k_durations );
2571 for( uint32_t k = 0; k < i_runs; k++ )
2572 text_style_Delete( pp_styles[k] );
2574 free( pi_run_lengths );
2575 free( pi_k_run_lengths );
2576 free( pi_k_durations );
2579 p_region_out->i_x = p_region_in->i_x;
2580 p_region_out->i_y = p_region_in->i_y;
2582 /* Don't attempt to render text that couldn't be layed out
2584 if( !rv && i_text_length > 0 )
2586 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2587 RenderYUVP( p_filter, p_region_out, p_lines,
2588 result.x, result.y );
2590 RenderYUVA( p_filter, p_region_out, p_lines,
2591 result.x, result.y );
2594 /* With karaoke, we're going to have to render the text a number
2595 * of times to show the progress marker on the text.
2598 var_SetBool( p_filter, "text-rerender", true );
2601 FreeLines( p_lines );
2605 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
2606 subpicture_region_t *p_region_in )
2608 return RenderCommon( p_filter, p_region_out, p_region_in, false );
2613 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2614 subpicture_region_t *p_region_in )
2616 return RenderCommon( p_filter, p_region_out, p_region_in, true );
2621 /*****************************************************************************
2622 * Create: allocates osd-text video thread output method
2623 *****************************************************************************
2624 * This function allocates and initializes a Clone vout method.
2625 *****************************************************************************/
2626 static int Create( vlc_object_t *p_this )
2628 filter_t *p_filter = (filter_t *)p_this;
2629 filter_sys_t *p_sys;
2630 char *psz_fontfile = NULL;
2631 char *psz_fontfamily = NULL;
2632 int i_error = 0, fontindex = 0;
2634 /* Allocate structure */
2635 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
2639 p_sys->psz_fontfamily = NULL;
2641 p_sys->p_xml = NULL;
2644 p_sys->p_library = 0;
2645 p_sys->i_font_size = 0;
2646 p_sys->i_display_height = 0;
2648 var_Create( p_filter, "freetype-rel-fontsize",
2649 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
2651 psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
2652 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
2653 p_sys->i_effect = var_InheritInteger( p_filter, "freetype-effect" );
2654 p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
2655 p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
2656 p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
2657 p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
2660 /* Get Windows Font folder */
2661 wchar_t wdir[MAX_PATH];
2662 if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
2664 GetWindowsDirectoryW( wdir, MAX_PATH );
2665 wcscat( wdir, L"\\fonts" );
2667 p_sys->psz_win_fonts_path = FromWide( wdir );
2670 /* Set default psz_fontfamily */
2671 if( !psz_fontfamily || !*psz_fontfamily )
2673 free( psz_fontfamily );
2675 psz_fontfamily = strdup( DEFAULT_FAMILY );
2678 if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
2681 psz_fontfamily = strdup( DEFAULT_FONT_FILE );
2683 msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
2687 /* Set the current font file */
2688 p_sys->psz_fontfamily = psz_fontfamily;
2690 #ifdef HAVE_FONTCONFIG
2691 FontConfig_BuildCache( p_filter );
2694 psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
2695 p_sys->i_default_font_size, &fontindex );
2696 #elif defined(WIN32)
2697 psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
2698 p_sys->i_default_font_size, &fontindex );
2701 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
2703 /* If nothing is found, use the default family */
2705 psz_fontfile = strdup( psz_fontfamily );
2707 #else /* !HAVE_STYLES */
2708 /* Use the default file */
2709 psz_fontfile = psz_fontfamily;
2713 i_error = FT_Init_FreeType( &p_sys->p_library );
2716 msg_Err( p_filter, "couldn't initialize freetype" );
2720 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
2721 fontindex, &p_sys->p_face );
2723 if( i_error == FT_Err_Unknown_File_Format )
2725 msg_Err( p_filter, "file %s have unknown format",
2726 psz_fontfile ? psz_fontfile : "(null)" );
2731 msg_Err( p_filter, "failed to load font file %s",
2732 psz_fontfile ? psz_fontfile : "(null)" );
2736 free( psz_fontfile );
2739 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
2742 msg_Err( p_filter, "font has no unicode translation table" );
2746 p_sys->i_use_kerning = FT_HAS_KERNING( p_sys->p_face );
2748 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
2751 p_sys->pp_font_attachments = NULL;
2752 p_sys->i_font_attachments = 0;
2754 p_filter->pf_render_text = RenderText;
2756 p_filter->pf_render_html = RenderHtml;
2758 p_filter->pf_render_html = NULL;
2761 LoadFontsFromAttachments( p_filter );
2766 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
2767 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
2769 free( psz_fontfile );
2771 free( psz_fontfamily );
2773 return VLC_EGENERIC;
2776 /*****************************************************************************
2777 * Destroy: destroy Clone video thread output method
2778 *****************************************************************************
2779 * Clean up all data and library connections
2780 *****************************************************************************/
2781 static void Destroy( vlc_object_t *p_this )
2783 filter_t *p_filter = (filter_t *)p_this;
2784 filter_sys_t *p_sys = p_filter->p_sys;
2786 if( p_sys->pp_font_attachments )
2788 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2789 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2791 free( p_sys->pp_font_attachments );
2795 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2797 free( p_sys->psz_fontfamily );
2799 /* FcFini asserts calling the subfunction FcCacheFini()
2800 * even if no other library functions have been made since FcInit(),
2801 * so don't call it. */
2803 FT_Done_Face( p_sys->p_face );
2804 FT_Done_FreeType( p_sys->p_library );