X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fmisc%2Ffreetype.c;h=298d27fbf3b001edbfdc3ed1469338313f8d6b68;hb=4d4eb33495d371286c1c2b76f2255cd38217fcf6;hp=88a6e6c52cde452f0e1383e4172c05204093f359;hpb=015a1605686a8277f4657e4cd40cb4d155bc36d7;p=vlc diff --git a/modules/misc/freetype.c b/modules/misc/freetype.c index 88a6e6c52c..298d27fbf3 100644 --- a/modules/misc/freetype.c +++ b/modules/misc/freetype.c @@ -1,11 +1,12 @@ /***************************************************************************** * freetype.c : Put text on the video, using freetype2 ***************************************************************************** - * Copyright (C) 2002 - 2005 VideoLAN + * Copyright (C) 2002 - 2007 the VideoLAN team * $Id$ * - * Authors: Sigmund Augdal + * Authors: Sigmund Augdal Helberg * Gildas Bazin + * Bernie Purcell * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +20,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /***************************************************************************** @@ -33,10 +34,12 @@ #endif #include -#include -#include "osd.h" -#include "vlc_block.h" -#include "vlc_filter.h" +#include +#include +#include +#include +#include +#include #include @@ -47,21 +50,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 SYS_DARWIN +#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,7 +87,13 @@ 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 * ); -static line_desc_t *NewLine( byte_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( int ); static int SetFontSize( filter_t *, int ); @@ -81,30 +101,49 @@ static int SetFontSize( filter_t *, int ); * Module descriptor *****************************************************************************/ #define FONT_TEXT N_("Font") -#define FONT_LONGTEXT N_("Font filename") +#define FONT_LONGTEXT N_("Filename for the font you want to use") #define FONTSIZE_TEXT N_("Font size in pixels") -#define FONTSIZE_LONGTEXT N_("The size of the fonts used by the osd module. " \ +#define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \ + "that will be rendered on the video. " \ "If set to something different than 0 this option will override the " \ - "relative font size " ) -#define OPACITY_TEXT N_("Opacity, 0..255") -#define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of " \ - "overlay text. 0 = transparent, 255 = totally opaque. " ) -#define COLOR_TEXT N_("Text Default Color") -#define COLOR_LONGTEXT N_("The color of overlay text. 1 byte for each color, "\ - "hexadecimal. #000000 = all colors off, 0xFF0000 = just Red, " \ - "0xFFFFFF = all color on [White]" ) -#define FONTSIZER_TEXT N_("Font size") -#define FONTSIZER_LONGTEXT N_("The size of the fonts used by the osd module" ) + "relative font size." ) +#define OPACITY_TEXT N_("Opacity") +#define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \ + "text that will be rendered on the video. 0 = transparent, " \ + "255 = totally opaque. " ) +#define COLOR_TEXT N_("Text default color") +#define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\ + "the video. This must be an hexadecimal (like HTML colors). The first two "\ + "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\ + " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" ) +#define FONTSIZER_TEXT N_("Relative font size") +#define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \ + "fonts that will be rendered on the video. If absolute font size is set, "\ + "relative size will be overriden." ) static int pi_sizes[] = { 20, 18, 16, 12, 6 }; -static char *ppsz_sizes_text[] = { N_("Smaller"), N_("Small"), N_("Normal"), - N_("Large"), N_("Larger") }; +static const char *ppsz_sizes_text[] = { N_("Smaller"), N_("Small"), N_("Normal"), + N_("Large"), N_("Larger") }; +#define YUVP_TEXT N_("Use YUVP renderer") +#define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \ + "This option is only needed if you want to encode into DVB subtitles" ) +#define EFFECT_TEXT N_("Font Effect") +#define EFFECT_LONGTEXT N_("It is possible to apply effects to the rendered " \ +"text to improve its readability." ) + +#define EFFECT_BACKGROUND 1 +#define EFFECT_OUTLINE 2 +#define EFFECT_OUTLINE_FAT 3 + +static int pi_effects[] = { 1, 2, 3 }; +static const char *ppsz_effects_text[] = { N_("Background"),N_("Outline"), + N_("Fat Outline") }; static int pi_color_values[] = { 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000, - 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080, - 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF }; + 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080, + 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF }; -static char *ppsz_color_descriptions[] = { +static const char *ppsz_color_descriptions[] = { N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"), N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") }; @@ -123,17 +162,22 @@ vlc_module_begin(); /* opacity valid on 0..255, with default 255 = fully opaque */ add_integer_with_range( "freetype-opacity", 255, 0, 255, NULL, - OPACITY_TEXT, OPACITY_LONGTEXT, VLC_FALSE ); + OPACITY_TEXT, OPACITY_LONGTEXT, VLC_TRUE ); /* hook to the color values list, with default 0x00ffffff = white */ add_integer( "freetype-color", 0x00FFFFFF, NULL, COLOR_TEXT, - COLOR_LONGTEXT, VLC_TRUE ); + COLOR_LONGTEXT, VLC_FALSE ); change_integer_list( pi_color_values, ppsz_color_descriptions, 0 ); add_integer( "freetype-rel-fontsize", 16, NULL, FONTSIZER_TEXT, FONTSIZER_LONGTEXT, VLC_FALSE ); change_integer_list( pi_sizes, ppsz_sizes_text, 0 ); + add_integer( "freetype-effect", 2, NULL, EFFECT_TEXT, + EFFECT_LONGTEXT, VLC_FALSE ); + change_integer_list( pi_effects, ppsz_effects_text, 0 ); + add_bool( "freetype-yuvp", 0, NULL, YUVP_TEXT, + YUVP_LONGTEXT, VLC_TRUE ); set_capability( "text renderer", 100 ); add_shortcut( "text" ); set_callbacks( Create, Destroy ); @@ -145,6 +189,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; @@ -154,6 +207,27 @@ 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; +}; + +typedef struct +{ + int i_font_size; + uint32_t i_font_color; /* ARGB */ + vlc_bool_t b_italic; + vlc_bool_t b_bold; + vlc_bool_t b_underline; + char *psz_fontname; +} ft_style_t; + 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 * ); @@ -172,9 +246,13 @@ struct filter_sys_t uint8_t i_font_opacity; int i_font_color; int i_font_size; + int i_effect; int i_default_font_size; int i_display_height; +#ifdef HAVE_FONTCONFIG + FcConfig *p_fontconfig; +#endif }; /***************************************************************************** @@ -210,12 +288,15 @@ static int Create( vlc_object_t *p_this ) VLC_VAR_INTEGER | VLC_VAR_DOINHERIT ); var_Create( p_filter, "freetype-opacity", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT ); + var_Create( p_filter, "freetype-effect", + VLC_VAR_INTEGER | VLC_VAR_DOINHERIT ); var_Get( p_filter, "freetype-opacity", &val ); p_sys->i_font_opacity = __MAX( __MIN( val.i_int, 255 ), 0 ); var_Create( p_filter, "freetype-color", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT ); var_Get( p_filter, "freetype-color", &val ); p_sys->i_font_color = __MAX( __MIN( val.i_int, 0xFFFFFF ), 0 ); + p_sys->i_effect = var_GetInteger( p_filter, "freetype-effect" ); /* Look what method was requested */ var_Get( p_filter, "freetype-font", &val ); @@ -224,10 +305,15 @@ static int Create( vlc_object_t *p_this ) { if( psz_fontfile ) free( psz_fontfile ); psz_fontfile = (char *)malloc( PATH_MAX + 1 ); + if( !psz_fontfile ) + { + msg_Err( p_filter, "out of memory" ); + goto error; + } #ifdef WIN32 GetWindowsDirectory( psz_fontfile, PATH_MAX + 1 ); strcat( psz_fontfile, "\\fonts\\arial.ttf" ); -#elif SYS_DARWIN +#elif defined(__APPLE__) strcpy( psz_fontfile, DEFAULT_FONT ); #else msg_Err( p_filter, "user didn't specify a font" ); @@ -235,13 +321,18 @@ 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 ) { msg_Err( p_filter, "couldn't initialize freetype" ); goto error; } - i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "", 0, &p_sys->p_face ); if( i_error == FT_Err_Unknown_File_Format ) @@ -258,7 +349,7 @@ static int Create( vlc_object_t *p_this ) i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode ); if( i_error ) { - msg_Err( p_filter, "Font has no unicode translation table" ); + msg_Err( p_filter, "font has no unicode translation table" ); goto error; } @@ -270,6 +361,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: @@ -290,6 +386,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 ); @@ -304,8 +407,8 @@ static int Render( filter_t *p_filter, subpicture_region_t *p_region, line_desc_t *p_line, int i_width, int i_height ) { static uint8_t pi_gamma[16] = - {0x00, 0x41, 0x52, 0x63, 0x84, 0x85, 0x96, 0xa7, 0xb8, 0xc9, - 0xca, 0xdb, 0xdc, 0xed, 0xee, 0xff}; + {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; uint8_t *p_dst; video_format_t fmt; @@ -320,6 +423,10 @@ static int Render( filter_t *p_filter, subpicture_region_t *p_region, fmt.i_aspect = 0; fmt.i_width = fmt.i_visible_width = i_width + 4; fmt.i_height = fmt.i_visible_height = i_height + 4; + if( p_region->fmt.i_visible_width > 0 ) + fmt.i_visible_width = p_region->fmt.i_visible_width; + if( p_region->fmt.i_visible_height > 0 ) + fmt.i_visible_height = p_region->fmt.i_visible_height; fmt.i_x_offset = fmt.i_y_offset = 0; p_region_tmp = spu_CreateRegion( p_filter, &fmt ); if( !p_region_tmp ) @@ -342,7 +449,16 @@ static int Render( filter_t *p_filter, subpicture_region_t *p_region, /* Build palette */ fmt.p_palette->i_entries = 16; - for( i = 0; i < fmt.p_palette->i_entries; i++ ) + for( i = 0; i < 8; i++ ) + { + fmt.p_palette->palette[i][0] = 0; + fmt.p_palette->palette[i][1] = 0x80; + fmt.p_palette->palette[i][2] = 0x80; + fmt.p_palette->palette[i][3] = pi_gamma[i]; + fmt.p_palette->palette[i][3] = + (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255; + } + for( i = 8; i < fmt.p_palette->i_entries; i++ ) { fmt.p_palette->palette[i][0] = i * 16 * i_y / 256; fmt.p_palette->palette[i][1] = i_u; @@ -370,11 +486,11 @@ static int Render( filter_t *p_filter, subpicture_region_t *p_region, if( p_line->i_width < i_width ) { - if( p_region->i_text_align == SUBPICTURE_ALIGN_RIGHT ) + if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT ) { i_align_offset = i_width - p_line->i_width; } - else if( p_region->i_text_align != SUBPICTURE_ALIGN_LEFT ) + else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT ) { i_align_offset = ( i_width - p_line->i_width ) / 2; } @@ -411,15 +527,15 @@ static int Render( filter_t *p_filter, subpicture_region_t *p_region, for( y = 1; y < (int)fmt.i_height - 1; y++ ) { - memcpy( p_top, p_dst, fmt.i_width ); + if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width ); p_dst += p_region->picture.Y_PITCH; left = 0; for( x = 1; x < (int)fmt.i_width - 1; x++ ) { current = p_dst[x]; - p_dst[x] = ( 4 * (int)p_dst[x] + left + p_top[x] + p_dst[x+1] + - p_dst[x + p_region->picture.Y_PITCH]) / 8; + p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] + + p_dst[x -1 + p_region->picture.Y_PITCH ] + p_dst[x + p_region->picture.Y_PITCH] + p_dst[x + 1 + p_region->picture.Y_PITCH]) / 16; left = current; } } @@ -429,6 +545,320 @@ 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; + int i_pitch = p_region->picture.A_PITCH; + int x,y; + + for( ; p_line != NULL; p_line = p_line->p_next ) + { + int i_glyph_tmax=0, i = 0; + int i_bitmap_offset, i_offset, i_align_offset = 0; + for( i = 0; p_line->pp_glyphs[i] != NULL; i++ ) + { + FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ]; + i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top ); + } + + if( p_line->i_width < i_width ) + { + if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT ) + { + i_align_offset = i_width - p_line->i_width; + } + else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT ) + { + i_align_offset = ( i_width - p_line->i_width ) / 2; + } + } + + for( i = 0; p_line->pp_glyphs[i] != NULL; i++ ) + { + FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ]; + + i_offset = ( p_line->p_glyph_pos[ i ].y + + i_glyph_tmax - p_glyph->top + 3 + yoffset ) * + i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 + + i_align_offset +xoffset; + + 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++ ) + { + if( p_glyph->bitmap.buffer[i_bitmap_offset] ) + if( p_dst[i_offset+x] < + ((int)p_glyph->bitmap.buffer[i_bitmap_offset]) ) + p_dst[i_offset+x] = + ((int)p_glyph->bitmap.buffer[i_bitmap_offset]); + } + i_offset += i_pitch; + } + } + } + +} + +/***************************************************************************** + * Render: place string in picture + ***************************************************************************** + * This function merges the previously rendered freetype glyphs into a picture + *****************************************************************************/ +static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, + line_desc_t *p_line, int i_width, int i_height ) +{ + uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a; + video_format_t fmt; + int i, x, y, i_pitch, i_alpha; + uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */ + subpicture_region_t *p_region_tmp; + + if( i_width == 0 || i_height == 0 ) + return VLC_SUCCESS; + + /* Create a new subpicture region */ + memset( &fmt, 0, sizeof(video_format_t) ); + fmt.i_chroma = VLC_FOURCC('Y','U','V','A'); + fmt.i_aspect = 0; + fmt.i_width = fmt.i_visible_width = i_width + 6; + fmt.i_height = fmt.i_visible_height = i_height + 6; + if( p_region->fmt.i_visible_width > 0 ) + fmt.i_visible_width = p_region->fmt.i_visible_width; + if( p_region->fmt.i_visible_height > 0 ) + fmt.i_visible_height = p_region->fmt.i_visible_height; + fmt.i_x_offset = fmt.i_y_offset = 0; + p_region_tmp = spu_CreateRegion( p_filter, &fmt ); + if( !p_region_tmp ) + { + msg_Err( p_filter, "cannot allocate SPU region" ); + return VLC_EGENERIC; + } + + p_region->fmt = p_region_tmp->fmt; + p_region->picture = p_region_tmp->picture; + free( p_region_tmp ); + + /* Calculate text color components */ + i_y = (uint8_t)__MIN(abs( 2104 * p_line->i_red + 4130 * p_line->i_green + + 802 * p_line->i_blue + 4096 + 131072 ) >> 13, 235); + i_u = (uint8_t)__MIN(abs( -1214 * p_line->i_red + -2384 * p_line->i_green + + 3598 * p_line->i_blue + 4096 + 1048576) >> 13, 240); + i_v = (uint8_t)__MIN(abs( 3598 * p_line->i_red + -3013 * p_line->i_green + + -585 * p_line->i_blue + 4096 + 1048576) >> 13, 240); + i_alpha = p_line->i_alpha; + + 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; + + /* Initialize the region pixels */ + if( p_filter->p_sys->i_effect != EFFECT_BACKGROUND ) + { + memset( p_dst_y, 0x00, i_pitch * p_region->fmt.i_height ); + memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height ); + memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height ); + memset( p_dst_a, 0, i_pitch * p_region->fmt.i_height ); + } + else + { + memset( p_dst_y, 0x0, i_pitch * p_region->fmt.i_height ); + memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height ); + memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height ); + memset( p_dst_a, 0x80, i_pitch * p_region->fmt.i_height ); + } + if( p_filter->p_sys->i_effect == EFFECT_OUTLINE || + p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT ) + { + DrawBlack( p_line, i_width, p_region, 0, 0); + DrawBlack( p_line, i_width, p_region, -1, 0); + DrawBlack( p_line, i_width, p_region, 0, -1); + DrawBlack( p_line, i_width, p_region, 1, 0); + DrawBlack( p_line, i_width, p_region, 0, 1); + } + + if( p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT ) + { + DrawBlack( p_line, i_width, p_region, -1, -1); + DrawBlack( p_line, i_width, p_region, -1, 1); + DrawBlack( p_line, i_width, p_region, 1, -1); + DrawBlack( p_line, i_width, p_region, 1, 1); + + DrawBlack( p_line, i_width, p_region, -2, 0); + DrawBlack( p_line, i_width, p_region, 0, -2); + DrawBlack( p_line, i_width, p_region, 2, 0); + DrawBlack( p_line, i_width, p_region, 0, 2); + + DrawBlack( p_line, i_width, p_region, -2, -2); + DrawBlack( p_line, i_width, p_region, -2, 2); + DrawBlack( p_line, i_width, p_region, 2, -2); + DrawBlack( p_line, i_width, p_region, 2, 2); + + DrawBlack( p_line, i_width, p_region, -3, 0); + DrawBlack( p_line, i_width, p_region, 0, -3); + DrawBlack( p_line, i_width, p_region, 3, 0); + DrawBlack( p_line, i_width, p_region, 0, 3); + } + + for( ; p_line != NULL; p_line = p_line->p_next ) + { + int i_glyph_tmax = 0; + int i_bitmap_offset, i_offset, i_align_offset = 0; + for( i = 0; p_line->pp_glyphs[i] != NULL; i++ ) + { + FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ]; + i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top ); + } + + if( p_line->i_width < i_width ) + { + if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT ) + { + i_align_offset = i_width - p_line->i_width; + } + else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT ) + { + i_align_offset = ( i_width - p_line->i_width ) / 2; + } + } + + for( i = 0; p_line->pp_glyphs[i] != NULL; i++ ) + { + FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ]; + + i_offset = ( p_line->p_glyph_pos[ i ].y + + i_glyph_tmax - p_glyph->top + 3 ) * + 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++ ) + { + if( p_glyph->bitmap.buffer[i_bitmap_offset] ) + { + p_dst_y[i_offset+x] = ((p_dst_y[i_offset+x] *(255-(int)p_glyph->bitmap.buffer[i_bitmap_offset])) + + i_y * ((int)p_glyph->bitmap.buffer[i_bitmap_offset])) >> 8; + + p_dst_u[i_offset+x] = i_u; + p_dst_v[i_offset+x] = i_v; + + if( p_filter->p_sys->i_effect == EFFECT_BACKGROUND ) + p_dst_a[i_offset+x] = 0xff; + } + } + 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); + } + } + } + + /* Apply the alpha setting */ + for( i = 0; i < (int)fmt.i_height * i_pitch; i++ ) + p_dst_a[i] = p_dst_a[i] * (255 - i_alpha) / 255; + + return VLC_SUCCESS; +} + /** * This function renders a text subpicture region into another one. * It also calculates the size needed for this string, and renders the @@ -439,9 +869,9 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, subpicture_region_t *p_region_in ) { filter_sys_t *p_sys = p_filter->p_sys; - line_desc_t *p_lines = 0, *p_line = 0, *p_next = 0, *p_prev = 0; + line_desc_t *p_lines = NULL, *p_line = NULL, *p_next = NULL, *p_prev = NULL; int i, i_pen_y, i_pen_x, i_error, i_glyph_index, i_previous; - uint32_t *psz_unicode, *psz_unicode_orig = 0, i_char, *psz_line_start; + uint32_t *psz_unicode, *psz_unicode_orig = NULL, i_char, *psz_line_start; int i_string_length; char *psz_string; vlc_iconv_t iconv_handle = (vlc_iconv_t)(-1); @@ -457,13 +887,21 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, psz_string = p_region_in->psz_text; if( !psz_string || !*psz_string ) return VLC_EGENERIC; - i_font_color = __MAX( __MIN( p_region_in->i_text_color, 0xFFFFFF ), 0 ); - if( i_font_color == 0xFFFFFF ) i_font_color = p_sys->i_font_color; + if( p_region_in->p_style ) + { + i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 ); + i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 ); + i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ); + } + else + { + i_font_color = p_sys->i_font_color; + i_font_alpha = 255 - p_sys->i_font_opacity; + i_font_size = p_sys->i_default_font_size; + } - i_font_alpha = __MAX( __MIN( p_region_in->i_text_alpha, 255 ), 0 ); + if( i_font_color == 0xFFFFFF ) i_font_color = p_sys->i_font_color; if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity; - - i_font_size = __MAX( __MIN( p_region_in->i_text_size, 255 ), 0 ); SetFontSize( p_filter, i_font_size ); i_red = ( i_font_color & 0x00FF0000 ) >> 16; @@ -492,14 +930,14 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, } { - char *p_in_buffer, *p_out_buffer; + char *p_out_buffer; + const char *p_in_buffer = psz_string; 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, &p_in_buffer, &i_in_bytes, + 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 ); @@ -507,7 +945,7 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, if( i_in_bytes ) { msg_Warn( p_filter, "failed to convert string to unicode (%s), " - "bytes left %d", strerror(errno), i_in_bytes ); + "bytes left %d", strerror(errno), (int)i_in_bytes ); goto error; } *(uint32_t*)p_out_buffer = 0; @@ -517,10 +955,40 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, #if defined(HAVE_FRIBIDI) { uint32_t *p_fribidi_string; - FriBidiCharType base_dir = FRIBIDI_TYPE_ON; + int start_pos, pos = 0; + p_fribidi_string = malloc( (i_string_length + 1) * sizeof(uint32_t) ); - fribidi_log2vis( (FriBidiChar*)psz_unicode, i_string_length, - &base_dir, (FriBidiChar*)p_fribidi_string, 0, 0, 0 ); + if( !p_fribidi_string ) + { + msg_Err( p_filter, "out of memory" ); + goto error; + } + + /* Do bidi conversion line-by-line */ + while(pos < i_string_length) + { + while(pos < i_string_length) { + i_char = psz_unicode[pos]; + if (i_char != '\r' && i_char != '\n') + break; + p_fribidi_string[pos] = i_char; + ++pos; + } + start_pos = pos; + while(pos < i_string_length) { + i_char = psz_unicode[pos]; + if (i_char == '\r' || i_char == '\n') + break; + ++pos; + } + if (pos > start_pos) + { + FriBidiCharType base_dir = FRIBIDI_TYPE_LTR; + fribidi_log2vis((FriBidiChar*)psz_unicode + start_pos, pos - start_pos, + &base_dir, (FriBidiChar*)p_fribidi_string + start_pos, 0, 0, 0); + } + } + free( psz_unicode_orig ); psz_unicode = psz_unicode_orig = p_fribidi_string; p_fribidi_string[ i_string_length ] = 0; @@ -529,7 +997,7 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, /* Calculate relative glyph positions and a bounding box for the * entire string */ - if( !(p_line = NewLine( psz_string )) ) + if( !(p_line = NewLine( strlen( psz_string ))) ) { msg_Err( p_filter, "out of memory" ); goto error; @@ -553,7 +1021,7 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, if( i_char == '\n' ) { psz_line_start = psz_unicode; - if( !(p_next = NewLine( psz_string )) ) + if( !(p_next = NewLine( strlen( psz_string ))) ) { msg_Err( p_filter, "out of memory" ); goto error; @@ -595,18 +1063,24 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, i_error = FT_Load_Glyph( face, i_glyph_index, FT_LOAD_DEFAULT ); if( i_error ) { - msg_Err( p_filter, "FT_Load_Glyph returned %d", i_error ); + msg_Err( p_filter, "unable to render text FT_Load_Glyph returned" + " %d", i_error ); goto error; } i_error = FT_Get_Glyph( glyph, &tmp_glyph ); if( i_error ) { - msg_Err( p_filter, "FT_Get_Glyph returned %d", i_error ); + msg_Err( p_filter, "unable to render text FT_Get_Glyph returned " + "%d", i_error ); goto error; } 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 ) continue; + if( i_error ) + { + FT_Done_Glyph( tmp_glyph ); + continue; + } p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph; /* Do rest */ @@ -616,7 +1090,7 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, { p_line->pp_glyphs[ i ] = NULL; FreeLine( p_line ); - p_line = NewLine( psz_string ); + p_line = NewLine( strlen( psz_string )); if( p_prev ) p_prev->p_next = p_line; else p_lines = p_line; @@ -663,7 +1137,10 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, p_region_out->i_x = p_region_in->i_x; p_region_out->i_y = p_region_in->i_y; - Render( p_filter, p_region_out, p_lines, result.x, result.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 ); if( psz_unicode_orig ) free( psz_unicode_orig ); FreeLines( p_lines ); @@ -675,61 +1152,1116 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, return VLC_EGENERIC; } -static void FreeLine( line_desc_t *p_line ) +#ifdef HAVE_FONTCONFIG +static ft_style_t *CreateStyle( char *psz_fontname, int i_font_size, + int i_font_color, int i_font_alpha, vlc_bool_t b_bold, + vlc_bool_t b_italic, vlc_bool_t b_uline ) { - unsigned int i; - for( i = 0; p_line->pp_glyphs[ i ] != NULL; i++ ) + ft_style_t *p_style = malloc( sizeof( ft_style_t )); + + if( p_style ) { - FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] ); + p_style->i_font_size = i_font_size; + p_style->i_font_color = ( i_font_color & 0x00ffffff ) + | (( i_font_alpha & 0xff ) << 24 ); + p_style->b_italic = b_italic; + p_style->b_bold = b_bold; + p_style->b_underline = b_uline; + /* p_style has just been malloc'ed in this function - + * it CAN'T have a previous assignment, and hence we + * don't need to do a free() for any previous value - + * which will in fact be undefined. */ + p_style->psz_fontname = strdup( psz_fontname ); } - free( p_line->pp_glyphs ); - free( p_line->p_glyph_pos ); - free( p_line ); + return p_style; } -static void FreeLines( line_desc_t *p_lines ) +static void DeleteStyle( ft_style_t *p_style ) { - line_desc_t *p_line, *p_next; - - if( !p_lines ) return; + if( p_style ) + { + if( p_style->psz_fontname ) + free( p_style->psz_fontname ); + free( p_style ); + } +} - for( p_line = p_lines; p_line != NULL; p_line = p_next ) +static vlc_bool_t StyleEquals( ft_style_t *s1, ft_style_t *s2 ) +{ + if( !s1 || !s2 ) + return VLC_FALSE; + if( s1 == s2 ) + return VLC_TRUE; + + if(( s1->i_font_size == s2->i_font_size ) && + ( s1->i_font_color == s2->i_font_color ) && + ( s1->b_italic == s2->b_italic ) && + ( s1->b_bold == s2->b_bold ) && + ( s1->b_underline == s2->b_underline ) && + ( !strcmp( s1->psz_fontname, s2->psz_fontname ))) { - p_next = p_line->p_next; - FreeLine( p_line ); + return VLC_TRUE; } + return VLC_FALSE; } -static line_desc_t *NewLine( byte_t *psz_string ) +static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size, + int i_color, int i_alpha ) { - int i_count; - line_desc_t *p_line = malloc( sizeof(line_desc_t) ); + font_stack_t *p_new; - if( !p_line ) return NULL; - p_line->i_height = 0; - p_line->i_width = 0; - p_line->p_next = NULL; + if( !p_font ) + return VLC_EGENERIC; + + p_new = malloc( sizeof( font_stack_t ) ); + if( ! p_new ) + return VLC_ENOMEM; - /* We don't use CountUtf8Characters() here because we are not acutally - * sure the string is utf8. Better be safe than sorry. */ - i_count = strlen( psz_string ); + p_new->p_next = NULL; - p_line->pp_glyphs = malloc( sizeof(FT_BitmapGlyph) * ( i_count + 1 ) ); - if( p_line->pp_glyphs == 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 ) { - free( p_line ); - return NULL; + *p_font = p_new; } - p_line->pp_glyphs[0] = NULL; + 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_line->p_glyph_pos = malloc( sizeof( FT_Vector ) * i_count + 1 ); - if( p_line->p_glyph_pos == NULL ) + p_next_to_last = NULL; + for( p_last = *p_font; + p_last->p_next; + p_last = p_last->p_next ) { - free( p_line->pp_glyphs ); - free( p_line ); - return NULL; + 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 void IconvText( filter_t *p_filter, const char *psz_string, + uint32_t *i_string_length, uint32_t **ppsz_unicode ) +{ + vlc_iconv_t iconv_handle = (vlc_iconv_t)(-1); + + /* If memory hasn't been allocated for our output string, allocate it here + * - the calling function must now be responsible for freeing it. + */ + if( !*ppsz_unicode ) + *ppsz_unicode = (uint32_t *) + malloc( (strlen( psz_string ) + 1) * sizeof( uint32_t )); + + /* We don't need to handle a NULL pointer in *ppsz_unicode + * if we are instead testing for a non NULL value like we are here */ + + if( *ppsz_unicode ) + { +#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 ) + { + 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 = (char *) psz_string; + p_out_buffer = (char *) *ppsz_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 ); + } + else + { + *(uint32_t*)p_out_buffer = 0; + *i_string_length = + (i_out_bytes - i_out_bytes_left) / sizeof(uint32_t); + } + } + else + { + msg_Warn( p_filter, "unable to do conversion" ); + } + } +} + +static ft_style_t *GetStyleFromFontStack( filter_sys_t *p_sys, + font_stack_t **p_fonts, vlc_bool_t b_bold, vlc_bool_t b_italic, + vlc_bool_t b_uline ) +{ + ft_style_t *p_style = NULL; + + char *psz_fontname = NULL; + int i_font_color = p_sys->i_font_color; + int i_font_alpha = 0; + int i_font_size = p_sys->i_font_size; + + if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size, + &i_font_color, &i_font_alpha ) ) + { + p_style = CreateStyle( psz_fontname, i_font_size, i_font_color, + i_font_alpha, b_bold, b_italic, b_uline ); + } + return p_style; +} + +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; + vlc_bool_t b_first_on_line = VLC_TRUE; + + 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( (FT_Glyph) 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; + + if( line.xMax > 0 ) + b_first_on_line = VLC_FALSE; + + while( *psz_unicode && ( *psz_unicode != '\n' ) ) + { + 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 ) + { + if( b_first_on_line ) + { + msg_Warn( p_filter, "unbreakable string" ); + p_line->pp_glyphs[ i ] = NULL; + return VLC_EGENERIC; + } + *pi_pen_x = i_pen_x_start; + + 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, + i_yMax - i_yMin ) ); + + *pi_start = i; + return VLC_SUCCESS; + } + else + { + *psz_unicode = '\n'; + } + 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 HandleFontAttributes( xml_reader_t *p_xml_reader, + font_stack_t **p_fonts ) +{ + int rv; + 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 ) ) + { + if( ( *psz_value == '+' ) || ( *psz_value == '-' ) ) + { + int i_value = atoi( psz_value ); + + if( ( i_value >= -5 ) && ( i_value <= 5 ) ) + i_font_size += ( i_value * i_font_size ) / 10; + else if( i_value < -5 ) + i_font_size = - i_value; + else if( i_value > 5 ) + i_font_size = i_value; + } + else + 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 ); + } + } + rv = PushFont( p_fonts, + psz_fontname, + i_font_size, + i_font_color, + i_font_alpha ); + + free( psz_fontname ); + + return rv; +} + +static void SetupLine( filter_t *p_filter, const char *psz_text_in, + uint32_t **psz_text_out, uint32_t *pi_runs, + uint32_t **ppi_run_lengths, ft_style_t ***ppp_styles, + ft_style_t *p_style ) +{ + uint32_t i_string_length = 0; + + IconvText( p_filter, psz_text_in, &i_string_length, psz_text_out ); + *psz_text_out += i_string_length; + + if( ppp_styles && ppi_run_lengths ) + { + (*pi_runs)++; + + if( *ppp_styles ) + { + *ppp_styles = (ft_style_t **) + realloc( *ppp_styles, *pi_runs * sizeof( ft_style_t * ) ); + } + else if( *pi_runs == 1 ) + { + *ppp_styles = (ft_style_t **) + malloc( *pi_runs * sizeof( ft_style_t * ) ); + } + + /* We have just malloc'ed this memory successfully - + * *pi_runs HAS to be within the memory area of *ppp_styles */ + if( *ppp_styles ) + { + (*ppp_styles)[ *pi_runs - 1 ] = p_style; + p_style = NULL; + } + + if( *ppi_run_lengths ) + { + *ppi_run_lengths = (uint32_t *) + realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) ); + } + else if( *pi_runs == 1 ) + { + *ppi_run_lengths = (uint32_t *) + malloc( *pi_runs * sizeof( uint32_t ) ); + } + + /* same remarks here */ + if( *ppi_run_lengths ) + { + (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length; + } + } + /* If we couldn't use the p_style argument due to memory allocation + * problems above, release it here. + */ + if( p_style ) DeleteStyle( p_style ); +} + +static int ProcessNodes( filter_t *p_filter, xml_reader_t *p_xml_reader, + text_style_t *p_font_style, uint32_t *psz_text, + int *pi_len, uint32_t *pi_runs, + uint32_t **ppi_run_lengths, ft_style_t ***ppp_styles) +{ + int rv = VLC_SUCCESS; + filter_sys_t *p_sys = p_filter->p_sys; + uint32_t *psz_text_orig = psz_text; + font_stack_t *p_fonts = NULL; + + char *psz_node = NULL; + + vlc_bool_t b_italic = VLC_FALSE; + vlc_bool_t b_bold = VLC_FALSE; + vlc_bool_t b_uline = VLC_FALSE; + + if( p_font_style ) + { + rv = 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 + { + rv = PushFont( &p_fonts, + FC_DEFAULT_FONT, + p_sys->i_font_size, + 0xffffff, + 0 ); + } + if( rv != VLC_SUCCESS ) + return rv; + + while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) ) + { + 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 ) ) + rv = HandleFontAttributes( p_xml_reader, &p_fonts ); + 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 ) ) + { + SetupLine( p_filter, "\n", &psz_text, + pi_runs, ppi_run_lengths, ppp_styles, + GetStyleFromFontStack( p_sys, + &p_fonts, + b_bold, + b_italic, + b_uline ) ); + } + + free( psz_node ); + } + break; + case XML_READER_TEXT: + psz_node = xml_ReaderValue( p_xml_reader ); + if( psz_node ) + { + /* Turn any multiple-whitespaces into single spaces */ + char *s = strpbrk( psz_node, "\t\r\n " ); + while( s ) + { + int i_whitespace = strspn( s, "\t\r\n " ); + + if( i_whitespace > 1 ) + memmove( &s[1], + &s[i_whitespace], + strlen( s ) - i_whitespace + 1 ); + *s++ = ' '; + + s = strpbrk( s, "\t\r\n " ); + } + SetupLine( p_filter, psz_node, &psz_text, + pi_runs, ppi_run_lengths, ppp_styles, + GetStyleFromFontStack( p_sys, + &p_fonts, + b_bold, + b_italic, + b_uline ) ); + free( psz_node ); + } + break; + } + if( rv != VLC_SUCCESS ) + { + psz_text = psz_text_orig; + break; + } + } + + *pi_len = psz_text - psz_text_orig; + + while( VLC_SUCCESS == PopFont( &p_fonts ) ); + + return rv; +} + +static int ProcessLines( filter_t *p_filter, uint32_t *psz_text, + int i_len, uint32_t i_runs, + uint32_t *pi_run_lengths, ft_style_t **pp_styles, + line_desc_t **pp_lines, FT_Vector *p_result ) +{ + filter_sys_t *p_sys = p_filter->p_sys; + ft_style_t **pp_char_styles; + uint32_t i, j, k; + int i_prev; + + /* Assign each character in the text string its style explicitly, so that + * after the characters have been shuffled around by Fribidi, we can re-apply + * the styles, and to simplify the calculation of runs within a line. + */ + pp_char_styles = (ft_style_t **) malloc( i_len * sizeof( ft_style_t * )); + if( !pp_char_styles ) + return VLC_ENOMEM; + + i = 0; + for( j = 0; j < i_runs; j++ ) + for( k = 0; k < pi_run_lengths[ j ]; k++ ) + pp_char_styles[ i++ ] = pp_styles[ j ]; + +#if defined(HAVE_FRIBIDI) + { + ft_style_t **pp_char_styles_new; + int *p_positions; + uint32_t *p_fribidi_string; + int start_pos, pos = 0; + + p_fribidi_string = malloc( (i_len + 1) * sizeof(uint32_t) ); + if(! p_fribidi_string ) + { + msg_Err( p_filter, "out of memory" ); + free( pp_char_styles ); + return VLC_ENOMEM; + } + pp_char_styles_new = (ft_style_t **) + malloc( i_len * sizeof( ft_style_t * )); + if(! pp_char_styles_new ) + { + msg_Err( p_filter, "out of memory" ); + free( p_fribidi_string ); + free( pp_char_styles ); + return VLC_ENOMEM; + } + p_positions = (int *) malloc( (i_len + 1) * sizeof( int ) ); + if(! p_positions ) + { + msg_Err( p_filter, "out of memory" ); + free( pp_char_styles_new ); + free( p_fribidi_string ); + free( pp_char_styles ); + return VLC_ENOMEM; + } + + /* Do bidi conversion line-by-line */ + while(pos < i_len) + { + while(pos < i_len) { + if (psz_text[pos] != '\n') + break; + p_fribidi_string[pos] = psz_text[pos]; + pp_char_styles_new[pos] = pp_char_styles[pos]; + ++pos; + } + start_pos = pos; + while(pos < i_len) { + if (psz_text[pos] == '\n') + break; + ++pos; + } + if (pos > start_pos) + { + FriBidiCharType base_dir = FRIBIDI_TYPE_LTR; + fribidi_log2vis((FriBidiChar*)psz_text + start_pos, + pos - start_pos, &base_dir, + (FriBidiChar*)p_fribidi_string + start_pos, + 0, + p_positions, 0); + + for( j = (uint32_t) start_pos; j < (uint32_t) pos; j++ ) + { + pp_char_styles_new[ j ] = pp_char_styles[ start_pos + + p_positions[ j - start_pos ] ]; + } + } + } + free( p_positions ); + free( pp_char_styles ); + pp_char_styles = pp_char_styles_new; + psz_text = p_fribidi_string; + p_fribidi_string[ i_len ] = 0; + } +#endif + FT_Vector tmp_result; + + line_desc_t *p_line = NULL; + line_desc_t *p_prev = NULL; + + int i_pen_x = 0; + int i_pen_y = 0; + int i_posn = 0; + + p_result->x = p_result->y = 0; + tmp_result.x = tmp_result.y = 0; + + i_prev = 0; + for( k = 0; k <= (uint32_t) i_len; k++ ) + { + if( ( k == (uint32_t) i_len ) || + ( ( k > 0 ) && + !StyleEquals( pp_char_styles[ k ], pp_char_styles[ k - 1] ) ) ) + { + ft_style_t *p_style = pp_char_styles[ k - 1 ]; + + /* End of the current style run */ + FT_Face p_face = NULL; + int i_idx = 0; + char *psz_fontfile = FontConfig_Select( p_sys->p_fontconfig, + p_style->psz_fontname, + p_style->b_bold, + p_style->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( pp_char_styles ); +#if defined(HAVE_FRIBIDI) + free( psz_text ); +#endif + return VLC_EGENERIC; + } + 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, + p_style->i_font_size ) ) + { + if( p_face ) FT_Done_Face( p_face ); + free( pp_char_styles ); +#if defined(HAVE_FRIBIDI) + free( psz_text ); +#endif + return VLC_EGENERIC; + } + p_sys->i_use_kerning = + FT_HAS_KERNING( ( p_face ? p_face : p_sys->p_face ) ); + + + uint32_t *psz_unicode = (uint32_t *) + malloc( (k - i_prev + 1) * sizeof( uint32_t )); + if( !psz_unicode ) + { + msg_Err( p_filter, "out of memory" ); + if( p_face ) FT_Done_Face( p_face ); + free( pp_char_styles ); + free( psz_unicode ); +#if defined(HAVE_FRIBIDI) + free( psz_text ); +#endif + return VLC_ENOMEM; + } + memcpy( psz_unicode, psz_text + i_prev, + sizeof( uint32_t ) * ( k - i_prev ) ); + psz_unicode[ k - i_prev ] = 0; + while( *psz_unicode ) + { + if( !p_line ) + { + if( !(p_line = NewLine( i_len - i_prev)) ) + { + msg_Err( p_filter, "out of memory" ); + if( p_face ) FT_Done_Face( p_face ); + free( pp_char_styles ); + free( psz_unicode ); +#if defined(HAVE_FRIBIDI) + free( psz_text ); +#endif + return VLC_ENOMEM; + } + /* 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 = ( p_style->i_font_color & 0xff000000 ) >> 24; + p_line->i_red = ( p_style->i_font_color & 0x00ff0000 ) >> 16; + p_line->i_green = ( p_style->i_font_color & 0x0000ff00 ) >> 8; + p_line->i_blue = ( p_style->i_font_color & 0x000000ff ); + 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 *pp_lines = p_line; + } + if( RenderTag( p_filter, p_face ? p_face : p_sys->p_face, + p_style->i_font_color, p_style->b_underline, + p_line, psz_unicode, &i_pen_x, i_pen_y, &i_posn, + &tmp_result ) != VLC_SUCCESS ) + { + if( p_face ) FT_Done_Face( p_face ); + free( pp_char_styles ); + free( psz_unicode ); +#if defined(HAVE_FRIBIDI) + free( psz_text ); +#endif + return VLC_EGENERIC; + } + 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; + } + } + free( psz_unicode ); + if( p_face ) FT_Done_Face( p_face ); + i_prev = k; + } + } + free( pp_char_styles ); +#if defined(HAVE_FRIBIDI) + free( psz_text ); +#endif + + if( p_line ) + { + p_result->x = __MAX( p_result->x, tmp_result.x ); + p_result->y += tmp_result.y; + } + return VLC_SUCCESS; +} + +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), + (uint8_t *) p_region_in->psz_html, + strlen( p_region_in->psz_html ), + VLC_TRUE ); + 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 ) + { + uint32_t *psz_text; + int i_len; + uint32_t i_runs = 0; + uint32_t *pi_run_lengths = NULL; + ft_style_t **pp_styles = NULL; + FT_Vector result; + line_desc_t *p_lines = NULL; + + psz_text = (uint32_t *)malloc( strlen( p_region_in->psz_html ) * + sizeof( uint32_t ) ); + if( psz_text ) + { + uint32_t k; + + rv = ProcessNodes( p_filter, p_xml_reader, + p_region_in->p_style, psz_text, &i_len, + &i_runs, &pi_run_lengths, &pp_styles ); + + p_region_out->i_x = p_region_in->i_x; + p_region_out->i_y = p_region_in->i_y; + + if( rv == VLC_SUCCESS ) + { + rv = ProcessLines( p_filter, psz_text, i_len, i_runs, + pi_run_lengths, pp_styles, &p_lines, &result ); + } + + for( k=0; kpp_glyphs[ i ] != NULL; i++ ) + { + FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] ); + } + 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 ); +} + +static void FreeLines( line_desc_t *p_lines ) +{ + line_desc_t *p_line, *p_next; + + if( !p_lines ) return; + + for( p_line = p_lines; p_line != NULL; p_line = p_next ) + { + p_next = p_line->p_next; + FreeLine( p_line ); + } +} + +static line_desc_t *NewLine( int i_count ) +{ + line_desc_t *p_line = malloc( sizeof(line_desc_t) ); + + if( !p_line ) return NULL; + p_line->i_height = 0; + p_line->i_width = 0; + p_line->p_next = NULL; + + p_line->pp_glyphs = malloc( sizeof(FT_BitmapGlyph) * ( i_count + 1 ) ); + p_line->p_glyph_pos = malloc( sizeof( FT_Vector ) * ( i_count + 1 ) ); + 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 ) ) + { + 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; } @@ -760,11 +2292,11 @@ static int SetFontSize( filter_t *p_filter, int i_size ) } if( i_size <= 0 ) { - msg_Warn( p_filter, "Invalid fontsize, using 12" ); + msg_Warn( p_filter, "invalid fontsize, using 12" ); i_size = 12; } - msg_Dbg( p_filter, "Using fontsize: %i", i_size ); + msg_Dbg( p_filter, "using fontsize: %i", i_size ); } p_sys->i_font_size = i_size;