From cf9e0c81162d09b7ee079d512436715fd243ffad Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kempf Date: Tue, 13 Mar 2007 21:28:34 +0000 Subject: [PATCH] MKV USF subtitles support and other Subtitles improvements. Patch by Bernie Purcell, with two small modifications by thedj. --- NEWS | 2 + THANKS | 1 + configure.ac | 3 + include/vlc_filter.h | 2 + include/vlc_vout.h | 1 + modules/codec/subsdec.c | 399 ++++++++++++- modules/demux/mkv.cpp | 11 + modules/misc/dummy/renderer.c | 1 + modules/misc/freetype.c | 882 +++++++++++++++++++++++++++- modules/misc/svg.c | 1 + modules/misc/win32text.c | 1 + src/video_output/vout_subpictures.c | 16 +- 12 files changed, 1303 insertions(+), 17 deletions(-) diff --git a/NEWS b/NEWS index 9d1ec831bb..ead7d737e6 100644 --- a/NEWS +++ b/NEWS @@ -35,6 +35,7 @@ Input/Demuxers: Decoders: * VP60/VP61 codecs support * Teletext subtitles (telx) support + * MKV USF subtitles support Video output: * Adjust, Invert and Distort (now splitted into Wave, Ripple, Gradient @@ -43,6 +44,7 @@ Video output: * Rewrite motion detection video filter * New extract video filter (extract Red, Green and Blue components from a video) * New sharpen video filter (increase the contrast of adjacent pixels) + * Enhancements to subtitles' renderer to support bold, italics and some HTML tags Stream output: * UDP-Lite (requires OS support) for RTP/TS encapsulation diff --git a/THANKS b/THANKS index 08e23a0da2..92cb4c85eb 100644 --- a/THANKS +++ b/THANKS @@ -30,6 +30,7 @@ Basil Achermann - Patch to handle esc and space key eve Barak Ori - Bidi fixes Benjamin Mironer - Mac OS X fixes Benoit Steiner - MPEG system input, network input +Bernie Purcell - MKV USF subtitles support,HTML tags for subtitles and subtitles renderer enhancements Bill - memleak fixes Bill Eldridge - documentation Bob Maguire - addition of some controls to the OSX interface diff --git a/configure.ac b/configure.ac index c34e1c655e..3bccc79b02 100644 --- a/configure.ac +++ b/configure.ac @@ -3948,6 +3948,9 @@ then VLC_ADD_PLUGINS([freetype]) VLC_ADD_CFLAGS([freetype],[`${FREETYPE_CONFIG} --cflags`]) VLC_ADD_LDFLAGS([freetype],[`${FREETYPE_CONFIG} --libs`]) + AC_CHECK_HEADERS(fontconfig/fontconfig.h, + [VLC_ADD_CFLAGS([freetype],[-DHAVE_FONTCONFIG]) + VLC_ADD_LDFLAGS([freetype],[-lfontconfig])]) AC_CHECK_HEADERS(Carbon/Carbon.h, [VLC_ADD_LDFLAGS([freetype],[-framework Carbon])]) elif test "${enable_freetype}" = "yes" diff --git a/include/vlc_filter.h b/include/vlc_filter.h index 4e420c24ee..fd6897bb22 100644 --- a/include/vlc_filter.h +++ b/include/vlc_filter.h @@ -68,6 +68,8 @@ struct filter_t subpicture_t * ( *pf_sub_filter ) ( filter_t *, mtime_t ); int ( *pf_render_text ) ( filter_t *, subpicture_region_t *, subpicture_region_t * ); + int ( *pf_render_html ) ( filter_t *, subpicture_region_t *, + subpicture_region_t * ); /* * Buffers allocation diff --git a/include/vlc_vout.h b/include/vlc_vout.h index baa6b1e1d0..8c5f5aa6c6 100644 --- a/include/vlc_vout.h +++ b/include/vlc_vout.h @@ -213,6 +213,7 @@ struct subpicture_region_t int i_align; /**< alignment within a region */ char *psz_text; /**< text string comprising this region */ + char *psz_html; /**< HTML version of subtitle (NULL = use psz_text) */ text_style_t *p_style; /* a description of the text style formatting */ subpicture_region_t *p_next; /**< next region in the list */ diff --git a/modules/codec/subsdec.c b/modules/codec/subsdec.c index 910833a0c5..be206da4b2 100644 --- a/modules/codec/subsdec.c +++ b/modules/codec/subsdec.c @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include @@ -70,7 +72,10 @@ static void CloseDecoder ( vlc_object_t * ); static subpicture_t *DecodeBlock ( decoder_t *, block_t ** ); static subpicture_t *ParseText ( decoder_t *, block_t * ); static void ParseSSAHeader ( decoder_t * ); +static void ParseUSFHeader ( decoder_t * ); +static void ParseUSFHeaderTags( decoder_sys_t *, xml_reader_t * ); static void ParseSSAString ( decoder_t *, char *, subpicture_t * ); +static void ParseUSFString ( decoder_t *, char *, subpicture_t * ); static void ParseColor ( decoder_t *, char *, int *, int * ); static void StripTags ( char * ); @@ -175,6 +180,7 @@ static int OpenDecoder( vlc_object_t *p_this ) vlc_value_t val; if( p_dec->fmt_in.i_codec != VLC_FOURCC('s','u','b','t') && + p_dec->fmt_in.i_codec != VLC_FOURCC('u','s','f',' ') && p_dec->fmt_in.i_codec != VLC_FOURCC('s','s','a',' ') ) { return VLC_EGENERIC; @@ -268,6 +274,11 @@ static int OpenDecoder( vlc_object_t *p_this ) if( p_dec->fmt_in.i_extra > 0 ) ParseSSAHeader( p_dec ); } + else if( p_dec->fmt_in.i_codec == VLC_FOURCC('u','s','f',' ') && var_CreateGetBool( p_dec, "subsdec-formatted" ) ) + { + if( p_dec->fmt_in.i_extra > 0 ) + ParseUSFHeader( p_dec ); + } return VLC_SUCCESS; } @@ -431,7 +442,8 @@ static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block ) } /* Decode and format the subpicture unit */ - if( p_dec->fmt_in.i_codec != VLC_FOURCC('s','s','a',' ') ) + if( p_dec->fmt_in.i_codec != VLC_FOURCC('s','s','a',' ') && + p_dec->fmt_in.i_codec != VLC_FOURCC('u','s','f',' ') ) { /* Normal text subs, easy markup */ p_spu->i_flags = SUBPICTURE_ALIGN_BOTTOM | p_sys->i_align; @@ -442,6 +454,7 @@ static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block ) StripTags( psz_subtitle ); p_spu->p_region->psz_text = psz_subtitle; + p_spu->p_region->psz_html = NULL; p_spu->i_start = p_block->i_pts; p_spu->i_stop = p_block->i_pts + p_block->i_length; p_spu->b_ephemer = (p_block->i_length == 0); @@ -449,8 +462,12 @@ static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block ) } else { - /* Decode SSA strings */ - ParseSSAString( p_dec, psz_subtitle, p_spu ); + /* Decode SSA/USF strings */ + if( p_dec->fmt_in.i_codec == VLC_FOURCC('s','s','a',' ') ) + ParseSSAString( p_dec, psz_subtitle, p_spu ); + else + ParseUSFString( p_dec, psz_subtitle, p_spu ); + p_spu->i_start = p_block->i_pts; p_spu->i_stop = p_block->i_pts + p_block->i_length; p_spu->b_ephemer = (p_block->i_length == 0); @@ -462,6 +479,111 @@ static subpicture_t *ParseText( decoder_t *p_dec, block_t *p_block ) return p_spu; } +static void ParseUSFString( decoder_t *p_dec, char *psz_subtitle, subpicture_t *p_spu_in ) +{ + decoder_sys_t *p_sys = p_dec->p_sys; + subpicture_t *p_spu = p_spu_in; + char *psz_text; + char *psz_text_start; + ssa_style_t *p_style = NULL; + int i; + + /* Create a text only copy of the subtitle (for legacy implementations) and copy + * the rich html version across as is - for parsing by a rendering engine capable + * of understanding it. + */ + p_spu->p_region->psz_text = NULL; + p_spu->p_region->psz_html = strdup( psz_subtitle ); + + for( i = 0; i < p_sys->i_ssa_styles; i++ ) + { + if( !strcasecmp( p_sys->pp_ssa_styles[i]->psz_stylename, "Default" ) ) + p_style = p_sys->pp_ssa_styles[i]; + } + + /* The StripTags() function doesn't handle HTML tags that have attribute/values with + * them, or properly translate
sequences into newlines, or handle &' sequences + * so do it here ourselves. + */ + psz_text_start = malloc( strlen( psz_subtitle )); + + psz_text = psz_text_start; + while( *psz_subtitle ) + { + if( *psz_subtitle == '<' ) + { + if( !strncasecmp( psz_subtitle, "
", 5 )) + *psz_text++ = '\n'; + else if( strncasecmp( psz_subtitle, "' ) )) + { + int i_len; + + psz_style += strspn( psz_style, "\"" ) + 1; + i_len = strspn( psz_style, "\"" ); + + psz_style[ i_len ] = '\0'; + + for( i = 0; i < p_sys->i_ssa_styles; i++ ) + { + if( !strcmp( p_sys->pp_ssa_styles[i]->psz_stylename, psz_style ) ) + p_style = p_sys->pp_ssa_styles[i]; + } + + psz_style[ i_len ] = '\"'; + } + } + + psz_subtitle += strcspn( psz_subtitle, ">" ); + } + else if( *psz_subtitle == '&' ) + { + if( !strncasecmp( psz_subtitle, "<", 4 )) + *psz_text++ = '<'; + else if( !strncasecmp( psz_subtitle, ">", 4 )) + *psz_text++ = '>'; + else if( !strncasecmp( psz_subtitle, "&", 5 )) + *psz_text++ = '&'; + + psz_subtitle += strcspn( psz_subtitle, ";" ); + } + else if( ( *psz_subtitle == '\t' ) || + ( *psz_subtitle == '\r' ) || + ( *psz_subtitle == '\n' ) || + ( *psz_subtitle == ' ' ) ) + { + if( ( psz_text_start < psz_text ) && + ( *(psz_text-1) != ' ' ) ) + { + *psz_text++ = ' '; + } + } + else + *psz_text++ = *psz_subtitle; + + psz_subtitle++; + } + *psz_text = '\0'; + p_spu->p_region->psz_text = strdup( psz_text_start ); + free( psz_text_start ); + + if( p_style == NULL ) + { + p_spu->i_flags = SUBPICTURE_ALIGN_BOTTOM | p_sys->i_align; + p_spu->i_x = p_sys->i_align ? 20 : 0; + p_spu->i_y = 10; + } + else + { + msg_Dbg( p_dec, "style is: %s", p_style->psz_stylename); + p_spu->p_region->p_style = &p_style->font_style; + p_spu->i_flags = p_style->i_align; + } +} + static void ParseSSAString( decoder_t *p_dec, char *psz_subtitle, subpicture_t *p_spu_in ) { /* We expect MKV formatted SSA: @@ -480,6 +602,8 @@ static void ParseSSAString( decoder_t *p_dec, char *psz_subtitle, subpicture_t * psz_buffer_sub = psz_subtitle; + p_spu->p_region->psz_html = NULL; + i_comma = 0; while( i_comma < 8 && *psz_buffer_sub != '\0' ) { @@ -614,6 +738,275 @@ static void ParseColor( decoder_t *p_dec, char *psz_color, int *pi_color, int *p *pi_alpha = ( i_color & 0xFF000000 ) >> 24; } +/***************************************************************************** + * ParseUSFHeader: Retrieve global formatting information etc + *****************************************************************************/ +static void ParseUSFHeader( decoder_t *p_dec ) +{ + decoder_sys_t *p_sys = p_dec->p_sys; + stream_t *p_sub = NULL; + xml_t *p_xml = NULL; + xml_reader_t *p_xml_reader = NULL; + + p_sub = stream_MemoryNew( VLC_OBJECT(p_dec), + p_dec->fmt_in.p_extra, + p_dec->fmt_in.i_extra, + VLC_FALSE ); + if( p_sub ) + { + p_xml = xml_Create( p_dec ); + if( p_xml ) + { + p_xml_reader = xml_ReaderCreate( p_xml, p_sub ); + if( p_xml_reader ) + { + /* Look for Root Node */ + if( xml_ReaderRead( p_xml_reader ) == 1 ) + { + char *psz_node = xml_ReaderName( p_xml_reader ); + + if( !strcasecmp( "usfsubtitles", psz_node ) ) + ParseUSFHeaderTags( p_sys, p_xml_reader ); + + free( psz_node ); + } + + xml_ReaderDelete( p_xml, p_xml_reader ); + } + xml_Delete( p_xml ); + } + stream_Delete( p_sub ); + } +} + +static void ParseUSFHeaderTags( decoder_sys_t *p_sys, xml_reader_t *p_xml_reader ) +{ + char *psz_node; + ssa_style_t *p_style = NULL; + int i_style_level = 0; + int i_metadata_level = 0; + + while ( xml_ReaderRead( p_xml_reader ) == 1 ) + { + switch ( xml_ReaderNodeType( p_xml_reader ) ) + { + case XML_READER_TEXT: + case XML_READER_NONE: + break; + case XML_READER_ENDELEM: + psz_node = xml_ReaderName( p_xml_reader ); + + if( psz_node ) + { + switch (i_style_level) + { + case 0: + if( !strcasecmp( "metadata", psz_node ) && (i_metadata_level == 1) ) + { + i_metadata_level--; + } + break; + case 1: + if( !strcasecmp( "styles", psz_node ) ) + { + i_style_level--; + } + break; + case 2: + if( !strcasecmp( "style", psz_node ) ) + { + p_style->font_style.i_text_align = p_style->i_align; + + TAB_APPEND( p_sys->i_ssa_styles, p_sys->pp_ssa_styles, p_style ); + + p_style = NULL; + i_style_level--; + } + break; + } + + free( psz_node ); + } + break; + case XML_READER_STARTELEM: + psz_node = xml_ReaderName( p_xml_reader ); + + if( psz_node ) + { + if( !strcasecmp( "metadata", psz_node ) && (i_style_level == 0) ) + { + i_metadata_level++; + } + else if( !strcasecmp( "resolution", psz_node ) && (i_metadata_level == 1) ) + { + while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS ) + { + char *psz_name = xml_ReaderName ( p_xml_reader ); + char *psz_value = xml_ReaderValue ( p_xml_reader ); + + if( psz_name && psz_value ) + { + if( !strcasecmp( "x", psz_name ) ) + p_sys->i_original_width = atoi( psz_value ); + else if( !strcasecmp( "y", psz_name ) ) + p_sys->i_original_height = atoi( psz_value ); + } + if( psz_name ) free( psz_name ); + if( psz_value ) free( psz_value ); + } + } + else if( !strcasecmp( "styles", psz_node ) && (i_style_level == 0) ) + { + i_style_level++; + } + else if( !strcasecmp( "style", psz_node ) && (i_style_level == 1) ) + { + i_style_level++; + + p_style = calloc( 1, sizeof(ssa_style_t) ); + + while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS ) + { + char *psz_name = xml_ReaderName ( p_xml_reader ); + char *psz_value = xml_ReaderValue ( p_xml_reader ); + + if( psz_name && psz_value ) + { + if( !strcasecmp( "name", psz_name ) ) + p_style->psz_stylename = strdup( psz_value); + } + if( psz_name ) free( psz_name ); + if( psz_value ) free( psz_value ); + } + } + else if( !strcasecmp( "fontstyle", psz_node ) && (i_style_level == 2) ) + { + while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS ) + { + char *psz_name = xml_ReaderName ( p_xml_reader ); + char *psz_value = xml_ReaderValue ( p_xml_reader ); + + if( psz_name && psz_value ) + { + if( !strcasecmp( "face", psz_name ) ) + p_style->font_style.psz_fontname = strdup( psz_value); + else if( !strcasecmp( "size", psz_name ) ) + p_style->font_style.i_font_size = atoi( psz_value); + else if( !strcasecmp( "italic", psz_name ) ) + { + if( !strcasecmp( "yes", psz_value )) + p_style->font_style.i_style_flags |= STYLE_ITALIC; + } + else if( !strcasecmp( "weight", psz_name ) ) + { + if( !strcasecmp( "bold", psz_value )) + p_style->font_style.i_style_flags |= STYLE_BOLD; + } + else if( !strcasecmp( "underline", psz_name ) ) + { + if( !strcasecmp( "yes", psz_value )) + p_style->font_style.i_style_flags |= STYLE_UNDERLINE; + } + else if( !strcasecmp( "color", psz_name ) ) + { + if( *psz_value == '#' ) + { + unsigned long col = strtol(psz_value+1, NULL, 16); + p_style->font_style.i_font_color = (col & 0x00ffffff); + // From DTD: + p_style->font_style.i_font_alpha = ((col >> 24) & 0xff) * 255 / 100; + } + } + else if( !strcasecmp( "outline-color", psz_name ) ) + { + if( *psz_value == '#' ) + { + unsigned long col = strtol(psz_value+1, NULL, 16); + p_style->font_style.i_outline_color = (col & 0x00ffffff); + // From DTD: + p_style->font_style.i_outline_alpha = ((col >> 24) & 0xff) * 255 / 100; + } + } + else if( !strcasecmp( "shadow-color", psz_name ) ) + { + if( *psz_value == '#' ) + { + unsigned long col = strtol(psz_value+1, NULL, 16); + p_style->font_style.i_shadow_color = (col & 0x00ffffff); + // From DTD: + p_style->font_style.i_shadow_alpha = ((col >> 24) & 0xff) * 255 / 100; + } + } + } + if( psz_name ) free( psz_name ); + if( psz_value ) free( psz_value ); + } + } + else if( !strcasecmp( "position", psz_node ) && (i_style_level == 2) ) + { + while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS ) + { + char *psz_name = xml_ReaderName ( p_xml_reader ); + char *psz_value = xml_ReaderValue ( p_xml_reader ); + + if( psz_name && psz_value ) + { + if( !strcasecmp( "alignment", psz_name ) ) + { + if( !strcasecmp( "TopLeft", psz_value ) ) + { + p_style->i_align |= SUBPICTURE_ALIGN_TOP; + p_style->i_align |= SUBPICTURE_ALIGN_LEFT; + } + else if( !strcasecmp( "TopCenter", psz_value ) ) + { + p_style->i_align |= SUBPICTURE_ALIGN_TOP; + } + else if( !strcasecmp( "TopRight", psz_value ) ) + { + p_style->i_align |= SUBPICTURE_ALIGN_TOP; + p_style->i_align |= SUBPICTURE_ALIGN_RIGHT; + } + else if( !strcasecmp( "MiddleLeft", psz_value ) ) + { + p_style->i_align |= SUBPICTURE_ALIGN_LEFT; + } + else if( !strcasecmp( "MiddleCenter", psz_value ) ) + { + p_style->i_align = 0; + } + else if( !strcasecmp( "MiddleRight", psz_value ) ) + { + p_style->i_align |= SUBPICTURE_ALIGN_RIGHT; + } + else if( !strcasecmp( "BottomLeft", psz_value ) ) + { + p_style->i_align |= SUBPICTURE_ALIGN_BOTTOM; + p_style->i_align |= SUBPICTURE_ALIGN_LEFT; + } + else if( !strcasecmp( "BottomCenter", psz_value ) ) + { + p_style->i_align |= SUBPICTURE_ALIGN_BOTTOM; + } + else if( !strcasecmp( "BottomRight", psz_value ) ) + { + p_style->i_align |= SUBPICTURE_ALIGN_BOTTOM; + p_style->i_align |= SUBPICTURE_ALIGN_RIGHT; + } + } + } + if( psz_name ) free( psz_name ); + if( psz_value ) free( psz_value ); + } + } + + free( psz_node ); + } + break; + } + } + if( p_style ) free( p_style ); +} /***************************************************************************** * ParseSSAHeader: Retrieve global formatting information etc *****************************************************************************/ diff --git a/modules/demux/mkv.cpp b/modules/demux/mkv.cpp index d9c4791717..3e81018a81 100644 --- a/modules/demux/mkv.cpp +++ b/modules/demux/mkv.cpp @@ -2489,6 +2489,17 @@ bool matroska_segment_c::Select( mtime_t i_start_time ) tracks[i_track]->fmt.i_codec = VLC_FOURCC( 's', 'u', 'b', 't' ); tracks[i_track]->fmt.subs.psz_encoding = strdup( "UTF-8" ); } + else if( !strcmp( tracks[i_track]->psz_codec, "S_TEXT/USF" ) ) + { + tracks[i_track]->fmt.i_codec = VLC_FOURCC( 'u', 's', 'f', ' ' ); + tracks[i_track]->fmt.subs.psz_encoding = strdup( "UTF-8" ); + if( tracks[i_track]->i_extra_data ) + { + tracks[i_track]->fmt.i_extra = tracks[i_track]->i_extra_data; + tracks[i_track]->fmt.p_extra = malloc( tracks[i_track]->i_extra_data ); + memcpy( tracks[i_track]->fmt.p_extra, tracks[i_track]->p_extra_data, tracks[i_track]->i_extra_data ); + } + } else if( !strcmp( tracks[i_track]->psz_codec, "S_TEXT/SSA" ) || !strcmp( tracks[i_track]->psz_codec, "S_TEXT/ASS" ) || !strcmp( tracks[i_track]->psz_codec, "S_SSA" ) || diff --git a/modules/misc/dummy/renderer.c b/modules/misc/dummy/renderer.c index 7e5802995c..b6e89ec20d 100644 --- a/modules/misc/dummy/renderer.c +++ b/modules/misc/dummy/renderer.c @@ -35,6 +35,7 @@ int E_(OpenRenderer)( vlc_object_t *p_this ) { filter_t *p_filter = (filter_t *)p_this; p_filter->pf_render_text = RenderText; + p_filter->pf_render_html = NULL; return VLC_SUCCESS; } diff --git a/modules/misc/freetype.c b/modules/misc/freetype.c index 427319d8ce..20030c88f4 100644 --- a/modules/misc/freetype.c +++ b/modules/misc/freetype.c @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include @@ -47,21 +49,32 @@ #include #include FT_FREETYPE_H #include FT_GLYPH_H +#define FT_FLOOR(X) ((X & -64) >> 6) +#define FT_CEIL(X) (((X + 63) & -64) >> 6) +#define FT_MulFix(v, s) (((v)*(s))>>16) #ifdef __APPLE__ #define DEFAULT_FONT "/System/Library/Fonts/LucidaGrande.dfont" +#define FC_DEFAULT_FONT "Lucida Grande" #elif defined( SYS_BEOS ) #define DEFAULT_FONT "/boot/beos/etc/fonts/ttfonts/Swiss721.ttf" +#define FC_DEFAULT_FONT "Swiss" #elif defined( WIN32 ) #define DEFAULT_FONT "" /* Default font found at run-time */ +#define FC_DEFAULT_FONT "Arial" #else #define DEFAULT_FONT "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf" +#define FC_DEFAULT_FONT "Serif Bold" #endif #if defined(HAVE_FRIBIDI) #include #endif +#ifdef HAVE_FONTCONFIG +#include +#endif + typedef struct line_desc_t line_desc_t; /***************************************************************************** @@ -73,6 +86,12 @@ static void Destroy( vlc_object_t * ); /* The RenderText call maps to pf_render_string, defined in vlc_filter.h */ static int RenderText( filter_t *, subpicture_region_t *, subpicture_region_t * ); +#ifdef HAVE_FONTCONFIG +static int RenderHtml( filter_t *, subpicture_region_t *, + subpicture_region_t * ); +static char *FontConfig_Select( FcConfig *, const char *, + vlc_bool_t, vlc_bool_t, int * ); +#endif static line_desc_t *NewLine( byte_t * ); static int SetFontSize( filter_t *, int ); @@ -169,6 +188,15 @@ struct line_desc_t FT_BitmapGlyph *pp_glyphs; /** list of relative positions for the glyphs */ FT_Vector *p_glyph_pos; + /** list of RGB information for styled text + * -- if the rendering mode supports it (RenderYUVA) and + * b_new_color_mode is set, then it becomes possible to + * have multicoloured text within the subtitles. */ + uint32_t *p_rgb; + vlc_bool_t b_new_color_mode; + /** underline information -- only supplied if text should be underlined */ + uint16_t *pi_underline_offset; + uint16_t *pi_underline_thickness; int i_height; int i_width; @@ -178,6 +206,17 @@ struct line_desc_t line_desc_t *p_next; }; +typedef struct font_stack_t font_stack_t; +struct font_stack_t +{ + char *psz_name; + int i_size; + int i_color; + int i_alpha; + + font_stack_t *p_next; +}; + static int Render( filter_t *, subpicture_region_t *, line_desc_t *, int, int); static void FreeLines( line_desc_t * ); static void FreeLine( line_desc_t * ); @@ -200,6 +239,9 @@ struct filter_sys_t int i_default_font_size; int i_display_height; +#ifdef HAVE_FONTCONFIG + FcConfig *p_fontconfig; +#endif }; /***************************************************************************** @@ -263,6 +305,12 @@ static int Create( vlc_object_t *p_this ) #endif } +#ifdef HAVE_FONTCONFIG + if( FcInit() ) + p_sys->p_fontconfig = FcConfigGetCurrent(); + else + p_sys->p_fontconfig = NULL; +#endif i_error = FT_Init_FreeType( &p_sys->p_library ); if( i_error ) { @@ -298,6 +346,11 @@ static int Create( vlc_object_t *p_this ) if( psz_fontfile ) free( psz_fontfile ); p_filter->pf_render_text = RenderText; +#ifdef HAVE_FONTCONFIG + p_filter->pf_render_html = RenderHtml; +#else + p_filter->pf_render_html = NULL; +#endif return VLC_SUCCESS; error: @@ -318,6 +371,13 @@ static void Destroy( vlc_object_t *p_this ) filter_t *p_filter = (filter_t *)p_this; filter_sys_t *p_sys = p_filter->p_sys; +#ifdef HAVE_FONTCONFIG + FcConfigDestroy( p_sys->p_fontconfig ); + p_sys->p_fontconfig = NULL; + /* FcFini asserts calling the subfunction FcCacheFini() + * even if no other library functions have been made since FcInit(), + * so don't call it. */ +#endif FT_Done_Face( p_sys->p_face ); FT_Done_FreeType( p_sys->p_library ); free( p_sys ); @@ -468,6 +528,78 @@ static int Render( filter_t *p_filter, subpicture_region_t *p_region, return VLC_SUCCESS; } +static void UnderlineGlyphYUVA( int i_line_thickness, int i_line_offset, vlc_bool_t b_ul_next_char, + FT_BitmapGlyph p_this_glyph, FT_Vector *p_this_glyph_pos, + FT_BitmapGlyph p_next_glyph, FT_Vector *p_next_glyph_pos, + int i_glyph_tmax, int i_align_offset, + uint8_t i_y, uint8_t i_u, uint8_t i_v, uint8_t i_alpha, + subpicture_region_t *p_region) +{ + int y, x, z; + int i_pitch; + uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a; + + p_dst_y = p_region->picture.Y_PIXELS; + p_dst_u = p_region->picture.U_PIXELS; + p_dst_v = p_region->picture.V_PIXELS; + p_dst_a = p_region->picture.A_PIXELS; + i_pitch = p_region->picture.A_PITCH; + + int i_offset = ( p_this_glyph_pos->y + i_glyph_tmax + i_line_offset + 3 ) * i_pitch + + p_this_glyph_pos->x + p_this_glyph->left + 3 + i_align_offset; + + for( y = 0; y < i_line_thickness; y++ ) + { + int i_extra = p_this_glyph->bitmap.width; + + if( b_ul_next_char ) + { + i_extra = (p_next_glyph_pos->x + p_next_glyph->left) - + (p_this_glyph_pos->x + p_this_glyph->left); + } + for( x = 0; x < i_extra; x++ ) + { + vlc_bool_t b_ok = VLC_TRUE; + + /* break the underline around the tails of any glyphs which cross it */ + for( z = x - i_line_thickness; + z < x + i_line_thickness && b_ok; + z++ ) + { + if( p_next_glyph && ( z >= i_extra ) ) + { + int i_row = i_line_offset + p_next_glyph->top + y; + + if( ( p_next_glyph->bitmap.rows > i_row ) && + p_next_glyph->bitmap.buffer[p_next_glyph->bitmap.width * i_row + z-i_extra] ) + { + b_ok = VLC_FALSE; + } + } + else if ((z > 0 ) && (z < p_this_glyph->bitmap.width)) + { + int i_row = i_line_offset + p_this_glyph->top + y; + + if( ( p_this_glyph->bitmap.rows > i_row ) && + p_this_glyph->bitmap.buffer[p_this_glyph->bitmap.width * i_row + z] ) + { + b_ok = VLC_FALSE; + } + } + } + + if( b_ok ) + { + p_dst_y[i_offset+x] = (i_y * 255) >> 8; + p_dst_u[i_offset+x] = i_u; + p_dst_v[i_offset+x] = i_v; + p_dst_a[i_offset+x] = 255; + } + } + i_offset += i_pitch; + } +} + static void DrawBlack( line_desc_t *p_line, int i_width, subpicture_region_t *p_region, int xoffset, int yoffset ) { uint8_t *p_dst = p_region->picture.A_PIXELS; @@ -655,6 +787,21 @@ static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 + i_align_offset; + if( p_line->b_new_color_mode ) + { + /* Every glyph can (and in fact must) have its own color */ + int i_red = ( p_line->p_rgb[ i ] & 0x00ff0000 ) >> 16; + int i_green = ( p_line->p_rgb[ i ] & 0x0000ff00 ) >> 8; + int i_blue = ( p_line->p_rgb[ i ] & 0x000000ff ); + + i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green + + 802 * i_blue + 4096 + 131072 ) >> 13, 235); + i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green + + 3598 * i_blue + 4096 + 1048576) >> 13, 240); + i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green + + -585 * i_blue + 4096 + 1048576) >> 13, 240); + } + for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ ) { for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ ) @@ -673,6 +820,18 @@ static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, } i_offset += i_pitch; } + + if( p_line->pi_underline_thickness[ i ] ) + { + UnderlineGlyphYUVA( p_line->pi_underline_thickness[ i ], + p_line->pi_underline_offset[ i ], + (p_line->pp_glyphs[i+1] && (p_line->pi_underline_thickness[ i + 1] > 0)), + p_line->pp_glyphs[i], &(p_line->p_glyph_pos[i]), + p_line->pp_glyphs[i+1], &(p_line->p_glyph_pos[i+1]), + i_glyph_tmax, i_align_offset, + i_y, i_u, i_v, i_alpha, + p_region); + } } } @@ -971,6 +1130,700 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, return VLC_EGENERIC; } +#ifdef HAVE_FONTCONFIG +static int PushFont( font_stack_t **p_font, char *psz_name, int i_size, + int i_color, int i_alpha ) +{ + font_stack_t *p_new; + + if( !p_font ) + return VLC_EGENERIC; + + p_new = malloc( sizeof( font_stack_t ) ); + p_new->p_next = NULL; + + if( psz_name ) + p_new->psz_name = strdup( psz_name ); + else + p_new->psz_name = NULL; + + p_new->i_size = i_size; + p_new->i_color = i_color; + p_new->i_alpha = i_alpha; + + if( !*p_font ) + { + *p_font = p_new; + } + else + { + font_stack_t *p_last; + + for( p_last = *p_font; + p_last->p_next; + p_last = p_last->p_next ) + ; + + p_last->p_next = p_new; + } + return VLC_SUCCESS; +} + +static int PopFont( font_stack_t **p_font ) +{ + font_stack_t *p_last, *p_next_to_last; + + if( !p_font || !*p_font ) + return VLC_EGENERIC; + + p_next_to_last = NULL; + for( p_last = *p_font; + p_last->p_next; + p_last = p_last->p_next ) + { + p_next_to_last = p_last; + } + + if( p_next_to_last ) + p_next_to_last->p_next = NULL; + else + *p_font = NULL; + + free( p_last->psz_name ); + free( p_last ); + + return VLC_SUCCESS; +} + +static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size, + int *i_color, int *i_alpha ) +{ + font_stack_t *p_last; + + if( !p_font || !*p_font ) + return VLC_EGENERIC; + + for( p_last=*p_font; + p_last->p_next; + p_last=p_last->p_next ) + ; + + *psz_name = p_last->psz_name; + *i_size = p_last->i_size; + *i_color = p_last->i_color; + *i_alpha = p_last->i_alpha; + + return VLC_SUCCESS; +} + +static uint32_t *IconvText( filter_t *p_filter, char *psz_string ) +{ + vlc_iconv_t iconv_handle = (vlc_iconv_t)(-1); + uint32_t *psz_unicode; + int i_string_length; + + psz_unicode = + malloc( ( strlen( psz_string ) + 1 ) * sizeof( uint32_t ) ); + if( psz_unicode == NULL ) + { + msg_Err( p_filter, "out of memory" ); + return NULL;; + } +#if defined(WORDS_BIGENDIAN) + iconv_handle = vlc_iconv_open( "UCS-4BE", "UTF-8" ); +#else + iconv_handle = vlc_iconv_open( "UCS-4LE", "UTF-8" ); +#endif + if( iconv_handle == (vlc_iconv_t)-1 ) + { + msg_Warn( p_filter, "unable to do conversion" ); + free( psz_unicode ); + return NULL;; + } + + { + char *p_in_buffer, *p_out_buffer; + size_t i_in_bytes, i_out_bytes, i_out_bytes_left, i_ret; + i_in_bytes = strlen( psz_string ); + i_out_bytes = i_in_bytes * sizeof( uint32_t ); + i_out_bytes_left = i_out_bytes; + p_in_buffer = psz_string; + p_out_buffer = (char *)psz_unicode; + i_ret = vlc_iconv( iconv_handle, (const char**)&p_in_buffer, &i_in_bytes, + &p_out_buffer, &i_out_bytes_left ); + + vlc_iconv_close( iconv_handle ); + + if( i_in_bytes ) + { + msg_Warn( p_filter, "failed to convert string to unicode (%s), " + "bytes left %d", strerror(errno), (int)i_in_bytes ); + free( psz_unicode ); + return NULL;; + } + *(uint32_t*)p_out_buffer = 0; + i_string_length = (i_out_bytes - i_out_bytes_left) / sizeof(uint32_t); + } + +#if defined(HAVE_FRIBIDI) + { + uint32_t *p_fribidi_string; + + p_fribidi_string = malloc( (i_string_length + 1) * sizeof(uint32_t) ); + + /* Do bidi conversion line-by-line */ + FriBidiCharType base_dir = FRIBIDI_TYPE_LTR; + fribidi_log2vis((FriBidiChar*)psz_unicode, i_string_length, + &base_dir, (FriBidiChar*)p_fribidi_string, 0, 0, 0); + + free( psz_unicode ); + psz_unicode = p_fribidi_string; + p_fribidi_string[ i_string_length ] = 0; + } +#endif + return psz_unicode; +} + +static int RenderTag( filter_t *p_filter, FT_Face p_face, int i_font_color, + vlc_bool_t b_uline, line_desc_t *p_line, uint32_t *psz_unicode, + int *pi_pen_x, int i_pen_y, int *pi_start, + FT_Vector *p_result ) +{ + FT_BBox line; + int i_yMin, i_yMax; + int i; + + int i_previous = 0; + int i_pen_x_start = *pi_pen_x; + + uint32_t *psz_unicode_start = psz_unicode; + + line.xMin = line.xMax = line.yMin = line.yMax = 0; + + /* Account for part of line already in position */ + for( i=0; i<*pi_start; i++ ) + { + FT_BBox glyph_size; + + FT_Glyph_Get_CBox( p_line->pp_glyphs[ i ], ft_glyph_bbox_pixels, &glyph_size ); + + line.xMax = p_line->p_glyph_pos[ i ].x + glyph_size.xMax - + glyph_size.xMin + p_line->pp_glyphs[ i ]->left; + line.yMax = __MAX( line.yMax, glyph_size.yMax ); + line.yMin = __MIN( line.yMin, glyph_size.yMin ); + } + i_yMin = line.yMin; + i_yMax = line.yMax; + + while( *psz_unicode && ( *psz_unicode != 0xffff ) ) + { + FT_BBox glyph_size; + FT_Glyph tmp_glyph; + int i_error; + + int i_glyph_index = FT_Get_Char_Index( p_face, *psz_unicode++ ); + if( FT_HAS_KERNING( p_face ) && i_glyph_index + && i_previous ) + { + FT_Vector delta; + FT_Get_Kerning( p_face, i_previous, i_glyph_index, + ft_kerning_default, &delta ); + *pi_pen_x += delta.x >> 6; + } + p_line->p_glyph_pos[ i ].x = *pi_pen_x; + p_line->p_glyph_pos[ i ].y = i_pen_y; + + i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ); + if( i_error ) + { + msg_Err( p_filter, "unable to render text FT_Load_Glyph returned %d", i_error ); + p_line->pp_glyphs[ i ] = NULL; + return VLC_EGENERIC; + } + i_error = FT_Get_Glyph( p_face->glyph, &tmp_glyph ); + if( i_error ) + { + msg_Err( p_filter, "unable to render text FT_Get_Glyph returned %d", i_error ); + p_line->pp_glyphs[ i ] = NULL; + return VLC_EGENERIC; + } + FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size ); + i_error = FT_Glyph_To_Bitmap( &tmp_glyph, FT_RENDER_MODE_NORMAL, 0, 1); + if( i_error ) + { + FT_Done_Glyph( tmp_glyph ); + continue; + } + if( b_uline ) + { + float aOffset = FT_FLOOR(FT_MulFix(p_face->underline_position, p_face->size->metrics.y_scale)); + float aSize = FT_CEIL(FT_MulFix(p_face->underline_thickness, p_face->size->metrics.y_scale)); + + p_line->pi_underline_offset[ i ] = ( aOffset < 0 ) ? -aOffset : aOffset; + p_line->pi_underline_thickness[ i ] = ( aSize < 0 ) ? -aSize : aSize; + } + p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph; + p_line->p_rgb[ i ] = i_font_color & 0x00ffffff; + + line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax - + glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left; + if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 ) + { + while( --i > *pi_start ) + { + FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] ); + } + + while( psz_unicode > psz_unicode_start && *psz_unicode != ' ' ) + { + psz_unicode--; + } + if( psz_unicode == psz_unicode_start ) + { + msg_Warn( p_filter, "unbreakable string" ); + p_line->pp_glyphs[ i ] = NULL; + return VLC_EGENERIC; + } + else + { + *psz_unicode = 0xffff; + } + psz_unicode = psz_unicode_start; + *pi_pen_x = i_pen_x_start; + i_previous = 0; + + line.yMax = i_yMax; + line.yMin = i_yMin; + + continue; + } + line.yMax = __MAX( line.yMax, glyph_size.yMax ); + line.yMin = __MIN( line.yMin, glyph_size.yMin ); + + i_previous = i_glyph_index; + *pi_pen_x += p_face->glyph->advance.x >> 6; + i++; + } + p_line->i_width = line.xMax; + p_line->i_height = __MAX( p_line->i_height, p_face->size->metrics.height >> 6 ); + p_line->pp_glyphs[ i ] = NULL; + + p_result->x = __MAX( p_result->x, line.xMax ); + p_result->y = __MAX( p_result->y, __MAX( p_line->i_height, line.yMax - line.yMin ) ); + + *pi_start = i; + + /* Get rid of any text processed - if necessary repositioning + * at the start of a new line of text + */ + if( !*psz_unicode ) + { + *psz_unicode_start = '\0'; + } + else + { + psz_unicode++; + for( i=0; psz_unicode[ i ]; i++ ) + psz_unicode_start[ i ] = psz_unicode[ i ]; + psz_unicode_start[ i ] = '\0'; + } + + return VLC_SUCCESS; +} + +static int ProcessNodes( filter_t *p_filter, xml_reader_t *p_xml_reader, char *psz_html, text_style_t *p_font_style, line_desc_t **p_lines, FT_Vector *p_result ) +{ + filter_sys_t *p_sys = p_filter->p_sys; + + FT_Vector tmp_result; + + font_stack_t *p_fonts = NULL; + vlc_bool_t b_italic = VLC_FALSE; + vlc_bool_t b_bold = VLC_FALSE; + vlc_bool_t b_uline = VLC_FALSE; + + line_desc_t *p_line = NULL; + line_desc_t *p_prev = NULL; + + char *psz_node = NULL; + + int i_pen_x = 0; + int i_pen_y = 0; + int i_posn = 0; + + int rv = VLC_SUCCESS; + + p_result->x = p_result->y = 0; + tmp_result.x = tmp_result.y = 0; + + if( p_font_style ) + { + PushFont( &p_fonts, + p_font_style->psz_fontname, + p_font_style->i_font_size, + p_font_style->i_font_color, + p_font_style->i_font_alpha ); + + if( p_font_style->i_style_flags & STYLE_BOLD ) + b_bold = VLC_TRUE; + if( p_font_style->i_style_flags & STYLE_ITALIC ) + b_italic = VLC_TRUE; + if( p_font_style->i_style_flags & STYLE_UNDERLINE ) + b_uline = VLC_TRUE; + } + else + { + PushFont( &p_fonts, FC_DEFAULT_FONT, 24, 0xffffff, 0 ); + } + + while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) && ( rv == VLC_SUCCESS ) ) + { + switch ( xml_ReaderNodeType( p_xml_reader ) ) + { + case XML_READER_NONE: + break; + case XML_READER_ENDELEM: + psz_node = xml_ReaderName( p_xml_reader ); + + if( psz_node ) + { + if( !strcasecmp( "font", psz_node ) ) + PopFont( &p_fonts ); + else if( !strcasecmp( "b", psz_node ) ) + b_bold = VLC_FALSE; + else if( !strcasecmp( "i", psz_node ) ) + b_italic = VLC_FALSE; + else if( !strcasecmp( "u", psz_node ) ) + b_uline = VLC_FALSE; + + free( psz_node ); + } + break; + case XML_READER_STARTELEM: + psz_node = xml_ReaderName( p_xml_reader ); + if( psz_node ) + { + if( !strcasecmp( "font", psz_node ) ) + { + char *psz_fontname = NULL; + int i_font_color = 0xffffff; + int i_font_alpha = 0; + int i_font_size = 24; + + /* Default all attributes to the top font in the stack -- in case not + * all attributes are specified in the sub-font + */ + if( VLC_SUCCESS == PeekFont( &p_fonts, &psz_fontname, &i_font_size, &i_font_color, &i_font_alpha )) + { + psz_fontname = strdup( psz_fontname ); + } + + while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS ) + { + char *psz_name = xml_ReaderName ( p_xml_reader ); + char *psz_value = xml_ReaderValue ( p_xml_reader ); + + if( psz_name && psz_value ) + { + if( !strcasecmp( "face", psz_name ) ) + { + if( psz_fontname ) free( psz_fontname ); + psz_fontname = strdup( psz_value ); + } + else if( !strcasecmp( "size", psz_name ) ) + { + i_font_size = atoi( psz_value ); + } + else if( !strcasecmp( "color", psz_name ) && + ( psz_value[0] == '#' ) ) + { + i_font_color = strtol( psz_value+1, NULL, 16 ); + i_font_color &= 0x00ffffff; + } + else if( !strcasecmp( "alpha", psz_name ) && + ( psz_value[0] == '#' ) ) + { + i_font_alpha = strtol( psz_value+1, NULL, 16 ); + i_font_alpha &= 0xff; + } + free( psz_name ); + free( psz_value ); + } + } + PushFont( &p_fonts, psz_fontname, i_font_size, i_font_color, i_font_alpha ); + free( psz_fontname ); + } + else if( !strcasecmp( "b", psz_node ) ) + { + b_bold = VLC_TRUE; + } + else if( !strcasecmp( "i", psz_node ) ) + { + b_italic = VLC_TRUE; + } + else if( !strcasecmp( "u", psz_node ) ) + { + b_uline = VLC_TRUE; + } + else if( !strcasecmp( "br", psz_node ) ) + { + if( p_line ) + { + p_prev = p_line; + if( !(p_line = NewLine( (byte_t *)psz_html )) ) + { + msg_Err( p_filter, "out of memory" ); + free( psz_node ); + rv = VLC_EGENERIC; + break; + } + p_line->b_new_color_mode = VLC_TRUE; + p_result->x = __MAX( p_result->x, tmp_result.x ); + p_result->y += tmp_result.y; + + p_line->p_next = NULL; + i_pen_x = 0; + i_pen_y += tmp_result.y; + i_posn = 0; + p_prev->p_next = p_line; + tmp_result.x = 0; + tmp_result.y = 0; + } + } + free( psz_node ); + } + break; + case XML_READER_TEXT: + psz_node = xml_ReaderValue( p_xml_reader ); + if( psz_node ) + { + char *psz_fontname = NULL; + int i_font_color = 0xffffff; + int i_font_alpha = 0; + int i_font_size = 24; + FT_Face p_face = NULL; + + if( VLC_SUCCESS == PeekFont( &p_fonts, &psz_fontname, &i_font_size, &i_font_color, &i_font_alpha ) ) + { + int i_idx = 0; + char *psz_fontfile = FontConfig_Select( p_sys->p_fontconfig, psz_fontname, b_bold, b_italic, &i_idx ); + + if( psz_fontfile ) + { + if( FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "", + i_idx, &p_face ) ) + { + free( psz_fontfile ); + free( psz_node ); + rv = VLC_EGENERIC; + break; + } + free( psz_fontfile ); + } + } + + if( FT_Select_Charmap( p_face ? p_face : p_sys->p_face, ft_encoding_unicode ) || + FT_Set_Pixel_Sizes( p_face ? p_face : p_sys->p_face, 0, i_font_size ) ) + { + free( psz_node ); + rv = VLC_EGENERIC; + break; + } + p_sys->i_use_kerning = FT_HAS_KERNING( ( p_face ? p_face : p_sys->p_face ) ); + + uint32_t *psz_unicode = IconvText( p_filter, psz_node ); + + if( !psz_unicode ) + { + free( psz_node ); + if( p_face ) FT_Done_Face( p_face ); + rv = VLC_EGENERIC; + break; + } + + while( *psz_unicode ) + { + if( !p_line ) + { + if( !(p_line = NewLine( (byte_t *)psz_html )) ) + { + msg_Err( p_filter, "out of memory" ); + free( psz_node ); + if( p_face ) FT_Done_Face( p_face ); + rv = VLC_EGENERIC; + break; + } + /* New Color mode only works in YUVA rendering mode -- + * (RGB mode has palette constraints on it). We therefore + * need to populate the legacy colour fields also. + */ + p_line->b_new_color_mode = VLC_TRUE; + p_line->i_alpha = i_font_alpha; + p_line->i_red = ( i_font_color & 0xff0000 ) >> 16; + p_line->i_green = ( i_font_color & 0x00ff00 ) >> 8; + p_line->i_blue = ( i_font_color & 0x0000ff ); + p_line->p_next = NULL; + i_pen_x = 0; + i_pen_y += tmp_result.y; + tmp_result.x = 0; + tmp_result.y = 0; + i_posn = 0; + if( p_prev ) p_prev->p_next = p_line; + else *p_lines = p_line; + } + + if( RenderTag( p_filter, p_face, i_font_color, b_uline, p_line, psz_unicode, &i_pen_x, i_pen_y, &i_posn, &tmp_result ) != VLC_SUCCESS ) + { + free( psz_node ); + if( p_face ) FT_Done_Face( p_face ); + rv = VLC_EGENERIC; + break; + } + if( *psz_unicode ) + { + p_result->x = __MAX( p_result->x, tmp_result.x ); + p_result->y += tmp_result.y; + + p_prev = p_line; + p_line = NULL; + } + } + if( rv != VLC_SUCCESS ) break; + + if( p_face ) FT_Done_Face( p_face ); + free( psz_unicode ); + free( psz_node ); + } + break; + } + } + if( p_line ) + { + p_result->x = __MAX( p_result->x, tmp_result.x ); + p_result->y += tmp_result.y; + } + + + while( VLC_SUCCESS == PopFont( &p_fonts ) ); + + return rv; +} + + +static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out, + subpicture_region_t *p_region_in ) +{ + int rv = VLC_SUCCESS; + stream_t *p_sub = NULL; + xml_t *p_xml = NULL; + xml_reader_t *p_xml_reader = NULL; + + if( !p_region_in || !p_region_in->psz_html ) + return VLC_EGENERIC; + + p_sub = stream_MemoryNew( VLC_OBJECT(p_filter), + p_region_in->psz_html, + strlen( p_region_in->psz_html ), + VLC_FALSE ); + if( p_sub ) + { + p_xml = xml_Create( p_filter ); + if( p_xml ) + { + p_xml_reader = xml_ReaderCreate( p_xml, p_sub ); + if( p_xml_reader ) + { + FT_Vector result; + line_desc_t *p_lines = NULL; + + rv = ProcessNodes( p_filter, p_xml_reader, p_region_in->psz_html, p_region_in->p_style, &p_lines, &result ); + + if( rv == VLC_SUCCESS ) + { + p_region_out->i_x = p_region_in->i_x; + p_region_out->i_y = p_region_in->i_y; + + if( config_GetInt( p_filter, "freetype-yuvp" ) ) + Render( p_filter, p_region_out, p_lines, result.x, result.y ); + else + RenderYUVA( p_filter, p_region_out, p_lines, result.x, result.y ); + } + FreeLines( p_lines ); + + xml_ReaderDelete( p_xml, p_xml_reader ); + } + xml_Delete( p_xml ); + } + stream_Delete( p_sub ); + } + /* No longer need a HTML version of the text */ + free( p_region_in->psz_html ); + p_region_in->psz_html = NULL; + + return rv; +} + +static char* FontConfig_Select( FcConfig* priv, const char* family, vlc_bool_t b_bold, vlc_bool_t b_italic, int *i_idx ) +{ + FcResult result; + FcPattern *pat, *p_pat; + FcChar8* val_s; + FcBool val_b; + + pat = FcPatternCreate(); + if (!pat) return NULL; + + FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family ); + FcPatternAddBool( pat, FC_OUTLINE, FcTrue ); + FcPatternAddInteger( pat, FC_SLANT, b_italic ? 1000 : 0 ); + FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? 1000 : 0 ); + + FcDefaultSubstitute( pat ); + + if( !FcConfigSubstitute( priv, pat, FcMatchPattern ) ) + { + FcPatternDestroy( pat ); + return NULL; + } + + p_pat = FcFontMatch( priv, pat, &result ); + FcPatternDestroy( pat ); + if( !p_pat ) return NULL; + + if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) ) || + ( val_b != FcTrue ) ) + { + FcPatternDestroy( p_pat ); + return NULL; + } + if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) ) + { + *i_idx = 0; + } + + if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) ) + { + FcPatternDestroy( p_pat ); + return NULL; + } + + /* + if( strcasecmp((const char*)val_s, family ) != 0 ) + msg_Warn( p_filter, "fontconfig: selected font family is not the requested one: '%s' != '%s'\n", + (const char*)val_s, family ); + */ + + if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) ) + { + FcPatternDestroy( p_pat ); + return NULL; + } + + FcPatternDestroy( p_pat ); + return strdup( (const char*)val_s ); +} +#endif + static void FreeLine( line_desc_t *p_line ) { unsigned int i; @@ -980,6 +1833,9 @@ static void FreeLine( line_desc_t *p_line ) } free( p_line->pp_glyphs ); free( p_line->p_glyph_pos ); + free( p_line->p_rgb ); + free( p_line->pi_underline_offset ); + free( p_line->pi_underline_thickness ); free( p_line ); } @@ -1011,21 +1867,27 @@ static line_desc_t *NewLine( byte_t *psz_string ) i_count = strlen( (char *)psz_string ); p_line->pp_glyphs = malloc( sizeof(FT_BitmapGlyph) * ( i_count + 1 ) ); - if( p_line->pp_glyphs == NULL ) - { - free( p_line ); - return NULL; - } - p_line->pp_glyphs[0] = NULL; - p_line->p_glyph_pos = malloc( sizeof( FT_Vector ) * i_count + 1 ); - if( p_line->p_glyph_pos == NULL ) + p_line->p_rgb = malloc( sizeof( uint32_t ) * i_count + 1 ); + p_line->pi_underline_offset = calloc( i_count+ + 1, sizeof( uint16_t ) ); + p_line->pi_underline_thickness = calloc( i_count+ + 1, sizeof( uint16_t ) ); + if( ( p_line->pp_glyphs == NULL ) || + ( p_line->p_glyph_pos == NULL ) || + ( p_line->p_rgb == NULL ) || + ( p_line->pi_underline_offset == NULL ) || + ( p_line->pi_underline_thickness == NULL ) ) { - free( p_line->pp_glyphs ); + if( p_line->pi_underline_thickness ) free( p_line->pi_underline_thickness ); + if( p_line->pi_underline_offset ) free( p_line->pi_underline_offset ); + if( p_line->p_rgb ) free( p_line->p_rgb ); + if( p_line->p_glyph_pos ) free( p_line->p_glyph_pos ); + if( p_line->pp_glyphs ) free( p_line->pp_glyphs ); free( p_line ); return NULL; } - + p_line->pp_glyphs[0] = NULL; + p_line->b_new_color_mode = VLC_FALSE; + return p_line; } diff --git a/modules/misc/svg.c b/modules/misc/svg.c index bb7b78d414..c68941b12c 100644 --- a/modules/misc/svg.c +++ b/modules/misc/svg.c @@ -145,6 +145,7 @@ static int Create( vlc_object_t *p_this ) p_sys->i_height = p_filter->fmt_out.video.i_height; p_filter->pf_render_text = RenderText; + p_filter->pf_render_html = NULL; p_filter->p_sys = p_sys; /* MUST call this before any RSVG funcs */ diff --git a/modules/misc/win32text.c b/modules/misc/win32text.c index ca6d03cb4b..5b6fa4c30b 100644 --- a/modules/misc/win32text.c +++ b/modules/misc/win32text.c @@ -184,6 +184,7 @@ static int Create( vlc_object_t *p_this ) if( psz_fontfile ) free( psz_fontfile ); p_filter->pf_render_text = RenderText; + p_filter->pf_render_html = NULL; return VLC_SUCCESS; error: diff --git a/src/video_output/vout_subpictures.c b/src/video_output/vout_subpictures.c index fb412a8ffc..d167168595 100644 --- a/src/video_output/vout_subpictures.c +++ b/src/video_output/vout_subpictures.c @@ -629,12 +629,20 @@ void spu_RenderSubpictures( spu_t *p_spu, video_format_t *p_fmt, if( p_region->fmt.i_chroma == VLC_FOURCC('T','E','X','T') ) { - if( p_spu->p_text && p_spu->p_text->p_module && - p_spu->p_text->pf_render_text ) + if( p_spu->p_text && p_spu->p_text->p_module ) { p_region->i_align = p_subpic->i_flags; - p_spu->p_text->pf_render_text( p_spu->p_text, - p_region, p_region ); + + if( p_spu->p_text->pf_render_html && p_region->psz_html ) + { + p_spu->p_text->pf_render_html( p_spu->p_text, + p_region, p_region ); + } + else if( p_spu->p_text->pf_render_text ) + { + p_spu->p_text->pf_render_text( p_spu->p_text, + p_region, p_region ); + } } } -- 2.39.2