1 /*****************************************************************************
2 * quartztext.c : Put text on the video, using Mac OS X Quartz Engine
3 *****************************************************************************
4 * Copyright (C) 2007, 2009 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 //////////////////////////////////////////////////////////////////////////////
30 #warning "No text renderer build! Quartztext isn't 64bit compatible!"
31 #warning "RE-WRITE ME!"
39 #include <vlc_common.h>
40 #include <vlc_plugin.h>
43 #include <vlc_block.h>
44 #include <vlc_filter.h>
45 #include <vlc_stream.h>
47 #include <vlc_input.h>
48 #include <vlc_strings.h>
52 #include <Carbon/Carbon.h>
54 #define DEFAULT_FONT "Arial Black"
55 #define DEFAULT_FONT_COLOR 0xffffff
56 #define DEFAULT_REL_FONT_SIZE 16
58 #define VERTICAL_MARGIN 3
59 #define HORIZONTAL_MARGIN 10
61 //////////////////////////////////////////////////////////////////////////////
63 //////////////////////////////////////////////////////////////////////////////
64 static int Create ( vlc_object_t * );
65 static void Destroy( vlc_object_t * );
67 static int LoadFontsFromAttachments( filter_t *p_filter );
69 static int RenderText( filter_t *, subpicture_region_t *,
70 subpicture_region_t * );
71 static int RenderHtml( filter_t *, subpicture_region_t *,
72 subpicture_region_t * );
74 static int GetFontSize( filter_t *p_filter );
75 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
76 UniChar *psz_utfString, uint32_t i_text_len,
77 uint32_t i_runs, uint32_t *pi_run_lengths,
78 ATSUStyle *pp_styles );
79 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size,
80 uint32_t i_font_color,
81 bool b_bold, bool b_italic,
83 //////////////////////////////////////////////////////////////////////////////
85 //////////////////////////////////////////////////////////////////////////////
87 // The preferred way to set font style information is for it to come from the
88 // subtitle file, and for it to be rendered with RenderHtml instead of
89 // RenderText. This module, unlike Freetype, doesn't provide any options to
90 // override the fallback font selection used when this style information is
92 #define FONT_TEXT N_("Font")
93 #define FONT_LONGTEXT N_("Name for the font you want to use")
94 #define FONTSIZER_TEXT N_("Relative font size")
95 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
96 "fonts that will be rendered on the video. If absolute font size is set, "\
97 "relative size will be overriden." )
98 #define COLOR_TEXT N_("Text default color")
99 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
100 "the video. This must be an hexadecimal (like HTML colors). The first two "\
101 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
102 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
104 static const int pi_color_values[] = {
105 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
106 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
107 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
109 static const char *const ppsz_color_descriptions[] = {
110 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
111 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
112 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
114 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
115 static const char *const ppsz_sizes_text[] = {
116 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
119 set_shortname( N_("Mac Text renderer"))
120 set_description( N_("Quartz font renderer") )
121 set_category( CAT_VIDEO )
122 set_subcategory( SUBCAT_VIDEO_SUBPIC )
124 add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
126 add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT,
127 FONTSIZER_LONGTEXT, false )
128 change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
129 add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
130 COLOR_LONGTEXT, false )
131 change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
132 set_capability( "text renderer", 150 )
133 add_shortcut( "text" )
134 set_callbacks( Create, Destroy )
137 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
138 struct offscreen_bitmap_t
141 int i_bitsPerChannel;
147 //////////////////////////////////////////////////////////////////////////////
148 // filter_sys_t: quartztext local data
149 //////////////////////////////////////////////////////////////////////////////
150 // This structure is part of the video output thread descriptor.
151 // It describes the freetype specific properties of an output thread.
152 //////////////////////////////////////////////////////////////////////////////
156 uint8_t i_font_opacity;
160 ATSFontContainerRef *p_fonts;
164 #define UCHAR UniChar
165 #define TR_DEFAULT_FONT p_sys->psz_font_name
166 #define TR_FONT_STYLE_PTR ATSUStyle
168 #include "text_renderer.h"
170 //////////////////////////////////////////////////////////////////////////////
171 // Create: allocates osd-text video thread output method
172 //////////////////////////////////////////////////////////////////////////////
173 // This function allocates and initializes a Clone vout method.
174 //////////////////////////////////////////////////////////////////////////////
175 static int Create( vlc_object_t *p_this )
177 filter_t *p_filter = (filter_t *)p_this;
180 // Allocate structure
181 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
184 p_sys->psz_font_name = var_CreateGetString( p_this, "quartztext-font" );
185 p_sys->i_font_opacity = 255;
186 p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
187 p_sys->i_font_size = GetFontSize( p_filter );
189 p_filter->pf_render_text = RenderText;
190 p_filter->pf_render_html = RenderHtml;
192 p_sys->p_fonts = NULL;
195 LoadFontsFromAttachments( p_filter );
200 //////////////////////////////////////////////////////////////////////////////
201 // Destroy: destroy Clone video thread output method
202 //////////////////////////////////////////////////////////////////////////////
203 // Clean up all data and library connections
204 //////////////////////////////////////////////////////////////////////////////
205 static void Destroy( vlc_object_t *p_this )
207 filter_t *p_filter = (filter_t *)p_this;
208 filter_sys_t *p_sys = p_filter->p_sys;
214 for( k = 0; k < p_sys->i_fonts; k++ )
216 ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
219 free( p_sys->p_fonts );
222 free( p_sys->psz_font_name );
226 //////////////////////////////////////////////////////////////////////////////
227 // Make any TTF/OTF fonts present in the attachments of the media file
228 // available to the Quartz engine for text rendering
229 //////////////////////////////////////////////////////////////////////////////
230 static int LoadFontsFromAttachments( filter_t *p_filter )
232 filter_sys_t *p_sys = p_filter->p_sys;
233 input_thread_t *p_input;
234 input_attachment_t **pp_attachments;
235 int i_attachments_cnt;
237 int rv = VLC_SUCCESS;
239 p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
243 if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
245 vlc_object_release(p_input);
250 p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
251 if(! p_sys->p_fonts )
254 for( k = 0; k < i_attachments_cnt; k++ )
256 input_attachment_t *p_attach = pp_attachments[k];
260 if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
261 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
262 ( p_attach->i_data > 0 ) &&
263 ( p_attach->p_data != NULL ) )
265 ATSFontContainerRef container;
267 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
269 kATSFontContextLocal,
270 kATSFontFormatUnspecified,
272 kATSOptionFlagsDefault,
275 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
279 vlc_input_attachment_Delete( p_attach );
281 free( pp_attachments );
283 vlc_object_release(p_input);
288 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
289 // Original version of these functions available on:
290 // http://developer.apple.com/documentation/Carbon/Conceptual/QuickDrawToQuartz2D/tq_color/chapter_4_section_3.html
292 #define kGenericRGBProfilePathStr "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc"
294 static CMProfileRef OpenGenericProfile( void )
296 static CMProfileRef cached_rgb_prof = NULL;
298 // Create the profile reference only once
299 if( cached_rgb_prof == NULL )
302 CMProfileLocation loc;
304 loc.locType = cmPathBasedProfile;
305 strcpy( loc.u.pathLoc.path, kGenericRGBProfilePathStr );
307 err = CMOpenProfile( &cached_rgb_prof, &loc );
311 cached_rgb_prof = NULL;
315 if( cached_rgb_prof )
317 // Clone the profile reference so that the caller has
318 // their own reference, not our cached one.
319 CMCloneProfileRef( cached_rgb_prof );
322 return cached_rgb_prof;
325 static CGColorSpaceRef CreateGenericRGBColorSpace( void )
327 static CGColorSpaceRef p_generic_rgb_cs = NULL;
329 if( p_generic_rgb_cs == NULL )
331 CMProfileRef generic_rgb_prof = OpenGenericProfile();
333 if( generic_rgb_prof )
335 p_generic_rgb_cs = CGColorSpaceCreateWithPlatformColorSpace( generic_rgb_prof );
337 CMCloseProfile( generic_rgb_prof );
341 return p_generic_rgb_cs;
345 static char *EliminateCRLF( char *psz_string )
350 for( p = psz_string; p && *p; p++ )
352 if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
354 for( q = p + 1; *q; q++ )
363 // Convert UTF-8 string to UTF-16 character array -- internal Mac Endian-ness ;
364 // we don't need to worry about bidirectional text conversion as ATSUI should
365 // handle that for us automatically
366 static void ConvertToUTF16( const char *psz_utf8_str, uint32_t *pi_strlen, UniChar **ppsz_utf16_str )
368 CFStringRef p_cfString;
371 p_cfString = CFStringCreateWithCString( NULL, psz_utf8_str, kCFStringEncodingUTF8 );
375 i_string_length = CFStringGetLength( p_cfString );
378 *pi_strlen = i_string_length;
380 if( !*ppsz_utf16_str )
381 *ppsz_utf16_str = (UniChar *) calloc( i_string_length, sizeof( UniChar ) );
383 CFStringGetCharacters( p_cfString, CFRangeMake( 0, i_string_length ), *ppsz_utf16_str );
385 CFRelease( p_cfString );
388 // Renders a text subpicture region into another one.
389 // It is used as pf_add_string callback in the vout method by this module
390 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
391 subpicture_region_t *p_region_in )
393 filter_sys_t *p_sys = p_filter->p_sys;
394 UniChar *psz_utf16_str = NULL;
395 uint32_t i_string_length;
397 int i_font_color, i_font_alpha, i_font_size;
401 p_sys->i_font_size = GetFontSize( p_filter );
404 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
405 psz_string = p_region_in->psz_text;
406 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
408 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
411 if( p_region_in->p_style )
413 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
414 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
415 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
419 i_font_color = p_sys->i_font_color;
420 i_font_alpha = 255 - p_sys->i_font_opacity;
421 i_font_size = p_sys->i_font_size;
424 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
426 ConvertToUTF16( EliminateCRLF( psz_string ), &i_string_length, &psz_utf16_str );
428 p_region_out->i_x = p_region_in->i_x;
429 p_region_out->i_y = p_region_in->i_y;
431 if( psz_utf16_str != NULL )
433 ATSUStyle p_style = CreateStyle( p_sys->psz_font_name, i_font_size,
434 (i_font_color & 0xffffff) |
435 ((i_font_alpha & 0xff) << 24),
436 false, false, false );
439 RenderYUVA( p_filter, p_region_out, psz_utf16_str, i_string_length,
440 1, &i_string_length, &p_style );
443 ATSUDisposeStyle( p_style );
444 free( psz_utf16_str );
451 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size, uint32_t i_font_color,
452 bool b_bold, bool b_italic, bool b_uline )
458 float f_red = (float)(( i_font_color & 0x00FF0000 ) >> 16) / 255.0;
459 float f_green = (float)(( i_font_color & 0x0000FF00 ) >> 8) / 255.0;
460 float f_blue = (float)( i_font_color & 0x000000FF ) / 255.0;
461 float f_alpha = ( 255.0 - (float)(( i_font_color & 0xFF000000 ) >> 24)) / 255.0;
464 Fixed font_size = IntToFixed( i_font_size );
465 ATSURGBAlphaColor font_color = { f_red, f_green, f_blue, f_alpha };
466 Boolean bold = b_bold;
467 Boolean italic = b_italic;
468 Boolean uline = b_uline;
470 ATSUAttributeTag tags[] = { kATSUSizeTag, kATSURGBAlphaColorTag, kATSUQDItalicTag,
471 kATSUQDBoldfaceTag, kATSUQDUnderlineTag, kATSUFontTag };
472 ByteCount sizes[] = { sizeof( Fixed ), sizeof( ATSURGBAlphaColor ), sizeof( Boolean ),
473 sizeof( Boolean ), sizeof( Boolean ), sizeof( ATSUFontID )};
474 ATSUAttributeValuePtr values[] = { &font_size, &font_color, &italic, &bold, &uline, &font };
476 i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
478 status = ATSUFindFontFromName( psz_fontname,
479 strlen( psz_fontname ),
486 if( status != noErr )
488 // If we can't find a suitable font, just do everything else
492 if( noErr == ATSUCreateStyle( &p_style ) )
494 if( noErr == ATSUSetAttributes( p_style, i_tag_cnt, tags, sizes, values ) )
498 ATSUDisposeStyle( p_style );
503 static ATSUStyle GetStyleFromFontStack( filter_sys_t *p_sys,
504 font_stack_t **p_fonts, bool b_bold, bool b_italic,
507 ATSUStyle p_style = NULL;
509 char *psz_fontname = NULL;
510 uint32_t i_font_color = p_sys->i_font_color;
511 uint32_t i_karaoke_bg_color = i_font_color; /* Use it */
512 int i_font_size = p_sys->i_font_size;
514 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
515 &i_font_color, &i_karaoke_bg_color ))
517 p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
518 b_bold, b_italic, b_uline );
523 static void SetupLine( filter_t *p_filter, const char *psz_text_in,
524 UniChar **psz_text_out, uint32_t *pi_runs,
525 uint32_t **ppi_run_lengths, ATSUStyle **ppp_styles,
528 uint32_t i_string_length = 0;
530 ConvertToUTF16( psz_text_in, &i_string_length, psz_text_out );
531 *psz_text_out += i_string_length;
533 if( ppp_styles && ppi_run_lengths )
538 *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
540 *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
542 (*ppp_styles)[ *pi_runs - 1 ] = p_style;
544 if( *ppi_run_lengths )
545 *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
547 *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
549 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
553 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
554 subpicture_region_t *p_region_in )
556 int rv = VLC_SUCCESS;
557 stream_t *p_sub = NULL;
559 xml_reader_t *p_xml_reader = NULL;
561 if( !p_region_in || !p_region_in->psz_html )
564 /* Reset the default fontsize in case screen metrics have changed */
565 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
567 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
568 (uint8_t *) p_region_in->psz_html,
569 strlen( p_region_in->psz_html ),
573 p_xml = xml_Create( p_filter );
576 bool b_karaoke = false;
578 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
581 /* Look for Root Node */
582 if( xml_ReaderRead( p_xml_reader ) == 1 )
584 char *psz_node = xml_ReaderName( p_xml_reader );
586 if( !strcasecmp( "karaoke", psz_node ) )
588 /* We're going to have to render the text a number
589 * of times to show the progress marker on the text.
591 var_SetBool( p_filter, "text-rerender", true );
594 else if( !strcasecmp( "text", psz_node ) )
600 /* Only text and karaoke tags are supported */
601 xml_ReaderDelete( p_xml, p_xml_reader );
615 uint32_t i_k_runs = 0;
616 uint32_t *pi_run_lengths = NULL;
617 uint32_t *pi_k_run_lengths = NULL;
618 uint32_t *pi_k_durations = NULL;
619 ATSUStyle *pp_styles = NULL;
621 psz_text = (UniChar *) malloc( strlen( p_region_in->psz_html ) *
627 rv = ProcessNodes( p_filter, p_xml_reader,
628 p_region_in->p_style, psz_text, &i_len,
629 &i_runs, &pi_run_lengths, &pp_styles,
630 /* No karaoke support */
631 false, &i_k_runs, &pi_k_run_lengths, &pi_k_durations );
633 assert( pi_k_run_lengths == NULL && pi_k_durations == NULL );
635 p_region_out->i_x = p_region_in->i_x;
636 p_region_out->i_y = p_region_in->i_y;
638 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
640 RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs,
641 pi_run_lengths, pp_styles);
644 for( k=0; k<i_runs; k++)
645 ATSUDisposeStyle( pp_styles[k] );
647 free( pi_run_lengths );
651 xml_ReaderDelete( p_xml, p_xml_reader );
655 stream_Delete( p_sub );
661 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
662 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
664 offscreen_bitmap_t *p_bitmap;
665 CGContextRef p_context = NULL;
667 p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
670 p_bitmap->i_bitsPerChannel = 8;
671 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
672 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
673 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
675 p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
677 *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
679 if( p_bitmap->p_data && *pp_colorSpace )
681 p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
682 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
683 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
687 if( CGContextSetAllowsAntialiasing != NULL )
689 CGContextSetAllowsAntialiasing( p_context, true );
692 *pp_memory = p_bitmap;
698 static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len,
699 uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles,
700 int i_width, int i_height, int *pi_textblock_height )
702 offscreen_bitmap_t *p_offScreen = NULL;
703 CGColorSpaceRef p_colorSpace = NULL;
704 CGContextRef p_context = NULL;
706 p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
710 ATSUTextLayout p_textLayout;
711 OSStatus status = noErr;
713 status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
715 (const UniCharCount *) pi_run_lengths,
718 if( status == noErr )
720 // Attach our offscreen Image Graphics Context to the text style
721 // and setup the line alignment (have to specify the line width
722 // also in order for our chosen alignment to work)
724 Fract alignment = kATSUStartAlignment;
725 Fixed line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
727 ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
728 ByteCount sizes[] = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
729 ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
731 int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
733 if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
735 alignment = kATSUEndAlignment;
737 else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
739 alignment = kATSUCenterAlignment;
742 ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
744 // let ATSUI deal with characters not-in-our-specified-font
745 ATSUSetTransientFontMatching( p_textLayout, true );
747 Fixed x = Long2Fix( HORIZONTAL_MARGIN );
748 Fixed y = Long2Fix( i_height );
750 // Set the line-breaks and draw individual lines
751 uint32_t i_start = 0;
752 uint32_t i_end = i_text_len;
754 // Set up black outlining of the text --
755 CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
756 CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
757 CGContextSetShadow( p_context, CGSizeMake( 0, 0 ), 5 );
758 float black_components[4] = {0, 0, 0, 1};
759 CGColorRef outlinecolor = CGColorCreate( CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB ), black_components );
760 CGContextSetShadowWithColor (p_context, CGSizeMake( 0, 0 ), 5, outlinecolor);
761 CGColorRelease( outlinecolor );
764 // ATSUBreakLine will automatically pick up any manual '\n's also
765 status = ATSUBreakLine( p_textLayout, i_start, line_width, true, (UniCharArrayOffset *) &i_end );
766 if( ( status == noErr ) || ( status == kATSULineBreakInWord ) )
770 uint32_t i_actualSize;
772 // Come down far enough to fit the height of this line --
773 ATSUGetLineControl( p_textLayout, i_start, kATSULineAscentTag,
774 sizeof( Fixed ), &ascent, (ByteCount *) &i_actualSize );
776 // Quartz uses an upside-down co-ordinate space -> y values decrease as
777 // you move down the page
780 // Set the outlining for this line to be dependent on the size of the line -
781 // make it about 5% of the ascent, with a minimum at 1.0
782 float f_thickness = FixedToFloat( ascent ) * 0.05;
783 CGContextSetLineWidth( p_context, (( f_thickness < 1.0 ) ? 1.0 : f_thickness ));
784 ATSUDrawText( p_textLayout, i_start, i_end - i_start, x, y );
786 // and now prepare for the next line by coming down far enough for our
788 ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag,
789 sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize );
797 while( i_end < i_text_len );
799 *pi_textblock_height = i_height - Fix2Long( y );
800 CGContextFlush( p_context );
802 ATSUDisposeTextLayout( p_textLayout );
805 CGContextRelease( p_context );
807 if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
812 static int GetFontSize( filter_t *p_filter )
814 return p_filter->fmt_out.video.i_height / __MAX(1, var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" ));
817 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniChar *psz_utf16_str,
818 uint32_t i_text_len, uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles )
820 offscreen_bitmap_t *p_offScreen = NULL;
821 int i_textblock_height = 0;
823 int i_width = p_filter->fmt_out.video.i_visible_width;
824 int i_height = p_filter->fmt_out.video.i_visible_height;
825 int i_text_align = p_region->i_align & 0x3;
829 msg_Err( p_filter, "Invalid argument to RenderYUVA" );
833 p_offScreen = Compose( i_text_align, psz_utf16_str, i_text_len,
834 i_runs, pi_run_lengths, pp_styles,
835 i_width, i_height, &i_textblock_height );
839 msg_Err( p_filter, "No offscreen buffer" );
843 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
845 int x, y, i_offset, i_pitch;
846 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
848 // Create a new subpicture region
849 memset( &fmt, 0, sizeof(video_format_t) );
850 fmt.i_chroma = VLC_CODEC_YUVA;
852 fmt.i_width = fmt.i_visible_width = i_width;
853 fmt.i_height = fmt.i_visible_height = i_textblock_height + VERTICAL_MARGIN * 2;
854 fmt.i_x_offset = fmt.i_y_offset = 0;
856 p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect );
857 if( !p_region->p_picture )
861 p_dst_y = p_region->p_picture->Y_PIXELS;
862 p_dst_u = p_region->p_picture->U_PIXELS;
863 p_dst_v = p_region->p_picture->V_PIXELS;
864 p_dst_a = p_region->p_picture->A_PIXELS;
865 i_pitch = p_region->p_picture->A_PITCH;
867 i_offset = VERTICAL_MARGIN *i_pitch;
868 for( y=0; y<i_textblock_height; y++)
870 for( x=0; x<i_width; x++)
872 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
873 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
874 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
875 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
877 i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
878 802 * i_blue + 4096 + 131072 ) >> 13, 235);
879 i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
880 3598 * i_blue + 4096 + 1048576) >> 13, 240);
881 i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
882 -585 * i_blue + 4096 + 1048576) >> 13, 240);
884 p_dst_y[ i_offset + x ] = i_y;
885 p_dst_u[ i_offset + x ] = i_u;
886 p_dst_v[ i_offset + x ] = i_v;
887 p_dst_a[ i_offset + x ] = i_alpha;
892 free( p_offScreen->p_data );