]> git.sesse.net Git - vlc/blob - modules/misc/quartztext.c
Use resolve_xml_special_chars where appropriate.
[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 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 HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_vout.h>
35 #include <vlc_osd.h>
36 #include <vlc_block.h>
37 #include <vlc_filter.h>
38 #include <vlc_stream.h>
39 #include <vlc_xml.h>
40 #include <vlc_input.h>
41 #include <vlc_strings.h>
42
43 #include <math.h>
44
45 #include <Carbon/Carbon.h>
46
47 #define DEFAULT_FONT           "Arial Black"
48 #define DEFAULT_FONT_COLOR     0xffffff
49 #define DEFAULT_REL_FONT_SIZE  16
50
51 #define VERTICAL_MARGIN 3
52 #define HORIZONTAL_MARGIN 10
53
54 //////////////////////////////////////////////////////////////////////////////
55 // Local prototypes
56 //////////////////////////////////////////////////////////////////////////////
57 static int  Create ( vlc_object_t * );
58 static void Destroy( vlc_object_t * );
59
60 static int LoadFontsFromAttachments( filter_t *p_filter );
61
62 static int RenderText( filter_t *, subpicture_region_t *,
63                        subpicture_region_t * );
64 static int RenderHtml( filter_t *, subpicture_region_t *,
65                        subpicture_region_t * );
66
67 static int GetFontSize( filter_t *p_filter );
68 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
69                        UniChar *psz_utfString, uint32_t i_text_len,
70                        uint32_t i_runs, uint32_t *pi_run_lengths,
71                        ATSUStyle *pp_styles );
72 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size,
73                               uint32_t i_font_color,
74                               bool b_bold, bool b_italic,
75                               bool b_uline );
76 //////////////////////////////////////////////////////////////////////////////
77 // Module descriptor
78 //////////////////////////////////////////////////////////////////////////////
79
80 // The preferred way to set font style information is for it to come from the
81 // subtitle file, and for it to be rendered with RenderHtml instead of
82 // RenderText. This module, unlike Freetype, doesn't provide any options to
83 // override the fallback font selection used when this style information is
84 // absent.
85 #define FONT_TEXT N_("Font")
86 #define FONT_LONGTEXT N_("Name for the font you want to use")
87 #define FONTSIZER_TEXT N_("Relative font size")
88 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
89     "fonts that will be rendered on the video. If absolute font size is set, "\
90     "relative size will be overriden." )
91 #define COLOR_TEXT N_("Text default color")
92 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
93     "the video. This must be an hexadecimal (like HTML colors). The first two "\
94     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
95     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
96
97 static const int pi_color_values[] = {
98   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
99   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
100   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
101
102 static const char *const ppsz_color_descriptions[] = {
103   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
104   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
105   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
106
107 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
108 static const char *const ppsz_sizes_text[] = {
109     N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
110
111 vlc_module_begin();
112     set_shortname( N_("Mac Text renderer"));
113     set_description( N_("Quartz font renderer") );
114     set_category( CAT_VIDEO );
115     set_subcategory( SUBCAT_VIDEO_SUBPIC );
116
117     add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
118               false );
119     add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT,
120                  FONTSIZER_LONGTEXT, false );
121         change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
122     add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
123                  COLOR_LONGTEXT, false );
124         change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
125     set_capability( "text renderer", 150 );
126     add_shortcut( "text" );
127     set_callbacks( Create, Destroy );
128 vlc_module_end();
129
130 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
131 struct offscreen_bitmap_t
132 {
133     uint8_t       *p_data;
134     int            i_bitsPerChannel;
135     int            i_bitsPerPixel;
136     int            i_bytesPerPixel;
137     int            i_bytesPerRow;
138 };
139
140 //////////////////////////////////////////////////////////////////////////////
141 // filter_sys_t: quartztext local data
142 //////////////////////////////////////////////////////////////////////////////
143 // This structure is part of the video output thread descriptor.
144 // It describes the freetype specific properties of an output thread.
145 //////////////////////////////////////////////////////////////////////////////
146 struct filter_sys_t
147 {
148     char          *psz_font_name;
149     uint8_t        i_font_opacity;
150     int            i_font_color;
151     int            i_font_size;
152
153     ATSFontContainerRef    *p_fonts;
154     int                     i_fonts;
155 };
156
157 #define UCHAR UniChar
158 #define TR_DEFAULT_FONT p_sys->psz_font_name
159 #define TR_DEFAULT_COLOR p_sys->i_font_color
160 #define TR_FONT_STYLE_PTR ATSUStyle
161
162 #include "text_renderer.h"
163
164 //////////////////////////////////////////////////////////////////////////////
165 // Create: allocates osd-text video thread output method
166 //////////////////////////////////////////////////////////////////////////////
167 // This function allocates and initializes a Clone vout method.
168 //////////////////////////////////////////////////////////////////////////////
169 static int Create( vlc_object_t *p_this )
170 {
171     filter_t *p_filter = (filter_t *)p_this;
172     filter_sys_t *p_sys;
173
174     // Allocate structure
175     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
176     if( !p_sys )
177         return VLC_ENOMEM;
178     p_sys->psz_font_name  = var_CreateGetString( p_this, "quartztext-font" );
179     p_sys->i_font_opacity = 255;
180     p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
181     p_sys->i_font_size    = GetFontSize( p_filter );
182
183     p_filter->pf_render_text = RenderText;
184     p_filter->pf_render_html = RenderHtml;
185
186     p_sys->p_fonts = NULL;
187     p_sys->i_fonts = 0;
188
189     LoadFontsFromAttachments( p_filter );
190
191     return VLC_SUCCESS;
192 }
193
194 //////////////////////////////////////////////////////////////////////////////
195 // Destroy: destroy Clone video thread output method
196 //////////////////////////////////////////////////////////////////////////////
197 // Clean up all data and library connections
198 //////////////////////////////////////////////////////////////////////////////
199 static void Destroy( vlc_object_t *p_this )
200 {
201     filter_t *p_filter = (filter_t *)p_this;
202     filter_sys_t *p_sys = p_filter->p_sys;
203
204     if( p_sys->p_fonts )
205     {
206         int   k;
207
208         for( k = 0; k < p_sys->i_fonts; k++ )
209         {
210             ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
211         }
212
213         free( p_sys->p_fonts );
214     }
215
216     free( p_sys->psz_font_name );
217     free( p_sys );
218 }
219
220 //////////////////////////////////////////////////////////////////////////////
221 // Make any TTF/OTF fonts present in the attachments of the media file
222 // available to the Quartz engine for text rendering
223 //////////////////////////////////////////////////////////////////////////////
224 static int LoadFontsFromAttachments( filter_t *p_filter )
225 {
226     filter_sys_t         *p_sys = p_filter->p_sys;
227     input_thread_t       *p_input;
228     input_attachment_t  **pp_attachments;
229     int                   i_attachments_cnt;
230     int                   k;
231     int                   rv = VLC_SUCCESS;
232
233     p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
234     if( ! p_input )
235         return VLC_EGENERIC;
236
237     if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
238     {
239         vlc_object_release(p_input);
240         return VLC_EGENERIC;
241     }
242
243     p_sys->i_fonts = 0;
244     p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
245     if(! p_sys->p_fonts )
246         rv = VLC_ENOMEM;
247
248     for( k = 0; k < i_attachments_cnt; k++ )
249     {
250         input_attachment_t *p_attach = pp_attachments[k];
251
252         if( p_sys->p_fonts )
253         {
254             if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
255                  !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) &&    // OTF
256                ( p_attach->i_data > 0 ) &&
257                ( p_attach->p_data != NULL ) )
258             {
259                 ATSFontContainerRef  container;
260
261                 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
262                                                         p_attach->i_data,
263                                                         kATSFontContextLocal,
264                                                         kATSFontFormatUnspecified,
265                                                         NULL,
266                                                         kATSOptionFlagsDefault,
267                                                         &container ))
268                 {
269                     p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
270                 }
271             }
272         }
273         vlc_input_attachment_Delete( p_attach );
274     }
275     free( pp_attachments );
276
277     vlc_object_release(p_input);
278
279     return rv;
280 }
281
282 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
283 // Original version of these functions available on:
284 // http://developer.apple.com/documentation/Carbon/Conceptual/QuickDrawToQuartz2D/tq_color/chapter_4_section_3.html
285
286 #define kGenericRGBProfilePathStr "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc"
287
288 static CMProfileRef OpenGenericProfile( void )
289 {
290     static CMProfileRef cached_rgb_prof = NULL;
291
292     // Create the profile reference only once
293     if( cached_rgb_prof == NULL )
294     {
295         OSStatus            err;
296         CMProfileLocation   loc;
297
298         loc.locType = cmPathBasedProfile;
299         strcpy( loc.u.pathLoc.path, kGenericRGBProfilePathStr );
300
301         err = CMOpenProfile( &cached_rgb_prof, &loc );
302
303         if( err != noErr )
304         {
305             cached_rgb_prof = NULL;
306         }
307     }
308
309     if( cached_rgb_prof )
310     {
311         // Clone the profile reference so that the caller has
312         // their own reference, not our cached one.
313         CMCloneProfileRef( cached_rgb_prof );
314     }
315
316     return cached_rgb_prof;
317 }
318
319 static CGColorSpaceRef CreateGenericRGBColorSpace( void )
320 {
321     static CGColorSpaceRef p_generic_rgb_cs = NULL;
322
323     if( p_generic_rgb_cs == NULL )
324     {
325         CMProfileRef generic_rgb_prof = OpenGenericProfile();
326
327         if( generic_rgb_prof )
328         {
329             p_generic_rgb_cs = CGColorSpaceCreateWithPlatformColorSpace( generic_rgb_prof );
330
331             CMCloseProfile( generic_rgb_prof );
332         }
333     }
334
335     return p_generic_rgb_cs;
336 }
337 #endif
338
339 static char *EliminateCRLF( char *psz_string )
340 {
341     char *p;
342     char *q;
343
344     for( p = psz_string; p && *p; p++ )
345     {
346         if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
347         {
348             for( q = p + 1; *q; q++ )
349                 *( q - 1 ) = *q;
350
351             *( q - 1 ) = '\0';
352         }
353     }
354     return psz_string;
355 }
356
357 // Convert UTF-8 string to UTF-16 character array -- internal Mac Endian-ness ;
358 // we don't need to worry about bidirectional text conversion as ATSUI should
359 // handle that for us automatically
360 static void ConvertToUTF16( const char *psz_utf8_str, uint32_t *pi_strlen, UniChar **ppsz_utf16_str )
361 {
362     CFStringRef   p_cfString;
363     int           i_string_length;
364
365     p_cfString = CFStringCreateWithCString( NULL, psz_utf8_str, kCFStringEncodingUTF8 );
366     if( !p_cfString )
367         return;
368
369     i_string_length = CFStringGetLength( p_cfString );
370
371     if( pi_strlen )
372         *pi_strlen = i_string_length;
373
374     if( !*ppsz_utf16_str )
375         *ppsz_utf16_str = (UniChar *) calloc( i_string_length, sizeof( UniChar ) );
376
377     CFStringGetCharacters( p_cfString, CFRangeMake( 0, i_string_length ), *ppsz_utf16_str );
378
379     CFRelease( p_cfString );
380 }
381
382 // Renders a text subpicture region into another one.
383 // It is used as pf_add_string callback in the vout method by this module
384 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
385                        subpicture_region_t *p_region_in )
386 {
387     filter_sys_t *p_sys = p_filter->p_sys;
388     UniChar      *psz_utf16_str = NULL;
389     uint32_t      i_string_length;
390     char         *psz_string;
391     int           i_font_color, i_font_alpha, i_font_size;
392     vlc_value_t val;
393     int i_scale = 1000;
394
395     p_sys->i_font_size    = GetFontSize( p_filter );
396
397     // Sanity check
398     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
399     psz_string = p_region_in->psz_text;
400     if( !psz_string || !*psz_string ) return VLC_EGENERIC;
401
402     if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
403         i_scale = val.i_int;
404
405     if( p_region_in->p_style )
406     {
407         i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
408         i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
409         i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
410     }
411     else
412     {
413         i_font_color = p_sys->i_font_color;
414         i_font_alpha = 255 - p_sys->i_font_opacity;
415         i_font_size  = p_sys->i_font_size;
416     }
417
418     if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
419
420     ConvertToUTF16( EliminateCRLF( psz_string ), &i_string_length, &psz_utf16_str );
421
422     p_region_out->i_x = p_region_in->i_x;
423     p_region_out->i_y = p_region_in->i_y;
424
425     if( psz_utf16_str != NULL )
426     {
427         ATSUStyle p_style = CreateStyle( p_sys->psz_font_name, i_font_size,
428                                          (i_font_color & 0xffffff) |
429                                          ((i_font_alpha & 0xff) << 24),
430                                          false, false, false );
431         if( p_style )
432         {
433             RenderYUVA( p_filter, p_region_out, psz_utf16_str, i_string_length,
434                         1, &i_string_length, &p_style );
435         }
436
437         ATSUDisposeStyle( p_style );
438         free( psz_utf16_str );
439     }
440
441     return VLC_SUCCESS;
442 }
443
444
445 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size, uint32_t i_font_color,
446                               bool b_bold, bool b_italic, bool b_uline )
447 {
448     ATSUStyle   p_style;
449     OSStatus    status;
450     uint32_t    i_tag_cnt;
451
452     float f_red   = (float)(( i_font_color & 0x00FF0000 ) >> 16) / 255.0;
453     float f_green = (float)(( i_font_color & 0x0000FF00 ) >>  8) / 255.0;
454     float f_blue  = (float)(  i_font_color & 0x000000FF        ) / 255.0;
455     float f_alpha = ( 255.0 - (float)(( i_font_color & 0xFF000000 ) >> 24)) / 255.0;
456
457     ATSUFontID           font;
458     Fixed                font_size  = IntToFixed( i_font_size );
459     ATSURGBAlphaColor    font_color = { f_red, f_green, f_blue, f_alpha };
460     Boolean              bold       = b_bold;
461     Boolean              italic     = b_italic;
462     Boolean              uline      = b_uline;
463
464     ATSUAttributeTag tags[]        = { kATSUSizeTag, kATSURGBAlphaColorTag, kATSUQDItalicTag,
465                                        kATSUQDBoldfaceTag, kATSUQDUnderlineTag, kATSUFontTag };
466     ByteCount sizes[]              = { sizeof( Fixed ), sizeof( ATSURGBAlphaColor ), sizeof( Boolean ),
467                                        sizeof( Boolean ), sizeof( Boolean ), sizeof( ATSUFontID )};
468     ATSUAttributeValuePtr values[] = { &font_size, &font_color, &italic, &bold, &uline, &font };
469
470     i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
471
472     status = ATSUFindFontFromName( psz_fontname,
473                                    strlen( psz_fontname ),
474                                    kFontFullName,
475                                    kFontNoPlatform,
476                                    kFontNoScript,
477                                    kFontNoLanguageCode,
478                                    &font );
479
480     if( status != noErr )
481     {
482         // If we can't find a suitable font, just do everything else
483         i_tag_cnt--;
484     }
485
486     if( noErr == ATSUCreateStyle( &p_style ) )
487     {
488         if( noErr == ATSUSetAttributes( p_style, i_tag_cnt, tags, sizes, values ) )
489         {
490             return p_style;
491         }
492         ATSUDisposeStyle( p_style );
493     }
494     return NULL;
495 }
496
497 static ATSUStyle GetStyleFromFontStack( filter_sys_t *p_sys,
498         font_stack_t **p_fonts, bool b_bold, bool b_italic,
499         bool b_uline )
500 {
501     ATSUStyle   p_style = NULL;
502
503     char     *psz_fontname = NULL;
504     uint32_t  i_font_color = p_sys->i_font_color;
505     uint32_t  i_karaoke_bg_color = i_font_color; /* Use it */
506     int       i_font_size  = p_sys->i_font_size;
507
508     if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
509                                  &i_font_color, &i_karaoke_bg_color ))
510     {
511         p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
512                                b_bold, b_italic, b_uline );
513     }
514     return p_style;
515 }
516
517 static void SetupLine( filter_t *p_filter, const char *psz_text_in,
518                        UniChar **psz_text_out, uint32_t *pi_runs,
519                        uint32_t **ppi_run_lengths, ATSUStyle **ppp_styles,
520                        ATSUStyle p_style )
521 {
522     uint32_t i_string_length = 0;
523
524     ConvertToUTF16( psz_text_in, &i_string_length, psz_text_out );
525     *psz_text_out += i_string_length;
526
527     if( ppp_styles && ppi_run_lengths )
528     {
529         (*pi_runs)++;
530
531         if( *ppp_styles )
532             *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
533         else
534             *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
535
536         (*ppp_styles)[ *pi_runs - 1 ] = p_style;
537
538         if( *ppi_run_lengths )
539             *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
540         else
541             *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
542
543         (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
544     }
545 }
546
547 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
548                        subpicture_region_t *p_region_in )
549 {
550     int          rv = VLC_SUCCESS;
551     stream_t     *p_sub = NULL;
552     xml_t        *p_xml = NULL;
553     xml_reader_t *p_xml_reader = NULL;
554
555     if( !p_region_in || !p_region_in->psz_html )
556         return VLC_EGENERIC;
557
558     /* Reset the default fontsize in case screen metrics have changed */
559     p_filter->p_sys->i_font_size = GetFontSize( p_filter );
560
561     p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
562                               (uint8_t *) p_region_in->psz_html,
563                               strlen( p_region_in->psz_html ),
564                               true );
565     if( p_sub )
566     {
567         p_xml = xml_Create( p_filter );
568         if( p_xml )
569         {
570             bool b_karaoke = false;
571
572             p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
573             if( p_xml_reader )
574             {
575                 /* Look for Root Node */
576                 if( xml_ReaderRead( p_xml_reader ) == 1 )
577                 {
578                     char *psz_node = xml_ReaderName( p_xml_reader );
579
580                     if( !strcasecmp( "karaoke", psz_node ) )
581                     {
582                         /* We're going to have to render the text a number
583                          * of times to show the progress marker on the text.
584                          */
585                         var_SetBool( p_filter, "text-rerender", true );
586                         b_karaoke = true;
587                     }
588                     else if( !strcasecmp( "text", psz_node ) )
589                     {
590                         b_karaoke = false;
591                     }
592                     else
593                     {
594                         /* Only text and karaoke tags are supported */
595                         xml_ReaderDelete( p_xml, p_xml_reader );
596                         p_xml_reader = NULL;
597                         rv = VLC_EGENERIC;
598                     }
599
600                     free( psz_node );
601                 }
602             }
603
604             if( p_xml_reader )
605             {
606                 UniChar    *psz_text;
607                 int         i_len = 0;
608                 uint32_t    i_runs = 0;
609                 uint32_t    i_k_runs = 0;
610                 uint32_t   *pi_run_lengths = NULL;
611                 uint32_t   *pi_k_run_lengths = NULL;
612                 uint32_t   *pi_k_durations = NULL;
613                 ATSUStyle  *pp_styles = NULL;
614
615                 psz_text = (UniChar *) malloc( strlen( p_region_in->psz_html ) *
616                                                 sizeof( UniChar ) );
617                 if( psz_text )
618                 {
619                     uint32_t k;
620
621                     rv = ProcessNodes( p_filter, p_xml_reader,
622                                   p_region_in->p_style, psz_text, &i_len,
623                                   &i_runs, &pi_run_lengths, &pp_styles,
624                                   /* No karaoke support */
625                                   false, &i_k_runs, &pi_k_run_lengths, &pi_k_durations );
626
627                     assert( pi_k_run_lengths == NULL && pi_k_durations == NULL );
628
629                     p_region_out->i_x = p_region_in->i_x;
630                     p_region_out->i_y = p_region_in->i_y;
631
632                     if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
633                     {
634                         RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs,
635                              pi_run_lengths, pp_styles);
636                     }
637
638                     for( k=0; k<i_runs; k++)
639                         ATSUDisposeStyle( pp_styles[k] );
640                     free( pp_styles );
641                     free( pi_run_lengths );
642                     free( psz_text );
643                 }
644
645                 xml_ReaderDelete( p_xml, p_xml_reader );
646             }
647             xml_Delete( p_xml );
648         }
649         stream_Delete( p_sub );
650     }
651
652     return rv;
653 }
654
655 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
656                          offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
657 {
658     offscreen_bitmap_t *p_bitmap;
659     CGContextRef        p_context = NULL;
660
661     p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
662     if( p_bitmap )
663     {
664         p_bitmap->i_bitsPerChannel = 8;
665         p_bitmap->i_bitsPerPixel   = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
666         p_bitmap->i_bytesPerPixel  = p_bitmap->i_bitsPerPixel / 8;
667         p_bitmap->i_bytesPerRow    = i_width * p_bitmap->i_bytesPerPixel;
668
669         p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
670
671 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
672         *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
673 #else
674         *pp_colorSpace = CreateGenericRGBColorSpace();
675 #endif
676
677         if( p_bitmap->p_data && *pp_colorSpace )
678         {
679             p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
680                                 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
681                                 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
682         }
683         if( p_context )
684         {
685 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_1
686             // OS X 10.1 doesn't support weak linking of this call which is only available
687             // int 10.4 and later
688             if( CGContextSetAllowsAntialiasing != NULL )
689             {
690                 CGContextSetAllowsAntialiasing( p_context, true );
691             }
692 #endif
693         }
694         *pp_memory = p_bitmap;
695     }
696
697     return p_context;
698 }
699
700 static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len,
701                                     uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles,
702                                     int i_width, int i_height, int *pi_textblock_height )
703 {
704     offscreen_bitmap_t  *p_offScreen  = NULL;
705     CGColorSpaceRef      p_colorSpace = NULL;
706     CGContextRef         p_context = NULL;
707
708     p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
709
710     if( p_context )
711     {
712         ATSUTextLayout p_textLayout;
713         OSStatus status = noErr;
714
715         status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
716                                                   i_runs,
717                                                   (const UniCharCount *) pi_run_lengths,
718                                                   pp_styles,
719                                                   &p_textLayout );
720         if( status == noErr )
721         {
722             // Attach our offscreen Image Graphics Context to the text style
723             // and setup the line alignment (have to specify the line width
724             // also in order for our chosen alignment to work)
725
726             Fract   alignment  = kATSUStartAlignment;
727             Fixed   line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
728
729             ATSUAttributeTag tags[]        = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
730             ByteCount sizes[]              = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
731             ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
732
733             int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
734
735             if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
736             {
737                 alignment = kATSUEndAlignment;
738             }
739             else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
740             {
741                 alignment = kATSUCenterAlignment;
742             }
743
744             ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
745
746             // let ATSUI deal with characters not-in-our-specified-font
747             ATSUSetTransientFontMatching( p_textLayout, true );
748
749             Fixed x = Long2Fix( HORIZONTAL_MARGIN );
750             Fixed y = Long2Fix( i_height );
751
752             // Set the line-breaks and draw individual lines
753             uint32_t i_start = 0;
754             uint32_t i_end = i_text_len;
755
756             // Set up black outlining of the text --
757             CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
758             CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
759             CGContextSetShadow( p_context, CGSizeMake( 0, 0 ), 5 );
760             float black_components[4] = {0, 0, 0, 1};
761             CGContextSetShadowWithColor (p_context, CGSizeMake( 0, 0 ), 5, CGColorCreate( CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB ), black_components ));
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_FOURCC('Y','U','V','A');
851     fmt.i_aspect = 0;
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;
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 = VERTICAL_MARGIN *i_pitch;
868     for( y=0; y<i_textblock_height; y++)
869     {
870         for( x=0; x<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 }