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 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
37 #include <vlc_filter.h>
38 #include <vlc_stream.h>
40 #include <vlc_input.h>
41 #include <vlc_strings.h>
42 #include <vlc_dialog.h>
43 #include <vlc_memory.h>
48 #include <freetype/ftsynth.h>
49 #include FT_FREETYPE_H
51 #define FT_FLOOR(X) ((X & -64) >> 6)
52 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
54 #define FT_MulFix(v, s) (((v)*(s))>>16)
58 #define DEFAULT_FONT "/Library/Fonts/Arial Black.ttf"
59 #define FC_DEFAULT_FONT "Arial Black"
60 #elif defined( SYS_BEOS )
61 #define DEFAULT_FONT "/boot/beos/etc/fonts/ttfonts/Swiss721.ttf"
62 #define FC_DEFAULT_FONT "Swiss"
63 #elif defined( WIN32 )
64 #define DEFAULT_FONT "" /* Default font found at run-time */
65 #define FC_DEFAULT_FONT "Arial"
66 #elif defined( HAVE_MAEMO )
67 #define DEFAULT_FONT "/usr/share/fonts/nokia/nosnb.ttf"
68 #define FC_DEFAULT_FONT "Nokia Sans Bold"
70 #define DEFAULT_FONT "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
71 #define FC_DEFAULT_FONT "Serif Bold"
74 #if defined(HAVE_FRIBIDI)
75 #include <fribidi/fribidi.h>
78 #ifdef HAVE_FONTCONFIG
79 #include <fontconfig/fontconfig.h>
81 #define DEFAULT_FONT FC_DEFAULT_FONT
86 /*****************************************************************************
88 *****************************************************************************/
89 static int Create ( vlc_object_t * );
90 static void Destroy( vlc_object_t * );
92 #define FONT_TEXT N_("Font")
94 #ifdef HAVE_FONTCONFIG
95 #define FONT_LONGTEXT N_("Font family for the font you want to use")
97 #define FONT_LONGTEXT N_("Fontfile for the font you want to use")
100 #define FONTSIZE_TEXT N_("Font size in pixels")
101 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
102 "that will be rendered on the video. " \
103 "If set to something different than 0 this option will override the " \
104 "relative font size." )
105 #define OPACITY_TEXT N_("Opacity")
106 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
107 "text that will be rendered on the video. 0 = transparent, " \
108 "255 = totally opaque. " )
109 #define COLOR_TEXT N_("Text default color")
110 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
111 "the video. This must be an hexadecimal (like HTML colors). The first two "\
112 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
113 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
114 #define FONTSIZER_TEXT N_("Relative font size")
115 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
116 "fonts that will be rendered on the video. If absolute font size is set, "\
117 "relative size will be overriden." )
119 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
120 static const char *const ppsz_sizes_text[] = {
121 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
122 #define YUVP_TEXT N_("Use YUVP renderer")
123 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
124 "This option is only needed if you want to encode into DVB subtitles" )
125 #define EFFECT_TEXT N_("Font Effect")
126 #define EFFECT_LONGTEXT N_("It is possible to apply effects to the rendered " \
127 "text to improve its readability." )
129 #define EFFECT_BACKGROUND 1
130 #define EFFECT_OUTLINE 2
131 #define EFFECT_OUTLINE_FAT 3
133 static int const pi_effects[] = { 1, 2, 3 };
134 static const char *const ppsz_effects_text[] = {
135 N_("Background"),N_("Outline"), N_("Fat Outline") };
136 static const int pi_color_values[] = {
137 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
138 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
139 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
141 static const char *const ppsz_color_descriptions[] = {
142 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
143 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
144 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
147 set_shortname( N_("Text renderer"))
148 set_description( N_("Freetype2 font renderer") )
149 set_category( CAT_VIDEO )
150 set_subcategory( SUBCAT_VIDEO_SUBPIC )
152 add_font( "freetype-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
155 add_integer( "freetype-fontsize", 0, NULL, FONTSIZE_TEXT,
156 FONTSIZE_LONGTEXT, true )
158 /* opacity valid on 0..255, with default 255 = fully opaque */
159 add_integer_with_range( "freetype-opacity", 255, 0, 255, NULL,
160 OPACITY_TEXT, OPACITY_LONGTEXT, true )
162 /* hook to the color values list, with default 0x00ffffff = white */
163 add_integer( "freetype-color", 0x00FFFFFF, NULL, COLOR_TEXT,
164 COLOR_LONGTEXT, false )
165 change_integer_list( pi_color_values, ppsz_color_descriptions, NULL )
167 add_integer( "freetype-rel-fontsize", 16, NULL, FONTSIZER_TEXT,
168 FONTSIZER_LONGTEXT, false )
169 change_integer_list( pi_sizes, ppsz_sizes_text, NULL )
170 add_integer( "freetype-effect", 2, NULL, EFFECT_TEXT,
171 EFFECT_LONGTEXT, false )
172 change_integer_list( pi_effects, ppsz_effects_text, NULL )
174 add_bool( "freetype-yuvp", false, NULL, YUVP_TEXT,
175 YUVP_LONGTEXT, true )
176 set_capability( "text renderer", 100 )
177 add_shortcut( "text" )
178 set_callbacks( Create, Destroy )
183 /*****************************************************************************
185 *****************************************************************************/
187 /* The RenderText call maps to pf_render_string, defined in vlc_filter.h */
188 static int RenderText( filter_t *, subpicture_region_t *,
189 subpicture_region_t * );
190 #ifdef HAVE_FONTCONFIG
191 static int RenderHtml( filter_t *, subpicture_region_t *,
192 subpicture_region_t * );
193 static char *FontConfig_Select( FcConfig *, const char *,
198 static int LoadFontsFromAttachments( filter_t *p_filter );
200 static int GetFontSize( filter_t *p_filter );
201 static int SetFontSize( filter_t *, int );
202 static void YUVFromRGB( uint32_t i_argb,
203 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v );
205 typedef struct line_desc_t line_desc_t;
208 /** NULL-terminated list of glyphs making the string */
209 FT_BitmapGlyph *pp_glyphs;
210 /** list of relative positions for the glyphs */
211 FT_Vector *p_glyph_pos;
212 /** list of RGB information for styled text
213 * -- if the rendering mode supports it (RenderYUVA) and
214 * b_new_color_mode is set, then it becomes possible to
215 * have multicoloured text within the subtitles. */
218 uint8_t *p_fg_bg_ratio; /* 0x00=100% FG --> 0x7F=100% BG */
219 bool b_new_color_mode;
220 /** underline information -- only supplied if text should be underlined */
221 int *pi_underline_offset;
222 uint16_t *pi_underline_thickness;
226 int i_red, i_green, i_blue;
231 static line_desc_t *NewLine( int );
236 uint32_t i_font_color; /* ARGB */
237 uint32_t i_karaoke_bg_color; /* ARGB */
245 static int Render( filter_t *, subpicture_region_t *, line_desc_t *, int, int);
246 static void FreeLines( line_desc_t * );
247 static void FreeLine( line_desc_t * );
249 /*****************************************************************************
250 * filter_sys_t: freetype local data
251 *****************************************************************************
252 * This structure is part of the video output thread descriptor.
253 * It describes the freetype specific properties of an output thread.
254 *****************************************************************************/
257 FT_Library p_library; /* handle to library */
258 FT_Face p_face; /* handle to face object */
260 uint8_t i_font_opacity;
265 int i_default_font_size;
266 int i_display_height;
267 #ifdef HAVE_FONTCONFIG
268 char* psz_fontfamily;
272 input_attachment_t **pp_font_attachments;
273 int i_font_attachments;
277 #define UCHAR uint32_t
278 #define TR_DEFAULT_FONT p_sys->psz_fontfamily
279 #define TR_FONT_STYLE_PTR ft_style_t *
281 #include "text_renderer.h"
283 /*****************************************************************************
284 * Create: allocates osd-text video thread output method
285 *****************************************************************************
286 * This function allocates and initializes a Clone vout method.
287 *****************************************************************************/
288 static int Create( vlc_object_t *p_this )
290 filter_t *p_filter = (filter_t *)p_this;
292 char *psz_fontfile=NULL;
293 char *psz_fontfamily=NULL;
294 int i_error,fontindex;
296 #ifdef HAVE_FONTCONFIG
297 FcPattern *fontpattern = NULL, *fontmatch = NULL;
298 /* Initialise result to Match, as fontconfig doesnt
299 * really set this other than some error-cases */
300 FcResult fontresult = FcResultMatch;
304 /* Allocate structure */
305 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
308 #ifdef HAVE_FONTCONFIG
309 p_sys->psz_fontfamily = NULL;
313 p_sys->p_library = 0;
314 p_sys->i_font_size = 0;
315 p_sys->i_display_height = 0;
317 var_Create( p_filter, "freetype-rel-fontsize",
318 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
320 psz_fontfamily = var_CreateGetString( p_filter, "freetype-font" );
321 p_sys->i_default_font_size = var_CreateGetInteger( p_filter, "freetype-fontsize" );
322 p_sys->i_effect = var_CreateGetInteger( p_filter, "freetype-effect" );
323 p_sys->i_font_opacity = var_CreateGetInteger( p_filter,"freetype-opacity" );
324 p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
325 p_sys->i_font_color = var_CreateGetInteger( p_filter, "freetype-color" );
326 p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
329 if( !psz_fontfamily || !*psz_fontfamily )
331 free( psz_fontfamily );
332 #ifdef HAVE_FONTCONFIG
333 psz_fontfamily=strdup( DEFAULT_FONT );
335 psz_fontfamily = (char *)malloc( PATH_MAX + 1 );
336 if( !psz_fontfamily )
339 GetWindowsDirectory( psz_fontfamily , PATH_MAX + 1 );
340 strcat( psz_fontfamily, "\\fonts\\arial.ttf" );
342 strcpy( psz_fontfamily, DEFAULT_FONT );
344 msg_Err( p_filter,"User didn't specify fontfile, using %s", psz_fontfamily);
348 #ifdef HAVE_FONTCONFIG
349 /* Lets find some fontfile from freetype-font variable family */
351 if( asprintf( &psz_fontsize, "%d", p_sys->i_default_font_size ) == -1 )
355 dialog_progress_bar_t *p_dialog = dialog_ProgressCreate( p_filter,
356 _("Building font cache"),
357 _("Please wait while your font cache is rebuilt.\n"
358 "This should take less than few minutes."), NULL );
359 char *path = xmalloc( PATH_MAX + 1 );
360 /* Fontconfig doesnt seem to know where windows fonts are with
361 * current contribs. So just tell default windows font directory
362 * is the place to search fonts
364 GetWindowsDirectory( path, PATH_MAX + 1 );
365 strcat( path, "\\fonts" );
367 dialog_ProgressSet( p_dialog, NULL, 0.4 );
369 FcConfigAppFontAddDir( NULL , path );
374 dialog_ProgressSet( p_dialog, NULL, 0.5 );
378 msg_Dbg( p_filter, "Building font database.");
380 FcConfigBuildFonts( NULL );
383 msg_Dbg( p_filter, "Finished building font database." );
384 msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
386 fontpattern = FcPatternCreate();
390 msg_Err( p_filter, "Creating fontpattern failed");
396 dialog_ProgressSet( p_dialog, NULL, 0.7 );
398 FcPatternAddString( fontpattern, FC_FAMILY, psz_fontfamily);
399 FcPatternAddString( fontpattern, FC_SIZE, psz_fontsize );
400 free( psz_fontsize );
402 if( FcConfigSubstitute( NULL, fontpattern, FcMatchPattern ) == FcFalse )
404 msg_Err( p_filter, "FontSubstitute failed");
407 FcDefaultSubstitute( fontpattern );
411 dialog_ProgressSet( p_dialog, NULL, 0.8 );
413 /* testing fontresult here doesn't do any good really, but maybe it will
414 * in future as fontconfig code doesn't set it in all cases and just
415 * returns NULL or doesn't set to to Match on all Match cases.*/
416 fontmatch = FcFontMatch( NULL, fontpattern, &fontresult );
417 if( !fontmatch || fontresult == FcResultNoMatch )
419 msg_Err( p_filter, "Fontmatching failed");
423 FcPatternGetString( fontmatch, FC_FILE, 0, &psz_fontfile);
424 FcPatternGetInteger( fontmatch, FC_INDEX, 0, &fontindex );
427 msg_Err( p_filter, "Failed to get fontfile");
431 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily,
432 psz_fontfile ? psz_fontfile : "(null)" );
433 p_sys->psz_fontfamily = strdup( psz_fontfamily );
437 dialog_ProgressSet( p_dialog, NULL, 1.0 );
438 dialog_ProgressDestroy( p_dialog );
444 #ifdef HAVE_FONTCONFIG
445 p_sys->psz_fontfamily = strdup( DEFAULT_FONT );
446 psz_fontfile = psz_fontfamily;
451 i_error = FT_Init_FreeType( &p_sys->p_library );
454 msg_Err( p_filter, "couldn't initialize freetype" );
458 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
459 fontindex, &p_sys->p_face );
461 if( i_error == FT_Err_Unknown_File_Format )
463 msg_Err( p_filter, "file %s have unknown format",
464 psz_fontfile ? psz_fontfile : "(null)" );
469 msg_Err( p_filter, "failed to load font file %s",
470 psz_fontfile ? psz_fontfile : "(null)" );
474 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
477 msg_Err( p_filter, "font has no unicode translation table" );
481 p_sys->i_use_kerning = FT_HAS_KERNING( p_sys->p_face );
483 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
486 p_sys->pp_font_attachments = NULL;
487 p_sys->i_font_attachments = 0;
489 p_filter->pf_render_text = RenderText;
490 #ifdef HAVE_FONTCONFIG
491 p_filter->pf_render_html = RenderHtml;
492 FcPatternDestroy( fontmatch );
493 FcPatternDestroy( fontpattern );
495 p_filter->pf_render_html = NULL;
498 free( psz_fontfamily );
499 LoadFontsFromAttachments( p_filter );
504 #ifdef HAVE_FONTCONFIG
505 if( fontmatch ) FcPatternDestroy( fontmatch );
506 if( fontpattern ) FcPatternDestroy( fontpattern );
508 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
509 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
510 free( psz_fontfamily );
515 /*****************************************************************************
516 * Destroy: destroy Clone video thread output method
517 *****************************************************************************
518 * Clean up all data and library connections
519 *****************************************************************************/
520 static void Destroy( vlc_object_t *p_this )
522 filter_t *p_filter = (filter_t *)p_this;
523 filter_sys_t *p_sys = p_filter->p_sys;
525 if( p_sys->pp_font_attachments )
529 for( k = 0; k < p_sys->i_font_attachments; k++ )
530 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
532 free( p_sys->pp_font_attachments );
535 #ifdef HAVE_FONTCONFIG
536 if( p_sys->p_xml ) xml_Delete( p_sys->p_xml );
537 free( p_sys->psz_fontfamily );
540 /* FcFini asserts calling the subfunction FcCacheFini()
541 * even if no other library functions have been made since FcInit(),
542 * so don't call it. */
544 FT_Done_Face( p_sys->p_face );
545 FT_Done_FreeType( p_sys->p_library );
549 /*****************************************************************************
550 * Make any TTF/OTF fonts present in the attachments of the media file
551 * and store them for later use by the FreeType Engine
552 *****************************************************************************/
553 static int LoadFontsFromAttachments( filter_t *p_filter )
555 filter_sys_t *p_sys = p_filter->p_sys;
556 input_thread_t *p_input;
557 input_attachment_t **pp_attachments;
558 int i_attachments_cnt;
560 int rv = VLC_SUCCESS;
562 p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
566 if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
568 vlc_object_release(p_input);
572 p_sys->i_font_attachments = 0;
573 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof( input_attachment_t * ));
574 if(! p_sys->pp_font_attachments )
577 for( k = 0; k < i_attachments_cnt; k++ )
579 input_attachment_t *p_attach = pp_attachments[k];
581 if( p_sys->pp_font_attachments )
583 if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
584 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
585 ( p_attach->i_data > 0 ) &&
586 ( p_attach->p_data != NULL ) )
588 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
592 vlc_input_attachment_Delete( p_attach );
597 vlc_input_attachment_Delete( p_attach );
600 free( pp_attachments );
602 vlc_object_release(p_input);
607 /*****************************************************************************
608 * Render: place string in picture
609 *****************************************************************************
610 * This function merges the previously rendered freetype glyphs into a picture
611 *****************************************************************************/
612 static int Render( filter_t *p_filter, subpicture_region_t *p_region,
613 line_desc_t *p_line, int i_width, int i_height )
615 VLC_UNUSED(p_filter);
616 static const uint8_t pi_gamma[16] =
617 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
618 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
622 int i, x, y, i_pitch;
623 uint8_t i_y; /* YUV values, derived from incoming RGB */
626 /* Create a new subpicture region */
627 memset( &fmt, 0, sizeof(video_format_t) );
628 fmt.i_chroma = VLC_CODEC_YUVP;
629 fmt.i_width = fmt.i_visible_width = i_width + 4;
630 fmt.i_height = fmt.i_visible_height = i_height + 4;
631 if( p_region->fmt.i_visible_width > 0 )
632 fmt.i_visible_width = p_region->fmt.i_visible_width;
633 if( p_region->fmt.i_visible_height > 0 )
634 fmt.i_visible_height = p_region->fmt.i_visible_height;
635 fmt.i_x_offset = fmt.i_y_offset = 0;
637 assert( !p_region->p_picture );
638 p_region->p_picture = picture_NewFromFormat( &fmt );
639 if( !p_region->p_picture )
641 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
644 /* Calculate text color components */
645 i_y = (uint8_t)(( 66 * p_line->i_red + 129 * p_line->i_green +
646 25 * p_line->i_blue + 128) >> 8) + 16;
647 i_u = (int8_t)(( -38 * p_line->i_red - 74 * p_line->i_green +
648 112 * p_line->i_blue + 128) >> 8) + 128;
649 i_v = (int8_t)(( 112 * p_line->i_red - 94 * p_line->i_green -
650 18 * p_line->i_blue + 128) >> 8) + 128;
653 fmt.p_palette->i_entries = 16;
654 for( i = 0; i < 8; i++ )
656 fmt.p_palette->palette[i][0] = 0;
657 fmt.p_palette->palette[i][1] = 0x80;
658 fmt.p_palette->palette[i][2] = 0x80;
659 fmt.p_palette->palette[i][3] = pi_gamma[i];
660 fmt.p_palette->palette[i][3] =
661 (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
663 for( i = 8; i < fmt.p_palette->i_entries; i++ )
665 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
666 fmt.p_palette->palette[i][1] = i_u;
667 fmt.p_palette->palette[i][2] = i_v;
668 fmt.p_palette->palette[i][3] = pi_gamma[i];
669 fmt.p_palette->palette[i][3] =
670 (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
673 p_dst = p_region->p_picture->Y_PIXELS;
674 i_pitch = p_region->p_picture->Y_PITCH;
676 /* Initialize the region pixels */
677 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
679 for( ; p_line != NULL; p_line = p_line->p_next )
681 int i_glyph_tmax = 0;
682 int i_bitmap_offset, i_offset, i_align_offset = 0;
683 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
685 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
686 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
689 if( p_line->i_width < i_width )
691 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
693 i_align_offset = i_width - p_line->i_width;
695 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
697 i_align_offset = ( i_width - p_line->i_width ) / 2;
701 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
703 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
705 i_offset = ( p_line->p_glyph_pos[ i ].y +
706 i_glyph_tmax - p_glyph->top + 2 ) *
707 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 2 +
710 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
712 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
714 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
716 ((int)p_glyph->bitmap.buffer[i_bitmap_offset] + 8)/16;
723 /* Outlining (find something better than nearest neighbour filtering ?) */
726 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
727 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
728 uint8_t left, current;
730 for( y = 1; y < (int)fmt.i_height - 1; y++ )
732 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
733 p_dst += p_region->p_picture->Y_PITCH;
736 for( x = 1; x < (int)fmt.i_width - 1; x++ )
739 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
740 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;
744 memset( p_top, 0, fmt.i_width );
750 static void UnderlineGlyphYUVA( int i_line_thickness, int i_line_offset, bool b_ul_next_char,
751 FT_BitmapGlyph p_this_glyph, FT_Vector *p_this_glyph_pos,
752 FT_BitmapGlyph p_next_glyph, FT_Vector *p_next_glyph_pos,
753 int i_glyph_tmax, int i_align_offset,
754 uint8_t i_y, uint8_t i_u, uint8_t i_v,
755 subpicture_region_t *p_region)
759 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
761 p_dst_y = p_region->p_picture->Y_PIXELS;
762 p_dst_u = p_region->p_picture->U_PIXELS;
763 p_dst_v = p_region->p_picture->V_PIXELS;
764 p_dst_a = p_region->p_picture->A_PIXELS;
765 i_pitch = p_region->p_picture->A_PITCH;
767 int i_offset = ( p_this_glyph_pos->y + i_glyph_tmax + i_line_offset + 3 ) * i_pitch +
768 p_this_glyph_pos->x + p_this_glyph->left + 3 + i_align_offset;
770 for( y = 0; y < i_line_thickness; y++ )
772 int i_extra = p_this_glyph->bitmap.width;
776 i_extra = (p_next_glyph_pos->x + p_next_glyph->left) -
777 (p_this_glyph_pos->x + p_this_glyph->left);
779 for( x = 0; x < i_extra; x++ )
783 /* break the underline around the tails of any glyphs which cross it */
784 /* Strikethrough doesn't get broken */
785 for( z = x - i_line_thickness;
786 z < x + i_line_thickness && b_ok && (i_line_offset >= 0);
789 if( p_next_glyph && ( z >= i_extra ) )
791 int i_row = i_line_offset + p_next_glyph->top + y;
793 if( ( p_next_glyph->bitmap.rows > i_row ) &&
794 p_next_glyph->bitmap.buffer[p_next_glyph->bitmap.width * i_row + z-i_extra] )
799 else if ((z > 0 ) && (z < p_this_glyph->bitmap.width))
801 int i_row = i_line_offset + p_this_glyph->top + y;
803 if( ( p_this_glyph->bitmap.rows > i_row ) &&
804 p_this_glyph->bitmap.buffer[p_this_glyph->bitmap.width * i_row + z] )
813 p_dst_y[i_offset+x] = (i_y * 255) >> 8;
814 p_dst_u[i_offset+x] = i_u;
815 p_dst_v[i_offset+x] = i_v;
816 p_dst_a[i_offset+x] = 255;
823 static void DrawBlack( line_desc_t *p_line, int i_width, subpicture_region_t *p_region, int xoffset, int yoffset )
825 uint8_t *p_dst = p_region->p_picture->A_PIXELS;
826 int i_pitch = p_region->p_picture->A_PITCH;
829 for( ; p_line != NULL; p_line = p_line->p_next )
831 int i_glyph_tmax=0, i = 0;
832 int i_bitmap_offset, i_offset, i_align_offset = 0;
833 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
835 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
836 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
839 if( p_line->i_width < i_width )
841 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
843 i_align_offset = i_width - p_line->i_width;
845 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
847 i_align_offset = ( i_width - p_line->i_width ) / 2;
851 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
853 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
855 i_offset = ( p_line->p_glyph_pos[ i ].y +
856 i_glyph_tmax - p_glyph->top + 3 + yoffset ) *
857 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
858 i_align_offset +xoffset;
860 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
862 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
864 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
865 if( p_dst[i_offset+x] <
866 ((int)p_glyph->bitmap.buffer[i_bitmap_offset]) )
868 ((int)p_glyph->bitmap.buffer[i_bitmap_offset]);
877 /*****************************************************************************
878 * Render: place string in picture
879 *****************************************************************************
880 * This function merges the previously rendered freetype glyphs into a picture
881 *****************************************************************************/
882 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
883 line_desc_t *p_line, int i_width, int i_height )
885 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
887 int i, x, y, i_pitch, i_alpha;
888 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
890 if( i_width == 0 || i_height == 0 )
893 /* Create a new subpicture region */
894 memset( &fmt, 0, sizeof(video_format_t) );
895 fmt.i_chroma = VLC_CODEC_YUVA;
896 fmt.i_width = fmt.i_visible_width = i_width + 6;
897 fmt.i_height = fmt.i_visible_height = i_height + 6;
898 if( p_region->fmt.i_visible_width > 0 )
899 fmt.i_visible_width = p_region->fmt.i_visible_width;
900 if( p_region->fmt.i_visible_height > 0 )
901 fmt.i_visible_height = p_region->fmt.i_visible_height;
902 fmt.i_x_offset = fmt.i_y_offset = 0;
904 p_region->p_picture = picture_NewFromFormat( &fmt );
905 if( !p_region->p_picture )
909 /* Calculate text color components */
910 YUVFromRGB( (p_line->i_red << 16) |
911 (p_line->i_green << 8) |
914 i_alpha = p_line->i_alpha;
916 p_dst_y = p_region->p_picture->Y_PIXELS;
917 p_dst_u = p_region->p_picture->U_PIXELS;
918 p_dst_v = p_region->p_picture->V_PIXELS;
919 p_dst_a = p_region->p_picture->A_PIXELS;
920 i_pitch = p_region->p_picture->A_PITCH;
922 /* Initialize the region pixels */
923 if( p_filter->p_sys->i_effect != EFFECT_BACKGROUND )
925 memset( p_dst_y, 0x00, 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, 0, i_pitch * p_region->fmt.i_height );
932 memset( p_dst_y, 0x0, i_pitch * p_region->fmt.i_height );
933 memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
934 memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
935 memset( p_dst_a, 0x80, i_pitch * p_region->fmt.i_height );
937 if( p_filter->p_sys->i_effect == EFFECT_OUTLINE ||
938 p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
940 DrawBlack( p_line, i_width, p_region, 0, 0);
941 DrawBlack( p_line, i_width, p_region, -1, 0);
942 DrawBlack( p_line, i_width, p_region, 0, -1);
943 DrawBlack( p_line, i_width, p_region, 1, 0);
944 DrawBlack( p_line, i_width, p_region, 0, 1);
947 if( p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
949 DrawBlack( p_line, i_width, p_region, -1, -1);
950 DrawBlack( p_line, i_width, p_region, -1, 1);
951 DrawBlack( p_line, i_width, p_region, 1, -1);
952 DrawBlack( p_line, i_width, p_region, 1, 1);
954 DrawBlack( p_line, i_width, p_region, -2, 0);
955 DrawBlack( p_line, i_width, p_region, 0, -2);
956 DrawBlack( p_line, i_width, p_region, 2, 0);
957 DrawBlack( p_line, i_width, p_region, 0, 2);
959 DrawBlack( p_line, i_width, p_region, -2, -2);
960 DrawBlack( p_line, i_width, p_region, -2, 2);
961 DrawBlack( p_line, i_width, p_region, 2, -2);
962 DrawBlack( p_line, i_width, p_region, 2, 2);
964 DrawBlack( p_line, i_width, p_region, -3, 0);
965 DrawBlack( p_line, i_width, p_region, 0, -3);
966 DrawBlack( p_line, i_width, p_region, 3, 0);
967 DrawBlack( p_line, i_width, p_region, 0, 3);
970 for( ; p_line != NULL; p_line = p_line->p_next )
972 int i_glyph_tmax = 0;
973 int i_bitmap_offset, i_offset, i_align_offset = 0;
974 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
976 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
977 i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
980 if( p_line->i_width < i_width )
982 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
984 i_align_offset = i_width - p_line->i_width;
986 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
988 i_align_offset = ( i_width - p_line->i_width ) / 2;
992 for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
994 FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
996 i_offset = ( p_line->p_glyph_pos[ i ].y +
997 i_glyph_tmax - p_glyph->top + 3 ) *
998 i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
1001 if( p_line->b_new_color_mode )
1003 /* Every glyph can (and in fact must) have its own color */
1004 YUVFromRGB( p_line->p_fg_rgb[ i ], &i_y, &i_u, &i_v );
1007 for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
1009 for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
1011 uint8_t i_y_local = i_y;
1012 uint8_t i_u_local = i_u;
1013 uint8_t i_v_local = i_v;
1015 if( p_line->p_fg_bg_ratio != 0x00 )
1017 int i_split = p_glyph->bitmap.width *
1018 p_line->p_fg_bg_ratio[ i ] / 0x7f;
1022 YUVFromRGB( p_line->p_bg_rgb[ i ],
1023 &i_y_local, &i_u_local, &i_v_local );
1027 if( p_glyph->bitmap.buffer[i_bitmap_offset] )
1029 p_dst_y[i_offset+x] = ((p_dst_y[i_offset+x] *(255-(int)p_glyph->bitmap.buffer[i_bitmap_offset])) +
1030 i_y * ((int)p_glyph->bitmap.buffer[i_bitmap_offset])) >> 8;
1032 p_dst_u[i_offset+x] = i_u;
1033 p_dst_v[i_offset+x] = i_v;
1035 if( p_filter->p_sys->i_effect == EFFECT_BACKGROUND )
1036 p_dst_a[i_offset+x] = 0xff;
1039 i_offset += i_pitch;
1042 if( p_line->pi_underline_thickness[ i ] )
1044 UnderlineGlyphYUVA( p_line->pi_underline_thickness[ i ],
1045 p_line->pi_underline_offset[ i ],
1046 (p_line->pp_glyphs[i+1] && (p_line->pi_underline_thickness[ i + 1] > 0)),
1047 p_line->pp_glyphs[i], &(p_line->p_glyph_pos[i]),
1048 p_line->pp_glyphs[i+1], &(p_line->p_glyph_pos[i+1]),
1049 i_glyph_tmax, i_align_offset,
1056 /* Apply the alpha setting */
1057 for( i = 0; i < (int)fmt.i_height * i_pitch; i++ )
1058 p_dst_a[i] = p_dst_a[i] * (255 - i_alpha) / 255;
1064 * This function renders a text subpicture region into another one.
1065 * It also calculates the size needed for this string, and renders the
1066 * needed glyphs into memory. It is used as pf_add_string callback in
1067 * the vout method by this module
1069 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
1070 subpicture_region_t *p_region_in )
1072 filter_sys_t *p_sys = p_filter->p_sys;
1073 line_desc_t *p_lines = NULL, *p_line = NULL, *p_next = NULL, *p_prev = NULL;
1074 int i, i_pen_y, i_pen_x, i_error, i_glyph_index, i_previous;
1075 uint32_t *psz_unicode, *psz_unicode_orig = NULL, i_char, *psz_line_start;
1076 int i_string_length;
1078 vlc_iconv_t iconv_handle = (vlc_iconv_t)(-1);
1079 int i_font_color, i_font_alpha, i_font_size, i_red, i_green, i_blue;
1089 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
1090 psz_string = p_region_in->psz_text;
1091 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
1093 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
1094 i_scale = val.i_int;
1096 if( p_region_in->p_style )
1098 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
1099 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
1100 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
1104 i_font_color = p_sys->i_font_color;
1105 i_font_alpha = 255 - p_sys->i_font_opacity;
1106 i_font_size = p_sys->i_default_font_size * i_scale / 1000;
1109 if( i_font_color == 0xFFFFFF ) i_font_color = p_sys->i_font_color;
1110 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
1111 SetFontSize( p_filter, i_font_size );
1113 i_red = ( i_font_color & 0x00FF0000 ) >> 16;
1114 i_green = ( i_font_color & 0x0000FF00 ) >> 8;
1115 i_blue = i_font_color & 0x000000FF;
1117 result.x = result.y = 0;
1118 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1120 psz_unicode = psz_unicode_orig =
1121 malloc( ( strlen(psz_string) + 1 ) * sizeof(uint32_t) );
1122 if( psz_unicode == NULL )
1124 #if defined(WORDS_BIGENDIAN)
1125 iconv_handle = vlc_iconv_open( "UCS-4BE", "UTF-8" );
1127 iconv_handle = vlc_iconv_open( "UCS-4LE", "UTF-8" );
1129 if( iconv_handle == (vlc_iconv_t)-1 )
1131 msg_Warn( p_filter, "unable to do conversion" );
1137 const char *p_in_buffer = psz_string;
1138 size_t i_in_bytes, i_out_bytes, i_out_bytes_left, i_ret;
1139 i_in_bytes = strlen( psz_string );
1140 i_out_bytes = i_in_bytes * sizeof( uint32_t );
1141 i_out_bytes_left = i_out_bytes;
1142 p_out_buffer = (char *)psz_unicode;
1143 i_ret = vlc_iconv( iconv_handle, (const char**)&p_in_buffer,
1145 &p_out_buffer, &i_out_bytes_left );
1147 vlc_iconv_close( iconv_handle );
1151 msg_Warn( p_filter, "failed to convert string to unicode (%m), "
1152 "bytes left %u", (unsigned)i_in_bytes );
1155 *(uint32_t*)p_out_buffer = 0;
1156 i_string_length = (i_out_bytes - i_out_bytes_left) / sizeof(uint32_t);
1159 #if defined(HAVE_FRIBIDI)
1161 uint32_t *p_fribidi_string;
1162 int32_t start_pos, pos = 0;
1164 p_fribidi_string = malloc( (i_string_length + 1) * sizeof(uint32_t) );
1165 if( !p_fribidi_string )
1168 /* Do bidi conversion line-by-line */
1169 while( pos < i_string_length )
1171 while( pos < i_string_length )
1173 i_char = psz_unicode[pos];
1174 if (i_char != '\r' && i_char != '\n')
1176 p_fribidi_string[pos] = i_char;
1180 while( pos < i_string_length )
1182 i_char = psz_unicode[pos];
1183 if (i_char == '\r' || i_char == '\n')
1187 if (pos > start_pos)
1189 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1190 fribidi_log2vis((FriBidiChar*)psz_unicode + start_pos,
1193 (FriBidiChar*)p_fribidi_string + start_pos,
1198 free( psz_unicode_orig );
1199 psz_unicode = psz_unicode_orig = p_fribidi_string;
1200 p_fribidi_string[ i_string_length ] = 0;
1204 /* Calculate relative glyph positions and a bounding box for the
1206 if( !(p_line = NewLine( strlen( psz_string ))) )
1209 i_pen_x = i_pen_y = 0;
1211 psz_line_start = psz_unicode;
1213 #define face p_sys->p_face
1214 #define glyph face->glyph
1216 while( *psz_unicode )
1218 i_char = *psz_unicode++;
1219 if( i_char == '\r' ) /* ignore CR chars wherever they may be */
1224 if( i_char == '\n' )
1226 psz_line_start = psz_unicode;
1227 if( !(p_next = NewLine( strlen( psz_string ))) )
1229 p_line->p_next = p_next;
1230 p_line->i_width = line.xMax;
1231 p_line->i_height = face->size->metrics.height >> 6;
1232 p_line->pp_glyphs[ i ] = NULL;
1233 p_line->i_alpha = i_font_alpha;
1234 p_line->i_red = i_red;
1235 p_line->i_green = i_green;
1236 p_line->i_blue = i_blue;
1239 result.x = __MAX( result.x, line.xMax );
1240 result.y += face->size->metrics.height >> 6;
1243 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1244 i_pen_y += face->size->metrics.height >> 6;
1246 msg_Dbg( p_filter, "Creating new line, i is %d", i );
1251 i_glyph_index = FT_Get_Char_Index( face, i_char );
1252 if( p_sys->i_use_kerning && i_glyph_index
1256 FT_Get_Kerning( face, i_previous, i_glyph_index,
1257 ft_kerning_default, &delta );
1258 i_pen_x += delta.x >> 6;
1261 p_line->p_glyph_pos[ i ].x = i_pen_x;
1262 p_line->p_glyph_pos[ i ].y = i_pen_y;
1263 i_error = FT_Load_Glyph( face, i_glyph_index, FT_LOAD_DEFAULT );
1266 msg_Err( p_filter, "unable to render text FT_Load_Glyph returned"
1270 i_error = FT_Get_Glyph( glyph, &tmp_glyph );
1273 msg_Err( p_filter, "unable to render text FT_Get_Glyph returned "
1277 FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
1278 i_error = FT_Glyph_To_Bitmap( &tmp_glyph, ft_render_mode_normal, 0, 1);
1281 FT_Done_Glyph( tmp_glyph );
1284 p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
1287 line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
1288 glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
1289 if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
1291 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
1292 p_line->pp_glyphs[ i ] = NULL;
1294 p_line = NewLine( strlen( psz_string ));
1295 if( p_prev ) p_prev->p_next = p_line;
1296 else p_lines = p_line;
1298 uint32_t *psz_unicode_saved = psz_unicode;
1299 while( psz_unicode > psz_line_start && *psz_unicode != ' ' )
1303 if( psz_unicode == psz_line_start )
1304 { /* try harder to break that line */
1305 psz_unicode = psz_unicode_saved;
1306 while( psz_unicode > psz_line_start &&
1307 *psz_unicode != '_' && *psz_unicode != '/' &&
1308 *psz_unicode != '\\' && *psz_unicode != '.' )
1313 if( psz_unicode == psz_line_start )
1315 msg_Warn( p_filter, "unbreakable string" );
1320 *psz_unicode = '\n';
1322 psz_unicode = psz_line_start;
1325 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1328 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1329 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1331 i_previous = i_glyph_index;
1332 i_pen_x += glyph->advance.x >> 6;
1336 p_line->i_width = line.xMax;
1337 p_line->i_height = face->size->metrics.height >> 6;
1338 p_line->pp_glyphs[ i ] = NULL;
1339 p_line->i_alpha = i_font_alpha;
1340 p_line->i_red = i_red;
1341 p_line->i_green = i_green;
1342 p_line->i_blue = i_blue;
1343 result.x = __MAX( result.x, line.xMax );
1344 result.y += line.yMax - line.yMin;
1349 p_region_out->i_x = p_region_in->i_x;
1350 p_region_out->i_y = p_region_in->i_y;
1352 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
1353 Render( p_filter, p_region_out, p_lines, result.x, result.y );
1355 RenderYUVA( p_filter, p_region_out, p_lines, result.x, result.y );
1357 free( psz_unicode_orig );
1358 FreeLines( p_lines );
1362 free( psz_unicode_orig );
1363 FreeLines( p_lines );
1364 return VLC_EGENERIC;
1367 #ifdef HAVE_FONTCONFIG
1368 static ft_style_t *CreateStyle( char *psz_fontname, int i_font_size,
1369 uint32_t i_font_color, uint32_t i_karaoke_bg_color, bool b_bold,
1370 bool b_italic, bool b_uline, bool b_through )
1372 ft_style_t *p_style = malloc( sizeof( ft_style_t ));
1376 p_style->i_font_size = i_font_size;
1377 p_style->i_font_color = i_font_color;
1378 p_style->i_karaoke_bg_color = i_karaoke_bg_color;
1379 p_style->b_italic = b_italic;
1380 p_style->b_bold = b_bold;
1381 p_style->b_underline = b_uline;
1382 p_style->b_through = b_through;
1384 p_style->psz_fontname = strdup( psz_fontname );
1389 static void DeleteStyle( ft_style_t *p_style )
1393 free( p_style->psz_fontname );
1398 static bool StyleEquals( ft_style_t *s1, ft_style_t *s2 )
1405 if(( s1->i_font_size == s2->i_font_size ) &&
1406 ( s1->i_font_color == s2->i_font_color ) &&
1407 ( s1->b_italic == s2->b_italic ) &&
1408 ( s1->b_through == s2->b_through ) &&
1409 ( s1->b_bold == s2->b_bold ) &&
1410 ( s1->b_underline == s2->b_underline ) &&
1411 ( !strcmp( s1->psz_fontname, s2->psz_fontname )))
1418 static void IconvText( filter_t *p_filter, const char *psz_string,
1419 uint32_t *i_string_length, uint32_t **ppsz_unicode )
1421 vlc_iconv_t iconv_handle = (vlc_iconv_t)(-1);
1423 /* If memory hasn't been allocated for our output string, allocate it here
1424 * - the calling function must now be responsible for freeing it.
1426 if( !*ppsz_unicode )
1427 *ppsz_unicode = (uint32_t *)
1428 malloc( (strlen( psz_string ) + 1) * sizeof( uint32_t ));
1430 /* We don't need to handle a NULL pointer in *ppsz_unicode
1431 * if we are instead testing for a non NULL value like we are here */
1435 #if defined(WORDS_BIGENDIAN)
1436 iconv_handle = vlc_iconv_open( "UCS-4BE", "UTF-8" );
1438 iconv_handle = vlc_iconv_open( "UCS-4LE", "UTF-8" );
1440 if( iconv_handle != (vlc_iconv_t)-1 )
1442 char *p_in_buffer, *p_out_buffer;
1443 size_t i_in_bytes, i_out_bytes, i_out_bytes_left, i_ret;
1444 i_in_bytes = strlen( psz_string );
1445 i_out_bytes = i_in_bytes * sizeof( uint32_t );
1446 i_out_bytes_left = i_out_bytes;
1447 p_in_buffer = (char *) psz_string;
1448 p_out_buffer = (char *) *ppsz_unicode;
1449 i_ret = vlc_iconv( iconv_handle, (const char**)&p_in_buffer,
1450 &i_in_bytes, &p_out_buffer, &i_out_bytes_left );
1452 vlc_iconv_close( iconv_handle );
1456 msg_Warn( p_filter, "failed to convert string to unicode (%m), "
1457 "bytes left %u", (unsigned)i_in_bytes );
1461 *(uint32_t*)p_out_buffer = 0;
1463 (i_out_bytes - i_out_bytes_left) / sizeof(uint32_t);
1468 msg_Warn( p_filter, "unable to do conversion" );
1473 static ft_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
1474 font_stack_t **p_fonts, bool b_bold, bool b_italic,
1475 bool b_uline, bool b_through )
1477 ft_style_t *p_style = NULL;
1479 char *psz_fontname = NULL;
1480 uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff;
1481 uint32_t i_karaoke_bg_color = i_font_color;
1482 int i_font_size = p_sys->i_font_size;
1484 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
1485 &i_font_color, &i_karaoke_bg_color ))
1487 p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
1488 i_karaoke_bg_color, b_bold, b_italic, b_uline, b_through );
1493 static int RenderTag( filter_t *p_filter, FT_Face p_face, int i_font_color,
1494 bool b_uline, bool b_through, bool b_bold,
1495 bool b_italic, int i_karaoke_bgcolor,
1496 line_desc_t *p_line, uint32_t *psz_unicode,
1497 int *pi_pen_x, int i_pen_y, int *pi_start,
1498 FT_Vector *p_result )
1503 bool b_first_on_line = true;
1506 int i_pen_x_start = *pi_pen_x;
1508 uint32_t *psz_unicode_start = psz_unicode;
1510 line.xMin = line.xMax = line.yMin = line.yMax = 0;
1512 /* Account for part of line already in position */
1513 for( i=0; i<*pi_start; i++ )
1517 FT_Glyph_Get_CBox( (FT_Glyph) p_line->pp_glyphs[ i ],
1518 ft_glyph_bbox_pixels, &glyph_size );
1520 line.xMax = p_line->p_glyph_pos[ i ].x + glyph_size.xMax -
1521 glyph_size.xMin + p_line->pp_glyphs[ i ]->left;
1522 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1523 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1529 b_first_on_line = false;
1531 while( *psz_unicode && ( *psz_unicode != '\n' ) )
1537 int i_glyph_index = FT_Get_Char_Index( p_face, *psz_unicode++ );
1538 if( FT_HAS_KERNING( p_face ) && i_glyph_index
1542 FT_Get_Kerning( p_face, i_previous, i_glyph_index,
1543 ft_kerning_default, &delta );
1544 *pi_pen_x += delta.x >> 6;
1546 p_line->p_glyph_pos[ i ].x = *pi_pen_x;
1547 p_line->p_glyph_pos[ i ].y = i_pen_y;
1549 i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT );
1553 "unable to render text FT_Load_Glyph returned %d", i_error );
1554 p_line->pp_glyphs[ i ] = NULL;
1555 return VLC_EGENERIC;
1558 /* Do synthetic styling now that Freetype supports it;
1559 * ie. if the font we have loaded is NOT already in the
1560 * style that the tags want, then switch it on; if they
1561 * are then don't. */
1562 if (b_bold && !( p_face->style_flags & FT_STYLE_FLAG_BOLD ))
1563 FT_GlyphSlot_Embolden( p_face->glyph );
1564 if (b_italic && !( p_face->style_flags & FT_STYLE_FLAG_ITALIC ))
1565 FT_GlyphSlot_Oblique( p_face->glyph );
1567 i_error = FT_Get_Glyph( p_face->glyph, &tmp_glyph );
1571 "unable to render text FT_Get_Glyph returned %d", i_error );
1572 p_line->pp_glyphs[ i ] = NULL;
1573 return VLC_EGENERIC;
1575 FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
1576 i_error = FT_Glyph_To_Bitmap( &tmp_glyph, FT_RENDER_MODE_NORMAL, 0, 1);
1579 FT_Done_Glyph( tmp_glyph );
1582 if( b_uline || b_through )
1584 float aOffset = FT_FLOOR(FT_MulFix(p_face->underline_position,
1585 p_face->size->metrics.y_scale));
1586 float aSize = FT_CEIL(FT_MulFix(p_face->underline_thickness,
1587 p_face->size->metrics.y_scale));
1589 p_line->pi_underline_offset[ i ] =
1590 ( aOffset < 0 ) ? -aOffset : aOffset;
1591 p_line->pi_underline_thickness[ i ] =
1592 ( aSize < 0 ) ? -aSize : aSize;
1595 /* Move the baseline to make it strikethrough instead of
1596 * underline. That means that strikethrough takes precedence
1598 float aDescent = FT_FLOOR(FT_MulFix(p_face->descender*2,
1599 p_face->size->metrics.y_scale));
1601 p_line->pi_underline_offset[ i ] -=
1602 ( aDescent < 0 ) ? -aDescent : aDescent;
1606 p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
1607 p_line->p_fg_rgb[ i ] = i_font_color & 0x00ffffff;
1608 p_line->p_bg_rgb[ i ] = i_karaoke_bgcolor & 0x00ffffff;
1609 p_line->p_fg_bg_ratio[ i ] = 0x00;
1611 line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
1612 glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
1613 if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
1615 for( ; i >= *pi_start; i-- )
1616 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
1619 while( psz_unicode > psz_unicode_start && *psz_unicode != ' ' )
1623 if( psz_unicode == psz_unicode_start )
1625 if( b_first_on_line )
1627 msg_Warn( p_filter, "unbreakable string" );
1628 p_line->pp_glyphs[ i ] = NULL;
1629 return VLC_EGENERIC;
1631 *pi_pen_x = i_pen_x_start;
1633 p_line->i_width = line.xMax;
1634 p_line->i_height = __MAX( p_line->i_height,
1635 p_face->size->metrics.height >> 6 );
1636 p_line->pp_glyphs[ i ] = NULL;
1638 p_result->x = __MAX( p_result->x, line.xMax );
1639 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1640 i_yMax - i_yMin ) );
1645 *psz_unicode = '\n';
1647 psz_unicode = psz_unicode_start;
1648 *pi_pen_x = i_pen_x_start;
1656 line.yMax = __MAX( line.yMax, glyph_size.yMax );
1657 line.yMin = __MIN( line.yMin, glyph_size.yMin );
1659 i_previous = i_glyph_index;
1660 *pi_pen_x += p_face->glyph->advance.x >> 6;
1663 p_line->i_width = line.xMax;
1664 p_line->i_height = __MAX( p_line->i_height,
1665 p_face->size->metrics.height >> 6 );
1666 p_line->pp_glyphs[ i ] = NULL;
1668 p_result->x = __MAX( p_result->x, line.xMax );
1669 p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
1670 line.yMax - line.yMin ) );
1674 /* Get rid of any text processed - if necessary repositioning
1675 * at the start of a new line of text
1679 *psz_unicode_start = '\0';
1681 else if( psz_unicode > psz_unicode_start )
1683 for( i=0; psz_unicode[ i ]; i++ )
1684 psz_unicode_start[ i ] = psz_unicode[ i ];
1685 psz_unicode_start[ i ] = '\0';
1691 static void SetupLine( filter_t *p_filter, const char *psz_text_in,
1692 uint32_t **psz_text_out, uint32_t *pi_runs,
1693 uint32_t **ppi_run_lengths, ft_style_t ***ppp_styles,
1694 ft_style_t *p_style )
1696 uint32_t i_string_length = 0;
1698 IconvText( p_filter, psz_text_in, &i_string_length, psz_text_out );
1699 *psz_text_out += i_string_length;
1701 if( ppp_styles && ppi_run_lengths )
1705 /* XXX this logic looks somewhat broken */
1709 *ppp_styles = realloc_or_free( *ppp_styles,
1710 *pi_runs * sizeof( ft_style_t * ) );
1712 else if( *pi_runs == 1 )
1714 *ppp_styles = malloc( *pi_runs * sizeof( ft_style_t * ) );
1717 /* We have just malloc'ed this memory successfully -
1718 * *pi_runs HAS to be within the memory area of *ppp_styles */
1721 (*ppp_styles)[ *pi_runs - 1 ] = p_style;
1725 /* XXX more iffy logic */
1727 if( *ppi_run_lengths )
1729 *ppi_run_lengths = realloc_or_free( *ppi_run_lengths,
1730 *pi_runs * sizeof( uint32_t ) );
1732 else if( *pi_runs == 1 )
1734 *ppi_run_lengths = (uint32_t *)
1735 malloc( *pi_runs * sizeof( uint32_t ) );
1738 /* same remarks here */
1739 if( *ppi_run_lengths )
1741 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
1744 /* If we couldn't use the p_style argument due to memory allocation
1745 * problems above, release it here.
1747 if( p_style ) DeleteStyle( p_style );
1750 static int CheckForEmbeddedFont( filter_sys_t *p_sys, FT_Face *pp_face, ft_style_t *p_style )
1754 for( k=0; k < p_sys->i_font_attachments; k++ )
1756 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
1758 FT_Face p_face = NULL;
1760 while( 0 == FT_New_Memory_Face( p_sys->p_library,
1768 bool match = !strcasecmp( p_face->family_name,
1769 p_style->psz_fontname );
1771 if( p_face->style_flags & FT_STYLE_FLAG_BOLD )
1772 match = match && p_style->b_bold;
1774 match = match && !p_style->b_bold;
1776 if( p_face->style_flags & FT_STYLE_FLAG_ITALIC )
1777 match = match && p_style->b_italic;
1779 match = match && !p_style->b_italic;
1787 FT_Done_Face( p_face );
1792 return VLC_EGENERIC;
1795 static int ProcessLines( filter_t *p_filter,
1800 uint32_t *pi_run_lengths,
1801 ft_style_t **pp_styles,
1802 line_desc_t **pp_lines,
1804 FT_Vector *p_result,
1808 uint32_t *pi_k_run_lengths,
1809 uint32_t *pi_k_durations )
1811 filter_sys_t *p_sys = p_filter->p_sys;
1812 ft_style_t **pp_char_styles;
1813 int *p_new_positions = NULL;
1814 int8_t *p_levels = NULL;
1815 uint8_t *pi_karaoke_bar = NULL;
1819 /* Assign each character in the text string its style explicitly, so that
1820 * after the characters have been shuffled around by Fribidi, we can re-apply
1821 * the styles, and to simplify the calculation of runs within a line.
1823 pp_char_styles = (ft_style_t **) malloc( i_len * sizeof( ft_style_t * ));
1824 if( !pp_char_styles )
1829 pi_karaoke_bar = (uint8_t *) malloc( i_len * sizeof( uint8_t ));
1830 /* If we can't allocate sufficient memory for karaoke, continue anyway -
1831 * we just won't be able to display the progress bar; at least we'll
1837 for( j = 0; j < i_runs; j++ )
1838 for( k = 0; k < pi_run_lengths[ j ]; k++ )
1839 pp_char_styles[ i++ ] = pp_styles[ j ];
1841 #if defined(HAVE_FRIBIDI)
1843 ft_style_t **pp_char_styles_new;
1844 int *p_old_positions;
1845 uint32_t *p_fribidi_string;
1846 int start_pos, pos = 0;
1848 pp_char_styles_new = (ft_style_t **)
1849 malloc( i_len * sizeof( ft_style_t * ));
1851 p_fribidi_string = (uint32_t *)
1852 malloc( (i_len + 1) * sizeof(uint32_t) );
1853 p_old_positions = (int *)
1854 malloc( (i_len + 1) * sizeof( int ) );
1855 p_new_positions = (int *)
1856 malloc( (i_len + 1) * sizeof( int ) );
1857 p_levels = (int8_t *)
1858 malloc( (i_len + 1) * sizeof( int8_t ) );
1860 if( ! pp_char_styles_new ||
1861 ! p_fribidi_string ||
1862 ! p_old_positions ||
1863 ! p_new_positions ||
1867 free( p_old_positions );
1868 free( p_new_positions );
1869 free( p_fribidi_string );
1870 free( pp_char_styles_new );
1871 free( pi_karaoke_bar );
1873 free( pp_char_styles );
1877 /* Do bidi conversion line-by-line */
1880 while(pos < i_len) {
1881 if (psz_text[pos] != '\n')
1883 p_fribidi_string[pos] = psz_text[pos];
1884 pp_char_styles_new[pos] = pp_char_styles[pos];
1885 p_new_positions[pos] = pos;
1890 while(pos < i_len) {
1891 if (psz_text[pos] == '\n')
1895 if (pos > start_pos)
1897 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1898 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1899 pos - start_pos, &base_dir,
1900 (FriBidiChar*)p_fribidi_string + start_pos,
1901 p_new_positions + start_pos,
1903 p_levels + start_pos );
1904 for( j = (uint32_t) start_pos; j < (uint32_t) pos; j++ )
1906 pp_char_styles_new[ j ] = pp_char_styles[ start_pos +
1907 p_old_positions[ j - start_pos ] ];
1908 p_new_positions[ j ] += start_pos;
1912 free( p_old_positions );
1913 free( pp_char_styles );
1914 pp_char_styles = pp_char_styles_new;
1915 psz_text = p_fribidi_string;
1916 p_fribidi_string[ i_len ] = 0;
1919 /* Work out the karaoke */
1920 if( pi_karaoke_bar )
1922 int64_t i_last_duration = 0;
1923 int64_t i_duration = 0;
1924 int64_t i_start_pos = 0;
1925 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1927 for( k = 0; k< i_k_runs; k++ )
1929 double fraction = 0.0;
1931 i_duration += pi_k_durations[ k ];
1933 if( i_duration < i_elapsed )
1935 /* Completely finished this run-length -
1936 * let it render normally */
1940 else if( i_elapsed < i_last_duration )
1942 /* Haven't got up to this segment yet -
1943 * render it completely in karaoke BG mode */
1949 /* Partway through this run */
1951 fraction = (double)(i_elapsed - i_last_duration) /
1952 (double)pi_k_durations[ k ];
1954 for( i = 0; i < pi_k_run_lengths[ k ]; i++ )
1956 double shade = pi_k_run_lengths[ k ] * fraction;
1958 if( p_new_positions )
1959 j = p_new_positions[ i_start_pos + i ];
1961 j = i_start_pos + i;
1963 if( i < (uint32_t)shade )
1964 pi_karaoke_bar[ j ] = 0xff;
1965 else if( (double)i > shade )
1966 pi_karaoke_bar[ j ] = 0x00;
1969 shade -= (int)shade;
1970 pi_karaoke_bar[ j ] = ((int)(shade * 128.0) & 0x7f) |
1971 ((p_levels ? (p_levels[ j ] % 2) : 0 ) << 7);
1975 i_last_duration = i_duration;
1976 i_start_pos += pi_k_run_lengths[ k ];
1980 free( p_new_positions );
1982 FT_Vector tmp_result;
1984 line_desc_t *p_line = NULL;
1985 line_desc_t *p_prev = NULL;
1991 p_result->x = p_result->y = 0;
1992 tmp_result.x = tmp_result.y = 0;
1995 for( k = 0; k <= (uint32_t) i_len; k++ )
1997 if( ( k == (uint32_t) i_len ) ||
1999 !StyleEquals( pp_char_styles[ k ], pp_char_styles[ k - 1] ) ) )
2001 ft_style_t *p_style = pp_char_styles[ k - 1 ];
2003 /* End of the current style run */
2004 FT_Face p_face = NULL;
2007 /* Look for a match amongst our attachments first */
2008 CheckForEmbeddedFont( p_sys, &p_face, p_style );
2012 char *psz_fontfile = NULL;
2014 psz_fontfile = FontConfig_Select( NULL,
2015 p_style->psz_fontname,
2019 if( psz_fontfile && ! *psz_fontfile )
2021 msg_Warn( p_filter, "Fontconfig was unable to find a font: \"%s\" %s"
2022 " so using default font", p_style->psz_fontname,
2023 ((p_style->b_bold && p_style->b_italic) ? "(Bold,Italic)" :
2024 (p_style->b_bold ? "(Bold)" :
2025 (p_style->b_italic ? "(Italic)" : ""))) );
2026 free( psz_fontfile );
2027 psz_fontfile = NULL;
2032 if( FT_New_Face( p_sys->p_library,
2033 psz_fontfile, i_idx, &p_face ) )
2035 free( psz_fontfile );
2036 free( pp_char_styles );
2037 #if defined(HAVE_FRIBIDI)
2040 free( pi_karaoke_bar );
2041 return VLC_EGENERIC;
2043 free( psz_fontfile );
2047 FT_Select_Charmap( p_face, ft_encoding_unicode ) )
2049 /* We've loaded a font face which is unhelpful for actually
2050 * rendering text - fallback to the default one.
2052 FT_Done_Face( p_face );
2056 if( FT_Select_Charmap( p_face ? p_face : p_sys->p_face,
2057 ft_encoding_unicode ) ||
2058 FT_Set_Pixel_Sizes( p_face ? p_face : p_sys->p_face, 0,
2059 p_style->i_font_size ) )
2061 if( p_face ) FT_Done_Face( p_face );
2062 free( pp_char_styles );
2063 #if defined(HAVE_FRIBIDI)
2066 free( pi_karaoke_bar );
2067 return VLC_EGENERIC;
2069 p_sys->i_use_kerning =
2070 FT_HAS_KERNING( ( p_face ? p_face : p_sys->p_face ) );
2073 uint32_t *psz_unicode = (uint32_t *)
2074 malloc( (k - i_prev + 1) * sizeof( uint32_t ));
2077 if( p_face ) FT_Done_Face( p_face );
2078 free( pp_char_styles );
2079 free( psz_unicode );
2080 #if defined(HAVE_FRIBIDI)
2083 free( pi_karaoke_bar );
2086 memcpy( psz_unicode, psz_text + i_prev,
2087 sizeof( uint32_t ) * ( k - i_prev ) );
2088 psz_unicode[ k - i_prev ] = 0;
2089 while( *psz_unicode )
2093 if( !(p_line = NewLine( i_len - i_prev)) )
2095 if( p_face ) FT_Done_Face( p_face );
2096 free( pp_char_styles );
2097 free( psz_unicode );
2098 #if defined(HAVE_FRIBIDI)
2101 free( pi_karaoke_bar );
2104 /* New Color mode only works in YUVA rendering mode --
2105 * (RGB mode has palette constraints on it). We therefore
2106 * need to populate the legacy colour fields also.
2108 p_line->b_new_color_mode = true;
2109 p_line->i_alpha = ( p_style->i_font_color & 0xff000000 ) >> 24;
2110 p_line->i_red = ( p_style->i_font_color & 0x00ff0000 ) >> 16;
2111 p_line->i_green = ( p_style->i_font_color & 0x0000ff00 ) >> 8;
2112 p_line->i_blue = ( p_style->i_font_color & 0x000000ff );
2113 p_line->p_next = NULL;
2115 i_pen_y += tmp_result.y;
2119 if( p_prev ) p_prev->p_next = p_line;
2120 else *pp_lines = p_line;
2123 if( RenderTag( p_filter, p_face ? p_face : p_sys->p_face,
2124 p_style->i_font_color, p_style->b_underline,
2128 p_style->i_karaoke_bg_color,
2129 p_line, psz_unicode, &i_pen_x, i_pen_y, &i_posn,
2130 &tmp_result ) != VLC_SUCCESS )
2132 if( p_face ) FT_Done_Face( p_face );
2133 free( pp_char_styles );
2134 free( psz_unicode );
2135 #if defined(HAVE_FRIBIDI)
2138 free( pi_karaoke_bar );
2139 return VLC_EGENERIC;
2144 p_result->x = __MAX( p_result->x, tmp_result.x );
2145 p_result->y += tmp_result.y;
2150 if( *psz_unicode == '\n')
2154 for( c_ptr = psz_unicode; *c_ptr; c_ptr++ )
2156 *c_ptr = *(c_ptr+1);
2161 free( psz_unicode );
2162 if( p_face ) FT_Done_Face( p_face );
2166 free( pp_char_styles );
2167 #if defined(HAVE_FRIBIDI)
2172 p_result->x = __MAX( p_result->x, tmp_result.x );
2173 p_result->y += tmp_result.y;
2176 if( pi_karaoke_bar )
2179 for( p_line = *pp_lines; p_line; p_line=p_line->p_next )
2181 for( k = 0; p_line->pp_glyphs[ k ]; k++, i++ )
2183 if( (pi_karaoke_bar[ i ] & 0x7f) == 0x7f)
2187 else if( (pi_karaoke_bar[ i ] & 0x7f) == 0x00)
2189 /* 100% BG colour will render faster if we
2190 * instead make it 100% FG colour, so leave
2191 * the ratio alone and copy the value across
2193 p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2197 if( pi_karaoke_bar[ i ] & 0x80 )
2199 /* Swap Left and Right sides over for Right aligned
2200 * language text (eg. Arabic, Hebrew)
2202 uint32_t i_tmp = p_line->p_fg_rgb[ k ];
2204 p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
2205 p_line->p_bg_rgb[ k ] = i_tmp;
2207 p_line->p_fg_bg_ratio[ k ] = (pi_karaoke_bar[ i ] & 0x7f);
2210 /* Jump over the '\n' at the line-end */
2213 free( pi_karaoke_bar );
2219 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
2220 subpicture_region_t *p_region_in )
2222 int rv = VLC_SUCCESS;
2223 stream_t *p_sub = NULL;
2224 xml_reader_t *p_xml_reader = NULL;
2226 if( !p_region_in || !p_region_in->psz_html )
2227 return VLC_EGENERIC;
2229 /* Reset the default fontsize in case screen metrics have changed */
2230 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
2232 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
2233 (uint8_t *) p_region_in->psz_html,
2234 strlen( p_region_in->psz_html ),
2238 if( !p_filter->p_sys->p_xml ) p_filter->p_sys->p_xml = xml_Create( p_filter );
2239 if( p_filter->p_sys->p_xml )
2241 bool b_karaoke = false;
2243 p_xml_reader = xml_ReaderCreate( p_filter->p_sys->p_xml, p_sub );
2246 /* Look for Root Node */
2247 if( xml_ReaderRead( p_xml_reader ) == 1 )
2249 char *psz_node = xml_ReaderName( p_xml_reader );
2251 if( !strcasecmp( "karaoke", psz_node ) )
2253 /* We're going to have to render the text a number
2254 * of times to show the progress marker on the text.
2256 var_SetBool( p_filter, "text-rerender", true );
2259 else if( !strcasecmp( "text", psz_node ) )
2265 /* Only text and karaoke tags are supported */
2266 msg_Dbg( p_filter, "Unsupported top-level tag '%s' ignored.", psz_node );
2267 xml_ReaderDelete( p_filter->p_sys->p_xml, p_xml_reader );
2268 p_xml_reader = NULL;
2280 uint32_t i_runs = 0;
2281 uint32_t i_k_runs = 0;
2282 uint32_t *pi_run_lengths = NULL;
2283 uint32_t *pi_k_run_lengths = NULL;
2284 uint32_t *pi_k_durations = NULL;
2285 ft_style_t **pp_styles = NULL;
2287 line_desc_t *p_lines = NULL;
2289 psz_text = (uint32_t *)malloc( strlen( p_region_in->psz_html ) *
2290 sizeof( uint32_t ) );
2295 rv = ProcessNodes( p_filter, p_xml_reader,
2296 p_region_in->p_style, psz_text, &i_len,
2297 &i_runs, &pi_run_lengths, &pp_styles,
2299 b_karaoke, &i_k_runs, &pi_k_run_lengths,
2302 p_region_out->i_x = p_region_in->i_x;
2303 p_region_out->i_y = p_region_in->i_y;
2305 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
2307 rv = ProcessLines( p_filter, psz_text, i_len, i_runs,
2308 pi_run_lengths, pp_styles, &p_lines, &result,
2309 b_karaoke, i_k_runs, pi_k_run_lengths,
2313 for( k=0; k<i_runs; k++)
2314 DeleteStyle( pp_styles[k] );
2316 free( pi_run_lengths );
2319 /* Don't attempt to render text that couldn't be layed out
2322 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
2324 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
2326 Render( p_filter, p_region_out, p_lines,
2327 result.x, result.y );
2331 RenderYUVA( p_filter, p_region_out, p_lines,
2332 result.x, result.y );
2336 FreeLines( p_lines );
2338 xml_ReaderDelete( p_filter->p_sys->p_xml, p_xml_reader );
2341 stream_Delete( p_sub );
2347 static char* FontConfig_Select( FcConfig* priv, const char* family,
2348 bool b_bold, bool b_italic, int *i_idx )
2351 FcPattern *pat, *p_pat;
2355 pat = FcPatternCreate();
2356 if (!pat) return NULL;
2358 FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
2359 FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
2360 FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
2361 FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
2363 FcDefaultSubstitute( pat );
2365 if( !FcConfigSubstitute( priv, pat, FcMatchPattern ) )
2367 FcPatternDestroy( pat );
2371 p_pat = FcFontMatch( priv, pat, &result );
2372 FcPatternDestroy( pat );
2373 if( !p_pat ) return NULL;
2375 if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
2376 || ( val_b != FcTrue ) )
2378 FcPatternDestroy( p_pat );
2381 if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
2386 if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
2388 FcPatternDestroy( p_pat );
2393 if( strcasecmp((const char*)val_s, family ) != 0 )
2394 msg_Warn( p_filter, "fontconfig: selected font family is not"
2395 "the requested one: '%s' != '%s'\n",
2396 (const char*)val_s, family );
2399 if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
2401 FcPatternDestroy( p_pat );
2405 FcPatternDestroy( p_pat );
2406 return strdup( (const char*)val_s );
2410 static void SetupLine( filter_t *p_filter, const char *psz_text_in,
2411 uint32_t **psz_text_out, uint32_t *pi_runs,
2412 uint32_t **ppi_run_lengths, ft_style_t ***ppp_styles,
2413 ft_style_t *p_style )
2415 VLC_UNUSED(p_filter);
2416 VLC_UNUSED(psz_text_in);
2417 VLC_UNUSED(psz_text_out);
2418 VLC_UNUSED(pi_runs);
2419 VLC_UNUSED(ppi_run_lengths);
2420 VLC_UNUSED(ppp_styles);
2421 VLC_UNUSED(p_style);
2424 static ft_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
2425 font_stack_t **p_fonts, bool b_bold, bool b_italic,
2426 bool b_uline, bool b_through )
2429 VLC_UNUSED(p_fonts);
2431 VLC_UNUSED(b_italic);
2432 VLC_UNUSED(b_uline);
2433 VLC_UNUSED(b_through);
2438 static void FreeLine( line_desc_t *p_line )
2441 for( i = 0; p_line->pp_glyphs[ i ] != NULL; i++ )
2443 FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
2445 free( p_line->pp_glyphs );
2446 free( p_line->p_glyph_pos );
2447 free( p_line->p_fg_rgb );
2448 free( p_line->p_bg_rgb );
2449 free( p_line->p_fg_bg_ratio );
2450 free( p_line->pi_underline_offset );
2451 free( p_line->pi_underline_thickness );
2455 static void FreeLines( line_desc_t *p_lines )
2457 line_desc_t *p_line, *p_next;
2459 if( !p_lines ) return;
2461 for( p_line = p_lines; p_line != NULL; p_line = p_next )
2463 p_next = p_line->p_next;
2468 static line_desc_t *NewLine( int i_count )
2470 line_desc_t *p_line = malloc( sizeof(line_desc_t) );
2472 if( !p_line ) return NULL;
2473 p_line->i_height = 0;
2474 p_line->i_width = 0;
2475 p_line->p_next = NULL;
2477 p_line->pp_glyphs = malloc( sizeof(FT_BitmapGlyph) * ( i_count + 1 ) );
2478 p_line->p_glyph_pos = malloc( sizeof( FT_Vector ) * ( i_count + 1 ) );
2479 p_line->p_fg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) );
2480 p_line->p_bg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) );
2481 p_line->p_fg_bg_ratio = calloc( i_count + 1, sizeof( uint8_t ) );
2482 p_line->pi_underline_offset = calloc( i_count + 1, sizeof( int ) );
2483 p_line->pi_underline_thickness = calloc( i_count + 1, sizeof( uint16_t ) );
2484 if( ( p_line->pp_glyphs == NULL ) ||
2485 ( p_line->p_glyph_pos == NULL ) ||
2486 ( p_line->p_fg_rgb == NULL ) ||
2487 ( p_line->p_bg_rgb == NULL ) ||
2488 ( p_line->p_fg_bg_ratio == NULL ) ||
2489 ( p_line->pi_underline_offset == NULL ) ||
2490 ( p_line->pi_underline_thickness == NULL ) )
2492 free( p_line->pi_underline_thickness );
2493 free( p_line->pi_underline_offset );
2494 free( p_line->p_fg_rgb );
2495 free( p_line->p_bg_rgb );
2496 free( p_line->p_fg_bg_ratio );
2497 free( p_line->p_glyph_pos );
2498 free( p_line->pp_glyphs );
2502 p_line->pp_glyphs[0] = NULL;
2503 p_line->b_new_color_mode = false;
2508 static int GetFontSize( filter_t *p_filter )
2510 filter_sys_t *p_sys = p_filter->p_sys;
2514 if( p_sys->i_default_font_size )
2516 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
2517 i_size = p_sys->i_default_font_size * val.i_int / 1000;
2519 i_size = p_sys->i_default_font_size;
2523 var_Get( p_filter, "freetype-rel-fontsize", &val );
2526 i_size = (int)p_filter->fmt_out.video.i_height / val.i_int;
2527 p_filter->p_sys->i_display_height =
2528 p_filter->fmt_out.video.i_height;
2533 msg_Warn( p_filter, "invalid fontsize, using 12" );
2534 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
2535 i_size = 12 * val.i_int / 1000;
2542 static int SetFontSize( filter_t *p_filter, int i_size )
2544 filter_sys_t *p_sys = p_filter->p_sys;
2548 i_size = GetFontSize( p_filter );
2550 msg_Dbg( p_filter, "using fontsize: %i", i_size );
2553 p_sys->i_font_size = i_size;
2555 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
2557 msg_Err( p_filter, "couldn't set font size to %d", i_size );
2558 return VLC_EGENERIC;
2564 static void YUVFromRGB( uint32_t i_argb,
2565 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
2567 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
2568 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
2569 int i_blue = ( i_argb & 0x000000ff );
2571 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
2572 802 * i_blue + 4096 + 131072 ) >> 13, 235);
2573 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
2574 3598 * i_blue + 4096 + 1048576) >> 13, 240);
2575 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
2576 -585 * i_blue + 4096 + 1048576) >> 13, 240);