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 FT_Vector advance = {
1368 while( i_part_length > 0 )
1370 const text_style_t *p_glyph_style = pp_styles[i_index];
1371 uni_char_t character = psz_text[i_index];
1372 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
1374 /* If the missing glyph is U+FEFF (ZERO WIDTH NO-BREAK SPACE) */
1375 /* we can safely ignore it. Otherwise extra squares show up */
1376 /* in Arabic text. */
1377 if( i_glyph_index == 0 && character == 0xFEFF )
1380 /* These are the most common Arabic diacritics */
1381 #define DIACRITIC( a ) ( a >= 0x064B && a <= 0x0653 )
1383 /* Diacritics should be rendered over the preceding base glyph */
1384 if( DIACRITIC( character ) )
1390 /* Get kerning vector */
1391 FT_Vector kerning = { .x = 0, .y = 0 };
1392 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
1394 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
1396 if( p_glyph_style->i_spacing > 0 && i_glyph_last != 0 && i_glyph_index != 0 )
1398 kerning.x = (p_glyph_style->i_spacing) << 6;
1401 /* Get the glyph bitmap and its bounding box and all the associated properties */
1402 FT_Vector pen_new = {
1403 .x = pen.x + kerning.x,
1404 .y = pen.y + kerning.y,
1407 int i_font_width = ( p_current_style->i_style_flags & STYLE_HALFWIDTH )
1408 ? p_current_style->i_font_size / 2
1409 : p_current_style->i_font_size;
1410 FT_Vector pen_shadow_new = {
1411 .x = pen_new.x + p_sys->f_shadow_vector_x * (i_font_width << 6),
1412 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
1418 FT_BBox outline_bbox;
1420 FT_BBox shadow_bbox;
1422 if( GetGlyph( p_filter,
1423 &glyph, &glyph_bbox,
1424 &outline, &outline_bbox,
1425 &shadow, &shadow_bbox,
1426 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
1427 &pen_new, &pen_shadow_new ) )
1430 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
1432 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
1434 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
1436 /* FIXME and what about outline */
1438 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
1439 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
1440 (p_glyph_style->i_karaoke_background_alpha << 24))
1441 : (p_glyph_style->i_font_color |
1442 (p_glyph_style->i_font_alpha << 24));
1443 int i_line_offset = 0;
1444 int i_line_thickness = 0;
1445 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1447 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
1448 p_current_face->size->metrics.y_scale)) );
1450 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
1451 p_current_face->size->metrics.y_scale)) );
1453 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
1455 /* Move the baseline to make it strikethrough instead of
1456 * underline. That means that strikethrough takes precedence
1458 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
1459 p_current_face->size->metrics.y_scale)) );
1461 else if( i_line_thickness > 0 )
1463 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
1465 /* The real underline thickness and position are
1466 * updated once the whole line has been parsed */
1467 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
1468 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
1469 i_line_thickness = -1;
1472 FT_BBox line_bbox_new = line_bbox;
1473 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
1475 BBoxEnlarge( &line_bbox_new, &outline_bbox );
1477 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
1479 b_break_line = i_index > i_start &&
1480 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
1483 FT_Done_Glyph( glyph );
1485 FT_Done_Glyph( outline );
1487 FT_Done_Glyph( shadow );
1489 break_point_t *p_bp = NULL;
1490 if( break_point.i_index > i_start )
1491 p_bp = &break_point;
1492 else if( break_point_fallback.i_index > i_start )
1493 p_bp = &break_point_fallback;
1497 msg_Dbg( p_filter, "Breaking line");
1498 for( int i = p_bp->i_index; i < i_index; i++ )
1500 line_character_t *ch = &p_line->p_character[i - i_start];
1501 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1503 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1505 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1507 p_line->i_character_count = p_bp->i_index - i_start;
1509 i_index = p_bp->i_index;
1511 line_bbox = p_bp->line_bbox;
1512 i_face_height = p_bp->i_face_height;
1513 i_ul_offset = p_bp->i_ul_offset;
1514 i_ul_thickness = p_bp->i_ul_thickness;
1518 msg_Err( p_filter, "Breaking unbreakable line");
1523 p_line->p_character[p_line->i_character_count++] = (line_character_t){
1524 .p_glyph = (FT_BitmapGlyph)glyph,
1525 .p_outline = (FT_BitmapGlyph)outline,
1526 .p_shadow = (FT_BitmapGlyph)shadow,
1528 .i_line_offset = i_line_offset,
1529 .i_line_thickness = i_line_thickness,
1532 /* Diacritics do not determine advance values. We use */
1533 /* the advance values from the last encountered base glyph, */
1534 /* since multiple diacritics may follow a single base glyph. */
1535 if( !DIACRITIC( character ) )
1537 advance.x = p_current_face->glyph->advance.x;
1538 advance.y = p_current_face->glyph->advance.y;
1541 pen.x = pen_new.x + advance.x;
1542 pen.y = pen_new.y + advance.y;
1543 line_bbox = line_bbox_new;
1545 i_glyph_last = i_glyph_index;
1549 if( character == ' ' || character == '\t' )
1550 SAVE_BP( break_point );
1551 else if( character == 160 )
1552 SAVE_BP( break_point_fallback );
1558 /* Update our baseline */
1559 if( i_face_height_previous > 0 )
1560 i_base_line += __MAX(i_face_height, i_face_height_previous);
1561 if( i_face_height > 0 )
1562 i_face_height_previous = i_face_height;
1564 /* Update the line bbox with the actual base line */
1565 if (line_bbox.yMax > line_bbox.yMin) {
1566 line_bbox.yMin -= i_base_line;
1567 line_bbox.yMax -= i_base_line;
1569 BBoxEnlarge( &bbox, &line_bbox );
1571 /* Terminate and append the line */
1574 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
1575 p_line->i_base_line = i_base_line;
1576 p_line->i_height = __MAX(i_face_height, i_face_height_previous);
1577 if( i_ul_thickness > 0 )
1579 for( int i = 0; i < p_line->i_character_count; i++ )
1581 line_character_t *ch = &p_line->p_character[i];
1582 if( ch->i_line_thickness < 0 )
1584 ch->i_line_offset = i_ul_offset;
1585 ch->i_line_thickness = i_ul_thickness;
1590 *pp_line_next = p_line;
1591 pp_line_next = &p_line->p_next;
1594 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
1596 /* Skip what we have rendered and the line delimitor if present */
1598 if( i_start < i_len && psz_text[i_start] == '\n' )
1601 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
1603 msg_Err( p_filter, "Truncated too high subtitle" );
1608 FT_Done_Face( p_face );
1610 free( pp_fribidi_styles );
1611 free( p_fribidi_string );
1612 free( pi_karaoke_bar );
1618 static xml_reader_t *GetXMLReader( filter_t *p_filter, stream_t *p_sub )
1620 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
1622 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
1624 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
1625 p_filter->p_sys->p_xml = p_xml_reader;
1627 return p_xml_reader;
1631 * This function renders a text subpicture region into another one.
1632 * It also calculates the size needed for this string, and renders the
1633 * needed glyphs into memory. It is used as pf_add_string callback in
1634 * the vout method by this module
1636 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
1637 subpicture_region_t *p_region_in, bool b_html,
1638 const vlc_fourcc_t *p_chroma_list )
1640 filter_sys_t *p_sys = p_filter->p_sys;
1643 return VLC_EGENERIC;
1644 if( b_html && !p_region_in->psz_html )
1645 return VLC_EGENERIC;
1646 if( !b_html && !p_region_in->psz_text )
1647 return VLC_EGENERIC;
1649 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
1650 : p_region_in->psz_text );
1652 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
1653 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
1654 if( !psz_text || !pp_styles )
1658 return VLC_EGENERIC;
1661 /* Reset the default fontsize in case screen metrics have changed */
1662 p_filter->p_sys->style.i_font_size = GetFontSize( p_filter );
1665 int rv = VLC_SUCCESS;
1666 int i_text_length = 0;
1668 int i_max_face_height;
1669 line_desc_t *p_lines = NULL;
1671 uint32_t *pi_k_durations = NULL;
1675 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
1676 (uint8_t *) p_region_in->psz_html,
1677 strlen( p_region_in->psz_html ),
1679 if( unlikely(p_sub == NULL) )
1686 xml_reader_t *p_xml_reader = GetXMLReader( p_filter, p_sub );
1693 /* Look for Root Node */
1696 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
1698 if( strcasecmp( "karaoke", node ) == 0 )
1700 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
1702 else if( strcasecmp( "text", node ) != 0 )
1704 /* Only text and karaoke tags are supported */
1705 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
1712 msg_Err( p_filter, "Malformed HTML subtitle" );
1718 rv = ProcessNodes( p_filter,
1719 psz_text, pp_styles, pi_k_durations, &i_text_length,
1720 p_xml_reader, p_region_in->p_style, &p_filter->p_sys->style );
1724 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
1726 stream_Delete( p_sub );
1730 text_style_t *p_style;
1731 if( p_region_in->p_style )
1733 p_style = CreateStyle( p_region_in->p_style->psz_fontname ? p_region_in->p_style->psz_fontname
1734 : p_sys->style.psz_fontname,
1735 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
1736 : p_sys->style.i_font_size,
1737 (p_region_in->p_style->i_font_color & 0xffffff) |
1738 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
1740 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
1745 p_style->i_spacing = p_region_in->p_style->i_spacing;
1749 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
1750 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
1751 p_style = CreateStyle( p_sys->style.psz_fontname,
1752 p_sys->style.i_font_size,
1753 (i_font_color & 0xffffff) |
1754 ((p_sys->style.i_font_alpha & 0xff) << 24),
1757 if( p_sys->style.i_style_flags & STYLE_BOLD )
1758 p_style->i_style_flags |= STYLE_BOLD;
1760 i_text_length = SetupText( p_filter,
1764 p_region_in->psz_text, p_style, 0 );
1767 if( !rv && i_text_length > 0 )
1769 rv = ProcessLines( p_filter,
1770 &p_lines, &bbox, &i_max_face_height,
1771 psz_text, pp_styles, pi_k_durations, i_text_length );
1774 p_region_out->i_x = p_region_in->i_x;
1775 p_region_out->i_y = p_region_in->i_y;
1777 /* Don't attempt to render text that couldn't be layed out
1779 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
1781 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
1782 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
1784 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
1785 p_chroma_list = p_chroma_list_yuvp;
1786 else if( !p_chroma_list || *p_chroma_list == 0 )
1787 p_chroma_list = p_chroma_list_rgba;
1789 uint8_t i_background_opacity = var_InheritInteger( p_filter, "freetype-background-opacity" );
1790 i_background_opacity = VLC_CLIP( i_background_opacity, 0, 255 );
1791 const int i_margin = i_background_opacity > 0 ? i_max_face_height / 4 : 0;
1792 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
1795 if( *p_chroma == VLC_CODEC_YUVP )
1796 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
1797 else if( *p_chroma == VLC_CODEC_YUVA )
1798 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
1803 else if( *p_chroma == VLC_CODEC_RGBA )
1804 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
1809 else if( *p_chroma == VLC_CODEC_ARGB )
1810 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox,
1811 i_margin, *p_chroma, RGBFromRGB,
1812 FillARGBPicture, BlendARGBPixel );
1818 /* With karaoke, we're going to have to render the text a number
1819 * of times to show the progress marker on the text.
1821 if( pi_k_durations )
1822 var_SetBool( p_filter, "text-rerender", true );
1825 FreeLines( p_lines );
1828 for( int i = 0; i < i_text_length; i++ )
1830 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
1831 text_style_Delete( pp_styles[i] );
1834 free( pi_k_durations );
1839 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
1840 subpicture_region_t *p_region_in,
1841 const vlc_fourcc_t *p_chroma_list )
1843 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
1846 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
1847 subpicture_region_t *p_region_in,
1848 const vlc_fourcc_t *p_chroma_list )
1850 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
1853 /*****************************************************************************
1854 * Create: allocates osd-text video thread output method
1855 *****************************************************************************
1856 * This function allocates and initializes a Clone vout method.
1857 *****************************************************************************/
1858 static int Init_FT( vlc_object_t *p_this,
1859 const char *psz_fontfile,
1860 const int fontindex,
1861 const float f_outline_thickness)
1863 filter_t *p_filter = (filter_t *)p_this;
1864 filter_sys_t *p_sys = p_filter->p_sys;
1867 int i_error = FT_Init_FreeType( &p_sys->p_library );
1870 msg_Err( p_filter, "couldn't initialize freetype" );
1874 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
1875 fontindex, &p_sys->p_face );
1877 if( i_error == FT_Err_Unknown_File_Format )
1879 msg_Err( p_filter, "file %s have unknown format",
1880 psz_fontfile ? psz_fontfile : "(null)" );
1885 msg_Err( p_filter, "failed to load font file %s",
1886 psz_fontfile ? psz_fontfile : "(null)" );
1890 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
1893 msg_Err( p_filter, "font has no unicode translation table" );
1897 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
1899 p_sys->p_stroker = NULL;
1900 if( f_outline_thickness > .001f )
1902 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
1904 msg_Err( p_filter, "Failed to create stroker for outlining" );
1910 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
1911 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
1913 return VLC_EGENERIC;
1917 static int Create( vlc_object_t *p_this )
1919 filter_t *p_filter = (filter_t *)p_this;
1920 filter_sys_t *p_sys;
1921 char *psz_fontfile = NULL;
1922 char *psz_fontname = NULL;
1923 char *psz_monofontfile = NULL;
1924 char *psz_monofontfamily = NULL;
1925 int fontindex = 0, monofontindex = 0;
1927 /* Allocate structure */
1928 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
1932 p_sys->style.psz_fontname = NULL;
1933 p_sys->p_xml = NULL;
1935 p_sys->p_library = 0;
1936 p_sys->style.i_font_size = 0;
1937 p_sys->style.i_style_flags = 0;
1940 * The following variables should not be cached, as they might be changed on-the-fly:
1941 * freetype-rel-fontsize, freetype-background-opacity, freetype-background-color,
1942 * freetype-outline-thickness, freetype-color
1946 psz_fontname = var_InheritString( p_filter, "freetype-font" );
1947 psz_monofontfamily = var_InheritString( p_filter, "freetype-monofont" );
1948 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
1949 p_sys->style.i_font_alpha = var_InheritInteger( p_filter,"freetype-opacity" );
1950 p_sys->style.i_font_alpha = VLC_CLIP( p_sys->style.i_font_alpha, 0, 255 );
1951 if( var_InheritBool( p_filter, "freetype-bold" ) )
1952 p_sys->style.i_style_flags |= STYLE_BOLD;
1954 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
1955 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
1956 p_sys->style.i_outline_alpha = var_InheritInteger( p_filter, "freetype-outline-opacity" );
1957 p_sys->style.i_outline_alpha = VLC_CLIP( p_sys->style.i_outline_alpha, 0, 255 );
1958 p_sys->style.i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
1959 p_sys->style.i_outline_color = VLC_CLIP( p_sys->style.i_outline_color, 0, 0xFFFFFF );
1961 p_sys->style.i_shadow_alpha = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
1962 p_sys->style.i_shadow_alpha = VLC_CLIP( p_sys->style.i_shadow_alpha, 0, 255 );
1963 p_sys->style.i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
1964 p_sys->style.i_shadow_color = VLC_CLIP( p_sys->style.i_shadow_color, 0, 0xFFFFFF );
1965 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
1966 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
1967 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
1968 p_sys->f_shadow_vector_x = f_shadow_distance * cosf((float)(2. * M_PI) * f_shadow_angle / 360);
1969 p_sys->f_shadow_vector_y = f_shadow_distance * sinf((float)(2. * M_PI) * f_shadow_angle / 360);
1971 /* Set default psz_fontname */
1972 if( !psz_fontname || !*psz_fontname )
1974 free( psz_fontname );
1975 #ifdef HAVE_GET_FONT_BY_FAMILY_NAME
1976 psz_fontname = strdup( DEFAULT_FAMILY );
1978 psz_fontname = File_Select( DEFAULT_FONT_FILE );
1982 /* set default psz_monofontname */
1983 if( !psz_monofontfamily || !*psz_monofontfamily )
1985 free( psz_monofontfamily );
1986 #ifdef HAVE_GET_FONT_BY_FAMILY_NAME
1987 psz_monofontfamily = strdup( DEFAULT_MONOSPACE_FAMILY );
1989 psz_monofontfamily = File_Select( DEFAULT_MONOSPACE_FONT_FILE );
1993 /* Set the current font file */
1994 p_sys->style.psz_fontname = psz_fontname;
1995 p_sys->style.psz_monofontname = psz_monofontfamily;
1997 #ifdef HAVE_FONTCONFIG
1998 p_sys->pf_select = FontConfig_Select;
1999 FontConfig_BuildCache( p_filter );
2000 #elif defined( __APPLE__ )
2001 #if !TARGET_OS_IPHONE
2002 p_sys->pf_select = MacLegacy_Select;
2004 #elif defined( _WIN32 ) && defined( HAVE_GET_FONT_BY_FAMILY_NAME )
2005 p_sys->pf_select = Win32_Select;
2007 p_sys->pf_select = Dummy_Select;
2011 psz_fontfile = p_sys->pf_select( p_filter, psz_fontname, false, false,
2012 p_sys->i_default_font_size, &fontindex );
2013 psz_monofontfile = p_sys->pf_select( p_filter, psz_monofontfamily, false,
2014 false, p_sys->i_default_font_size,
2016 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontname, psz_fontfile );
2017 msg_Dbg( p_filter, "Using %s as mono-font from file %s", psz_monofontfamily, psz_monofontfile );
2019 /* If nothing is found, use the default family */
2021 psz_fontfile = File_Select( psz_fontname );
2022 if( !psz_monofontfile )
2023 psz_monofontfile = File_Select( psz_monofontfamily );
2025 if( Init_FT( p_this, psz_fontfile, fontindex, f_outline_thickness ) != VLC_SUCCESS )
2028 p_sys->pp_font_attachments = NULL;
2029 p_sys->i_font_attachments = 0;
2031 p_filter->pf_render_text = RenderText;
2032 p_filter->pf_render_html = RenderHtml;
2034 LoadFontsFromAttachments( p_filter );
2036 free( psz_fontfile );
2037 free( psz_monofontfile );
2042 free( psz_fontfile );
2043 free( psz_monofontfile );
2044 free( psz_fontname );
2045 free( psz_monofontfamily );
2047 return VLC_EGENERIC;
2051 static void Destroy_FT( vlc_object_t *p_this )
2053 filter_t *p_filter = (filter_t *)p_this;
2054 filter_sys_t *p_sys = p_filter->p_sys;
2056 if( p_sys->p_stroker )
2057 FT_Stroker_Done( p_sys->p_stroker );
2058 FT_Done_Face( p_sys->p_face );
2059 FT_Done_FreeType( p_sys->p_library );
2062 /*****************************************************************************
2063 * Destroy: destroy Clone video thread output method
2064 *****************************************************************************
2065 * Clean up all data and library connections
2066 *****************************************************************************/
2067 static void Destroy( vlc_object_t *p_this )
2069 filter_t *p_filter = (filter_t *)p_this;
2070 filter_sys_t *p_sys = p_filter->p_sys;
2072 if( p_sys->pp_font_attachments )
2074 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2075 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2077 free( p_sys->pp_font_attachments );
2080 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2081 free( p_sys->style.psz_fontname );
2082 free( p_sys->style.psz_monofontname );
2084 Destroy_FT( p_this );