1 /*****************************************************************************
2 * quartztext.c : Put text on the video, using Mac OS X Quartz Engine
3 *****************************************************************************
4 * Copyright (C) 2007 the VideoLAN team
7 * Authors: Bernie Purcell <bitmap@videolan.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 //////////////////////////////////////////////////////////////////////////////
26 //////////////////////////////////////////////////////////////////////////////
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
36 #include <vlc_block.h>
37 #include <vlc_filter.h>
38 #include <vlc_stream.h>
40 #include <vlc_input.h>
44 #include <Carbon/Carbon.h>
46 #define DEFAULT_FONT "Verdana"
47 #define DEFAULT_FONT_COLOR 0xffffff
48 #define DEFAULT_REL_FONT_SIZE 16
50 #define VERTICAL_MARGIN 3
51 #define HORIZONTAL_MARGIN 10
53 //////////////////////////////////////////////////////////////////////////////
55 //////////////////////////////////////////////////////////////////////////////
56 static int Create ( vlc_object_t * );
57 static void Destroy( vlc_object_t * );
59 static int LoadFontsFromAttachments( filter_t *p_filter );
61 static int RenderText( filter_t *, subpicture_region_t *,
62 subpicture_region_t * );
63 static int RenderHtml( filter_t *, subpicture_region_t *,
64 subpicture_region_t * );
66 static int GetFontSize( filter_t *p_filter );
67 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
68 UniChar *psz_utfString, uint32_t i_text_len,
69 uint32_t i_runs, uint32_t *pi_run_lengths,
70 ATSUStyle *pp_styles );
71 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size,
72 uint32_t i_font_color,
73 bool b_bold, bool b_italic,
75 //////////////////////////////////////////////////////////////////////////////
77 //////////////////////////////////////////////////////////////////////////////
79 // The preferred way to set font style information is for it to come from the
80 // subtitle file, and for it to be rendered with RenderHtml instead of
81 // RenderText. This module, unlike Freetype, doesn't provide any options to
82 // override the fallback font selection used when this style information is
84 #define FONT_TEXT N_("Font")
85 #define FONT_LONGTEXT N_("Name for the font you want to use")
86 #define FONTSIZER_TEXT N_("Relative font size")
87 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
88 "fonts that will be rendered on the video. If absolute font size is set, "\
89 "relative size will be overriden." )
90 #define COLOR_TEXT N_("Text default color")
91 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
92 "the video. This must be an hexadecimal (like HTML colors). The first two "\
93 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
94 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
96 static const int pi_color_values[] = {
97 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
98 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
99 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
101 static const char *const ppsz_color_descriptions[] = {
102 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
103 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
104 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
106 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
107 static const char *const ppsz_sizes_text[] = {
108 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
111 set_shortname( N_("Mac Text renderer"));
112 set_description( N_("Quartz font renderer") );
113 set_category( CAT_VIDEO );
114 set_subcategory( SUBCAT_VIDEO_SUBPIC );
116 add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
118 add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT,
119 FONTSIZER_LONGTEXT, false );
120 change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
121 add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
122 COLOR_LONGTEXT, false );
123 change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
124 set_capability( "text renderer", 120 );
125 add_shortcut( "text" );
126 set_callbacks( Create, Destroy );
129 typedef struct font_stack_t font_stack_t;
134 uint32_t i_color; // ARGB
136 font_stack_t *p_next;
139 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
140 struct offscreen_bitmap_t
143 int i_bitsPerChannel;
149 //////////////////////////////////////////////////////////////////////////////
150 // filter_sys_t: quartztext local data
151 //////////////////////////////////////////////////////////////////////////////
152 // This structure is part of the video output thread descriptor.
153 // It describes the freetype specific properties of an output thread.
154 //////////////////////////////////////////////////////////////////////////////
158 uint8_t i_font_opacity;
162 ATSFontContainerRef *p_fonts;
166 //////////////////////////////////////////////////////////////////////////////
167 // Create: allocates osd-text video thread output method
168 //////////////////////////////////////////////////////////////////////////////
169 // This function allocates and initializes a Clone vout method.
170 //////////////////////////////////////////////////////////////////////////////
171 static int Create( vlc_object_t *p_this )
173 filter_t *p_filter = (filter_t *)p_this;
176 // Allocate structure
177 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
180 p_sys->psz_font_name = var_CreateGetString( p_this, "quartztext-font" );
181 p_sys->i_font_opacity = 255;
182 p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
183 p_sys->i_font_size = GetFontSize( p_filter );
185 p_filter->pf_render_text = RenderText;
186 p_filter->pf_render_html = RenderHtml;
188 p_sys->p_fonts = NULL;
191 LoadFontsFromAttachments( p_filter );
196 //////////////////////////////////////////////////////////////////////////////
197 // Destroy: destroy Clone video thread output method
198 //////////////////////////////////////////////////////////////////////////////
199 // Clean up all data and library connections
200 //////////////////////////////////////////////////////////////////////////////
201 static void Destroy( vlc_object_t *p_this )
203 filter_t *p_filter = (filter_t *)p_this;
204 filter_sys_t *p_sys = p_filter->p_sys;
210 for( k = 0; k < p_sys->i_fonts; k++ )
212 ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
215 free( p_sys->p_fonts );
218 free( p_sys->psz_font_name );
222 //////////////////////////////////////////////////////////////////////////////
223 // Make any TTF/OTF fonts present in the attachments of the media file
224 // available to the Quartz engine for text rendering
225 //////////////////////////////////////////////////////////////////////////////
226 static int LoadFontsFromAttachments( filter_t *p_filter )
228 filter_sys_t *p_sys = p_filter->p_sys;
229 input_thread_t *p_input;
230 input_attachment_t **pp_attachments;
231 int i_attachments_cnt;
233 int rv = VLC_SUCCESS;
235 p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
239 if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
241 vlc_object_release(p_input);
246 p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
247 if(! p_sys->p_fonts )
250 for( k = 0; k < i_attachments_cnt; k++ )
252 input_attachment_t *p_attach = pp_attachments[k];
256 if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
257 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
258 ( p_attach->i_data > 0 ) &&
259 ( p_attach->p_data != NULL ) )
261 ATSFontContainerRef container;
263 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
265 kATSFontContextLocal,
266 kATSFontFormatUnspecified,
268 kATSOptionFlagsDefault,
271 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
275 vlc_input_attachment_Delete( p_attach );
277 free( pp_attachments );
279 vlc_object_release(p_input);
284 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
285 // Original version of these functions available on:
286 // http://developer.apple.com/documentation/Carbon/Conceptual/QuickDrawToQuartz2D/tq_color/chapter_4_section_3.html
288 #define kGenericRGBProfilePathStr "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc"
290 static CMProfileRef OpenGenericProfile( void )
292 static CMProfileRef cached_rgb_prof = NULL;
294 // Create the profile reference only once
295 if( cached_rgb_prof == NULL )
298 CMProfileLocation loc;
300 loc.locType = cmPathBasedProfile;
301 strcpy( loc.u.pathLoc.path, kGenericRGBProfilePathStr );
303 err = CMOpenProfile( &cached_rgb_prof, &loc );
307 cached_rgb_prof = NULL;
311 if( cached_rgb_prof )
313 // Clone the profile reference so that the caller has
314 // their own reference, not our cached one.
315 CMCloneProfileRef( cached_rgb_prof );
318 return cached_rgb_prof;
321 static CGColorSpaceRef CreateGenericRGBColorSpace( void )
323 static CGColorSpaceRef p_generic_rgb_cs = NULL;
325 if( p_generic_rgb_cs == NULL )
327 CMProfileRef generic_rgb_prof = OpenGenericProfile();
329 if( generic_rgb_prof )
331 p_generic_rgb_cs = CGColorSpaceCreateWithPlatformColorSpace( generic_rgb_prof );
333 CMCloseProfile( generic_rgb_prof );
337 return p_generic_rgb_cs;
341 static char *EliminateCRLF( char *psz_string )
346 for( p = psz_string; p && *p; p++ )
348 if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
350 for( q = p + 1; *q; q++ )
359 // Convert UTF-8 string to UTF-16 character array -- internal Mac Endian-ness ;
360 // we don't need to worry about bidirectional text conversion as ATSUI should
361 // handle that for us automatically
362 static void ConvertToUTF16( const char *psz_utf8_str, uint32_t *pi_strlen, UniChar **ppsz_utf16_str )
364 CFStringRef p_cfString;
367 p_cfString = CFStringCreateWithCString( NULL, psz_utf8_str, kCFStringEncodingUTF8 );
371 i_string_length = CFStringGetLength( p_cfString );
374 *pi_strlen = i_string_length;
376 if( !*ppsz_utf16_str )
377 *ppsz_utf16_str = (UniChar *) calloc( i_string_length, sizeof( UniChar ) );
379 CFStringGetCharacters( p_cfString, CFRangeMake( 0, i_string_length ), *ppsz_utf16_str );
381 CFRelease( p_cfString );
384 // Renders a text subpicture region into another one.
385 // It is used as pf_add_string callback in the vout method by this module
386 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
387 subpicture_region_t *p_region_in )
389 filter_sys_t *p_sys = p_filter->p_sys;
390 UniChar *psz_utf16_str = NULL;
391 uint32_t i_string_length;
393 int i_font_color, i_font_alpha, i_font_size;
397 p_sys->i_font_size = GetFontSize( p_filter );
400 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
401 psz_string = p_region_in->psz_text;
402 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
404 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
407 if( p_region_in->p_style )
409 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
410 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
411 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
415 i_font_color = p_sys->i_font_color;
416 i_font_alpha = 255 - p_sys->i_font_opacity;
417 i_font_size = p_sys->i_font_size;
420 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
422 ConvertToUTF16( EliminateCRLF( psz_string ), &i_string_length, &psz_utf16_str );
424 p_region_out->i_x = p_region_in->i_x;
425 p_region_out->i_y = p_region_in->i_y;
427 if( psz_utf16_str != NULL )
429 ATSUStyle p_style = CreateStyle( p_sys->psz_font_name, i_font_size,
430 (i_font_color & 0xffffff) |
431 ((i_font_alpha & 0xff) << 24),
432 false, false, false );
435 RenderYUVA( p_filter, p_region_out, psz_utf16_str, i_string_length,
436 1, &i_string_length, &p_style );
439 ATSUDisposeStyle( p_style );
440 free( psz_utf16_str );
447 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size, uint32_t i_font_color,
448 bool b_bold, bool b_italic, bool b_uline )
454 float f_red = (float)(( i_font_color & 0x00FF0000 ) >> 16) / 255.0;
455 float f_green = (float)(( i_font_color & 0x0000FF00 ) >> 8) / 255.0;
456 float f_blue = (float)( i_font_color & 0x000000FF ) / 255.0;
457 float f_alpha = ( 255.0 - (float)(( i_font_color & 0xFF000000 ) >> 24)) / 255.0;
460 Fixed font_size = IntToFixed( i_font_size );
461 ATSURGBAlphaColor font_color = { f_red, f_green, f_blue, f_alpha };
462 Boolean bold = b_bold;
463 Boolean italic = b_italic;
464 Boolean uline = b_uline;
466 ATSUAttributeTag tags[] = { kATSUSizeTag, kATSURGBAlphaColorTag, kATSUQDItalicTag,
467 kATSUQDBoldfaceTag, kATSUQDUnderlineTag, kATSUFontTag };
468 ByteCount sizes[] = { sizeof( Fixed ), sizeof( ATSURGBAlphaColor ), sizeof( Boolean ),
469 sizeof( Boolean ), sizeof( Boolean ), sizeof( ATSUFontID )};
470 ATSUAttributeValuePtr values[] = { &font_size, &font_color, &italic, &bold, &uline, &font };
472 i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
474 status = ATSUFindFontFromName( psz_fontname,
475 strlen( psz_fontname ),
482 if( status != noErr )
484 // If we can't find a suitable font, just do everything else
488 if( noErr == ATSUCreateStyle( &p_style ) )
490 if( noErr == ATSUSetAttributes( p_style, i_tag_cnt, tags, sizes, values ) )
494 ATSUDisposeStyle( p_style );
499 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
507 p_new = malloc( sizeof( font_stack_t ) );
511 p_new->p_next = NULL;
514 p_new->psz_name = strdup( psz_name );
516 p_new->psz_name = NULL;
518 p_new->i_size = i_size;
519 p_new->i_color = i_color;
527 font_stack_t *p_last;
529 for( p_last = *p_font;
531 p_last = p_last->p_next )
534 p_last->p_next = p_new;
539 static int PopFont( font_stack_t **p_font )
541 font_stack_t *p_last, *p_next_to_last;
543 if( !p_font || !*p_font )
546 p_next_to_last = NULL;
547 for( p_last = *p_font;
549 p_last = p_last->p_next )
551 p_next_to_last = p_last;
555 p_next_to_last->p_next = NULL;
559 free( p_last->psz_name );
565 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
568 font_stack_t *p_last;
570 if( !p_font || !*p_font )
575 p_last=p_last->p_next )
578 *psz_name = p_last->psz_name;
579 *i_size = p_last->i_size;
580 *i_color = p_last->i_color;
585 static ATSUStyle GetStyleFromFontStack( filter_sys_t *p_sys,
586 font_stack_t **p_fonts, bool b_bold, bool b_italic,
589 ATSUStyle p_style = NULL;
591 char *psz_fontname = NULL;
592 uint32_t i_font_color = p_sys->i_font_color;
593 int i_font_size = p_sys->i_font_size;
595 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
598 p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
599 b_bold, b_italic, b_uline );
604 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
605 font_stack_t **p_fonts, int i_scale )
608 char *psz_fontname = NULL;
609 uint32_t i_font_color = 0xffffff;
610 int i_font_alpha = 0;
611 int i_font_size = 24;
613 // Default all attributes to the top font in the stack -- in case not
614 // all attributes are specified in the sub-font
615 if( VLC_SUCCESS == PeekFont( p_fonts,
620 psz_fontname = strdup( psz_fontname );
621 i_font_size = i_font_size * 1000 / i_scale;
623 i_font_alpha = (i_font_color >> 24) & 0xff;
624 i_font_color &= 0x00ffffff;
626 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
628 char *psz_name = xml_ReaderName( p_xml_reader );
629 char *psz_value = xml_ReaderValue( p_xml_reader );
631 if( psz_name && psz_value )
633 if( !strcasecmp( "face", psz_name ) )
635 free( psz_fontname );
636 psz_fontname = strdup( psz_value );
638 else if( !strcasecmp( "size", psz_name ) )
640 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
642 int i_value = atoi( psz_value );
644 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
645 i_font_size += ( i_value * i_font_size ) / 10;
646 else if( i_value < -5 )
647 i_font_size = - i_value;
648 else if( i_value > 5 )
649 i_font_size = i_value;
652 i_font_size = atoi( psz_value );
654 else if( !strcasecmp( "color", psz_name ) &&
655 ( psz_value[0] == '#' ) )
657 i_font_color = strtol( psz_value + 1, NULL, 16 );
658 i_font_color &= 0x00ffffff;
660 else if( !strcasecmp( "alpha", psz_name ) &&
661 ( psz_value[0] == '#' ) )
663 i_font_alpha = strtol( psz_value + 1, NULL, 16 );
664 i_font_alpha &= 0xff;
670 rv = PushFont( p_fonts,
672 i_font_size * i_scale / 1000,
673 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
675 free( psz_fontname );
680 static int ProcessNodes( filter_t *p_filter,
681 xml_reader_t *p_xml_reader,
682 text_style_t *p_font_style,
687 uint32_t **ppi_run_lengths,
688 ATSUStyle **ppp_styles )
690 int rv = VLC_SUCCESS;
691 filter_sys_t *p_sys = p_filter->p_sys;
692 UniChar *psz_text_orig = psz_text;
693 font_stack_t *p_fonts = NULL;
697 char *psz_node = NULL;
699 bool b_italic = false;
701 bool b_uline = false;
703 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
708 rv = PushFont( &p_fonts,
709 p_font_style->psz_fontname,
710 p_font_style->i_font_size * i_scale / 1000,
711 (p_font_style->i_font_color & 0xffffff) |
712 ((p_font_style->i_font_alpha & 0xff) << 24) );
714 if( p_font_style->i_style_flags & STYLE_BOLD )
716 if( p_font_style->i_style_flags & STYLE_ITALIC )
718 if( p_font_style->i_style_flags & STYLE_UNDERLINE )
723 rv = PushFont( &p_fonts,
724 p_sys->psz_font_name,
726 p_sys->i_font_color );
728 if( rv != VLC_SUCCESS )
731 while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
733 switch ( xml_ReaderNodeType( p_xml_reader ) )
735 case XML_READER_NONE:
737 case XML_READER_ENDELEM:
738 psz_node = xml_ReaderName( p_xml_reader );
741 if( !strcasecmp( "font", psz_node ) )
743 else if( !strcasecmp( "b", psz_node ) )
745 else if( !strcasecmp( "i", psz_node ) )
747 else if( !strcasecmp( "u", psz_node ) )
753 case XML_READER_STARTELEM:
754 psz_node = xml_ReaderName( p_xml_reader );
757 if( !strcasecmp( "font", psz_node ) )
758 rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
759 else if( !strcasecmp( "b", psz_node ) )
761 else if( !strcasecmp( "i", psz_node ) )
763 else if( !strcasecmp( "u", psz_node ) )
765 else if( !strcasecmp( "br", psz_node ) )
767 uint32_t i_string_length;
769 ConvertToUTF16( "\n", &i_string_length, &psz_text );
770 psz_text += i_string_length;
775 *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
777 *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
779 (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
781 if( *ppi_run_lengths )
782 *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
784 *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
786 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
791 case XML_READER_TEXT:
792 psz_node = xml_ReaderValue( p_xml_reader );
795 uint32_t i_string_length;
797 // Turn any multiple-whitespaces into single spaces
798 char *s = strpbrk( psz_node, "\t\r\n " );
801 int i_whitespace = strspn( s, "\t\r\n " );
803 if( i_whitespace > 1 )
806 strlen( s ) - i_whitespace + 1 );
809 s = strpbrk( s, "\t\r\n " );
812 ConvertToUTF16( psz_node, &i_string_length, &psz_text );
813 psz_text += i_string_length;
818 *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
820 *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
822 (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
824 if( *ppi_run_lengths )
825 *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
827 *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
829 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
837 *pi_len = psz_text - psz_text_orig;
839 while( VLC_SUCCESS == PopFont( &p_fonts ) );
844 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
845 subpicture_region_t *p_region_in )
847 int rv = VLC_SUCCESS;
848 stream_t *p_sub = NULL;
850 xml_reader_t *p_xml_reader = NULL;
852 if( !p_region_in || !p_region_in->psz_html )
855 /* Reset the default fontsize in case screen metrics have changed */
856 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
858 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
859 (uint8_t *) p_region_in->psz_html,
860 strlen( p_region_in->psz_html ),
864 p_xml = xml_Create( p_filter );
867 bool b_karaoke = false;
869 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
872 /* Look for Root Node */
873 if( xml_ReaderRead( p_xml_reader ) == 1 )
875 char *psz_node = xml_ReaderName( p_xml_reader );
877 if( !strcasecmp( "karaoke", psz_node ) )
879 /* We're going to have to render the text a number
880 * of times to show the progress marker on the text.
882 var_SetBool( p_filter, "text-rerender", true );
885 else if( !strcasecmp( "text", psz_node ) )
891 /* Only text and karaoke tags are supported */
892 xml_ReaderDelete( p_xml, p_xml_reader );
906 uint32_t *pi_run_lengths = NULL;
907 ATSUStyle *pp_styles = NULL;
909 psz_text = (UniChar *) malloc( strlen( p_region_in->psz_html ) *
915 rv = ProcessNodes( p_filter, p_xml_reader,
916 p_region_in->p_style, psz_text, &i_len,
917 &i_runs, &pi_run_lengths, &pp_styles );
919 p_region_out->i_x = p_region_in->i_x;
920 p_region_out->i_y = p_region_in->i_y;
922 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
924 RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs,
925 pi_run_lengths, pp_styles);
928 for( k=0; k<i_runs; k++)
929 ATSUDisposeStyle( pp_styles[k] );
931 free( pi_run_lengths );
935 xml_ReaderDelete( p_xml, p_xml_reader );
939 stream_Delete( p_sub );
945 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
946 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
948 offscreen_bitmap_t *p_bitmap;
949 CGContextRef p_context = NULL;
951 p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
954 p_bitmap->i_bitsPerChannel = 8;
955 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
956 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
957 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
959 p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
961 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
962 *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
964 *pp_colorSpace = CreateGenericRGBColorSpace();
967 if( p_bitmap->p_data && *pp_colorSpace )
969 p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
970 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
971 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
975 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_1
976 // OS X 10.1 doesn't support weak linking of this call which is only available
977 // int 10.4 and later
978 if( CGContextSetAllowsAntialiasing != NULL )
980 CGContextSetAllowsAntialiasing( p_context, true );
984 *pp_memory = p_bitmap;
990 static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len,
991 uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles,
992 int i_width, int i_height, int *pi_textblock_height )
994 offscreen_bitmap_t *p_offScreen = NULL;
995 CGColorSpaceRef p_colorSpace = NULL;
996 CGContextRef p_context = NULL;
998 p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
1002 ATSUTextLayout p_textLayout;
1003 OSStatus status = noErr;
1005 status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
1007 (const UniCharCount *) pi_run_lengths,
1010 if( status == noErr )
1012 // Attach our offscreen Image Graphics Context to the text style
1013 // and setup the line alignment (have to specify the line width
1014 // also in order for our chosen alignment to work)
1016 Fract alignment = kATSUStartAlignment;
1017 Fixed line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
1019 ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
1020 ByteCount sizes[] = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
1021 ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
1023 int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
1025 if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
1027 alignment = kATSUEndAlignment;
1029 else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
1031 alignment = kATSUCenterAlignment;
1034 ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
1036 // let ATSUI deal with characters not-in-our-specified-font
1037 ATSUSetTransientFontMatching( p_textLayout, true );
1039 Fixed x = Long2Fix( HORIZONTAL_MARGIN );
1040 Fixed y = Long2Fix( i_height );
1042 // Set the line-breaks and draw individual lines
1043 uint32_t i_start = 0;
1044 uint32_t i_end = i_text_len;
1046 // Set up black outlining of the text --
1047 CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
1048 CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
1052 // ATSUBreakLine will automatically pick up any manual '\n's also
1053 status = ATSUBreakLine( p_textLayout, i_start, line_width, true, (UniCharArrayOffset *) &i_end );
1054 if( ( status == noErr ) || ( status == kATSULineBreakInWord ) )
1058 uint32_t i_actualSize;
1060 // Come down far enough to fit the height of this line --
1061 ATSUGetLineControl( p_textLayout, i_start, kATSULineAscentTag,
1062 sizeof( Fixed ), &ascent, (ByteCount *) &i_actualSize );
1064 // Quartz uses an upside-down co-ordinate space -> y values decrease as
1065 // you move down the page
1068 // Set the outlining for this line to be dependent on the size of the line -
1069 // make it about 5% of the ascent, with a minimum at 1.0
1070 float f_thickness = FixedToFloat( ascent ) * 0.05;
1071 CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
1073 ATSUDrawText( p_textLayout, i_start, i_end - i_start, x, y );
1075 // and now prepare for the next line by coming down far enough for our
1077 ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag,
1078 sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize );
1086 while( i_end < i_text_len );
1088 *pi_textblock_height = i_height - Fix2Long( y );
1089 CGContextFlush( p_context );
1091 ATSUDisposeTextLayout( p_textLayout );
1094 CGContextRelease( p_context );
1096 if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
1101 static int GetFontSize( filter_t *p_filter )
1103 return p_filter->fmt_out.video.i_height / __MAX(1, var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" ));
1106 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniChar *psz_utf16_str,
1107 uint32_t i_text_len, uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles )
1109 offscreen_bitmap_t *p_offScreen = NULL;
1110 int i_textblock_height = 0;
1112 int i_width = p_filter->fmt_out.video.i_visible_width;
1113 int i_height = p_filter->fmt_out.video.i_visible_height;
1114 int i_text_align = p_region->i_align & 0x3;
1116 if( !psz_utf16_str )
1118 msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1119 return VLC_EGENERIC;
1122 p_offScreen = Compose( i_text_align, psz_utf16_str, i_text_len,
1123 i_runs, pi_run_lengths, pp_styles,
1124 i_width, i_height, &i_textblock_height );
1128 msg_Err( p_filter, "No offscreen buffer" );
1129 return VLC_EGENERIC;
1132 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1134 int x, y, i_offset, i_pitch;
1135 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1136 subpicture_region_t *p_region_tmp;
1138 // Create a new subpicture region
1139 memset( &fmt, 0, sizeof(video_format_t) );
1140 fmt.i_chroma = VLC_FOURCC('Y','U','V','A');
1142 fmt.i_width = fmt.i_visible_width = i_width;
1143 fmt.i_height = fmt.i_visible_height = i_textblock_height + VERTICAL_MARGIN * 2;
1144 fmt.i_x_offset = fmt.i_y_offset = 0;
1145 p_region_tmp = spu_CreateRegion( p_filter, &fmt );
1148 msg_Err( p_filter, "cannot allocate SPU region" );
1149 return VLC_EGENERIC;
1151 p_region->fmt = p_region_tmp->fmt;
1152 p_region->picture = p_region_tmp->picture;
1153 free( p_region_tmp );
1155 p_dst_y = p_region->picture.Y_PIXELS;
1156 p_dst_u = p_region->picture.U_PIXELS;
1157 p_dst_v = p_region->picture.V_PIXELS;
1158 p_dst_a = p_region->picture.A_PIXELS;
1159 i_pitch = p_region->picture.A_PITCH;
1161 i_offset = VERTICAL_MARGIN *i_pitch;
1162 for( y=0; y<i_textblock_height; y++)
1164 for( x=0; x<i_width; x++)
1166 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1167 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1168 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1169 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1171 i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
1172 802 * i_blue + 4096 + 131072 ) >> 13, 235);
1173 i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
1174 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1175 i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1176 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1178 p_dst_y[ i_offset + x ] = i_y;
1179 p_dst_u[ i_offset + x ] = i_u;
1180 p_dst_v[ i_offset + x ] = i_v;
1181 p_dst_a[ i_offset + x ] = i_alpha;
1183 i_offset += i_pitch;
1186 free( p_offScreen->p_data );
1187 free( p_offScreen );