X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fmisc%2Fquartztext.c;h=b1dd12ade814bb05b53cab008f14a5dd917ee8c0;hb=fdc455adc812cf9b02ae40c8bd47a1a261a32ce6;hp=9521efa06815053b3373254ecc51eb25120bb5fb;hpb=0c4e17d88da0b0d86f8f198db6211a907efd78d2;p=vlc diff --git a/modules/misc/quartztext.c b/modules/misc/quartztext.c index 9521efa068..b1dd12ade8 100644 --- a/modules/misc/quartztext.c +++ b/modules/misc/quartztext.c @@ -1,7 +1,7 @@ /***************************************************************************** * quartztext.c : Put text on the video, using Mac OS X Quartz Engine ***************************************************************************** - * Copyright (C) 2007 the VideoLAN team + * Copyright (C) 2007, 2009 the VideoLAN team * $Id$ * * Authors: Bernie Purcell @@ -31,17 +31,16 @@ #include #include -#include #include -#include -#include #include #include #include -#include - -#include +// Fix ourselves ColorSync headers that gets included in ApplicationServices. +#define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused))) +#define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused))) +#define __MACHINEEXCEPTIONS__ +#include #define DEFAULT_FONT "Arial Black" #define DEFAULT_FONT_COLOR 0xffffff @@ -65,28 +64,25 @@ static int RenderHtml( filter_t *, subpicture_region_t *, static int GetFontSize( filter_t *p_filter ); static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, - UniChar *psz_utfString, uint32_t i_text_len, - uint32_t i_runs, uint32_t *pi_run_lengths, - ATSUStyle *pp_styles ); -static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size, - uint32_t i_font_color, - bool b_bold, bool b_italic, - bool b_uline ); + CFMutableAttributedStringRef p_attrString ); + +static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color, + bool b_bold, bool b_italic, bool b_underline, + CFRange p_range, CFMutableAttributedStringRef p_attrString ); + ////////////////////////////////////////////////////////////////////////////// // Module descriptor ////////////////////////////////////////////////////////////////////////////// // The preferred way to set font style information is for it to come from the // subtitle file, and for it to be rendered with RenderHtml instead of -// RenderText. This module, unlike Freetype, doesn't provide any options to -// override the fallback font selection used when this style information is -// absent. +// RenderText. #define FONT_TEXT N_("Font") #define FONT_LONGTEXT N_("Name for the font you want to use") #define FONTSIZER_TEXT N_("Relative font size") #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \ "fonts that will be rendered on the video. If absolute font size is set, "\ - "relative size will be overriden." ) + "relative size will be overridden." ) #define COLOR_TEXT N_("Text default color") #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\ "the video. This must be an hexadecimal (like HTML colors). The first two "\ @@ -107,24 +103,44 @@ 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") }; -vlc_module_begin(); - set_shortname( N_("Mac Text renderer")); - set_description( N_("Quartz font renderer") ); - set_category( CAT_VIDEO ); - set_subcategory( SUBCAT_VIDEO_SUBPIC ); +vlc_module_begin () + set_shortname( N_("Text renderer for Mac")) + set_description( N_("CoreText font renderer") ) + set_category( CAT_VIDEO ) + set_subcategory( SUBCAT_VIDEO_SUBPIC ) add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT, - false ); + false ) add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT, - FONTSIZER_LONGTEXT, false ); + FONTSIZER_LONGTEXT, false ) change_integer_list( pi_sizes, ppsz_sizes_text, NULL ); add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT, - COLOR_LONGTEXT, false ); + COLOR_LONGTEXT, false ) change_integer_list( pi_color_values, ppsz_color_descriptions, NULL ); - set_capability( "text renderer", 150 ); - add_shortcut( "text" ); - set_callbacks( Create, Destroy ); -vlc_module_end(); + set_capability( "text renderer", 150 ) + add_shortcut( "text" ) + set_callbacks( Create, Destroy ) +vlc_module_end () + +typedef struct font_stack_t font_stack_t; +struct font_stack_t +{ + char *psz_name; + int i_size; + uint32_t i_color; // ARGB + + font_stack_t *p_next; +}; + +typedef struct +{ + int i_font_size; + uint32_t i_font_color; /* ARGB */ + bool b_italic; + bool b_bold; + bool b_underline; + char *psz_fontname; +} ft_style_t; typedef struct offscreen_bitmap_t offscreen_bitmap_t; struct offscreen_bitmap_t @@ -153,13 +169,6 @@ struct filter_sys_t int i_fonts; }; -#define UCHAR UniChar -#define TR_DEFAULT_FONT p_sys->psz_font_name -#define TR_DEFAULT_COLOR p_sys->i_font_color -#define TR_FONT_STYLE_PTR ATSUStyle - -#include "text_renderer.h" - ////////////////////////////////////////////////////////////////////////////// // Create: allocates osd-text video thread output method ////////////////////////////////////////////////////////////////////////////// @@ -223,117 +232,44 @@ static void Destroy( vlc_object_t *p_this ) static int LoadFontsFromAttachments( filter_t *p_filter ) { filter_sys_t *p_sys = p_filter->p_sys; - input_thread_t *p_input; input_attachment_t **pp_attachments; int i_attachments_cnt; - int k; - int rv = VLC_SUCCESS; - p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT ); - if( ! p_input ) + if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) ) return VLC_EGENERIC; - if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt )) - { - vlc_object_release(p_input); - return VLC_EGENERIC; - } - p_sys->i_fonts = 0; p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) ); if(! p_sys->p_fonts ) - rv = VLC_ENOMEM; + return VLC_ENOMEM; - for( k = 0; k < i_attachments_cnt; k++ ) + for( int k = 0; k < i_attachments_cnt; k++ ) { input_attachment_t *p_attach = pp_attachments[k]; - if( p_sys->p_fonts ) + if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF + !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF + p_attach->i_data > 0 && p_attach->p_data ) { - if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF - !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF - ( p_attach->i_data > 0 ) && - ( p_attach->p_data != NULL ) ) + ATSFontContainerRef container; + + if( noErr == ATSFontActivateFromMemory( p_attach->p_data, + p_attach->i_data, + kATSFontContextLocal, + kATSFontFormatUnspecified, + NULL, + kATSOptionFlagsDefault, + &container )) { - ATSFontContainerRef container; - - if( noErr == ATSFontActivateFromMemory( p_attach->p_data, - p_attach->i_data, - kATSFontContextLocal, - kATSFontFormatUnspecified, - NULL, - kATSOptionFlagsDefault, - &container )) - { - p_sys->p_fonts[ p_sys->i_fonts++ ] = container; - } + p_sys->p_fonts[ p_sys->i_fonts++ ] = container; } } vlc_input_attachment_Delete( p_attach ); } free( pp_attachments ); - vlc_object_release(p_input); - - return rv; -} - -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4 -// Original version of these functions available on: -// http://developer.apple.com/documentation/Carbon/Conceptual/QuickDrawToQuartz2D/tq_color/chapter_4_section_3.html - -#define kGenericRGBProfilePathStr "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc" - -static CMProfileRef OpenGenericProfile( void ) -{ - static CMProfileRef cached_rgb_prof = NULL; - - // Create the profile reference only once - if( cached_rgb_prof == NULL ) - { - OSStatus err; - CMProfileLocation loc; - - loc.locType = cmPathBasedProfile; - strcpy( loc.u.pathLoc.path, kGenericRGBProfilePathStr ); - - err = CMOpenProfile( &cached_rgb_prof, &loc ); - - if( err != noErr ) - { - cached_rgb_prof = NULL; - } - } - - if( cached_rgb_prof ) - { - // Clone the profile reference so that the caller has - // their own reference, not our cached one. - CMCloneProfileRef( cached_rgb_prof ); - } - - return cached_rgb_prof; -} - -static CGColorSpaceRef CreateGenericRGBColorSpace( void ) -{ - static CGColorSpaceRef p_generic_rgb_cs = NULL; - - if( p_generic_rgb_cs == NULL ) - { - CMProfileRef generic_rgb_prof = OpenGenericProfile(); - - if( generic_rgb_prof ) - { - p_generic_rgb_cs = CGColorSpaceCreateWithPlatformColorSpace( generic_rgb_prof ); - - CMCloseProfile( generic_rgb_prof ); - } - } - - return p_generic_rgb_cs; + return VLC_SUCCESS; } -#endif static char *EliminateCRLF( char *psz_string ) { @@ -353,43 +289,19 @@ static char *EliminateCRLF( char *psz_string ) return psz_string; } -// Convert UTF-8 string to UTF-16 character array -- internal Mac Endian-ness ; -// we don't need to worry about bidirectional text conversion as ATSUI should -// handle that for us automatically -static void ConvertToUTF16( const char *psz_utf8_str, uint32_t *pi_strlen, UniChar **ppsz_utf16_str ) -{ - CFStringRef p_cfString; - int i_string_length; - - p_cfString = CFStringCreateWithCString( NULL, psz_utf8_str, kCFStringEncodingUTF8 ); - if( !p_cfString ) - return; - - i_string_length = CFStringGetLength( p_cfString ); - - if( pi_strlen ) - *pi_strlen = i_string_length; - - if( !*ppsz_utf16_str ) - *ppsz_utf16_str = (UniChar *) calloc( i_string_length, sizeof( UniChar ) ); - - CFStringGetCharacters( p_cfString, CFRangeMake( 0, i_string_length ), *ppsz_utf16_str ); - - CFRelease( p_cfString ); -} - // Renders a text subpicture region into another one. // 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 ) { filter_sys_t *p_sys = p_filter->p_sys; - UniChar *psz_utf16_str = NULL; - uint32_t i_string_length; char *psz_string; - int i_font_color, i_font_alpha, i_font_size; + int i_font_alpha, i_font_size; + uint32_t i_font_color; + bool b_bold, b_uline, b_italic; vlc_value_t val; int i_scale = 1000; + b_bold = b_uline = b_italic = FALSE; p_sys->i_font_size = GetFontSize( p_filter ); @@ -406,6 +318,15 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, 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; + if( p_region_in->p_style->i_style_flags ) + { + if( p_region_in->p_style->i_style_flags & STYLE_BOLD ) + b_bold = TRUE; + if( p_region_in->p_style->i_style_flags & STYLE_ITALIC ) + b_italic = TRUE; + if( p_region_in->p_style->i_style_flags & STYLE_UNDERLINE ) + b_uline = TRUE; + } } else { @@ -416,131 +337,446 @@ static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out, if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity; - ConvertToUTF16( EliminateCRLF( psz_string ), &i_string_length, &psz_utf16_str ); + if( i_font_size <= 0 ) + { + msg_Warn( p_filter, "invalid fontsize, using 12" ); + if( VLC_SUCCESS == var_Get( p_filter, "scale", &val )) + i_font_size = 12 * val.i_int / 1000; + else + i_font_size = 12; + } p_region_out->i_x = p_region_in->i_x; p_region_out->i_y = p_region_in->i_y; - if( psz_utf16_str != NULL ) + CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); + + if( p_attrString ) { - ATSUStyle p_style = CreateStyle( p_sys->psz_font_name, i_font_size, - (i_font_color & 0xffffff) | - ((i_font_alpha & 0xff) << 24), - false, false, false ); - if( p_style ) - { - RenderYUVA( p_filter, p_region_out, psz_utf16_str, i_string_length, - 1, &i_string_length, &p_style ); - } + CFStringRef p_cfString; + int len; - ATSUDisposeStyle( p_style ); - free( psz_utf16_str ); + EliminateCRLF( psz_string); + p_cfString = CFStringCreateWithCString( NULL, psz_string, kCFStringEncodingUTF8 ); + CFAttributedStringReplaceString( p_attrString, CFRangeMake(0, 0), p_cfString ); + CFRelease( p_cfString ); + len = CFAttributedStringGetLength( p_attrString ); + + setFontAttibutes( p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline, + CFRangeMake( 0, len ), p_attrString); + + RenderYUVA( p_filter, p_region_out, p_attrString ); } + CFRelease(p_attrString); return VLC_SUCCESS; } -static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size, uint32_t i_font_color, - bool b_bold, bool b_italic, bool b_uline ) +static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size, + uint32_t i_color ) { - ATSUStyle p_style; - OSStatus status; - uint32_t i_tag_cnt; - - float f_red = (float)(( i_font_color & 0x00FF0000 ) >> 16) / 255.0; - float f_green = (float)(( i_font_color & 0x0000FF00 ) >> 8) / 255.0; - float f_blue = (float)( i_font_color & 0x000000FF ) / 255.0; - float f_alpha = ( 255.0 - (float)(( i_font_color & 0xFF000000 ) >> 24)) / 255.0; - - ATSUFontID font; - Fixed font_size = IntToFixed( i_font_size ); - ATSURGBAlphaColor font_color = { f_red, f_green, f_blue, f_alpha }; - Boolean bold = b_bold; - Boolean italic = b_italic; - Boolean uline = b_uline; - - ATSUAttributeTag tags[] = { kATSUSizeTag, kATSURGBAlphaColorTag, kATSUQDItalicTag, - kATSUQDBoldfaceTag, kATSUQDUnderlineTag, kATSUFontTag }; - ByteCount sizes[] = { sizeof( Fixed ), sizeof( ATSURGBAlphaColor ), sizeof( Boolean ), - sizeof( Boolean ), sizeof( Boolean ), sizeof( ATSUFontID )}; - ATSUAttributeValuePtr values[] = { &font_size, &font_color, &italic, &bold, &uline, &font }; - - i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag ); - - status = ATSUFindFontFromName( psz_fontname, - strlen( psz_fontname ), - kFontFullName, - kFontNoPlatform, - kFontNoScript, - kFontNoLanguageCode, - &font ); - - if( status != noErr ) + font_stack_t *p_new; + + if( !p_font ) + return VLC_EGENERIC; + + p_new = malloc( sizeof( font_stack_t ) ); + if( ! p_new ) + return VLC_ENOMEM; + + p_new->p_next = NULL; + + if( psz_name ) + p_new->psz_name = strdup( psz_name ); + else + p_new->psz_name = NULL; + + p_new->i_size = i_size; + p_new->i_color = i_color; + + if( !*p_font ) + { + *p_font = p_new; + } + else { - // If we can't find a suitable font, just do everything else - i_tag_cnt--; + font_stack_t *p_last; + + for( p_last = *p_font; + p_last->p_next; + p_last = p_last->p_next ) + ; + + p_last->p_next = p_new; } + return VLC_SUCCESS; +} - if( noErr == ATSUCreateStyle( &p_style ) ) +static int PopFont( font_stack_t **p_font ) +{ + font_stack_t *p_last, *p_next_to_last; + + if( !p_font || !*p_font ) + return VLC_EGENERIC; + + p_next_to_last = NULL; + for( p_last = *p_font; + p_last->p_next; + p_last = p_last->p_next ) { - if( noErr == ATSUSetAttributes( p_style, i_tag_cnt, tags, sizes, values ) ) + p_next_to_last = p_last; + } + + if( p_next_to_last ) + p_next_to_last->p_next = NULL; + else + *p_font = NULL; + + free( p_last->psz_name ); + free( p_last ); + + return VLC_SUCCESS; +} + +static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size, + uint32_t *i_color ) +{ + font_stack_t *p_last; + + if( !p_font || !*p_font ) + return VLC_EGENERIC; + + for( p_last=*p_font; + p_last->p_next; + p_last=p_last->p_next ) + ; + + *psz_name = p_last->psz_name; + *i_size = p_last->i_size; + *i_color = p_last->i_color; + + return VLC_SUCCESS; +} + +static int HandleFontAttributes( xml_reader_t *p_xml_reader, + font_stack_t **p_fonts, int i_scale ) +{ + int rv; + char *psz_fontname = NULL; + uint32_t i_font_color = 0xffffff; + int i_font_alpha = 0; + int i_font_size = 24; + + // Default all attributes to the top font in the stack -- in case not + // all attributes are specified in the sub-font + if( VLC_SUCCESS == PeekFont( p_fonts, + &psz_fontname, + &i_font_size, + &i_font_color )) + { + psz_fontname = strdup( psz_fontname ); + i_font_size = i_font_size * 1000 / i_scale; + } + i_font_alpha = (i_font_color >> 24) & 0xff; + i_font_color &= 0x00ffffff; + + while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS ) + { + char *psz_name = xml_ReaderName( p_xml_reader ); + char *psz_value = xml_ReaderValue( p_xml_reader ); + + if( psz_name && psz_value ) { - return p_style; + if( !strcasecmp( "face", psz_name ) ) + { + free( psz_fontname ); + psz_fontname = strdup( psz_value ); + } + else if( !strcasecmp( "size", psz_name ) ) + { + if( ( *psz_value == '+' ) || ( *psz_value == '-' ) ) + { + int i_value = atoi( psz_value ); + + if( ( i_value >= -5 ) && ( i_value <= 5 ) ) + i_font_size += ( i_value * i_font_size ) / 10; + else if( i_value < -5 ) + i_font_size = - i_value; + else if( i_value > 5 ) + i_font_size = i_value; + } + else + i_font_size = atoi( psz_value ); + } + else if( !strcasecmp( "color", psz_name ) && + ( psz_value[0] == '#' ) ) + { + i_font_color = strtol( psz_value + 1, NULL, 16 ); + i_font_color &= 0x00ffffff; + } + else if( !strcasecmp( "alpha", psz_name ) && + ( psz_value[0] == '#' ) ) + { + i_font_alpha = strtol( psz_value + 1, NULL, 16 ); + i_font_alpha &= 0xff; + } + free( psz_name ); + free( psz_value ); } - ATSUDisposeStyle( p_style ); } - return NULL; + rv = PushFont( p_fonts, + psz_fontname, + i_font_size * i_scale / 1000, + (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) ); + + free( psz_fontname ); + + return rv; } -static ATSUStyle GetStyleFromFontStack( filter_sys_t *p_sys, - font_stack_t **p_fonts, bool b_bold, bool b_italic, - bool b_uline ) +static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color, + bool b_bold, bool b_italic, bool b_underline, + CFRange p_range, CFMutableAttributedStringRef p_attrString ) { - ATSUStyle p_style = NULL; + CFStringRef p_cfString; + CTFontRef p_font; + + // Handle font name and size + p_cfString = CFStringCreateWithCString( NULL, + psz_fontname, + kCFStringEncodingUTF8 ); + p_font = CTFontCreateWithName( p_cfString, + (float)i_font_size, + NULL ); + CFRelease( p_cfString ); + CFAttributedStringSetAttribute( p_attrString, + p_range, + kCTFontAttributeName, + p_font ); + CFRelease( p_font ); + + // Handle Underline + SInt32 _uline; + if( b_underline ) + _uline = kCTUnderlineStyleSingle; + else + _uline = kCTUnderlineStyleNone; + + CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline); + CFAttributedStringSetAttribute( p_attrString, + p_range, + kCTUnderlineStyleAttributeName, + underline ); + CFRelease( underline ); + + // Handle Bold + float _weight; + if( b_bold ) + _weight = 0.5; + else + _weight = 0.0; + + CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight); + CFAttributedStringSetAttribute( p_attrString, + p_range, + kCTFontWeightTrait, + weight ); + CFRelease( weight ); + + // Handle Italic + float _slant; + if( b_italic ) + _slant = 1.0; + else + _slant = 0.0; + + CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant); + CFAttributedStringSetAttribute( p_attrString, + p_range, + kCTFontSlantTrait, + slant ); + CFRelease( slant ); + + // Handle foreground colour + CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0, + (float)((i_font_color & 0x0000ff00) >> 8) / 255.0, + (float)((i_font_color & 0x000000ff) ) / 255.0, + (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 }; + CGColorRef fg_text = CGColorCreate(rgbColorSpace, components); + CGColorSpaceRelease(rgbColorSpace); + + CFAttributedStringSetAttribute( p_attrString, + p_range, + kCTForegroundColorAttributeName, + fg_text ); + CFRelease( fg_text ); + +} - char *psz_fontname = NULL; - uint32_t i_font_color = p_sys->i_font_color; - uint32_t i_karaoke_bg_color = i_font_color; /* Use it */ - int i_font_size = p_sys->i_font_size; +static void GetAttrStrFromFontStack( font_stack_t **p_fonts, + bool b_bold, bool b_italic, bool b_uline, + CFRange p_range, CFMutableAttributedStringRef p_attrString ) +{ + char *psz_fontname = NULL; + int i_font_size = 0; + uint32_t i_font_color = 0; if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size, - &i_font_color, &i_karaoke_bg_color )) + &i_font_color )) { - p_style = CreateStyle( psz_fontname, i_font_size, i_font_color, - b_bold, b_italic, b_uline ); + setFontAttibutes( psz_fontname, + i_font_size, + i_font_color, + b_bold, b_italic, b_uline, + p_range, + p_attrString ); } - return p_style; } -static void SetupLine( filter_t *p_filter, const char *psz_text_in, - UniChar **psz_text_out, uint32_t *pi_runs, - uint32_t **ppi_run_lengths, ATSUStyle **ppp_styles, - ATSUStyle p_style ) +static int ProcessNodes( filter_t *p_filter, + xml_reader_t *p_xml_reader, + text_style_t *p_font_style, + CFMutableAttributedStringRef p_attrString ) { - uint32_t i_string_length = 0; + int rv = VLC_SUCCESS; + filter_sys_t *p_sys = p_filter->p_sys; + font_stack_t *p_fonts = NULL; + vlc_value_t val; + int i_scale = 1000; + + char *psz_node = NULL; - ConvertToUTF16( psz_text_in, &i_string_length, psz_text_out ); - *psz_text_out += i_string_length; + bool b_italic = false; + bool b_bold = false; + bool b_uline = false; - if( ppp_styles && ppi_run_lengths ) + if( VLC_SUCCESS == var_Get( p_filter, "scale", &val )) + i_scale = val.i_int; + + if( p_font_style ) + { + rv = PushFont( &p_fonts, + p_font_style->psz_fontname, + p_font_style->i_font_size * i_scale / 1000, + (p_font_style->i_font_color & 0xffffff) | + ((p_font_style->i_font_alpha & 0xff) << 24) ); + + if( p_font_style->i_style_flags & STYLE_BOLD ) + b_bold = true; + if( p_font_style->i_style_flags & STYLE_ITALIC ) + b_italic = true; + if( p_font_style->i_style_flags & STYLE_UNDERLINE ) + b_uline = true; + } + else { - (*pi_runs)++; + rv = PushFont( &p_fonts, + p_sys->psz_font_name, + p_sys->i_font_size, + p_sys->i_font_color ); + } + if( rv != VLC_SUCCESS ) + return rv; - if( *ppp_styles ) - *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) ); - else - *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) ); + while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) ) + { + switch ( xml_ReaderNodeType( p_xml_reader ) ) + { + case XML_READER_NONE: + break; + case XML_READER_ENDELEM: + psz_node = xml_ReaderName( p_xml_reader ); - (*ppp_styles)[ *pi_runs - 1 ] = p_style; + if( psz_node ) + { + if( !strcasecmp( "font", psz_node ) ) + PopFont( &p_fonts ); + else if( !strcasecmp( "b", psz_node ) ) + b_bold = false; + else if( !strcasecmp( "i", psz_node ) ) + b_italic = false; + else if( !strcasecmp( "u", psz_node ) ) + b_uline = false; - if( *ppi_run_lengths ) - *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) ); - else - *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) ); + free( psz_node ); + } + break; + case XML_READER_STARTELEM: + psz_node = xml_ReaderName( p_xml_reader ); + if( psz_node ) + { + if( !strcasecmp( "font", psz_node ) ) + rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale ); + else if( !strcasecmp( "b", psz_node ) ) + b_bold = true; + else if( !strcasecmp( "i", psz_node ) ) + b_italic = true; + else if( !strcasecmp( "u", psz_node ) ) + b_uline = true; + else if( !strcasecmp( "br", psz_node ) ) + { + CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); + CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), CFSTR("\n") ); + + GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline, + CFRangeMake( 0, 1 ), + p_attrnode ); + CFAttributedStringReplaceAttributedString( p_attrString, + CFRangeMake(CFAttributedStringGetLength(p_attrString), 0), + p_attrnode); + CFRelease( p_attrnode ); + } + free( psz_node ); + } + break; + case XML_READER_TEXT: + psz_node = xml_ReaderValue( p_xml_reader ); + if( psz_node ) + { + CFStringRef p_cfString; + int len; + + // Turn any multiple-whitespaces into single spaces + char *s = strpbrk( psz_node, "\t\r\n " ); + while( s ) + { + int i_whitespace = strspn( s, "\t\r\n " ); + + if( i_whitespace > 1 ) + memmove( &s[1], + &s[i_whitespace], + strlen( s ) - i_whitespace + 1 ); + *s++ = ' '; + + s = strpbrk( s, "\t\r\n " ); + } + + + CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); + p_cfString = CFStringCreateWithCString( NULL, psz_node, kCFStringEncodingUTF8 ); + CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), p_cfString ); + CFRelease( p_cfString ); + len = CFAttributedStringGetLength( p_attrnode ); - (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length; + GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline, + CFRangeMake( 0, len ), + p_attrnode ); + + CFAttributedStringReplaceAttributedString( p_attrString, + CFRangeMake(CFAttributedStringGetLength(p_attrString), 0), + p_attrnode); + CFRelease( p_attrnode ); + free( psz_node ); + } + break; + } } + + while( VLC_SUCCESS == PopFont( &p_fonts ) ); + + return rv; } static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out, @@ -591,7 +827,7 @@ static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out, else { /* Only text and karaoke tags are supported */ - xml_ReaderDelete( p_xml, p_xml_reader ); + xml_ReaderDelete( p_xml_reader ); p_xml_reader = NULL; rv = VLC_EGENERIC; } @@ -602,46 +838,24 @@ static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out, if( p_xml_reader ) { - UniChar *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; - ATSUStyle *pp_styles = NULL; - - psz_text = (UniChar *) malloc( strlen( p_region_in->psz_html ) * - sizeof( UniChar ) ); - if( psz_text ) - { - uint32_t k; - - rv = ProcessNodes( p_filter, p_xml_reader, - p_region_in->p_style, psz_text, &i_len, - &i_runs, &pi_run_lengths, &pp_styles, - /* No karaoke support */ - false, &i_k_runs, &pi_k_run_lengths, &pi_k_durations ); + int i_len; - assert( pi_k_run_lengths == NULL && pi_k_durations == NULL ); + CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); + rv = ProcessNodes( p_filter, p_xml_reader, + p_region_in->p_style, p_attrString ); - p_region_out->i_x = p_region_in->i_x; - p_region_out->i_y = p_region_in->i_y; + i_len = CFAttributedStringGetLength( p_attrString ); - if(( rv == VLC_SUCCESS ) && ( i_len > 0 )) - { - RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs, - pi_run_lengths, pp_styles); - } + p_region_out->i_x = p_region_in->i_x; + p_region_out->i_y = p_region_in->i_y; - for( k=0; k 0 )) + { + RenderYUVA( p_filter, p_region_out, p_attrString ); } + CFRelease(p_attrString); - xml_ReaderDelete( p_xml, p_xml_reader ); + xml_ReaderDelete( p_xml_reader ); } xml_Delete( p_xml ); } @@ -667,11 +881,7 @@ static CGContextRef CreateOffScreenContext( int i_width, int i_height, p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow ); -#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4 *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB ); -#else - *pp_colorSpace = CreateGenericRGBColorSpace(); -#endif if( p_bitmap->p_data && *pp_colorSpace ) { @@ -681,14 +891,10 @@ static CGContextRef CreateOffScreenContext( int i_width, int i_height, } if( p_context ) { -#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_1 - // OS X 10.1 doesn't support weak linking of this call which is only available - // int 10.4 and later if( CGContextSetAllowsAntialiasing != NULL ) { CGContextSetAllowsAntialiasing( p_context, true ); } -#endif } *pp_memory = p_bitmap; } @@ -696,9 +902,11 @@ static CGContextRef CreateOffScreenContext( int i_width, int i_height, return p_context; } -static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len, - uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles, - int i_width, int i_height, int *pi_textblock_height ) +static offscreen_bitmap_t *Compose( int i_text_align, + CFMutableAttributedStringRef p_attrString, + unsigned i_width, + unsigned i_height, + unsigned *pi_textblock_height ) { offscreen_bitmap_t *p_offScreen = NULL; CGColorSpaceRef p_colorSpace = NULL; @@ -706,101 +914,75 @@ static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, ui p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace ); + *pi_textblock_height = 0; if( p_context ) { - ATSUTextLayout p_textLayout; - OSStatus status = noErr; - - status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len, - i_runs, - (const UniCharCount *) pi_run_lengths, - pp_styles, - &p_textLayout ); - if( status == noErr ) - { - // Attach our offscreen Image Graphics Context to the text style - // and setup the line alignment (have to specify the line width - // also in order for our chosen alignment to work) + float horiz_flush; - Fract alignment = kATSUStartAlignment; - Fixed line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 ); - - ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag }; - ByteCount sizes[] = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) }; - ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width }; - - int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag ); - - if( i_text_align == SUBPICTURE_ALIGN_RIGHT ) - { - alignment = kATSUEndAlignment; - } - else if( i_text_align != SUBPICTURE_ALIGN_LEFT ) - { - alignment = kATSUCenterAlignment; - } + CGContextSetTextMatrix( p_context, CGAffineTransformIdentity ); - ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values ); + if( i_text_align == SUBPICTURE_ALIGN_RIGHT ) + horiz_flush = 1.0; + else if( i_text_align != SUBPICTURE_ALIGN_LEFT ) + horiz_flush = 0.5; + else + horiz_flush = 0.0; - // let ATSUI deal with characters not-in-our-specified-font - ATSUSetTransientFontMatching( p_textLayout, true ); + // Create the framesetter with the attributed string. + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString); + if( framesetter ) + { + CTFrameRef frame; + CGMutablePathRef p_path = CGPathCreateMutable(); + CGRect p_bounds = CGRectMake( (float)HORIZONTAL_MARGIN, + (float)VERTICAL_MARGIN, + (float)(i_width - HORIZONTAL_MARGIN*2), + (float)(i_height - VERTICAL_MARGIN *2)); + CGPathAddRect( p_path, NULL, p_bounds ); - Fixed x = Long2Fix( HORIZONTAL_MARGIN ); - Fixed y = Long2Fix( i_height ); + // Create the frame and draw it into the graphics context + frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL); - // Set the line-breaks and draw individual lines - uint32_t i_start = 0; - uint32_t i_end = i_text_len; + CGPathRelease(p_path); // Set up black outlining of the text -- CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 ); CGContextSetTextDrawingMode( p_context, kCGTextFillStroke ); - CGContextSetShadow( p_context, CGSizeMake( 0, 0 ), 5 ); - float black_components[4] = {0, 0, 0, 1}; - CGContextSetShadowWithColor (p_context, CGSizeMake( 0, 0 ), 5, CGColorCreate( CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB ), black_components )); - do + + if( frame != NULL ) { - // ATSUBreakLine will automatically pick up any manual '\n's also - status = ATSUBreakLine( p_textLayout, i_start, line_width, true, (UniCharArrayOffset *) &i_end ); - if( ( status == noErr ) || ( status == kATSULineBreakInWord ) ) - { - Fixed ascent; - Fixed descent; - uint32_t i_actualSize; + CFArrayRef lines; + CGPoint penPosition; - // Come down far enough to fit the height of this line -- - ATSUGetLineControl( p_textLayout, i_start, kATSULineAscentTag, - sizeof( Fixed ), &ascent, (ByteCount *) &i_actualSize ); + lines = CTFrameGetLines( frame ); + penPosition.y = i_height; + for (int i=0; i y values decrease as - // you move down the page - y -= ascent; + CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i); + CTLineGetTypographicBounds(line, &ascent, &descent, &leading); - // Set the outlining for this line to be dependent on the size of the line - + // Set the outlining for this line to be dependant on the size of the line - // make it about 5% of the ascent, with a minimum at 1.0 - float f_thickness = FixedToFloat( ascent ) * 0.05; - CGContextSetLineWidth( p_context, (( f_thickness < 1.0 ) ? 1.0 : f_thickness )); - ATSUDrawText( p_textLayout, i_start, i_end - i_start, x, y ); + float f_thickness = ascent * 0.05; + CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness )); - // and now prepare for the next line by coming down far enough for our - // descent - ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag, - sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize ); - y -= descent; + double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2)); + penPosition.x = HORIZONTAL_MARGIN + penOffset; + penPosition.y -= ascent; + CGContextSetTextPosition( p_context, penPosition.x, penPosition.y ); + CTLineDraw( line, p_context ); + penPosition.y -= descent + leading; - i_start = i_end; } - else - break; - } - while( i_end < i_text_len ); - - *pi_textblock_height = i_height - Fix2Long( y ); - CGContextFlush( p_context ); + *pi_textblock_height = i_height - penPosition.y; - ATSUDisposeTextLayout( p_textLayout ); + CFRelease(frame); + } + CFRelease(framesetter); } - + CGContextFlush( p_context ); CGContextRelease( p_context ); } if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace ); @@ -810,27 +992,26 @@ static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, ui static int GetFontSize( filter_t *p_filter ) { - return p_filter->fmt_out.video.i_height / __MAX(1, var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" )); + return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE; } -static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniChar *psz_utf16_str, - uint32_t i_text_len, uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles ) +static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, + CFMutableAttributedStringRef p_attrString ) { offscreen_bitmap_t *p_offScreen = NULL; - int i_textblock_height = 0; + unsigned i_textblock_height = 0; - int i_width = p_filter->fmt_out.video.i_visible_width; - int i_height = p_filter->fmt_out.video.i_visible_height; - int i_text_align = p_region->i_align & 0x3; + unsigned i_width = p_filter->fmt_out.video.i_visible_width; + unsigned i_height = p_filter->fmt_out.video.i_visible_height; + unsigned i_text_align = p_region->i_align & 0x3; - if( !psz_utf16_str ) + if( !p_attrString ) { msg_Err( p_filter, "Invalid argument to RenderYUVA" ); return VLC_EGENERIC; } - p_offScreen = Compose( i_text_align, psz_utf16_str, i_text_len, - i_runs, pi_run_lengths, pp_styles, + p_offScreen = Compose( i_text_align, p_attrString, i_width, i_height, &i_textblock_height ); if( !p_offScreen ) @@ -841,18 +1022,18 @@ static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniCha uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a; video_format_t fmt; - int x, y, i_offset, i_pitch; + int i_offset; + unsigned x, y, i_pitch; uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB // Create a new subpicture region memset( &fmt, 0, sizeof(video_format_t) ); - fmt.i_chroma = VLC_FOURCC('Y','U','V','A'); - fmt.i_aspect = 0; + fmt.i_chroma = VLC_CODEC_YUVA; fmt.i_width = fmt.i_visible_width = i_width; - fmt.i_height = fmt.i_visible_height = i_textblock_height + VERTICAL_MARGIN * 2; + fmt.i_height = fmt.i_visible_height = __MIN( i_height, i_textblock_height + VERTICAL_MARGIN * 2); fmt.i_x_offset = fmt.i_y_offset = 0; - p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect ); + p_region->p_picture = picture_NewFromFormat( &fmt ); if( !p_region->p_picture ) return VLC_EGENERIC; p_region->fmt = fmt; @@ -863,10 +1044,10 @@ static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniCha p_dst_a = p_region->p_picture->A_PIXELS; i_pitch = p_region->p_picture->A_PITCH; - i_offset = VERTICAL_MARGIN *i_pitch; - for( y=0; yp_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ]; int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];