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 );
368 i_string_length = CFStringGetLength( p_cfString );
371 *pi_strlen = i_string_length;
373 if( !*ppsz_utf16_str )
374 *ppsz_utf16_str = (UniChar *) calloc( i_string_length, sizeof( UniChar ) );
376 CFStringGetCharacters( p_cfString, CFRangeMake( 0, i_string_length ), *ppsz_utf16_str );
378 CFRelease( p_cfString );
381 // Renders a text subpicture region into another one.
382 // It is used as pf_add_string callback in the vout method by this module
383 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
384 subpicture_region_t *p_region_in )
386 filter_sys_t *p_sys = p_filter->p_sys;
387 UniChar *psz_utf16_str = NULL;
388 uint32_t i_string_length;
390 int i_font_color, i_font_alpha, i_font_size;
394 p_sys->i_font_size = GetFontSize( p_filter );
397 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
398 psz_string = p_region_in->psz_text;
399 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
401 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
404 if( p_region_in->p_style )
406 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
407 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
408 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
412 i_font_color = p_sys->i_font_color;
413 i_font_alpha = 255 - p_sys->i_font_opacity;
414 i_font_size = p_sys->i_font_size;
417 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
419 ConvertToUTF16( EliminateCRLF( psz_string ), &i_string_length, &psz_utf16_str );
421 p_region_out->i_x = p_region_in->i_x;
422 p_region_out->i_y = p_region_in->i_y;
424 if( psz_utf16_str != NULL )
426 ATSUStyle p_style = CreateStyle( p_sys->psz_font_name, i_font_size,
427 (i_font_color & 0xffffff) |
428 ((i_font_alpha & 0xff) << 24),
429 false, false, false );
432 RenderYUVA( p_filter, p_region_out, psz_utf16_str, i_string_length,
433 1, &i_string_length, &p_style );
436 ATSUDisposeStyle( p_style );
437 free( psz_utf16_str );
444 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size, uint32_t i_font_color,
445 bool b_bold, bool b_italic, bool b_uline )
451 float f_red = (float)(( i_font_color & 0x00FF0000 ) >> 16) / 255.0;
452 float f_green = (float)(( i_font_color & 0x0000FF00 ) >> 8) / 255.0;
453 float f_blue = (float)( i_font_color & 0x000000FF ) / 255.0;
454 float f_alpha = ( 255.0 - (float)(( i_font_color & 0xFF000000 ) >> 24)) / 255.0;
457 Fixed font_size = IntToFixed( i_font_size );
458 ATSURGBAlphaColor font_color = { f_red, f_green, f_blue, f_alpha };
459 Boolean bold = b_bold;
460 Boolean italic = b_italic;
461 Boolean uline = b_uline;
463 ATSUAttributeTag tags[] = { kATSUSizeTag, kATSURGBAlphaColorTag, kATSUQDItalicTag,
464 kATSUQDBoldfaceTag, kATSUQDUnderlineTag, kATSUFontTag };
465 ByteCount sizes[] = { sizeof( Fixed ), sizeof( ATSURGBAlphaColor ), sizeof( Boolean ),
466 sizeof( Boolean ), sizeof( Boolean ), sizeof( ATSUFontID )};
467 ATSUAttributeValuePtr values[] = { &font_size, &font_color, &italic, &bold, &uline, &font };
469 i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
471 status = ATSUFindFontFromName( psz_fontname,
472 strlen( psz_fontname ),
479 if( status != noErr )
481 // If we can't find a suitable font, just do everything else
485 if( noErr == ATSUCreateStyle( &p_style ) )
487 if( noErr == ATSUSetAttributes( p_style, i_tag_cnt, tags, sizes, values ) )
491 ATSUDisposeStyle( p_style );
496 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
504 p_new = malloc( sizeof( font_stack_t ) );
508 p_new->p_next = NULL;
511 p_new->psz_name = strdup( psz_name );
513 p_new->psz_name = NULL;
515 p_new->i_size = i_size;
516 p_new->i_color = i_color;
524 font_stack_t *p_last;
526 for( p_last = *p_font;
528 p_last = p_last->p_next )
531 p_last->p_next = p_new;
536 static int PopFont( font_stack_t **p_font )
538 font_stack_t *p_last, *p_next_to_last;
540 if( !p_font || !*p_font )
543 p_next_to_last = NULL;
544 for( p_last = *p_font;
546 p_last = p_last->p_next )
548 p_next_to_last = p_last;
552 p_next_to_last->p_next = NULL;
556 free( p_last->psz_name );
562 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
565 font_stack_t *p_last;
567 if( !p_font || !*p_font )
572 p_last=p_last->p_next )
575 *psz_name = p_last->psz_name;
576 *i_size = p_last->i_size;
577 *i_color = p_last->i_color;
582 static ATSUStyle GetStyleFromFontStack( filter_sys_t *p_sys,
583 font_stack_t **p_fonts, bool b_bold, bool b_italic,
586 ATSUStyle p_style = NULL;
588 char *psz_fontname = NULL;
589 uint32_t i_font_color = p_sys->i_font_color;
590 int i_font_size = p_sys->i_font_size;
592 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
595 p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
596 b_bold, b_italic, b_uline );
601 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
602 font_stack_t **p_fonts, int i_scale )
605 char *psz_fontname = NULL;
606 uint32_t i_font_color = 0xffffff;
607 int i_font_alpha = 0;
608 int i_font_size = 24;
610 // Default all attributes to the top font in the stack -- in case not
611 // all attributes are specified in the sub-font
612 if( VLC_SUCCESS == PeekFont( p_fonts,
617 psz_fontname = strdup( psz_fontname );
618 i_font_size = i_font_size * 1000 / i_scale;
620 i_font_alpha = (i_font_color >> 24) & 0xff;
621 i_font_color &= 0x00ffffff;
623 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
625 char *psz_name = xml_ReaderName( p_xml_reader );
626 char *psz_value = xml_ReaderValue( p_xml_reader );
628 if( psz_name && psz_value )
630 if( !strcasecmp( "face", psz_name ) )
632 free( psz_fontname );
633 psz_fontname = strdup( psz_value );
635 else if( !strcasecmp( "size", psz_name ) )
637 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
639 int i_value = atoi( psz_value );
641 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
642 i_font_size += ( i_value * i_font_size ) / 10;
643 else if( i_value < -5 )
644 i_font_size = - i_value;
645 else if( i_value > 5 )
646 i_font_size = i_value;
649 i_font_size = atoi( psz_value );
651 else if( !strcasecmp( "color", psz_name ) &&
652 ( psz_value[0] == '#' ) )
654 i_font_color = strtol( psz_value + 1, NULL, 16 );
655 i_font_color &= 0x00ffffff;
657 else if( !strcasecmp( "alpha", psz_name ) &&
658 ( psz_value[0] == '#' ) )
660 i_font_alpha = strtol( psz_value + 1, NULL, 16 );
661 i_font_alpha &= 0xff;
667 rv = PushFont( p_fonts,
669 i_font_size * i_scale / 1000,
670 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
672 free( psz_fontname );
677 static int ProcessNodes( filter_t *p_filter,
678 xml_reader_t *p_xml_reader,
679 text_style_t *p_font_style,
684 uint32_t **ppi_run_lengths,
685 ATSUStyle **ppp_styles )
687 int rv = VLC_SUCCESS;
688 filter_sys_t *p_sys = p_filter->p_sys;
689 UniChar *psz_text_orig = psz_text;
690 font_stack_t *p_fonts = NULL;
694 char *psz_node = NULL;
696 bool b_italic = false;
698 bool b_uline = false;
700 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
705 rv = PushFont( &p_fonts,
706 p_font_style->psz_fontname,
707 p_font_style->i_font_size * i_scale / 1000,
708 (p_font_style->i_font_color & 0xffffff) |
709 ((p_font_style->i_font_alpha & 0xff) << 24) );
711 if( p_font_style->i_style_flags & STYLE_BOLD )
713 if( p_font_style->i_style_flags & STYLE_ITALIC )
715 if( p_font_style->i_style_flags & STYLE_UNDERLINE )
720 rv = PushFont( &p_fonts,
721 p_sys->psz_font_name,
723 p_sys->i_font_color );
725 if( rv != VLC_SUCCESS )
728 while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
730 switch ( xml_ReaderNodeType( p_xml_reader ) )
732 case XML_READER_NONE:
734 case XML_READER_ENDELEM:
735 psz_node = xml_ReaderName( p_xml_reader );
739 if( !strcasecmp( "font", psz_node ) )
741 else if( !strcasecmp( "b", psz_node ) )
743 else if( !strcasecmp( "i", psz_node ) )
745 else if( !strcasecmp( "u", psz_node ) )
751 case XML_READER_STARTELEM:
752 psz_node = xml_ReaderName( p_xml_reader );
755 if( !strcasecmp( "font", psz_node ) )
756 rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
757 else if( !strcasecmp( "b", psz_node ) )
759 else if( !strcasecmp( "i", psz_node ) )
761 else if( !strcasecmp( "u", psz_node ) )
763 else if( !strcasecmp( "br", psz_node ) )
765 uint32_t i_string_length;
767 ConvertToUTF16( "\n", &i_string_length, &psz_text );
768 psz_text += i_string_length;
773 *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
775 *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
777 (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
779 if( *ppi_run_lengths )
780 *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
782 *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
784 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
789 case XML_READER_TEXT:
790 psz_node = xml_ReaderValue( p_xml_reader );
793 uint32_t i_string_length;
795 // Turn any multiple-whitespaces into single spaces
796 char *s = strpbrk( psz_node, "\t\r\n " );
799 int i_whitespace = strspn( s, "\t\r\n " );
801 if( i_whitespace > 1 )
804 strlen( s ) - i_whitespace + 1 );
807 s = strpbrk( s, "\t\r\n " );
810 ConvertToUTF16( psz_node, &i_string_length, &psz_text );
811 psz_text += i_string_length;
816 *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
818 *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
820 (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
822 if( *ppi_run_lengths )
823 *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
825 *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
827 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
835 *pi_len = psz_text - psz_text_orig;
837 while( VLC_SUCCESS == PopFont( &p_fonts ) );
842 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
843 subpicture_region_t *p_region_in )
845 int rv = VLC_SUCCESS;
846 stream_t *p_sub = NULL;
848 xml_reader_t *p_xml_reader = NULL;
850 if( !p_region_in || !p_region_in->psz_html )
853 /* Reset the default fontsize in case screen metrics have changed */
854 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
856 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
857 (uint8_t *) p_region_in->psz_html,
858 strlen( p_region_in->psz_html ),
862 p_xml = xml_Create( p_filter );
865 bool b_karaoke = false;
867 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
870 /* Look for Root Node */
871 if( xml_ReaderRead( p_xml_reader ) == 1 )
873 char *psz_node = xml_ReaderName( p_xml_reader );
875 if( !strcasecmp( "karaoke", psz_node ) )
877 /* We're going to have to render the text a number
878 * of times to show the progress marker on the text.
880 var_SetBool( p_filter, "text-rerender", true );
883 else if( !strcasecmp( "text", psz_node ) )
889 /* Only text and karaoke tags are supported */
890 xml_ReaderDelete( p_xml, p_xml_reader );
904 uint32_t *pi_run_lengths = NULL;
905 ATSUStyle *pp_styles = NULL;
907 psz_text = (UniChar *) malloc( strlen( p_region_in->psz_html ) *
913 rv = ProcessNodes( p_filter, p_xml_reader,
914 p_region_in->p_style, psz_text, &i_len,
915 &i_runs, &pi_run_lengths, &pp_styles );
917 p_region_out->i_x = p_region_in->i_x;
918 p_region_out->i_y = p_region_in->i_y;
920 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
922 RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs,
923 pi_run_lengths, pp_styles);
926 for( k=0; k<i_runs; k++)
927 ATSUDisposeStyle( pp_styles[k] );
929 free( pi_run_lengths );
933 xml_ReaderDelete( p_xml, p_xml_reader );
937 stream_Delete( p_sub );
943 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
944 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
946 offscreen_bitmap_t *p_bitmap;
947 CGContextRef p_context = NULL;
949 p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
952 p_bitmap->i_bitsPerChannel = 8;
953 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
954 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
955 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
957 p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
959 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
960 *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
962 *pp_colorSpace = CreateGenericRGBColorSpace();
965 if( p_bitmap->p_data && *pp_colorSpace )
967 p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
968 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
969 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
973 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_1
974 // OS X 10.1 doesn't support weak linking of this call which is only available
975 // int 10.4 and later
976 if( CGContextSetAllowsAntialiasing != NULL )
978 CGContextSetAllowsAntialiasing( p_context, true );
982 *pp_memory = p_bitmap;
988 static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len,
989 uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles,
990 int i_width, int i_height, int *pi_textblock_height )
992 offscreen_bitmap_t *p_offScreen = NULL;
993 CGColorSpaceRef p_colorSpace = NULL;
994 CGContextRef p_context = NULL;
996 p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
1000 ATSUTextLayout p_textLayout;
1001 OSStatus status = noErr;
1003 status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
1005 (const UniCharCount *) pi_run_lengths,
1008 if( status == noErr )
1010 // Attach our offscreen Image Graphics Context to the text style
1011 // and setup the line alignment (have to specify the line width
1012 // also in order for our chosen alignment to work)
1014 Fract alignment = kATSUStartAlignment;
1015 Fixed line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
1017 ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
1018 ByteCount sizes[] = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
1019 ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
1021 int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
1023 if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
1025 alignment = kATSUEndAlignment;
1027 else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
1029 alignment = kATSUCenterAlignment;
1032 ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
1034 // let ATSUI deal with characters not-in-our-specified-font
1035 ATSUSetTransientFontMatching( p_textLayout, true );
1037 Fixed x = Long2Fix( HORIZONTAL_MARGIN );
1038 Fixed y = Long2Fix( i_height );
1040 // Set the line-breaks and draw individual lines
1041 uint32_t i_start = 0;
1042 uint32_t i_end = i_text_len;
1044 // Set up black outlining of the text --
1045 CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
1046 CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
1050 // ATSUBreakLine will automatically pick up any manual '\n's also
1051 status = ATSUBreakLine( p_textLayout, i_start, line_width, true, (UniCharArrayOffset *) &i_end );
1052 if( ( status == noErr ) || ( status == kATSULineBreakInWord ) )
1056 uint32_t i_actualSize;
1058 // Come down far enough to fit the height of this line --
1059 ATSUGetLineControl( p_textLayout, i_start, kATSULineAscentTag,
1060 sizeof( Fixed ), &ascent, (ByteCount *) &i_actualSize );
1062 // Quartz uses an upside-down co-ordinate space -> y values decrease as
1063 // you move down the page
1066 // Set the outlining for this line to be dependent on the size of the line -
1067 // make it about 5% of the ascent, with a minimum at 1.0
1068 float f_thickness = FixedToFloat( ascent ) * 0.05;
1069 CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
1071 ATSUDrawText( p_textLayout, i_start, i_end - i_start, x, y );
1073 // and now prepare for the next line by coming down far enough for our
1075 ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag,
1076 sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize );
1084 while( i_end < i_text_len );
1086 *pi_textblock_height = i_height - Fix2Long( y );
1087 CGContextFlush( p_context );
1089 ATSUDisposeTextLayout( p_textLayout );
1092 CGContextRelease( p_context );
1094 if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
1099 static int GetFontSize( filter_t *p_filter )
1101 return p_filter->fmt_out.video.i_height / __MAX(1, var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" ));
1104 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniChar *psz_utf16_str,
1105 uint32_t i_text_len, uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles )
1107 offscreen_bitmap_t *p_offScreen = NULL;
1108 int i_textblock_height = 0;
1110 int i_width = p_filter->fmt_out.video.i_visible_width;
1111 int i_height = p_filter->fmt_out.video.i_visible_height;
1112 int i_text_align = p_region->i_align & 0x3;
1114 if( !psz_utf16_str )
1116 msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1117 return VLC_EGENERIC;
1120 p_offScreen = Compose( i_text_align, psz_utf16_str, i_text_len,
1121 i_runs, pi_run_lengths, pp_styles,
1122 i_width, i_height, &i_textblock_height );
1126 msg_Err( p_filter, "No offscreen buffer" );
1127 return VLC_EGENERIC;
1130 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1132 int x, y, i_offset, i_pitch;
1133 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1134 subpicture_region_t *p_region_tmp;
1136 // Create a new subpicture region
1137 memset( &fmt, 0, sizeof(video_format_t) );
1138 fmt.i_chroma = VLC_FOURCC('Y','U','V','A');
1140 fmt.i_width = fmt.i_visible_width = i_width;
1141 fmt.i_height = fmt.i_visible_height = i_textblock_height + VERTICAL_MARGIN * 2;
1142 fmt.i_x_offset = fmt.i_y_offset = 0;
1143 p_region_tmp = spu_CreateRegion( p_filter, &fmt );
1146 msg_Err( p_filter, "cannot allocate SPU region" );
1147 return VLC_EGENERIC;
1149 p_region->fmt = p_region_tmp->fmt;
1150 p_region->picture = p_region_tmp->picture;
1151 free( p_region_tmp );
1153 p_dst_y = p_region->picture.Y_PIXELS;
1154 p_dst_u = p_region->picture.U_PIXELS;
1155 p_dst_v = p_region->picture.V_PIXELS;
1156 p_dst_a = p_region->picture.A_PIXELS;
1157 i_pitch = p_region->picture.A_PITCH;
1159 i_offset = VERTICAL_MARGIN *i_pitch;
1160 for( y=0; y<i_textblock_height; y++)
1162 for( x=0; x<i_width; x++)
1164 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1165 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1166 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1167 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1169 i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
1170 802 * i_blue + 4096 + 131072 ) >> 13, 235);
1171 i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
1172 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1173 i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1174 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1176 p_dst_y[ i_offset + x ] = i_y;
1177 p_dst_u[ i_offset + x ] = i_u;
1178 p_dst_v[ i_offset + x ] = i_v;
1179 p_dst_a[ i_offset + x ] = i_alpha;
1181 i_offset += i_pitch;
1184 free( p_offScreen->p_data );
1185 free( p_offScreen );