]> git.sesse.net Git - vlc/blob - modules/misc/quartztext.c
mac text renderer: Fix a nasty crash in the YUVA renderer
[vlc] / modules / misc / quartztext.c
1 /*****************************************************************************
2  * quartztext.c : Put text on the video, using Mac OS X Quartz Engine
3  *****************************************************************************
4  * Copyright (C) 2007, 2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Bernie Purcell <bitmap@videolan.org>
8  *
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.
13  *
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.
18  *
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  *****************************************************************************/
23
24 //////////////////////////////////////////////////////////////////////////////
25 // Preamble
26 //////////////////////////////////////////////////////////////////////////////
27
28 #ifdef __x86_64__
29
30 #warning "No text renderer build! Quartztext isn't 64bit compatible!"
31 #warning "RE-WRITE ME!"
32
33 #else
34
35 #ifdef HAVE_CONFIG_H
36 # include "config.h"
37 #endif
38
39 #include <vlc_common.h>
40 #include <vlc_plugin.h>
41 #include <vlc_vout.h>
42 #include <vlc_osd.h>
43 #include <vlc_block.h>
44 #include <vlc_filter.h>
45 #include <vlc_stream.h>
46 #include <vlc_xml.h>
47 #include <vlc_input.h>
48 #include <vlc_strings.h>
49
50 #include <math.h>
51
52 #include <Carbon/Carbon.h>
53
54 #define DEFAULT_FONT           "Arial Black"
55 #define DEFAULT_FONT_COLOR     0xffffff
56 #define DEFAULT_REL_FONT_SIZE  16
57
58 #define VERTICAL_MARGIN 3
59 #define HORIZONTAL_MARGIN 10
60
61 //////////////////////////////////////////////////////////////////////////////
62 // Local prototypes
63 //////////////////////////////////////////////////////////////////////////////
64 static int  Create ( vlc_object_t * );
65 static void Destroy( vlc_object_t * );
66
67 static int LoadFontsFromAttachments( filter_t *p_filter );
68
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 * );
73
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,
82                               bool b_uline );
83 //////////////////////////////////////////////////////////////////////////////
84 // Module descriptor
85 //////////////////////////////////////////////////////////////////////////////
86
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
91 // absent.
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" )
103
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 };
108
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") };
113
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") };
117
118 vlc_module_begin ()
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 )
123
124     add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
125               false )
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 )
135 vlc_module_end ()
136
137 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
138 struct offscreen_bitmap_t
139 {
140     uint8_t       *p_data;
141     int            i_bitsPerChannel;
142     int            i_bitsPerPixel;
143     int            i_bytesPerPixel;
144     int            i_bytesPerRow;
145 };
146
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 //////////////////////////////////////////////////////////////////////////////
153 struct filter_sys_t
154 {
155     char          *psz_font_name;
156     uint8_t        i_font_opacity;
157     int            i_font_color;
158     int            i_font_size;
159
160     ATSFontContainerRef    *p_fonts;
161     int                     i_fonts;
162 };
163
164 #define UCHAR UniChar
165 #define TR_DEFAULT_FONT p_sys->psz_font_name
166 #define TR_FONT_STYLE_PTR ATSUStyle
167
168 #include "text_renderer.h"
169
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 )
176 {
177     filter_t *p_filter = (filter_t *)p_this;
178     filter_sys_t *p_sys;
179
180     // Allocate structure
181     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
182     if( !p_sys )
183         return VLC_ENOMEM;
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 );
188
189     p_filter->pf_render_text = RenderText;
190     p_filter->pf_render_html = RenderHtml;
191
192     p_sys->p_fonts = NULL;
193     p_sys->i_fonts = 0;
194
195     LoadFontsFromAttachments( p_filter );
196
197     return VLC_SUCCESS;
198 }
199
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 )
206 {
207     filter_t *p_filter = (filter_t *)p_this;
208     filter_sys_t *p_sys = p_filter->p_sys;
209
210     if( p_sys->p_fonts )
211     {
212         int   k;
213
214         for( k = 0; k < p_sys->i_fonts; k++ )
215         {
216             ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
217         }
218
219         free( p_sys->p_fonts );
220     }
221
222     free( p_sys->psz_font_name );
223     free( p_sys );
224 }
225
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 )
231 {
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;
236     int                   k;
237     int                   rv = VLC_SUCCESS;
238
239     p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
240     if( ! p_input )
241         return VLC_EGENERIC;
242
243     if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
244     {
245         vlc_object_release(p_input);
246         return VLC_EGENERIC;
247     }
248
249     p_sys->i_fonts = 0;
250     p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
251     if(! p_sys->p_fonts )
252         rv = VLC_ENOMEM;
253
254     for( k = 0; k < i_attachments_cnt; k++ )
255     {
256         input_attachment_t *p_attach = pp_attachments[k];
257
258         if( p_sys->p_fonts )
259         {
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 ) )
264             {
265                 ATSFontContainerRef  container;
266
267                 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
268                                                         p_attach->i_data,
269                                                         kATSFontContextLocal,
270                                                         kATSFontFormatUnspecified,
271                                                         NULL,
272                                                         kATSOptionFlagsDefault,
273                                                         &container ))
274                 {
275                     p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
276                 }
277             }
278         }
279         vlc_input_attachment_Delete( p_attach );
280     }
281     free( pp_attachments );
282
283     vlc_object_release(p_input);
284
285     return rv;
286 }
287
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
291
292 #define kGenericRGBProfilePathStr "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc"
293
294 static CMProfileRef OpenGenericProfile( void )
295 {
296     static CMProfileRef cached_rgb_prof = NULL;
297
298     // Create the profile reference only once
299     if( cached_rgb_prof == NULL )
300     {
301         OSStatus            err;
302         CMProfileLocation   loc;
303
304         loc.locType = cmPathBasedProfile;
305         strcpy( loc.u.pathLoc.path, kGenericRGBProfilePathStr );
306
307         err = CMOpenProfile( &cached_rgb_prof, &loc );
308
309         if( err != noErr )
310         {
311             cached_rgb_prof = NULL;
312         }
313     }
314
315     if( cached_rgb_prof )
316     {
317         // Clone the profile reference so that the caller has
318         // their own reference, not our cached one.
319         CMCloneProfileRef( cached_rgb_prof );
320     }
321
322     return cached_rgb_prof;
323 }
324
325 static CGColorSpaceRef CreateGenericRGBColorSpace( void )
326 {
327     static CGColorSpaceRef p_generic_rgb_cs = NULL;
328
329     if( p_generic_rgb_cs == NULL )
330     {
331         CMProfileRef generic_rgb_prof = OpenGenericProfile();
332
333         if( generic_rgb_prof )
334         {
335             p_generic_rgb_cs = CGColorSpaceCreateWithPlatformColorSpace( generic_rgb_prof );
336
337             CMCloseProfile( generic_rgb_prof );
338         }
339     }
340
341     return p_generic_rgb_cs;
342 }
343 #endif
344
345 static char *EliminateCRLF( char *psz_string )
346 {
347     char *p;
348     char *q;
349
350     for( p = psz_string; p && *p; p++ )
351     {
352         if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
353         {
354             for( q = p + 1; *q; q++ )
355                 *( q - 1 ) = *q;
356
357             *( q - 1 ) = '\0';
358         }
359     }
360     return psz_string;
361 }
362
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 )
367 {
368     CFStringRef   p_cfString;
369     int           i_string_length;
370
371     p_cfString = CFStringCreateWithCString( NULL, psz_utf8_str, kCFStringEncodingUTF8 );
372     if( !p_cfString )
373         return;
374
375     i_string_length = CFStringGetLength( p_cfString );
376
377     if( pi_strlen )
378         *pi_strlen = i_string_length;
379
380     if( !*ppsz_utf16_str )
381         *ppsz_utf16_str = (UniChar *) calloc( i_string_length, sizeof( UniChar ) );
382
383     CFStringGetCharacters( p_cfString, CFRangeMake( 0, i_string_length ), *ppsz_utf16_str );
384
385     CFRelease( p_cfString );
386 }
387
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 )
392 {
393     filter_sys_t *p_sys = p_filter->p_sys;
394     UniChar      *psz_utf16_str = NULL;
395     uint32_t      i_string_length;
396     char         *psz_string;
397     int           i_font_color, i_font_alpha, i_font_size;
398     vlc_value_t val;
399     int i_scale = 1000;
400
401     p_sys->i_font_size    = GetFontSize( p_filter );
402
403     // Sanity check
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;
407
408     if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
409         i_scale = val.i_int;
410
411     if( p_region_in->p_style )
412     {
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;
416     }
417     else
418     {
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;
422     }
423
424     if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
425
426     ConvertToUTF16( EliminateCRLF( psz_string ), &i_string_length, &psz_utf16_str );
427
428     p_region_out->i_x = p_region_in->i_x;
429     p_region_out->i_y = p_region_in->i_y;
430
431     if( psz_utf16_str != NULL )
432     {
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 );
437         if( p_style )
438         {
439             RenderYUVA( p_filter, p_region_out, psz_utf16_str, i_string_length,
440                         1, &i_string_length, &p_style );
441         }
442
443         ATSUDisposeStyle( p_style );
444         free( psz_utf16_str );
445     }
446
447     return VLC_SUCCESS;
448 }
449
450
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 )
453 {
454     ATSUStyle   p_style;
455     OSStatus    status;
456     uint32_t    i_tag_cnt;
457
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;
462
463     ATSUFontID           font;
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;
469
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 };
475
476     i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
477
478     status = ATSUFindFontFromName( psz_fontname,
479                                    strlen( psz_fontname ),
480                                    kFontFullName,
481                                    kFontNoPlatform,
482                                    kFontNoScript,
483                                    kFontNoLanguageCode,
484                                    &font );
485
486     if( status != noErr )
487     {
488         // If we can't find a suitable font, just do everything else
489         i_tag_cnt--;
490     }
491
492     if( noErr == ATSUCreateStyle( &p_style ) )
493     {
494         if( noErr == ATSUSetAttributes( p_style, i_tag_cnt, tags, sizes, values ) )
495         {
496             return p_style;
497         }
498         ATSUDisposeStyle( p_style );
499     }
500     return NULL;
501 }
502
503 static ATSUStyle GetStyleFromFontStack( filter_sys_t *p_sys,
504         font_stack_t **p_fonts, bool b_bold, bool b_italic,
505         bool b_uline )
506 {
507     ATSUStyle   p_style = NULL;
508
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;
513
514     if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
515                                  &i_font_color, &i_karaoke_bg_color ))
516     {
517         p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
518                                b_bold, b_italic, b_uline );
519     }
520     return p_style;
521 }
522
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,
526                        ATSUStyle p_style )
527 {
528     uint32_t i_string_length = 0;
529
530     ConvertToUTF16( psz_text_in, &i_string_length, psz_text_out );
531     *psz_text_out += i_string_length;
532
533     if( ppp_styles && ppi_run_lengths )
534     {
535         (*pi_runs)++;
536
537         if( *ppp_styles )
538             *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
539         else
540             *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
541
542         (*ppp_styles)[ *pi_runs - 1 ] = p_style;
543
544         if( *ppi_run_lengths )
545             *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
546         else
547             *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
548
549         (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
550     }
551 }
552
553 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
554                        subpicture_region_t *p_region_in )
555 {
556     int          rv = VLC_SUCCESS;
557     stream_t     *p_sub = NULL;
558     xml_t        *p_xml = NULL;
559     xml_reader_t *p_xml_reader = NULL;
560
561     if( !p_region_in || !p_region_in->psz_html )
562         return VLC_EGENERIC;
563
564     /* Reset the default fontsize in case screen metrics have changed */
565     p_filter->p_sys->i_font_size = GetFontSize( p_filter );
566
567     p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
568                               (uint8_t *) p_region_in->psz_html,
569                               strlen( p_region_in->psz_html ),
570                               true );
571     if( p_sub )
572     {
573         p_xml = xml_Create( p_filter );
574         if( p_xml )
575         {
576             bool b_karaoke = false;
577
578             p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
579             if( p_xml_reader )
580             {
581                 /* Look for Root Node */
582                 if( xml_ReaderRead( p_xml_reader ) == 1 )
583                 {
584                     char *psz_node = xml_ReaderName( p_xml_reader );
585
586                     if( !strcasecmp( "karaoke", psz_node ) )
587                     {
588                         /* We're going to have to render the text a number
589                          * of times to show the progress marker on the text.
590                          */
591                         var_SetBool( p_filter, "text-rerender", true );
592                         b_karaoke = true;
593                     }
594                     else if( !strcasecmp( "text", psz_node ) )
595                     {
596                         b_karaoke = false;
597                     }
598                     else
599                     {
600                         /* Only text and karaoke tags are supported */
601                         xml_ReaderDelete( p_xml, p_xml_reader );
602                         p_xml_reader = NULL;
603                         rv = VLC_EGENERIC;
604                     }
605
606                     free( psz_node );
607                 }
608             }
609
610             if( p_xml_reader )
611             {
612                 UniChar    *psz_text;
613                 int         i_len = 0;
614                 uint32_t    i_runs = 0;
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;
620
621                 psz_text = (UniChar *) malloc( strlen( p_region_in->psz_html ) *
622                                                 sizeof( UniChar ) );
623                 if( psz_text )
624                 {
625                     uint32_t k;
626
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 );
632
633                     assert( pi_k_run_lengths == NULL && pi_k_durations == NULL );
634
635                     p_region_out->i_x = p_region_in->i_x;
636                     p_region_out->i_y = p_region_in->i_y;
637
638                     if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
639                     {
640                         RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs,
641                              pi_run_lengths, pp_styles);
642                     }
643
644                     for( k=0; k<i_runs; k++)
645                         ATSUDisposeStyle( pp_styles[k] );
646                     free( pp_styles );
647                     free( pi_run_lengths );
648                     free( psz_text );
649                 }
650
651                 xml_ReaderDelete( p_xml, p_xml_reader );
652             }
653             xml_Delete( p_xml );
654         }
655         stream_Delete( p_sub );
656     }
657
658     return rv;
659 }
660
661 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
662                          offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
663 {
664     offscreen_bitmap_t *p_bitmap;
665     CGContextRef        p_context = NULL;
666
667     p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
668     if( p_bitmap )
669     {
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;
674
675         p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
676
677         *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
678
679         if( p_bitmap->p_data && *pp_colorSpace )
680         {
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);
684         }
685         if( p_context )
686         {
687             if( CGContextSetAllowsAntialiasing != NULL )
688             {
689                 CGContextSetAllowsAntialiasing( p_context, true );
690             }
691         }
692         *pp_memory = p_bitmap;
693     }
694
695     return p_context;
696 }
697
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 )
701 {
702     offscreen_bitmap_t  *p_offScreen  = NULL;
703     CGColorSpaceRef      p_colorSpace = NULL;
704     CGContextRef         p_context = NULL;
705
706     p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
707
708     if( p_context )
709     {
710         ATSUTextLayout p_textLayout;
711         OSStatus status = noErr;
712
713         status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
714                                                   i_runs,
715                                                   (const UniCharCount *) pi_run_lengths,
716                                                   pp_styles,
717                                                   &p_textLayout );
718         if( status == noErr )
719         {
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)
723
724             Fract   alignment  = kATSUStartAlignment;
725             Fixed   line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
726
727             ATSUAttributeTag tags[]        = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
728             ByteCount sizes[]              = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
729             ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
730
731             int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
732
733             if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
734             {
735                 alignment = kATSUEndAlignment;
736             }
737             else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
738             {
739                 alignment = kATSUCenterAlignment;
740             }
741
742             ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
743
744             // let ATSUI deal with characters not-in-our-specified-font
745             ATSUSetTransientFontMatching( p_textLayout, true );
746
747             Fixed x = Long2Fix( HORIZONTAL_MARGIN );
748             Fixed y = Long2Fix( i_height );
749
750             // Set the line-breaks and draw individual lines
751             uint32_t i_start = 0;
752             uint32_t i_end = i_text_len;
753
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 );
762             do
763             {
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 ) )
767                 {
768                     Fixed     ascent;
769                     Fixed     descent;
770                     uint32_t  i_actualSize;
771
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 );
775
776                     // Quartz uses an upside-down co-ordinate space -> y values decrease as
777                     // you move down the page
778                     y -= ascent;
779
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 );
785
786                     // and now prepare for the next line by coming down far enough for our
787                     // descent
788                     ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag,
789                                     sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize );
790                     y -= descent;
791
792                     i_start = i_end;
793                 }
794                 else
795                     break;
796             }
797             while( i_end < i_text_len );
798
799             *pi_textblock_height = i_height - Fix2Long( y );
800             CGContextFlush( p_context );
801
802             ATSUDisposeTextLayout( p_textLayout );
803         }
804
805         CGContextRelease( p_context );
806     }
807     if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
808
809     return p_offScreen;
810 }
811
812 static int GetFontSize( filter_t *p_filter )
813 {
814     return p_filter->fmt_out.video.i_height / __MAX(1, var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" ));
815 }
816
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 )
819 {
820     offscreen_bitmap_t *p_offScreen = NULL;
821     int      i_textblock_height = 0;
822
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;
826
827     if( !psz_utf16_str )
828     {
829         msg_Err( p_filter, "Invalid argument to RenderYUVA" );
830         return VLC_EGENERIC;
831     }
832
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 );
836
837     if( !p_offScreen )
838     {
839         msg_Err( p_filter, "No offscreen buffer" );
840         return VLC_EGENERIC;
841     }
842
843     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
844     video_format_t fmt;
845     int x, y, i_offset, i_pitch;
846     uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
847
848     // Create a new subpicture region
849     memset( &fmt, 0, sizeof(video_format_t) );
850     fmt.i_chroma = VLC_CODEC_YUVA;
851     fmt.i_aspect = 0;
852     fmt.i_width = fmt.i_visible_width = i_width;
853     fmt.i_height = fmt.i_visible_height = __MIN( i_height, i_textblock_height + VERTICAL_MARGIN * 2);
854     fmt.i_x_offset = fmt.i_y_offset = 0;
855
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 )
858         return VLC_EGENERIC;
859     p_region->fmt = fmt;
860
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;
866
867     i_offset = (i_height+VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
868     for( y=0; y<fmt.i_height; y++)
869     {
870         for( x=0; x<fmt.i_width; x++)
871         {
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 ];
876
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);
883
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;
888         }
889         i_offset += i_pitch;
890     }
891
892     free( p_offScreen->p_data );
893     free( p_offScreen );
894
895     return VLC_SUCCESS;
896 }
897
898 #endif