1 /*****************************************************************************
2 * freetype.c : Put text on the video, using freetype2
3 *****************************************************************************
4 * Copyright (C) 2002 - 2007 the VideoLAN team
7 * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8 * Gildas Bazin <gbazin@videolan.org>
9 * Bernie Purcell <bitmap@videolan.org>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
26 /*****************************************************************************
28 *****************************************************************************/
33 #include <vlc_block.h>
34 #include <vlc_filter.h>
35 #include <vlc_stream.h>
37 #include <vlc_input.h>
39 #ifdef HAVE_LINUX_LIMITS_H
40 # include <linux/limits.h>
48 #include FT_FREETYPE_H
50 #define FT_FLOOR(X) ((X & -64) >> 6)
51 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
52 #define FT_MulFix(v, s) (((v)*(s))>>16)
55 #define DEFAULT_FONT "/System/Library/Fonts/LucidaGrande.dfont"
56 #define FC_DEFAULT_FONT "Lucida Grande"
57 #elif defined( SYS_BEOS )
58 #define DEFAULT_FONT "/boot/beos/etc/fonts/ttfonts/Swiss721.ttf"
59 #define FC_DEFAULT_FONT "Swiss"
60 #elif defined( WIN32 )
61 #define DEFAULT_FONT "" /* Default font found at run-time */
62 #define FC_DEFAULT_FONT "Arial"
64 #define DEFAULT_FONT "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
65 #define FC_DEFAULT_FONT "Serif Bold"
68 #if defined(HAVE_FRIBIDI)
69 #include <fribidi/fribidi.h>
72 #ifdef HAVE_FONTCONFIG
73 #include <fontconfig/fontconfig.h>
76 typedef struct line_desc_t line_desc_t;
78 /*****************************************************************************
80 *****************************************************************************/
81 static int Create ( vlc_object_t * );
82 static void Destroy( vlc_object_t * );
84 static int LoadFontsFromAttachments( filter_t *p_filter );
86 /* The RenderText call maps to pf_render_string, defined in vlc_filter.h */
87 static int RenderText( filter_t *, subpicture_region_t *,
88 subpicture_region_t * );
89 #ifdef HAVE_FONTCONFIG
90 static int RenderHtml( filter_t *, subpicture_region_t *,
91 subpicture_region_t * );
92 static char *FontConfig_Select( FcConfig *, const char *,
93 vlc_bool_t, vlc_bool_t, int * );
94 static int CheckIfFontBuildComplete( filter_t *p_filter );
96 static line_desc_t *NewLine( int );
98 static int GetFontSize( filter_t *p_filter );
99 static int SetFontSize( filter_t *, int );
100 static void YUVFromRGB( uint32_t i_argb,
101 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v );
103 /*****************************************************************************
105 *****************************************************************************/
106 #define FONT_TEXT N_("Font")
107 #define FONT_LONGTEXT N_("Filename for the font you want to use")
108 #define FONTSIZE_TEXT N_("Font size in pixels")
109 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
110 "that will be rendered on the video. " \
111 "If set to something different than 0 this option will override the " \
112 "relative font size." )
113 #define OPACITY_TEXT N_("Opacity")
114 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
115 "text that will be rendered on the video. 0 = transparent, " \
116 "255 = totally opaque. " )
117 #define COLOR_TEXT N_("Text default color")
118 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
119 "the video. This must be an hexadecimal (like HTML colors). The first two "\
120 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
121 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
122 #define FONTSIZER_TEXT N_("Relative font size")
123 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
124 "fonts that will be rendered on the video. If absolute font size is set, "\
125 "relative size will be overriden." )
127 static int pi_sizes[] = { 20, 18, 16, 12, 6 };
128 static const char *ppsz_sizes_text[] = { N_("Smaller"), N_("Small"), N_("Normal"),
129 N_("Large"), N_("Larger") };
130 #define YUVP_TEXT N_("Use YUVP renderer")
131 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
132 "This option is only needed if you want to encode into DVB subtitles" )
133 #define EFFECT_TEXT N_("Font Effect")
134 #define EFFECT_LONGTEXT N_("It is possible to apply effects to the rendered " \
135 "text to improve its readability." )
137 #define EFFECT_BACKGROUND 1
138 #define EFFECT_OUTLINE 2
139 #define EFFECT_OUTLINE_FAT 3
141 static int pi_effects[] = { 1, 2, 3 };
142 static const char *ppsz_effects_text[] = { N_("Background"),N_("Outline"),
144 static int pi_color_values[] = {
145 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
146 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
147 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
149 static const char *ppsz_color_descriptions[] = {
150 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
151 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
152 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
155 set_shortname( _("Text renderer"));
156 set_description( _("Freetype2 font renderer") );
157 set_category( CAT_VIDEO );
158 set_subcategory( SUBCAT_VIDEO_SUBPIC );
160 add_file( "freetype-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
163 add_integer( "freetype-fontsize", 0, NULL, FONTSIZE_TEXT,
164 FONTSIZE_LONGTEXT, VLC_TRUE );
166 /* opacity valid on 0..255, with default 255 = fully opaque */
167 add_integer_with_range( "freetype-opacity", 255, 0, 255, NULL,
168 OPACITY_TEXT, OPACITY_LONGTEXT, VLC_TRUE );
170 /* hook to the color values list, with default 0x00ffffff = white */
171 add_integer( "freetype-color", 0x00FFFFFF, NULL, COLOR_TEXT,
172 COLOR_LONGTEXT, VLC_FALSE );
173 change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
175 add_integer( "freetype-rel-fontsize", 16, NULL, FONTSIZER_TEXT,
176 FONTSIZER_LONGTEXT, VLC_FALSE );
177 change_integer_list( pi_sizes, ppsz_sizes_text, 0 );
178 add_integer( "freetype-effect", 2, NULL, EFFECT_TEXT,
179 EFFECT_LONGTEXT, VLC_FALSE );
180 change_integer_list( pi_effects, ppsz_effects_text, 0 );
182 add_bool( "freetype-yuvp", 0, NULL, YUVP_TEXT,
183 YUVP_LONGTEXT, VLC_TRUE );
184 set_capability( "text renderer", 100 );
185 add_shortcut( "text" );
186 set_callbacks( Create, Destroy );
191 /** NULL-terminated list of glyphs making the string */
192 FT_BitmapGlyph *pp_glyphs;
193 /** list of relative positions for the glyphs */
194 FT_Vector *p_glyph_pos;
195 /** list of RGB information for styled text
196 * -- if the rendering mode supports it (RenderYUVA) and
197 * b_new_color_mode is set, then it becomes possible to
198 * have multicoloured text within the subtitles. */
201 uint8_t *p_fg_bg_ratio; /* 0x00=100% FG --> 0x7F=100% BG */
202 vlc_bool_t b_new_color_mode;
203 /** underline information -- only supplied if text should be underlined */
204 uint16_t *pi_underline_offset;
205 uint16_t *pi_underline_thickness;
209 int i_red, i_green, i_blue;
215 typedef struct font_stack_t font_stack_t;
220 uint32_t i_color; /* ARGB */
221 uint32_t i_karaoke_bg_color; /* ARGB */
223 font_stack_t *p_next;
229 uint32_t i_font_color; /* ARGB */
230 uint32_t i_karaoke_bg_color; /* ARGB */
233 vlc_bool_t b_underline;
237 static int Render( filter_t *, subpicture_region_t *, line_desc_t *, int, int);
238 static void FreeLines( line_desc_t * );
239 static void FreeLine( line_desc_t * );
240 #ifdef HAVE_FONTCONFIG
241 static void FontBuilder( vlc_object_t *p_this);
244 /*****************************************************************************
245 * filter_sys_t: freetype local data
246 *****************************************************************************
247 * This structure is part of the video output thread descriptor.
248 * It describes the freetype specific properties of an output thread.
249 *****************************************************************************/
252 FT_Library p_library; /* handle to library */
253 FT_Face p_face; /* handle to face object */
254 vlc_bool_t i_use_kerning;
255 uint8_t i_font_opacity;
260 int i_default_font_size;
261 int i_display_height;
262 #ifdef HAVE_FONTCONFIG
263 FcConfig *p_fontconfig;
264 vlc_bool_t b_fontconfig_ok;
265 vlc_mutex_t fontconfig_lock;
268 input_attachment_t **pp_font_attachments;
269 int i_font_attachments;
272 /*****************************************************************************
273 * Create: allocates osd-text video thread output method
274 *****************************************************************************
275 * This function allocates and initializes a Clone vout method.
276 *****************************************************************************/
277 static int Create( vlc_object_t *p_this )
279 filter_t *p_filter = (filter_t *)p_this;
281 char *psz_fontfile = NULL;
285 vlc_object_t *p_fontbuilder;
287 /* Allocate structure */
288 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
291 msg_Err( p_filter, "out of memory" );
295 p_sys->p_library = 0;
296 p_sys->i_font_size = 0;
297 p_sys->i_display_height = 0;
299 var_Create( p_filter, "freetype-font",
300 VLC_VAR_STRING | VLC_VAR_DOINHERIT );
301 var_Create( p_filter, "freetype-fontsize",
302 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
303 var_Create( p_filter, "freetype-rel-fontsize",
304 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
305 var_Create( p_filter, "freetype-opacity",
306 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
307 var_Create( p_filter, "freetype-effect",
308 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
309 var_Get( p_filter, "freetype-opacity", &val );
310 p_sys->i_font_opacity = __MAX( __MIN( val.i_int, 255 ), 0 );
311 var_Create( p_filter, "freetype-color",
312 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
313 var_Get( p_filter, "freetype-color", &val );
314 p_sys->i_font_color = __MAX( __MIN( val.i_int, 0xFFFFFF ), 0 );
315 p_sys->i_effect = var_GetInteger( p_filter, "freetype-effect" );
317 /* Look what method was requested */
318 var_Get( p_filter, "freetype-font", &val );
319 psz_fontfile = val.psz_string;
320 if( !psz_fontfile || !*psz_fontfile )
322 if( psz_fontfile ) free( psz_fontfile );
323 psz_fontfile = (char *)malloc( PATH_MAX + 1 );
326 msg_Err( p_filter, "out of memory" );
330 GetWindowsDirectory( psz_fontfile, PATH_MAX + 1 );
331 strcat( psz_fontfile, "\\fonts\\arial.ttf" );
332 #elif defined(__APPLE__)
333 strcpy( psz_fontfile, DEFAULT_FONT );
335 msg_Err( p_filter, "user didn't specify a font" );
340 i_error = FT_Init_FreeType( &p_sys->p_library );
343 msg_Err( p_filter, "couldn't initialize freetype" );
346 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
348 if( i_error == FT_Err_Unknown_File_Format )
350 msg_Err( p_filter, "file %s have unknown format", psz_fontfile );
355 msg_Err( p_filter, "failed to load font file %s", psz_fontfile );
359 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
362 msg_Err( p_filter, "font has no unicode translation table" );
366 #ifdef HAVE_FONTCONFIG
367 vlc_mutex_init( p_filter, &p_sys->fontconfig_lock );
368 p_sys->b_fontconfig_ok = VLC_FALSE;
369 p_sys->p_fontconfig = NULL;
371 /* Check for an existing Fontbuilder thread */
372 lock = var_AcquireMutex( "fontbuilder" );
373 p_fontbuilder = vlc_object_find_name( p_filter->p_libvlc,
377 if( ! p_fontbuilder )
379 /* Create the FontBuilder thread as a child of a top-level
380 * object, so that it can survive the destruction of the
381 * freetype object - the fontlist only needs to be built once,
382 * and calling the fontbuild a second time while the first is
383 * still in progress can cause thread instabilities.
386 p_fontbuilder = vlc_object_create( p_filter->p_libvlc,
387 VLC_OBJECT_GENERIC );
390 p_fontbuilder->psz_object_name = "fontlist builder";
391 vlc_object_attach( p_fontbuilder, p_filter->p_libvlc );
393 var_Create( p_fontbuilder, "build-done", VLC_VAR_BOOL );
394 var_SetBool( p_fontbuilder, "build-done", VLC_FALSE );
396 if( vlc_thread_create( p_fontbuilder,
399 VLC_THREAD_PRIORITY_LOW,
402 /* Don't destroy the fontconfig object - we won't be able to do
403 * italics or bold or change the font face, but we will still
404 * be able to do underline and change the font size.
406 msg_Warn( p_filter, "fontconfig database builder thread can't "
407 "be launched. Font styling support will be limited." );
412 vlc_object_destroy( p_fontbuilder );
417 vlc_object_release( p_fontbuilder );
419 vlc_mutex_unlock( lock );
423 p_sys->i_use_kerning = FT_HAS_KERNING( p_sys->p_face );
425 var_Get( p_filter, "freetype-fontsize", &val );
426 p_sys->i_default_font_size = val.i_int;
427 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
429 if( psz_fontfile ) free( psz_fontfile );
431 p_sys->pp_font_attachments = NULL;
432 p_sys->i_font_attachments = 0;
434 p_filter->pf_render_text = RenderText;
435 #ifdef HAVE_FONTCONFIG
436 p_filter->pf_render_html = RenderHtml;
438 p_filter->pf_render_html = NULL;
441 LoadFontsFromAttachments( p_filter );
446 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
447 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
448 if( psz_fontfile ) free( psz_fontfile );
453 /*****************************************************************************
454 * Destroy: destroy Clone video thread output method
455 *****************************************************************************
456 * Clean up all data and library connections
457 *****************************************************************************/
458 static void Destroy( vlc_object_t *p_this )
460 filter_t *p_filter = (filter_t *)p_this;
461 filter_sys_t *p_sys = p_filter->p_sys;
463 if( p_sys->pp_font_attachments )
467 for( k = 0; k < p_sys->i_font_attachments; k++ )
469 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
472 free( p_sys->pp_font_attachments );
475 #ifdef HAVE_FONTCONFIG
476 vlc_mutex_destroy( &p_sys->fontconfig_lock );
478 if( p_sys->p_fontconfig )
480 FcConfigDestroy( p_sys->p_fontconfig );
481 p_sys->p_fontconfig = NULL;
483 /* FcFini asserts calling the subfunction FcCacheFini()
484 * even if no other library functions have been made since FcInit(),
485 * so don't call it. */
487 FT_Done_Face( p_sys->p_face );
488 FT_Done_FreeType( p_sys->p_library );
492 #ifdef HAVE_FONTCONFIG
494 static void FontBuilder( vlc_object_t *p_this )
496 FcConfig *p_fontconfig = FcInitLoadConfig();
499 vlc_thread_ready( p_this );
505 msg_Dbg( p_this, "Building font database..." );
507 if(! FcConfigBuildFonts( p_fontconfig ))
509 /* Don't destroy the fontconfig object - we won't be able to do
510 * italics or bold or change the font face, but we will still
511 * be able to do underline and change the font size.
513 msg_Err( p_this, "fontconfig database can't be built. "
514 "Font styling won't be available" );
518 msg_Dbg( p_this, "Finished building font database." );
519 if( t1 > 0 && t2 > 0 )
520 msg_Dbg( p_this, "Took %ld seconds", t2 - t1 );
522 lock = var_AcquireMutex( "fontbuilder" );
523 var_SetBool( p_this, "build-done", VLC_TRUE );
525 FcConfigDestroy( p_fontconfig );
526 vlc_mutex_unlock( lock );
532 /*****************************************************************************
533 * Make any TTF/OTF fonts present in the attachments of the media file
534 * and store them for later use by the FreeType Engine
535 *****************************************************************************/
536 static int LoadFontsFromAttachments( filter_t *p_filter )
538 filter_sys_t *p_sys = p_filter->p_sys;
539 input_thread_t *p_input;
540 input_attachment_t **pp_attachments;
541 int i_attachments_cnt;
543 int rv = VLC_SUCCESS;
545 p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
549 if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
551 vlc_object_release(p_input);
555 p_sys->i_font_attachments = 0;
556 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof( input_attachment_t * ));
557 if(! p_sys->pp_font_attachments )
560 for( k = 0; k < i_attachments_cnt; k++ )
562 input_attachment_t *p_attach = pp_attachments[k];
564 if( p_sys->pp_font_attachments )
566 if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
567 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
568 ( p_attach->i_data > 0 ) &&
569 ( p_attach->p_data != NULL ) )
571 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
575 vlc_input_attachment_Delete( p_attach );
580 vlc_input_attachment_Delete( p_attach );
583 free( pp_attachments );
585 vlc_object_release(p_input);
590 /*****************************************************************************
591 * Render: place string in picture
592 *****************************************************************************
593 * This function merges the previously rendered freetype glyphs into a picture
594 *****************************************************************************/
595 static int Render( filter_t *p_filter, subpicture_region_t *p_region,
596 line_desc_t *p_line, int i_width, int i_height )
598 static uint8_t pi_gamma[16] =
599 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
600 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
604 int i, x, y, i_pitch;
605 uint8_t i_y; /* YUV values, derived from incoming RGB */
607 subpicture_region_t *p_region_tmp;
609 /* Create a new subpicture region */
610 memset( &fmt, 0, sizeof(video_format_t) );
611 fmt.i_chroma = VLC_FOURCC('Y','U','V','P');
613 fmt.i_width = fmt.i_visible_width = i_width + 4;
614 fmt.i_height = fmt.i_visible_height = i_height + 4;
615 if( p_region->fmt.i_visible_width > 0 )
616 fmt.i_visible_width = p_region->fmt.i_visible_width;
617 if( p_region->fmt.i_visible_height > 0 )
618 fmt.i_visible_height = p_region->fmt.i_visible_height;
619 fmt.i_x_offset = fmt.i_y_offset = 0;
620 p_region_tmp = spu_CreateRegion( p_filter, &fmt );
623 msg_Err( p_filter, "cannot allocate SPU region" );
627 p_region->fmt = p_region_tmp->fmt;
628 p_region->picture = p_region_tmp->picture;
629 free( p_region_tmp );
631 /* Calculate text color components */
632 i_y = (uint8_t)(( 66 * p_line->i_red + 129 * p_line->i_green +
633 25 * p_line->i_blue + 128) >> 8) + 16;
634 i_u = (int8_t)(( -38 * p_line->i_red - 74 * p_line->i_green +
635 112 * p_line->i_blue + 128) >> 8) + 128;
636 i_v = (int8_t)(( 112 * p_line->i_red - 94 * p_line->i_green -
637 18 * p_line->i_blue + 128) >> 8) + 128;
640 fmt.p_palette->i_entries = 16;
641 for( i = 0; i < 8; i++ )
643 fmt.p_palette->palette[i][0] = 0;
644 fmt.p_palette->palette[i][1] = 0x80;
645 fmt.p_palette->palette[i][2] = 0x80;
646 fmt.p_palette->palette[i][3] = pi_gamma[i];
647 fmt.p_palette->palette[i][3] =
648 (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
650 for( i = 8; i < fmt.p_palette->i_entries; i++ )
652 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
653 fmt.p_palette->palette[i][1] = i_u;
654 fmt.p_palette->palette[i][2] = i_v;
655 fmt.p_palette->palette[i][3] = pi_gamma[i];
656 fmt.p_palette->palette[i][3] =
657 (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
660 p_dst = p_region->picture.Y_PIXELS;
661 i_pitch = p_region->picture.Y_PITCH;
663 /* Initialize the region pixels */
664 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
666 for( ; p_line != NULL; p_line = p_line->p_next )
668 int i_glyph_tmax = 0;
669 int i_bitmap_offset, i_offset, i_align_offset = 0;
670 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
672 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
673 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
676 if( p_line->i_width < i_width )
678 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
680 i_align_offset = i_width - p_line->i_width;
682 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
684 i_align_offset = ( i_width - p_line->i_width ) / 2;
688 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
690 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
692 i_offset = ( p_line->p_glyph_pos[ i ].y +
693 i_glyph_tmax - p_glyph->top + 2 ) *
694 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 2 +
697 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
699 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
701 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
703 ((int)p_glyph->bitmap.buffer[i_bitmap_offset] + 8)/16;
710 /* Outlining (find something better than nearest neighbour filtering ?) */
713 uint8_t *p_dst = p_region->picture.Y_PIXELS;
714 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
715 uint8_t left, current;
717 for( y = 1; y < (int)fmt.i_height - 1; y++ )
719 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
720 p_dst += p_region->picture.Y_PITCH;
723 for( x = 1; x < (int)fmt.i_width - 1; x++ )
726 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
727 p_dst[x -1 + p_region->picture.Y_PITCH ] + p_dst[x + p_region->picture.Y_PITCH] + p_dst[x + 1 + p_region->picture.Y_PITCH]) / 16;
731 memset( p_top, 0, fmt.i_width );
737 static void UnderlineGlyphYUVA( int i_line_thickness, int i_line_offset, vlc_bool_t b_ul_next_char,
738 FT_BitmapGlyph p_this_glyph, FT_Vector *p_this_glyph_pos,
739 FT_BitmapGlyph p_next_glyph, FT_Vector *p_next_glyph_pos,
740 int i_glyph_tmax, int i_align_offset,
741 uint8_t i_y, uint8_t i_u, uint8_t i_v, uint8_t i_alpha,
742 subpicture_region_t *p_region)
746 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
748 p_dst_y = p_region->picture.Y_PIXELS;
749 p_dst_u = p_region->picture.U_PIXELS;
750 p_dst_v = p_region->picture.V_PIXELS;
751 p_dst_a = p_region->picture.A_PIXELS;
752 i_pitch = p_region->picture.A_PITCH;
754 int i_offset = ( p_this_glyph_pos->y + i_glyph_tmax + i_line_offset + 3 ) * i_pitch +
755 p_this_glyph_pos->x + p_this_glyph->left + 3 + i_align_offset;
757 for( y = 0; y < i_line_thickness; y++ )
759 int i_extra = p_this_glyph->bitmap.width;
763 i_extra = (p_next_glyph_pos->x + p_next_glyph->left) -
764 (p_this_glyph_pos->x + p_this_glyph->left);
766 for( x = 0; x < i_extra; x++ )
768 vlc_bool_t b_ok = VLC_TRUE;
770 /* break the underline around the tails of any glyphs which cross it */
771 for( z = x - i_line_thickness;
772 z < x + i_line_thickness && b_ok;
775 if( p_next_glyph && ( z >= i_extra ) )
777 int i_row = i_line_offset + p_next_glyph->top + y;
779 if( ( p_next_glyph->bitmap.rows > i_row ) &&
780 p_next_glyph->bitmap.buffer[p_next_glyph->bitmap.width * i_row + z-i_extra] )
785 else if ((z > 0 ) && (z < p_this_glyph->bitmap.width))
787 int i_row = i_line_offset + p_this_glyph->top + y;
789 if( ( p_this_glyph->bitmap.rows > i_row ) &&
790 p_this_glyph->bitmap.buffer[p_this_glyph->bitmap.width * i_row + z] )
799 p_dst_y[i_offset+x] = (i_y * 255) >> 8;
800 p_dst_u[i_offset+x] = i_u;
801 p_dst_v[i_offset+x] = i_v;
802 p_dst_a[i_offset+x] = 255;
809 static void DrawBlack( line_desc_t *p_line, int i_width, subpicture_region_t *p_region, int xoffset, int yoffset )
811 uint8_t *p_dst = p_region->picture.A_PIXELS;
812 int i_pitch = p_region->picture.A_PITCH;
815 for( ; p_line != NULL; p_line = p_line->p_next )
817 int i_glyph_tmax=0, i = 0;
818 int i_bitmap_offset, i_offset, i_align_offset = 0;
819 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
821 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
822 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
825 if( p_line->i_width < i_width )
827 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
829 i_align_offset = i_width - p_line->i_width;
831 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
833 i_align_offset = ( i_width - p_line->i_width ) / 2;
837 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
839 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
841 i_offset = ( p_line->p_glyph_pos[ i ].y +
842 i_glyph_tmax - p_glyph->top + 3 + yoffset ) *
843 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
844 i_align_offset +xoffset;
846 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
848 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
850 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
851 if( p_dst[i_offset+x] <
852 ((int)p_glyph->bitmap.buffer[i_bitmap_offset]) )
854 ((int)p_glyph->bitmap.buffer[i_bitmap_offset]);
863 /*****************************************************************************
864 * Render: place string in picture
865 *****************************************************************************
866 * This function merges the previously rendered freetype glyphs into a picture
867 *****************************************************************************/
868 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
869 line_desc_t *p_line, int i_width, int i_height )
871 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
873 int i, x, y, i_pitch, i_alpha;
874 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
875 subpicture_region_t *p_region_tmp;
877 if( i_width == 0 || i_height == 0 )
880 /* Create a new subpicture region */
881 memset( &fmt, 0, sizeof(video_format_t) );
882 fmt.i_chroma = VLC_FOURCC('Y','U','V','A');
884 fmt.i_width = fmt.i_visible_width = i_width + 6;
885 fmt.i_height = fmt.i_visible_height = i_height + 6;
886 if( p_region->fmt.i_visible_width > 0 )
887 fmt.i_visible_width = p_region->fmt.i_visible_width;
888 if( p_region->fmt.i_visible_height > 0 )
889 fmt.i_visible_height = p_region->fmt.i_visible_height;
890 fmt.i_x_offset = fmt.i_y_offset = 0;
891 p_region_tmp = spu_CreateRegion( p_filter, &fmt );
894 msg_Err( p_filter, "cannot allocate SPU region" );
898 p_region->fmt = p_region_tmp->fmt;
899 p_region->picture = p_region_tmp->picture;
900 free( p_region_tmp );
902 /* Calculate text color components */
903 YUVFromRGB( (p_line->i_red << 16) |
904 (p_line->i_green << 8) |
907 i_alpha = p_line->i_alpha;
909 p_dst_y = p_region->picture.Y_PIXELS;
910 p_dst_u = p_region->picture.U_PIXELS;
911 p_dst_v = p_region->picture.V_PIXELS;
912 p_dst_a = p_region->picture.A_PIXELS;
913 i_pitch = p_region->picture.A_PITCH;
915 /* Initialize the region pixels */
916 if( p_filter->p_sys->i_effect != EFFECT_BACKGROUND )
918 memset( p_dst_y, 0x00, i_pitch * p_region->fmt.i_height );
919 memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
920 memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
921 memset( p_dst_a, 0, i_pitch * p_region->fmt.i_height );
925 memset( p_dst_y, 0x0, i_pitch * p_region->fmt.i_height );
926 memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
927 memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
928 memset( p_dst_a, 0x80, i_pitch * p_region->fmt.i_height );
930 if( p_filter->p_sys->i_effect == EFFECT_OUTLINE ||
931 p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
933 DrawBlack( p_line, i_width, p_region, 0, 0);
934 DrawBlack( p_line, i_width, p_region, -1, 0);
935 DrawBlack( p_line, i_width, p_region, 0, -1);
936 DrawBlack( p_line, i_width, p_region, 1, 0);
937 DrawBlack( p_line, i_width, p_region, 0, 1);
940 if( p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
942 DrawBlack( p_line, i_width, p_region, -1, -1);
943 DrawBlack( p_line, i_width, p_region, -1, 1);
944 DrawBlack( p_line, i_width, p_region, 1, -1);
945 DrawBlack( p_line, i_width, p_region, 1, 1);
947 DrawBlack( p_line, i_width, p_region, -2, 0);
948 DrawBlack( p_line, i_width, p_region, 0, -2);
949 DrawBlack( p_line, i_width, p_region, 2, 0);
950 DrawBlack( p_line, i_width, p_region, 0, 2);
952 DrawBlack( p_line, i_width, p_region, -2, -2);
953 DrawBlack( p_line, i_width, p_region, -2, 2);
954 DrawBlack( p_line, i_width, p_region, 2, -2);
955 DrawBlack( p_line, i_width, p_region, 2, 2);
957 DrawBlack( p_line, i_width, p_region, -3, 0);
958 DrawBlack( p_line, i_width, p_region, 0, -3);
959 DrawBlack( p_line, i_width, p_region, 3, 0);
960 DrawBlack( p_line, i_width, p_region, 0, 3);
963 for( ; p_line != NULL; p_line = p_line->p_next )
965 int i_glyph_tmax = 0;
966 int i_bitmap_offset, i_offset, i_align_offset = 0;
967 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
969 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
970 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
973 if( p_line->i_width < i_width )
975 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
977 i_align_offset = i_width - p_line->i_width;
979 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
981 i_align_offset = ( i_width - p_line->i_width ) / 2;
985 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
987 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
989 i_offset = ( p_line->p_glyph_pos[ i ].y +
990 i_glyph_tmax - p_glyph->top + 3 ) *
991 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
994 if( p_line->b_new_color_mode )
996 /* Every glyph can (and in fact must) have its own color */
997 YUVFromRGB( p_line->p_fg_rgb[ i ], &i_y, &i_u, &i_v );
1000 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
1002 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
1004 uint8_t i_y_local = i_y;
1005 uint8_t i_u_local = i_u;
1006 uint8_t i_v_local = i_v;
1008 if( p_line->p_fg_bg_ratio != 0x00 )
1010 int i_split = p_glyph->bitmap.width *
1011 p_line->p_fg_bg_ratio[ i ] / 0x7f;
1015 YUVFromRGB( p_line->p_bg_rgb[ i ],
1016 &i_y_local, &i_u_local, &i_v_local );
1020 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
1022 p_dst_y[i_offset+x] = ((p_dst_y[i_offset+x] *(255-(int)p_glyph->bitmap.buffer[i_bitmap_offset])) +
1023 i_y * ((int)p_glyph->bitmap.buffer[i_bitmap_offset])) >> 8;
1025 p_dst_u[i_offset+x] = i_u;
1026 p_dst_v[i_offset+x] = i_v;
1028 if( p_filter->p_sys->i_effect == EFFECT_BACKGROUND )
1029 p_dst_a[i_offset+x] = 0xff;
1032 i_offset += i_pitch;
1035 if( p_line->pi_underline_thickness[ i ] )
1037 UnderlineGlyphYUVA( p_line->pi_underline_thickness[ i ],
1038 p_line->pi_underline_offset[ i ],
1039 (p_line->pp_glyphs[i+1] && (p_line->pi_underline_thickness[ i + 1] > 0)),
1040 p_line->pp_glyphs[i], &(p_line->p_glyph_pos[i]),
1041 p_line->pp_glyphs[i+1], &(p_line->p_glyph_pos[i+1]),
1042 i_glyph_tmax, i_align_offset,
1043 i_y, i_u, i_v, i_alpha,
1049 /* Apply the alpha setting */
1050 for( i = 0; i < (int)fmt.i_height * i_pitch; i++ )
1051 p_dst_a[i] = p_dst_a[i] * (255 - i_alpha) / 255;
1057 * This function renders a text subpicture region into another one.
1058 * It also calculates the size needed for this string, and renders the
1059 * needed glyphs into memory. It is used as pf_add_string callback in
1060 * the vout method by this module
1062 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
1063 subpicture_region_t *p_region_in )
1065 filter_sys_t *p_sys = p_filter->p_sys;
1066 line_desc_t *p_lines = NULL, *p_line = NULL, *p_next = NULL, *p_prev = NULL;
1067 int i, i_pen_y, i_pen_x, i_error, i_glyph_index, i_previous;
1068 uint32_t *psz_unicode, *psz_unicode_orig = NULL, i_char, *psz_line_start;
1069 int i_string_length;
1071 vlc_iconv_t iconv_handle = (vlc_iconv_t)(-1);
1072 int i_font_color, i_font_alpha, i_font_size, i_red, i_green, i_blue;
1082 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
1083 psz_string = p_region_in->psz_text;
1084 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
1086 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
1087 i_scale = val.i_int;
1089 if( p_region_in->p_style )
1091 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
1092 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
1093 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
1097 i_font_color = p_sys->i_font_color;
1098 i_font_alpha = 255 - p_sys->i_font_opacity;
1099 i_font_size = p_sys->i_default_font_size * i_scale / 1000;
1102 if( i_font_color == 0xFFFFFF ) i_font_color = p_sys->i_font_color;
1103 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
1104 SetFontSize( p_filter, i_font_size );
1106 i_red = ( i_font_color & 0x00FF0000 ) >> 16;
1107 i_green = ( i_font_color & 0x0000FF00 ) >> 8;
1108 i_blue = i_font_color & 0x000000FF;
1110 result.x = result.y = 0;
1111 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1113 psz_unicode = psz_unicode_orig =
1114 malloc( ( strlen(psz_string) + 1 ) * sizeof(uint32_t) );
1115 if( psz_unicode == NULL )
1117 msg_Err( p_filter, "out of memory" );
1120 #if defined(WORDS_BIGENDIAN)
1121 iconv_handle = vlc_iconv_open( "UCS-4BE", "UTF-8" );
1123 iconv_handle = vlc_iconv_open( "UCS-4LE", "UTF-8" );
1125 if( iconv_handle == (vlc_iconv_t)-1 )
1127 msg_Warn( p_filter, "unable to do conversion" );
1133 const char *p_in_buffer = psz_string;
1134 size_t i_in_bytes, i_out_bytes, i_out_bytes_left, i_ret;
1135 i_in_bytes = strlen( psz_string );
1136 i_out_bytes = i_in_bytes * sizeof( uint32_t );
1137 i_out_bytes_left = i_out_bytes;
1138 p_out_buffer = (char *)psz_unicode;
1139 i_ret = vlc_iconv( iconv_handle, (const char**)&p_in_buffer, &i_in_bytes,
1140 &p_out_buffer, &i_out_bytes_left );
1142 vlc_iconv_close( iconv_handle );
1146 msg_Warn( p_filter, "failed to convert string to unicode (%m), "
1147 "bytes left %u", (unsigned)i_in_bytes );
1150 *(uint32_t*)p_out_buffer = 0;
1151 i_string_length = (i_out_bytes - i_out_bytes_left) / sizeof(uint32_t);
1154 #if defined(HAVE_FRIBIDI)
1156 uint32_t *p_fribidi_string;
1157 int start_pos, pos = 0;
1159 p_fribidi_string = malloc( (i_string_length + 1) * sizeof(uint32_t) );
1160 if( !p_fribidi_string )
1162 msg_Err( p_filter, "out of memory" );
1166 /* Do bidi conversion line-by-line */
1167 while(pos < i_string_length)
1169 while(pos < i_string_length) {
1170 i_char = psz_unicode[pos];
1171 if (i_char != '\r' && i_char != '\n')
1173 p_fribidi_string[pos] = i_char;
1177 while(pos < i_string_length) {
1178 i_char = psz_unicode[pos];
1179 if (i_char == '\r' || i_char == '\n')
1183 if (pos > start_pos)
1185 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1186 fribidi_log2vis((FriBidiChar*)psz_unicode + start_pos, pos - start_pos,
1187 &base_dir, (FriBidiChar*)p_fribidi_string + start_pos, 0, 0, 0);
1191 free( psz_unicode_orig );
1192 psz_unicode = psz_unicode_orig = p_fribidi_string;
1193 p_fribidi_string[ i_string_length ] = 0;
1197 /* Calculate relative glyph positions and a bounding box for the
1199 if( !(p_line = NewLine( strlen( psz_string ))) )
1201 msg_Err( p_filter, "out of memory" );
1205 i_pen_x = i_pen_y = 0;
1207 psz_line_start = psz_unicode;
1209 #define face p_sys->p_face
1210 #define glyph face->glyph
1212 while( *psz_unicode )
1214 i_char = *psz_unicode++;
1215 if( i_char == '\r' ) /* ignore CR chars wherever they may be */
1220 if( i_char == '\n' )
1222 psz_line_start = psz_unicode;
1223 if( !(p_next = NewLine( strlen( psz_string ))) )
1225 msg_Err( p_filter, "out of memory" );
1228 p_line->p_next = p_next;
1229 p_line->i_width = line.xMax;
1230 p_line->i_height = face->size->metrics.height >> 6;
1231 p_line->pp_glyphs[ i ] = NULL;
1232 p_line->i_alpha = i_font_alpha;
1233 p_line->i_red = i_red;
1234 p_line->i_green = i_green;
1235 p_line->i_blue = i_blue;
1238 result.x = __MAX( result.x, line.xMax );
1239 result.y += face->size->metrics.height >> 6;
1242 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1243 i_pen_y += face->size->metrics.height >> 6;
1245 msg_Dbg( p_filter, "Creating new line, i is %d", i );
1250 i_glyph_index = FT_Get_Char_Index( face, i_char );
1251 if( p_sys->i_use_kerning && i_glyph_index
1255 FT_Get_Kerning( face, i_previous, i_glyph_index,
1256 ft_kerning_default, &delta );
1257 i_pen_x += delta.x >> 6;
1260 p_line->p_glyph_pos[ i ].x = i_pen_x;
1261 p_line->p_glyph_pos[ i ].y = i_pen_y;
1262 i_error = FT_Load_Glyph( face, i_glyph_index, FT_LOAD_DEFAULT );
1265 msg_Err( p_filter, "unable to render text FT_Load_Glyph returned"
1269 i_error = FT_Get_Glyph( glyph, &tmp_glyph );
1272 msg_Err( p_filter, "unable to render text FT_Get_Glyph returned "
1276 FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
1277 i_error = FT_Glyph_To_Bitmap( &tmp_glyph, ft_render_mode_normal, 0, 1);
1280 FT_Done_Glyph( tmp_glyph );
1283 p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
1286 line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
1287 glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
1288 if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
1290 p_line->pp_glyphs[ i ] = NULL;
1292 p_line = NewLine( strlen( psz_string ));
1293 if( p_prev ) p_prev->p_next = p_line;
1294 else p_lines = p_line;
1296 uint32_t *psz_unicode_saved = psz_unicode;
1297 while( psz_unicode > psz_line_start && *psz_unicode != ' ' )
1301 if( psz_unicode == psz_line_start )
1302 { /* try harder to break that line */
1303 psz_unicode = psz_unicode_saved;
1304 while( psz_unicode > psz_line_start &&
1305 *psz_unicode != '_' && *psz_unicode != '/' &&
1306 *psz_unicode != '\\' && *psz_unicode != '.' )
1311 if( psz_unicode == psz_line_start )
1313 msg_Warn( p_filter, "unbreakable string" );
1318 *psz_unicode = '\n';
1320 psz_unicode = psz_line_start;
1323 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1326 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1327 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1329 i_previous = i_glyph_index;
1330 i_pen_x += glyph->advance.x >> 6;
1334 p_line->i_width = line.xMax;
1335 p_line->i_height = face->size->metrics.height >> 6;
1336 p_line->pp_glyphs[ i ] = NULL;
1337 p_line->i_alpha = i_font_alpha;
1338 p_line->i_red = i_red;
1339 p_line->i_green = i_green;
1340 p_line->i_blue = i_blue;
1341 result.x = __MAX( result.x, line.xMax );
1342 result.y += line.yMax - line.yMin;
1347 p_region_out->i_x = p_region_in->i_x;
1348 p_region_out->i_y = p_region_in->i_y;
1350 if( config_GetInt( p_filter, "freetype-yuvp" ) )
1351 Render( p_filter, p_region_out, p_lines, result.x, result.y );
1353 RenderYUVA( p_filter, p_region_out, p_lines, result.x, result.y );
1355 if( psz_unicode_orig ) free( psz_unicode_orig );
1356 FreeLines( p_lines );
1360 if( psz_unicode_orig ) free( psz_unicode_orig );
1361 FreeLines( p_lines );
1362 return VLC_EGENERIC;
1365 #ifdef HAVE_FONTCONFIG
1366 static ft_style_t *CreateStyle( char *psz_fontname, int i_font_size,
1367 uint32_t i_font_color, uint32_t i_karaoke_bg_color, vlc_bool_t b_bold,
1368 vlc_bool_t b_italic, vlc_bool_t b_uline )
1370 ft_style_t *p_style = malloc( sizeof( ft_style_t ));
1374 p_style->i_font_size = i_font_size;
1375 p_style->i_font_color = i_font_color;
1376 p_style->i_karaoke_bg_color = i_karaoke_bg_color;
1377 p_style->b_italic = b_italic;
1378 p_style->b_bold = b_bold;
1379 p_style->b_underline = b_uline;
1381 p_style->psz_fontname = strdup( psz_fontname );
1386 static void DeleteStyle( ft_style_t *p_style )
1390 if( p_style->psz_fontname )
1391 free( p_style->psz_fontname );
1396 static vlc_bool_t StyleEquals( ft_style_t *s1, ft_style_t *s2 )
1403 if(( s1->i_font_size == s2->i_font_size ) &&
1404 ( s1->i_font_color == s2->i_font_color ) &&
1405 ( s1->b_italic == s2->b_italic ) &&
1406 ( s1->b_bold == s2->b_bold ) &&
1407 ( s1->b_underline == s2->b_underline ) &&
1408 ( !strcmp( s1->psz_fontname, s2->psz_fontname )))
1415 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
1416 uint32_t i_color, uint32_t i_karaoke_bg_color )
1418 font_stack_t *p_new;
1421 return VLC_EGENERIC;
1423 p_new = malloc( sizeof( font_stack_t ) );
1427 p_new->p_next = NULL;
1430 p_new->psz_name = strdup( psz_name );
1432 p_new->psz_name = NULL;
1434 p_new->i_size = i_size;
1435 p_new->i_color = i_color;
1436 p_new->i_karaoke_bg_color = i_karaoke_bg_color;
1444 font_stack_t *p_last;
1446 for( p_last = *p_font;
1448 p_last = p_last->p_next )
1451 p_last->p_next = p_new;
1456 static int PopFont( font_stack_t **p_font )
1458 font_stack_t *p_last, *p_next_to_last;
1460 if( !p_font || !*p_font )
1461 return VLC_EGENERIC;
1463 p_next_to_last = NULL;
1464 for( p_last = *p_font;
1466 p_last = p_last->p_next )
1468 p_next_to_last = p_last;
1471 if( p_next_to_last )
1472 p_next_to_last->p_next = NULL;
1476 free( p_last->psz_name );
1482 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
1483 uint32_t *i_color, uint32_t *i_karaoke_bg_color )
1485 font_stack_t *p_last;
1487 if( !p_font || !*p_font )
1488 return VLC_EGENERIC;
1490 for( p_last=*p_font;
1492 p_last=p_last->p_next )
1495 *psz_name = p_last->psz_name;
1496 *i_size = p_last->i_size;
1497 *i_color = p_last->i_color;
1498 *i_karaoke_bg_color = p_last->i_karaoke_bg_color;
1503 static void IconvText( filter_t *p_filter, const char *psz_string,
1504 uint32_t *i_string_length, uint32_t **ppsz_unicode )
1506 vlc_iconv_t iconv_handle = (vlc_iconv_t)(-1);
1508 /* If memory hasn't been allocated for our output string, allocate it here
1509 * - the calling function must now be responsible for freeing it.
1511 if( !*ppsz_unicode )
1512 *ppsz_unicode = (uint32_t *)
1513 malloc( (strlen( psz_string ) + 1) * sizeof( uint32_t ));
1515 /* We don't need to handle a NULL pointer in *ppsz_unicode
1516 * if we are instead testing for a non NULL value like we are here */
1520 #if defined(WORDS_BIGENDIAN)
1521 iconv_handle = vlc_iconv_open( "UCS-4BE", "UTF-8" );
1523 iconv_handle = vlc_iconv_open( "UCS-4LE", "UTF-8" );
1525 if( iconv_handle != (vlc_iconv_t)-1 )
1527 char *p_in_buffer, *p_out_buffer;
1528 size_t i_in_bytes, i_out_bytes, i_out_bytes_left, i_ret;
1529 i_in_bytes = strlen( psz_string );
1530 i_out_bytes = i_in_bytes * sizeof( uint32_t );
1531 i_out_bytes_left = i_out_bytes;
1532 p_in_buffer = (char *) psz_string;
1533 p_out_buffer = (char *) *ppsz_unicode;
1534 i_ret = vlc_iconv( iconv_handle, (const char**)&p_in_buffer,
1535 &i_in_bytes, &p_out_buffer, &i_out_bytes_left );
1537 vlc_iconv_close( iconv_handle );
1541 msg_Warn( p_filter, "failed to convert string to unicode (%m), "
1542 "bytes left %u", (unsigned)i_in_bytes );
1546 *(uint32_t*)p_out_buffer = 0;
1548 (i_out_bytes - i_out_bytes_left) / sizeof(uint32_t);
1553 msg_Warn( p_filter, "unable to do conversion" );
1558 static ft_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1559 font_stack_t **p_fonts, vlc_bool_t b_bold, vlc_bool_t b_italic,
1560 vlc_bool_t b_uline )
1562 ft_style_t *p_style = NULL;
1564 char *psz_fontname = NULL;
1565 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1566 uint32_t i_karaoke_bg_color = i_font_color;
1567 int i_font_size = p_sys->i_font_size;
1569 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
1570 &i_font_color, &i_karaoke_bg_color ))
1572 p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
1573 i_karaoke_bg_color, b_bold, b_italic, b_uline );
1578 static int RenderTag( filter_t *p_filter, FT_Face p_face, int i_font_color,
1579 vlc_bool_t b_uline, int i_karaoke_bgcolor,
1580 line_desc_t *p_line, uint32_t *psz_unicode,
1581 int *pi_pen_x, int i_pen_y, int *pi_start,
1582 FT_Vector *p_result )
1587 vlc_bool_t b_first_on_line = VLC_TRUE;
1590 int i_pen_x_start = *pi_pen_x;
1592 uint32_t *psz_unicode_start = psz_unicode;
1594 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1596 /* Account for part of line already in position */
1597 for( i=0; i<*pi_start; i++ )
1601 FT_Glyph_Get_CBox( (FT_Glyph) p_line->pp_glyphs[ i ],
1602 ft_glyph_bbox_pixels, &glyph_size );
1604 line.xMax = p_line->p_glyph_pos[ i ].x + glyph_size.xMax -
1605 glyph_size.xMin + p_line->pp_glyphs[ i ]->left;
1606 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1607 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1613 b_first_on_line = VLC_FALSE;
1615 while( *psz_unicode && ( *psz_unicode != '\n' ) )
1621 int i_glyph_index = FT_Get_Char_Index( p_face, *psz_unicode++ );
1622 if( FT_HAS_KERNING( p_face ) && i_glyph_index
1626 FT_Get_Kerning( p_face, i_previous, i_glyph_index,
1627 ft_kerning_default, &delta );
1628 *pi_pen_x += delta.x >> 6;
1630 p_line->p_glyph_pos[ i ].x = *pi_pen_x;
1631 p_line->p_glyph_pos[ i ].y = i_pen_y;
1633 i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT );
1637 "unable to render text FT_Load_Glyph returned %d", i_error );
1638 p_line->pp_glyphs[ i ] = NULL;
1639 return VLC_EGENERIC;
1641 i_error = FT_Get_Glyph( p_face->glyph, &tmp_glyph );
1645 "unable to render text FT_Get_Glyph returned %d", i_error );
1646 p_line->pp_glyphs[ i ] = NULL;
1647 return VLC_EGENERIC;
1649 FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
1650 i_error = FT_Glyph_To_Bitmap( &tmp_glyph, FT_RENDER_MODE_NORMAL, 0, 1);
1653 FT_Done_Glyph( tmp_glyph );
1658 float aOffset = FT_FLOOR(FT_MulFix(p_face->underline_position,
1659 p_face->size->metrics.y_scale));
1660 float aSize = FT_CEIL(FT_MulFix(p_face->underline_thickness,
1661 p_face->size->metrics.y_scale));
1663 p_line->pi_underline_offset[ i ] =
1664 ( aOffset < 0 ) ? -aOffset : aOffset;
1665 p_line->pi_underline_thickness[ i ] =
1666 ( aSize < 0 ) ? -aSize : aSize;
1668 p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
1669 p_line->p_fg_rgb[ i ] = i_font_color & 0x00ffffff;
1670 p_line->p_bg_rgb[ i ] = i_karaoke_bgcolor & 0x00ffffff;
1671 p_line->p_fg_bg_ratio[ i ] = 0x00;
1673 line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
1674 glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
1675 if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
1677 while( --i > *pi_start )
1679 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
1682 while( psz_unicode > psz_unicode_start && *psz_unicode != ' ' )
1686 if( psz_unicode == psz_unicode_start )
1688 if( b_first_on_line )
1690 msg_Warn( p_filter, "unbreakable string" );
1691 p_line->pp_glyphs[ i ] = NULL;
1692 return VLC_EGENERIC;
1694 *pi_pen_x = i_pen_x_start;
1696 p_line->i_width = line.xMax;
1697 p_line->i_height = __MAX( p_line->i_height,
1698 p_face->size->metrics.height >> 6 );
1699 p_line->pp_glyphs[ i ] = NULL;
1701 p_result->x = __MAX( p_result->x, line.xMax );
1702 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1703 i_yMax - i_yMin ) );
1710 *psz_unicode = '\n';
1712 psz_unicode = psz_unicode_start;
1713 *pi_pen_x = i_pen_x_start;
1721 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1722 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1724 i_previous = i_glyph_index;
1725 *pi_pen_x += p_face->glyph->advance.x >> 6;
1728 p_line->i_width = line.xMax;
1729 p_line->i_height = __MAX( p_line->i_height,
1730 p_face->size->metrics.height >> 6 );
1731 p_line->pp_glyphs[ i ] = NULL;
1733 p_result->x = __MAX( p_result->x, line.xMax );
1734 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1735 line.yMax - line.yMin ) );
1739 /* Get rid of any text processed - if necessary repositioning
1740 * at the start of a new line of text
1744 *psz_unicode_start = '\0';
1746 else if( psz_unicode > psz_unicode_start )
1748 for( i=0; psz_unicode[ i ]; i++ )
1749 psz_unicode_start[ i ] = psz_unicode[ i ];
1750 psz_unicode_start[ i ] = '\0';
1756 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
1757 font_stack_t **p_fonts, int i_scale )
1760 char *psz_fontname = NULL;
1761 uint32_t i_font_color = 0xffffff;
1762 int i_font_alpha = 0;
1763 uint32_t i_karaoke_bg_color = 0x00ffffff;
1764 int i_font_size = 24;
1766 /* Default all attributes to the top font in the stack -- in case not
1767 * all attributes are specified in the sub-font
1769 if( VLC_SUCCESS == PeekFont( p_fonts,
1773 &i_karaoke_bg_color ))
1775 psz_fontname = strdup( psz_fontname );
1776 i_font_size = i_font_size * 1000 / i_scale;
1778 i_font_alpha = (i_font_color >> 24) & 0xff;
1779 i_font_color &= 0x00ffffff;
1781 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
1783 char *psz_name = xml_ReaderName( p_xml_reader );
1784 char *psz_value = xml_ReaderValue( p_xml_reader );
1786 if( psz_name && psz_value )
1788 if( !strcasecmp( "face", psz_name ) )
1790 if( psz_fontname ) free( psz_fontname );
1791 psz_fontname = strdup( psz_value );
1793 else if( !strcasecmp( "size", psz_name ) )
1795 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
1797 int i_value = atoi( psz_value );
1799 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
1800 i_font_size += ( i_value * i_font_size ) / 10;
1801 else if( i_value < -5 )
1802 i_font_size = - i_value;
1803 else if( i_value > 5 )
1804 i_font_size = i_value;
1807 i_font_size = atoi( psz_value );
1809 else if( !strcasecmp( "color", psz_name ) &&
1810 ( psz_value[0] == '#' ) )
1812 i_font_color = strtol( psz_value + 1, NULL, 16 );
1813 i_font_color &= 0x00ffffff;
1815 else if( !strcasecmp( "alpha", psz_name ) &&
1816 ( psz_value[0] == '#' ) )
1818 i_font_alpha = strtol( psz_value + 1, NULL, 16 );
1819 i_font_alpha &= 0xff;
1825 rv = PushFont( p_fonts,
1827 i_font_size * i_scale / 1000,
1828 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
1829 i_karaoke_bg_color );
1831 free( psz_fontname );
1836 static void SetupLine( filter_t *p_filter, const char *psz_text_in,
1837 uint32_t **psz_text_out, uint32_t *pi_runs,
1838 uint32_t **ppi_run_lengths, ft_style_t ***ppp_styles,
1839 ft_style_t *p_style )
1841 uint32_t i_string_length = 0;
1843 IconvText( p_filter, psz_text_in, &i_string_length, psz_text_out );
1844 *psz_text_out += i_string_length;
1846 if( ppp_styles && ppi_run_lengths )
1852 *ppp_styles = (ft_style_t **)
1853 realloc( *ppp_styles, *pi_runs * sizeof( ft_style_t * ) );
1855 else if( *pi_runs == 1 )
1857 *ppp_styles = (ft_style_t **)
1858 malloc( *pi_runs * sizeof( ft_style_t * ) );
1861 /* We have just malloc'ed this memory successfully -
1862 * *pi_runs HAS to be within the memory area of *ppp_styles */
1865 (*ppp_styles)[ *pi_runs - 1 ] = p_style;
1869 if( *ppi_run_lengths )
1871 *ppi_run_lengths = (uint32_t *)
1872 realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
1874 else if( *pi_runs == 1 )
1876 *ppi_run_lengths = (uint32_t *)
1877 malloc( *pi_runs * sizeof( uint32_t ) );
1880 /* same remarks here */
1881 if( *ppi_run_lengths )
1883 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
1886 /* If we couldn't use the p_style argument due to memory allocation
1887 * problems above, release it here.
1889 if( p_style ) DeleteStyle( p_style );
1892 static void SetKaraokeLen( uint32_t i_runs, uint32_t *pi_run_lengths,
1893 uint32_t i_k_runs, uint32_t *pi_k_run_lengths )
1895 /* Karaoke tags _PRECEDE_ the text they specify a duration
1896 * for, therefore we are working out the length for the
1897 * previous tag, and first time through we have nothing
1899 if( pi_k_run_lengths )
1904 /* Work out how many characters are presently in the string
1906 for( i = 0; i < i_runs; i++ )
1907 i_chars += pi_run_lengths[ i ];
1909 /* Subtract away those we've already allocated to other
1912 for( i = 0; i < i_k_runs; i++ )
1913 i_chars -= pi_k_run_lengths[ i ];
1915 pi_k_run_lengths[ i_k_runs - 1 ] = i_chars;
1919 static void SetupKaraoke( xml_reader_t *p_xml_reader, uint32_t *pi_k_runs,
1920 uint32_t **ppi_k_run_lengths,
1921 uint32_t **ppi_k_durations )
1923 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
1925 char *psz_name = xml_ReaderName( p_xml_reader );
1926 char *psz_value = xml_ReaderValue( p_xml_reader );
1928 if( psz_name && psz_value &&
1929 !strcasecmp( "t", psz_name ) )
1931 if( ppi_k_durations && ppi_k_run_lengths )
1935 if( *ppi_k_durations )
1937 *ppi_k_durations = (uint32_t *)
1938 realloc( *ppi_k_durations,
1939 *pi_k_runs * sizeof( uint32_t ) );
1941 else if( *pi_k_runs == 1 )
1943 *ppi_k_durations = (uint32_t *)
1944 malloc( *pi_k_runs * sizeof( uint32_t ) );
1947 if( *ppi_k_run_lengths )
1949 *ppi_k_run_lengths = (uint32_t *)
1950 realloc( *ppi_k_run_lengths,
1951 *pi_k_runs * sizeof( uint32_t ) );
1953 else if( *pi_k_runs == 1 )
1955 *ppi_k_run_lengths = (uint32_t *)
1956 malloc( *pi_k_runs * sizeof( uint32_t ) );
1958 if( *ppi_k_durations )
1959 (*ppi_k_durations)[ *pi_k_runs - 1 ] = atoi( psz_value );
1961 if( *ppi_k_run_lengths )
1962 (*ppi_k_run_lengths)[ *pi_k_runs - 1 ] = 0;
1965 if( psz_name ) free( psz_name );
1966 if( psz_value ) free( psz_value );
1970 static int ProcessNodes( filter_t *p_filter,
1971 xml_reader_t *p_xml_reader,
1972 text_style_t *p_font_style,
1977 uint32_t **ppi_run_lengths,
1978 ft_style_t ***ppp_styles,
1980 vlc_bool_t b_karaoke,
1981 uint32_t *pi_k_runs,
1982 uint32_t **ppi_k_run_lengths,
1983 uint32_t **ppi_k_durations )
1985 int rv = VLC_SUCCESS;
1986 filter_sys_t *p_sys = p_filter->p_sys;
1987 uint32_t *psz_text_orig = psz_text;
1988 font_stack_t *p_fonts = NULL;
1992 char *psz_node = NULL;
1994 vlc_bool_t b_italic = VLC_FALSE;
1995 vlc_bool_t b_bold = VLC_FALSE;
1996 vlc_bool_t b_uline = VLC_FALSE;
1998 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
1999 i_scale = val.i_int;
2003 rv = PushFont( &p_fonts,
2004 p_font_style->psz_fontname,
2005 p_font_style->i_font_size * i_scale / 1000,
2006 (p_font_style->i_font_color & 0xffffff) |
2007 ((p_font_style->i_font_alpha & 0xff) << 24),
2008 (p_font_style->i_karaoke_background_color & 0xffffff) |
2009 ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
2011 if( p_font_style->i_style_flags & STYLE_BOLD )
2013 if( p_font_style->i_style_flags & STYLE_ITALIC )
2014 b_italic = VLC_TRUE;
2015 if( p_font_style->i_style_flags & STYLE_UNDERLINE )
2020 rv = PushFont( &p_fonts,
2026 if( rv != VLC_SUCCESS )
2029 while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
2031 switch ( xml_ReaderNodeType( p_xml_reader ) )
2033 case XML_READER_NONE:
2035 case XML_READER_ENDELEM:
2036 psz_node = xml_ReaderName( p_xml_reader );
2040 if( !strcasecmp( "font", psz_node ) )
2041 PopFont( &p_fonts );
2042 else if( !strcasecmp( "b", psz_node ) )
2044 else if( !strcasecmp( "i", psz_node ) )
2045 b_italic = VLC_FALSE;
2046 else if( !strcasecmp( "u", psz_node ) )
2047 b_uline = VLC_FALSE;
2052 case XML_READER_STARTELEM:
2053 psz_node = xml_ReaderName( p_xml_reader );
2056 if( !strcasecmp( "font", psz_node ) )
2057 rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
2058 else if( !strcasecmp( "b", psz_node ) )
2060 else if( !strcasecmp( "i", psz_node ) )
2061 b_italic = VLC_TRUE;
2062 else if( !strcasecmp( "u", psz_node ) )
2064 else if( !strcasecmp( "br", psz_node ) )
2066 SetupLine( p_filter, "\n", &psz_text,
2067 pi_runs, ppi_run_lengths, ppp_styles,
2068 GetStyleFromFontStack( p_sys,
2074 else if( !strcasecmp( "k", psz_node ) )
2076 /* Only valid in karaoke */
2079 if( *pi_k_runs > 0 )
2081 SetKaraokeLen( *pi_runs, *ppi_run_lengths,
2082 *pi_k_runs, *ppi_k_run_lengths );
2084 SetupKaraoke( p_xml_reader, pi_k_runs,
2085 ppi_k_run_lengths, ppi_k_durations );
2092 case XML_READER_TEXT:
2093 psz_node = xml_ReaderValue( p_xml_reader );
2096 /* Turn any multiple-whitespaces into single spaces */
2097 char *s = strpbrk( psz_node, "\t\r\n " );
2100 int i_whitespace = strspn( s, "\t\r\n " );
2102 if( i_whitespace > 1 )
2105 strlen( s ) - i_whitespace + 1 );
2108 s = strpbrk( s, "\t\r\n " );
2110 SetupLine( p_filter, psz_node, &psz_text,
2111 pi_runs, ppi_run_lengths, ppp_styles,
2112 GetStyleFromFontStack( p_sys,
2121 if( rv != VLC_SUCCESS )
2123 psz_text = psz_text_orig;
2129 SetKaraokeLen( *pi_runs, *ppi_run_lengths,
2130 *pi_k_runs, *ppi_k_run_lengths );
2133 *pi_len = psz_text - psz_text_orig;
2135 while( VLC_SUCCESS == PopFont( &p_fonts ) );
2140 static int CheckForEmbeddedFont( filter_sys_t *p_sys, FT_Face *pp_face, ft_style_t *p_style )
2144 for( k=0; k < p_sys->i_font_attachments; k++ )
2146 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
2148 FT_Face p_face = NULL;
2150 while( 0 == FT_New_Memory_Face( p_sys->p_library,
2158 vlc_bool_t match = !strcasecmp( p_face->family_name,
2159 p_style->psz_fontname );
2161 if( p_face->style_flags & FT_STYLE_FLAG_BOLD )
2162 match = match && p_style->b_bold;
2164 match = match && !p_style->b_bold;
2166 if( p_face->style_flags & FT_STYLE_FLAG_ITALIC )
2167 match = match && p_style->b_italic;
2169 match = match && !p_style->b_italic;
2177 FT_Done_Face( p_face );
2182 return VLC_EGENERIC;
2185 static int CheckIfFontBuildComplete( filter_t *p_filter )
2187 filter_sys_t *p_sys = p_filter->p_sys;
2188 vlc_object_t *p_fb = vlc_object_find_name( p_filter->p_libvlc,
2193 vlc_mutex_t *lock = var_AcquireMutex( "fontbuilder" );
2196 if( VLC_SUCCESS == var_Get( p_fb, "build-done", &val ))
2198 p_sys->b_fontconfig_ok = val.b_bool;
2200 if( p_sys->b_fontconfig_ok )
2203 p_sys->p_fontconfig = FcConfigGetCurrent();
2206 msg_Dbg( p_filter, "Font Build still not complete" );
2208 vlc_mutex_unlock( lock );
2209 vlc_object_release( p_fb );
2213 return VLC_EGENERIC;
2216 static int ProcessLines( filter_t *p_filter,
2221 uint32_t *pi_run_lengths,
2222 ft_style_t **pp_styles,
2223 line_desc_t **pp_lines,
2225 FT_Vector *p_result,
2227 vlc_bool_t b_karaoke,
2229 uint32_t *pi_k_run_lengths,
2230 uint32_t *pi_k_durations )
2232 filter_sys_t *p_sys = p_filter->p_sys;
2233 ft_style_t **pp_char_styles;
2234 int *p_new_positions = NULL;
2235 int8_t *p_levels = NULL;
2236 uint8_t *pi_karaoke_bar = NULL;
2240 /* Assign each character in the text string its style explicitly, so that
2241 * after the characters have been shuffled around by Fribidi, we can re-apply
2242 * the styles, and to simplify the calculation of runs within a line.
2244 pp_char_styles = (ft_style_t **) malloc( i_len * sizeof( ft_style_t * ));
2245 if( !pp_char_styles )
2250 pi_karaoke_bar = (uint8_t *) malloc( i_len * sizeof( uint8_t ));
2251 /* If we can't allocate sufficient memory for karaoke, continue anyway -
2252 * we just won't be able to display the progress bar; at least we'll
2258 for( j = 0; j < i_runs; j++ )
2259 for( k = 0; k < pi_run_lengths[ j ]; k++ )
2260 pp_char_styles[ i++ ] = pp_styles[ j ];
2262 #if defined(HAVE_FRIBIDI)
2264 ft_style_t **pp_char_styles_new;
2265 int *p_old_positions;
2266 uint32_t *p_fribidi_string;
2267 int start_pos, pos = 0;
2269 pp_char_styles_new = (ft_style_t **)
2270 malloc( i_len * sizeof( ft_style_t * ));
2272 p_fribidi_string = (uint32_t *)
2273 malloc( (i_len + 1) * sizeof(uint32_t) );
2274 p_old_positions = (int *)
2275 malloc( (i_len + 1) * sizeof( int ) );
2276 p_new_positions = (int *)
2277 malloc( (i_len + 1) * sizeof( int ) );
2278 p_levels = (int8_t *)
2279 malloc( (i_len + 1) * sizeof( int8_t ) );
2281 if( ! pp_char_styles_new ||
2282 ! p_fribidi_string ||
2283 ! p_old_positions ||
2284 ! p_new_positions ||
2287 msg_Err( p_filter, "out of memory" );
2288 if( p_levels ) free( p_levels );
2289 if( p_old_positions ) free( p_old_positions );
2290 if( p_new_positions ) free( p_new_positions );
2291 if( p_fribidi_string ) free( p_fribidi_string );
2292 if( pp_char_styles_new ) free( pp_char_styles_new );
2293 if( pi_karaoke_bar ) free( pi_karaoke_bar );
2295 free( pp_char_styles );
2299 /* Do bidi conversion line-by-line */
2302 while(pos < i_len) {
2303 if (psz_text[pos] != '\n')
2305 p_fribidi_string[pos] = psz_text[pos];
2306 pp_char_styles_new[pos] = pp_char_styles[pos];
2307 p_new_positions[pos] = pos;
2312 while(pos < i_len) {
2313 if (psz_text[pos] == '\n')
2317 if (pos > start_pos)
2319 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
2320 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
2321 pos - start_pos, &base_dir,
2322 (FriBidiChar*)p_fribidi_string + start_pos,
2323 p_new_positions + start_pos,
2325 p_levels + start_pos );
2326 for( j = (uint32_t) start_pos; j < (uint32_t) pos; j++ )
2328 pp_char_styles_new[ j ] = pp_char_styles[ start_pos +
2329 p_old_positions[ j - start_pos ] ];
2330 p_new_positions[ j ] += start_pos;
2334 free( p_old_positions );
2335 free( pp_char_styles );
2336 pp_char_styles = pp_char_styles_new;
2337 psz_text = p_fribidi_string;
2338 p_fribidi_string[ i_len ] = 0;
2341 /* Work out the karaoke */
2342 if( pi_karaoke_bar )
2344 int64_t i_last_duration = 0;
2345 int64_t i_duration = 0;
2346 int64_t i_start_pos = 0;
2347 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
2349 for( k = 0; k< i_k_runs; k++ )
2351 double fraction = 0.0;
2353 i_duration += pi_k_durations[ k ];
2355 if( i_duration < i_elapsed )
2357 /* Completely finished this run-length -
2358 * let it render normally */
2362 else if( i_elapsed < i_last_duration )
2364 /* Haven't got up to this segment yet -
2365 * render it completely in karaoke BG mode */
2371 /* Partway through this run */
2373 fraction = (double)(i_elapsed - i_last_duration) /
2374 (double)pi_k_durations[ k ];
2376 for( i = 0; i < pi_k_run_lengths[ k ]; i++ )
2378 double shade = pi_k_run_lengths[ k ] * fraction;
2380 if( p_new_positions )
2381 j = p_new_positions[ i_start_pos + i ];
2383 j = i_start_pos + i;
2385 if( i < (uint32_t)shade )
2386 pi_karaoke_bar[ j ] = 0xff;
2387 else if( (double)i > shade )
2388 pi_karaoke_bar[ j ] = 0x00;
2391 shade -= (int)shade;
2392 pi_karaoke_bar[ j ] = ((int)(shade * 128.0) & 0x7f) |
2393 ((p_levels ? (p_levels[ j ] % 2) : 0 ) << 7);
2397 i_last_duration = i_duration;
2398 i_start_pos += pi_k_run_lengths[ k ];
2401 if( p_levels ) free( p_levels );
2402 if( p_new_positions ) free( p_new_positions );
2404 FT_Vector tmp_result;
2406 line_desc_t *p_line = NULL;
2407 line_desc_t *p_prev = NULL;
2413 p_result->x = p_result->y = 0;
2414 tmp_result.x = tmp_result.y = 0;
2417 for( k = 0; k <= (uint32_t) i_len; k++ )
2419 if( ( k == (uint32_t) i_len ) ||
2421 !StyleEquals( pp_char_styles[ k ], pp_char_styles[ k - 1] ) ) )
2423 ft_style_t *p_style = pp_char_styles[ k - 1 ];
2425 /* End of the current style run */
2426 FT_Face p_face = NULL;
2429 /* Look for a match amongst our attachments first */
2430 CheckForEmbeddedFont( p_sys, &p_face, p_style );
2432 if( !p_sys->b_fontconfig_ok )
2434 if( VLC_EGENERIC == CheckIfFontBuildComplete( p_filter ))
2435 msg_Err( p_filter, "Can't find FontBuilder thread!" );
2438 if( ! p_face && p_sys->b_fontconfig_ok )
2441 vlc_mutex_lock( &p_sys->fontconfig_lock );
2443 psz_fontfile = FontConfig_Select( p_sys->p_fontconfig,
2444 p_style->psz_fontname,
2448 vlc_mutex_unlock( &p_sys->fontconfig_lock );
2450 if( psz_fontfile && ! *psz_fontfile )
2452 msg_Warn( p_filter, "Fontconfig was unable to find a font: \"%s\" %s"
2453 " so using default font", p_style->psz_fontname,
2454 ((p_style->b_bold && p_style->b_italic) ? "(Bold,Italic)" :
2455 (p_style->b_bold ? "(Bold)" :
2456 (p_style->b_italic ? "(Italic)" : ""))) );
2457 free( psz_fontfile );
2458 psz_fontfile = NULL;
2463 if( FT_New_Face( p_sys->p_library,
2464 psz_fontfile, i_idx, &p_face ) )
2466 free( psz_fontfile );
2467 free( pp_char_styles );
2468 #if defined(HAVE_FRIBIDI)
2471 if( pi_karaoke_bar )
2472 free( pi_karaoke_bar );
2473 return VLC_EGENERIC;
2475 free( psz_fontfile );
2479 FT_Select_Charmap( p_face, ft_encoding_unicode ) )
2481 /* We've loaded a font face which is unhelpful for actually
2482 * rendering text - fallback to the default one.
2484 FT_Done_Face( p_face );
2488 if( FT_Select_Charmap( p_face ? p_face : p_sys->p_face,
2489 ft_encoding_unicode ) ||
2490 FT_Set_Pixel_Sizes( p_face ? p_face : p_sys->p_face, 0,
2491 p_style->i_font_size ) )
2493 if( p_face ) FT_Done_Face( p_face );
2494 free( pp_char_styles );
2495 #if defined(HAVE_FRIBIDI)
2498 if( pi_karaoke_bar )
2499 free( pi_karaoke_bar );
2500 return VLC_EGENERIC;
2502 p_sys->i_use_kerning =
2503 FT_HAS_KERNING( ( p_face ? p_face : p_sys->p_face ) );
2506 uint32_t *psz_unicode = (uint32_t *)
2507 malloc( (k - i_prev + 1) * sizeof( uint32_t ));
2510 msg_Err( p_filter, "out of memory" );
2511 if( p_face ) FT_Done_Face( p_face );
2512 free( pp_char_styles );
2513 free( psz_unicode );
2514 #if defined(HAVE_FRIBIDI)
2517 if( pi_karaoke_bar )
2518 free( pi_karaoke_bar );
2521 memcpy( psz_unicode, psz_text + i_prev,
2522 sizeof( uint32_t ) * ( k - i_prev ) );
2523 psz_unicode[ k - i_prev ] = 0;
2524 while( *psz_unicode )
2528 if( !(p_line = NewLine( i_len - i_prev)) )
2530 msg_Err( p_filter, "out of memory" );
2531 if( p_face ) FT_Done_Face( p_face );
2532 free( pp_char_styles );
2533 free( psz_unicode );
2534 #if defined(HAVE_FRIBIDI)
2537 if( pi_karaoke_bar )
2538 free( pi_karaoke_bar );
2541 /* New Color mode only works in YUVA rendering mode --
2542 * (RGB mode has palette constraints on it). We therefore
2543 * need to populate the legacy colour fields also.
2545 p_line->b_new_color_mode = VLC_TRUE;
2546 p_line->i_alpha = ( p_style->i_font_color & 0xff000000 ) >> 24;
2547 p_line->i_red = ( p_style->i_font_color & 0x00ff0000 ) >> 16;
2548 p_line->i_green = ( p_style->i_font_color & 0x0000ff00 ) >> 8;
2549 p_line->i_blue = ( p_style->i_font_color & 0x000000ff );
2550 p_line->p_next = NULL;
2552 i_pen_y += tmp_result.y;
2556 if( p_prev ) p_prev->p_next = p_line;
2557 else *pp_lines = p_line;
2560 if( RenderTag( p_filter, p_face ? p_face : p_sys->p_face,
2561 p_style->i_font_color, p_style->b_underline,
2562 p_style->i_karaoke_bg_color,
2563 p_line, psz_unicode, &i_pen_x, i_pen_y, &i_posn,
2564 &tmp_result ) != VLC_SUCCESS )
2566 if( p_face ) FT_Done_Face( p_face );
2567 free( pp_char_styles );
2568 free( psz_unicode );
2569 #if defined(HAVE_FRIBIDI)
2572 if( pi_karaoke_bar )
2573 free( pi_karaoke_bar );
2574 return VLC_EGENERIC;
2579 p_result->x = __MAX( p_result->x, tmp_result.x );
2580 p_result->y += tmp_result.y;
2585 if( *psz_unicode == '\n')
2589 for( c_ptr = psz_unicode; *c_ptr; c_ptr++ )
2591 *c_ptr = *(c_ptr+1);
2596 free( psz_unicode );
2597 if( p_face ) FT_Done_Face( p_face );
2601 free( pp_char_styles );
2602 #if defined(HAVE_FRIBIDI)
2607 p_result->x = __MAX( p_result->x, tmp_result.x );
2608 p_result->y += tmp_result.y;
2611 if( pi_karaoke_bar )
2614 for( p_line = *pp_lines; p_line; p_line=p_line->p_next )
2616 for( k = 0; p_line->pp_glyphs[ k ]; k++, i++ )
2618 if( (pi_karaoke_bar[ i ] & 0x7f) == 0x7f)
2622 else if( (pi_karaoke_bar[ i ] & 0x7f) == 0x00)
2624 /* 100% BG colour will render faster if we
2625 * instead make it 100% FG colour, so leave
2626 * the ratio alone and copy the value across
2628 p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2632 if( pi_karaoke_bar[ i ] & 0x80 )
2634 /* Swap Left and Right sides over for Right aligned
2635 * language text (eg. Arabic, Hebrew)
2637 uint32_t i_tmp = p_line->p_fg_rgb[ k ];
2639 p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2640 p_line->p_bg_rgb[ k ] = i_tmp;
2642 p_line->p_fg_bg_ratio[ k ] = (pi_karaoke_bar[ i ] & 0x7f);
2645 /* Jump over the '\n' at the line-end */
2648 free( pi_karaoke_bar );
2654 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2655 subpicture_region_t *p_region_in )
2657 int rv = VLC_SUCCESS;
2658 stream_t *p_sub = NULL;
2659 xml_t *p_xml = NULL;
2660 xml_reader_t *p_xml_reader = NULL;
2662 if( !p_region_in || !p_region_in->psz_html )
2663 return VLC_EGENERIC;
2665 /* Reset the default fontsize in case screen metrics have changed */
2666 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2668 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2669 (uint8_t *) p_region_in->psz_html,
2670 strlen( p_region_in->psz_html ),
2674 p_xml = xml_Create( p_filter );
2677 vlc_bool_t b_karaoke = VLC_FALSE;
2679 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
2682 /* Look for Root Node */
2683 if( xml_ReaderRead( p_xml_reader ) == 1 )
2685 char *psz_node = xml_ReaderName( p_xml_reader );
2687 if( !strcasecmp( "karaoke", psz_node ) )
2689 /* We're going to have to render the text a number
2690 * of times to show the progress marker on the text.
2692 var_SetBool( p_filter, "text-rerender", VLC_TRUE );
2693 b_karaoke = VLC_TRUE;
2695 else if( !strcasecmp( "text", psz_node ) )
2697 b_karaoke = VLC_FALSE;
2701 /* Only text and karaoke tags are supported */
2702 msg_Dbg( p_filter, "Unsupported top-level tag '%s' ignored.", psz_node );
2703 xml_ReaderDelete( p_xml, p_xml_reader );
2704 p_xml_reader = NULL;
2716 uint32_t i_runs = 0;
2717 uint32_t i_k_runs = 0;
2718 uint32_t *pi_run_lengths = NULL;
2719 uint32_t *pi_k_run_lengths = NULL;
2720 uint32_t *pi_k_durations = NULL;
2721 ft_style_t **pp_styles = NULL;
2723 line_desc_t *p_lines = NULL;
2725 psz_text = (uint32_t *)malloc( strlen( p_region_in->psz_html ) *
2726 sizeof( uint32_t ) );
2731 rv = ProcessNodes( p_filter, p_xml_reader,
2732 p_region_in->p_style, psz_text, &i_len,
2733 &i_runs, &pi_run_lengths, &pp_styles,
2734 b_karaoke, &i_k_runs, &pi_k_run_lengths,
2737 p_region_out->i_x = p_region_in->i_x;
2738 p_region_out->i_y = p_region_in->i_y;
2740 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
2742 rv = ProcessLines( p_filter, psz_text, i_len, i_runs,
2743 pi_run_lengths, pp_styles, &p_lines, &result,
2744 b_karaoke, i_k_runs, pi_k_run_lengths,
2748 for( k=0; k<i_runs; k++)
2749 DeleteStyle( pp_styles[k] );
2751 free( pi_run_lengths );
2754 /* Don't attempt to render text that couldn't be layed out
2757 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
2759 if( config_GetInt( p_filter, "freetype-yuvp" ) )
2761 Render( p_filter, p_region_out, p_lines,
2762 result.x, result.y );
2766 RenderYUVA( p_filter, p_region_out, p_lines,
2767 result.x, result.y );
2771 FreeLines( p_lines );
2773 xml_ReaderDelete( p_xml, p_xml_reader );
2775 xml_Delete( p_xml );
2777 stream_Delete( p_sub );
2783 static char* FontConfig_Select( FcConfig* priv, const char* family,
2784 vlc_bool_t b_bold, vlc_bool_t b_italic, int *i_idx )
2787 FcPattern *pat, *p_pat;
2791 pat = FcPatternCreate();
2792 if (!pat) return NULL;
2794 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
2795 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
2796 FcPatternAddInteger( pat, FC_SLANT, b_italic ? 1000 : 0 );
2797 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? 1000 : 0 );
2799 FcDefaultSubstitute( pat );
2801 if( !FcConfigSubstitute( priv, pat, FcMatchPattern ) )
2803 FcPatternDestroy( pat );
2807 p_pat = FcFontMatch( priv, pat, &result );
2808 FcPatternDestroy( pat );
2809 if( !p_pat ) return NULL;
2811 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
2812 || ( val_b != FcTrue ) )
2814 FcPatternDestroy( p_pat );
2817 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
2822 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
2824 FcPatternDestroy( p_pat );
2829 if( strcasecmp((const char*)val_s, family ) != 0 )
2830 msg_Warn( p_filter, "fontconfig: selected font family is not"
2831 "the requested one: '%s' != '%s'\n",
2832 (const char*)val_s, family );
2835 if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
2837 FcPatternDestroy( p_pat );
2841 FcPatternDestroy( p_pat );
2842 return strdup( (const char*)val_s );
2846 static void FreeLine( line_desc_t *p_line )
2849 for( i = 0; p_line->pp_glyphs[ i ] != NULL; i++ )
2851 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
2853 free( p_line->pp_glyphs );
2854 free( p_line->p_glyph_pos );
2855 free( p_line->p_fg_rgb );
2856 free( p_line->p_bg_rgb );
2857 free( p_line->p_fg_bg_ratio );
2858 free( p_line->pi_underline_offset );
2859 free( p_line->pi_underline_thickness );
2863 static void FreeLines( line_desc_t *p_lines )
2865 line_desc_t *p_line, *p_next;
2867 if( !p_lines ) return;
2869 for( p_line = p_lines; p_line != NULL; p_line = p_next )
2871 p_next = p_line->p_next;
2876 static line_desc_t *NewLine( int i_count )
2878 line_desc_t *p_line = malloc( sizeof(line_desc_t) );
2880 if( !p_line ) return NULL;
2881 p_line->i_height = 0;
2882 p_line->i_width = 0;
2883 p_line->p_next = NULL;
2885 p_line->pp_glyphs = malloc( sizeof(FT_BitmapGlyph) * ( i_count + 1 ) );
2886 p_line->p_glyph_pos = malloc( sizeof( FT_Vector ) * ( i_count + 1 ) );
2887 p_line->p_fg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) );
2888 p_line->p_bg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) );
2889 p_line->p_fg_bg_ratio = calloc( i_count + 1, sizeof( uint8_t ) );
2890 p_line->pi_underline_offset = calloc( i_count + 1, sizeof( uint16_t ) );
2891 p_line->pi_underline_thickness = calloc( i_count + 1, sizeof( uint16_t ) );
2892 if( ( p_line->pp_glyphs == NULL ) ||
2893 ( p_line->p_glyph_pos == NULL ) ||
2894 ( p_line->p_fg_rgb == NULL ) ||
2895 ( p_line->p_bg_rgb == NULL ) ||
2896 ( p_line->p_fg_bg_ratio == NULL ) ||
2897 ( p_line->pi_underline_offset == NULL ) ||
2898 ( p_line->pi_underline_thickness == NULL ) )
2900 if( p_line->pi_underline_thickness )
2901 free( p_line->pi_underline_thickness );
2902 if( p_line->pi_underline_offset ) free( p_line->pi_underline_offset );
2903 if( p_line->p_fg_rgb ) free( p_line->p_fg_rgb );
2904 if( p_line->p_bg_rgb ) free( p_line->p_bg_rgb );
2905 if( p_line->p_fg_bg_ratio ) free( p_line->p_fg_bg_ratio );
2906 if( p_line->p_glyph_pos ) free( p_line->p_glyph_pos );
2907 if( p_line->pp_glyphs ) free( p_line->pp_glyphs );
2911 p_line->pp_glyphs[0] = NULL;
2912 p_line->b_new_color_mode = VLC_FALSE;
2917 static int GetFontSize( filter_t *p_filter )
2919 filter_sys_t *p_sys = p_filter->p_sys;
2923 if( p_sys->i_default_font_size )
2925 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
2926 i_size = p_sys->i_default_font_size * val.i_int / 1000;
2928 i_size = p_sys->i_default_font_size;
2932 var_Get( p_filter, "freetype-rel-fontsize", &val );
2933 i_size = (int)p_filter->fmt_out.video.i_height / val.i_int;
2934 p_filter->p_sys->i_display_height =
2935 p_filter->fmt_out.video.i_height;
2939 msg_Warn( p_filter, "invalid fontsize, using 12" );
2940 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
2941 i_size = 12 * val.i_int / 1000;
2948 static int SetFontSize( filter_t *p_filter, int i_size )
2950 filter_sys_t *p_sys = p_filter->p_sys;
2954 i_size = GetFontSize( p_filter );
2956 msg_Dbg( p_filter, "using fontsize: %i", i_size );
2959 p_sys->i_font_size = i_size;
2961 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
2963 msg_Err( p_filter, "couldn't set font size to %d", i_size );
2964 return VLC_EGENERIC;
2970 static void YUVFromRGB( uint32_t i_argb,
2971 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
2973 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
2974 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
2975 int i_blue = ( i_argb & 0x000000ff );
2977 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
2978 802 * i_blue + 4096 + 131072 ) >> 13, 235);
2979 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
2980 3598 * i_blue + 4096 + 1048576) >> 13, 240);
2981 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
2982 -585 * i_blue + 4096 + 1048576) >> 13, 240);