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 )
1336 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
1337 msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
1338 if( p_sys->p_stroker )
1340 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
1341 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
1342 int i_radius = (p_current_style->i_font_size << 6) * f_outline_thickness;
1343 FT_Stroker_Set( p_sys->p_stroker,
1345 FT_STROKER_LINECAP_ROUND,
1346 FT_STROKER_LINEJOIN_ROUND, 0 );
1349 p_previous_style = p_current_style;
1351 i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
1352 p_current_face->size->metrics.y_scale)));
1354 /* Render the part */
1355 bool b_break_line = false;
1356 int i_glyph_last = 0;
1357 while( i_part_length > 0 )
1359 const text_style_t *p_glyph_style = pp_styles[i_index];
1360 uni_char_t character = psz_text[i_index];
1361 int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
1363 /* Get kerning vector */
1364 FT_Vector kerning = { .x = 0, .y = 0 };
1365 if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
1366 FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
1368 /* Get the glyph bitmap and its bounding box and all the associated properties */
1369 FT_Vector pen_new = {
1370 .x = pen.x + kerning.x,
1371 .y = pen.y + kerning.y,
1373 FT_Vector pen_shadow_new = {
1374 .x = pen_new.x + p_sys->f_shadow_vector_x * (p_current_style->i_font_size << 6),
1375 .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
1380 FT_BBox outline_bbox;
1382 FT_BBox shadow_bbox;
1384 if( GetGlyph( p_filter,
1385 &glyph, &glyph_bbox,
1386 &outline, &outline_bbox,
1387 &shadow, &shadow_bbox,
1388 p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
1389 &pen_new, &pen_shadow_new ) )
1392 FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
1394 FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
1396 FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_new );
1398 /* FIXME and what about outline */
1400 bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
1401 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
1402 (p_glyph_style->i_karaoke_background_alpha << 24))
1403 : (p_glyph_style->i_font_color |
1404 (p_glyph_style->i_font_alpha << 24));
1405 int i_line_offset = 0;
1406 int i_line_thickness = 0;
1407 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1409 i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
1410 p_current_face->size->metrics.y_scale)) );
1412 i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
1413 p_current_face->size->metrics.y_scale)) );
1415 if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
1417 /* Move the baseline to make it strikethrough instead of
1418 * underline. That means that strikethrough takes precedence
1420 i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
1421 p_current_face->size->metrics.y_scale)) );
1423 else if( i_line_thickness > 0 )
1425 glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
1427 /* The real underline thickness and position are
1428 * updated once the whole line has been parsed */
1429 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
1430 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
1431 i_line_thickness = -1;
1434 FT_BBox line_bbox_new = line_bbox;
1435 BBoxEnlarge( &line_bbox_new, &glyph_bbox );
1437 BBoxEnlarge( &line_bbox_new, &outline_bbox );
1439 BBoxEnlarge( &line_bbox_new, &shadow_bbox );
1441 b_break_line = i_index > i_start &&
1442 line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
1445 FT_Done_Glyph( glyph );
1447 FT_Done_Glyph( outline );
1449 FT_Done_Glyph( shadow );
1451 break_point_t *p_bp = NULL;
1452 if( break_point.i_index > i_start )
1453 p_bp = &break_point;
1454 else if( break_point_fallback.i_index > i_start )
1455 p_bp = &break_point_fallback;
1459 msg_Dbg( p_filter, "Breaking line");
1460 for( int i = p_bp->i_index; i < i_index; i++ )
1462 line_character_t *ch = &p_line->p_character[i - i_start];
1463 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
1465 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
1467 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
1469 p_line->i_character_count = p_bp->i_index - i_start;
1471 i_index = p_bp->i_index;
1473 line_bbox = p_bp->line_bbox;
1474 i_face_height = p_bp->i_face_height;
1475 i_ul_offset = p_bp->i_ul_offset;
1476 i_ul_thickness = p_bp->i_ul_thickness;
1480 msg_Err( p_filter, "Breaking unbreakable line");
1485 assert( p_line->i_character_count == i_index - i_start);
1486 p_line->p_character[p_line->i_character_count++] = (line_character_t){
1487 .p_glyph = (FT_BitmapGlyph)glyph,
1488 .p_outline = (FT_BitmapGlyph)outline,
1489 .p_shadow = (FT_BitmapGlyph)shadow,
1491 .i_line_offset = i_line_offset,
1492 .i_line_thickness = i_line_thickness,
1495 pen.x = pen_new.x + p_current_face->glyph->advance.x;
1496 pen.y = pen_new.y + p_current_face->glyph->advance.y;
1497 line_bbox = line_bbox_new;
1499 i_glyph_last = i_glyph_index;
1503 if( character == ' ' || character == '\t' )
1504 SAVE_BP( break_point );
1505 else if( character == 160 )
1506 SAVE_BP( break_point_fallback );
1512 /* Update our baseline */
1513 if( i_face_height_previous > 0 )
1514 i_base_line += __MAX(i_face_height, i_face_height_previous);
1515 if( i_face_height > 0 )
1516 i_face_height_previous = i_face_height;
1518 /* Update the line bbox with the actual base line */
1519 if (line_bbox.yMax > line_bbox.yMin) {
1520 line_bbox.yMin -= i_base_line;
1521 line_bbox.yMax -= i_base_line;
1523 BBoxEnlarge( &bbox, &line_bbox );
1525 /* Terminate and append the line */
1528 p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
1529 p_line->i_base_line = i_base_line;
1530 p_line->i_height = __MAX(i_face_height, i_face_height_previous);
1531 if( i_ul_thickness > 0 )
1533 for( int i = 0; i < p_line->i_character_count; i++ )
1535 line_character_t *ch = &p_line->p_character[i];
1536 if( ch->i_line_thickness < 0 )
1538 ch->i_line_offset = i_ul_offset;
1539 ch->i_line_thickness = i_ul_thickness;
1544 *pp_line_next = p_line;
1545 pp_line_next = &p_line->p_next;
1548 *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
1550 /* Skip what we have rendered and the line delimitor if present */
1552 if( i_start < i_len && psz_text[i_start] == '\n' )
1555 if( bbox.yMax - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
1557 msg_Err( p_filter, "Truncated too high subtitle" );
1562 FT_Done_Face( p_face );
1564 free( pp_fribidi_styles );
1565 free( p_fribidi_string );
1566 free( pi_karaoke_bar );
1572 static xml_reader_t *GetXMLReader( filter_t *p_filter, stream_t *p_sub )
1574 xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
1576 p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
1578 p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
1579 p_filter->p_sys->p_xml = p_xml_reader;
1581 return p_xml_reader;
1585 * This function renders a text subpicture region into another one.
1586 * It also calculates the size needed for this string, and renders the
1587 * needed glyphs into memory. It is used as pf_add_string callback in
1588 * the vout method by this module
1590 static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
1591 subpicture_region_t *p_region_in, bool b_html,
1592 const vlc_fourcc_t *p_chroma_list )
1594 filter_sys_t *p_sys = p_filter->p_sys;
1597 return VLC_EGENERIC;
1598 if( b_html && !p_region_in->psz_html )
1599 return VLC_EGENERIC;
1600 if( !b_html && !p_region_in->psz_text )
1601 return VLC_EGENERIC;
1603 const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
1604 : p_region_in->psz_text );
1606 uni_char_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
1607 text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
1608 if( !psz_text || !pp_styles )
1612 return VLC_EGENERIC;
1615 /* Reset the default fontsize in case screen metrics have changed */
1616 p_filter->p_sys->style.i_font_size = GetFontSize( p_filter );
1619 int rv = VLC_SUCCESS;
1620 int i_text_length = 0;
1622 int i_max_face_height;
1623 line_desc_t *p_lines = NULL;
1625 uint32_t *pi_k_durations = NULL;
1629 stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
1630 (uint8_t *) p_region_in->psz_html,
1631 strlen( p_region_in->psz_html ),
1633 if( unlikely(p_sub == NULL) )
1640 xml_reader_t *p_xml_reader = GetXMLReader( p_filter, p_sub );
1647 /* Look for Root Node */
1650 if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
1652 if( strcasecmp( "karaoke", node ) == 0 )
1654 pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
1656 else if( strcasecmp( "text", node ) != 0 )
1658 /* Only text and karaoke tags are supported */
1659 msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
1666 msg_Err( p_filter, "Malformed HTML subtitle" );
1672 rv = ProcessNodes( p_filter,
1673 psz_text, pp_styles, pi_k_durations, &i_text_length,
1674 p_xml_reader, p_region_in->p_style, &p_filter->p_sys->style );
1678 p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
1680 stream_Delete( p_sub );
1684 text_style_t *p_style;
1685 if( p_region_in->p_style )
1686 p_style = CreateStyle( p_region_in->p_style->psz_fontname ? p_region_in->p_style->psz_fontname
1687 : p_sys->style.psz_fontname,
1688 p_region_in->p_style->i_font_size > 0 ? p_region_in->p_style->i_font_size
1689 : p_sys->style.i_font_size,
1690 (p_region_in->p_style->i_font_color & 0xffffff) |
1691 ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
1693 p_region_in->p_style->i_style_flags & (STYLE_BOLD |
1699 uint32_t i_font_color = var_InheritInteger( p_filter, "freetype-color" );
1700 i_font_color = VLC_CLIP( i_font_color, 0, 0xFFFFFF );
1701 p_style = CreateStyle( p_sys->style.psz_fontname,
1702 p_sys->style.i_font_size,
1703 (i_font_color & 0xffffff) |
1704 ((p_sys->style.i_font_alpha & 0xff) << 24),
1707 if( p_sys->style.i_style_flags & STYLE_BOLD )
1708 p_style->i_style_flags |= STYLE_BOLD;
1710 i_text_length = SetupText( p_filter,
1714 p_region_in->psz_text, p_style, 0 );
1717 if( !rv && i_text_length > 0 )
1719 rv = ProcessLines( p_filter,
1720 &p_lines, &bbox, &i_max_face_height,
1721 psz_text, pp_styles, pi_k_durations, i_text_length );
1724 p_region_out->i_x = p_region_in->i_x;
1725 p_region_out->i_y = p_region_in->i_y;
1727 /* Don't attempt to render text that couldn't be layed out
1729 if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
1731 const vlc_fourcc_t p_chroma_list_yuvp[] = { VLC_CODEC_YUVP, 0 };
1732 const vlc_fourcc_t p_chroma_list_rgba[] = { VLC_CODEC_RGBA, 0 };
1734 if( var_InheritBool( p_filter, "freetype-yuvp" ) )
1735 p_chroma_list = p_chroma_list_yuvp;
1736 else if( !p_chroma_list || *p_chroma_list == 0 )
1737 p_chroma_list = p_chroma_list_rgba;
1739 uint8_t i_background_opacity = var_InheritInteger( p_filter, "freetype-background-opacity" );
1740 i_background_opacity = VLC_CLIP( i_background_opacity, 0, 255 );
1741 const int i_margin = i_background_opacity > 0 ? i_max_face_height / 4 : 0;
1742 for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
1745 if( *p_chroma == VLC_CODEC_YUVP )
1746 rv = RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
1747 else if( *p_chroma == VLC_CODEC_YUVA )
1748 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
1753 else if( *p_chroma == VLC_CODEC_RGBA )
1754 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox, i_margin,
1759 else if( *p_chroma == VLC_CODEC_ARGB )
1760 rv = RenderAXYZ( p_filter, p_region_out, p_lines, &bbox,
1761 i_margin, *p_chroma, RGBFromRGB,
1762 FillARGBPicture, BlendARGBPixel );
1768 /* With karaoke, we're going to have to render the text a number
1769 * of times to show the progress marker on the text.
1771 if( pi_k_durations )
1772 var_SetBool( p_filter, "text-rerender", true );
1775 FreeLines( p_lines );
1778 for( int i = 0; i < i_text_length; i++ )
1780 if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
1781 text_style_Delete( pp_styles[i] );
1784 free( pi_k_durations );
1789 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
1790 subpicture_region_t *p_region_in,
1791 const vlc_fourcc_t *p_chroma_list )
1793 return RenderCommon( p_filter, p_region_out, p_region_in, false, p_chroma_list );
1796 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
1797 subpicture_region_t *p_region_in,
1798 const vlc_fourcc_t *p_chroma_list )
1800 return RenderCommon( p_filter, p_region_out, p_region_in, true, p_chroma_list );
1803 /*****************************************************************************
1804 * Create: allocates osd-text video thread output method
1805 *****************************************************************************
1806 * This function allocates and initializes a Clone vout method.
1807 *****************************************************************************/
1808 static int Init_FT( vlc_object_t *p_this,
1809 const char *psz_fontfile,
1810 const int fontindex,
1811 const float f_outline_thickness)
1813 filter_t *p_filter = (filter_t *)p_this;
1814 filter_sys_t *p_sys = p_filter->p_sys;
1817 int i_error = FT_Init_FreeType( &p_sys->p_library );
1820 msg_Err( p_filter, "couldn't initialize freetype" );
1824 i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
1825 fontindex, &p_sys->p_face );
1827 if( i_error == FT_Err_Unknown_File_Format )
1829 msg_Err( p_filter, "file %s have unknown format",
1830 psz_fontfile ? psz_fontfile : "(null)" );
1835 msg_Err( p_filter, "failed to load font file %s",
1836 psz_fontfile ? psz_fontfile : "(null)" );
1840 i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
1843 msg_Err( p_filter, "font has no unicode translation table" );
1847 if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
1849 p_sys->p_stroker = NULL;
1850 if( f_outline_thickness > 0.001 )
1852 i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
1854 msg_Err( p_filter, "Failed to create stroker for outlining" );
1860 if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
1861 if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
1863 return VLC_EGENERIC;
1867 static int Create( vlc_object_t *p_this )
1869 filter_t *p_filter = (filter_t *)p_this;
1870 filter_sys_t *p_sys;
1871 char *psz_fontfile = NULL;
1872 char *psz_fontname = NULL;
1873 char *psz_monofontfile = NULL;
1874 char *psz_monofontfamily = NULL;
1875 int fontindex = 0, monofontindex = 0;
1877 /* Allocate structure */
1878 p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
1882 p_sys->style.psz_fontname = NULL;
1883 p_sys->p_xml = NULL;
1885 p_sys->p_library = 0;
1886 p_sys->style.i_font_size = 0;
1887 p_sys->style.i_style_flags = 0;
1890 * The following variables should not be cached, as they might be changed on-the-fly:
1891 * freetype-rel-fontsize, freetype-background-opacity, freetype-background-color,
1892 * freetype-outline-thickness, freetype-color
1896 psz_fontname = var_InheritString( p_filter, "freetype-font" );
1897 psz_monofontfamily = var_InheritString( p_filter, "freetype-monofont" );
1898 p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
1899 p_sys->style.i_font_alpha = var_InheritInteger( p_filter,"freetype-opacity" );
1900 p_sys->style.i_font_alpha = VLC_CLIP( p_sys->style.i_font_alpha, 0, 255 );
1901 if( var_InheritBool( p_filter, "freetype-bold" ) )
1902 p_sys->style.i_style_flags |= STYLE_BOLD;
1904 double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
1905 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
1906 p_sys->style.i_outline_alpha = var_InheritInteger( p_filter, "freetype-outline-opacity" );
1907 p_sys->style.i_outline_alpha = VLC_CLIP( p_sys->style.i_outline_alpha, 0, 255 );
1908 p_sys->style.i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
1909 p_sys->style.i_outline_color = VLC_CLIP( p_sys->style.i_outline_color, 0, 0xFFFFFF );
1911 p_sys->style.i_shadow_alpha = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
1912 p_sys->style.i_shadow_alpha = VLC_CLIP( p_sys->style.i_shadow_alpha, 0, 255 );
1913 p_sys->style.i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
1914 p_sys->style.i_shadow_color = VLC_CLIP( p_sys->style.i_shadow_color, 0, 0xFFFFFF );
1915 float f_shadow_angle = var_InheritFloat( p_filter, "freetype-shadow-angle" );
1916 float f_shadow_distance = var_InheritFloat( p_filter, "freetype-shadow-distance" );
1917 f_shadow_distance = VLC_CLIP( f_shadow_distance, 0, 1 );
1918 p_sys->f_shadow_vector_x = f_shadow_distance * cos(2 * M_PI * f_shadow_angle / 360);
1919 p_sys->f_shadow_vector_y = f_shadow_distance * sin(2 * M_PI * f_shadow_angle / 360);
1921 /* Set default psz_fontname */
1922 if( !psz_fontname || !*psz_fontname )
1924 free( psz_fontname );
1925 #ifdef HAVE_GET_FONT_BY_FAMILY_NAME
1926 psz_fontname = strdup( DEFAULT_FAMILY );
1928 psz_fontname = File_Select( DEFAULT_FONT_FILE );
1932 /* set default psz_monofontname */
1933 if( !psz_monofontfamily || !*psz_monofontfamily )
1935 free( psz_monofontfamily );
1936 #ifdef HAVE_GET_FONT_BY_FAMILY_NAME
1937 psz_monofontfamily = strdup( DEFAULT_MONOSPACE_FAMILY );
1939 psz_monofontfamily = File_Select( DEFAULT_MONOSPACE_FONT_FILE );
1943 /* Set the current font file */
1944 p_sys->style.psz_fontname = psz_fontname;
1945 p_sys->style.psz_monofontname = psz_monofontfamily;
1947 #ifdef HAVE_FONTCONFIG
1948 p_sys->pf_select = FontConfig_Select;
1949 FontConfig_BuildCache( p_filter );
1950 #elif defined( __APPLE__ )
1951 #if !TARGET_OS_IPHONE
1952 p_sys->pf_select = MacLegacy_Select;
1954 #elif defined( _WIN32 ) && defined( HAVE_GET_FONT_BY_FAMILY_NAME )
1955 p_sys->pf_select = Win32_Select;
1957 p_sys->pf_select = Dummy_Select;
1961 psz_fontfile = p_sys->pf_select( p_filter, psz_fontname, false, false,
1962 p_sys->i_default_font_size, &fontindex );
1963 psz_monofontfile = p_sys->pf_select( p_filter, psz_monofontfamily, false,
1964 false, p_sys->i_default_font_size,
1966 msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontname, psz_fontfile );
1967 msg_Dbg( p_filter, "Using %s as mono-font from file %s", psz_monofontfamily, psz_monofontfile );
1969 /* If nothing is found, use the default family */
1971 psz_fontfile = File_Select( psz_fontname );
1972 if( !psz_monofontfile )
1973 psz_monofontfile = File_Select( psz_monofontfamily );
1975 if( Init_FT( p_this, psz_fontfile, fontindex, f_outline_thickness ) != VLC_SUCCESS )
1978 p_sys->pp_font_attachments = NULL;
1979 p_sys->i_font_attachments = 0;
1981 p_filter->pf_render_text = RenderText;
1982 p_filter->pf_render_html = RenderHtml;
1984 LoadFontsFromAttachments( p_filter );
1986 free( psz_fontfile );
1987 free( psz_monofontfile );
1992 free( psz_fontfile );
1993 free( psz_monofontfile );
1994 free( psz_fontname );
1995 free( psz_monofontfamily );
1997 return VLC_EGENERIC;
2001 static void Destroy_FT( vlc_object_t *p_this )
2003 filter_t *p_filter = (filter_t *)p_this;
2004 filter_sys_t *p_sys = p_filter->p_sys;
2006 if( p_sys->p_stroker )
2007 FT_Stroker_Done( p_sys->p_stroker );
2008 FT_Done_Face( p_sys->p_face );
2009 FT_Done_FreeType( p_sys->p_library );
2012 /*****************************************************************************
2013 * Destroy: destroy Clone video thread output method
2014 *****************************************************************************
2015 * Clean up all data and library connections
2016 *****************************************************************************/
2017 static void Destroy( vlc_object_t *p_this )
2019 filter_t *p_filter = (filter_t *)p_this;
2020 filter_sys_t *p_sys = p_filter->p_sys;
2022 if( p_sys->pp_font_attachments )
2024 for( int k = 0; k < p_sys->i_font_attachments; k++ )
2025 vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
2027 free( p_sys->pp_font_attachments );
2030 if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
2031 free( p_sys->style.psz_fontname );
2032 free( p_sys->style.psz_monofontname );
2034 Destroy_FT( p_this );