1 /*****************************************************************************
2 * freetype.c : Put text on the video, using freetype2
3 *****************************************************************************
4 * Copyright (C) 2002 - 2012 VLC authors and VideoLAN
7 * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8 * Gildas Bazin <gbazin@videolan.org>
9 * Bernie Purcell <bitmap@videolan.org>
10 * Jean-Baptiste Kempf <jb@videolan.org>
11 * Felix Paul Kühne <fkuehne@videolan.org>
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU Lesser General Public License as published by
15 * the Free Software Foundation; either version 2.1 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public License
24 * along with this program; if not, write to the Free Software Foundation, Inc.,
25 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 *****************************************************************************/
28 /*****************************************************************************
30 *****************************************************************************/
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
39 #include <vlc_stream.h> /* stream_MemoryNew */
40 #include <vlc_input.h> /* vlc_input_attachment_* */
41 #include <vlc_xml.h> /* xml_reader */
42 #include <vlc_dialog.h> /* FcCache dialog */
43 #include <vlc_filter.h> /* filter_sys_t */
44 #include <vlc_text_style.h> /* text_style_t*/
48 #include FT_FREETYPE_H
51 #include FT_SYNTHESIS_H
53 #define FT_FLOOR(X) ((X & -64) >> 6)
54 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
56 #define FT_MulFix(v, s) (((v)*(s))>>16)
60 #if defined(HAVE_FRIBIDI)
61 # include <fribidi/fribidi.h>
66 # include <TargetConditionals.h>
67 # undef HAVE_FONTCONFIG
68 # define HAVE_GET_FONT_BY_FAMILY_NAME
73 # undef HAVE_FONTCONFIG
74 # if !VLC_WINSTORE_APP
75 # define HAVE_GET_FONT_BY_FAMILY_NAME
80 #ifdef HAVE_FONTCONFIG
81 # define HAVE_GET_FONT_BY_FAMILY_NAME
86 #include "text_renderer.h"
87 #include "platform_fonts.h"
89 /*****************************************************************************
91 *****************************************************************************/
92 static int Create ( vlc_object_t * );
93 static void Destroy( vlc_object_t * );
95 #define FONT_TEXT N_("Font")
96 #define MONOSPACE_FONT_TEXT N_("Monospace Font")
98 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
99 #define FONT_LONGTEXT N_("Font file for the font you want to use")
101 #define FONTSIZE_TEXT N_("Font size in pixels")
102 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
103 "that will be rendered on the video. " \
104 "If set to something different than 0 this option will override the " \
105 "relative font size." )
106 #define OPACITY_TEXT N_("Text opacity")
107 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
108 "text that will be rendered on the video. 0 = transparent, " \
109 "255 = totally opaque. " )
110 #define COLOR_TEXT N_("Text default color")
111 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
112 "the video. This must be an hexadecimal (like HTML colors). The first two "\
113 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
114 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
115 #define FONTSIZER_TEXT N_("Relative font size")
116 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
117 "fonts that will be rendered on the video. If absolute font size is set, "\
118 "relative size will be overridden." )
119 #define BOLD_TEXT N_("Force bold")
121 #define BG_OPACITY_TEXT N_("Background opacity")
122 #define BG_COLOR_TEXT N_("Background color")
124 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
125 #define OUTLINE_COLOR_TEXT N_("Outline color")
126 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
128 #define SHADOW_OPACITY_TEXT N_("Shadow opacity")
129 #define SHADOW_COLOR_TEXT N_("Shadow color")
130 #define SHADOW_ANGLE_TEXT N_("Shadow angle")
131 #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
134 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
135 static const char *const ppsz_sizes_text[] = {
136 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
137 #define YUVP_TEXT N_("Use YUVP renderer")
138 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
139 "This option is only needed if you want to encode into DVB subtitles" )
141 static const int pi_color_values[] = {
142 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
143 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
144 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
146 static const char *const ppsz_color_descriptions[] = {
147 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
148 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
149 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
151 static const int pi_outline_thickness[] = {
154 static const char *const ppsz_outline_thickness[] = {
155 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
159 set_shortname( N_("Text renderer"))
160 set_description( N_("Freetype2 font renderer") )
161 set_category( CAT_VIDEO )
162 set_subcategory( SUBCAT_VIDEO_SUBPIC )
164 #ifdef HAVE_GET_FONT_BY_FAMILY_NAME
165 add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
166 add_font( "freetype-monofont", DEFAULT_MONOSPACE_FAMILY, MONOSPACE_FONT_TEXT, FAMILY_LONGTEXT, false )
168 add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
169 add_loadfile( "freetype-monofont", DEFAULT_MONOSPACE_FONT_FILE, MONOSPACE_FONT_TEXT, FONT_LONGTEXT, false )
172 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
173 FONTSIZE_LONGTEXT, true )
174 change_integer_range( 0, 4096)
177 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
178 FONTSIZER_LONGTEXT, false )
179 change_integer_list( pi_sizes, ppsz_sizes_text )
182 /* opacity valid on 0..255, with default 255 = fully opaque */
183 add_integer_with_range( "freetype-opacity", 255, 0, 255,
184 OPACITY_TEXT, OPACITY_LONGTEXT, false )
187 /* hook to the color values list, with default 0x00ffffff = white */
188 add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
189 COLOR_LONGTEXT, false )
190 change_integer_list( pi_color_values, ppsz_color_descriptions )
193 add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
196 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
197 BG_OPACITY_TEXT, NULL, false )
199 add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
201 change_integer_list( pi_color_values, ppsz_color_descriptions )
204 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
205 OUTLINE_OPACITY_TEXT, NULL, false )
207 add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
209 change_integer_list( pi_color_values, ppsz_color_descriptions )
211 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
213 change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
216 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
217 SHADOW_OPACITY_TEXT, NULL, false )
219 add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
221 change_integer_list( pi_color_values, ppsz_color_descriptions )
223 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
224 SHADOW_ANGLE_TEXT, NULL, false )
226 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
227 SHADOW_DISTANCE_TEXT, NULL, false )
230 add_obsolete_integer( "freetype-effect" );
232 add_bool( "freetype-yuvp", false, YUVP_TEXT,
233 YUVP_LONGTEXT, true )
234 set_capability( "text renderer", 100 )
235 add_shortcut( "text" )
236 set_callbacks( Create, Destroy )
240 /*****************************************************************************
242 *****************************************************************************/
246 FT_BitmapGlyph p_glyph;
247 FT_BitmapGlyph p_outline;
248 FT_BitmapGlyph p_shadow;
249 uint32_t i_color; /* ARGB color */
250 int i_line_offset; /* underline/strikethrough offset */
251 int i_line_thickness; /* underline/strikethrough thickness */
254 typedef struct line_desc_t line_desc_t;
262 int i_character_count;
263 line_character_t *p_character;
266 /*****************************************************************************
267 * filter_sys_t: freetype local data
268 *****************************************************************************
269 * This structure is part of the video output thread descriptor.
270 * It describes the freetype specific properties of an output thread.
271 *****************************************************************************/
274 FT_Library p_library; /* handle to library */
275 FT_Face p_face; /* handle to face object */
276 FT_Stroker p_stroker; /* handle to path stroker object */
278 xml_reader_t *p_xml; /* vlc xml parser */
280 text_style_t style; /* Current Style */
283 float f_shadow_vector_x;
284 float f_shadow_vector_y;
285 int i_default_font_size;
288 input_attachment_t **pp_font_attachments;
289 int i_font_attachments;
291 char * (*pf_select) (filter_t *, const char* family,
292 bool bold, bool italic, int size,
298 static void YUVFromRGB( uint32_t i_argb,
299 uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
301 int i_red = ( i_argb & 0x00ff0000 ) >> 16;
302 int i_green = ( i_argb & 0x0000ff00 ) >> 8;
303 int i_blue = ( i_argb & 0x000000ff );
305 *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
306 802 * i_blue + 4096 + 131072 ) >> 13, 235);
307 *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
308 3598 * i_blue + 4096 + 1048576) >> 13, 240);
309 *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
310 -585 * i_blue + 4096 + 1048576) >> 13, 240);
312 static void RGBFromRGB( uint32_t i_argb,
313 uint8_t *pi_r, uint8_t *pi_g, uint8_t *pi_b )
315 *pi_r = ( i_argb & 0x00ff0000 ) >> 16;
316 *pi_g = ( i_argb & 0x0000ff00 ) >> 8;
317 *pi_b = ( i_argb & 0x000000ff );
320 /*****************************************************************************
321 * Make any TTF/OTF fonts present in the attachments of the media file
322 * and store them for later use by the FreeType Engine
323 *****************************************************************************/
324 static int LoadFontsFromAttachments( filter_t *p_filter )
326 filter_sys_t *p_sys = p_filter->p_sys;
327 input_attachment_t **pp_attachments;
328 int i_attachments_cnt;
330 if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
333 p_sys->i_font_attachments = 0;
334 p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
335 if( !p_sys->pp_font_attachments )
338 for( int k = 0; k < i_attachments_cnt; k++ )
340 input_attachment_t *p_attach = pp_attachments[k];
342 if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
343 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
344 p_attach->i_data > 0 && p_attach->p_data )
346 p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
350 vlc_input_attachment_Delete( p_attach );
353 free( pp_attachments );
358 static int GetFontSize( filter_t *p_filter )
360 filter_sys_t *p_sys = p_filter->p_sys;
363 if( p_sys->i_default_font_size )
365 i_size = p_sys->i_default_font_size;
369 int i_ratio = var_InheritInteger( p_filter, "freetype-rel-fontsize" );
372 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
377 msg_Warn( p_filter, "invalid fontsize, using 12" );
383 static int SetFontSize( filter_t *p_filter, int i_size )
385 filter_sys_t *p_sys = p_filter->p_sys;
389 i_size = GetFontSize( p_filter );
391 msg_Dbg( p_filter, "using fontsize: %i", i_size );
394 p_sys->style.i_font_size = i_size;
396 if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
398 msg_Err( p_filter, "couldn't set font size to %d", i_size );
405 /*****************************************************************************
406 * RenderYUVP: place string in picture
407 *****************************************************************************
408 * This function merges the previously rendered freetype glyphs into a picture
409 *****************************************************************************/
410 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
414 VLC_UNUSED(p_filter);
415 static const uint8_t pi_gamma[16] =
416 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
417 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
421 int i, x, y, i_pitch;
422 uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
424 /* Create a new subpicture region */
425 video_format_Init( &fmt, VLC_CODEC_YUVP );
427 fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
429 fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
431 assert( !p_region->p_picture );
432 p_region->p_picture = picture_NewFromFormat( &fmt );
433 if( !p_region->p_picture )
435 fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
438 /* Calculate text color components
439 * Only use the first color */
440 int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
441 YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
444 fmt.p_palette->i_entries = 16;
445 for( i = 0; i < 8; i++ )
447 fmt.p_palette->palette[i][0] = 0;
448 fmt.p_palette->palette[i][1] = 0x80;
449 fmt.p_palette->palette[i][2] = 0x80;
450 fmt.p_palette->palette[i][3] = pi_gamma[i];
451 fmt.p_palette->palette[i][3] =
452 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
454 for( i = 8; i < fmt.p_palette->i_entries; i++ )
456 fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
457 fmt.p_palette->palette[i][1] = i_u;
458 fmt.p_palette->palette[i][2] = i_v;
459 fmt.p_palette->palette[i][3] = pi_gamma[i];
460 fmt.p_palette->palette[i][3] =
461 (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
464 p_dst = p_region->p_picture->Y_PIXELS;
465 i_pitch = p_region->p_picture->Y_PITCH;
467 /* Initialize the region pixels */
468 memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
470 for( ; p_line != NULL; p_line = p_line->p_next )
472 int i_align_left = 0;
473 if( p_line->i_width < (int)fmt.i_visible_width )
475 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
476 i_align_left = ( fmt.i_visible_width - p_line->i_width );
477 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
478 i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
482 for( i = 0; i < p_line->i_character_count; i++ )
484 const line_character_t *ch = &p_line->p_character[i];
485 FT_BitmapGlyph p_glyph = ch->p_glyph;
487 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
488 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
490 for( y = 0; y < p_glyph->bitmap.rows; y++ )
492 for( x = 0; x < p_glyph->bitmap.width; x++ )
494 if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
495 p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
496 (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
502 /* Outlining (find something better than nearest neighbour filtering ?) */
505 uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
506 uint8_t *p_top = p_dst; /* Use 1st line as a cache */
507 uint8_t left, current;
509 for( y = 1; y < (int)fmt.i_height - 1; y++ )
511 if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
512 p_dst += p_region->p_picture->Y_PITCH;
515 for( x = 1; x < (int)fmt.i_width - 1; x++ )
518 p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
519 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;
523 memset( p_top, 0, fmt.i_width );
529 /*****************************************************************************
530 * RenderYUVA: place string in picture
531 *****************************************************************************
532 * This function merges the previously rendered freetype glyphs into a picture
533 *****************************************************************************/
534 static void FillYUVAPicture( picture_t *p_picture,
535 int i_a, int i_y, int i_u, int i_v )
537 memset( p_picture->p[0].p_pixels, i_y,
538 p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
539 memset( p_picture->p[1].p_pixels, i_u,
540 p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
541 memset( p_picture->p[2].p_pixels, i_v,
542 p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
543 memset( p_picture->p[3].p_pixels, i_a,
544 p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
547 static inline void BlendYUVAPixel( picture_t *p_picture,
548 int i_picture_x, int i_picture_y,
549 int i_a, int i_y, int i_u, int i_v,
552 int i_an = i_a * i_alpha / 255;
554 uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
555 uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
556 uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
557 uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
569 *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
572 *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
573 *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
574 *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
579 static void FillRGBAPicture( picture_t *p_picture,
580 int i_a, int i_r, int i_g, int i_b )
582 for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
584 for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
586 uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
595 static inline void BlendRGBAPixel( picture_t *p_picture,
596 int i_picture_x, int i_picture_y,
597 int i_a, int i_r, int i_g, int i_b,
600 int i_an = i_a * i_alpha / 255;
602 uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];
604 int i_ao = p_rgba[3];
614 p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
617 p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
618 p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
619 p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
624 static void FillARGBPicture(picture_t *pic, int a, int r, int g, int b)
628 if (a == r && a == b && a == g)
630 memset(pic->p->p_pixels, a, pic->p->i_visible_lines * pic->p->i_pitch);
634 uint_fast32_t pixel = VLC_FOURCC(a, r, g, b);
635 uint8_t *line = pic->p->p_pixels;
637 for (unsigned lines = pic->p->i_visible_lines; lines > 0; lines--)
639 uint32_t *pixels = (uint32_t *)line;
640 for (unsigned cols = pic->p->i_visible_pitch; cols > 0; cols -= 4)
642 line += pic->p->i_pitch;
646 static inline void BlendARGBPixel(picture_t *pic, int pic_x, int pic_y,
647 int a, int r, int g, int b, int alpha)
649 uint8_t *rgba = &pic->p->p_pixels[pic_y * pic->p->i_pitch + 4 * pic_x];
650 int an = a * alpha / 255;
662 rgba[0] = 255 - (255 - rgba[0]) * (255 - an) / 255;
665 rgba[1] = (rgba[1] * ao * (255 - an) / 255 + r * an ) / rgba[0];
666 rgba[2] = (rgba[2] * ao * (255 - an) / 255 + g * an ) / rgba[0];
667 rgba[3] = (rgba[3] * ao * (255 - an) / 255 + b * an ) / rgba[0];
672 static inline void BlendAXYZGlyph( picture_t *p_picture,
673 int i_picture_x, int i_picture_y,
674 int i_a, int i_x, int i_y, int i_z,
675 FT_BitmapGlyph p_glyph,
676 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
679 for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
681 for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
682 BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
684 p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
688 static inline void BlendAXYZLine( picture_t *p_picture,
689 int i_picture_x, int i_picture_y,
690 int i_a, int i_x, int i_y, int i_z,
691 const line_character_t *p_current,
692 const line_character_t *p_next,
693 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
695 int i_line_width = p_current->p_glyph->bitmap.width;
697 i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
699 for( int dx = 0; dx < i_line_width; dx++ )
701 for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
702 BlendPixel( p_picture,
704 i_picture_y + p_current->i_line_offset + dy,
705 i_a, i_x, i_y, i_z, 0xff );
709 static inline void RenderBackground( subpicture_region_t *p_region,
710 line_desc_t *p_line_head,
713 picture_t *p_picture,
715 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
716 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
718 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
720 int i_align_left = i_margin;
721 int i_align_top = i_margin;
724 unsigned line_top = 0;
728 if( p_line->i_width < i_text_width )
730 /* Left offset to take into account alignment */
731 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
732 i_align_left += ( i_text_width - p_line->i_width );
733 else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
734 i_align_left = i_margin; /* Keep it the way it is */
735 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
736 i_align_left += ( i_text_width - p_line->i_width ) / 2;
739 /* Find the tallest character in the line */
740 for( int i = 0; i < p_line->i_character_count; i++ ) {
741 const line_character_t *ch = &p_line->p_character[i];
742 FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
743 if (p_glyph->top > max_height)
744 max_height = p_glyph->top;
747 /* Compute the background for the line (identify leading/trailing space) */
748 for( int i = 0; i < p_line->i_character_count; i++ ) {
749 const line_character_t *ch = &p_line->p_character[i];
750 FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
751 if (p_glyph && p_glyph->bitmap.rows > 0) {
752 // Found a non-whitespace character
753 line_start = i_align_left + p_glyph->left - p_bbox->xMin;
758 /* Fudge factor to make sure caption background edges are left aligned
759 despite variable font width */
763 /* Find right boundary for bounding box for background */
764 for( int i = p_line->i_character_count; i > 0; i-- ) {
765 const line_character_t *ch = &p_line->p_character[i - 1];
766 FT_BitmapGlyph p_glyph = ch->p_shadow ? ch->p_shadow : ch->p_glyph;
767 if (p_glyph && p_glyph->bitmap.rows > 0) {
768 // Found a non-whitespace character
769 line_end = i_align_left + p_glyph->left - p_bbox->xMin + p_glyph->bitmap.width;
774 /* Setup color for the background */
775 uint8_t i_x, i_y, i_z;
776 ExtractComponents( 0x000000, &i_x, &i_y, &i_z );
778 /* Compute the upper boundary for the background */
779 if ((i_align_top + p_line->i_base_line - max_height) < 0)
780 line_top = i_align_top + p_line->i_base_line;
782 line_top = i_align_top + p_line->i_base_line - max_height;
784 /* Compute lower boundary for the background */
785 line_bottom = __MIN(line_top + p_line->i_height, p_region->fmt.i_visible_height);
787 /* Render the actual background */
788 for( int dy = line_top; dy < line_bottom; dy++ )
790 for( int dx = line_start; dx < line_end; dx++ )
791 BlendPixel( p_picture, dx, dy, 0xff, i_x, i_y, i_z, 0xff );
796 static inline int RenderAXYZ( filter_t *p_filter,
797 subpicture_region_t *p_region,
798 line_desc_t *p_line_head,
801 vlc_fourcc_t i_chroma,
802 void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
803 void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
804 void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
806 filter_sys_t *p_sys = p_filter->p_sys;
808 /* Create a new subpicture region */
809 const int i_text_width = p_bbox->xMax - p_bbox->xMin;
810 const int i_text_height = p_bbox->yMax - p_bbox->yMin;
812 video_format_Init( &fmt, i_chroma );
814 fmt.i_visible_width = i_text_width + 2 * i_margin;
816 fmt.i_visible_height = i_text_height + 2 * i_margin;
818 picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
819 if( !p_region->p_picture )
823 /* Initialize the picture background */
824 uint8_t i_a = var_InheritInteger( p_filter, "freetype-background-opacity" );
825 i_a = VLC_CLIP( i_a, 0, 255 );
826 uint8_t i_x, i_y, i_z;
828 if (p_region->b_renderbg) {
829 /* Render the background just under the text */
830 FillPicture( p_picture, 0x00, 0x00, 0x00, 0x00 );
831 RenderBackground(p_region, p_line_head, p_bbox, i_margin, p_picture, i_text_width,
832 ExtractComponents, BlendPixel);
834 /* Render background under entire subpicture block */
835 int i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
836 i_background_color = VLC_CLIP( i_background_color, 0, 0xFFFFFF );
837 ExtractComponents( i_background_color, &i_x, &i_y, &i_z );
838 FillPicture( p_picture, i_a, i_x, i_y, i_z );
841 /* Render shadow then outline and then normal glyphs */
842 for( int g = 0; g < 3; g++ )
844 /* Render all lines */
845 for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
847 int i_align_left = i_margin;
848 if( p_line->i_width < i_text_width )
850 /* Left offset to take into account alignment */
851 if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
852 i_align_left += ( i_text_width - p_line->i_width );
853 else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
854 i_align_left = i_margin; /* Keep it the way it is */
855 else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
856 i_align_left += ( i_text_width - p_line->i_width ) / 2;
858 int i_align_top = i_margin;
860 /* Render all glyphs and underline/strikethrough */
861 for( int i = 0; i < p_line->i_character_count; i++ )
863 const line_character_t *ch = &p_line->p_character[i];
864 FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
868 i_a = (ch->i_color >> 24) & 0xff;
872 i_a = i_a * p_sys->style.i_shadow_alpha / 255;
873 i_color = p_sys->style.i_shadow_color;
876 i_a = i_a * p_sys->style.i_outline_alpha / 255;
877 i_color = p_sys->style.i_outline_color;
880 i_color = ch->i_color;
883 ExtractComponents( i_color, &i_x, &i_y, &i_z );
885 int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
886 int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
888 BlendAXYZGlyph( p_picture,
889 i_glyph_x, i_glyph_y,
894 /* underline/strikethrough are only rendered for the normal glyph */
895 if( g == 2 && ch->i_line_thickness > 0 )
896 BlendAXYZLine( p_picture,
897 i_glyph_x, i_glyph_y + p_glyph->top,
900 i + 1 < p_line->i_character_count ? &ch[1] : NULL,
911 static void FreeLine( line_desc_t *p_line )
913 for( int i = 0; i < p_line->i_character_count; i++ )
915 line_character_t *ch = &p_line->p_character[i];
916 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
918 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
920 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
923 free( p_line->p_character );
927 static void FreeLines( line_desc_t *p_lines )
929 for( line_desc_t *p_line = p_lines; p_line != NULL; )
931 line_desc_t *p_next = p_line->p_next;
937 static line_desc_t *NewLine( int i_count )
939 line_desc_t *p_line = malloc( sizeof(*p_line) );
944 p_line->p_next = NULL;
946 p_line->i_base_line = 0;
947 p_line->i_character_count = 0;
949 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
950 if( !p_line->p_character )
958 static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
960 for( int k = 0; k < p_sys->i_font_attachments; k++ )
962 input_attachment_t *p_attach = p_sys->pp_font_attachments[k];
964 FT_Face p_face = NULL;
966 while( 0 == FT_New_Memory_Face( p_sys->p_library,
974 int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) |
975 ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
976 if( p_face->family_name != NULL
977 && !strcasecmp( p_face->family_name, p_style->psz_fontname )
978 && (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC))
979 == i_style_received )
982 FT_Done_Face( p_face );
990 static FT_Face LoadFace( filter_t *p_filter,
991 const text_style_t *p_style )
993 filter_sys_t *p_sys = p_filter->p_sys;
995 /* Look for a match amongst our attachments first */
996 FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
998 /* Load system wide font otheriwse */
1002 char *psz_fontfile = NULL;
1003 if( p_sys->pf_select )
1004 psz_fontfile = p_sys->pf_select( p_filter,
1005 p_style->psz_fontname,
1006 (p_style->i_style_flags & STYLE_BOLD) != 0,
1007 (p_style->i_style_flags & STYLE_ITALIC) != 0,
1011 psz_fontfile = NULL;
1016 if( *psz_fontfile == '\0' )
1019 "We were not able to find a matching font: \"%s\" (%s %s),"
1020 " so using default font",
1021 p_style->psz_fontname,
1022 (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
1023 (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
1028 if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
1031 free( psz_fontfile );
1036 if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
1038 /* We've loaded a font face which is unhelpful for actually
1039 * rendering text - fallback to the default one.
1041 FT_Done_Face( p_face );
1047 static int GetGlyph( filter_t *p_filter,
1048 FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
1049 FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1050 FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
1056 FT_Vector *p_pen_shadow )
1058 if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
1059 FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1061 msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
1062 return VLC_EGENERIC;
1065 /* Do synthetic styling now that Freetype supports it;
1066 * ie. if the font we have loaded is NOT already in the
1067 * style that the tags want, then switch it on; if they
1068 * are then don't. */
1069 if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
1070 FT_GlyphSlot_Embolden( p_face->glyph );
1071 if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
1072 FT_GlyphSlot_Oblique( p_face->glyph );
1075 if( FT_Get_Glyph( p_face->glyph, &glyph ) )
1077 msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
1078 return VLC_EGENERIC;
1081 FT_Glyph outline = NULL;
1082 if( p_filter->p_sys->p_stroker )
1085 if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
1089 FT_Glyph shadow = NULL;
1090 if( p_filter->p_sys->style.i_shadow_alpha > 0 )
1092 shadow = outline ? outline : glyph;
1093 if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
1099 FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
1102 *pp_shadow = shadow;
1104 if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1106 FT_Done_Glyph( glyph );
1108 FT_Done_Glyph( outline );
1110 FT_Done_Glyph( shadow );
1111 return VLC_EGENERIC;
1113 FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1118 FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
1119 FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
1121 *pp_outline = outline;
1126 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
1128 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
1129 if( p_bbox->xMin >= p_bbox->xMax )
1131 p_bbox->xMin = FT_CEIL(p_pen->x);
1132 p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
1133 glyph_bmp->left = p_bbox->xMin;
1135 if( p_bbox->yMin >= p_bbox->yMax )
1137 p_bbox->yMax = FT_CEIL(p_pen->y);
1138 p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
1139 glyph_bmp->top = p_bbox->yMax;
1143 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
1145 p_max->xMin = __MIN(p_max->xMin, p->xMin);
1146 p_max->yMin = __MIN(p_max->yMin, p->yMin);
1147 p_max->xMax = __MAX(p_max->xMax, p->xMax);
1148 p_max->yMax = __MAX(p_max->yMax, p->yMax);
1151 static int ProcessLines( filter_t *p_filter,
1152 line_desc_t **pp_lines,
1154 int *pi_max_face_height,
1156 uni_char_t *psz_text,
1157 text_style_t **pp_styles,
1158 uint32_t *pi_k_dates,
1161 filter_sys_t *p_sys = p_filter->p_sys;
1162 uni_char_t *p_fribidi_string = NULL;
1163 text_style_t **pp_fribidi_styles = NULL;
1164 int *p_new_positions = NULL;
1166 #if defined(HAVE_FRIBIDI)
1168 int *p_old_positions;
1169 int start_pos, pos = 0;
1171 pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1173 p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
1174 p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
1175 p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
1177 if( ! pp_fribidi_styles ||
1178 ! p_fribidi_string ||
1179 ! p_old_positions ||
1182 free( p_old_positions );
1183 free( p_new_positions );
1184 free( p_fribidi_string );
1185 free( pp_fribidi_styles );
1189 /* Do bidi conversion line-by-line */
1192 while(pos < i_len) {
1193 if (psz_text[pos] != '\n')
1195 p_fribidi_string[pos] = psz_text[pos];
1196 pp_fribidi_styles[pos] = pp_styles[pos];
1197 p_new_positions[pos] = pos;
1201 while(pos < i_len) {
1202 if (psz_text[pos] == '\n')
1206 if (pos > start_pos)
1208 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1209 FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
1211 FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1213 fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
1214 pos - start_pos, &base_dir,
1215 (FriBidiChar*)p_fribidi_string + start_pos,
1216 p_new_positions + start_pos,
1219 for( int j = start_pos; j < pos; j++ )
1221 pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1222 p_new_positions[ j ] += start_pos;
1226 p_fribidi_string[ i_len ] = 0;
1227 free( p_old_positions );
1229 pp_styles = pp_fribidi_styles;
1230 psz_text = p_fribidi_string;
1233 /* Work out the karaoke */
1234 uint8_t *pi_karaoke_bar = NULL;
1237 pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
1238 if( pi_karaoke_bar )
1240 int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
1241 for( int i = 0; i < i_len; i++ )
1243 unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
1244 pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
1248 free( p_new_positions );
1250 *pi_max_face_height = 0;
1252 line_desc_t **pp_line_next = pp_lines;
1260 int i_face_height_previous = 0;
1261 int i_base_line = 0;
1262 const text_style_t *p_previous_style = NULL;
1263 FT_Face p_face = NULL;
1264 for( int i_start = 0; i_start < i_len; )
1266 /* Compute the length of the current text line */
1268 while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
1271 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1272 line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
1273 int i_index = i_start;
1278 int i_face_height = 0;
1279 FT_BBox line_bbox = {
1285 int i_ul_offset = 0;
1286 int i_ul_thickness = 0;
1295 break_point_t break_point;
1296 break_point_t break_point_fallback;
1298 #define SAVE_BP(dst) do { \
1299 dst.i_index = i_index; \
1301 dst.line_bbox = line_bbox; \
1302 dst.i_face_height = i_face_height; \
1303 dst.i_ul_offset = i_ul_offset; \
1304 dst.i_ul_thickness = i_ul_thickness; \
1307 SAVE_BP( break_point );
1308 SAVE_BP( break_point_fallback );
1310 while( i_index < i_start + i_length )
1312 /* Split by common FT_Face + Size */
1313 const text_style_t *p_current_style = pp_styles[i_index];
1314 int i_part_length = 0;
1315 while( i_index + i_part_length < i_start + i_length )
1317 const text_style_t *p_style = pp_styles[i_index + i_part_length];
1318 if( !FaceStyleEquals( p_style, p_current_style ) ||
1319 p_style->i_font_size != p_current_style->i_font_size )
1324 /* (Re)load/reconfigure the face if needed */
1325 if( !FaceStyleEquals( p_current_style, p_previous_style ) )
1328 FT_Done_Face( p_face );
1329 p_previous_style = NULL;
1331 p_face = LoadFace( p_filter, p_current_style );
1333 FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
1334 if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size ||
1335 ((p_previous_style->i_style_flags ^ p_current_style->i_style_flags) & STYLE_HALFWIDTH) )
1338 int i_font_width = ( p_current_style->i_style_flags & STYLE_HALFWIDTH )
1339 ? p_current_style->i_font_size / 2
1340 : p_current_style->i_font_size;
1341 if( FT_Set_Pixel_Sizes( p_current_face,
1343 p_current_style->i_font_size ) )
1344 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
1345 if( p_sys->p_stroker )
1347 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
1348 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
1349 int i_radius = (p_current_style->i_font_size << 6) * f_outline_thickness;
1350 FT_Stroker_Set( p_sys->p_stroker,
1352 FT_STROKER_LINECAP_ROUND,
1353 FT_STROKER_LINEJOIN_ROUND, 0 );
1356 p_previous_style = p_current_style;
1358 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
1359 p_current_face->size->metrics.y_scale)));
1361 /* Render the part */
1362 bool b_break_line = false;
1363 int i_glyph_last = 0;
1364 while( i_part_length > 0 )
1366 const text_style_t *p_glyph_style = pp_styles[i_index];
1367 uni_char_t character = psz_text[i_index];
1368 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
1370 /* Get kerning vector */
1371 FT_Vector kerning = { .x = 0, .y = 0 };
1372 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
1374 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
1376 if( p_glyph_style->i_spacing > 0 && i_glyph_last != 0 && i_glyph_index != 0 )
1378 kerning.x = (p_glyph_style->i_spacing) << 6;
1381 /* Get the glyph bitmap and its bounding box and all the associated properties */
1382 FT_Vector pen_new = {
1383 .x = pen.x + kerning.x,
1384 .y = pen.y + kerning.y,
1387 int i_font_width = ( p_current_style->i_style_flags & STYLE_HALFWIDTH )
1388 ? p_current_style->i_font_size / 2
1389 : p_current_style->i_font_size;
1390 FT_Vector pen_shadow_new = {
1391 .x = pen_new.x + p_sys->f_shadow_vector_x * (i_font_width << 6),
1392 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
1398 FT_BBox outline_bbox;
1400 FT_BBox shadow_bbox;
1402 if( GetGlyph( p_filter,
1403 &glyph, &glyph_bbox,
1404 &outline, &outline_bbox,
1405 &shadow, &shadow_bbox,
1406 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
1407 &pen_new, &pen_shadow_new ) )
1410 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
1412 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
1414 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
1416 /* FIXME and what about outline */
1418 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
1419 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
1420 (p_glyph_style->i_karaoke_background_alpha << 24))
1421 : (p_glyph_style->i_font_color |
1422 (p_glyph_style->i_font_alpha << 24));
1423 int i_line_offset = 0;
1424 int i_line_thickness = 0;
1425 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1427 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
1428 p_current_face->size->metrics.y_scale)) );
1430 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
1431 p_current_face->size->metrics.y_scale)) );
1433 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
1435 /* Move the baseline to make it strikethrough instead of
1436 * underline. That means that strikethrough takes precedence
1438 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
1439 p_current_face->size->metrics.y_scale)) );
1441 else if( i_line_thickness > 0 )
1443 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
1445 /* The real underline thickness and position are
1446 * updated once the whole line has been parsed */
1447 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
1448 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
1449 i_line_thickness = -1;
1452 FT_BBox line_bbox_new = line_bbox;
1453 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
1455 BBoxEnlarge( &line_bbox_new, &outline_bbox );
1457 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
1459 b_break_line = i_index > i_start &&
1460 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
1463 FT_Done_Glyph( glyph );
1465 FT_Done_Glyph( outline );
1467 FT_Done_Glyph( shadow );
1469 break_point_t *p_bp = NULL;
1470 if( break_point.i_index > i_start )
1471 p_bp = &break_point;
1472 else if( break_point_fallback.i_index > i_start )
1473 p_bp = &break_point_fallback;
1477 msg_Dbg( p_filter, "Breaking line");
1478 for( int i = p_bp->i_index; i < i_index; i++ )
1480 line_character_t *ch = &p_line->p_character[i - i_start];
1481 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1483 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1485 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1487 p_line->i_character_count = p_bp->i_index - i_start;
1489 i_index = p_bp->i_index;
1491 line_bbox = p_bp->line_bbox;
1492 i_face_height = p_bp->i_face_height;
1493 i_ul_offset = p_bp->i_ul_offset;
1494 i_ul_thickness = p_bp->i_ul_thickness;
1498 msg_Err( p_filter, "Breaking unbreakable line");
1503 assert( p_line->i_character_count == i_index - i_start);
1504 p_line->p_character[p_line->i_character_count++] = (line_character_t){
1505 .p_glyph = (FT_BitmapGlyph)glyph,
1506 .p_outline = (FT_BitmapGlyph)outline,
1507 .p_shadow = (FT_BitmapGlyph)shadow,
1509 .i_line_offset = i_line_offset,
1510 .i_line_thickness = i_line_thickness,
1513 pen.x = pen_new.x + p_current_face->glyph->advance.x;
1514 pen.y = pen_new.y + p_current_face->glyph->advance.y;
1515 line_bbox = line_bbox_new;
1517 i_glyph_last = i_glyph_index;
1521 if( character == ' ' || character == '\t' )
1522 SAVE_BP( break_point );
1523 else if( character == 160 )
1524 SAVE_BP( break_point_fallback );
1530 /* Update our baseline */
1531 if( i_face_height_previous > 0 )
1532 i_base_line += __MAX(i_face_height, i_face_height_previous);
1533 if( i_face_height > 0 )
1534 i_face_height_previous = i_face_height;
1536 /* Update the line bbox with the actual base line */
1537 if (line_bbox.yMax > line_bbox.yMin) {
1538 line_bbox.yMin -= i_base_line;
1539 line_bbox.yMax -= i_base_line;
1541 BBoxEnlarge( &bbox, &line_bbox );
1543 /* Terminate and append the line */
1546 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
1547 p_line->i_base_line = i_base_line;
1548 p_line->i_height = __MAX(i_face_height, i_face_height_previous);
1549 if( i_ul_thickness > 0 )
1551 for( int i = 0; i < p_line->i_character_count; i++ )
1553 line_character_t *ch = &p_line->p_character[i];
1554 if( ch->i_line_thickness < 0 )
1556 ch->i_line_offset = i_ul_offset;
1557 ch->i_line_thickness = i_ul_thickness;
1562 *pp_line_next = p_line;
1563 pp_line_next = &p_line->p_next;
1566 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
1568 /* Skip what we have rendered and the line delimitor if present */
1570 if( i_start < i_len && psz_text[i_start] == '\n' )
1573 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
1575 msg_Err( p_filter, "Truncated too high subtitle" );
1580 FT_Done_Face( p_face );
1582 free( pp_fribidi_styles );
1583 free( p_fribidi_string );
1584 free( pi_karaoke_bar );
1590 static xml_reader_t *GetXMLReader( filter_t *p_filter, stream_t *p_sub )
1592 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
1594 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
1596 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
1597 p_filter->p_sys->p_xml = p_xml_reader;
1599 return p_xml_reader;
1603 * This function renders a text subpicture region into another one.
1604 * It also calculates the size needed for this string, and renders the
1605 * needed glyphs into memory. It is used as pf_add_string callback in
1606 * the vout method by this module
1608 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
1609 subpicture_region_t *p_region_in, bool b_html,
1610 const vlc_fourcc_t *p_chroma_list )
1612 filter_sys_t *p_sys = p_filter->p_sys;
1615 return VLC_EGENERIC;
1616 if( b_html && !p_region_in->psz_html )
1617 return VLC_EGENERIC;
1618 if( !b_html && !p_region_in->psz_text )
1619 return VLC_EGENERIC;
1621 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
1622 : p_region_in->psz_text );
1624 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
1625 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
1626 if( !psz_text || !pp_styles )
1630 return VLC_EGENERIC;
1633 /* Reset the default fontsize in case screen metrics have changed */
1634 p_filter->p_sys->style.i_font_size = GetFontSize( p_filter );
1637 int rv = VLC_SUCCESS;
1638 int i_text_length = 0;
1640 int i_max_face_height;
1641 line_desc_t *p_lines = NULL;
1643 uint32_t *pi_k_durations = NULL;
1647 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
1648 (uint8_t *) p_region_in->psz_html,
1649 strlen( p_region_in->psz_html ),
1651 if( unlikely(p_sub == NULL) )
1658 xml_reader_t *p_xml_reader = GetXMLReader( p_filter, p_sub );
1665 /* Look for Root Node */
1668 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
1670 if( strcasecmp( "karaoke", node ) == 0 )
1672 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
1674 else if( strcasecmp( "text", node ) != 0 )
1676 /* Only text and karaoke tags are supported */
1677 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
1684 msg_Err( p_filter, "Malformed HTML subtitle" );
1690 rv = ProcessNodes( p_filter,
1691 psz_text, pp_styles, pi_k_durations, &i_text_length,
1692 p_xml_reader, p_region_in->p_style, &p_filter->p_sys->style );
1696 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
1698 stream_Delete( p_sub );
1702 text_style_t *p_style;
1703 if( p_region_in->p_style )
1705 p_style = CreateStyle( p_region_in->p_style->psz_fontname ? p_region_in->p_style->psz_fontname
1706 : p_sys->style.psz_fontname,
1707 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
1708 : p_sys->style.i_font_size,
1709 (p_region_in->p_style->i_font_color & 0xffffff) |
1710 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
1712 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
1717 p_style->i_spacing = p_region_in->p_style->i_spacing;
1721 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
1722 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
1723 p_style = CreateStyle( p_sys->style.psz_fontname,
1724 p_sys->style.i_font_size,
1725 (i_font_color & 0xffffff) |
1726 ((p_sys->style.i_font_alpha & 0xff) << 24),
1729 if( p_sys->style.i_style_flags & STYLE_BOLD )
1730 p_style->i_style_flags |= STYLE_BOLD;
1732 i_text_length = SetupText( p_filter,
1736 p_region_in->psz_text, p_style, 0 );
1739 if( !rv && i_text_length > 0 )
1741 rv = ProcessLines( p_filter,
1742 &p_lines, &bbox, &i_max_face_height,
1743 psz_text, pp_styles, pi_k_durations, i_text_length );
1746 p_region_out->i_x = p_region_in->i_x;
1747 p_region_out->i_y = p_region_in->i_y;
1749 /* Don't attempt to render text that couldn't be layed out
1751 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
1753 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
1754 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
1756 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
1757 p_chroma_list = p_chroma_list_yuvp;
1758 else if( !p_chroma_list || *p_chroma_list == 0 )
1759 p_chroma_list = p_chroma_list_rgba;
1761 uint8_t i_background_opacity = var_InheritInteger( p_filter, "freetype-background-opacity" );
1762 i_background_opacity = VLC_CLIP( i_background_opacity, 0, 255 );
1763 const int i_margin = i_background_opacity > 0 ? i_max_face_height / 4 : 0;
1764 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
1767 if( *p_chroma == VLC_CODEC_YUVP )
1768 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
1769 else if( *p_chroma == VLC_CODEC_YUVA )
1770 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
1775 else if( *p_chroma == VLC_CODEC_RGBA )
1776 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
1781 else if( *p_chroma == VLC_CODEC_ARGB )
1782 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox,
1783 i_margin, *p_chroma, RGBFromRGB,
1784 FillARGBPicture, BlendARGBPixel );
1790 /* With karaoke, we're going to have to render the text a number
1791 * of times to show the progress marker on the text.
1793 if( pi_k_durations )
1794 var_SetBool( p_filter, "text-rerender", true );
1797 FreeLines( p_lines );
1800 for( int i = 0; i < i_text_length; i++ )
1802 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
1803 text_style_Delete( pp_styles[i] );
1806 free( pi_k_durations );
1811 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
1812 subpicture_region_t *p_region_in,
1813 const vlc_fourcc_t *p_chroma_list )
1815 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
1818 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
1819 subpicture_region_t *p_region_in,
1820 const vlc_fourcc_t *p_chroma_list )
1822 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
1825 /*****************************************************************************
1826 * Create: allocates osd-text video thread output method
1827 *****************************************************************************
1828 * This function allocates and initializes a Clone vout method.
1829 *****************************************************************************/
1830 static int Init_FT( vlc_object_t *p_this,
1831 const char *psz_fontfile,
1832 const int fontindex,
1833 const float f_outline_thickness)
1835 filter_t *p_filter = (filter_t *)p_this;
1836 filter_sys_t *p_sys = p_filter->p_sys;
1839 int i_error = FT_Init_FreeType( &p_sys->p_library );
1842 msg_Err( p_filter, "couldn't initialize freetype" );
1846 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
1847 fontindex, &p_sys->p_face );
1849 if( i_error == FT_Err_Unknown_File_Format )
1851 msg_Err( p_filter, "file %s have unknown format",
1852 psz_fontfile ? psz_fontfile : "(null)" );
1857 msg_Err( p_filter, "failed to load font file %s",
1858 psz_fontfile ? psz_fontfile : "(null)" );
1862 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
1865 msg_Err( p_filter, "font has no unicode translation table" );
1869 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
1871 p_sys->p_stroker = NULL;
1872 if( f_outline_thickness > .001f )
1874 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
1876 msg_Err( p_filter, "Failed to create stroker for outlining" );
1882 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
1883 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
1885 return VLC_EGENERIC;
1889 static int Create( vlc_object_t *p_this )
1891 filter_t *p_filter = (filter_t *)p_this;
1892 filter_sys_t *p_sys;
1893 char *psz_fontfile = NULL;
1894 char *psz_fontname = NULL;
1895 char *psz_monofontfile = NULL;
1896 char *psz_monofontfamily = NULL;
1897 int fontindex = 0, monofontindex = 0;
1899 /* Allocate structure */
1900 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
1904 p_sys->style.psz_fontname = NULL;
1905 p_sys->p_xml = NULL;
1907 p_sys->p_library = 0;
1908 p_sys->style.i_font_size = 0;
1909 p_sys->style.i_style_flags = 0;
1912 * The following variables should not be cached, as they might be changed on-the-fly:
1913 * freetype-rel-fontsize, freetype-background-opacity, freetype-background-color,
1914 * freetype-outline-thickness, freetype-color
1918 psz_fontname = var_InheritString( p_filter, "freetype-font" );
1919 psz_monofontfamily = var_InheritString( p_filter, "freetype-monofont" );
1920 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
1921 p_sys->style.i_font_alpha = var_InheritInteger( p_filter,"freetype-opacity" );
1922 p_sys->style.i_font_alpha = VLC_CLIP( p_sys->style.i_font_alpha, 0, 255 );
1923 if( var_InheritBool( p_filter, "freetype-bold" ) )
1924 p_sys->style.i_style_flags |= STYLE_BOLD;
1926 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
1927 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
1928 p_sys->style.i_outline_alpha = var_InheritInteger( p_filter, "freetype-outline-opacity" );
1929 p_sys->style.i_outline_alpha = VLC_CLIP( p_sys->style.i_outline_alpha, 0, 255 );
1930 p_sys->style.i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
1931 p_sys->style.i_outline_color = VLC_CLIP( p_sys->style.i_outline_color, 0, 0xFFFFFF );
1933 p_sys->style.i_shadow_alpha = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
1934 p_sys->style.i_shadow_alpha = VLC_CLIP( p_sys->style.i_shadow_alpha, 0, 255 );
1935 p_sys->style.i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
1936 p_sys->style.i_shadow_color = VLC_CLIP( p_sys->style.i_shadow_color, 0, 0xFFFFFF );
1937 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
1938 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
1939 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
1940 p_sys->f_shadow_vector_x = f_shadow_distance * cosf((float)(2. * M_PI) * f_shadow_angle / 360);
1941 p_sys->f_shadow_vector_y = f_shadow_distance * sinf((float)(2. * M_PI) * f_shadow_angle / 360);
1943 /* Set default psz_fontname */
1944 if( !psz_fontname || !*psz_fontname )
1946 free( psz_fontname );
1947 #ifdef HAVE_GET_FONT_BY_FAMILY_NAME
1948 psz_fontname = strdup( DEFAULT_FAMILY );
1950 psz_fontname = File_Select( DEFAULT_FONT_FILE );
1954 /* set default psz_monofontname */
1955 if( !psz_monofontfamily || !*psz_monofontfamily )
1957 free( psz_monofontfamily );
1958 #ifdef HAVE_GET_FONT_BY_FAMILY_NAME
1959 psz_monofontfamily = strdup( DEFAULT_MONOSPACE_FAMILY );
1961 psz_monofontfamily = File_Select( DEFAULT_MONOSPACE_FONT_FILE );
1965 /* Set the current font file */
1966 p_sys->style.psz_fontname = psz_fontname;
1967 p_sys->style.psz_monofontname = psz_monofontfamily;
1969 #ifdef HAVE_FONTCONFIG
1970 p_sys->pf_select = FontConfig_Select;
1971 FontConfig_BuildCache( p_filter );
1972 #elif defined( __APPLE__ )
1973 #if !TARGET_OS_IPHONE
1974 p_sys->pf_select = MacLegacy_Select;
1976 #elif defined( _WIN32 ) && defined( HAVE_GET_FONT_BY_FAMILY_NAME )
1977 p_sys->pf_select = Win32_Select;
1979 p_sys->pf_select = Dummy_Select;
1983 psz_fontfile = p_sys->pf_select( p_filter, psz_fontname, false, false,
1984 p_sys->i_default_font_size, &fontindex );
1985 psz_monofontfile = p_sys->pf_select( p_filter, psz_monofontfamily, false,
1986 false, p_sys->i_default_font_size,
1988 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontname, psz_fontfile );
1989 msg_Dbg( p_filter, "Using %s as mono-font from file %s", psz_monofontfamily, psz_monofontfile );
1991 /* If nothing is found, use the default family */
1993 psz_fontfile = File_Select( psz_fontname );
1994 if( !psz_monofontfile )
1995 psz_monofontfile = File_Select( psz_monofontfamily );
1997 if( Init_FT( p_this, psz_fontfile, fontindex, f_outline_thickness ) != VLC_SUCCESS )
2000 p_sys->pp_font_attachments = NULL;
2001 p_sys->i_font_attachments = 0;
2003 p_filter->pf_render_text = RenderText;
2004 p_filter->pf_render_html = RenderHtml;
2006 LoadFontsFromAttachments( p_filter );
2008 free( psz_fontfile );
2009 free( psz_monofontfile );
2014 free( psz_fontfile );
2015 free( psz_monofontfile );
2016 free( psz_fontname );
2017 free( psz_monofontfamily );
2019 return VLC_EGENERIC;
2023 static void Destroy_FT( vlc_object_t *p_this )
2025 filter_t *p_filter = (filter_t *)p_this;
2026 filter_sys_t *p_sys = p_filter->p_sys;
2028 if( p_sys->p_stroker )
2029 FT_Stroker_Done( p_sys->p_stroker );
2030 FT_Done_Face( p_sys->p_face );
2031 FT_Done_FreeType( p_sys->p_library );
2034 /*****************************************************************************
2035 * Destroy: destroy Clone video thread output method
2036 *****************************************************************************
2037 * Clean up all data and library connections
2038 *****************************************************************************/
2039 static void Destroy( vlc_object_t *p_this )
2041 filter_t *p_filter = (filter_t *)p_this;
2042 filter_sys_t *p_sys = p_filter->p_sys;
2044 if( p_sys->pp_font_attachments )
2046 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2047 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2049 free( p_sys->pp_font_attachments );
2052 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2053 free( p_sys->style.psz_fontname );
2054 free( p_sys->style.psz_monofontname );
2056 Destroy_FT( p_this );