X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fmisc%2Ftext_renderer%2Ffreetype.c;h=c431748c94fe491850da39c51a0d184c4afa440e;hb=be03564a2edcd4ab4de3a86c0f2aa64bc1c60d39;hp=a6f407bb2a450f5271821acd8f10040d44add031;hpb=02522fbf22deb6a725c5a660606f5c45dcbed6bc;p=vlc diff --git a/modules/misc/text_renderer/freetype.c b/modules/misc/text_renderer/freetype.c index a6f407bb2a..c431748c94 100644 --- a/modules/misc/text_renderer/freetype.c +++ b/modules/misc/text_renderer/freetype.c @@ -40,19 +40,21 @@ #include /* resolve_xml_special_chars */ #include /* ToCharset */ #include /* FcCache dialog */ +#include /* filter_sys_t */ +#include /* text_style_t*/ /* Default fonts */ #ifdef __APPLE__ -# define DEFAULT_FONT "/Library/Fonts/Arial Black.ttf" +# define DEFAULT_FONT_FILE "/Library/Fonts/Arial Black.ttf" # define DEFAULT_FAMILY "Arial Black" #elif defined( WIN32 ) -# define DEFAULT_FONT "arial.ttf" /* Default path font found at run-time */ +# define DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */ # define DEFAULT_FAMILY "Arial" #elif defined( HAVE_MAEMO ) -# define DEFAULT_FONT "/usr/share/fonts/nokia/nosnb.ttf" +# define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf" # define DEFAULT_FAMILY "Nokia Sans Bold" #else -# define DEFAULT_FONT "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf" +# define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf" # define DEFAULT_FAMILY "Serif Bold" #endif @@ -60,6 +62,8 @@ #include #include FT_FREETYPE_H #include FT_GLYPH_H +#include FT_STROKER_H + #define FT_FLOOR(X) ((X & -64) >> 6) #define FT_CEIL(X) (((X + 63) & -64) >> 6) #ifndef FT_MulFix @@ -73,7 +77,6 @@ /* Win32 GDI */ #ifdef WIN32 -# define _WIN32_IE 0x0500 # include # include # define HAVE_STYLES @@ -118,23 +121,19 @@ static void Destroy( vlc_object_t * ); "fonts that will be rendered on the video. If absolute font size is set, "\ "relative size will be overridden." ) +#define BG_OPACITY_TEXT N_("Background opacity") +#define BG_COLOR_TEXT N_("Background color") + +#define OUTLINE_OPACITY_TEXT N_("Outline opacity") +#define OUTLINE_COLOR_TEXT N_("Outline color") +#define OUTLINE_THICKNESS_TEXT N_("Outline thickness") + static const int pi_sizes[] = { 20, 18, 16, 12, 6 }; static const char *const 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." ) - -enum { EFFECT_BACKGROUND = 1, - EFFECT_OUTLINE = 2, - EFFECT_OUTLINE_FAT = 3, -}; -static int const pi_effects[] = { EFFECT_BACKGROUND, EFFECT_OUTLINE, EFFECT_OUTLINE_FAT }; -static const char *const ppsz_effects_text[] = { - N_("Background"),N_("Outline"), N_("Fat Outline") }; static const int pi_color_values[] = { 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000, @@ -146,6 +145,13 @@ static const char *const ppsz_color_descriptions[] = { N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") }; +static const int pi_outline_thickness[] = { + 0, 2, 4, 6, +}; +static const char *const ppsz_outline_thickness[] = { + N_("None"), N_("Thin"), N_("Normal"), N_("Thick"), +}; + vlc_module_begin () set_shortname( N_("Text renderer")) set_description( N_("Freetype2 font renderer") ) @@ -155,15 +161,20 @@ vlc_module_begin () #ifdef HAVE_STYLES add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false ) #else - add_font( "freetype-font", DEFAULT_FONT, FONT_TEXT, FONT_LONGTEXT, false ) + add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false ) #endif add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT, FONTSIZE_LONGTEXT, true ) change_safe() + add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT, + FONTSIZER_LONGTEXT, false ) + change_integer_list( pi_sizes, ppsz_sizes_text ) + change_safe() + /* opacity valid on 0..255, with default 255 = fully opaque */ - add_integer_with_range( "freetype-opacity", 255, 0, 255, NULL, + add_integer_with_range( "freetype-opacity", 255, 0, 255, OPACITY_TEXT, OPACITY_LONGTEXT, false ) change_safe() @@ -173,15 +184,27 @@ vlc_module_begin () change_integer_list( pi_color_values, ppsz_color_descriptions ) change_safe() - add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT, - FONTSIZER_LONGTEXT, false ) - change_integer_list( pi_sizes, ppsz_sizes_text ) + add_integer_with_range( "freetype-background-opacity", 0, 0, 255, + BG_OPACITY_TEXT, "", false ) + change_safe() + add_integer( "freetype-background-color", 0x00000000, BG_COLOR_TEXT, + "", false ) + change_integer_list( pi_color_values, ppsz_color_descriptions ) change_safe() - add_integer( "freetype-effect", 2, EFFECT_TEXT, - EFFECT_LONGTEXT, false ) - change_integer_list( pi_effects, ppsz_effects_text ) + add_integer_with_range( "freetype-outline-opacity", 255, 0, 255, + OUTLINE_OPACITY_TEXT, "", false ) + change_safe() + add_integer( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT, + "", false ) + change_integer_list( pi_color_values, ppsz_color_descriptions ) change_safe() + add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT, + "", false ) + change_integer_list( pi_outline_thickness, ppsz_outline_thickness ) + change_safe() + + add_obsolete_integer( "freetype-effect" ); add_bool( "freetype-yuvp", false, YUVP_TEXT, YUVP_LONGTEXT, true ) @@ -195,74 +218,36 @@ vlc_module_end () * Local prototypes *****************************************************************************/ -/* 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_STYLES -static int RenderHtml( filter_t *, subpicture_region_t *, - subpicture_region_t * ); -#endif -#ifdef HAVE_FONTCONFIG -static void FontConfig_BuildCache( filter_t * ); -static char* FontConfig_Select( FcConfig *, const char *, - bool, bool, int, int * ); -#endif -#ifdef WIN32 -static char* Win32_Select( filter_t *, const char *, - bool, bool, int, int * ); -#endif - -static int LoadFontsFromAttachments( filter_t *p_filter ); - -static int GetFontSize( filter_t *p_filter ); -static int SetFontSize( filter_t *, int ); -static void YUVFromRGB( uint32_t i_argb, - uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v ); +typedef struct +{ + FT_BitmapGlyph p_glyph; + FT_BitmapGlyph p_outline; + uint32_t i_color; /* ARGB color */ + int i_line_offset; /* underline/strikethrough offset */ + int i_line_thickness; /* underline/strikethrough thickness */ +} line_character_t; typedef struct line_desc_t line_desc_t; struct line_desc_t { - /** NULL-terminated list of glyphs making the string */ - 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_fg_rgb; - uint32_t *p_bg_rgb; - uint8_t *p_fg_bg_ratio; /* 0x00=100% FG --> 0x7F=100% BG */ - bool b_new_color_mode; - /** underline information -- only supplied if text should be underlined */ - int *pi_underline_offset; - uint16_t *pi_underline_thickness; - - int i_height; - int i_width; - int i_red, i_green, i_blue; - int i_alpha; - line_desc_t *p_next; + + int i_width; + int i_base_line; + int i_character_count; + line_character_t *p_character; }; -static line_desc_t *NewLine( int ); -typedef struct +typedef struct font_stack_t font_stack_t; +struct font_stack_t { - int i_font_size; - uint32_t i_font_color; /* ARGB */ - uint32_t i_karaoke_bg_color; /* ARGB */ - bool b_italic; - bool b_bold; - bool b_underline; - bool b_through; - 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 * ); + char *psz_name; + int i_size; + uint32_t i_color; /* ARGB */ + uint32_t i_karaoke_bg_color; /* ARGB */ + + font_stack_t *p_next; +}; /***************************************************************************** * filter_sys_t: freetype local data @@ -274,16 +259,22 @@ struct filter_sys_t { FT_Library p_library; /* handle to library */ FT_Face p_face; /* handle to face object */ - bool i_use_kerning; + FT_Stroker p_stroker; uint8_t i_font_opacity; int i_font_color; int i_font_size; - int i_effect; + + uint8_t i_background_opacity; + int i_background_color; + + double f_outline_thickness; + uint8_t i_outline_opacity; + int i_outline_color; int i_default_font_size; int i_display_height; -#ifdef HAVE_STYLES char* psz_fontfamily; +#ifdef HAVE_STYLES xml_reader_t *p_xml; #ifdef WIN32 char* psz_win_fonts_path; @@ -294,194 +285,20 @@ struct filter_sys_t int i_font_attachments; }; -#define UCHAR uint32_t -#define TR_DEFAULT_FONT p_sys->psz_fontfamily -#define TR_FONT_STYLE_PTR ft_style_t * - -#include "text_renderer.h" - -/***************************************************************************** - * Create: allocates osd-text video thread output method - ***************************************************************************** - * This function allocates and initializes a Clone vout method. - *****************************************************************************/ -static int Create( vlc_object_t *p_this ) -{ - filter_t *p_filter = (filter_t *)p_this; - filter_sys_t *p_sys; - char *psz_fontfile = NULL; - char *psz_fontfamily = NULL; - int i_error = 0, fontindex = 0; - - /* Allocate structure */ - p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) ); - if( !p_sys ) - return VLC_ENOMEM; - -#ifdef HAVE_STYLES - p_sys->psz_fontfamily = NULL; - p_sys->p_xml = NULL; -#endif - p_sys->p_face = 0; - p_sys->p_library = 0; - p_sys->i_font_size = 0; - p_sys->i_display_height = 0; - - var_Create( p_filter, "freetype-rel-fontsize", - VLC_VAR_INTEGER | VLC_VAR_DOINHERIT ); - - psz_fontfamily = var_InheritString( p_filter, "freetype-font" ); - p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" ); - p_sys->i_effect = var_InheritInteger( p_filter, "freetype-effect" ); - p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" ); - p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 ); - p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" ); - p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 ); - -#ifdef WIN32 - /* Get Windows Font folder */ - wchar_t wdir[MAX_PATH]; - if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) ) - { - GetWindowsDirectoryW( wdir, MAX_PATH ); - wcscat( wdir, L"\\fonts" ); - } - p_sys->psz_win_fonts_path = FromWide( wdir ); -#endif - - /* Set default psz_fontfamily */ - if( !psz_fontfamily || !*psz_fontfamily ) - { - free( psz_fontfamily ); -#ifdef HAVE_STYLES - psz_fontfamily = strdup( DEFAULT_FAMILY ); -#else - psz_fontfamily = (char *)malloc( PATH_MAX + 1 ); - if( !psz_fontfamily ) - goto error; -# ifdef WIN32 - strcat( psz_fontfamily, p_sys->psz_win_fonts_path ); - strcat( psz_fontfamily, DEFAULT_FONT ); -# else - strcpy( psz_fontfamily, DEFAULT_FONT ); -# endif - msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily ); -#endif - } - - /* Set the current font file */ -#ifdef HAVE_STYLES - p_sys->psz_fontfamily = psz_fontfamily; -#ifdef HAVE_FONTCONFIG - FontConfig_BuildCache( p_filter ); - - /* */ - psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false, - p_sys->i_default_font_size, &fontindex ); -#elif defined(WIN32) - psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false, - p_sys->i_default_font_size, &fontindex ); - -#endif - msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile ); - - /* If nothing is found, use the default family */ - if( !psz_fontfile ) - psz_fontfile = psz_fontfamily; - -#else /* !HAVE_STYLES */ - /* Use the default file */ - psz_fontfile = psz_fontfamily; -#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 : "", - fontindex, &p_sys->p_face ); - - if( i_error == FT_Err_Unknown_File_Format ) - { - msg_Err( p_filter, "file %s have unknown format", - psz_fontfile ? psz_fontfile : "(null)" ); - goto error; - } - else if( i_error ) - { - msg_Err( p_filter, "failed to load font file %s", - psz_fontfile ? psz_fontfile : "(null)" ); - goto error; - } - - 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" ); - goto error; - } - - p_sys->i_use_kerning = FT_HAS_KERNING( p_sys->p_face ); - - if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error; - - - p_sys->pp_font_attachments = NULL; - p_sys->i_font_attachments = 0; - - p_filter->pf_render_text = RenderText; -#ifdef HAVE_STYLES - p_filter->pf_render_html = RenderHtml; -#else - p_filter->pf_render_html = NULL; -#endif - - LoadFontsFromAttachments( p_filter ); - - return VLC_SUCCESS; - -error: - if( p_sys->p_face ) FT_Done_Face( p_sys->p_face ); - if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library ); - free( psz_fontfamily ); - free( p_sys ); - return VLC_EGENERIC; -} - -/***************************************************************************** - * Destroy: destroy Clone video thread output method - ***************************************************************************** - * Clean up all data and library connections - *****************************************************************************/ -static void Destroy( vlc_object_t *p_this ) +/* */ +static void YUVFromRGB( uint32_t i_argb, + uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v ) { - filter_t *p_filter = (filter_t *)p_this; - filter_sys_t *p_sys = p_filter->p_sys; - - if( p_sys->pp_font_attachments ) - { - for( int k = 0; k < p_sys->i_font_attachments; k++ ) - vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] ); - - free( p_sys->pp_font_attachments ); - } - -#ifdef HAVE_STYLES - if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml ); - free( p_sys->psz_fontfamily ); -#endif - - /* FcFini asserts calling the subfunction FcCacheFini() - * even if no other library functions have been made since FcInit(), - * so don't call it. */ + int i_red = ( i_argb & 0x00ff0000 ) >> 16; + int i_green = ( i_argb & 0x0000ff00 ) >> 8; + int i_blue = ( i_argb & 0x000000ff ); - FT_Done_Face( p_sys->p_face ); - FT_Done_FreeType( p_sys->p_library ); - free( p_sys ); + *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green + + 802 * i_blue + 4096 + 131072 ) >> 13, 235); + *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green + + 3598 * i_blue + 4096 + 1048576) >> 13, 240); + *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green + + -585 * i_blue + 4096 + 1048576) >> 13, 240); } /***************************************************************************** @@ -498,7 +315,7 @@ static int LoadFontsFromAttachments( filter_t *p_filter ) return VLC_EGENERIC; p_sys->i_font_attachments = 0; - p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof( input_attachment_t * )); + p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments)); if( !p_sys->pp_font_attachments ) return VLC_ENOMEM; @@ -522,1101 +339,1167 @@ static int LoadFontsFromAttachments( filter_t *p_filter ) return VLC_SUCCESS; } -/***************************************************************************** - * Render: place string in picture - ***************************************************************************** - * This function merges the previously rendered freetype glyphs into a picture - *****************************************************************************/ -static int Render( filter_t *p_filter, subpicture_region_t *p_region, - line_desc_t *p_line, int i_width, int i_height ) +static int GetFontSize( filter_t *p_filter ) { - VLC_UNUSED(p_filter); - static const uint8_t pi_gamma[16] = - {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; - - uint8_t *p_dst; - video_format_t fmt; - int i, x, y, i_pitch; - uint8_t i_y; /* YUV values, derived from incoming RGB */ - int8_t i_u, i_v; - - /* Create a new subpicture region */ - memset( &fmt, 0, sizeof(video_format_t) ); - fmt.i_chroma = VLC_CODEC_YUVP; - 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; - - assert( !p_region->p_picture ); - p_region->p_picture = picture_NewFromFormat( &fmt ); - if( !p_region->p_picture ) - return VLC_EGENERIC; - fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette)); - p_region->fmt = fmt; - - /* Calculate text color components */ - i_y = (uint8_t)(( 66 * p_line->i_red + 129 * p_line->i_green + - 25 * p_line->i_blue + 128) >> 8) + 16; - i_u = (int8_t)(( -38 * p_line->i_red - 74 * p_line->i_green + - 112 * p_line->i_blue + 128) >> 8) + 128; - i_v = (int8_t)(( 112 * p_line->i_red - 94 * p_line->i_green - - 18 * p_line->i_blue + 128) >> 8) + 128; + filter_sys_t *p_sys = p_filter->p_sys; + int i_size = 0; - /* Build palette */ - fmt.p_palette->i_entries = 16; - for( i = 0; i < 8; i++ ) + if( p_sys->i_default_font_size ) { - 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; + i_size = p_sys->i_default_font_size; } - for( i = 8; i < fmt.p_palette->i_entries; i++ ) + else { - fmt.p_palette->palette[i][0] = i * 16 * i_y / 256; - fmt.p_palette->palette[i][1] = i_u; - fmt.p_palette->palette[i][2] = i_v; - 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; + int i_ratio = var_GetInteger( p_filter, "freetype-rel-fontsize" ); + if( i_ratio > 0 ) + { + i_size = (int)p_filter->fmt_out.video.i_height / i_ratio; + p_filter->p_sys->i_display_height = p_filter->fmt_out.video.i_height; + } + } + if( i_size <= 0 ) + { + msg_Warn( p_filter, "invalid fontsize, using 12" ); + i_size = 12; } + return i_size; +} - p_dst = p_region->p_picture->Y_PIXELS; - i_pitch = p_region->p_picture->Y_PITCH; +static int SetFontSize( filter_t *p_filter, int i_size ) +{ + filter_sys_t *p_sys = p_filter->p_sys; - /* Initialize the region pixels */ - memset( p_dst, 0, i_pitch * p_region->fmt.i_height ); + if( !i_size ) + { + i_size = GetFontSize( p_filter ); - for( ; p_line != NULL; p_line = p_line->p_next ) + msg_Dbg( p_filter, "using fontsize: %i", i_size ); + } + + p_sys->i_font_size = i_size; + + if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) ) { - 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 ); - } + msg_Err( p_filter, "couldn't set font size to %d", i_size ); + return VLC_EGENERIC; + } + + return VLC_SUCCESS; +} + +#ifdef HAVE_STYLES +#ifdef HAVE_FONTCONFIG +static void FontConfig_BuildCache( filter_t *p_filter ) +{ + /* */ + msg_Dbg( p_filter, "Building font databases."); + mtime_t t1, t2; + t1 = mdate(); + +#ifdef WIN32 + dialog_progress_bar_t *p_dialog = NULL; + FcConfig *fcConfig = FcInitLoadConfig(); + + p_dialog = dialog_ProgressCreate( p_filter, + _("Building font cache"), + _("Please wait while your font cache is rebuilt.\n" + "This should take less than a few minutes."), NULL ); + +/* if( p_dialog ) + dialog_ProgressSet( p_dialog, NULL, 0.5 ); */ + + FcConfigBuildFonts( fcConfig ); + if( p_dialog ) + { +// dialog_ProgressSet( p_dialog, NULL, 1.0 ); + dialog_ProgressDestroy( p_dialog ); + p_dialog = NULL; + } +#endif + t2 = mdate(); + msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) ); +} + +/*** + * \brief Selects a font matching family, bold, italic provided + ***/ +static char* FontConfig_Select( FcConfig* config, const char* family, + bool b_bold, bool b_italic, int i_size, int *i_idx ) +{ + FcResult result = FcResultMatch; + FcPattern *pat, *p_pat; + FcChar8* val_s; + FcBool val_b; - if( p_line->i_width < i_width ) + /* Create a pattern and fills it */ + pat = FcPatternCreate(); + if (!pat) return NULL; + + /* */ + FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family ); + FcPatternAddBool( pat, FC_OUTLINE, FcTrue ); + FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN ); + FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL ); + if( i_size != -1 ) + { + char *psz_fontsize; + if( asprintf( &psz_fontsize, "%d", i_size ) != -1 ) { - 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; - } + FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize ); + free( psz_fontsize ); } + } - for( i = 0; p_line->pp_glyphs[i] != NULL; i++ ) - { - FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ]; + /* */ + FcDefaultSubstitute( pat ); + if( !FcConfigSubstitute( config, pat, FcMatchPattern ) ) + { + FcPatternDestroy( pat ); + return NULL; + } - i_offset = ( p_line->p_glyph_pos[ i ].y + - i_glyph_tmax - p_glyph->top + 2 ) * - i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 2 + - i_align_offset; + /* Find the best font for the pattern, destroy the pattern */ + p_pat = FcFontMatch( config, pat, &result ); + FcPatternDestroy( pat ); + if( !p_pat || result == FcResultNoMatch ) return NULL; - 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[i_offset+x] = - ((int)p_glyph->bitmap.buffer[i_bitmap_offset] + 8)/16; - } - i_offset += i_pitch; - } - } + /* Check the new pattern */ + 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; } - /* Outlining (find something better than nearest neighbour filtering ?) */ - if( 1 ) + if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) ) { - uint8_t *p_dst = p_region->p_picture->Y_PIXELS; - uint8_t *p_top = p_dst; /* Use 1st line as a cache */ - uint8_t left, current; + FcPatternDestroy( p_pat ); + return NULL; + } - for( y = 1; y < (int)fmt.i_height - 1; y++ ) - { - if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width ); - p_dst += p_region->p_picture->Y_PITCH; - left = 0; + /* 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 ); */ - for( x = 1; x < (int)fmt.i_width - 1; x++ ) - { - current = p_dst[x]; - 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->p_picture->Y_PITCH ] + p_dst[x + p_region->p_picture->Y_PITCH] + p_dst[x + 1 + p_region->p_picture->Y_PITCH]) / 16; - left = current; - } - } - memset( p_top, 0, fmt.i_width ); + if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) ) + { + FcPatternDestroy( p_pat ); + return NULL; } - return VLC_SUCCESS; + FcPatternDestroy( p_pat ); + return strdup( (const char*)val_s ); } +#endif -static void UnderlineGlyphYUVA( int i_line_thickness, int i_line_offset, bool 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, - subpicture_region_t *p_region) -{ - int i_pitch; - uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a; +#ifdef WIN32 +#define UNICODE +#define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" - p_dst_y = p_region->p_picture->Y_PIXELS; - p_dst_u = p_region->p_picture->U_PIXELS; - p_dst_v = p_region->p_picture->V_PIXELS; - p_dst_a = p_region->p_picture->A_PIXELS; - i_pitch = p_region->p_picture->A_PITCH; +static int GetFileFontByName( const char *font_name, char **psz_filename ) +{ + HKEY hKey; + wchar_t vbuffer[MAX_PATH]; + wchar_t dbuffer[256]; - 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; + if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey) != ERROR_SUCCESS ) + return 1; - for( int y = 0; y < i_line_thickness; y++ ) + for( int index = 0;; index++ ) { - 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( int x = 0; x < i_extra; x++ ) - { - bool b_ok = true; + DWORD vbuflen = MAX_PATH - 1; + DWORD dbuflen = 255; - /* break the underline around the tails of any glyphs which cross it */ - /* Strikethrough doesn't get broken */ - for( int z = x - i_line_thickness; - z < x + i_line_thickness && b_ok && (i_line_offset >= 0); - z++ ) - { - if( p_next_glyph && ( z >= i_extra ) ) - { - int i_row = i_line_offset + p_next_glyph->top + y; + if( RegEnumValueW( hKey, index, vbuffer, &vbuflen, + NULL, NULL, (LPBYTE)dbuffer, &dbuflen) != ERROR_SUCCESS ) + return 2; - 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 = false; - } - } - else if ((z > 0 ) && (z < p_this_glyph->bitmap.width)) - { - int i_row = i_line_offset + p_this_glyph->top + y; + char *psz_value = FromWide( vbuffer ); - if( ( p_this_glyph->bitmap.rows > i_row ) && - p_this_glyph->bitmap.buffer[p_this_glyph->bitmap.width * i_row + z] ) - { - b_ok = false; - } - } - } + char *s = strchr( psz_value,'(' ); + if( s != NULL && s != psz_value ) s[-1] = '\0'; - 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; - } + /* Manage concatenated font names */ + if( strchr( psz_value, '&') ) { + if( strcasestr( psz_value, font_name ) != NULL ) + break; + } + else { + if( strcasecmp( psz_value, font_name ) == 0 ) + break; } - i_offset += i_pitch; } + + *psz_filename = FromWide( dbuffer ); + return 0; +} + + +static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric, + DWORD type, LPARAM lParam) +{ + VLC_UNUSED( metric ); + if( (type & RASTER_FONTTYPE) ) return 1; + // if( lpelfe->elfScript ) FIXME + + return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam ); } -static void DrawBlack( line_desc_t *p_line, int i_width, subpicture_region_t *p_region, int xoffset, int yoffset ) +static char* Win32_Select( filter_t *p_filter, const char* family, + bool b_bold, bool b_italic, int i_size, int *i_idx ) { - uint8_t *p_dst = p_region->p_picture->A_PIXELS; - int i_pitch = p_region->p_picture->A_PITCH; - int y; + VLC_UNUSED( i_size ); + // msg_Dbg( p_filter, "Here in Win32_Select, asking for %s", family ); - for( ; p_line != NULL; p_line = p_line->p_next ) + /* */ + LOGFONT lf; + lf.lfCharSet = DEFAULT_CHARSET; + if( b_italic ) + lf.lfItalic = true; + if( b_bold ) + lf.lfWeight = FW_BOLD; + strncpy( (LPSTR)&lf.lfFaceName, family, 32); + + /* */ + char *psz_filename = NULL; + HDC hDC = GetDC( NULL ); + EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0); + ReleaseDC(NULL, hDC); + + if( psz_filename == NULL ) + return NULL; + + /* FIXME: increase i_idx, when concatenated strings */ + i_idx = 0; + + /* */ + char *psz_tmp; + if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 ) + return NULL; + return psz_tmp; +} +#endif + +#endif + + +/***************************************************************************** + * RenderYUVP: place string in picture + ***************************************************************************** + * This function merges the previously rendered freetype glyphs into a picture + *****************************************************************************/ +static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region, + line_desc_t *p_line, + FT_BBox *p_bbox ) +{ + VLC_UNUSED(p_filter); + static const uint8_t pi_gamma[16] = + {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + uint8_t *p_dst; + video_format_t fmt; + int i, x, y, i_pitch; + uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */ + + /* Create a new subpicture region */ + video_format_Init( &fmt, VLC_CODEC_YUVP ); + fmt.i_width = + fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4; + fmt.i_height = + fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4; + + assert( !p_region->p_picture ); + p_region->p_picture = picture_NewFromFormat( &fmt ); + if( !p_region->p_picture ) + return VLC_EGENERIC; + fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette)); + p_region->fmt = fmt; + + /* Calculate text color components + * Only use the first color */ + int i_alpha = 0xff - ((p_line->p_character[0].i_color >> 24) & 0xff); + YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v ); + + /* Build palette */ + fmt.p_palette->i_entries = 16; + for( i = 0; i < 8; i++ ) { - 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 ); - } + 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] * 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; + fmt.p_palette->palette[i][2] = i_v; + fmt.p_palette->palette[i][3] = pi_gamma[i]; + fmt.p_palette->palette[i][3] = + (int)fmt.p_palette->palette[i][3] * i_alpha / 255; + } + + p_dst = p_region->p_picture->Y_PIXELS; + i_pitch = p_region->p_picture->Y_PITCH; - if( p_line->i_width < i_width ) + /* Initialize the region pixels */ + memset( p_dst, 0, i_pitch * p_region->fmt.i_height ); + + for( ; p_line != NULL; p_line = p_line->p_next ) + { + int i_align_left = 0; + if( p_line->i_width < fmt.i_visible_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; - } + i_align_left = ( fmt.i_visible_width - p_line->i_width ); + else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT ) + i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2; } + int i_align_top = 0; - for( i = 0; p_line->pp_glyphs[i] != NULL; i++ ) + for( i = 0; i < p_line->i_character_count; i++ ) { - FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ]; + const line_character_t *ch = &p_line->p_character[i]; + FT_BitmapGlyph p_glyph = ch->p_glyph; - 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; + int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line; + int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin; - for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ ) + for( y = 0; y < p_glyph->bitmap.rows; y++ ) { - for( int x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ ) + for( x = 0; x < p_glyph->bitmap.width; x++ ) { - 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]); + if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] ) + p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] = + (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16; } - i_offset += i_pitch; } } } + + /* Outlining (find something better than nearest neighbour filtering ?) */ + if( 1 ) + { + uint8_t *p_dst = p_region->p_picture->Y_PIXELS; + uint8_t *p_top = p_dst; /* Use 1st line as a cache */ + uint8_t left, current; + + for( y = 1; y < (int)fmt.i_height - 1; y++ ) + { + if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width ); + p_dst += p_region->p_picture->Y_PITCH; + left = 0; + + for( x = 1; x < (int)fmt.i_width - 1; x++ ) + { + current = p_dst[x]; + 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->p_picture->Y_PITCH ] + p_dst[x + p_region->p_picture->Y_PITCH] + p_dst[x + 1 + p_region->p_picture->Y_PITCH]) / 16; + left = current; + } + } + memset( p_top, 0, fmt.i_width ); + } + + return VLC_SUCCESS; } /***************************************************************************** - * Render: place string in picture + * RenderYUVA: 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 ) +static inline void BlendYUVAPixel( picture_t *p_picture, + int i_picture_x, int i_picture_y, + int i_a, int i_y, int i_u, int i_v, + int i_alpha ) { - uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a; - video_format_t fmt; - int i, y, i_pitch, i_alpha; - uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */ - - 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_CODEC_YUVA; - 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; + int i_an = i_a * i_alpha / 255; - p_region->p_picture = picture_NewFromFormat( &fmt ); - if( !p_region->p_picture ) - return VLC_EGENERIC; - p_region->fmt = fmt; + uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x]; + uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x]; + uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x]; + uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x]; - /* Calculate text color components */ - YUVFromRGB( (p_line->i_red << 16) | - (p_line->i_green << 8) | - (p_line->i_blue ), - &i_y, &i_u, &i_v); - i_alpha = p_line->i_alpha; - - p_dst_y = p_region->p_picture->Y_PIXELS; - p_dst_u = p_region->p_picture->U_PIXELS; - p_dst_v = p_region->p_picture->V_PIXELS; - p_dst_a = p_region->p_picture->A_PIXELS; - i_pitch = p_region->p_picture->A_PITCH; - - /* Initialize the region pixels */ - if( p_filter->p_sys->i_effect != EFFECT_BACKGROUND ) + int i_ao = *p_a; + if( i_ao == 0 ) { - 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 ); + *p_y = i_y; + *p_u = i_u; + *p_v = i_v; + *p_a = i_an; } 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 ); + *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255; + if( *p_a != 0 ) + { + *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a; + *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a; + *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a; + } } - if( p_filter->p_sys->i_effect == EFFECT_OUTLINE || - p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT ) +} + +static inline void BlendYUVAGlyph( picture_t *p_picture, + int i_picture_x, int i_picture_y, + int i_a, int i_y, int i_u, int i_v, + FT_BitmapGlyph p_glyph ) +{ + for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ ) { - 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); + for( int dx = 0; dx < p_glyph->bitmap.width; dx++ ) + BlendYUVAPixel( p_picture, i_picture_x + dx, i_picture_y + dy, + i_a, i_y, i_u, i_v, + p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] ); } +} + +static inline void BlendYUVALine( picture_t *p_picture, + int i_picture_x, int i_picture_y, + int i_a, int i_y, int i_u, int i_v, + const line_character_t *p_current, + const line_character_t *p_next ) +{ + int i_line_width = p_current->p_glyph->bitmap.width; + if( p_next ) + i_line_width = p_next->p_glyph->left - p_current->p_glyph->left; - if( p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT ) + for( int dx = 0; dx < i_line_width; dx++ ) { - 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); + for( int dy = 0; dy < p_current->i_line_thickness; dy++ ) + BlendYUVAPixel( p_picture, + i_picture_x + dx, + i_picture_y + p_current->i_line_offset + dy, + i_a, i_y, i_u, i_v, 0xff ); + } +} - 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); +static int RenderYUVA( filter_t *p_filter, + subpicture_region_t *p_region, + line_desc_t *p_line_head, + FT_BBox *p_bbox, + int i_margin ) +{ + filter_sys_t *p_sys = p_filter->p_sys; - 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); + /* Create a new subpicture region */ + const int i_text_width = p_bbox->xMax - p_bbox->xMin; + const int i_text_height = p_bbox->yMax - p_bbox->yMin; + video_format_t fmt; + video_format_Init( &fmt, VLC_CODEC_YUVA ); + fmt.i_width = + fmt.i_visible_width = i_text_width + 2 * i_margin; + fmt.i_height = + fmt.i_visible_height = i_text_height + 2 * i_margin; - 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); - } + picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt ); + if( !p_region->p_picture ) + return VLC_EGENERIC; + p_region->fmt = fmt; - for( ; p_line != NULL; p_line = p_line->p_next ) + /* Initialize the picture background */ + uint8_t i_a = p_sys->i_background_opacity; + uint8_t i_y, i_u, i_v; + YUVFromRGB( p_sys->i_background_color, &i_y, &i_u, &i_v ); + + memset( p_picture->p[0].p_pixels, i_y, + p_picture->p[0].i_pitch * p_picture->p[0].i_lines ); + memset( p_picture->p[1].p_pixels, i_u, + p_picture->p[1].i_pitch * p_picture->p[1].i_lines ); + memset( p_picture->p[2].p_pixels, i_v, + p_picture->p[2].i_pitch * p_picture->p[2].i_lines ); + memset( p_picture->p[3].p_pixels, i_a, + p_picture->p[3].i_pitch * p_picture->p[3].i_lines ); + + /* Render outline glyphs in the first pass, and then the normal glyphs */ + for( int g = 0; g < 2; g++ ) { - 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 ) + /* Render all lines */ + for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next ) { - if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT ) + int i_align_left = i_margin; + if( p_line->i_width < i_text_width ) { - i_align_offset = i_width - p_line->i_width; + /* Left offset to take into account alignment */ + if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT ) + i_align_left += ( i_text_width - p_line->i_width ); + else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT ) + i_align_left += ( i_text_width - p_line->i_width ) / 2; } - else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT ) + int i_align_top = i_margin; + + /* Render all glyphs and underline/strikethrough */ + for( int i = 0; i < p_line->i_character_count; i++ ) { - i_align_offset = ( i_width - p_line->i_width ) / 2; + const line_character_t *ch = &p_line->p_character[i]; + FT_BitmapGlyph p_glyph = g == 0 ? ch->p_outline : ch->p_glyph; + if( !p_glyph ) + continue; + + uint32_t i_color = ch->i_color; + i_a = 0xff - ((i_color >> 24) & 0xff); + if( g == 0 ) + { + i_a = i_a * p_sys->i_outline_opacity / 255; + i_color = p_sys->i_outline_color; + } + YUVFromRGB( i_color, &i_y, &i_u, &i_v ); + + int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line; + int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin; + + BlendYUVAGlyph( p_picture, + i_glyph_x, i_glyph_y, + i_a, i_y, i_u, i_v, + p_glyph ); + + /* underline/strikethrough are only rendered for the normal glyph */ + if( g == 1 && ch->i_line_thickness > 0 ) + BlendYUVALine( p_picture, + i_glyph_x, i_glyph_y + p_glyph->top, + i_a, i_y, i_u, i_v, + &ch[0], + i + 1 < p_line->i_character_count ? &ch[1] : NULL ); } } + } - 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; + return VLC_SUCCESS; +} - if( p_line->b_new_color_mode ) - { - /* Every glyph can (and in fact must) have its own color */ - YUVFromRGB( p_line->p_fg_rgb[ i ], &i_y, &i_u, &i_v ); - } +static text_style_t *CreateStyle( char *psz_fontname, int i_font_size, + uint32_t i_font_color, uint32_t i_karaoke_bg_color, + int i_style_flags ) +{ + text_style_t *p_style = text_style_New(); + if( !p_style ) + return NULL; - for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ ) - { - for( int x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ ) - { - uint8_t i_y_local = i_y; - uint8_t i_u_local = i_u; - uint8_t i_v_local = i_v; + p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL; + p_style->i_font_size = i_font_size; + p_style->i_font_color = (i_font_color & 0x00ffffff) >> 0; + p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24; + p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >> 0; + p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24; + p_style->i_style_flags |= i_style_flags; + return p_style; +} - if( p_line->p_fg_bg_ratio != 0x00 ) - { - int i_split = p_glyph->bitmap.width * - p_line->p_fg_bg_ratio[ i ] / 0x7f; +static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size, + uint32_t i_color, uint32_t i_karaoke_bg_color ) +{ + if( !p_font ) + return VLC_EGENERIC; - if( x > i_split ) - { - YUVFromRGB( p_line->p_bg_rgb[ i ], - &i_y_local, &i_u_local, &i_v_local ); - } - } + font_stack_t *p_new = malloc( sizeof(*p_new) ); + if( !p_new ) + return VLC_ENOMEM; - 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_new->p_next = NULL; - p_dst_u[i_offset+x] = i_u; - p_dst_v[i_offset+x] = i_v; + if( psz_name ) + p_new->psz_name = strdup( psz_name ); + else + p_new->psz_name = NULL; - if( p_filter->p_sys->i_effect == EFFECT_BACKGROUND ) - p_dst_a[i_offset+x] = 0xff; - } - } - i_offset += i_pitch; - } + p_new->i_size = i_size; + p_new->i_color = i_color; + p_new->i_karaoke_bg_color = i_karaoke_bg_color; - 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, - p_region); - } - } + if( !*p_font ) + { + *p_font = p_new; } + else + { + font_stack_t *p_last; - /* 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; + for( p_last = *p_font; + p_last->p_next; + p_last = p_last->p_next ) + ; + p_last->p_next = p_new; + } 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 - * needed glyphs into memory. It is used as pf_add_string callback in - * the vout method by this module - */ -static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, - subpicture_region_t *p_region_in ) +static int PopFont( font_stack_t **p_font ) { - filter_sys_t *p_sys = p_filter->p_sys; - 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 = NULL, i_char, *psz_line_start; - size_t i_string_length; - char *psz_string; - int i_font_color, i_font_alpha, i_font_size, i_red, i_green, i_blue; - vlc_value_t val; - int i_scale = 1000; + font_stack_t *p_last, *p_next_to_last; - FT_BBox line; - FT_BBox glyph_size; - FT_Vector result; - FT_Glyph tmp_glyph; - - /* Sanity check */ - if( !p_region_in || !p_region_out ) return VLC_EGENERIC; - psz_string = p_region_in->psz_text; - if( !psz_string || !*psz_string ) return VLC_EGENERIC; - - if( VLC_SUCCESS == var_Get( p_filter, "scale", &val )) - i_scale = val.i_int; + if( !p_font || !*p_font ) + return VLC_EGENERIC; - 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 ) * i_scale / 1000; - } - else + p_next_to_last = NULL; + for( p_last = *p_font; + p_last->p_next; + p_last = p_last->p_next ) { - 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_scale / 1000; + p_next_to_last = p_last; } - 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; - SetFontSize( p_filter, i_font_size ); - - i_red = ( i_font_color & 0x00FF0000 ) >> 16; - i_green = ( i_font_color & 0x0000FF00 ) >> 8; - i_blue = i_font_color & 0x000000FF; + if( p_next_to_last ) + p_next_to_last->p_next = NULL; + else + *p_font = NULL; - result.x = result.y = 0; - line.xMin = line.xMax = line.yMin = line.yMax = 0; + free( p_last->psz_name ); + free( p_last ); -#if defined(WORDS_BIGENDIAN) - psz_unicode = ToCharset( "UCS-4BE", psz_string, &i_string_length ); -#else - psz_unicode = ToCharset( "UCS-4LE", psz_string, &i_string_length ); -#endif - if( psz_unicode == NULL ) - goto error; - psz_unicode_orig = psz_unicode; - i_string_length /= 4; + return VLC_SUCCESS; +} -#if defined(HAVE_FRIBIDI) - { - uint32_t *p_fribidi_string; - size_t start_pos; - size_t pos = 0; +static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size, + uint32_t *i_color, uint32_t *i_karaoke_bg_color ) +{ + font_stack_t *p_last; - p_fribidi_string = malloc( (i_string_length + 1) * sizeof(uint32_t) ); - if( !p_fribidi_string ) - goto error; + if( !p_font || !*p_font ) + return VLC_EGENERIC; - /* 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) - { -#if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0) - FriBidiCharType base_dir = FRIBIDI_TYPE_LTR; -#else - FriBidiParType base_dir = FRIBIDI_PAR_LTR; -#endif - fribidi_log2vis((FriBidiChar*)psz_unicode + start_pos, - pos - start_pos, - &base_dir, - (FriBidiChar*)p_fribidi_string + start_pos, - 0, 0, 0); - } - } + for( p_last=*p_font; + p_last->p_next; + p_last=p_last->p_next ) + ; - free( psz_unicode_orig ); - psz_unicode = psz_unicode_orig = p_fribidi_string; - p_fribidi_string[ i_string_length ] = 0; - } -#endif + *psz_name = p_last->psz_name; + *i_size = p_last->i_size; + *i_color = p_last->i_color; + *i_karaoke_bg_color = p_last->i_karaoke_bg_color; - /* Calculate relative glyph positions and a bounding box for the - * entire string */ - if( !(p_line = NewLine( strlen( psz_string ))) ) - goto error; - p_lines = p_line; - i_pen_x = i_pen_y = 0; - i_previous = i = 0; - psz_line_start = psz_unicode; + return VLC_SUCCESS; +} -#define face p_sys->p_face -#define glyph face->glyph +static const struct { + const char *psz_name; + uint32_t i_value; +} p_html_colors[] = { + /* Official html colors */ + { "Aqua", 0x00FFFF }, + { "Black", 0x000000 }, + { "Blue", 0x0000FF }, + { "Fuchsia", 0xFF00FF }, + { "Gray", 0x808080 }, + { "Green", 0x008000 }, + { "Lime", 0x00FF00 }, + { "Maroon", 0x800000 }, + { "Navy", 0x000080 }, + { "Olive", 0x808000 }, + { "Purple", 0x800080 }, + { "Red", 0xFF0000 }, + { "Silver", 0xC0C0C0 }, + { "Teal", 0x008080 }, + { "White", 0xFFFFFF }, + { "Yellow", 0xFFFF00 }, + + /* Common ones */ + { "AliceBlue", 0xF0F8FF }, + { "AntiqueWhite", 0xFAEBD7 }, + { "Aqua", 0x00FFFF }, + { "Aquamarine", 0x7FFFD4 }, + { "Azure", 0xF0FFFF }, + { "Beige", 0xF5F5DC }, + { "Bisque", 0xFFE4C4 }, + { "Black", 0x000000 }, + { "BlanchedAlmond", 0xFFEBCD }, + { "Blue", 0x0000FF }, + { "BlueViolet", 0x8A2BE2 }, + { "Brown", 0xA52A2A }, + { "BurlyWood", 0xDEB887 }, + { "CadetBlue", 0x5F9EA0 }, + { "Chartreuse", 0x7FFF00 }, + { "Chocolate", 0xD2691E }, + { "Coral", 0xFF7F50 }, + { "CornflowerBlue", 0x6495ED }, + { "Cornsilk", 0xFFF8DC }, + { "Crimson", 0xDC143C }, + { "Cyan", 0x00FFFF }, + { "DarkBlue", 0x00008B }, + { "DarkCyan", 0x008B8B }, + { "DarkGoldenRod", 0xB8860B }, + { "DarkGray", 0xA9A9A9 }, + { "DarkGrey", 0xA9A9A9 }, + { "DarkGreen", 0x006400 }, + { "DarkKhaki", 0xBDB76B }, + { "DarkMagenta", 0x8B008B }, + { "DarkOliveGreen", 0x556B2F }, + { "Darkorange", 0xFF8C00 }, + { "DarkOrchid", 0x9932CC }, + { "DarkRed", 0x8B0000 }, + { "DarkSalmon", 0xE9967A }, + { "DarkSeaGreen", 0x8FBC8F }, + { "DarkSlateBlue", 0x483D8B }, + { "DarkSlateGray", 0x2F4F4F }, + { "DarkSlateGrey", 0x2F4F4F }, + { "DarkTurquoise", 0x00CED1 }, + { "DarkViolet", 0x9400D3 }, + { "DeepPink", 0xFF1493 }, + { "DeepSkyBlue", 0x00BFFF }, + { "DimGray", 0x696969 }, + { "DimGrey", 0x696969 }, + { "DodgerBlue", 0x1E90FF }, + { "FireBrick", 0xB22222 }, + { "FloralWhite", 0xFFFAF0 }, + { "ForestGreen", 0x228B22 }, + { "Fuchsia", 0xFF00FF }, + { "Gainsboro", 0xDCDCDC }, + { "GhostWhite", 0xF8F8FF }, + { "Gold", 0xFFD700 }, + { "GoldenRod", 0xDAA520 }, + { "Gray", 0x808080 }, + { "Grey", 0x808080 }, + { "Green", 0x008000 }, + { "GreenYellow", 0xADFF2F }, + { "HoneyDew", 0xF0FFF0 }, + { "HotPink", 0xFF69B4 }, + { "IndianRed", 0xCD5C5C }, + { "Indigo", 0x4B0082 }, + { "Ivory", 0xFFFFF0 }, + { "Khaki", 0xF0E68C }, + { "Lavender", 0xE6E6FA }, + { "LavenderBlush", 0xFFF0F5 }, + { "LawnGreen", 0x7CFC00 }, + { "LemonChiffon", 0xFFFACD }, + { "LightBlue", 0xADD8E6 }, + { "LightCoral", 0xF08080 }, + { "LightCyan", 0xE0FFFF }, + { "LightGoldenRodYellow", 0xFAFAD2 }, + { "LightGray", 0xD3D3D3 }, + { "LightGrey", 0xD3D3D3 }, + { "LightGreen", 0x90EE90 }, + { "LightPink", 0xFFB6C1 }, + { "LightSalmon", 0xFFA07A }, + { "LightSeaGreen", 0x20B2AA }, + { "LightSkyBlue", 0x87CEFA }, + { "LightSlateGray", 0x778899 }, + { "LightSlateGrey", 0x778899 }, + { "LightSteelBlue", 0xB0C4DE }, + { "LightYellow", 0xFFFFE0 }, + { "Lime", 0x00FF00 }, + { "LimeGreen", 0x32CD32 }, + { "Linen", 0xFAF0E6 }, + { "Magenta", 0xFF00FF }, + { "Maroon", 0x800000 }, + { "MediumAquaMarine", 0x66CDAA }, + { "MediumBlue", 0x0000CD }, + { "MediumOrchid", 0xBA55D3 }, + { "MediumPurple", 0x9370D8 }, + { "MediumSeaGreen", 0x3CB371 }, + { "MediumSlateBlue", 0x7B68EE }, + { "MediumSpringGreen", 0x00FA9A }, + { "MediumTurquoise", 0x48D1CC }, + { "MediumVioletRed", 0xC71585 }, + { "MidnightBlue", 0x191970 }, + { "MintCream", 0xF5FFFA }, + { "MistyRose", 0xFFE4E1 }, + { "Moccasin", 0xFFE4B5 }, + { "NavajoWhite", 0xFFDEAD }, + { "Navy", 0x000080 }, + { "OldLace", 0xFDF5E6 }, + { "Olive", 0x808000 }, + { "OliveDrab", 0x6B8E23 }, + { "Orange", 0xFFA500 }, + { "OrangeRed", 0xFF4500 }, + { "Orchid", 0xDA70D6 }, + { "PaleGoldenRod", 0xEEE8AA }, + { "PaleGreen", 0x98FB98 }, + { "PaleTurquoise", 0xAFEEEE }, + { "PaleVioletRed", 0xD87093 }, + { "PapayaWhip", 0xFFEFD5 }, + { "PeachPuff", 0xFFDAB9 }, + { "Peru", 0xCD853F }, + { "Pink", 0xFFC0CB }, + { "Plum", 0xDDA0DD }, + { "PowderBlue", 0xB0E0E6 }, + { "Purple", 0x800080 }, + { "Red", 0xFF0000 }, + { "RosyBrown", 0xBC8F8F }, + { "RoyalBlue", 0x4169E1 }, + { "SaddleBrown", 0x8B4513 }, + { "Salmon", 0xFA8072 }, + { "SandyBrown", 0xF4A460 }, + { "SeaGreen", 0x2E8B57 }, + { "SeaShell", 0xFFF5EE }, + { "Sienna", 0xA0522D }, + { "Silver", 0xC0C0C0 }, + { "SkyBlue", 0x87CEEB }, + { "SlateBlue", 0x6A5ACD }, + { "SlateGray", 0x708090 }, + { "SlateGrey", 0x708090 }, + { "Snow", 0xFFFAFA }, + { "SpringGreen", 0x00FF7F }, + { "SteelBlue", 0x4682B4 }, + { "Tan", 0xD2B48C }, + { "Teal", 0x008080 }, + { "Thistle", 0xD8BFD8 }, + { "Tomato", 0xFF6347 }, + { "Turquoise", 0x40E0D0 }, + { "Violet", 0xEE82EE }, + { "Wheat", 0xF5DEB3 }, + { "White", 0xFFFFFF }, + { "WhiteSmoke", 0xF5F5F5 }, + { "Yellow", 0xFFFF00 }, + { "YellowGreen", 0x9ACD32 }, + + { NULL, 0 } +}; - while( *psz_unicode ) +static int HandleFontAttributes( xml_reader_t *p_xml_reader, + font_stack_t **p_fonts ) +{ + int rv; + char *psz_fontname = NULL; + uint32_t i_font_color = 0xffffff; + int i_font_alpha = 0; + uint32_t i_karaoke_bg_color = 0x00ffffff; + 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_karaoke_bg_color )) { - i_char = *psz_unicode++; - if( i_char == '\r' ) /* ignore CR chars wherever they may be */ - { - continue; - } - - if( i_char == '\n' ) - { - psz_line_start = psz_unicode; - if( !(p_next = NewLine( strlen( psz_string ))) ) - goto error; - p_line->p_next = p_next; - p_line->i_width = line.xMax; - p_line->i_height = face->size->metrics.height >> 6; - p_line->pp_glyphs[ i ] = NULL; - p_line->i_alpha = i_font_alpha; - p_line->i_red = i_red; - p_line->i_green = i_green; - p_line->i_blue = i_blue; - p_prev = p_line; - p_line = p_next; - result.x = __MAX( result.x, line.xMax ); - result.y += face->size->metrics.height >> 6; - i_pen_x = 0; - i_previous = i = 0; - line.xMin = line.xMax = line.yMin = line.yMax = 0; - i_pen_y += face->size->metrics.height >> 6; -#if 0 - msg_Dbg( p_filter, "Creating new line, i is %d", i ); -#endif - continue; - } + psz_fontname = strdup( psz_fontname ); + i_font_size = i_font_size; + } + i_font_alpha = (i_font_color >> 24) & 0xff; + i_font_color &= 0x00ffffff; - i_glyph_index = FT_Get_Char_Index( face, i_char ); - if( p_sys->i_use_kerning && i_glyph_index - && i_previous ) + const char *name, *value; + while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL ) + { + if( !strcasecmp( "face", name ) ) { - FT_Vector delta; - FT_Get_Kerning( face, i_previous, i_glyph_index, - ft_kerning_default, &delta ); - i_pen_x += delta.x >> 6; - + free( psz_fontname ); + psz_fontname = strdup( value ); } - p_line->p_glyph_pos[ i ].x = i_pen_x; - p_line->p_glyph_pos[ i ].y = i_pen_y; - i_error = FT_Load_Glyph( face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ); - if( i_error ) + else if( !strcasecmp( "size", name ) ) { - i_error = FT_Load_Glyph( face, i_glyph_index, FT_LOAD_DEFAULT ); - if( i_error ) + if( ( *value == '+' ) || ( *value == '-' ) ) { - msg_Err( p_filter, "unable to render text FT_Load_Glyph returned" - " %d", i_error ); - goto error; + int i_value = atoi( 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( value ); } - i_error = FT_Get_Glyph( glyph, &tmp_glyph ); - if( 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 ) - { - FT_Done_Glyph( tmp_glyph ); - continue; - } - p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph; - - /* Do rest */ - 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 ) + else if( !strcasecmp( "color", name ) ) { - FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] ); - p_line->pp_glyphs[ i ] = NULL; - FreeLine( p_line ); - p_line = NewLine( strlen( psz_string )); - if( p_prev ) p_prev->p_next = p_line; - else p_lines = p_line; - - uint32_t *psz_unicode_saved = psz_unicode; - while( psz_unicode > psz_line_start && *psz_unicode != ' ' ) - { - psz_unicode--; - } - if( psz_unicode == psz_line_start ) - { /* try harder to break that line */ - psz_unicode = psz_unicode_saved; - while( psz_unicode > psz_line_start && - *psz_unicode != '_' && *psz_unicode != '/' && - *psz_unicode != '\\' && *psz_unicode != '.' ) - { - psz_unicode--; - } - } - if( psz_unicode == psz_line_start ) + if( value[0] == '#' ) { - msg_Warn( p_filter, "unbreakable string" ); - goto error; + i_font_color = strtol( value + 1, NULL, 16 ); + i_font_color &= 0x00ffffff; } else { - *psz_unicode = '\n'; + for( int i = 0; p_html_colors[i].psz_name != NULL; i++ ) + { + if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) ) + { + i_font_color = p_html_colors[i].i_value; + break; + } + } } - psz_unicode = psz_line_start; - i_pen_x = 0; - i_previous = i = 0; - line.xMin = line.xMax = line.yMin = line.yMax = 0; - continue; } - line.yMax = __MAX( line.yMax, glyph_size.yMax ); - line.yMin = __MIN( line.yMin, glyph_size.yMin ); - - i_previous = i_glyph_index; - i_pen_x += glyph->advance.x >> 6; - i++; + else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) ) + { + i_font_alpha = strtol( value + 1, NULL, 16 ); + i_font_alpha &= 0xff; + } } + rv = PushFont( p_fonts, + psz_fontname, + i_font_size, + (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24), + i_karaoke_bg_color ); - p_line->i_width = line.xMax; - p_line->i_height = face->size->metrics.height >> 6; - p_line->pp_glyphs[ i ] = NULL; - p_line->i_alpha = i_font_alpha; - p_line->i_red = i_red; - p_line->i_green = i_green; - p_line->i_blue = i_blue; - result.x = __MAX( result.x, line.xMax ); - result.y += line.yMax - line.yMin; - -#undef face -#undef glyph - - p_region_out->i_x = p_region_in->i_x; - p_region_out->i_y = p_region_in->i_y; - - if( var_InheritBool( 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 ); - - free( psz_unicode_orig ); - FreeLines( p_lines ); - return VLC_SUCCESS; + free( psz_fontname ); - error: - free( psz_unicode_orig ); - FreeLines( p_lines ); - return VLC_EGENERIC; + return rv; } -#ifdef HAVE_STYLES -static ft_style_t *CreateStyle( char *psz_fontname, int i_font_size, - uint32_t i_font_color, uint32_t i_karaoke_bg_color, bool b_bold, - bool b_italic, bool b_uline, bool b_through ) +/* Turn any multiple-whitespaces into single spaces */ +static void HandleWhiteSpace( char *psz_node ) { - ft_style_t *p_style = malloc( sizeof( ft_style_t )); - - if( p_style ) + char *s = strpbrk( psz_node, "\t\r\n " ); + while( s ) { - p_style->i_font_size = i_font_size; - p_style->i_font_color = i_font_color; - p_style->i_karaoke_bg_color = i_karaoke_bg_color; - p_style->b_italic = b_italic; - p_style->b_bold = b_bold; - p_style->b_underline = b_uline; - p_style->b_through = b_through; + int i_whitespace = strspn( s, "\t\r\n " ); - p_style->psz_fontname = strdup( psz_fontname ); - } - return p_style; -} + if( i_whitespace > 1 ) + memmove( &s[1], + &s[i_whitespace], + strlen( s ) - i_whitespace + 1 ); + *s++ = ' '; -static void DeleteStyle( ft_style_t *p_style ) -{ - if( p_style ) - { - free( p_style->psz_fontname ); - free( p_style ); + s = strpbrk( s, "\t\r\n " ); } } -static bool StyleEquals( ft_style_t *s1, ft_style_t *s2 ) + +static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys, + font_stack_t **p_fonts, + int i_style_flags ) { - if( !s1 || !s2 ) - return false; - if( s1 == s2 ) - return true; + char *psz_fontname = NULL; + uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff; + uint32_t i_karaoke_bg_color = i_font_color; + int i_font_size = p_sys->i_font_size; - 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_through == s2->b_through ) && - ( s1->b_bold == s2->b_bold ) && - ( s1->b_underline == s2->b_underline ) && - ( !strcmp( s1->psz_fontname, s2->psz_fontname ))) - { - return true; - } - return false; + if( PeekFont( p_fonts, &psz_fontname, &i_font_size, + &i_font_color, &i_karaoke_bg_color ) ) + return NULL; + + return CreateStyle( psz_fontname, i_font_size, i_font_color, + i_karaoke_bg_color, + i_style_flags ); } -static void IconvText( filter_t *p_filter, const char *psz_string, - size_t *i_string_length, uint32_t **ppsz_unicode ) +static unsigned SetupText( filter_t *p_filter, + uint32_t *psz_text_out, + text_style_t **pp_styles, + uint32_t *pi_k_dates, + + const char *psz_text_in, + text_style_t *p_style, + uint32_t i_k_date ) { - *i_string_length = 0; - if( *ppsz_unicode == NULL ) - return; + size_t i_string_length; - size_t i_length; - uint32_t *psz_tmp = + size_t i_string_bytes; #if defined(WORDS_BIGENDIAN) - ToCharset( "UCS-4BE", psz_string, &i_length ); + uint32_t *psz_tmp = ToCharset( "UCS-4BE", psz_text_in, &i_string_bytes ); #else - ToCharset( "UCS-4LE", psz_string, &i_length ); + uint32_t *psz_tmp = ToCharset( "UCS-4LE", psz_text_in, &i_string_bytes ); #endif - if( !psz_tmp ) + if( psz_tmp ) + { + memcpy( psz_text_out, psz_tmp, i_string_bytes ); + i_string_length = i_string_bytes / 4; + free( psz_tmp ); + } + else { msg_Warn( p_filter, "failed to convert string to unicode (%m)" ); - return; + i_string_length = 0; } - memcpy( *ppsz_unicode, psz_tmp, i_length ); - *i_string_length = i_length / 4; - - free( psz_tmp ); -} -static ft_style_t *GetStyleFromFontStack( filter_sys_t *p_sys, - font_stack_t **p_fonts, bool b_bold, bool b_italic, - bool b_uline, bool b_through ) -{ - ft_style_t *p_style = NULL; - - char *psz_fontname = NULL; - uint32_t i_font_color = p_sys->i_font_color & 0x00ffffff; - uint32_t i_karaoke_bg_color = i_font_color; - int i_font_size = p_sys->i_font_size; - - if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size, - &i_font_color, &i_karaoke_bg_color )) + if( i_string_length > 0 ) { - p_style = CreateStyle( psz_fontname, i_font_size, i_font_color, - i_karaoke_bg_color, b_bold, b_italic, b_uline, b_through ); + for( unsigned i = 0; i < i_string_length; i++ ) + pp_styles[i] = p_style; } - return p_style; + else + { + text_style_Delete( p_style ); + } + if( i_string_length > 0 && pi_k_dates ) + { + for( unsigned i = 0; i < i_string_length; i++ ) + pi_k_dates[i] = i_k_date; + } + return i_string_length; } -static int RenderTag( filter_t *p_filter, FT_Face p_face, int i_font_color, - bool b_uline, bool b_through, bool b_bold, - bool b_italic, int i_karaoke_bgcolor, - line_desc_t *p_line, uint32_t *psz_unicode, - int *pi_pen_x, int i_pen_y, int *pi_start, - FT_Vector *p_result ) +static int ProcessNodes( filter_t *p_filter, + uint32_t *psz_text, + text_style_t **pp_styles, + uint32_t *pi_k_dates, + int *pi_len, + xml_reader_t *p_xml_reader, + text_style_t *p_font_style ) { - FT_BBox line; - int i_yMin, i_yMax; - int i; - bool b_first_on_line = true; - - int i_previous = 0; - int i_pen_x_start = *pi_pen_x; - - uint32_t *psz_unicode_start = psz_unicode; + int rv = VLC_SUCCESS; + filter_sys_t *p_sys = p_filter->p_sys; + int i_text_length = 0; + font_stack_t *p_fonts = NULL; + uint32_t i_k_date = 0; - line.xMin = line.xMax = line.yMin = line.yMax = 0; + int i_style_flags = 0; - /* Account for part of line already in position */ - for( i = 0; i<*pi_start; i++ ) + if( p_font_style ) { - 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 ); + rv = PushFont( &p_fonts, + p_font_style->psz_fontname, + p_font_style->i_font_size, + (p_font_style->i_font_color & 0xffffff) | + ((p_font_style->i_font_alpha & 0xff) << 24), + (p_font_style->i_karaoke_background_color & 0xffffff) | + ((p_font_style->i_karaoke_background_alpha & 0xff) << 24)); + + i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD | + STYLE_ITALIC | + STYLE_UNDERLINE | + STYLE_STRIKEOUT); } - i_yMin = line.yMin; - i_yMax = line.yMax; - - if( line.xMax > 0 ) - b_first_on_line = false; - - while( *psz_unicode && ( *psz_unicode != '\n' ) ) +#ifdef HAVE_STYLES + else { - 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_NO_BITMAP | FT_LOAD_DEFAULT ); - if( i_error ) - { - 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; - } - } - - /* Do synthetic styling now that Freetype supports it; - * ie. if the font we have loaded is NOT already in the - * style that the tags want, then switch it on; if they - * are then don't. */ - if (b_bold && !( p_face->style_flags & FT_STYLE_FLAG_BOLD )) - FT_GlyphSlot_Embolden( p_face->glyph ); - if (b_italic && !( p_face->style_flags & FT_STYLE_FLAG_ITALIC )) - FT_GlyphSlot_Oblique( p_face->glyph ); + rv = PushFont( &p_fonts, + p_sys->psz_fontfamily, + p_sys->i_font_size, + (p_sys->i_font_color & 0xffffff) | + (((255-p_sys->i_font_opacity) & 0xff) << 24), + 0x00ffffff ); + } +#endif - 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 || b_through ) - { - 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; - if (b_through) - { - /* Move the baseline to make it strikethrough instead of - * underline. That means that strikethrough takes precedence - */ - float aDescent = FT_FLOOR(FT_MulFix(p_face->descender*2, - p_face->size->metrics.y_scale)); - - p_line->pi_underline_offset[ i ] -= - ( aDescent < 0 ) ? -aDescent : aDescent; - } - } + if( rv != VLC_SUCCESS ) + return rv; - p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph; - p_line->p_fg_rgb[ i ] = i_font_color & 0x00ffffff; - p_line->p_bg_rgb[ i ] = i_karaoke_bgcolor & 0x00ffffff; - p_line->p_fg_bg_ratio[ i ] = 0x00; + const char *node; + int type; - 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 ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 ) + { + switch ( type ) { - for( ; i >= *pi_start; i-- ) - FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] ); - i = *pi_start; + case XML_READER_ENDELEM: + if( !strcasecmp( "font", node ) ) + PopFont( &p_fonts ); + else if( !strcasecmp( "b", node ) ) + i_style_flags &= ~STYLE_BOLD; + else if( !strcasecmp( "i", node ) ) + i_style_flags &= ~STYLE_ITALIC; + else if( !strcasecmp( "u", node ) ) + i_style_flags &= ~STYLE_UNDERLINE; + else if( !strcasecmp( "s", node ) ) + i_style_flags &= ~STYLE_STRIKEOUT; + break; - while( psz_unicode > psz_unicode_start && *psz_unicode != ' ' ) - { - psz_unicode--; - } - if( psz_unicode == psz_unicode_start ) - { - if( b_first_on_line ) + case XML_READER_STARTELEM: + if( !strcasecmp( "font", node ) ) + HandleFontAttributes( p_xml_reader, &p_fonts ); + else if( !strcasecmp( "b", node ) ) + i_style_flags |= STYLE_BOLD; + else if( !strcasecmp( "i", node ) ) + i_style_flags |= STYLE_ITALIC; + else if( !strcasecmp( "u", node ) ) + i_style_flags |= STYLE_UNDERLINE; + else if( !strcasecmp( "s", node ) ) + i_style_flags |= STYLE_STRIKEOUT; + else if( !strcasecmp( "br", node ) ) { - msg_Warn( p_filter, "unbreakable string" ); - p_line->pp_glyphs[ i ] = NULL; - return VLC_EGENERIC; + i_text_length += SetupText( p_filter, + &psz_text[i_text_length], + &pp_styles[i_text_length], + pi_k_dates ? &pi_k_dates[i_text_length] : NULL, + "\n", + GetStyleFromFontStack( p_sys, + &p_fonts, + i_style_flags ), + i_k_date ); } - *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; + else if( !strcasecmp( "k", node ) ) + { + /* Karaoke tags */ + const char *name, *value; + while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL ) + { + if( !strcasecmp( "t", name ) && value ) + i_k_date += atoi( value ); + } + } + break; - 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 ) ); - return VLC_SUCCESS; - } - else + case XML_READER_TEXT: { - *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; + char *psz_node = strdup( node ); + if( unlikely(!psz_node) ) + break; - continue; + HandleWhiteSpace( psz_node ); + resolve_xml_special_chars( psz_node ); + + i_text_length += SetupText( p_filter, + &psz_text[i_text_length], + &pp_styles[i_text_length], + pi_k_dates ? &pi_k_dates[i_text_length] : NULL, + psz_node, + GetStyleFromFontStack( p_sys, + &p_fonts, + i_style_flags ), + i_k_date ); + free( psz_node ); + break; + } } - 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; + *pi_len = i_text_length; - /* 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 if( psz_unicode > psz_unicode_start ) - { - for( i=0; psz_unicode[ i ]; i++ ) - psz_unicode_start[ i ] = psz_unicode[ i ]; - psz_unicode_start[ i ] = '\0'; - } + while( VLC_SUCCESS == PopFont( &p_fonts ) ); return VLC_SUCCESS; } -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 ) +static void FreeLine( line_desc_t *p_line ) { - size_t i_string_length; - - IconvText( p_filter, psz_text_in, &i_string_length, psz_text_out ); - *psz_text_out += i_string_length; - - if( ppp_styles && ppi_run_lengths ) + for( int i = 0; i < p_line->i_character_count; i++ ) { - (*pi_runs)++; + line_character_t *ch = &p_line->p_character[i]; + FT_Done_Glyph( (FT_Glyph)ch->p_glyph ); + if( ch->p_outline ) + FT_Done_Glyph( (FT_Glyph)ch->p_outline ); + } - /* XXX this logic looks somewhat broken */ + free( p_line->p_character ); + free( p_line ); +} - if( *ppp_styles ) - { - *ppp_styles = realloc_or_free( *ppp_styles, - *pi_runs * sizeof( ft_style_t * ) ); - } - else if( *pi_runs == 1 ) - { - *ppp_styles = malloc( *pi_runs * sizeof( ft_style_t * ) ); - } +static void FreeLines( line_desc_t *p_lines ) +{ + for( line_desc_t *p_line = p_lines; p_line != NULL; ) + { + line_desc_t *p_next = p_line->p_next; + FreeLine( p_line ); + p_line = p_next; + } +} - /* 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; - } +static line_desc_t *NewLine( int i_count ) +{ + line_desc_t *p_line = malloc( sizeof(*p_line) ); - /* XXX more iffy logic */ + if( !p_line ) + return NULL; - if( *ppi_run_lengths ) - { - *ppi_run_lengths = realloc_or_free( *ppi_run_lengths, - *pi_runs * sizeof( uint32_t ) ); - } - else if( *pi_runs == 1 ) - { - *ppi_run_lengths = (uint32_t *) - malloc( *pi_runs * sizeof( uint32_t ) ); - } + p_line->p_next = NULL; + p_line->i_width = 0; + p_line->i_base_line = 0; + p_line->i_character_count = 0; - /* same remarks here */ - if( *ppi_run_lengths ) - { - (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length; - } + p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) ); + if( !p_line->p_character ) + { + free( p_line ); + return NULL; } - /* If we couldn't use the p_style argument due to memory allocation - * problems above, release it here. - */ - if( p_style ) DeleteStyle( p_style ); + return p_line; } -static int CheckForEmbeddedFont( filter_sys_t *p_sys, FT_Face *pp_face, ft_style_t *p_style ) +static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style ) { for( int k = 0; k < p_sys->i_font_attachments; k++ ) { @@ -1632,960 +1515,964 @@ static int CheckForEmbeddedFont( filter_sys_t *p_sys, FT_Face *pp_face, ft_style { if( p_face ) { - bool match = !strcasecmp( p_face->family_name, - p_style->psz_fontname ); - - if( p_face->style_flags & FT_STYLE_FLAG_BOLD ) - match = match && p_style->b_bold; - else - match = match && !p_style->b_bold; - - if( p_face->style_flags & FT_STYLE_FLAG_ITALIC ) - match = match && p_style->b_italic; - else - match = match && !p_style->b_italic; - - if( match ) - { - *pp_face = p_face; - return VLC_SUCCESS; - } + int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD) ? STYLE_BOLD : 0) | + ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0); + if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) && + (p_style->i_style_flags & (STYLE_BOLD | STYLE_BOLD)) == i_style_received ) + return p_face; FT_Done_Face( p_face ); } i_font_idx++; } } - return VLC_EGENERIC; + return NULL; } -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, - - bool b_karaoke, - uint32_t i_k_runs, - uint32_t *pi_k_run_lengths, - uint32_t *pi_k_durations ) +static FT_Face LoadFace( filter_t *p_filter, + const text_style_t *p_style ) { - filter_sys_t *p_sys = p_filter->p_sys; - ft_style_t **pp_char_styles; - int *p_new_positions = NULL; - int8_t *p_levels = NULL; - uint8_t *pi_karaoke_bar = NULL; - 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; - - if( b_karaoke ) - { - pi_karaoke_bar = (uint8_t *) malloc( i_len * sizeof( uint8_t )); - /* If we can't allocate sufficient memory for karaoke, continue anyway - - * we just won't be able to display the progress bar; at least we'll - * get the text. - */ - } + filter_sys_t *p_sys = p_filter->p_sys; - 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 ]; + /* Look for a match amongst our attachments first */ + FT_Face p_face = LoadEmbeddedFace( p_sys, p_style ); -#if defined(HAVE_FRIBIDI) + /* Load system wide font otheriwse */ + if( !p_face ) { - ft_style_t **pp_char_styles_new; - int *p_old_positions; - uint32_t *p_fribidi_string; - int start_pos, pos = 0; - - pp_char_styles_new = (ft_style_t **) - malloc( i_len * sizeof( ft_style_t * )); - - p_fribidi_string = (uint32_t *) - malloc( (i_len + 1) * sizeof(uint32_t) ); - p_old_positions = (int *) - malloc( (i_len + 1) * sizeof( int ) ); - p_new_positions = (int *) - malloc( (i_len + 1) * sizeof( int ) ); - p_levels = (int8_t *) - malloc( (i_len + 1) * sizeof( int8_t ) ); + int i_idx = 0; + char *psz_fontfile; +#ifdef HAVE_FONTCONFIG + psz_fontfile = FontConfig_Select( NULL, + p_style->psz_fontname, + (p_style->i_style_flags & STYLE_BOLD) != 0, + (p_style->i_style_flags & STYLE_ITALIC) != 0, + -1, + &i_idx ); +#elif defined( WIN32 ) + psz_fontfile = Win32_Select( p_filter, + p_style->psz_fontname, + (p_style->i_style_flags & STYLE_BOLD) != 0, + (p_style->i_style_flags & STYLE_ITALIC) != 0, + -1, + &i_idx ); +#else + psz_fontfile = NULL; +#endif + if( !psz_fontfile ) + return NULL; - if( ! pp_char_styles_new || - ! p_fribidi_string || - ! p_old_positions || - ! p_new_positions || - ! p_levels ) + if( *psz_fontfile == '\0' ) { - free( p_levels ); - free( p_old_positions ); - free( p_new_positions ); - free( p_fribidi_string ); - free( pp_char_styles_new ); - free( pi_karaoke_bar ); - - free( pp_char_styles ); - return VLC_ENOMEM; + msg_Warn( p_filter, + "We were not able to find a matching font: \"%s\" (%s %s)," + " so using default font", + p_style->psz_fontname, + (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "", + (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" ); + p_face = NULL; } - - /* Do bidi conversion line-by-line */ - while(pos < i_len) + else { - 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]; - p_new_positions[pos] = pos; - p_levels[pos] = 0; - ++pos; - } - start_pos = pos; - while(pos < i_len) { - if (psz_text[pos] == '\n') - break; - ++pos; - } - if (pos > start_pos) - { -#if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0) - FriBidiCharType base_dir = FRIBIDI_TYPE_LTR; -#else - FriBidiParType base_dir = FRIBIDI_PAR_LTR; -#endif - fribidi_log2vis((FriBidiChar*)psz_text + start_pos, - pos - start_pos, &base_dir, - (FriBidiChar*)p_fribidi_string + start_pos, - p_new_positions + start_pos, - p_old_positions, - p_levels + start_pos ); - for( j = (uint32_t) start_pos; j < (uint32_t) pos; j++ ) - { - pp_char_styles_new[ j ] = pp_char_styles[ start_pos + - p_old_positions[ j - start_pos ] ]; - p_new_positions[ j ] += start_pos; - } - } + if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) ) + p_face = NULL; } - free( p_old_positions ); - free( pp_char_styles ); - pp_char_styles = pp_char_styles_new; - psz_text = p_fribidi_string; - p_fribidi_string[ i_len ] = 0; + free( psz_fontfile ); } -#endif - /* Work out the karaoke */ - if( pi_karaoke_bar ) - { - int64_t i_last_duration = 0; - int64_t i_duration = 0; - int64_t i_start_pos = 0; - int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000; + if( !p_face ) + return NULL; - for( k = 0; k< i_k_runs; k++ ) - { - double fraction = 0.0; - - i_duration += pi_k_durations[ k ]; - - if( i_duration < i_elapsed ) - { - /* Completely finished this run-length - - * let it render normally */ - - fraction = 1.0; - } - else if( i_elapsed < i_last_duration ) - { - /* Haven't got up to this segment yet - - * render it completely in karaoke BG mode */ - - fraction = 0.0; - } - else - { - /* Partway through this run */ - - fraction = (double)(i_elapsed - i_last_duration) / - (double)pi_k_durations[ k ]; - } - for( i = 0; i < pi_k_run_lengths[ k ]; i++ ) - { - double shade = pi_k_run_lengths[ k ] * fraction; - - if( p_new_positions ) - j = p_new_positions[ i_start_pos + i ]; - else - j = i_start_pos + i; - - if( i < (uint32_t)shade ) - pi_karaoke_bar[ j ] = 0xff; - else if( (double)i > shade ) - pi_karaoke_bar[ j ] = 0x00; - else - { - shade -= (int)shade; - pi_karaoke_bar[ j ] = ((int)(shade * 128.0) & 0x7f) | - ((p_levels ? (p_levels[ j ] % 2) : 0 ) << 7); - } - } - - i_last_duration = i_duration; - i_start_pos += pi_k_run_lengths[ k ]; - } + if( FT_Select_Charmap( p_face, ft_encoding_unicode ) ) + { + /* We've loaded a font face which is unhelpful for actually + * rendering text - fallback to the default one. + */ + FT_Done_Face( p_face ); + return NULL; } - free( p_levels ); - free( p_new_positions ); - - FT_Vector tmp_result; + return p_face; +} - line_desc_t *p_line = NULL; - line_desc_t *p_prev = NULL; +static bool FaceStyleEquals( const text_style_t *p_style1, + const text_style_t *p_style2 ) +{ + if( !p_style1 || !p_style2 ) + return false; + if( p_style1 == p_style2 ) + return true; - int i_pen_x = 0; - int i_pen_y = 0; - int i_posn = 0; + const int i_style_mask = STYLE_BOLD | STYLE_ITALIC; + return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) && + !strcmp( p_style1->psz_fontname, p_style2->psz_fontname ); +} - p_result->x = p_result->y = 0; - tmp_result.x = tmp_result.y = 0; +static int GetGlyph( filter_t *p_filter, + FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox, + FT_Glyph *pp_outline, FT_BBox *p_outline_bbox, - i_prev = 0; - for( k = 0; k <= (uint32_t) i_len; k++ ) + FT_Face p_face, + int i_glyph_index, + int i_style_flags, + FT_Vector *p_pen ) +{ + if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) && + FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) ) { - 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; - - /* Look for a match amongst our attachments first */ - CheckForEmbeddedFont( p_sys, &p_face, p_style ); - - if( ! p_face ) - { - char *psz_fontfile = NULL; - -#ifdef HAVE_FONTCONFIG - psz_fontfile = FontConfig_Select( NULL, - p_style->psz_fontname, - p_style->b_bold, - p_style->b_italic, - -1, - &i_idx ); -#elif defined( WIN32 ) - psz_fontfile = Win32_Select( p_filter, - p_style->psz_fontname, - p_style->b_bold, - p_style->b_italic, - -1, - &i_idx ); -#else -# error FIXME -#endif - if( psz_fontfile && ! *psz_fontfile ) - { - msg_Warn( p_filter, "We were not able to find a matching font: \"%s\" %s," - " so using default font", p_style->psz_fontname, - ((p_style->b_bold && p_style->b_italic) ? "(Bold,Italic)" : - (p_style->b_bold ? "(Bold)" : - (p_style->b_italic ? "(Italic)" : ""))) ); - free( psz_fontfile ); - psz_fontfile = NULL; - } - - if( psz_fontfile ) - { - if( FT_New_Face( p_sys->p_library, - psz_fontfile, i_idx, &p_face ) ) - { - free( psz_fontfile ); - free( pp_char_styles ); -#if defined(HAVE_FRIBIDI) - free( psz_text ); -#endif - free( pi_karaoke_bar ); - return VLC_EGENERIC; - } - free( psz_fontfile ); - } - } - if( p_face && - FT_Select_Charmap( p_face, ft_encoding_unicode ) ) - { - /* We've loaded a font face which is unhelpful for actually - * rendering text - fallback to the default one. - */ - FT_Done_Face( p_face ); - p_face = NULL; - } - - 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 - free( pi_karaoke_bar ); - return VLC_EGENERIC; - } - p_sys->i_use_kerning = - FT_HAS_KERNING( ( p_face ? p_face : p_sys->p_face ) ); + msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" ); + return VLC_EGENERIC; + } + /* Do synthetic styling now that Freetype supports it; + * ie. if the font we have loaded is NOT already in the + * style that the tags want, then switch it on; if they + * are then don't. */ + if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD)) + FT_GlyphSlot_Embolden( p_face->glyph ); + if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC)) + FT_GlyphSlot_Oblique( p_face->glyph ); + + FT_Glyph glyph; + if( FT_Get_Glyph( p_face->glyph, &glyph ) ) + { + msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" ); + return VLC_EGENERIC; + } - uint32_t *psz_unicode = (uint32_t *) - malloc( (k - i_prev + 1) * sizeof( uint32_t )); - if( !psz_unicode ) - { - if( p_face ) FT_Done_Face( p_face ); - free( pp_char_styles ); - free( psz_unicode ); -#if defined(HAVE_FRIBIDI) - free( psz_text ); -#endif - free( pi_karaoke_bar ); - 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)) ) - { - if( p_face ) FT_Done_Face( p_face ); - free( pp_char_styles ); - free( psz_unicode ); -#if defined(HAVE_FRIBIDI) - free( psz_text ); -#endif - free( pi_karaoke_bar ); - 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 = 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; - } + FT_Glyph outline = NULL; + if( p_filter->p_sys->p_stroker ) + { + outline = glyph; + FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ); + FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 ); - if( RenderTag( p_filter, p_face ? p_face : p_sys->p_face, - p_style->i_font_color, p_style->b_underline, - p_style->b_through, - p_style->b_bold, - p_style->b_italic, - p_style->i_karaoke_bg_color, - 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 - free( pi_karaoke_bar ); - return VLC_EGENERIC; - } + FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox ); + } + *pp_outline = outline; - if( *psz_unicode ) - { - p_result->x = __MAX( p_result->x, tmp_result.x ); - p_result->y += tmp_result.y; + if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) ) + { + FT_Done_Glyph( glyph ); + return VLC_EGENERIC; + } - p_prev = p_line; - p_line = NULL; + FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox ); + *pp_glyph = glyph; - if( *psz_unicode == '\n') - { - uint32_t *c_ptr; + return VLC_SUCCESS; +} - for( c_ptr = psz_unicode; *c_ptr; c_ptr++ ) - { - *c_ptr = *(c_ptr+1); - } - } - } - } - 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 ) +static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen ) +{ + FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph; + if( p_bbox->xMin >= p_bbox->xMax ) { - p_result->x = __MAX( p_result->x, tmp_result.x ); - p_result->y += tmp_result.y; + p_bbox->xMin = FT_CEIL(p_pen->x); + p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x); + glyph_bmp->left = p_bbox->xMin; } - - if( pi_karaoke_bar ) + if( p_bbox->yMin >= p_bbox->yMax ) { - int i = 0; - for( p_line = *pp_lines; p_line; p_line=p_line->p_next ) - { - for( k = 0; p_line->pp_glyphs[ k ]; k++, i++ ) - { - if( (pi_karaoke_bar[ i ] & 0x7f) == 0x7f) - { - /* do nothing */ - } - else if( (pi_karaoke_bar[ i ] & 0x7f) == 0x00) - { - /* 100% BG colour will render faster if we - * instead make it 100% FG colour, so leave - * the ratio alone and copy the value across - */ - p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ]; - } - else - { - if( pi_karaoke_bar[ i ] & 0x80 ) - { - /* Swap Left and Right sides over for Right aligned - * language text (eg. Arabic, Hebrew) - */ - uint32_t i_tmp = p_line->p_fg_rgb[ k ]; - - p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ]; - p_line->p_bg_rgb[ k ] = i_tmp; - } - p_line->p_fg_bg_ratio[ k ] = (pi_karaoke_bar[ i ] & 0x7f); - } - } - /* Jump over the '\n' at the line-end */ - i++; - } - free( pi_karaoke_bar ); + p_bbox->yMax = FT_CEIL(p_pen->y); + p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y); + glyph_bmp->top = p_bbox->yMax; } - - return VLC_SUCCESS; } -static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out, - subpicture_region_t *p_region_in ) +static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p ) { - int rv = VLC_SUCCESS; - stream_t *p_sub = NULL; + p_max->xMin = __MIN(p_max->xMin, p->xMin); + p_max->yMin = __MIN(p_max->yMin, p->yMin); + p_max->xMax = __MAX(p_max->xMax, p->xMax); + p_max->yMax = __MAX(p_max->yMax, p->yMax); +} - if( !p_region_in || !p_region_in->psz_html ) - return VLC_EGENERIC; +static int ProcessLines( filter_t *p_filter, + line_desc_t **pp_lines, + FT_BBox *p_bbox, + int *pi_max_face_height, - /* Reset the default fontsize in case screen metrics have changed */ - p_filter->p_sys->i_font_size = GetFontSize( p_filter ); + uint32_t *psz_text, + text_style_t **pp_styles, + uint32_t *pi_k_dates, + int i_len ) +{ + filter_sys_t *p_sys = p_filter->p_sys; + uint32_t *p_fribidi_string = NULL; + text_style_t **pp_fribidi_styles = NULL; + int *p_new_positions = NULL; - p_sub = stream_MemoryNew( VLC_OBJECT(p_filter), - (uint8_t *) p_region_in->psz_html, - strlen( p_region_in->psz_html ), - true ); - if( unlikely(p_sub == NULL) ) - return VLC_SUCCESS; +#if defined(HAVE_FRIBIDI) + { + int *p_old_positions; + int start_pos, pos = 0; - xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml; - bool b_karaoke = false; + pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) ); - if( !p_xml_reader ) - p_xml_reader = xml_ReaderCreate( p_filter, p_sub ); - else - p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub ); + p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) ); + p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) ); + p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) ); - p_filter->p_sys->p_xml = p_xml_reader; - if( p_xml_reader ) - { - /* Look for Root Node */ - const char *node; + if( ! pp_fribidi_styles || + ! p_fribidi_string || + ! p_old_positions || + ! p_new_positions ) + { + free( p_old_positions ); + free( p_new_positions ); + free( p_fribidi_string ); + free( pp_fribidi_styles ); + return VLC_ENOMEM; + } - if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM ) + /* Do bidi conversion line-by-line */ + while(pos < i_len) { - if( !strcasecmp( "karaoke", node ) ) - { - /* We're going to have to render the text a number - * of times to show the progress marker on the text. - */ - var_SetBool( p_filter, "text-rerender", true ); - b_karaoke = true; + while(pos < i_len) { + if (psz_text[pos] != '\n') + break; + p_fribidi_string[pos] = psz_text[pos]; + pp_fribidi_styles[pos] = pp_styles[pos]; + p_new_positions[pos] = pos; + ++pos; } - else if( !strcasecmp( "text", node ) ) - { - b_karaoke = false; + start_pos = pos; + while(pos < i_len) { + if (psz_text[pos] == '\n') + break; + ++pos; } - else + if (pos > start_pos) { - /* Only text and karaoke tags are supported */ - msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.", - node ); - rv = VLC_EGENERIC; +#if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0) + FriBidiCharType base_dir = FRIBIDI_TYPE_LTR; +#else + FriBidiParType base_dir = FRIBIDI_PAR_LTR; +#endif + fribidi_log2vis((FriBidiChar*)psz_text + start_pos, + pos - start_pos, &base_dir, + (FriBidiChar*)p_fribidi_string + start_pos, + p_new_positions + start_pos, + p_old_positions, + NULL ); + for( int j = start_pos; j < pos; j++ ) + { + pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ]; + p_new_positions[ j ] += start_pos; + } } } - else - { - msg_Err( p_filter, "Malformed HTML subtitle" ); - rv = VLC_EGENERIC; - } + p_fribidi_string[ i_len ] = 0; + free( p_old_positions ); - if( rv != VLC_SUCCESS ) + pp_styles = pp_fribidi_styles; + psz_text = p_fribidi_string; + } +#endif + /* Work out the karaoke */ + uint8_t *pi_karaoke_bar = NULL; + if( pi_k_dates ) + { + pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar)); + if( pi_karaoke_bar ) { - p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL ); - p_xml_reader = NULL; + int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000; + for( int i = 0; i < i_len; i++ ) + { + unsigned i_bar = p_new_positions ? p_new_positions[i] : i; + pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed; + } } } + free( p_new_positions ); - if( p_xml_reader ) + *pi_max_face_height = 0; + *pp_lines = NULL; + line_desc_t **pp_line_next = pp_lines; + + FT_BBox bbox = { + .xMin = INT_MAX, + .yMin = INT_MAX, + .xMax = INT_MIN, + .yMax = INT_MIN, + }; + int i_face_height_previous = 0; + int i_base_line = 0; + const text_style_t *p_previous_style = NULL; + FT_Face p_face = NULL; + for( int i_start = 0; i_start < i_len; ) { - uint32_t *psz_text; - int i_len = 0; - uint32_t i_runs = 0; - uint32_t i_k_runs = 0; - uint32_t *pi_run_lengths = NULL; - uint32_t *pi_k_run_lengths = NULL; - uint32_t *pi_k_durations = NULL; - ft_style_t **pp_styles = NULL; - FT_Vector result = {0, 0}; - line_desc_t *p_lines = NULL; - - psz_text = (uint32_t *)malloc( strlen( p_region_in->psz_html ) * - sizeof( uint32_t ) ); - if( psz_text ) + /* Compute the length of the current text line */ + int i_length = 0; + while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' ) + i_length++; + + /* Render the text line (or the begining if too long) into 0 or 1 glyph line */ + line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL; + int i_index = i_start; + FT_Vector pen = { + .x = 0, + .y = 0, + }; + int i_face_height = 0; + FT_BBox line_bbox = { + .xMin = INT_MAX, + .yMin = INT_MAX, + .xMax = INT_MIN, + .yMax = INT_MIN, + }; + int i_ul_offset = 0; + int i_ul_thickness = 0; + typedef struct { + int i_index; + FT_Vector pen; + FT_BBox line_bbox; + int i_face_height; + int i_ul_offset; + int i_ul_thickness; + } break_point_t; + break_point_t break_point; + break_point_t break_point_fallback; + +#define SAVE_BP(dst) do { \ + dst.i_index = i_index; \ + dst.pen = pen; \ + dst.line_bbox = line_bbox; \ + dst.i_face_height = i_face_height; \ + dst.i_ul_offset = i_ul_offset; \ + dst.i_ul_thickness = i_ul_thickness; \ + } while(0) + + SAVE_BP( break_point ); + SAVE_BP( break_point_fallback ); + + while( i_index < i_start + i_length ) { - rv = ProcessNodes( p_filter, p_xml_reader, - p_region_in->p_style, psz_text, &i_len, - &i_runs, &pi_run_lengths, &pp_styles, - b_karaoke, &i_k_runs, &pi_k_run_lengths, - &pi_k_durations ); - - p_region_out->i_x = p_region_in->i_x; - p_region_out->i_y = p_region_in->i_y; - - if(( rv == VLC_SUCCESS ) && ( i_len > 0 )) + /* Split by common FT_Face + Size */ + const text_style_t *p_current_style = pp_styles[i_index]; + int i_part_length = 0; + while( i_index + i_part_length < i_start + i_length ) { - rv = ProcessLines( p_filter, psz_text, i_len, i_runs, - pi_run_lengths, pp_styles, &p_lines, - &result, b_karaoke, i_k_runs, - pi_k_run_lengths, pi_k_durations ); + const text_style_t *p_style = pp_styles[i_index + i_part_length]; + if( !FaceStyleEquals( p_style, p_current_style ) || + p_style->i_font_size != p_current_style->i_font_size ) + break; + i_part_length++; } - for( uint_fast32_t k=0; k 0 )) + p_face = LoadFace( p_filter, p_current_style ); + } + FT_Face p_current_face = p_face ? p_face : p_sys->p_face; + if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size ) { - if( var_InheritBool( 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( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) ) + msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size ); + if( p_sys->p_stroker ) + { + int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness; + FT_Stroker_Set( p_sys->p_stroker, + i_radius, + FT_STROKER_LINECAP_ROUND, + FT_STROKER_LINEJOIN_ROUND, 0 ); + } } - } - p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL ); - FreeLines( p_lines ); - } - stream_Delete( p_sub ); - return rv; -} + p_previous_style = p_current_style; -#ifdef WIN32 -#define UNICODE -#define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts" + i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height, + p_current_face->size->metrics.y_scale))); -static int GetFileFontByName( const char *font_name, char **psz_filename ) -{ - HKEY hKey; - wchar_t vbuffer[MAX_PATH]; - wchar_t dbuffer[256]; + /* Render the part */ + bool b_break_line = false; + int i_glyph_last = 0; + while( i_part_length > 0 ) + { + const text_style_t *p_glyph_style = pp_styles[i_index]; + uint32_t character = psz_text[i_index]; + int i_glyph_index = FT_Get_Char_Index( p_current_face, character ); + + /* Get kerning vector */ + FT_Vector kerning = { .x = 0, .y = 0 }; + if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 ) + FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning ); + + /* Get the glyph bitmap and its bounding box and all the associated properties */ + FT_Vector pen_new = { + .x = pen.x + kerning.x, + .y = pen.y + kerning.y, + }; + FT_Glyph glyph; + FT_BBox glyph_bbox; + FT_Glyph outline; + FT_BBox outline_bbox; + if( GetGlyph( p_filter, + &glyph, &glyph_bbox, + &outline, &outline_bbox, + p_current_face, i_glyph_index, p_glyph_style->i_style_flags, &pen_new ) ) + goto next; + + FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new ); + if( outline ) + FixGlyph( outline, &outline_bbox, p_current_face, &pen_new ); + /* FIXME and what about outline */ + + bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0; + uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color | + (p_glyph_style->i_karaoke_background_alpha << 24)) + : (p_glyph_style->i_font_color | + (p_glyph_style->i_font_alpha << 24)); + int i_line_offset = 0; + int i_line_thickness = 0; + if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) ) + { + i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position, + p_current_face->size->metrics.y_scale)) ); - if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey) != ERROR_SUCCESS ) - return 1; + i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness, + p_current_face->size->metrics.y_scale)) ); - for( int index = 0;; index++ ) - { - DWORD vbuflen = MAX_PATH - 1; - DWORD dbuflen = 255; + if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT ) + { + /* Move the baseline to make it strikethrough instead of + * underline. That means that strikethrough takes precedence + */ + i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2, + p_current_face->size->metrics.y_scale)) ); + } + else if( i_line_thickness > 0 ) + { + glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness ); - if( RegEnumValueW( hKey, index, vbuffer, &vbuflen, - NULL, NULL, (LPBYTE)dbuffer, &dbuflen) != ERROR_SUCCESS ) - return 2; + /* The real underline thickness and position are + * updated once the whole line has been parsed */ + i_ul_offset = __MAX( i_ul_offset, i_line_offset ); + i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness ); + i_line_thickness = -1; + } + } + FT_BBox line_bbox_new = line_bbox; + BBoxEnlarge( &line_bbox_new, &glyph_bbox ); + if( outline ) + BBoxEnlarge( &line_bbox_new, &outline_bbox ); + + b_break_line = i_index > i_start && + line_bbox_new.xMax - line_bbox_new.xMin >= p_filter->fmt_out.video.i_visible_width; + if( b_break_line ) + { + FT_Done_Glyph( glyph ); + if( outline ) + FT_Done_Glyph( outline ); - char *psz_value = FromWide( vbuffer ); + break_point_t *p_bp = NULL; + if( break_point.i_index > i_start ) + p_bp = &break_point; + else if( break_point_fallback.i_index > i_start ) + p_bp = &break_point_fallback; - char *s = strchr( psz_value,'(' ); - if( s != NULL && s != psz_value ) s[-1] = '\0'; + if( p_bp ) + { + msg_Dbg( p_filter, "Breaking line"); + for( int i = p_bp->i_index; i < i_index; i++ ) + { + line_character_t *ch = &p_line->p_character[i - i_start]; + FT_Done_Glyph( (FT_Glyph)ch->p_glyph ); + if( ch->p_outline ) + FT_Done_Glyph( (FT_Glyph)ch->p_outline ); + } + p_line->i_character_count = p_bp->i_index - i_start; + + i_index = p_bp->i_index; + pen = p_bp->pen; + line_bbox = p_bp->line_bbox; + i_face_height = p_bp->i_face_height; + i_ul_offset = p_bp->i_ul_offset; + i_ul_thickness = p_bp->i_ul_thickness; + } + else + { + msg_Err( p_filter, "Breaking unbreakable line"); + } + break; + } - /* Manage concatenated font names */ - if( strchr( psz_value, '&') ) { - if( strcasestr( psz_value, font_name ) != NULL ) + assert( p_line->i_character_count == i_index - i_start); + p_line->p_character[p_line->i_character_count++] = (line_character_t){ + .p_glyph = (FT_BitmapGlyph)glyph, + .p_outline = (FT_BitmapGlyph)outline, + .i_color = i_color, + .i_line_offset = i_line_offset, + .i_line_thickness = i_line_thickness, + }; + + pen.x = pen_new.x + p_current_face->glyph->advance.x; + pen.y = pen_new.y + p_current_face->glyph->advance.y; + line_bbox = line_bbox_new; + next: + i_glyph_last = i_glyph_index; + i_part_length--; + i_index++; + + if( character == ' ' || character == '\t' ) + SAVE_BP( break_point ); + else if( character == 160 ) + SAVE_BP( break_point_fallback ); + } + if( b_break_line ) break; } - else { - if( strcasecmp( psz_value, font_name ) == 0 ) - break; +#undef SAVE_BP + /* Update our baseline */ + if( i_face_height_previous > 0 ) + i_base_line += __MAX(i_face_height, i_face_height_previous); + i_face_height_previous = i_face_height; + + /* Update the line bbox with the actual base line */ + if (line_bbox.yMax > line_bbox.yMin) { + line_bbox.yMin -= i_base_line; + line_bbox.yMax -= i_base_line; } - } - - *psz_filename = FromWide( dbuffer ); - return 0; -} - - -static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric, - DWORD type, LPARAM lParam) -{ - VLC_UNUSED( metric ); - if( (type & RASTER_FONTTYPE) ) return 1; - // if( lpelfe->elfScript ) FIXME + BBoxEnlarge( &bbox, &line_bbox ); - return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam ); -} + /* Terminate and append the line */ + if( p_line ) + { + p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0); + p_line->i_base_line = i_base_line; + if( i_ul_thickness > 0 ) + { + for( int i = 0; i < p_line->i_character_count; i++ ) + { + line_character_t *ch = &p_line->p_character[i]; + if( ch->i_line_thickness < 0 ) + { + ch->i_line_offset = i_ul_offset; + ch->i_line_thickness = i_ul_thickness; + } + } + } -static char* Win32_Select( filter_t *p_filter, const char* family, - bool b_bold, bool b_italic, int i_size, int *i_idx ) -{ - VLC_UNUSED( i_size ); - // msg_Dbg( p_filter, "Here in Win32_Select, asking for %s", family ); + *pp_line_next = p_line; + pp_line_next = &p_line->p_next; + } - /* */ - LOGFONT lf; - lf.lfCharSet = DEFAULT_CHARSET; - if( b_italic ) - lf.lfItalic = true; - if( b_bold ) - lf.lfWeight = FW_BOLD; - strncpy( (LPSTR)&lf.lfFaceName, family, 32); + *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height ); - /* */ - char *psz_filename = NULL; - HDC hDC = GetDC( NULL ); - EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0); - ReleaseDC(NULL, hDC); + /* Skip what we have rendered and the line delimitor if present */ + i_start = i_index; + if( i_start < i_len && psz_text[i_start] == '\n' ) + i_start++; - if( psz_filename == NULL ) - return NULL; + if( bbox.yMax - bbox.yMin >= p_filter->fmt_out.video.i_visible_height ) + { + msg_Err( p_filter, "Truncated too high subtitle" ); + break; + } + } + if( p_face ) + FT_Done_Face( p_face ); - /* FIXME: increase i_idx, when concatenated strings */ - i_idx = 0; + free( pp_fribidi_styles ); + free( p_fribidi_string ); + free( pi_karaoke_bar ); - /* */ - char *psz_tmp; - if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 ) - return NULL; - return psz_tmp; + *p_bbox = bbox; + return VLC_SUCCESS; } -#endif -#ifdef HAVE_FONTCONFIG -static void FontConfig_BuildCache( filter_t *p_filter ) +/** + * This function renders a text subpicture region into another one. + * It also calculates the size needed for this string, and renders the + * needed glyphs into memory. It is used as pf_add_string callback in + * the vout method by this module + */ +static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out, + subpicture_region_t *p_region_in, bool b_html ) { - /* */ - msg_Dbg( p_filter, "Building font databases."); - mtime_t t1, t2; - t1 = mdate(); - -#ifdef WIN32 - dialog_progress_bar_t *p_dialog = NULL; - FcConfig *fcConfig = FcInitLoadConfig(); + filter_sys_t *p_sys = p_filter->p_sys; - p_dialog = dialog_ProgressCreate( p_filter, - _("Building font cache"), - _("Please wait while your font cache is rebuilt.\n" - "This should take less than a few minutes."), NULL ); + if( !p_region_in ) + return VLC_EGENERIC; + if( b_html && !p_region_in->psz_html ) + return VLC_EGENERIC; + if( !b_html && !p_region_in->psz_text ) + return VLC_EGENERIC; -/* if( p_dialog ) - dialog_ProgressSet( p_dialog, NULL, 0.5 ); */ + const size_t i_text_max = strlen( b_html ? p_region_in->psz_html + : p_region_in->psz_text ); - FcConfigBuildFonts( fcConfig ); - if( p_dialog ) + uint32_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) ); + text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) ); + if( !psz_text || !pp_styles ) { -// dialog_ProgressSet( p_dialog, NULL, 1.0 ); - dialog_ProgressDestroy( p_dialog ); - p_dialog = NULL; + free( psz_text ); + free( pp_styles ); + return VLC_EGENERIC; } -#endif - t2 = mdate(); - msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) ); -} - -/*** - * \brief Selects a font matching family, bold, italic provided - ***/ -static char* FontConfig_Select( FcConfig* config, const char* family, - bool b_bold, bool b_italic, int i_size, int *i_idx ) -{ - FcResult result = FcResultMatch; - FcPattern *pat, *p_pat; - FcChar8* val_s; - FcBool val_b; - /* Create a pattern and fills it */ - pat = FcPatternCreate(); - if (!pat) return NULL; + /* Reset the default fontsize in case screen metrics have changed */ + p_filter->p_sys->i_font_size = GetFontSize( p_filter ); /* */ - FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family ); - FcPatternAddBool( pat, FC_OUTLINE, FcTrue ); - FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN ); - FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL ); - if( i_size != -1 ) - { - char *psz_fontsize; - if( asprintf( &psz_fontsize, "%d", i_size ) != -1 ) - FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize ); - } + int rv = VLC_SUCCESS; + int i_text_length = 0; + FT_BBox bbox; + int i_max_face_height; + line_desc_t *p_lines = NULL; - /* */ - FcDefaultSubstitute( pat ); - if( !FcConfigSubstitute( config, pat, FcMatchPattern ) ) + uint32_t *pi_k_durations = NULL; + +#ifdef HAVE_STYLES + if( b_html ) { - FcPatternDestroy( pat ); - return NULL; - } + stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter), + (uint8_t *) p_region_in->psz_html, + strlen( p_region_in->psz_html ), + true ); + if( unlikely(p_sub == NULL) ) + return VLC_SUCCESS; + + xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml; + if( !p_xml_reader ) + p_xml_reader = xml_ReaderCreate( p_filter, p_sub ); + else + p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub ); + p_filter->p_sys->p_xml = p_xml_reader; + + if( !p_xml_reader ) + rv = VLC_EGENERIC; + + if( !rv ) + { + /* Look for Root Node */ + const char *node; + + if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM ) + { + if( strcasecmp( "karaoke", node ) == 0 ) + { + pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) ); + } + else if( strcasecmp( "text", node ) != 0 ) + { + /* Only text and karaoke tags are supported */ + msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.", + node ); + rv = VLC_EGENERIC; + } + } + else + { + msg_Err( p_filter, "Malformed HTML subtitle" ); + rv = VLC_EGENERIC; + } + } + if( !rv ) + { + rv = ProcessNodes( p_filter, + psz_text, pp_styles, pi_k_durations, &i_text_length, + p_xml_reader, p_region_in->p_style ); + } - /* Find the best font for the pattern, destroy the pattern */ - p_pat = FcFontMatch( config, pat, &result ); - FcPatternDestroy( pat ); - if( !p_pat || result == FcResultNoMatch ) return NULL; + if( p_xml_reader ) + p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL ); - /* Check the new pattern */ - if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) ) - || ( val_b != FcTrue ) ) + stream_Delete( p_sub ); + } + else +#endif { - FcPatternDestroy( p_pat ); - return NULL; + text_style_t *p_style; + if( p_region_in->p_style ) + p_style = CreateStyle( p_region_in->p_style->psz_fontname, + p_region_in->p_style->i_font_size, + (p_region_in->p_style->i_font_color & 0xffffff) | + ((p_region_in->p_style->i_font_alpha & 0xff) << 24), + 0x00ffffff, + p_region_in->p_style->i_style_flags & (STYLE_BOLD | + STYLE_ITALIC | + STYLE_UNDERLINE | + STYLE_STRIKEOUT) ); + else + p_style = CreateStyle( p_sys->psz_fontfamily, + p_sys->i_font_size, + (p_sys->i_font_color & 0xffffff) | + (((255-p_sys->i_font_opacity) & 0xff) << 24), + 0x00ffffff, 0); + + i_text_length = SetupText( p_filter, + psz_text, + pp_styles, + NULL, + p_region_in->psz_text, p_style, 0 ); } - if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) ) + + if( !rv && i_text_length > 0 ) { - *i_idx = 0; + rv = ProcessLines( p_filter, + &p_lines, &bbox, &i_max_face_height, + psz_text, pp_styles, pi_k_durations, i_text_length ); } - if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) ) + p_region_out->i_x = p_region_in->i_x; + p_region_out->i_y = p_region_in->i_y; + + /* Don't attempt to render text that couldn't be layed out + * properly. */ + if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax ) { - FcPatternDestroy( p_pat ); - return NULL; + if( var_InheritBool( p_filter, "freetype-yuvp" ) ) + RenderYUVP( p_filter, p_region_out, p_lines, &bbox ); + else + RenderYUVA( p_filter, p_region_out, + p_lines, + &bbox, + p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0 ); + + /* With karaoke, we're going to have to render the text a number + * of times to show the progress marker on the text. + */ + if( pi_k_durations ) + var_SetBool( p_filter, "text-rerender", true ); } - /* 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 ); */ + FreeLines( p_lines ); - if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) ) + free( psz_text ); + for( int i = 0; i < i_text_length; i++ ) { - FcPatternDestroy( p_pat ); - return NULL; + if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) ) + text_style_Delete( pp_styles[i] ); } + free( pp_styles ); + free( pi_k_durations ); - FcPatternDestroy( p_pat ); - return strdup( (const char*)val_s ); + return rv; } -#endif -#else /* from now no styles */ -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 ) +static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, + subpicture_region_t *p_region_in ) { - VLC_UNUSED(p_filter); - VLC_UNUSED(psz_text_in); - VLC_UNUSED(psz_text_out); - VLC_UNUSED(pi_runs); - VLC_UNUSED(ppi_run_lengths); - VLC_UNUSED(ppp_styles); - VLC_UNUSED(p_style); + return RenderCommon( p_filter, p_region_out, p_region_in, false ); } -static ft_style_t *GetStyleFromFontStack( filter_sys_t *p_sys, - font_stack_t **p_fonts, bool b_bold, bool b_italic, - bool b_uline, bool b_through ) +#ifdef HAVE_STYLES + +static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out, + subpicture_region_t *p_region_in ) { - VLC_UNUSED(p_sys); - VLC_UNUSED(p_fonts); - VLC_UNUSED(b_bold); - VLC_UNUSED(b_italic); - VLC_UNUSED(b_uline); - VLC_UNUSED(b_through); - return NULL; + return RenderCommon( p_filter, p_region_out, p_region_in, true ); } + #endif -static void FreeLine( line_desc_t *p_line ) +/***************************************************************************** + * Create: allocates osd-text video thread output method + ***************************************************************************** + * This function allocates and initializes a Clone vout method. + *****************************************************************************/ +static int Create( vlc_object_t *p_this ) { - unsigned int i; - for( i = 0; p_line->pp_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_fg_rgb ); - free( p_line->p_bg_rgb ); - free( p_line->p_fg_bg_ratio ); - free( p_line->pi_underline_offset ); - free( p_line->pi_underline_thickness ); - free( p_line ); -} + filter_t *p_filter = (filter_t *)p_this; + filter_sys_t *p_sys; + char *psz_fontfile = NULL; + char *psz_fontfamily = NULL; + int i_error = 0, fontindex = 0; -static void FreeLines( line_desc_t *p_lines ) -{ - line_desc_t *p_line, *p_next; + /* Allocate structure */ + p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) ); + if( !p_sys ) + return VLC_ENOMEM; + + p_sys->psz_fontfamily = NULL; +#ifdef HAVE_STYLES + p_sys->p_xml = NULL; +#endif + p_sys->p_face = 0; + p_sys->p_library = 0; + p_sys->i_font_size = 0; + p_sys->i_display_height = 0; + + var_Create( p_filter, "freetype-rel-fontsize", + VLC_VAR_INTEGER | VLC_VAR_DOINHERIT ); + + psz_fontfamily = var_InheritString( p_filter, "freetype-font" ); + p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" ); + p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" ); + p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 ); + p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" ); + p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 ); + + p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );; + p_sys->i_background_opacity = __MAX( __MIN( p_sys->i_background_opacity, 255 ), 0 ); + p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" ); + p_sys->i_background_color = __MAX( __MIN( p_sys->i_background_color, 0xFFFFFF ), 0 ); - if( !p_lines ) return; + p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0; + p_sys->f_outline_thickness = __MAX( __MIN( p_sys->f_outline_thickness, 0.5 ), 0.0 ); + p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" ); + p_sys->i_outline_opacity = __MAX( __MIN( p_sys->i_outline_opacity, 255 ), 0 ); + p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" ); + p_sys->i_outline_color = __MAX( __MIN( p_sys->i_outline_color, 0xFFFFFF ), 0 ); - for( p_line = p_lines; p_line != NULL; p_line = p_next ) +#ifdef WIN32 + /* Get Windows Font folder */ + wchar_t wdir[MAX_PATH]; + if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) ) { - p_next = p_line->p_next; - FreeLine( p_line ); + GetWindowsDirectoryW( wdir, MAX_PATH ); + wcscat( wdir, L"\\fonts" ); } -} + p_sys->psz_win_fonts_path = FromWide( wdir ); +#endif -static line_desc_t *NewLine( int i_count ) -{ - line_desc_t *p_line = malloc( sizeof(line_desc_t) ); + /* Set default psz_fontfamily */ + if( !psz_fontfamily || !*psz_fontfamily ) + { + free( psz_fontfamily ); +#ifdef HAVE_STYLES + psz_fontfamily = strdup( DEFAULT_FAMILY ); +#else +# ifdef WIN32 + if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 ) + goto error; +# else + psz_fontfamily = strdup( DEFAULT_FONT_FILE ); +# endif + msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily ); +#endif + } - if( !p_line ) return NULL; - p_line->i_height = 0; - p_line->i_width = 0; - p_line->p_next = NULL; + /* Set the current font file */ + p_sys->psz_fontfamily = psz_fontfamily; +#ifdef HAVE_STYLES +#ifdef HAVE_FONTCONFIG + FontConfig_BuildCache( p_filter ); - 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_fg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) ); - p_line->p_bg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) ); - p_line->p_fg_bg_ratio = calloc( i_count + 1, sizeof( uint8_t ) ); - p_line->pi_underline_offset = calloc( i_count + 1, sizeof( int ) ); - 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_fg_rgb == NULL ) || - ( p_line->p_bg_rgb == NULL ) || - ( p_line->p_fg_bg_ratio == NULL ) || - ( p_line->pi_underline_offset == NULL ) || - ( p_line->pi_underline_thickness == NULL ) ) - { - free( p_line->pi_underline_thickness ); - free( p_line->pi_underline_offset ); - free( p_line->p_fg_rgb ); - free( p_line->p_bg_rgb ); - free( p_line->p_fg_bg_ratio ); - free( p_line->p_glyph_pos ); - free( p_line->pp_glyphs ); - free( p_line ); - return NULL; - } - p_line->pp_glyphs[0] = NULL; - p_line->b_new_color_mode = false; + /* */ + psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false, + p_sys->i_default_font_size, &fontindex ); +#elif defined(WIN32) + psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false, + p_sys->i_default_font_size, &fontindex ); - return p_line; -} +#endif + msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile ); -static int GetFontSize( filter_t *p_filter ) -{ - filter_sys_t *p_sys = p_filter->p_sys; - vlc_value_t val; - int i_size = 0; + /* If nothing is found, use the default family */ + if( !psz_fontfile ) + psz_fontfile = strdup( psz_fontfamily ); - if( p_sys->i_default_font_size ) +#else /* !HAVE_STYLES */ + /* Use the default file */ + psz_fontfile = psz_fontfamily; +#endif + + /* */ + i_error = FT_Init_FreeType( &p_sys->p_library ); + if( i_error ) { - if( VLC_SUCCESS == var_Get( p_filter, "scale", &val )) - i_size = p_sys->i_default_font_size * val.i_int / 1000; - else - i_size = p_sys->i_default_font_size; + msg_Err( p_filter, "couldn't initialize freetype" ); + goto error; } - else + + i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "", + fontindex, &p_sys->p_face ); + + if( i_error == FT_Err_Unknown_File_Format ) { - var_Get( p_filter, "freetype-rel-fontsize", &val ); - if( val.i_int > 0 ) - { - i_size = (int)p_filter->fmt_out.video.i_height / val.i_int; - p_filter->p_sys->i_display_height = - p_filter->fmt_out.video.i_height; - } + msg_Err( p_filter, "file %s have unknown format", + psz_fontfile ? psz_fontfile : "(null)" ); + goto error; } - if( i_size <= 0 ) + else if( i_error ) { - msg_Warn( p_filter, "invalid fontsize, using 12" ); - if( VLC_SUCCESS == var_Get( p_filter, "scale", &val )) - i_size = 12 * val.i_int / 1000; - else - i_size = 12; + msg_Err( p_filter, "failed to load font file %s", + psz_fontfile ? psz_fontfile : "(null)" ); + goto error; } - return i_size; -} - -static int SetFontSize( filter_t *p_filter, int i_size ) -{ - filter_sys_t *p_sys = p_filter->p_sys; +#ifdef HAVE_STYLES + free( psz_fontfile ); +#endif - if( !i_size ) + i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode ); + if( i_error ) { - i_size = GetFontSize( p_filter ); - - msg_Dbg( p_filter, "using fontsize: %i", i_size ); + msg_Err( p_filter, "font has no unicode translation table" ); + goto error; } - p_sys->i_font_size = i_size; + if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error; - if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) ) + p_sys->p_stroker = NULL; + if( p_sys->f_outline_thickness > 0.001 ) { - msg_Err( p_filter, "couldn't set font size to %d", i_size ); - return VLC_EGENERIC; + i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker ); + if( i_error ) + msg_Err( p_filter, "Failed to create stroker for outlining" ); } + p_sys->pp_font_attachments = NULL; + p_sys->i_font_attachments = 0; + + p_filter->pf_render_text = RenderText; +#ifdef HAVE_STYLES + p_filter->pf_render_html = RenderHtml; +#else + p_filter->pf_render_html = NULL; +#endif + + LoadFontsFromAttachments( p_filter ); + return VLC_SUCCESS; + +error: + if( p_sys->p_face ) FT_Done_Face( p_sys->p_face ); + if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library ); +#ifdef HAVE_STYLES + free( psz_fontfile ); +#endif + free( psz_fontfamily ); + free( p_sys ); + return VLC_EGENERIC; } -static void YUVFromRGB( uint32_t i_argb, - uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v ) +/***************************************************************************** + * Destroy: destroy Clone video thread output method + ***************************************************************************** + * Clean up all data and library connections + *****************************************************************************/ +static void Destroy( vlc_object_t *p_this ) { - int i_red = ( i_argb & 0x00ff0000 ) >> 16; - int i_green = ( i_argb & 0x0000ff00 ) >> 8; - int i_blue = ( i_argb & 0x000000ff ); + filter_t *p_filter = (filter_t *)p_this; + filter_sys_t *p_sys = p_filter->p_sys; - *pi_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green + - 802 * i_blue + 4096 + 131072 ) >> 13, 235); - *pi_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green + - 3598 * i_blue + 4096 + 1048576) >> 13, 240); - *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green + - -585 * i_blue + 4096 + 1048576) >> 13, 240); + if( p_sys->pp_font_attachments ) + { + for( int k = 0; k < p_sys->i_font_attachments; k++ ) + vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] ); + + free( p_sys->pp_font_attachments ); + } + +#ifdef HAVE_STYLES + if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml ); +#endif + free( p_sys->psz_fontfamily ); + + /* FcFini asserts calling the subfunction FcCacheFini() + * even if no other library functions have been made since FcInit(), + * so don't call it. */ + + if( p_sys->p_stroker ) + FT_Stroker_Done( p_sys->p_stroker ); + FT_Done_Face( p_sys->p_face ); + FT_Done_FreeType( p_sys->p_library ); + free( p_sys ); } + +