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, 0 );
121 add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
122 COLOR_LONGTEXT, false );
123 change_integer_list( pi_color_values, ppsz_color_descriptions, 0 );
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 );
742 if( !strcasecmp( "font", psz_node ) )
744 else if( !strcasecmp( "b", psz_node ) )
746 else if( !strcasecmp( "i", psz_node ) )
748 else if( !strcasecmp( "u", psz_node ) )
754 case XML_READER_STARTELEM:
755 psz_node = xml_ReaderName( p_xml_reader );
758 if( !strcasecmp( "font", psz_node ) )
759 rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
760 else if( !strcasecmp( "b", psz_node ) )
762 else if( !strcasecmp( "i", psz_node ) )
764 else if( !strcasecmp( "u", psz_node ) )
766 else if( !strcasecmp( "br", psz_node ) )
768 uint32_t i_string_length;
770 ConvertToUTF16( "\n", &i_string_length, &psz_text );
771 psz_text += i_string_length;
776 *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
778 *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
780 (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
782 if( *ppi_run_lengths )
783 *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
785 *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
787 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
792 case XML_READER_TEXT:
793 psz_node = xml_ReaderValue( p_xml_reader );
796 uint32_t i_string_length;
798 // Turn any multiple-whitespaces into single spaces
799 char *s = strpbrk( psz_node, "\t\r\n " );
802 int i_whitespace = strspn( s, "\t\r\n " );
804 if( i_whitespace > 1 )
807 strlen( s ) - i_whitespace + 1 );
810 s = strpbrk( s, "\t\r\n " );
813 ConvertToUTF16( psz_node, &i_string_length, &psz_text );
814 psz_text += i_string_length;
819 *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
821 *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
823 (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
825 if( *ppi_run_lengths )
826 *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
828 *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
830 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
838 *pi_len = psz_text - psz_text_orig;
840 while( VLC_SUCCESS == PopFont( &p_fonts ) );
845 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
846 subpicture_region_t *p_region_in )
848 int rv = VLC_SUCCESS;
849 stream_t *p_sub = NULL;
851 xml_reader_t *p_xml_reader = NULL;
853 if( !p_region_in || !p_region_in->psz_html )
856 /* Reset the default fontsize in case screen metrics have changed */
857 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
859 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
860 (uint8_t *) p_region_in->psz_html,
861 strlen( p_region_in->psz_html ),
865 p_xml = xml_Create( p_filter );
868 bool b_karaoke = false;
870 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
873 /* Look for Root Node */
874 if( xml_ReaderRead( p_xml_reader ) == 1 )
876 char *psz_node = xml_ReaderName( p_xml_reader );
878 if( !strcasecmp( "karaoke", psz_node ) )
880 /* We're going to have to render the text a number
881 * of times to show the progress marker on the text.
883 var_SetBool( p_filter, "text-rerender", true );
886 else if( !strcasecmp( "text", psz_node ) )
892 /* Only text and karaoke tags are supported */
893 xml_ReaderDelete( p_xml, p_xml_reader );
907 uint32_t *pi_run_lengths = NULL;
908 ATSUStyle *pp_styles = NULL;
910 psz_text = (UniChar *) malloc( strlen( p_region_in->psz_html ) *
916 rv = ProcessNodes( p_filter, p_xml_reader,
917 p_region_in->p_style, psz_text, &i_len,
918 &i_runs, &pi_run_lengths, &pp_styles );
920 p_region_out->i_x = p_region_in->i_x;
921 p_region_out->i_y = p_region_in->i_y;
923 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
925 RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs,
926 pi_run_lengths, pp_styles);
929 for( k=0; k<i_runs; k++)
930 ATSUDisposeStyle( pp_styles[k] );
932 free( pi_run_lengths );
936 xml_ReaderDelete( p_xml, p_xml_reader );
940 stream_Delete( p_sub );
946 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
947 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
949 offscreen_bitmap_t *p_bitmap;
950 CGContextRef p_context = NULL;
952 p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
955 p_bitmap->i_bitsPerChannel = 8;
956 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
957 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
958 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
960 p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
962 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
963 *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
965 *pp_colorSpace = CreateGenericRGBColorSpace();
968 if( p_bitmap->p_data && *pp_colorSpace )
970 p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
971 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
972 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
976 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_1
977 // OS X 10.1 doesn't support weak linking of this call which is only available
978 // int 10.4 and later
979 if( CGContextSetAllowsAntialiasing != NULL )
981 CGContextSetAllowsAntialiasing( p_context, true );
985 *pp_memory = p_bitmap;
991 static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len,
992 uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles,
993 int i_width, int i_height, int *pi_textblock_height )
995 offscreen_bitmap_t *p_offScreen = NULL;
996 CGColorSpaceRef p_colorSpace = NULL;
997 CGContextRef p_context = NULL;
999 p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
1003 ATSUTextLayout p_textLayout;
1004 OSStatus status = noErr;
1006 status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
1008 (const UniCharCount *) pi_run_lengths,
1011 if( status == noErr )
1013 // Attach our offscreen Image Graphics Context to the text style
1014 // and setup the line alignment (have to specify the line width
1015 // also in order for our chosen alignment to work)
1017 Fract alignment = kATSUStartAlignment;
1018 Fixed line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
1020 ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
1021 ByteCount sizes[] = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
1022 ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
1024 int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
1026 if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
1028 alignment = kATSUEndAlignment;
1030 else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
1032 alignment = kATSUCenterAlignment;
1035 ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
1037 // let ATSUI deal with characters not-in-our-specified-font
1038 ATSUSetTransientFontMatching( p_textLayout, true );
1040 Fixed x = Long2Fix( HORIZONTAL_MARGIN );
1041 Fixed y = Long2Fix( i_height );
1043 // Set the line-breaks and draw individual lines
1044 uint32_t i_start = 0;
1045 uint32_t i_end = i_text_len;
1047 // Set up black outlining of the text --
1048 CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
1049 CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
1053 // ATSUBreakLine will automatically pick up any manual '\n's also
1054 status = ATSUBreakLine( p_textLayout, i_start, line_width, true, (UniCharArrayOffset *) &i_end );
1055 if( ( status == noErr ) || ( status == kATSULineBreakInWord ) )
1059 uint32_t i_actualSize;
1061 // Come down far enough to fit the height of this line --
1062 ATSUGetLineControl( p_textLayout, i_start, kATSULineAscentTag,
1063 sizeof( Fixed ), &ascent, (ByteCount *) &i_actualSize );
1065 // Quartz uses an upside-down co-ordinate space -> y values decrease as
1066 // you move down the page
1069 // Set the outlining for this line to be dependent on the size of the line -
1070 // make it about 5% of the ascent, with a minimum at 1.0
1071 float f_thickness = FixedToFloat( ascent ) * 0.05;
1072 CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
1074 ATSUDrawText( p_textLayout, i_start, i_end - i_start, x, y );
1076 // and now prepare for the next line by coming down far enough for our
1078 ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag,
1079 sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize );
1087 while( i_end < i_text_len );
1089 *pi_textblock_height = i_height - Fix2Long( y );
1090 CGContextFlush( p_context );
1092 ATSUDisposeTextLayout( p_textLayout );
1095 CGContextRelease( p_context );
1097 if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
1102 static int GetFontSize( filter_t *p_filter )
1104 return p_filter->fmt_out.video.i_height / __MAX(1, var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" ));
1107 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniChar *psz_utf16_str,
1108 uint32_t i_text_len, uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles )
1110 offscreen_bitmap_t *p_offScreen = NULL;
1111 int i_textblock_height = 0;
1113 int i_width = p_filter->fmt_out.video.i_visible_width;
1114 int i_height = p_filter->fmt_out.video.i_visible_height;
1115 int i_text_align = p_region->i_align & 0x3;
1117 if( !psz_utf16_str )
1119 msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1120 return VLC_EGENERIC;
1123 p_offScreen = Compose( i_text_align, psz_utf16_str, i_text_len,
1124 i_runs, pi_run_lengths, pp_styles,
1125 i_width, i_height, &i_textblock_height );
1129 msg_Err( p_filter, "No offscreen buffer" );
1130 return VLC_EGENERIC;
1133 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1135 int x, y, i_offset, i_pitch;
1136 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1137 subpicture_region_t *p_region_tmp;
1139 // Create a new subpicture region
1140 memset( &fmt, 0, sizeof(video_format_t) );
1141 fmt.i_chroma = VLC_FOURCC('Y','U','V','A');
1143 fmt.i_width = fmt.i_visible_width = i_width;
1144 fmt.i_height = fmt.i_visible_height = i_textblock_height + VERTICAL_MARGIN * 2;
1145 fmt.i_x_offset = fmt.i_y_offset = 0;
1146 p_region_tmp = spu_CreateRegion( p_filter, &fmt );
1149 msg_Err( p_filter, "cannot allocate SPU region" );
1150 return VLC_EGENERIC;
1152 p_region->fmt = p_region_tmp->fmt;
1153 p_region->picture = p_region_tmp->picture;
1154 free( p_region_tmp );
1156 p_dst_y = p_region->picture.Y_PIXELS;
1157 p_dst_u = p_region->picture.U_PIXELS;
1158 p_dst_v = p_region->picture.V_PIXELS;
1159 p_dst_a = p_region->picture.A_PIXELS;
1160 i_pitch = p_region->picture.A_PITCH;
1162 i_offset = VERTICAL_MARGIN *i_pitch;
1163 for( y=0; y<i_textblock_height; y++)
1165 for( x=0; x<i_width; x++)
1167 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1168 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1169 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1170 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1172 i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
1173 802 * i_blue + 4096 + 131072 ) >> 13, 235);
1174 i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
1175 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1176 i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1177 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1179 p_dst_y[ i_offset + x ] = i_y;
1180 p_dst_u[ i_offset + x ] = i_u;
1181 p_dst_v[ i_offset + x ] = i_v;
1182 p_dst_a[ i_offset + x ] = i_alpha;
1184 i_offset += i_pitch;
1187 free( p_offScreen->p_data );
1188 free( p_offScreen );