1 /*****************************************************************************
2 * freetype.c : Put text on the video, using freetype2
3 *****************************************************************************
4 * Copyright (C) 2002 - 2011 the VideoLAN team
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>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 /*****************************************************************************
29 *****************************************************************************/
36 #include <vlc_common.h>
37 #include <vlc_plugin.h>
38 #include <vlc_stream.h> /* stream_MemoryNew */
39 #include <vlc_input.h> /* vlc_input_attachment_* */
40 #include <vlc_xml.h> /* xml_reader */
41 #include <vlc_strings.h> /* resolve_xml_special_chars */
42 #include <vlc_charset.h> /* ToCharset */
43 #include <vlc_dialog.h> /* FcCache dialog */
44 #include <vlc_filter.h> /* filter_sys_t */
45 #include <vlc_text_style.h> /* text_style_t*/
49 # define DEFAULT_FONT_FILE "/Library/Fonts/Arial Black.ttf"
50 # define DEFAULT_FAMILY "Arial Black"
51 #elif defined( WIN32 )
52 # define DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
53 # define DEFAULT_FAMILY "Arial"
54 #elif defined( HAVE_MAEMO )
55 # define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf"
56 # define DEFAULT_FAMILY "Nokia Sans Bold"
58 # define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
59 # define DEFAULT_FAMILY "Serif Bold"
63 #include <freetype/ftsynth.h>
64 #include FT_FREETYPE_H
68 #define FT_FLOOR(X) ((X & -64) >> 6)
69 #define FT_CEIL(X) (((X + 63) & -64) >> 6)
71 #define FT_MulFix(v, s) (((v)*(s))>>16)
75 #if defined(HAVE_FRIBIDI)
76 # include <fribidi/fribidi.h>
84 # undef HAVE_FONTCONFIG
88 #ifdef HAVE_FONTCONFIG
89 # include <fontconfig/fontconfig.h>
95 /*****************************************************************************
97 *****************************************************************************/
98 static int Create ( vlc_object_t
* );
99 static void Destroy( vlc_object_t
* );
101 #define FONT_TEXT N_("Font")
103 #define FAMILY_LONGTEXT N_("Font family for the font you want to use")
104 #define FONT_LONGTEXT N_("Font file for the font you want to use")
106 #define FONTSIZE_TEXT N_("Font size in pixels")
107 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
108 "that will be rendered on the video. " \
109 "If set to something different than 0 this option will override the " \
110 "relative font size." )
111 #define OPACITY_TEXT N_("Text opacity")
112 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
113 "text that will be rendered on the video. 0 = transparent, " \
114 "255 = totally opaque. " )
115 #define COLOR_TEXT N_("Text default color")
116 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
117 "the video. This must be an hexadecimal (like HTML colors). The first two "\
118 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
119 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
120 #define FONTSIZER_TEXT N_("Relative font size")
121 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
122 "fonts that will be rendered on the video. If absolute font size is set, "\
123 "relative size will be overridden." )
124 #define BOLD_TEXT N_("Force bold")
126 #define BG_OPACITY_TEXT N_("Background opacity")
127 #define BG_COLOR_TEXT N_("Background color")
129 #define OUTLINE_OPACITY_TEXT N_("Outline opacity")
130 #define OUTLINE_COLOR_TEXT N_("Outline color")
131 #define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
133 #define SHADOW_OPACITY_TEXT N_("Shadow opacity")
134 #define SHADOW_COLOR_TEXT N_("Shadow color")
135 #define SHADOW_ANGLE_TEXT N_("Shadow angle")
136 #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
139 static const int pi_sizes
[] = { 20, 18, 16, 12, 6 };
140 static const char *const ppsz_sizes_text
[] = {
141 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
142 #define YUVP_TEXT N_("Use YUVP renderer")
143 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
144 "This option is only needed if you want to encode into DVB subtitles" )
146 static const int pi_color_values
[] = {
147 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
148 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
149 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
151 static const char *const ppsz_color_descriptions
[] = {
152 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
153 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
154 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
156 static const int pi_outline_thickness
[] = {
159 static const char *const ppsz_outline_thickness
[] = {
160 N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
164 set_shortname( N_("Text renderer"))
165 set_description( N_("Freetype2 font renderer") )
166 set_category( CAT_VIDEO
)
167 set_subcategory( SUBCAT_VIDEO_SUBPIC
)
170 add_font( "freetype-font", DEFAULT_FAMILY
, FONT_TEXT
, FAMILY_LONGTEXT
, false )
172 add_loadfile( "freetype-font", DEFAULT_FONT_FILE
, FONT_TEXT
, FONT_LONGTEXT
, false )
175 add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT
,
176 FONTSIZE_LONGTEXT
, true )
179 add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT
,
180 FONTSIZER_LONGTEXT
, false )
181 change_integer_list( pi_sizes
, ppsz_sizes_text
)
184 /* opacity valid on 0..255, with default 255 = fully opaque */
185 add_integer_with_range( "freetype-opacity", 255, 0, 255,
186 OPACITY_TEXT
, OPACITY_LONGTEXT
, false )
189 /* hook to the color values list, with default 0x00ffffff = white */
190 add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT
,
191 COLOR_LONGTEXT
, false )
192 change_integer_list( pi_color_values
, ppsz_color_descriptions
)
195 add_bool( "freetype-bold", false, BOLD_TEXT
, NULL
, false )
198 add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
199 BG_OPACITY_TEXT
, NULL
, false )
201 add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT
,
203 change_integer_list( pi_color_values
, ppsz_color_descriptions
)
206 add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
207 OUTLINE_OPACITY_TEXT
, NULL
, false )
209 add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT
,
211 change_integer_list( pi_color_values
, ppsz_color_descriptions
)
213 add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT
,
215 change_integer_list( pi_outline_thickness
, ppsz_outline_thickness
)
218 add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
219 SHADOW_OPACITY_TEXT
, NULL
, false )
221 add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT
,
223 change_integer_list( pi_color_values
, ppsz_color_descriptions
)
225 add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
226 SHADOW_ANGLE_TEXT
, NULL
, false )
228 add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
229 SHADOW_DISTANCE_TEXT
, NULL
, false )
232 add_obsolete_integer( "freetype-effect" );
234 add_bool( "freetype-yuvp", false, YUVP_TEXT
,
235 YUVP_LONGTEXT
, true )
236 set_capability( "text renderer", 100 )
237 add_shortcut( "text" )
238 set_callbacks( Create
, Destroy
)
242 /*****************************************************************************
244 *****************************************************************************/
248 FT_BitmapGlyph p_glyph
;
249 FT_BitmapGlyph p_outline
;
250 FT_BitmapGlyph p_shadow
;
251 uint32_t i_color
; /* ARGB color */
252 int i_line_offset
; /* underline/strikethrough offset */
253 int i_line_thickness
; /* underline/strikethrough thickness */
256 typedef struct line_desc_t line_desc_t
;
263 int i_character_count
;
264 line_character_t
*p_character
;
267 typedef struct font_stack_t font_stack_t
;
272 uint32_t i_color
; /* ARGB */
273 uint32_t i_karaoke_bg_color
; /* ARGB */
275 font_stack_t
*p_next
;
278 /*****************************************************************************
279 * filter_sys_t: freetype local data
280 *****************************************************************************
281 * This structure is part of the video output thread descriptor.
282 * It describes the freetype specific properties of an output thread.
283 *****************************************************************************/
286 FT_Library p_library
; /* handle to library */
287 FT_Face p_face
; /* handle to face object */
288 FT_Stroker p_stroker
;
289 uint8_t i_font_opacity
;
294 uint8_t i_background_opacity
;
295 int i_background_color
;
297 double f_outline_thickness
;
298 uint8_t i_outline_opacity
;
301 float f_shadow_vector_x
;
302 float f_shadow_vector_y
;
303 uint8_t i_shadow_opacity
;
306 int i_default_font_size
;
307 int i_display_height
;
308 char* psz_fontfamily
;
312 char* psz_win_fonts_path
;
316 input_attachment_t
**pp_font_attachments
;
317 int i_font_attachments
;
321 static void YUVFromRGB( uint32_t i_argb
,
322 uint8_t *pi_y
, uint8_t *pi_u
, uint8_t *pi_v
)
324 int i_red
= ( i_argb
& 0x00ff0000 ) >> 16;
325 int i_green
= ( i_argb
& 0x0000ff00 ) >> 8;
326 int i_blue
= ( i_argb
& 0x000000ff );
328 *pi_y
= (uint8_t)__MIN(abs( 2104 * i_red
+ 4130 * i_green
+
329 802 * i_blue
+ 4096 + 131072 ) >> 13, 235);
330 *pi_u
= (uint8_t)__MIN(abs( -1214 * i_red
+ -2384 * i_green
+
331 3598 * i_blue
+ 4096 + 1048576) >> 13, 240);
332 *pi_v
= (uint8_t)__MIN(abs( 3598 * i_red
+ -3013 * i_green
+
333 -585 * i_blue
+ 4096 + 1048576) >> 13, 240);
335 static void RGBFromRGB( uint32_t i_argb
,
336 uint8_t *pi_r
, uint8_t *pi_g
, uint8_t *pi_b
)
338 *pi_r
= ( i_argb
& 0x00ff0000 ) >> 16;
339 *pi_g
= ( i_argb
& 0x0000ff00 ) >> 8;
340 *pi_b
= ( i_argb
& 0x000000ff );
342 /*****************************************************************************
343 * Make any TTF/OTF fonts present in the attachments of the media file
344 * and store them for later use by the FreeType Engine
345 *****************************************************************************/
346 static int LoadFontsFromAttachments( filter_t
*p_filter
)
348 filter_sys_t
*p_sys
= p_filter
->p_sys
;
349 input_attachment_t
**pp_attachments
;
350 int i_attachments_cnt
;
352 if( filter_GetInputAttachments( p_filter
, &pp_attachments
, &i_attachments_cnt
) )
355 p_sys
->i_font_attachments
= 0;
356 p_sys
->pp_font_attachments
= malloc( i_attachments_cnt
* sizeof(*p_sys
->pp_font_attachments
));
357 if( !p_sys
->pp_font_attachments
)
360 for( int k
= 0; k
< i_attachments_cnt
; k
++ )
362 input_attachment_t
*p_attach
= pp_attachments
[k
];
364 if( ( !strcmp( p_attach
->psz_mime
, "application/x-truetype-font" ) || // TTF
365 !strcmp( p_attach
->psz_mime
, "application/x-font-otf" ) ) && // OTF
366 p_attach
->i_data
> 0 && p_attach
->p_data
)
368 p_sys
->pp_font_attachments
[ p_sys
->i_font_attachments
++ ] = p_attach
;
372 vlc_input_attachment_Delete( p_attach
);
375 free( pp_attachments
);
380 static int GetFontSize( filter_t
*p_filter
)
382 filter_sys_t
*p_sys
= p_filter
->p_sys
;
385 if( p_sys
->i_default_font_size
)
387 i_size
= p_sys
->i_default_font_size
;
391 int i_ratio
= var_GetInteger( p_filter
, "freetype-rel-fontsize" );
394 i_size
= (int)p_filter
->fmt_out
.video
.i_height
/ i_ratio
;
395 p_filter
->p_sys
->i_display_height
= p_filter
->fmt_out
.video
.i_height
;
400 msg_Warn( p_filter
, "invalid fontsize, using 12" );
406 static int SetFontSize( filter_t
*p_filter
, int i_size
)
408 filter_sys_t
*p_sys
= p_filter
->p_sys
;
412 i_size
= GetFontSize( p_filter
);
414 msg_Dbg( p_filter
, "using fontsize: %i", i_size
);
417 p_sys
->i_font_size
= i_size
;
419 if( FT_Set_Pixel_Sizes( p_sys
->p_face
, 0, i_size
) )
421 msg_Err( p_filter
, "couldn't set font size to %d", i_size
);
429 #ifdef HAVE_FONTCONFIG
430 static void FontConfig_BuildCache( filter_t
*p_filter
)
433 msg_Dbg( p_filter
, "Building font databases.");
438 dialog_progress_bar_t
*p_dialog
= NULL
;
439 FcConfig
*fcConfig
= FcInitLoadConfig();
441 p_dialog
= dialog_ProgressCreate( p_filter
,
442 _("Building font cache"),
443 _("Please wait while your font cache is rebuilt.\n"
444 "This should take less than a few minutes."), NULL
);
447 dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
449 FcConfigBuildFonts( fcConfig
);
452 // dialog_ProgressSet( p_dialog, NULL, 1.0 );
453 dialog_ProgressDestroy( p_dialog
);
458 msg_Dbg( p_filter
, "Took %ld microseconds", (long)((t2
- t1
)) );
462 * \brief Selects a font matching family, bold, italic provided
464 static char* FontConfig_Select( FcConfig
* config
, const char* family
,
465 bool b_bold
, bool b_italic
, int i_size
, int *i_idx
)
467 FcResult result
= FcResultMatch
;
468 FcPattern
*pat
, *p_pat
;
472 /* Create a pattern and fills it */
473 pat
= FcPatternCreate();
474 if (!pat
) return NULL
;
477 FcPatternAddString( pat
, FC_FAMILY
, (const FcChar8
*)family
);
478 FcPatternAddBool( pat
, FC_OUTLINE
, FcTrue
);
479 FcPatternAddInteger( pat
, FC_SLANT
, b_italic
? FC_SLANT_ITALIC
: FC_SLANT_ROMAN
);
480 FcPatternAddInteger( pat
, FC_WEIGHT
, b_bold
? FC_WEIGHT_EXTRABOLD
: FC_WEIGHT_NORMAL
);
484 if( asprintf( &psz_fontsize
, "%d", i_size
) != -1 )
486 FcPatternAddString( pat
, FC_SIZE
, (const FcChar8
*)psz_fontsize
);
487 free( psz_fontsize
);
492 FcDefaultSubstitute( pat
);
493 if( !FcConfigSubstitute( config
, pat
, FcMatchPattern
) )
495 FcPatternDestroy( pat
);
499 /* Find the best font for the pattern, destroy the pattern */
500 p_pat
= FcFontMatch( config
, pat
, &result
);
501 FcPatternDestroy( pat
);
502 if( !p_pat
|| result
== FcResultNoMatch
) return NULL
;
504 /* Check the new pattern */
505 if( ( FcResultMatch
!= FcPatternGetBool( p_pat
, FC_OUTLINE
, 0, &val_b
) )
506 || ( val_b
!= FcTrue
) )
508 FcPatternDestroy( p_pat
);
511 if( FcResultMatch
!= FcPatternGetInteger( p_pat
, FC_INDEX
, 0, i_idx
) )
516 if( FcResultMatch
!= FcPatternGetString( p_pat
, FC_FAMILY
, 0, &val_s
) )
518 FcPatternDestroy( p_pat
);
522 /* if( strcasecmp((const char*)val_s, family ) != 0 )
523 msg_Warn( p_filter, "fontconfig: selected font family is not"
524 "the requested one: '%s' != '%s'\n",
525 (const char*)val_s, family ); */
527 if( FcResultMatch
!= FcPatternGetString( p_pat
, FC_FILE
, 0, &val_s
) )
529 FcPatternDestroy( p_pat
);
533 FcPatternDestroy( p_pat
);
534 return strdup( (const char*)val_s
);
540 #define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
542 static int GetFileFontByName( const char *font_name
, char **psz_filename
)
545 wchar_t vbuffer
[MAX_PATH
];
546 wchar_t dbuffer
[256];
548 if( RegOpenKeyEx(HKEY_LOCAL_MACHINE
, FONT_DIR_NT
, 0, KEY_READ
, &hKey
) != ERROR_SUCCESS
)
551 for( int index
= 0;; index
++ )
553 DWORD vbuflen
= MAX_PATH
- 1;
556 if( RegEnumValueW( hKey
, index
, vbuffer
, &vbuflen
,
557 NULL
, NULL
, (LPBYTE
)dbuffer
, &dbuflen
) != ERROR_SUCCESS
)
560 char *psz_value
= FromWide( vbuffer
);
562 char *s
= strchr( psz_value
,'(' );
563 if( s
!= NULL
&& s
!= psz_value
) s
[-1] = '\0';
565 /* Manage concatenated font names */
566 if( strchr( psz_value
, '&') ) {
567 if( strcasestr( psz_value
, font_name
) != NULL
)
571 if( strcasecmp( psz_value
, font_name
) == 0 )
576 *psz_filename
= FromWide( dbuffer
);
581 static int CALLBACK
EnumFontCallback(const ENUMLOGFONTEX
*lpelfe
, const NEWTEXTMETRICEX
*metric
,
582 DWORD type
, LPARAM lParam
)
584 VLC_UNUSED( metric
);
585 if( (type
& RASTER_FONTTYPE
) ) return 1;
586 // if( lpelfe->elfScript ) FIXME
588 return GetFileFontByName( (const char *)lpelfe
->elfFullName
, (char **)lParam
);
591 static char* Win32_Select( filter_t
*p_filter
, const char* family
,
592 bool b_bold
, bool b_italic
, int i_size
, int *i_idx
)
594 VLC_UNUSED( i_size
);
598 lf
.lfCharSet
= DEFAULT_CHARSET
;
602 lf
.lfWeight
= FW_BOLD
;
603 strncpy( (LPSTR
)&lf
.lfFaceName
, family
, 32);
606 char *psz_filename
= NULL
;
607 HDC hDC
= GetDC( NULL
);
608 EnumFontFamiliesEx(hDC
, &lf
, (FONTENUMPROC
)&EnumFontCallback
, (LPARAM
)&psz_filename
, 0);
609 ReleaseDC(NULL
, hDC
);
612 if( psz_filename
!= NULL
)
614 /* FIXME: increase i_idx, when concatenated strings */
617 /* Prepend the Windows Font path, when only a filename was provided */
618 if( strchr( psz_filename
, DIR_SEP_CHAR
) )
623 if( asprintf( &psz_tmp
, "%s\\%s", p_filter
->p_sys
->psz_win_fonts_path
, psz_filename
) == -1 )
625 free( psz_filename
);
628 free( psz_filename
);
632 else /* Let's take any font we can */
635 if( asprintf( &psz_tmp
, "%s\\%s", p_filter
->p_sys
->psz_win_fonts_path
, "arial.ttf" ) == -1 )
641 #endif /* HAVE_WIN32 */
643 #endif /* HAVE_STYLES */
646 /*****************************************************************************
647 * RenderYUVP: place string in picture
648 *****************************************************************************
649 * This function merges the previously rendered freetype glyphs into a picture
650 *****************************************************************************/
651 static int RenderYUVP( filter_t
*p_filter
, subpicture_region_t
*p_region
,
655 VLC_UNUSED(p_filter
);
656 static const uint8_t pi_gamma
[16] =
657 {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
658 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
662 int i
, x
, y
, i_pitch
;
663 uint8_t i_y
, i_u
, i_v
; /* YUV values, derived from incoming RGB */
665 /* Create a new subpicture region */
666 video_format_Init( &fmt
, VLC_CODEC_YUVP
);
668 fmt
.i_visible_width
= p_bbox
->xMax
- p_bbox
->xMin
+ 4;
670 fmt
.i_visible_height
= p_bbox
->yMax
- p_bbox
->yMin
+ 4;
672 assert( !p_region
->p_picture
);
673 p_region
->p_picture
= picture_NewFromFormat( &fmt
);
674 if( !p_region
->p_picture
)
676 fmt
.p_palette
= p_region
->fmt
.p_palette
? p_region
->fmt
.p_palette
: malloc(sizeof(*fmt
.p_palette
));
679 /* Calculate text color components
680 * Only use the first color */
681 int i_alpha
= (p_line
->p_character
[0].i_color
>> 24) & 0xff;
682 YUVFromRGB( p_line
->p_character
[0].i_color
, &i_y
, &i_u
, &i_v
);
685 fmt
.p_palette
->i_entries
= 16;
686 for( i
= 0; i
< 8; i
++ )
688 fmt
.p_palette
->palette
[i
][0] = 0;
689 fmt
.p_palette
->palette
[i
][1] = 0x80;
690 fmt
.p_palette
->palette
[i
][2] = 0x80;
691 fmt
.p_palette
->palette
[i
][3] = pi_gamma
[i
];
692 fmt
.p_palette
->palette
[i
][3] =
693 (int)fmt
.p_palette
->palette
[i
][3] * i_alpha
/ 255;
695 for( i
= 8; i
< fmt
.p_palette
->i_entries
; i
++ )
697 fmt
.p_palette
->palette
[i
][0] = i
* 16 * i_y
/ 256;
698 fmt
.p_palette
->palette
[i
][1] = i_u
;
699 fmt
.p_palette
->palette
[i
][2] = i_v
;
700 fmt
.p_palette
->palette
[i
][3] = pi_gamma
[i
];
701 fmt
.p_palette
->palette
[i
][3] =
702 (int)fmt
.p_palette
->palette
[i
][3] * i_alpha
/ 255;
705 p_dst
= p_region
->p_picture
->Y_PIXELS
;
706 i_pitch
= p_region
->p_picture
->Y_PITCH
;
708 /* Initialize the region pixels */
709 memset( p_dst
, 0, i_pitch
* p_region
->fmt
.i_height
);
711 for( ; p_line
!= NULL
; p_line
= p_line
->p_next
)
713 int i_align_left
= 0;
714 if( p_line
->i_width
< (int)fmt
.i_visible_width
)
716 if( (p_region
->i_align
& 0x3) == SUBPICTURE_ALIGN_RIGHT
)
717 i_align_left
= ( fmt
.i_visible_width
- p_line
->i_width
);
718 else if( (p_region
->i_align
& 0x3) != SUBPICTURE_ALIGN_LEFT
)
719 i_align_left
= ( fmt
.i_visible_width
- p_line
->i_width
) / 2;
723 for( i
= 0; i
< p_line
->i_character_count
; i
++ )
725 const line_character_t
*ch
= &p_line
->p_character
[i
];
726 FT_BitmapGlyph p_glyph
= ch
->p_glyph
;
728 int i_glyph_y
= i_align_top
- p_glyph
->top
+ p_bbox
->yMax
+ p_line
->i_base_line
;
729 int i_glyph_x
= i_align_left
+ p_glyph
->left
- p_bbox
->xMin
;
731 for( y
= 0; y
< p_glyph
->bitmap
.rows
; y
++ )
733 for( x
= 0; x
< p_glyph
->bitmap
.width
; x
++ )
735 if( p_glyph
->bitmap
.buffer
[y
* p_glyph
->bitmap
.width
+ x
] )
736 p_dst
[(i_glyph_y
+ y
) * i_pitch
+ (i_glyph_x
+ x
)] =
737 (p_glyph
->bitmap
.buffer
[y
* p_glyph
->bitmap
.width
+ x
] + 8)/16;
743 /* Outlining (find something better than nearest neighbour filtering ?) */
746 uint8_t *p_dst
= p_region
->p_picture
->Y_PIXELS
;
747 uint8_t *p_top
= p_dst
; /* Use 1st line as a cache */
748 uint8_t left
, current
;
750 for( y
= 1; y
< (int)fmt
.i_height
- 1; y
++ )
752 if( y
> 1 ) memcpy( p_top
, p_dst
, fmt
.i_width
);
753 p_dst
+= p_region
->p_picture
->Y_PITCH
;
756 for( x
= 1; x
< (int)fmt
.i_width
- 1; x
++ )
759 p_dst
[x
] = ( 8 * (int)p_dst
[x
] + left
+ p_dst
[x
+1] + p_top
[x
-1]+ p_top
[x
] + p_top
[x
+1] +
760 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;
764 memset( p_top
, 0, fmt
.i_width
);
770 /*****************************************************************************
771 * RenderYUVA: place string in picture
772 *****************************************************************************
773 * This function merges the previously rendered freetype glyphs into a picture
774 *****************************************************************************/
775 static void FillYUVAPicture( picture_t
*p_picture
,
776 int i_a
, int i_y
, int i_u
, int i_v
)
778 memset( p_picture
->p
[0].p_pixels
, i_y
,
779 p_picture
->p
[0].i_pitch
* p_picture
->p
[0].i_lines
);
780 memset( p_picture
->p
[1].p_pixels
, i_u
,
781 p_picture
->p
[1].i_pitch
* p_picture
->p
[1].i_lines
);
782 memset( p_picture
->p
[2].p_pixels
, i_v
,
783 p_picture
->p
[2].i_pitch
* p_picture
->p
[2].i_lines
);
784 memset( p_picture
->p
[3].p_pixels
, i_a
,
785 p_picture
->p
[3].i_pitch
* p_picture
->p
[3].i_lines
);
788 static inline void BlendYUVAPixel( picture_t
*p_picture
,
789 int i_picture_x
, int i_picture_y
,
790 int i_a
, int i_y
, int i_u
, int i_v
,
793 int i_an
= i_a
* i_alpha
/ 255;
795 uint8_t *p_y
= &p_picture
->p
[0].p_pixels
[i_picture_y
* p_picture
->p
[0].i_pitch
+ i_picture_x
];
796 uint8_t *p_u
= &p_picture
->p
[1].p_pixels
[i_picture_y
* p_picture
->p
[1].i_pitch
+ i_picture_x
];
797 uint8_t *p_v
= &p_picture
->p
[2].p_pixels
[i_picture_y
* p_picture
->p
[2].i_pitch
+ i_picture_x
];
798 uint8_t *p_a
= &p_picture
->p
[3].p_pixels
[i_picture_y
* p_picture
->p
[3].i_pitch
+ i_picture_x
];
810 *p_a
= 255 - (255 - *p_a
) * (255 - i_an
) / 255;
813 *p_y
= ( *p_y
* i_ao
* (255 - i_an
) / 255 + i_y
* i_an
) / *p_a
;
814 *p_u
= ( *p_u
* i_ao
* (255 - i_an
) / 255 + i_u
* i_an
) / *p_a
;
815 *p_v
= ( *p_v
* i_ao
* (255 - i_an
) / 255 + i_v
* i_an
) / *p_a
;
820 static void FillRGBAPicture( picture_t
*p_picture
,
821 int i_a
, int i_r
, int i_g
, int i_b
)
823 for( int dy
= 0; dy
< p_picture
->p
[0].i_visible_lines
; dy
++ )
825 for( int dx
= 0; dx
< p_picture
->p
[0].i_visible_pitch
; dx
+= 4 )
827 uint8_t *p_rgba
= &p_picture
->p
->p_pixels
[dy
* p_picture
->p
->i_pitch
+ dx
];
836 static inline void BlendRGBAPixel( picture_t
*p_picture
,
837 int i_picture_x
, int i_picture_y
,
838 int i_a
, int i_r
, int i_g
, int i_b
,
841 int i_an
= i_a
* i_alpha
/ 255;
843 uint8_t *p_rgba
= &p_picture
->p
->p_pixels
[i_picture_y
* p_picture
->p
->i_pitch
+ 4 * i_picture_x
];
845 int i_ao
= p_rgba
[3];
855 p_rgba
[3] = 255 - (255 - p_rgba
[3]) * (255 - i_an
) / 255;
858 p_rgba
[0] = ( p_rgba
[0] * i_ao
* (255 - i_an
) / 255 + i_r
* i_an
) / p_rgba
[3];
859 p_rgba
[1] = ( p_rgba
[1] * i_ao
* (255 - i_an
) / 255 + i_g
* i_an
) / p_rgba
[3];
860 p_rgba
[2] = ( p_rgba
[2] * i_ao
* (255 - i_an
) / 255 + i_b
* i_an
) / p_rgba
[3];
865 static inline void BlendAXYZGlyph( picture_t
*p_picture
,
866 int i_picture_x
, int i_picture_y
,
867 int i_a
, int i_x
, int i_y
, int i_z
,
868 FT_BitmapGlyph p_glyph
,
869 void (*BlendPixel
)(picture_t
*, int, int, int, int, int, int, int) )
872 for( int dy
= 0; dy
< p_glyph
->bitmap
.rows
; dy
++ )
874 for( int dx
= 0; dx
< p_glyph
->bitmap
.width
; dx
++ )
875 BlendPixel( p_picture
, i_picture_x
+ dx
, i_picture_y
+ dy
,
877 p_glyph
->bitmap
.buffer
[dy
* p_glyph
->bitmap
.width
+ dx
] );
881 static inline void BlendAXYZLine( picture_t
*p_picture
,
882 int i_picture_x
, int i_picture_y
,
883 int i_a
, int i_x
, int i_y
, int i_z
,
884 const line_character_t
*p_current
,
885 const line_character_t
*p_next
,
886 void (*BlendPixel
)(picture_t
*, int, int, int, int, int, int, int) )
888 int i_line_width
= p_current
->p_glyph
->bitmap
.width
;
890 i_line_width
= p_next
->p_glyph
->left
- p_current
->p_glyph
->left
;
892 for( int dx
= 0; dx
< i_line_width
; dx
++ )
894 for( int dy
= 0; dy
< p_current
->i_line_thickness
; dy
++ )
895 BlendPixel( p_picture
,
897 i_picture_y
+ p_current
->i_line_offset
+ dy
,
898 i_a
, i_x
, i_y
, i_z
, 0xff );
902 static inline int RenderAXYZ( filter_t
*p_filter
,
903 subpicture_region_t
*p_region
,
904 line_desc_t
*p_line_head
,
907 vlc_fourcc_t i_chroma
,
908 void (*ExtractComponents
)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
909 void (*FillPicture
)( picture_t
*p_picture
, int, int, int, int ),
910 void (*BlendPixel
)(picture_t
*, int, int, int, int, int, int, int) )
912 filter_sys_t
*p_sys
= p_filter
->p_sys
;
914 /* Create a new subpicture region */
915 const int i_text_width
= p_bbox
->xMax
- p_bbox
->xMin
;
916 const int i_text_height
= p_bbox
->yMax
- p_bbox
->yMin
;
918 video_format_Init( &fmt
, i_chroma
);
920 fmt
.i_visible_width
= i_text_width
+ 2 * i_margin
;
922 fmt
.i_visible_height
= i_text_height
+ 2 * i_margin
;
924 picture_t
*p_picture
= p_region
->p_picture
= picture_NewFromFormat( &fmt
);
925 if( !p_region
->p_picture
)
929 /* Initialize the picture background */
930 uint8_t i_a
= p_sys
->i_background_opacity
;
931 uint8_t i_x
, i_y
, i_z
;
932 ExtractComponents( p_sys
->i_background_color
, &i_x
, &i_y
, &i_z
);
934 FillPicture( p_picture
, i_a
, i_x
, i_y
, i_z
);
936 /* Render shadow then outline and then normal glyphs */
937 for( int g
= 0; g
< 3; g
++ )
939 /* Render all lines */
940 for( line_desc_t
*p_line
= p_line_head
; p_line
!= NULL
; p_line
= p_line
->p_next
)
942 int i_align_left
= i_margin
;
943 if( p_line
->i_width
< i_text_width
)
945 /* Left offset to take into account alignment */
946 if( (p_region
->i_align
& 0x3) == SUBPICTURE_ALIGN_RIGHT
)
947 i_align_left
+= ( i_text_width
- p_line
->i_width
);
948 else if( (p_region
->i_align
& 0x3) != SUBPICTURE_ALIGN_LEFT
)
949 i_align_left
+= ( i_text_width
- p_line
->i_width
) / 2;
951 int i_align_top
= i_margin
;
953 /* Render all glyphs and underline/strikethrough */
954 for( int i
= 0; i
< p_line
->i_character_count
; i
++ )
956 const line_character_t
*ch
= &p_line
->p_character
[i
];
957 FT_BitmapGlyph p_glyph
= g
== 0 ? ch
->p_shadow
: g
== 1 ? ch
->p_outline
: ch
->p_glyph
;
961 i_a
= (ch
->i_color
>> 24) & 0xff;
965 i_a
= i_a
* p_sys
->i_shadow_opacity
/ 255;
966 i_color
= p_sys
->i_shadow_color
;
969 i_a
= i_a
* p_sys
->i_outline_opacity
/ 255;
970 i_color
= p_sys
->i_outline_color
;
973 i_color
= ch
->i_color
;
976 ExtractComponents( i_color
, &i_x
, &i_y
, &i_z
);
978 int i_glyph_y
= i_align_top
- p_glyph
->top
+ p_bbox
->yMax
+ p_line
->i_base_line
;
979 int i_glyph_x
= i_align_left
+ p_glyph
->left
- p_bbox
->xMin
;
981 BlendAXYZGlyph( p_picture
,
982 i_glyph_x
, i_glyph_y
,
987 /* underline/strikethrough are only rendered for the normal glyph */
988 if( g
== 2 && ch
->i_line_thickness
> 0 )
989 BlendAXYZLine( p_picture
,
990 i_glyph_x
, i_glyph_y
+ p_glyph
->top
,
993 i
+ 1 < p_line
->i_character_count
? &ch
[1] : NULL
,
1002 static text_style_t
*CreateStyle( char *psz_fontname
, int i_font_size
,
1003 uint32_t i_font_color
, uint32_t i_karaoke_bg_color
,
1006 text_style_t
*p_style
= text_style_New();
1010 p_style
->psz_fontname
= psz_fontname
? strdup( psz_fontname
) : NULL
;
1011 p_style
->i_font_size
= i_font_size
;
1012 p_style
->i_font_color
= (i_font_color
& 0x00ffffff) >> 0;
1013 p_style
->i_font_alpha
= (i_font_color
& 0xff000000) >> 24;
1014 p_style
->i_karaoke_background_color
= (i_karaoke_bg_color
& 0x00ffffff) >> 0;
1015 p_style
->i_karaoke_background_alpha
= (i_karaoke_bg_color
& 0xff000000) >> 24;
1016 p_style
->i_style_flags
|= i_style_flags
;
1020 static int PushFont( font_stack_t
**p_font
, const char *psz_name
, int i_size
,
1021 uint32_t i_color
, uint32_t i_karaoke_bg_color
)
1024 return VLC_EGENERIC
;
1026 font_stack_t
*p_new
= malloc( sizeof(*p_new
) );
1030 p_new
->p_next
= NULL
;
1033 p_new
->psz_name
= strdup( psz_name
);
1035 p_new
->psz_name
= NULL
;
1037 p_new
->i_size
= i_size
;
1038 p_new
->i_color
= i_color
;
1039 p_new
->i_karaoke_bg_color
= i_karaoke_bg_color
;
1047 font_stack_t
*p_last
;
1049 for( p_last
= *p_font
;
1051 p_last
= p_last
->p_next
)
1054 p_last
->p_next
= p_new
;
1059 static int PopFont( font_stack_t
**p_font
)
1061 font_stack_t
*p_last
, *p_next_to_last
;
1063 if( !p_font
|| !*p_font
)
1064 return VLC_EGENERIC
;
1066 p_next_to_last
= NULL
;
1067 for( p_last
= *p_font
;
1069 p_last
= p_last
->p_next
)
1071 p_next_to_last
= p_last
;
1074 if( p_next_to_last
)
1075 p_next_to_last
->p_next
= NULL
;
1079 free( p_last
->psz_name
);
1085 static int PeekFont( font_stack_t
**p_font
, char **psz_name
, int *i_size
,
1086 uint32_t *i_color
, uint32_t *i_karaoke_bg_color
)
1088 font_stack_t
*p_last
;
1090 if( !p_font
|| !*p_font
)
1091 return VLC_EGENERIC
;
1093 for( p_last
=*p_font
;
1095 p_last
=p_last
->p_next
)
1098 *psz_name
= p_last
->psz_name
;
1099 *i_size
= p_last
->i_size
;
1100 *i_color
= p_last
->i_color
;
1101 *i_karaoke_bg_color
= p_last
->i_karaoke_bg_color
;
1106 static const struct {
1107 const char *psz_name
;
1109 } p_html_colors
[] = {
1110 /* Official html colors */
1111 { "Aqua", 0x00FFFF },
1112 { "Black", 0x000000 },
1113 { "Blue", 0x0000FF },
1114 { "Fuchsia", 0xFF00FF },
1115 { "Gray", 0x808080 },
1116 { "Green", 0x008000 },
1117 { "Lime", 0x00FF00 },
1118 { "Maroon", 0x800000 },
1119 { "Navy", 0x000080 },
1120 { "Olive", 0x808000 },
1121 { "Purple", 0x800080 },
1122 { "Red", 0xFF0000 },
1123 { "Silver", 0xC0C0C0 },
1124 { "Teal", 0x008080 },
1125 { "White", 0xFFFFFF },
1126 { "Yellow", 0xFFFF00 },
1129 { "AliceBlue", 0xF0F8FF },
1130 { "AntiqueWhite", 0xFAEBD7 },
1131 { "Aqua", 0x00FFFF },
1132 { "Aquamarine", 0x7FFFD4 },
1133 { "Azure", 0xF0FFFF },
1134 { "Beige", 0xF5F5DC },
1135 { "Bisque", 0xFFE4C4 },
1136 { "Black", 0x000000 },
1137 { "BlanchedAlmond", 0xFFEBCD },
1138 { "Blue", 0x0000FF },
1139 { "BlueViolet", 0x8A2BE2 },
1140 { "Brown", 0xA52A2A },
1141 { "BurlyWood", 0xDEB887 },
1142 { "CadetBlue", 0x5F9EA0 },
1143 { "Chartreuse", 0x7FFF00 },
1144 { "Chocolate", 0xD2691E },
1145 { "Coral", 0xFF7F50 },
1146 { "CornflowerBlue", 0x6495ED },
1147 { "Cornsilk", 0xFFF8DC },
1148 { "Crimson", 0xDC143C },
1149 { "Cyan", 0x00FFFF },
1150 { "DarkBlue", 0x00008B },
1151 { "DarkCyan", 0x008B8B },
1152 { "DarkGoldenRod", 0xB8860B },
1153 { "DarkGray", 0xA9A9A9 },
1154 { "DarkGrey", 0xA9A9A9 },
1155 { "DarkGreen", 0x006400 },
1156 { "DarkKhaki", 0xBDB76B },
1157 { "DarkMagenta", 0x8B008B },
1158 { "DarkOliveGreen", 0x556B2F },
1159 { "Darkorange", 0xFF8C00 },
1160 { "DarkOrchid", 0x9932CC },
1161 { "DarkRed", 0x8B0000 },
1162 { "DarkSalmon", 0xE9967A },
1163 { "DarkSeaGreen", 0x8FBC8F },
1164 { "DarkSlateBlue", 0x483D8B },
1165 { "DarkSlateGray", 0x2F4F4F },
1166 { "DarkSlateGrey", 0x2F4F4F },
1167 { "DarkTurquoise", 0x00CED1 },
1168 { "DarkViolet", 0x9400D3 },
1169 { "DeepPink", 0xFF1493 },
1170 { "DeepSkyBlue", 0x00BFFF },
1171 { "DimGray", 0x696969 },
1172 { "DimGrey", 0x696969 },
1173 { "DodgerBlue", 0x1E90FF },
1174 { "FireBrick", 0xB22222 },
1175 { "FloralWhite", 0xFFFAF0 },
1176 { "ForestGreen", 0x228B22 },
1177 { "Fuchsia", 0xFF00FF },
1178 { "Gainsboro", 0xDCDCDC },
1179 { "GhostWhite", 0xF8F8FF },
1180 { "Gold", 0xFFD700 },
1181 { "GoldenRod", 0xDAA520 },
1182 { "Gray", 0x808080 },
1183 { "Grey", 0x808080 },
1184 { "Green", 0x008000 },
1185 { "GreenYellow", 0xADFF2F },
1186 { "HoneyDew", 0xF0FFF0 },
1187 { "HotPink", 0xFF69B4 },
1188 { "IndianRed", 0xCD5C5C },
1189 { "Indigo", 0x4B0082 },
1190 { "Ivory", 0xFFFFF0 },
1191 { "Khaki", 0xF0E68C },
1192 { "Lavender", 0xE6E6FA },
1193 { "LavenderBlush", 0xFFF0F5 },
1194 { "LawnGreen", 0x7CFC00 },
1195 { "LemonChiffon", 0xFFFACD },
1196 { "LightBlue", 0xADD8E6 },
1197 { "LightCoral", 0xF08080 },
1198 { "LightCyan", 0xE0FFFF },
1199 { "LightGoldenRodYellow", 0xFAFAD2 },
1200 { "LightGray", 0xD3D3D3 },
1201 { "LightGrey", 0xD3D3D3 },
1202 { "LightGreen", 0x90EE90 },
1203 { "LightPink", 0xFFB6C1 },
1204 { "LightSalmon", 0xFFA07A },
1205 { "LightSeaGreen", 0x20B2AA },
1206 { "LightSkyBlue", 0x87CEFA },
1207 { "LightSlateGray", 0x778899 },
1208 { "LightSlateGrey", 0x778899 },
1209 { "LightSteelBlue", 0xB0C4DE },
1210 { "LightYellow", 0xFFFFE0 },
1211 { "Lime", 0x00FF00 },
1212 { "LimeGreen", 0x32CD32 },
1213 { "Linen", 0xFAF0E6 },
1214 { "Magenta", 0xFF00FF },
1215 { "Maroon", 0x800000 },
1216 { "MediumAquaMarine", 0x66CDAA },
1217 { "MediumBlue", 0x0000CD },
1218 { "MediumOrchid", 0xBA55D3 },
1219 { "MediumPurple", 0x9370D8 },
1220 { "MediumSeaGreen", 0x3CB371 },
1221 { "MediumSlateBlue", 0x7B68EE },
1222 { "MediumSpringGreen", 0x00FA9A },
1223 { "MediumTurquoise", 0x48D1CC },
1224 { "MediumVioletRed", 0xC71585 },
1225 { "MidnightBlue", 0x191970 },
1226 { "MintCream", 0xF5FFFA },
1227 { "MistyRose", 0xFFE4E1 },
1228 { "Moccasin", 0xFFE4B5 },
1229 { "NavajoWhite", 0xFFDEAD },
1230 { "Navy", 0x000080 },
1231 { "OldLace", 0xFDF5E6 },
1232 { "Olive", 0x808000 },
1233 { "OliveDrab", 0x6B8E23 },
1234 { "Orange", 0xFFA500 },
1235 { "OrangeRed", 0xFF4500 },
1236 { "Orchid", 0xDA70D6 },
1237 { "PaleGoldenRod", 0xEEE8AA },
1238 { "PaleGreen", 0x98FB98 },
1239 { "PaleTurquoise", 0xAFEEEE },
1240 { "PaleVioletRed", 0xD87093 },
1241 { "PapayaWhip", 0xFFEFD5 },
1242 { "PeachPuff", 0xFFDAB9 },
1243 { "Peru", 0xCD853F },
1244 { "Pink", 0xFFC0CB },
1245 { "Plum", 0xDDA0DD },
1246 { "PowderBlue", 0xB0E0E6 },
1247 { "Purple", 0x800080 },
1248 { "Red", 0xFF0000 },
1249 { "RosyBrown", 0xBC8F8F },
1250 { "RoyalBlue", 0x4169E1 },
1251 { "SaddleBrown", 0x8B4513 },
1252 { "Salmon", 0xFA8072 },
1253 { "SandyBrown", 0xF4A460 },
1254 { "SeaGreen", 0x2E8B57 },
1255 { "SeaShell", 0xFFF5EE },
1256 { "Sienna", 0xA0522D },
1257 { "Silver", 0xC0C0C0 },
1258 { "SkyBlue", 0x87CEEB },
1259 { "SlateBlue", 0x6A5ACD },
1260 { "SlateGray", 0x708090 },
1261 { "SlateGrey", 0x708090 },
1262 { "Snow", 0xFFFAFA },
1263 { "SpringGreen", 0x00FF7F },
1264 { "SteelBlue", 0x4682B4 },
1265 { "Tan", 0xD2B48C },
1266 { "Teal", 0x008080 },
1267 { "Thistle", 0xD8BFD8 },
1268 { "Tomato", 0xFF6347 },
1269 { "Turquoise", 0x40E0D0 },
1270 { "Violet", 0xEE82EE },
1271 { "Wheat", 0xF5DEB3 },
1272 { "White", 0xFFFFFF },
1273 { "WhiteSmoke", 0xF5F5F5 },
1274 { "Yellow", 0xFFFF00 },
1275 { "YellowGreen", 0x9ACD32 },
1280 static int HandleFontAttributes( xml_reader_t
*p_xml_reader
,
1281 font_stack_t
**p_fonts
)
1284 char *psz_fontname
= NULL
;
1285 uint32_t i_font_color
= 0xffffff;
1286 int i_font_alpha
= 255;
1287 uint32_t i_karaoke_bg_color
= 0x00ffffff;
1288 int i_font_size
= 24;
1290 /* Default all attributes to the top font in the stack -- in case not
1291 * all attributes are specified in the sub-font
1293 if( VLC_SUCCESS
== PeekFont( p_fonts
,
1297 &i_karaoke_bg_color
))
1299 psz_fontname
= strdup( psz_fontname
);
1300 i_font_size
= i_font_size
;
1302 i_font_alpha
= (i_font_color
>> 24) & 0xff;
1303 i_font_color
&= 0x00ffffff;
1305 const char *name
, *value
;
1306 while( (name
= xml_ReaderNextAttr( p_xml_reader
, &value
)) != NULL
)
1308 if( !strcasecmp( "face", name
) )
1310 free( psz_fontname
);
1311 psz_fontname
= strdup( value
);
1313 else if( !strcasecmp( "size", name
) )
1315 if( ( *value
== '+' ) || ( *value
== '-' ) )
1317 int i_value
= atoi( value
);
1319 if( ( i_value
>= -5 ) && ( i_value
<= 5 ) )
1320 i_font_size
+= ( i_value
* i_font_size
) / 10;
1321 else if( i_value
< -5 )
1322 i_font_size
= - i_value
;
1323 else if( i_value
> 5 )
1324 i_font_size
= i_value
;
1327 i_font_size
= atoi( value
);
1329 else if( !strcasecmp( "color", name
) )
1331 if( value
[0] == '#' )
1333 i_font_color
= strtol( value
+ 1, NULL
, 16 );
1334 i_font_color
&= 0x00ffffff;
1339 uint32_t i_value
= strtol( value
, &end
, 16 );
1340 if( *end
== '\0' || *end
== ' ' )
1341 i_font_color
= i_value
& 0x00ffffff;
1343 for( int i
= 0; p_html_colors
[i
].psz_name
!= NULL
; i
++ )
1345 if( !strncasecmp( value
, p_html_colors
[i
].psz_name
, strlen(p_html_colors
[i
].psz_name
) ) )
1347 i_font_color
= p_html_colors
[i
].i_value
;
1353 else if( !strcasecmp( "alpha", name
) && ( value
[0] == '#' ) )
1355 i_font_alpha
= strtol( value
+ 1, NULL
, 16 );
1356 i_font_alpha
&= 0xff;
1359 rv
= PushFont( p_fonts
,
1362 (i_font_color
& 0xffffff) | ((i_font_alpha
& 0xff) << 24),
1363 i_karaoke_bg_color
);
1365 free( psz_fontname
);
1370 /* Turn any multiple-whitespaces into single spaces */
1371 static void HandleWhiteSpace( char *psz_node
)
1373 char *s
= strpbrk( psz_node
, "\t\r\n " );
1376 int i_whitespace
= strspn( s
, "\t\r\n " );
1378 if( i_whitespace
> 1 )
1381 strlen( s
) - i_whitespace
+ 1 );
1384 s
= strpbrk( s
, "\t\r\n " );
1389 static text_style_t
*GetStyleFromFontStack( filter_sys_t
*p_sys
,
1390 font_stack_t
**p_fonts
,
1393 char *psz_fontname
= NULL
;
1394 uint32_t i_font_color
= p_sys
->i_font_color
& 0x00ffffff;
1395 uint32_t i_karaoke_bg_color
= i_font_color
;
1396 int i_font_size
= p_sys
->i_font_size
;
1398 if( PeekFont( p_fonts
, &psz_fontname
, &i_font_size
,
1399 &i_font_color
, &i_karaoke_bg_color
) )
1402 return CreateStyle( psz_fontname
, i_font_size
, i_font_color
,
1407 static unsigned SetupText( filter_t
*p_filter
,
1408 uint32_t *psz_text_out
,
1409 text_style_t
**pp_styles
,
1410 uint32_t *pi_k_dates
,
1412 const char *psz_text_in
,
1413 text_style_t
*p_style
,
1416 size_t i_string_length
;
1418 size_t i_string_bytes
;
1419 #if defined(WORDS_BIGENDIAN)
1420 uint32_t *psz_tmp
= ToCharset( "UCS-4BE", psz_text_in
, &i_string_bytes
);
1422 uint32_t *psz_tmp
= ToCharset( "UCS-4LE", psz_text_in
, &i_string_bytes
);
1426 memcpy( psz_text_out
, psz_tmp
, i_string_bytes
);
1427 i_string_length
= i_string_bytes
/ 4;
1432 msg_Warn( p_filter
, "failed to convert string to unicode (%m)" );
1433 i_string_length
= 0;
1436 if( i_string_length
> 0 )
1438 for( unsigned i
= 0; i
< i_string_length
; i
++ )
1439 pp_styles
[i
] = p_style
;
1443 text_style_Delete( p_style
);
1445 if( i_string_length
> 0 && pi_k_dates
)
1447 for( unsigned i
= 0; i
< i_string_length
; i
++ )
1448 pi_k_dates
[i
] = i_k_date
;
1450 return i_string_length
;
1453 static int ProcessNodes( filter_t
*p_filter
,
1455 text_style_t
**pp_styles
,
1456 uint32_t *pi_k_dates
,
1458 xml_reader_t
*p_xml_reader
,
1459 text_style_t
*p_font_style
)
1461 int rv
= VLC_SUCCESS
;
1462 filter_sys_t
*p_sys
= p_filter
->p_sys
;
1463 int i_text_length
= 0;
1464 font_stack_t
*p_fonts
= NULL
;
1465 uint32_t i_k_date
= 0;
1467 int i_style_flags
= 0;
1471 rv
= PushFont( &p_fonts
,
1472 p_font_style
->psz_fontname
,
1473 p_font_style
->i_font_size
> 0 ? p_font_style
->i_font_size
1474 : p_sys
->i_font_size
,
1475 (p_font_style
->i_font_color
& 0xffffff) |
1476 ((p_font_style
->i_font_alpha
& 0xff) << 24),
1477 (p_font_style
->i_karaoke_background_color
& 0xffffff) |
1478 ((p_font_style
->i_karaoke_background_alpha
& 0xff) << 24));
1480 i_style_flags
= p_font_style
->i_style_flags
& (STYLE_BOLD
|
1488 rv
= PushFont( &p_fonts
,
1489 p_sys
->psz_fontfamily
,
1491 (p_sys
->i_font_color
& 0xffffff) |
1492 ((p_sys
->i_font_opacity
& 0xff) << 24),
1496 if( p_sys
->b_font_bold
)
1497 i_style_flags
|= STYLE_BOLD
;
1499 if( rv
!= VLC_SUCCESS
)
1505 while ( (type
= xml_ReaderNextNode( p_xml_reader
, &node
)) > 0 )
1509 case XML_READER_ENDELEM
:
1510 if( !strcasecmp( "font", node
) )
1511 PopFont( &p_fonts
);
1512 else if( !strcasecmp( "b", node
) )
1513 i_style_flags
&= ~STYLE_BOLD
;
1514 else if( !strcasecmp( "i", node
) )
1515 i_style_flags
&= ~STYLE_ITALIC
;
1516 else if( !strcasecmp( "u", node
) )
1517 i_style_flags
&= ~STYLE_UNDERLINE
;
1518 else if( !strcasecmp( "s", node
) )
1519 i_style_flags
&= ~STYLE_STRIKEOUT
;
1522 case XML_READER_STARTELEM
:
1523 if( !strcasecmp( "font", node
) )
1524 HandleFontAttributes( p_xml_reader
, &p_fonts
);
1525 else if( !strcasecmp( "b", node
) )
1526 i_style_flags
|= STYLE_BOLD
;
1527 else if( !strcasecmp( "i", node
) )
1528 i_style_flags
|= STYLE_ITALIC
;
1529 else if( !strcasecmp( "u", node
) )
1530 i_style_flags
|= STYLE_UNDERLINE
;
1531 else if( !strcasecmp( "s", node
) )
1532 i_style_flags
|= STYLE_STRIKEOUT
;
1533 else if( !strcasecmp( "br", node
) )
1535 i_text_length
+= SetupText( p_filter
,
1536 &psz_text
[i_text_length
],
1537 &pp_styles
[i_text_length
],
1538 pi_k_dates
? &pi_k_dates
[i_text_length
] : NULL
,
1540 GetStyleFromFontStack( p_sys
,
1545 else if( !strcasecmp( "k", node
) )
1548 const char *name
, *value
;
1549 while( (name
= xml_ReaderNextAttr( p_xml_reader
, &value
)) != NULL
)
1551 if( !strcasecmp( "t", name
) && value
)
1552 i_k_date
+= atoi( value
);
1557 case XML_READER_TEXT
:
1559 char *psz_node
= strdup( node
);
1560 if( unlikely(!psz_node
) )
1563 HandleWhiteSpace( psz_node
);
1564 resolve_xml_special_chars( psz_node
);
1566 i_text_length
+= SetupText( p_filter
,
1567 &psz_text
[i_text_length
],
1568 &pp_styles
[i_text_length
],
1569 pi_k_dates
? &pi_k_dates
[i_text_length
] : NULL
,
1571 GetStyleFromFontStack( p_sys
,
1581 *pi_len
= i_text_length
;
1583 while( VLC_SUCCESS
== PopFont( &p_fonts
) );
1588 static void FreeLine( line_desc_t
*p_line
)
1590 for( int i
= 0; i
< p_line
->i_character_count
; i
++ )
1592 line_character_t
*ch
= &p_line
->p_character
[i
];
1593 FT_Done_Glyph( (FT_Glyph
)ch
->p_glyph
);
1595 FT_Done_Glyph( (FT_Glyph
)ch
->p_outline
);
1597 FT_Done_Glyph( (FT_Glyph
)ch
->p_shadow
);
1600 free( p_line
->p_character
);
1604 static void FreeLines( line_desc_t
*p_lines
)
1606 for( line_desc_t
*p_line
= p_lines
; p_line
!= NULL
; )
1608 line_desc_t
*p_next
= p_line
->p_next
;
1614 static line_desc_t
*NewLine( int i_count
)
1616 line_desc_t
*p_line
= malloc( sizeof(*p_line
) );
1621 p_line
->p_next
= NULL
;
1622 p_line
->i_width
= 0;
1623 p_line
->i_base_line
= 0;
1624 p_line
->i_character_count
= 0;
1626 p_line
->p_character
= calloc( i_count
, sizeof(*p_line
->p_character
) );
1627 if( !p_line
->p_character
)
1635 static FT_Face
LoadEmbeddedFace( filter_sys_t
*p_sys
, const text_style_t
*p_style
)
1637 for( int k
= 0; k
< p_sys
->i_font_attachments
; k
++ )
1639 input_attachment_t
*p_attach
= p_sys
->pp_font_attachments
[k
];
1641 FT_Face p_face
= NULL
;
1643 while( 0 == FT_New_Memory_Face( p_sys
->p_library
,
1651 int i_style_received
= ((p_face
->style_flags
& FT_STYLE_FLAG_BOLD
) ? STYLE_BOLD
: 0) |
1652 ((p_face
->style_flags
& FT_STYLE_FLAG_ITALIC
) ? STYLE_ITALIC
: 0);
1653 if( !strcasecmp( p_face
->family_name
, p_style
->psz_fontname
) &&
1654 (p_style
->i_style_flags
& (STYLE_BOLD
| STYLE_BOLD
)) == i_style_received
)
1657 FT_Done_Face( p_face
);
1665 static FT_Face
LoadFace( filter_t
*p_filter
,
1666 const text_style_t
*p_style
)
1668 filter_sys_t
*p_sys
= p_filter
->p_sys
;
1670 /* Look for a match amongst our attachments first */
1671 FT_Face p_face
= LoadEmbeddedFace( p_sys
, p_style
);
1673 /* Load system wide font otheriwse */
1678 #ifdef HAVE_FONTCONFIG
1679 psz_fontfile
= FontConfig_Select( NULL
,
1680 p_style
->psz_fontname
,
1681 (p_style
->i_style_flags
& STYLE_BOLD
) != 0,
1682 (p_style
->i_style_flags
& STYLE_ITALIC
) != 0,
1685 #elif defined( WIN32 )
1686 psz_fontfile
= Win32_Select( p_filter
,
1687 p_style
->psz_fontname
,
1688 (p_style
->i_style_flags
& STYLE_BOLD
) != 0,
1689 (p_style
->i_style_flags
& STYLE_ITALIC
) != 0,
1693 psz_fontfile
= NULL
;
1698 if( *psz_fontfile
== '\0' )
1701 "We were not able to find a matching font: \"%s\" (%s %s),"
1702 " so using default font",
1703 p_style
->psz_fontname
,
1704 (p_style
->i_style_flags
& STYLE_BOLD
) ? "Bold" : "",
1705 (p_style
->i_style_flags
& STYLE_ITALIC
) ? "Italic" : "" );
1710 if( FT_New_Face( p_sys
->p_library
, psz_fontfile
, i_idx
, &p_face
) )
1713 free( psz_fontfile
);
1718 if( FT_Select_Charmap( p_face
, ft_encoding_unicode
) )
1720 /* We've loaded a font face which is unhelpful for actually
1721 * rendering text - fallback to the default one.
1723 FT_Done_Face( p_face
);
1729 static bool FaceStyleEquals( const text_style_t
*p_style1
,
1730 const text_style_t
*p_style2
)
1732 if( !p_style1
|| !p_style2
)
1734 if( p_style1
== p_style2
)
1737 const int i_style_mask
= STYLE_BOLD
| STYLE_ITALIC
;
1738 return (p_style1
->i_style_flags
& i_style_mask
) == (p_style2
->i_style_flags
& i_style_mask
) &&
1739 !strcmp( p_style1
->psz_fontname
, p_style2
->psz_fontname
);
1742 static int GetGlyph( filter_t
*p_filter
,
1743 FT_Glyph
*pp_glyph
, FT_BBox
*p_glyph_bbox
,
1744 FT_Glyph
*pp_outline
, FT_BBox
*p_outline_bbox
,
1745 FT_Glyph
*pp_shadow
, FT_BBox
*p_shadow_bbox
,
1751 FT_Vector
*p_pen_shadow
)
1753 if( FT_Load_Glyph( p_face
, i_glyph_index
, FT_LOAD_NO_BITMAP
| FT_LOAD_DEFAULT
) &&
1754 FT_Load_Glyph( p_face
, i_glyph_index
, FT_LOAD_DEFAULT
) )
1756 msg_Err( p_filter
, "unable to render text FT_Load_Glyph failed" );
1757 return VLC_EGENERIC
;
1760 /* Do synthetic styling now that Freetype supports it;
1761 * ie. if the font we have loaded is NOT already in the
1762 * style that the tags want, then switch it on; if they
1763 * are then don't. */
1764 if ((i_style_flags
& STYLE_BOLD
) && !(p_face
->style_flags
& FT_STYLE_FLAG_BOLD
))
1765 FT_GlyphSlot_Embolden( p_face
->glyph
);
1766 if ((i_style_flags
& STYLE_ITALIC
) && !(p_face
->style_flags
& FT_STYLE_FLAG_ITALIC
))
1767 FT_GlyphSlot_Oblique( p_face
->glyph
);
1770 if( FT_Get_Glyph( p_face
->glyph
, &glyph
) )
1772 msg_Err( p_filter
, "unable to render text FT_Get_Glyph failed" );
1773 return VLC_EGENERIC
;
1776 FT_Glyph outline
= NULL
;
1777 if( p_filter
->p_sys
->p_stroker
)
1780 if( FT_Glyph_StrokeBorder( &outline
, p_filter
->p_sys
->p_stroker
, 0, 0 ) )
1784 FT_Glyph shadow
= NULL
;
1785 if( p_filter
->p_sys
->i_shadow_opacity
> 0 )
1787 shadow
= outline
? outline
: glyph
;
1788 if( FT_Glyph_To_Bitmap( &shadow
, FT_RENDER_MODE_NORMAL
, p_pen_shadow
, 0 ) )
1794 FT_Glyph_Get_CBox( shadow
, ft_glyph_bbox_pixels
, p_shadow_bbox
);
1797 *pp_shadow
= shadow
;
1799 if( FT_Glyph_To_Bitmap( &glyph
, FT_RENDER_MODE_NORMAL
, p_pen
, 1) )
1801 FT_Done_Glyph( glyph
);
1803 FT_Done_Glyph( outline
);
1805 FT_Done_Glyph( shadow
);
1806 return VLC_EGENERIC
;
1808 FT_Glyph_Get_CBox( glyph
, ft_glyph_bbox_pixels
, p_glyph_bbox
);
1813 FT_Glyph_To_Bitmap( &outline
, FT_RENDER_MODE_NORMAL
, p_pen
, 1 );
1814 FT_Glyph_Get_CBox( outline
, ft_glyph_bbox_pixels
, p_outline_bbox
);
1816 *pp_outline
= outline
;
1821 static void FixGlyph( FT_Glyph glyph
, FT_BBox
*p_bbox
, FT_Face face
, const FT_Vector
*p_pen
)
1823 FT_BitmapGlyph glyph_bmp
= (FT_BitmapGlyph
)glyph
;
1824 if( p_bbox
->xMin
>= p_bbox
->xMax
)
1826 p_bbox
->xMin
= FT_CEIL(p_pen
->x
);
1827 p_bbox
->xMax
= FT_CEIL(p_pen
->x
+ face
->glyph
->advance
.x
);
1828 glyph_bmp
->left
= p_bbox
->xMin
;
1830 if( p_bbox
->yMin
>= p_bbox
->yMax
)
1832 p_bbox
->yMax
= FT_CEIL(p_pen
->y
);
1833 p_bbox
->yMin
= FT_CEIL(p_pen
->y
+ face
->glyph
->advance
.y
);
1834 glyph_bmp
->top
= p_bbox
->yMax
;
1838 static void BBoxEnlarge( FT_BBox
*p_max
, const FT_BBox
*p
)
1840 p_max
->xMin
= __MIN(p_max
->xMin
, p
->xMin
);
1841 p_max
->yMin
= __MIN(p_max
->yMin
, p
->yMin
);
1842 p_max
->xMax
= __MAX(p_max
->xMax
, p
->xMax
);
1843 p_max
->yMax
= __MAX(p_max
->yMax
, p
->yMax
);
1846 static int ProcessLines( filter_t
*p_filter
,
1847 line_desc_t
**pp_lines
,
1849 int *pi_max_face_height
,
1852 text_style_t
**pp_styles
,
1853 uint32_t *pi_k_dates
,
1856 filter_sys_t
*p_sys
= p_filter
->p_sys
;
1857 uint32_t *p_fribidi_string
= NULL
;
1858 text_style_t
**pp_fribidi_styles
= NULL
;
1859 int *p_new_positions
= NULL
;
1861 #if defined(HAVE_FRIBIDI)
1863 int *p_old_positions
;
1864 int start_pos
, pos
= 0;
1866 pp_fribidi_styles
= calloc( i_len
, sizeof(*pp_fribidi_styles
) );
1868 p_fribidi_string
= malloc( (i_len
+ 1) * sizeof(*p_fribidi_string
) );
1869 p_old_positions
= malloc( (i_len
+ 1) * sizeof(*p_old_positions
) );
1870 p_new_positions
= malloc( (i_len
+ 1) * sizeof(*p_new_positions
) );
1872 if( ! pp_fribidi_styles
||
1873 ! p_fribidi_string
||
1874 ! p_old_positions
||
1877 free( p_old_positions
);
1878 free( p_new_positions
);
1879 free( p_fribidi_string
);
1880 free( pp_fribidi_styles
);
1884 /* Do bidi conversion line-by-line */
1887 while(pos
< i_len
) {
1888 if (psz_text
[pos
] != '\n')
1890 p_fribidi_string
[pos
] = psz_text
[pos
];
1891 pp_fribidi_styles
[pos
] = pp_styles
[pos
];
1892 p_new_positions
[pos
] = pos
;
1896 while(pos
< i_len
) {
1897 if (psz_text
[pos
] == '\n')
1901 if (pos
> start_pos
)
1903 #if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
1904 FriBidiCharType base_dir
= FRIBIDI_TYPE_LTR
;
1906 FriBidiParType base_dir
= FRIBIDI_PAR_LTR
;
1908 fribidi_log2vis((FriBidiChar
*)psz_text
+ start_pos
,
1909 pos
- start_pos
, &base_dir
,
1910 (FriBidiChar
*)p_fribidi_string
+ start_pos
,
1911 p_new_positions
+ start_pos
,
1914 for( int j
= start_pos
; j
< pos
; j
++ )
1916 pp_fribidi_styles
[ j
] = pp_styles
[ start_pos
+ p_old_positions
[j
- start_pos
] ];
1917 p_new_positions
[ j
] += start_pos
;
1921 p_fribidi_string
[ i_len
] = 0;
1922 free( p_old_positions
);
1924 pp_styles
= pp_fribidi_styles
;
1925 psz_text
= p_fribidi_string
;
1928 /* Work out the karaoke */
1929 uint8_t *pi_karaoke_bar
= NULL
;
1932 pi_karaoke_bar
= malloc( i_len
* sizeof(*pi_karaoke_bar
));
1933 if( pi_karaoke_bar
)
1935 int64_t i_elapsed
= var_GetTime( p_filter
, "spu-elapsed" ) / 1000;
1936 for( int i
= 0; i
< i_len
; i
++ )
1938 unsigned i_bar
= p_new_positions
? p_new_positions
[i
] : i
;
1939 pi_karaoke_bar
[i_bar
] = pi_k_dates
[i
] >= i_elapsed
;
1943 free( p_new_positions
);
1945 *pi_max_face_height
= 0;
1947 line_desc_t
**pp_line_next
= pp_lines
;
1955 int i_face_height_previous
= 0;
1956 int i_base_line
= 0;
1957 const text_style_t
*p_previous_style
= NULL
;
1958 FT_Face p_face
= NULL
;
1959 for( int i_start
= 0; i_start
< i_len
; )
1961 /* Compute the length of the current text line */
1963 while( i_start
+ i_length
< i_len
&& psz_text
[i_start
+ i_length
] != '\n' )
1966 /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
1967 line_desc_t
*p_line
= i_length
> 0 ? NewLine( i_length
) : NULL
;
1968 int i_index
= i_start
;
1973 int i_face_height
= 0;
1974 FT_BBox line_bbox
= {
1980 int i_ul_offset
= 0;
1981 int i_ul_thickness
= 0;
1990 break_point_t break_point
;
1991 break_point_t break_point_fallback
;
1993 #define SAVE_BP(dst) do { \
1994 dst.i_index = i_index; \
1996 dst.line_bbox = line_bbox; \
1997 dst.i_face_height = i_face_height; \
1998 dst.i_ul_offset = i_ul_offset; \
1999 dst.i_ul_thickness = i_ul_thickness; \
2002 SAVE_BP( break_point
);
2003 SAVE_BP( break_point_fallback
);
2005 while( i_index
< i_start
+ i_length
)
2007 /* Split by common FT_Face + Size */
2008 const text_style_t
*p_current_style
= pp_styles
[i_index
];
2009 int i_part_length
= 0;
2010 while( i_index
+ i_part_length
< i_start
+ i_length
)
2012 const text_style_t
*p_style
= pp_styles
[i_index
+ i_part_length
];
2013 if( !FaceStyleEquals( p_style
, p_current_style
) ||
2014 p_style
->i_font_size
!= p_current_style
->i_font_size
)
2019 /* (Re)load/reconfigure the face if needed */
2020 if( !FaceStyleEquals( p_current_style
, p_previous_style
) )
2023 FT_Done_Face( p_face
);
2024 p_previous_style
= NULL
;
2026 p_face
= LoadFace( p_filter
, p_current_style
);
2028 FT_Face p_current_face
= p_face
? p_face
: p_sys
->p_face
;
2029 if( !p_previous_style
|| p_previous_style
->i_font_size
!= p_current_style
->i_font_size
)
2031 if( FT_Set_Pixel_Sizes( p_current_face
, 0, p_current_style
->i_font_size
) )
2032 msg_Err( p_filter
, "Failed to set font size to %d", p_current_style
->i_font_size
);
2033 if( p_sys
->p_stroker
)
2035 int i_radius
= (p_current_style
->i_font_size
<< 6) * p_sys
->f_outline_thickness
;
2036 FT_Stroker_Set( p_sys
->p_stroker
,
2038 FT_STROKER_LINECAP_ROUND
,
2039 FT_STROKER_LINEJOIN_ROUND
, 0 );
2042 p_previous_style
= p_current_style
;
2044 i_face_height
= __MAX(i_face_height
, FT_CEIL(FT_MulFix(p_current_face
->height
,
2045 p_current_face
->size
->metrics
.y_scale
)));
2047 /* Render the part */
2048 bool b_break_line
= false;
2049 int i_glyph_last
= 0;
2050 while( i_part_length
> 0 )
2052 const text_style_t
*p_glyph_style
= pp_styles
[i_index
];
2053 uint32_t character
= psz_text
[i_index
];
2054 int i_glyph_index
= FT_Get_Char_Index( p_current_face
, character
);
2056 /* Get kerning vector */
2057 FT_Vector kerning
= { .x
= 0, .y
= 0 };
2058 if( FT_HAS_KERNING( p_current_face
) && i_glyph_last
!= 0 && i_glyph_index
!= 0 )
2059 FT_Get_Kerning( p_current_face
, i_glyph_last
, i_glyph_index
, ft_kerning_default
, &kerning
);
2061 /* Get the glyph bitmap and its bounding box and all the associated properties */
2062 FT_Vector pen_new
= {
2063 .x
= pen
.x
+ kerning
.x
,
2064 .y
= pen
.y
+ kerning
.y
,
2066 FT_Vector pen_shadow_new
= {
2067 .x
= pen_new
.x
+ p_sys
->f_shadow_vector_x
* (p_current_style
->i_font_size
<< 6),
2068 .y
= pen_new
.y
+ p_sys
->f_shadow_vector_y
* (p_current_style
->i_font_size
<< 6),
2073 FT_BBox outline_bbox
;
2075 FT_BBox shadow_bbox
;
2077 if( GetGlyph( p_filter
,
2078 &glyph
, &glyph_bbox
,
2079 &outline
, &outline_bbox
,
2080 &shadow
, &shadow_bbox
,
2081 p_current_face
, i_glyph_index
, p_glyph_style
->i_style_flags
,
2082 &pen_new
, &pen_shadow_new
) )
2085 FixGlyph( glyph
, &glyph_bbox
, p_current_face
, &pen_new
);
2087 FixGlyph( outline
, &outline_bbox
, p_current_face
, &pen_new
);
2089 FixGlyph( shadow
, &shadow_bbox
, p_current_face
, &pen_shadow_new
);
2091 /* FIXME and what about outline */
2093 bool b_karaoke
= pi_karaoke_bar
&& pi_karaoke_bar
[i_index
] != 0;
2094 uint32_t i_color
= b_karaoke
? (p_glyph_style
->i_karaoke_background_color
|
2095 (p_glyph_style
->i_karaoke_background_alpha
<< 24))
2096 : (p_glyph_style
->i_font_color
|
2097 (p_glyph_style
->i_font_alpha
<< 24));
2098 int i_line_offset
= 0;
2099 int i_line_thickness
= 0;
2100 if( p_glyph_style
->i_style_flags
& (STYLE_UNDERLINE
| STYLE_STRIKEOUT
) )
2102 i_line_offset
= abs( FT_FLOOR(FT_MulFix(p_current_face
->underline_position
,
2103 p_current_face
->size
->metrics
.y_scale
)) );
2105 i_line_thickness
= abs( FT_CEIL(FT_MulFix(p_current_face
->underline_thickness
,
2106 p_current_face
->size
->metrics
.y_scale
)) );
2108 if( p_glyph_style
->i_style_flags
& STYLE_STRIKEOUT
)
2110 /* Move the baseline to make it strikethrough instead of
2111 * underline. That means that strikethrough takes precedence
2113 i_line_offset
-= abs( FT_FLOOR(FT_MulFix(p_current_face
->descender
*2,
2114 p_current_face
->size
->metrics
.y_scale
)) );
2116 else if( i_line_thickness
> 0 )
2118 glyph_bbox
.yMin
= __MIN( glyph_bbox
.yMin
, - i_line_offset
- i_line_thickness
);
2120 /* The real underline thickness and position are
2121 * updated once the whole line has been parsed */
2122 i_ul_offset
= __MAX( i_ul_offset
, i_line_offset
);
2123 i_ul_thickness
= __MAX( i_ul_thickness
, i_line_thickness
);
2124 i_line_thickness
= -1;
2127 FT_BBox line_bbox_new
= line_bbox
;
2128 BBoxEnlarge( &line_bbox_new
, &glyph_bbox
);
2130 BBoxEnlarge( &line_bbox_new
, &outline_bbox
);
2132 BBoxEnlarge( &line_bbox_new
, &shadow_bbox
);
2134 b_break_line
= i_index
> i_start
&&
2135 line_bbox_new
.xMax
- line_bbox_new
.xMin
>= (int)p_filter
->fmt_out
.video
.i_visible_width
;
2138 FT_Done_Glyph( glyph
);
2140 FT_Done_Glyph( outline
);
2142 FT_Done_Glyph( shadow
);
2144 break_point_t
*p_bp
= NULL
;
2145 if( break_point
.i_index
> i_start
)
2146 p_bp
= &break_point
;
2147 else if( break_point_fallback
.i_index
> i_start
)
2148 p_bp
= &break_point_fallback
;
2152 msg_Dbg( p_filter
, "Breaking line");
2153 for( int i
= p_bp
->i_index
; i
< i_index
; i
++ )
2155 line_character_t
*ch
= &p_line
->p_character
[i
- i_start
];
2156 FT_Done_Glyph( (FT_Glyph
)ch
->p_glyph
);
2158 FT_Done_Glyph( (FT_Glyph
)ch
->p_outline
);
2160 FT_Done_Glyph( (FT_Glyph
)ch
->p_shadow
);
2162 p_line
->i_character_count
= p_bp
->i_index
- i_start
;
2164 i_index
= p_bp
->i_index
;
2166 line_bbox
= p_bp
->line_bbox
;
2167 i_face_height
= p_bp
->i_face_height
;
2168 i_ul_offset
= p_bp
->i_ul_offset
;
2169 i_ul_thickness
= p_bp
->i_ul_thickness
;
2173 msg_Err( p_filter
, "Breaking unbreakable line");
2178 assert( p_line
->i_character_count
== i_index
- i_start
);
2179 p_line
->p_character
[p_line
->i_character_count
++] = (line_character_t
){
2180 .p_glyph
= (FT_BitmapGlyph
)glyph
,
2181 .p_outline
= (FT_BitmapGlyph
)outline
,
2182 .p_shadow
= (FT_BitmapGlyph
)shadow
,
2184 .i_line_offset
= i_line_offset
,
2185 .i_line_thickness
= i_line_thickness
,
2188 pen
.x
= pen_new
.x
+ p_current_face
->glyph
->advance
.x
;
2189 pen
.y
= pen_new
.y
+ p_current_face
->glyph
->advance
.y
;
2190 line_bbox
= line_bbox_new
;
2192 i_glyph_last
= i_glyph_index
;
2196 if( character
== ' ' || character
== '\t' )
2197 SAVE_BP( break_point
);
2198 else if( character
== 160 )
2199 SAVE_BP( break_point_fallback
);
2205 /* Update our baseline */
2206 if( i_face_height_previous
> 0 )
2207 i_base_line
+= __MAX(i_face_height
, i_face_height_previous
);
2208 if( i_face_height
> 0 )
2209 i_face_height_previous
= i_face_height
;
2211 /* Update the line bbox with the actual base line */
2212 if (line_bbox
.yMax
> line_bbox
.yMin
) {
2213 line_bbox
.yMin
-= i_base_line
;
2214 line_bbox
.yMax
-= i_base_line
;
2216 BBoxEnlarge( &bbox
, &line_bbox
);
2218 /* Terminate and append the line */
2221 p_line
->i_width
= __MAX(line_bbox
.xMax
- line_bbox
.xMin
, 0);
2222 p_line
->i_base_line
= i_base_line
;
2223 if( i_ul_thickness
> 0 )
2225 for( int i
= 0; i
< p_line
->i_character_count
; i
++ )
2227 line_character_t
*ch
= &p_line
->p_character
[i
];
2228 if( ch
->i_line_thickness
< 0 )
2230 ch
->i_line_offset
= i_ul_offset
;
2231 ch
->i_line_thickness
= i_ul_thickness
;
2236 *pp_line_next
= p_line
;
2237 pp_line_next
= &p_line
->p_next
;
2240 *pi_max_face_height
= __MAX( *pi_max_face_height
, i_face_height
);
2242 /* Skip what we have rendered and the line delimitor if present */
2244 if( i_start
< i_len
&& psz_text
[i_start
] == '\n' )
2247 if( bbox
.yMax
- bbox
.yMin
>= (int)p_filter
->fmt_out
.video
.i_visible_height
)
2249 msg_Err( p_filter
, "Truncated too high subtitle" );
2254 FT_Done_Face( p_face
);
2256 free( pp_fribidi_styles
);
2257 free( p_fribidi_string
);
2258 free( pi_karaoke_bar
);
2265 * This function renders a text subpicture region into another one.
2266 * It also calculates the size needed for this string, and renders the
2267 * needed glyphs into memory. It is used as pf_add_string callback in
2268 * the vout method by this module
2270 static int RenderCommon( filter_t
*p_filter
, subpicture_region_t
*p_region_out
,
2271 subpicture_region_t
*p_region_in
, bool b_html
,
2272 const vlc_fourcc_t
*p_chroma_list
)
2274 filter_sys_t
*p_sys
= p_filter
->p_sys
;
2277 return VLC_EGENERIC
;
2278 if( b_html
&& !p_region_in
->psz_html
)
2279 return VLC_EGENERIC
;
2280 if( !b_html
&& !p_region_in
->psz_text
)
2281 return VLC_EGENERIC
;
2283 const size_t i_text_max
= strlen( b_html
? p_region_in
->psz_html
2284 : p_region_in
->psz_text
);
2286 uint32_t *psz_text
= calloc( i_text_max
, sizeof( *psz_text
) );
2287 text_style_t
**pp_styles
= calloc( i_text_max
, sizeof( *pp_styles
) );
2288 if( !psz_text
|| !pp_styles
)
2292 return VLC_EGENERIC
;
2295 /* Reset the default fontsize in case screen metrics have changed */
2296 p_filter
->p_sys
->i_font_size
= GetFontSize( p_filter
);
2299 int rv
= VLC_SUCCESS
;
2300 int i_text_length
= 0;
2302 int i_max_face_height
;
2303 line_desc_t
*p_lines
= NULL
;
2305 uint32_t *pi_k_durations
= NULL
;
2310 stream_t
*p_sub
= stream_MemoryNew( VLC_OBJECT(p_filter
),
2311 (uint8_t *) p_region_in
->psz_html
,
2312 strlen( p_region_in
->psz_html
),
2314 if( unlikely(p_sub
== NULL
) )
2317 xml_reader_t
*p_xml_reader
= p_filter
->p_sys
->p_xml
;
2319 p_xml_reader
= xml_ReaderCreate( p_filter
, p_sub
);
2321 p_xml_reader
= xml_ReaderReset( p_xml_reader
, p_sub
);
2322 p_filter
->p_sys
->p_xml
= p_xml_reader
;
2329 /* Look for Root Node */
2332 if( xml_ReaderNextNode( p_xml_reader
, &node
) == XML_READER_STARTELEM
)
2334 if( strcasecmp( "karaoke", node
) == 0 )
2336 pi_k_durations
= calloc( i_text_max
, sizeof( *pi_k_durations
) );
2338 else if( strcasecmp( "text", node
) != 0 )
2340 /* Only text and karaoke tags are supported */
2341 msg_Dbg( p_filter
, "Unsupported top-level tag <%s> ignored.",
2348 msg_Err( p_filter
, "Malformed HTML subtitle" );
2354 rv
= ProcessNodes( p_filter
,
2355 psz_text
, pp_styles
, pi_k_durations
, &i_text_length
,
2356 p_xml_reader
, p_region_in
->p_style
);
2360 p_filter
->p_sys
->p_xml
= xml_ReaderReset( p_xml_reader
, NULL
);
2362 stream_Delete( p_sub
);
2367 text_style_t
*p_style
;
2368 if( p_region_in
->p_style
)
2369 p_style
= CreateStyle( p_region_in
->p_style
->psz_fontname
,
2370 p_region_in
->p_style
->i_font_size
> 0 ? p_region_in
->p_style
->i_font_size
2371 : p_sys
->i_font_size
,
2372 (p_region_in
->p_style
->i_font_color
& 0xffffff) |
2373 ((p_region_in
->p_style
->i_font_alpha
& 0xff) << 24),
2375 p_region_in
->p_style
->i_style_flags
& (STYLE_BOLD
|
2380 p_style
= CreateStyle( p_sys
->psz_fontfamily
,
2382 (p_sys
->i_font_color
& 0xffffff) |
2383 ((p_sys
->i_font_opacity
& 0xff) << 24),
2385 if( p_sys
->b_font_bold
)
2386 p_style
->i_style_flags
|= STYLE_BOLD
;
2388 i_text_length
= SetupText( p_filter
,
2392 p_region_in
->psz_text
, p_style
, 0 );
2395 if( !rv
&& i_text_length
> 0 )
2397 rv
= ProcessLines( p_filter
,
2398 &p_lines
, &bbox
, &i_max_face_height
,
2399 psz_text
, pp_styles
, pi_k_durations
, i_text_length
);
2402 p_region_out
->i_x
= p_region_in
->i_x
;
2403 p_region_out
->i_y
= p_region_in
->i_y
;
2405 /* Don't attempt to render text that couldn't be layed out
2407 if( !rv
&& i_text_length
> 0 && bbox
.xMin
< bbox
.xMax
&& bbox
.yMin
< bbox
.yMax
)
2409 const vlc_fourcc_t p_chroma_list_yuvp
[] = { VLC_CODEC_YUVP
, 0 };
2410 const vlc_fourcc_t p_chroma_list_rgba
[] = { VLC_CODEC_RGBA
, 0 };
2412 if( var_InheritBool( p_filter
, "freetype-yuvp" ) )
2413 p_chroma_list
= p_chroma_list_yuvp
;
2414 else if( !p_chroma_list
|| *p_chroma_list
== 0 )
2415 p_chroma_list
= p_chroma_list_rgba
;
2417 const int i_margin
= p_sys
->i_background_opacity
> 0 ? i_max_face_height
/ 4 : 0;
2418 for( const vlc_fourcc_t
*p_chroma
= p_chroma_list
; *p_chroma
!= 0; p_chroma
++ )
2421 if( *p_chroma
== VLC_CODEC_YUVP
)
2422 rv
= RenderYUVP( p_filter
, p_region_out
, p_lines
, &bbox
);
2423 else if( *p_chroma
== VLC_CODEC_YUVA
)
2424 rv
= RenderAXYZ( p_filter
, p_region_out
, p_lines
, &bbox
, i_margin
,
2429 else if( *p_chroma
== VLC_CODEC_RGBA
)
2430 rv
= RenderAXYZ( p_filter
, p_region_out
, p_lines
, &bbox
, i_margin
,
2439 /* With karaoke, we're going to have to render the text a number
2440 * of times to show the progress marker on the text.
2442 if( pi_k_durations
)
2443 var_SetBool( p_filter
, "text-rerender", true );
2446 FreeLines( p_lines
);
2449 for( int i
= 0; i
< i_text_length
; i
++ )
2451 if( pp_styles
[i
] && ( i
+ 1 == i_text_length
|| pp_styles
[i
] != pp_styles
[i
+ 1] ) )
2452 text_style_Delete( pp_styles
[i
] );
2455 free( pi_k_durations
);
2460 static int RenderText( filter_t
*p_filter
, subpicture_region_t
*p_region_out
,
2461 subpicture_region_t
*p_region_in
,
2462 const vlc_fourcc_t
*p_chroma_list
)
2464 return RenderCommon( p_filter
, p_region_out
, p_region_in
, false, p_chroma_list
);
2469 static int RenderHtml( filter_t
*p_filter
, subpicture_region_t
*p_region_out
,
2470 subpicture_region_t
*p_region_in
,
2471 const vlc_fourcc_t
*p_chroma_list
)
2473 return RenderCommon( p_filter
, p_region_out
, p_region_in
, true, p_chroma_list
);
2478 /*****************************************************************************
2479 * Create: allocates osd-text video thread output method
2480 *****************************************************************************
2481 * This function allocates and initializes a Clone vout method.
2482 *****************************************************************************/
2483 static int Create( vlc_object_t
*p_this
)
2485 filter_t
*p_filter
= (filter_t
*)p_this
;
2486 filter_sys_t
*p_sys
;
2487 char *psz_fontfile
= NULL
;
2488 char *psz_fontfamily
= NULL
;
2489 int i_error
= 0, fontindex
= 0;
2491 /* Allocate structure */
2492 p_filter
->p_sys
= p_sys
= malloc( sizeof(*p_sys
) );
2496 p_sys
->psz_fontfamily
= NULL
;
2498 p_sys
->p_xml
= NULL
;
2501 p_sys
->p_library
= 0;
2502 p_sys
->i_font_size
= 0;
2503 p_sys
->i_display_height
= 0;
2505 var_Create( p_filter
, "freetype-rel-fontsize",
2506 VLC_VAR_INTEGER
| VLC_VAR_DOINHERIT
);
2508 psz_fontfamily
= var_InheritString( p_filter
, "freetype-font" );
2509 p_sys
->i_default_font_size
= var_InheritInteger( p_filter
, "freetype-fontsize" );
2510 p_sys
->i_font_opacity
= var_InheritInteger( p_filter
,"freetype-opacity" );
2511 p_sys
->i_font_opacity
= __MAX( __MIN( p_sys
->i_font_opacity
, 255 ), 0 );
2512 p_sys
->i_font_color
= var_InheritInteger( p_filter
, "freetype-color" );
2513 p_sys
->i_font_color
= __MAX( __MIN( p_sys
->i_font_color
, 0xFFFFFF ), 0 );
2514 p_sys
->b_font_bold
= var_InheritBool( p_filter
, "freetype-bold" );
2516 p_sys
->i_background_opacity
= var_InheritInteger( p_filter
,"freetype-background-opacity" );;
2517 p_sys
->i_background_opacity
= __MAX( __MIN( p_sys
->i_background_opacity
, 255 ), 0 );
2518 p_sys
->i_background_color
= var_InheritInteger( p_filter
, "freetype-background-color" );
2519 p_sys
->i_background_color
= __MAX( __MIN( p_sys
->i_background_color
, 0xFFFFFF ), 0 );
2521 p_sys
->f_outline_thickness
= var_InheritInteger( p_filter
, "freetype-outline-thickness" ) / 100.0;
2522 p_sys
->f_outline_thickness
= __MAX( __MIN( p_sys
->f_outline_thickness
, 0.5 ), 0.0 );
2523 p_sys
->i_outline_opacity
= var_InheritInteger( p_filter
, "freetype-outline-opacity" );
2524 p_sys
->i_outline_opacity
= __MAX( __MIN( p_sys
->i_outline_opacity
, 255 ), 0 );
2525 p_sys
->i_outline_color
= var_InheritInteger( p_filter
, "freetype-outline-color" );
2526 p_sys
->i_outline_color
= __MAX( __MIN( p_sys
->i_outline_color
, 0xFFFFFF ), 0 );
2528 p_sys
->i_shadow_opacity
= var_InheritInteger( p_filter
, "freetype-shadow-opacity" );
2529 p_sys
->i_shadow_opacity
= __MAX( __MIN( p_sys
->i_shadow_opacity
, 255 ), 0 );
2530 p_sys
->i_shadow_color
= var_InheritInteger( p_filter
, "freetype-shadow-color" );
2531 p_sys
->i_shadow_color
= __MAX( __MIN( p_sys
->i_shadow_color
, 0xFFFFFF ), 0 );
2532 float f_shadow_angle
= var_InheritFloat( p_filter
, "freetype-shadow-angle" );
2533 float f_shadow_distance
= var_InheritFloat( p_filter
, "freetype-shadow-distance" );
2534 f_shadow_distance
= __MAX( __MIN( f_shadow_distance
, 1 ), 0 );
2535 p_sys
->f_shadow_vector_x
= f_shadow_distance
* cos(2 * M_PI
* f_shadow_angle
/ 360);
2536 p_sys
->f_shadow_vector_y
= f_shadow_distance
* sin(2 * M_PI
* f_shadow_angle
/ 360);
2539 /* Get Windows Font folder */
2540 wchar_t wdir
[MAX_PATH
];
2541 if( S_OK
!= SHGetFolderPathW( NULL
, CSIDL_FONTS
, NULL
, SHGFP_TYPE_CURRENT
, wdir
) )
2543 GetWindowsDirectoryW( wdir
, MAX_PATH
);
2544 wcscat( wdir
, L
"\\fonts" );
2546 p_sys
->psz_win_fonts_path
= FromWide( wdir
);
2549 /* Set default psz_fontfamily */
2550 if( !psz_fontfamily
|| !*psz_fontfamily
)
2552 free( psz_fontfamily
);
2554 psz_fontfamily
= strdup( DEFAULT_FAMILY
);
2557 if( asprintf( &psz_fontfamily
, "%s"DEFAULT_FONT_FILE
, p_sys
->psz_win_fonts_path
) == -1 )
2560 psz_fontfamily
= strdup( DEFAULT_FONT_FILE
);
2562 msg_Err( p_filter
,"User specified an empty fontfile, using %s", psz_fontfamily
);
2566 /* Set the current font file */
2567 p_sys
->psz_fontfamily
= psz_fontfamily
;
2569 #ifdef HAVE_FONTCONFIG
2570 FontConfig_BuildCache( p_filter
);
2573 psz_fontfile
= FontConfig_Select( NULL
, psz_fontfamily
, false, false,
2574 p_sys
->i_default_font_size
, &fontindex
);
2575 #elif defined(WIN32)
2576 psz_fontfile
= Win32_Select( p_filter
, psz_fontfamily
, false, false,
2577 p_sys
->i_default_font_size
, &fontindex
);
2580 msg_Dbg( p_filter
, "Using %s as font from file %s", psz_fontfamily
, psz_fontfile
);
2582 /* If nothing is found, use the default family */
2584 psz_fontfile
= strdup( psz_fontfamily
);
2586 #else /* !HAVE_STYLES */
2587 /* Use the default file */
2588 psz_fontfile
= psz_fontfamily
;
2592 i_error
= FT_Init_FreeType( &p_sys
->p_library
);
2595 msg_Err( p_filter
, "couldn't initialize freetype" );
2599 i_error
= FT_New_Face( p_sys
->p_library
, psz_fontfile
? psz_fontfile
: "",
2600 fontindex
, &p_sys
->p_face
);
2602 if( i_error
== FT_Err_Unknown_File_Format
)
2604 msg_Err( p_filter
, "file %s have unknown format",
2605 psz_fontfile
? psz_fontfile
: "(null)" );
2610 msg_Err( p_filter
, "failed to load font file %s",
2611 psz_fontfile
? psz_fontfile
: "(null)" );
2615 i_error
= FT_Select_Charmap( p_sys
->p_face
, ft_encoding_unicode
);
2618 msg_Err( p_filter
, "font has no unicode translation table" );
2622 if( SetFontSize( p_filter
, 0 ) != VLC_SUCCESS
) goto error
;
2624 p_sys
->p_stroker
= NULL
;
2625 if( p_sys
->f_outline_thickness
> 0.001 )
2627 i_error
= FT_Stroker_New( p_sys
->p_library
, &p_sys
->p_stroker
);
2629 msg_Err( p_filter
, "Failed to create stroker for outlining" );
2632 p_sys
->pp_font_attachments
= NULL
;
2633 p_sys
->i_font_attachments
= 0;
2635 p_filter
->pf_render_text
= RenderText
;
2637 p_filter
->pf_render_html
= RenderHtml
;
2639 p_filter
->pf_render_html
= NULL
;
2642 LoadFontsFromAttachments( p_filter
);
2645 free( psz_fontfile
);
2651 if( p_sys
->p_face
) FT_Done_Face( p_sys
->p_face
);
2652 if( p_sys
->p_library
) FT_Done_FreeType( p_sys
->p_library
);
2654 free( psz_fontfile
);
2656 free( psz_fontfamily
);
2658 return VLC_EGENERIC
;
2661 /*****************************************************************************
2662 * Destroy: destroy Clone video thread output method
2663 *****************************************************************************
2664 * Clean up all data and library connections
2665 *****************************************************************************/
2666 static void Destroy( vlc_object_t
*p_this
)
2668 filter_t
*p_filter
= (filter_t
*)p_this
;
2669 filter_sys_t
*p_sys
= p_filter
->p_sys
;
2671 if( p_sys
->pp_font_attachments
)
2673 for( int k
= 0; k
< p_sys
->i_font_attachments
; k
++ )
2674 vlc_input_attachment_Delete( p_sys
->pp_font_attachments
[k
] );
2676 free( p_sys
->pp_font_attachments
);
2680 if( p_sys
->p_xml
) xml_ReaderDelete( p_sys
->p_xml
);
2682 free( p_sys
->psz_fontfamily
);
2684 /* FcFini asserts calling the subfunction FcCacheFini()
2685 * even if no other library functions have been made since FcInit(),
2686 * so don't call it. */
2688 if( p_sys
->p_stroker
)
2689 FT_Stroker_Done( p_sys
->p_stroker
);
2690 FT_Done_Face( p_sys
->p_face
);
2691 FT_Done_FreeType( p_sys
->p_library
);