fb345a50d10384b731056fc112f82ed2080daf1b
[vlc.git] / 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
42 #include <math.h>
43
44 #include <Carbon/Carbon.h>
45
46 #define DEFAULT_FONT           "Arial Black"
47 #define DEFAULT_FONT_COLOR     0xffffff
48 #define DEFAULT_REL_FONT_SIZE  16
49
50 #define VERTICAL_MARGIN 3
51 #define HORIZONTAL_MARGIN 10
52
53 //////////////////////////////////////////////////////////////////////////////
54 // Local prototypes
55 //////////////////////////////////////////////////////////////////////////////
56 static int  Create ( vlc_object_t * );
57 static void Destroy( vlc_object_t * );
58
59 static int LoadFontsFromAttachments( filter_t *p_filter );
60
61 static int RenderText( filter_t *, subpicture_region_t *,
62                        subpicture_region_t * );
63 static int RenderHtml( filter_t *, subpicture_region_t *,
64                        subpicture_region_t * );
65
66 static int GetFontSize( filter_t *p_filter );
67 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
68                        UniChar *psz_utfString, uint32_t i_text_len,
69                        uint32_t i_runs, uint32_t *pi_run_lengths,
70                        ATSUStyle *pp_styles );
71 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size,
72                               uint32_t i_font_color,
73                               bool b_bold, bool b_italic,
74                               bool b_uline );
75 //////////////////////////////////////////////////////////////////////////////
76 // Module descriptor
77 //////////////////////////////////////////////////////////////////////////////
78
79 // The preferred way to set font style information is for it to come from the
80 // subtitle file, and for it to be rendered with RenderHtml instead of
81 // RenderText. This module, unlike Freetype, doesn't provide any options to
82 // override the fallback font selection used when this style information is
83 // absent.
84 #define FONT_TEXT N_("Font")
85 #define FONT_LONGTEXT N_("Name for the font you want to use")
86 #define FONTSIZER_TEXT N_("Relative font size")
87 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
88     "fonts that will be rendered on the video. If absolute font size is set, "\
89     "relative size will be overriden." )
90 #define COLOR_TEXT N_("Text default color")
91 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
92     "the video. This must be an hexadecimal (like HTML colors). The first two "\
93     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
94     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
95
96 static const int pi_color_values[] = {
97   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
98   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
99   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
100
101 static const char *const ppsz_color_descriptions[] = {
102   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
103   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
104   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
105
106 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
107 static const char *const ppsz_sizes_text[] = {
108     N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
109
110 vlc_module_begin();
111     set_shortname( N_("Mac Text renderer"));
112     set_description( N_("Quartz font renderer") );
113     set_category( CAT_VIDEO );
114     set_subcategory( SUBCAT_VIDEO_SUBPIC );
115
116     add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
117               false );
118     add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT,
119                  FONTSIZER_LONGTEXT, false );
120         change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
121     add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
122                  COLOR_LONGTEXT, false );
123         change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
124     set_capability( "text renderer", 150 );
125     add_shortcut( "text" );
126     set_callbacks( Create, Destroy );
127 vlc_module_end();
128
129 typedef struct font_stack_t font_stack_t;
130 struct font_stack_t
131 {
132     char          *psz_name;
133     int            i_size;
134     uint32_t       i_color;            // ARGB
135
136     font_stack_t  *p_next;
137 };
138
139 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
140 struct offscreen_bitmap_t
141 {
142     uint8_t       *p_data;
143     int            i_bitsPerChannel;
144     int            i_bitsPerPixel;
145     int            i_bytesPerPixel;
146     int            i_bytesPerRow;
147 };
148
149 //////////////////////////////////////////////////////////////////////////////
150 // filter_sys_t: quartztext local data
151 //////////////////////////////////////////////////////////////////////////////
152 // This structure is part of the video output thread descriptor.
153 // It describes the freetype specific properties of an output thread.
154 //////////////////////////////////////////////////////////////////////////////
155 struct filter_sys_t
156 {
157     char          *psz_font_name;
158     uint8_t        i_font_opacity;
159     int            i_font_color;
160     int            i_font_size;
161
162     ATSFontContainerRef    *p_fonts;
163     int                     i_fonts;
164 };
165
166 //////////////////////////////////////////////////////////////////////////////
167 // Create: allocates osd-text video thread output method
168 //////////////////////////////////////////////////////////////////////////////
169 // This function allocates and initializes a Clone vout method.
170 //////////////////////////////////////////////////////////////////////////////
171 static int Create( vlc_object_t *p_this )
172 {
173     filter_t *p_filter = (filter_t *)p_this;
174     filter_sys_t *p_sys;
175
176     // Allocate structure
177     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
178     if( !p_sys )
179         return VLC_ENOMEM;
180     p_sys->psz_font_name  = var_CreateGetString( p_this, "quartztext-font" );
181     p_sys->i_font_opacity = 255;
182     p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
183     p_sys->i_font_size    = GetFontSize( p_filter );
184
185     p_filter->pf_render_text = RenderText;
186     p_filter->pf_render_html = RenderHtml;
187
188     p_sys->p_fonts = NULL;
189     p_sys->i_fonts = 0;
190
191     LoadFontsFromAttachments( p_filter );
192
193     return VLC_SUCCESS;
194 }
195
196 //////////////////////////////////////////////////////////////////////////////
197 // Destroy: destroy Clone video thread output method
198 //////////////////////////////////////////////////////////////////////////////
199 // Clean up all data and library connections
200 //////////////////////////////////////////////////////////////////////////////
201 static void Destroy( vlc_object_t *p_this )
202 {
203     filter_t *p_filter = (filter_t *)p_this;
204     filter_sys_t *p_sys = p_filter->p_sys;
205
206     if( p_sys->p_fonts )
207     {
208         int   k;
209
210         for( k = 0; k < p_sys->i_fonts; k++ )
211         {
212             ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
213         }
214
215         free( p_sys->p_fonts );
216     }
217
218     free( p_sys->psz_font_name );
219     free( p_sys );
220 }
221
222 //////////////////////////////////////////////////////////////////////////////
223 // Make any TTF/OTF fonts present in the attachments of the media file
224 // available to the Quartz engine for text rendering
225 //////////////////////////////////////////////////////////////////////////////
226 static int LoadFontsFromAttachments( filter_t *p_filter )
227 {
228     filter_sys_t         *p_sys = p_filter->p_sys;
229     input_thread_t       *p_input;
230     input_attachment_t  **pp_attachments;
231     int                   i_attachments_cnt;
232     int                   k;
233     int                   rv = VLC_SUCCESS;
234
235     p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
236     if( ! p_input )
237         return VLC_EGENERIC;
238
239     if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
240     {
241         vlc_object_release(p_input);
242         return VLC_EGENERIC;
243     }
244
245     p_sys->i_fonts = 0;
246     p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
247     if(! p_sys->p_fonts )
248         rv = VLC_ENOMEM;
249
250     for( k = 0; k < i_attachments_cnt; k++ )
251     {
252         input_attachment_t *p_attach = pp_attachments[k];
253
254         if( p_sys->p_fonts )
255         {
256             if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
257                  !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) &&    // OTF
258                ( p_attach->i_data > 0 ) &&
259                ( p_attach->p_data != NULL ) )
260             {
261                 ATSFontContainerRef  container;
262
263                 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
264                                                         p_attach->i_data,
265                                                         kATSFontContextLocal,
266                                                         kATSFontFormatUnspecified,
267                                                         NULL,
268                                                         kATSOptionFlagsDefault,
269                                                         &container ))
270                 {
271                     p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
272                 }
273             }
274         }
275         vlc_input_attachment_Delete( p_attach );
276     }
277     free( pp_attachments );
278
279     vlc_object_release(p_input);
280
281     return rv;
282 }
283
284 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
285 // Original version of these functions available on:
286 // http://developer.apple.com/documentation/Carbon/Conceptual/QuickDrawToQuartz2D/tq_color/chapter_4_section_3.html
287
288 #define kGenericRGBProfilePathStr "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc"
289
290 static CMProfileRef OpenGenericProfile( void )
291 {
292     static CMProfileRef cached_rgb_prof = NULL;
293
294     // Create the profile reference only once
295     if( cached_rgb_prof == NULL )
296     {
297         OSStatus            err;
298         CMProfileLocation   loc;
299
300         loc.locType = cmPathBasedProfile;
301         strcpy( loc.u.pathLoc.path, kGenericRGBProfilePathStr );
302
303         err = CMOpenProfile( &cached_rgb_prof, &loc );
304
305         if( err != noErr )
306         {
307             cached_rgb_prof = NULL;
308         }
309     }
310
311     if( cached_rgb_prof )
312     {
313         // Clone the profile reference so that the caller has
314         // their own reference, not our cached one.
315         CMCloneProfileRef( cached_rgb_prof );
316     }
317
318     return cached_rgb_prof;
319 }
320
321 static CGColorSpaceRef CreateGenericRGBColorSpace( void )
322 {
323     static CGColorSpaceRef p_generic_rgb_cs = NULL;
324
325     if( p_generic_rgb_cs == NULL )
326     {
327         CMProfileRef generic_rgb_prof = OpenGenericProfile();
328
329         if( generic_rgb_prof )
330         {
331             p_generic_rgb_cs = CGColorSpaceCreateWithPlatformColorSpace( generic_rgb_prof );
332
333             CMCloseProfile( generic_rgb_prof );
334         }
335     }
336
337     return p_generic_rgb_cs;
338 }
339 #endif
340
341 static char *EliminateCRLF( char *psz_string )
342 {
343     char *p;
344     char *q;
345
346     for( p = psz_string; p && *p; p++ )
347     {
348         if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
349         {
350             for( q = p + 1; *q; q++ )
351                 *( q - 1 ) = *q;
352
353             *( q - 1 ) = '\0';
354         }
355     }
356     return psz_string;
357 }
358
359 // Convert UTF-8 string to UTF-16 character array -- internal Mac Endian-ness ;
360 // we don't need to worry about bidirectional text conversion as ATSUI should
361 // handle that for us automatically
362 static void ConvertToUTF16( const char *psz_utf8_str, uint32_t *pi_strlen, UniChar **ppsz_utf16_str )
363 {
364     CFStringRef   p_cfString;
365     int           i_string_length;
366
367     p_cfString = CFStringCreateWithCString( NULL, psz_utf8_str, kCFStringEncodingUTF8 );
368     if( !p_cfString )
369         return;
370
371     i_string_length = CFStringGetLength( p_cfString );
372
373     if( pi_strlen )
374         *pi_strlen = i_string_length;
375
376     if( !*ppsz_utf16_str )
377         *ppsz_utf16_str = (UniChar *) calloc( i_string_length, sizeof( UniChar ) );
378
379     CFStringGetCharacters( p_cfString, CFRangeMake( 0, i_string_length ), *ppsz_utf16_str );
380
381     CFRelease( p_cfString );
382 }
383
384 // Renders a text subpicture region into another one.
385 // It is used as pf_add_string callback in the vout method by this module
386 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
387                        subpicture_region_t *p_region_in )
388 {
389     filter_sys_t *p_sys = p_filter->p_sys;
390     UniChar      *psz_utf16_str = NULL;
391     uint32_t      i_string_length;
392     char         *psz_string;
393     int           i_font_color, i_font_alpha, i_font_size;
394     vlc_value_t val;
395     int i_scale = 1000;
396
397     p_sys->i_font_size    = GetFontSize( p_filter );
398
399     // Sanity check
400     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
401     psz_string = p_region_in->psz_text;
402     if( !psz_string || !*psz_string ) return VLC_EGENERIC;
403
404     if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
405         i_scale = val.i_int;
406
407     if( p_region_in->p_style )
408     {
409         i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
410         i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
411         i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
412     }
413     else
414     {
415         i_font_color = p_sys->i_font_color;
416         i_font_alpha = 255 - p_sys->i_font_opacity;
417         i_font_size  = p_sys->i_font_size;
418     }
419
420     if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
421
422     ConvertToUTF16( EliminateCRLF( psz_string ), &i_string_length, &psz_utf16_str );
423
424     p_region_out->i_x = p_region_in->i_x;
425     p_region_out->i_y = p_region_in->i_y;
426
427     if( psz_utf16_str != NULL )
428     {
429         ATSUStyle p_style = CreateStyle( p_sys->psz_font_name, i_font_size,
430                                          (i_font_color & 0xffffff) |
431                                          ((i_font_alpha & 0xff) << 24),
432                                          false, false, false );
433         if( p_style )
434         {
435             RenderYUVA( p_filter, p_region_out, psz_utf16_str, i_string_length,
436                         1, &i_string_length, &p_style );
437         }
438
439         ATSUDisposeStyle( p_style );
440         free( psz_utf16_str );
441     }
442
443     return VLC_SUCCESS;
444 }
445
446
447 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size, uint32_t i_font_color,
448                               bool b_bold, bool b_italic, bool b_uline )
449 {
450     ATSUStyle   p_style;
451     OSStatus    status;
452     uint32_t    i_tag_cnt;
453
454     float f_red   = (float)(( i_font_color & 0x00FF0000 ) >> 16) / 255.0;
455     float f_green = (float)(( i_font_color & 0x0000FF00 ) >>  8) / 255.0;
456     float f_blue  = (float)(  i_font_color & 0x000000FF        ) / 255.0;
457     float f_alpha = ( 255.0 - (float)(( i_font_color & 0xFF000000 ) >> 24)) / 255.0;
458
459     ATSUFontID           font;
460     Fixed                font_size  = IntToFixed( i_font_size );
461     ATSURGBAlphaColor    font_color = { f_red, f_green, f_blue, f_alpha };
462     Boolean              bold       = b_bold;
463     Boolean              italic     = b_italic;
464     Boolean              uline      = b_uline;
465
466     ATSUAttributeTag tags[]        = { kATSUSizeTag, kATSURGBAlphaColorTag, kATSUQDItalicTag,
467                                        kATSUQDBoldfaceTag, kATSUQDUnderlineTag, kATSUFontTag };
468     ByteCount sizes[]              = { sizeof( Fixed ), sizeof( ATSURGBAlphaColor ), sizeof( Boolean ),
469                                        sizeof( Boolean ), sizeof( Boolean ), sizeof( ATSUFontID )};
470     ATSUAttributeValuePtr values[] = { &font_size, &font_color, &italic, &bold, &uline, &font };
471
472     i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
473
474     status = ATSUFindFontFromName( psz_fontname,
475                                    strlen( psz_fontname ),
476                                    kFontFullName,
477                                    kFontNoPlatform,
478                                    kFontNoScript,
479                                    kFontNoLanguageCode,
480                                    &font );
481
482     if( status != noErr )
483     {
484         // If we can't find a suitable font, just do everything else
485         i_tag_cnt--;
486     }
487
488     if( noErr == ATSUCreateStyle( &p_style ) )
489     {
490         if( noErr == ATSUSetAttributes( p_style, i_tag_cnt, tags, sizes, values ) )
491         {
492             return p_style;
493         }
494         ATSUDisposeStyle( p_style );
495     }
496     return NULL;
497 }
498
499 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
500                      uint32_t i_color )
501 {
502     font_stack_t *p_new;
503
504     if( !p_font )
505         return VLC_EGENERIC;
506
507     p_new = malloc( sizeof( font_stack_t ) );
508     if( ! p_new )
509         return VLC_ENOMEM;
510
511     p_new->p_next = NULL;
512
513     if( psz_name )
514         p_new->psz_name = strdup( psz_name );
515     else
516         p_new->psz_name = NULL;
517
518     p_new->i_size   = i_size;
519     p_new->i_color  = i_color;
520
521     if( !*p_font )
522     {
523         *p_font = p_new;
524     }
525     else
526     {
527         font_stack_t *p_last;
528
529         for( p_last = *p_font;
530              p_last->p_next;
531              p_last = p_last->p_next )
532         ;
533
534         p_last->p_next = p_new;
535     }
536     return VLC_SUCCESS;
537 }
538
539 static int PopFont( font_stack_t **p_font )
540 {
541     font_stack_t *p_last, *p_next_to_last;
542
543     if( !p_font || !*p_font )
544         return VLC_EGENERIC;
545
546     p_next_to_last = NULL;
547     for( p_last = *p_font;
548          p_last->p_next;
549          p_last = p_last->p_next )
550     {
551         p_next_to_last = p_last;
552     }
553
554     if( p_next_to_last )
555         p_next_to_last->p_next = NULL;
556     else
557         *p_font = NULL;
558
559     free( p_last->psz_name );
560     free( p_last );
561
562     return VLC_SUCCESS;
563 }
564
565 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
566                      uint32_t *i_color )
567 {
568     font_stack_t *p_last;
569
570     if( !p_font || !*p_font )
571         return VLC_EGENERIC;
572
573     for( p_last=*p_font;
574          p_last->p_next;
575          p_last=p_last->p_next )
576     ;
577
578     *psz_name = p_last->psz_name;
579     *i_size   = p_last->i_size;
580     *i_color  = p_last->i_color;
581
582     return VLC_SUCCESS;
583 }
584
585 static ATSUStyle GetStyleFromFontStack( filter_sys_t *p_sys,
586         font_stack_t **p_fonts, bool b_bold, bool b_italic,
587         bool b_uline )
588 {
589     ATSUStyle   p_style = NULL;
590
591     char     *psz_fontname = NULL;
592     uint32_t  i_font_color = p_sys->i_font_color;
593     int       i_font_size  = p_sys->i_font_size;
594
595     if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
596                                  &i_font_color ))
597     {
598         p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
599                                b_bold, b_italic, b_uline );
600     }
601     return p_style;
602 }
603
604 static const struct {
605     const char *psz_name;
606     uint32_t   i_value;
607 } p_html_colors[] = {
608     /* Official html colors */
609     { "Aqua",    0x00FFFF },
610     { "Black",   0x000000 },
611     { "Blue",    0x0000FF },
612     { "Fuchsia", 0xFF00FF },
613     { "Gray",    0x808080 },
614     { "Green",   0x008000 },
615     { "Lime",    0x00FF00 },
616     { "Maroon",  0x800000 },
617     { "Navy",    0x000080 },
618     { "Olive",   0x808000 },
619     { "Purple",  0x800080 },
620     { "Red",     0xFF0000 },
621     { "Silver",  0xC0C0C0 },
622     { "Teal",    0x008080 },
623     { "White",   0xFFFFFF },
624     { "Yellow",  0xFFFF00 },
625
626     /* Common ones */
627     { "AliceBlue", 0xF0F8FF },
628     { "AntiqueWhite", 0xFAEBD7 },
629     { "Aqua", 0x00FFFF },
630     { "Aquamarine", 0x7FFFD4 },
631     { "Azure", 0xF0FFFF },
632     { "Beige", 0xF5F5DC },
633     { "Bisque", 0xFFE4C4 },
634     { "Black", 0x000000 },
635     { "BlanchedAlmond", 0xFFEBCD },
636     { "Blue", 0x0000FF },
637     { "BlueViolet", 0x8A2BE2 },
638     { "Brown", 0xA52A2A },
639     { "BurlyWood", 0xDEB887 },
640     { "CadetBlue", 0x5F9EA0 },
641     { "Chartreuse", 0x7FFF00 },
642     { "Chocolate", 0xD2691E },
643     { "Coral", 0xFF7F50 },
644     { "CornflowerBlue", 0x6495ED },
645     { "Cornsilk", 0xFFF8DC },
646     { "Crimson", 0xDC143C },
647     { "Cyan", 0x00FFFF },
648     { "DarkBlue", 0x00008B },
649     { "DarkCyan", 0x008B8B },
650     { "DarkGoldenRod", 0xB8860B },
651     { "DarkGray", 0xA9A9A9 },
652     { "DarkGrey", 0xA9A9A9 },
653     { "DarkGreen", 0x006400 },
654     { "DarkKhaki", 0xBDB76B },
655     { "DarkMagenta", 0x8B008B },
656     { "DarkOliveGreen", 0x556B2F },
657     { "Darkorange", 0xFF8C00 },
658     { "DarkOrchid", 0x9932CC },
659     { "DarkRed", 0x8B0000 },
660     { "DarkSalmon", 0xE9967A },
661     { "DarkSeaGreen", 0x8FBC8F },
662     { "DarkSlateBlue", 0x483D8B },
663     { "DarkSlateGray", 0x2F4F4F },
664     { "DarkSlateGrey", 0x2F4F4F },
665     { "DarkTurquoise", 0x00CED1 },
666     { "DarkViolet", 0x9400D3 },
667     { "DeepPink", 0xFF1493 },
668     { "DeepSkyBlue", 0x00BFFF },
669     { "DimGray", 0x696969 },
670     { "DimGrey", 0x696969 },
671     { "DodgerBlue", 0x1E90FF },
672     { "FireBrick", 0xB22222 },
673     { "FloralWhite", 0xFFFAF0 },
674     { "ForestGreen", 0x228B22 },
675     { "Fuchsia", 0xFF00FF },
676     { "Gainsboro", 0xDCDCDC },
677     { "GhostWhite", 0xF8F8FF },
678     { "Gold", 0xFFD700 },
679     { "GoldenRod", 0xDAA520 },
680     { "Gray", 0x808080 },
681     { "Grey", 0x808080 },
682     { "Green", 0x008000 },
683     { "GreenYellow", 0xADFF2F },
684     { "HoneyDew", 0xF0FFF0 },
685     { "HotPink", 0xFF69B4 },
686     { "IndianRed", 0xCD5C5C },
687     { "Indigo", 0x4B0082 },
688     { "Ivory", 0xFFFFF0 },
689     { "Khaki", 0xF0E68C },
690     { "Lavender", 0xE6E6FA },
691     { "LavenderBlush", 0xFFF0F5 },
692     { "LawnGreen", 0x7CFC00 },
693     { "LemonChiffon", 0xFFFACD },
694     { "LightBlue", 0xADD8E6 },
695     { "LightCoral", 0xF08080 },
696     { "LightCyan", 0xE0FFFF },
697     { "LightGoldenRodYellow", 0xFAFAD2 },
698     { "LightGray", 0xD3D3D3 },
699     { "LightGrey", 0xD3D3D3 },
700     { "LightGreen", 0x90EE90 },
701     { "LightPink", 0xFFB6C1 },
702     { "LightSalmon", 0xFFA07A },
703     { "LightSeaGreen", 0x20B2AA },
704     { "LightSkyBlue", 0x87CEFA },
705     { "LightSlateGray", 0x778899 },
706     { "LightSlateGrey", 0x778899 },
707     { "LightSteelBlue", 0xB0C4DE },
708     { "LightYellow", 0xFFFFE0 },
709     { "Lime", 0x00FF00 },
710     { "LimeGreen", 0x32CD32 },
711     { "Linen", 0xFAF0E6 },
712     { "Magenta", 0xFF00FF },
713     { "Maroon", 0x800000 },
714     { "MediumAquaMarine", 0x66CDAA },
715     { "MediumBlue", 0x0000CD },
716     { "MediumOrchid", 0xBA55D3 },
717     { "MediumPurple", 0x9370D8 },
718     { "MediumSeaGreen", 0x3CB371 },
719     { "MediumSlateBlue", 0x7B68EE },
720     { "MediumSpringGreen", 0x00FA9A },
721     { "MediumTurquoise", 0x48D1CC },
722     { "MediumVioletRed", 0xC71585 },
723     { "MidnightBlue", 0x191970 },
724     { "MintCream", 0xF5FFFA },
725     { "MistyRose", 0xFFE4E1 },
726     { "Moccasin", 0xFFE4B5 },
727     { "NavajoWhite", 0xFFDEAD },
728     { "Navy", 0x000080 },
729     { "OldLace", 0xFDF5E6 },
730     { "Olive", 0x808000 },
731     { "OliveDrab", 0x6B8E23 },
732     { "Orange", 0xFFA500 },
733     { "OrangeRed", 0xFF4500 },
734     { "Orchid", 0xDA70D6 },
735     { "PaleGoldenRod", 0xEEE8AA },
736     { "PaleGreen", 0x98FB98 },
737     { "PaleTurquoise", 0xAFEEEE },
738     { "PaleVioletRed", 0xD87093 },
739     { "PapayaWhip", 0xFFEFD5 },
740     { "PeachPuff", 0xFFDAB9 },
741     { "Peru", 0xCD853F },
742     { "Pink", 0xFFC0CB },
743     { "Plum", 0xDDA0DD },
744     { "PowderBlue", 0xB0E0E6 },
745     { "Purple", 0x800080 },
746     { "Red", 0xFF0000 },
747     { "RosyBrown", 0xBC8F8F },
748     { "RoyalBlue", 0x4169E1 },
749     { "SaddleBrown", 0x8B4513 },
750     { "Salmon", 0xFA8072 },
751     { "SandyBrown", 0xF4A460 },
752     { "SeaGreen", 0x2E8B57 },
753     { "SeaShell", 0xFFF5EE },
754     { "Sienna", 0xA0522D },
755     { "Silver", 0xC0C0C0 },
756     { "SkyBlue", 0x87CEEB },
757     { "SlateBlue", 0x6A5ACD },
758     { "SlateGray", 0x708090 },
759     { "SlateGrey", 0x708090 },
760     { "Snow", 0xFFFAFA },
761     { "SpringGreen", 0x00FF7F },
762     { "SteelBlue", 0x4682B4 },
763     { "Tan", 0xD2B48C },
764     { "Teal", 0x008080 },
765     { "Thistle", 0xD8BFD8 },
766     { "Tomato", 0xFF6347 },
767     { "Turquoise", 0x40E0D0 },
768     { "Violet", 0xEE82EE },
769     { "Wheat", 0xF5DEB3 },
770     { "White", 0xFFFFFF },
771     { "WhiteSmoke", 0xF5F5F5 },
772     { "Yellow", 0xFFFF00 },
773     { "YellowGreen", 0x9ACD32 },
774
775     { NULL, 0 }
776 };
777
778 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
779                                   font_stack_t **p_fonts, int i_scale )
780 {
781     int        rv;
782     char      *psz_fontname = NULL;
783     uint32_t   i_font_color = 0xffffff;
784     int        i_font_alpha = 0;
785     int        i_font_size  = 24;
786
787     // Default all attributes to the top font in the stack -- in case not
788     // all attributes are specified in the sub-font
789     if( VLC_SUCCESS == PeekFont( p_fonts,
790                                  &psz_fontname,
791                                  &i_font_size,
792                                  &i_font_color ))
793     {
794         psz_fontname = strdup( psz_fontname );
795         i_font_size = i_font_size * 1000 / i_scale;
796     }
797     i_font_alpha = (i_font_color >> 24) & 0xff;
798     i_font_color &= 0x00ffffff;
799
800     while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
801     {
802         char *psz_name = xml_ReaderName( p_xml_reader );
803         char *psz_value = xml_ReaderValue( p_xml_reader );
804
805         if( psz_name && psz_value )
806         {
807             if( !strcasecmp( "face", psz_name ) )
808             {
809                 free( psz_fontname );
810                 psz_fontname = strdup( psz_value );
811             }
812             else if( !strcasecmp( "size", psz_name ) )
813             {
814                 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
815                 {
816                     int i_value = atoi( psz_value );
817
818                     if( ( i_value >= -5 ) && ( i_value <= 5 ) )
819                         i_font_size += ( i_value * i_font_size ) / 10;
820                     else if( i_value < -5 )
821                         i_font_size = - i_value;
822                     else if( i_value > 5 )
823                         i_font_size = i_value;
824                 }
825                 else
826                     i_font_size = atoi( psz_value );
827             }
828             else if( !strcasecmp( "color", psz_name ) )
829             {
830                 if( psz_value[0] == '#' )
831                 {
832                     i_font_color = strtol( psz_value + 1, NULL, 16 );
833                     i_font_color &= 0x00ffffff;
834                 }
835                 else
836                 {
837                     for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
838                     {
839                         if( !strncasecmp( psz_value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
840                         {
841                             i_font_color = p_html_colors[i].i_value;
842                             break;
843                         }
844                     }
845                 }
846             }
847             else if( !strcasecmp( "alpha", psz_name ) &&
848                      ( psz_value[0] == '#' ) )
849             {
850                 i_font_alpha = strtol( psz_value + 1, NULL, 16 );
851                 i_font_alpha &= 0xff;
852             }
853         }
854         free( psz_name );
855         free( psz_value );
856     }
857     rv = PushFont( p_fonts,
858                    psz_fontname,
859                    i_font_size * i_scale / 1000,
860                    (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
861
862     free( psz_fontname );
863
864     return rv;
865 }
866
867 static int ProcessNodes( filter_t *p_filter,
868                          xml_reader_t *p_xml_reader,
869                          text_style_t *p_font_style,
870                          UniChar *psz_text,
871                          int *pi_len,
872
873                          uint32_t *pi_runs,
874                          uint32_t **ppi_run_lengths,
875                          ATSUStyle **ppp_styles )
876 {
877     int           rv             = VLC_SUCCESS;
878     filter_sys_t *p_sys          = p_filter->p_sys;
879     UniChar      *psz_text_orig  = psz_text;
880     font_stack_t *p_fonts        = NULL;
881     vlc_value_t   val;
882     int           i_scale        = 1000;
883
884     char *psz_node  = NULL;
885
886     bool b_italic = false;
887     bool b_bold   = false;
888     bool b_uline  = false;
889
890     if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
891         i_scale = val.i_int;
892
893     if( p_font_style )
894     {
895         rv = PushFont( &p_fonts,
896                p_font_style->psz_fontname,
897                p_font_style->i_font_size * i_scale / 1000,
898                (p_font_style->i_font_color & 0xffffff) |
899                    ((p_font_style->i_font_alpha & 0xff) << 24) );
900
901         if( p_font_style->i_style_flags & STYLE_BOLD )
902             b_bold = true;
903         if( p_font_style->i_style_flags & STYLE_ITALIC )
904             b_italic = true;
905         if( p_font_style->i_style_flags & STYLE_UNDERLINE )
906             b_uline = true;
907     }
908     else
909     {
910         rv = PushFont( &p_fonts,
911                        p_sys->psz_font_name,
912                        p_sys->i_font_size,
913                        p_sys->i_font_color );
914     }
915     if( rv != VLC_SUCCESS )
916         return rv;
917
918     while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
919     {
920         switch ( xml_ReaderNodeType( p_xml_reader ) )
921         {
922             case XML_READER_NONE:
923                 break;
924             case XML_READER_ENDELEM:
925                 psz_node = xml_ReaderName( p_xml_reader );
926                 if( psz_node )
927                 {
928                     if( !strcasecmp( "font", psz_node ) )
929                         PopFont( &p_fonts );
930                     else if( !strcasecmp( "b", psz_node ) )
931                         b_bold   = false;
932                     else if( !strcasecmp( "i", psz_node ) )
933                         b_italic = false;
934                     else if( !strcasecmp( "u", psz_node ) )
935                         b_uline  = false;
936
937                     free( psz_node );
938                 }
939                 break;
940             case XML_READER_STARTELEM:
941                 psz_node = xml_ReaderName( p_xml_reader );
942                 if( psz_node )
943                 {
944                     if( !strcasecmp( "font", psz_node ) )
945                         rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
946                     else if( !strcasecmp( "b", psz_node ) )
947                         b_bold = true;
948                     else if( !strcasecmp( "i", psz_node ) )
949                         b_italic = true;
950                     else if( !strcasecmp( "u", psz_node ) )
951                         b_uline = true;
952                     else if( !strcasecmp( "br", psz_node ) )
953                     {
954                         uint32_t i_string_length;
955
956                         ConvertToUTF16( "\n", &i_string_length, &psz_text );
957                         psz_text += i_string_length;
958
959                         (*pi_runs)++;
960
961                         if( *ppp_styles )
962                             *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
963                         else
964                             *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
965
966                         (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
967
968                         if( *ppi_run_lengths )
969                             *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
970                         else
971                             *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
972
973                         (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
974                     }
975                     free( psz_node );
976                 }
977                 break;
978             case XML_READER_TEXT:
979                 psz_node = xml_ReaderValue( p_xml_reader );
980                 if( psz_node )
981                 {
982                     uint32_t i_string_length;
983
984                     // Turn any multiple-whitespaces into single spaces
985                     char *s = strpbrk( psz_node, "\t\r\n " );
986                     while( s )
987                     {
988                         int i_whitespace = strspn( s, "\t\r\n " );
989
990                         if( i_whitespace > 1 )
991                             memmove( &s[1],
992                                      &s[i_whitespace],
993                                      strlen( s ) - i_whitespace + 1 );
994                         *s++ = ' ';
995
996                         s = strpbrk( s, "\t\r\n " );
997                     }
998
999                     ConvertToUTF16( psz_node, &i_string_length, &psz_text );
1000                     psz_text += i_string_length;
1001
1002                     (*pi_runs)++;
1003
1004                     if( *ppp_styles )
1005                         *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
1006                     else
1007                         *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
1008
1009                     (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
1010
1011                     if( *ppi_run_lengths )
1012                         *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
1013                     else
1014                         *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
1015
1016                     (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
1017
1018                     free( psz_node );
1019                 }
1020                 break;
1021         }
1022     }
1023
1024     *pi_len = psz_text - psz_text_orig;
1025
1026     while( VLC_SUCCESS == PopFont( &p_fonts ) );
1027
1028     return rv;
1029 }
1030
1031 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
1032                        subpicture_region_t *p_region_in )
1033 {
1034     int          rv = VLC_SUCCESS;
1035     stream_t     *p_sub = NULL;
1036     xml_t        *p_xml = NULL;
1037     xml_reader_t *p_xml_reader = NULL;
1038
1039     if( !p_region_in || !p_region_in->psz_html )
1040         return VLC_EGENERIC;
1041
1042     /* Reset the default fontsize in case screen metrics have changed */
1043     p_filter->p_sys->i_font_size = GetFontSize( p_filter );
1044
1045     p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
1046                               (uint8_t *) p_region_in->psz_html,
1047                               strlen( p_region_in->psz_html ),
1048                               true );
1049     if( p_sub )
1050     {
1051         p_xml = xml_Create( p_filter );
1052         if( p_xml )
1053         {
1054             bool b_karaoke = false;
1055
1056             p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
1057             if( p_xml_reader )
1058             {
1059                 /* Look for Root Node */
1060                 if( xml_ReaderRead( p_xml_reader ) == 1 )
1061                 {
1062                     char *psz_node = xml_ReaderName( p_xml_reader );
1063
1064                     if( !strcasecmp( "karaoke", psz_node ) )
1065                     {
1066                         /* We're going to have to render the text a number
1067                          * of times to show the progress marker on the text.
1068                          */
1069                         var_SetBool( p_filter, "text-rerender", true );
1070                         b_karaoke = true;
1071                     }
1072                     else if( !strcasecmp( "text", psz_node ) )
1073                     {
1074                         b_karaoke = false;
1075                     }
1076                     else
1077                     {
1078                         /* Only text and karaoke tags are supported */
1079                         xml_ReaderDelete( p_xml, p_xml_reader );
1080                         p_xml_reader = NULL;
1081                         rv = VLC_EGENERIC;
1082                     }
1083
1084                     free( psz_node );
1085                 }
1086             }
1087
1088             if( p_xml_reader )
1089             {
1090                 UniChar    *psz_text;
1091                 int         i_len = 0;
1092                 uint32_t    i_runs = 0;
1093                 uint32_t   *pi_run_lengths = NULL;
1094                 ATSUStyle  *pp_styles = NULL;
1095
1096                 psz_text = (UniChar *) malloc( strlen( p_region_in->psz_html ) *
1097                                                 sizeof( UniChar ) );
1098                 if( psz_text )
1099                 {
1100                     uint32_t k;
1101
1102                     rv = ProcessNodes( p_filter, p_xml_reader,
1103                                   p_region_in->p_style, psz_text, &i_len,
1104                                   &i_runs, &pi_run_lengths, &pp_styles );
1105
1106                     p_region_out->i_x = p_region_in->i_x;
1107                     p_region_out->i_y = p_region_in->i_y;
1108
1109                     if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
1110                     {
1111                         RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs,
1112                              pi_run_lengths, pp_styles);
1113                     }
1114
1115                     for( k=0; k<i_runs; k++)
1116                         ATSUDisposeStyle( pp_styles[k] );
1117                     free( pp_styles );
1118                     free( pi_run_lengths );
1119                     free( psz_text );
1120                 }
1121
1122                 xml_ReaderDelete( p_xml, p_xml_reader );
1123             }
1124             xml_Delete( p_xml );
1125         }
1126         stream_Delete( p_sub );
1127     }
1128
1129     return rv;
1130 }
1131
1132 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
1133                          offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
1134 {
1135     offscreen_bitmap_t *p_bitmap;
1136     CGContextRef        p_context = NULL;
1137
1138     p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
1139     if( p_bitmap )
1140     {
1141         p_bitmap->i_bitsPerChannel = 8;
1142         p_bitmap->i_bitsPerPixel   = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
1143         p_bitmap->i_bytesPerPixel  = p_bitmap->i_bitsPerPixel / 8;
1144         p_bitmap->i_bytesPerRow    = i_width * p_bitmap->i_bytesPerPixel;
1145
1146         p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
1147
1148 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
1149         *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
1150 #else
1151         *pp_colorSpace = CreateGenericRGBColorSpace();
1152 #endif
1153
1154         if( p_bitmap->p_data && *pp_colorSpace )
1155         {
1156             p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
1157                                 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
1158                                 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
1159         }
1160         if( p_context )
1161         {
1162 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_1
1163             // OS X 10.1 doesn't support weak linking of this call which is only available
1164             // int 10.4 and later
1165             if( CGContextSetAllowsAntialiasing != NULL )
1166             {
1167                 CGContextSetAllowsAntialiasing( p_context, true );
1168             }
1169 #endif
1170         }
1171         *pp_memory = p_bitmap;
1172     }
1173
1174     return p_context;
1175 }
1176
1177 static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len,
1178                                     uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles,
1179                                     int i_width, int i_height, int *pi_textblock_height )
1180 {
1181     offscreen_bitmap_t  *p_offScreen  = NULL;
1182     CGColorSpaceRef      p_colorSpace = NULL;
1183     CGContextRef         p_context = NULL;
1184
1185     p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
1186
1187     if( p_context )
1188     {
1189         ATSUTextLayout p_textLayout;
1190         OSStatus status = noErr;
1191
1192         status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
1193                                                   i_runs,
1194                                                   (const UniCharCount *) pi_run_lengths,
1195                                                   pp_styles,
1196                                                   &p_textLayout );
1197         if( status == noErr )
1198         {
1199             // Attach our offscreen Image Graphics Context to the text style
1200             // and setup the line alignment (have to specify the line width
1201             // also in order for our chosen alignment to work)
1202
1203             Fract   alignment  = kATSUStartAlignment;
1204             Fixed   line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
1205
1206             ATSUAttributeTag tags[]        = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
1207             ByteCount sizes[]              = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
1208             ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
1209
1210             int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
1211
1212             if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
1213             {
1214                 alignment = kATSUEndAlignment;
1215             }
1216             else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
1217             {
1218                 alignment = kATSUCenterAlignment;
1219             }
1220
1221             ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
1222
1223             // let ATSUI deal with characters not-in-our-specified-font
1224             ATSUSetTransientFontMatching( p_textLayout, true );
1225
1226             Fixed x = Long2Fix( HORIZONTAL_MARGIN );
1227             Fixed y = Long2Fix( i_height );
1228
1229             // Set the line-breaks and draw individual lines
1230             uint32_t i_start = 0;
1231             uint32_t i_end = i_text_len;
1232
1233             // Set up black outlining of the text --
1234             CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
1235             CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
1236             CGContextSetShadow( p_context, CGSizeMake( 0, 0 ), 5 );
1237             float black_components[4] = {0, 0, 0, 1};
1238             CGContextSetShadowWithColor (p_context, CGSizeMake( 0, 0 ), 5, CGColorCreate( CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB ), black_components ));
1239             do
1240             {
1241                 // ATSUBreakLine will automatically pick up any manual '\n's also
1242                 status = ATSUBreakLine( p_textLayout, i_start, line_width, true, (UniCharArrayOffset *) &i_end );
1243                 if( ( status == noErr ) || ( status == kATSULineBreakInWord ) )
1244                 {
1245                     Fixed     ascent;
1246                     Fixed     descent;
1247                     uint32_t  i_actualSize;
1248
1249                     // Come down far enough to fit the height of this line --
1250                     ATSUGetLineControl( p_textLayout, i_start, kATSULineAscentTag,
1251                                     sizeof( Fixed ), &ascent, (ByteCount *) &i_actualSize );
1252
1253                     // Quartz uses an upside-down co-ordinate space -> y values decrease as
1254                     // you move down the page
1255                     y -= ascent;
1256
1257                     // Set the outlining for this line to be dependent on the size of the line -
1258                     // make it about 5% of the ascent, with a minimum at 1.0
1259                     float f_thickness = FixedToFloat( ascent ) * 0.05;
1260                     CGContextSetLineWidth( p_context, (( f_thickness < 1.0 ) ? 1.0 : f_thickness ));
1261                     ATSUDrawText( p_textLayout, i_start, i_end - i_start, x, y );
1262
1263                     // and now prepare for the next line by coming down far enough for our
1264                     // descent
1265                     ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag,
1266                                     sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize );
1267                     y -= descent;
1268
1269                     i_start = i_end;
1270                 }
1271                 else
1272                     break;
1273             }
1274             while( i_end < i_text_len );
1275
1276             *pi_textblock_height = i_height - Fix2Long( y );
1277             CGContextFlush( p_context );
1278
1279             ATSUDisposeTextLayout( p_textLayout );
1280         }
1281
1282         CGContextRelease( p_context );
1283     }
1284     if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
1285
1286     return p_offScreen;
1287 }
1288
1289 static int GetFontSize( filter_t *p_filter )
1290 {
1291     return p_filter->fmt_out.video.i_height / __MAX(1, var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" ));
1292 }
1293
1294 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniChar *psz_utf16_str,
1295                        uint32_t i_text_len, uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles )
1296 {
1297     offscreen_bitmap_t *p_offScreen = NULL;
1298     int      i_textblock_height = 0;
1299
1300     int i_width = p_filter->fmt_out.video.i_visible_width;
1301     int i_height = p_filter->fmt_out.video.i_visible_height;
1302     int i_text_align = p_region->i_align & 0x3;
1303
1304     if( !psz_utf16_str )
1305     {
1306         msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1307         return VLC_EGENERIC;
1308     }
1309
1310     p_offScreen = Compose( i_text_align, psz_utf16_str, i_text_len,
1311                            i_runs, pi_run_lengths, pp_styles,
1312                            i_width, i_height, &i_textblock_height );
1313
1314     if( !p_offScreen )
1315     {
1316         msg_Err( p_filter, "No offscreen buffer" );
1317         return VLC_EGENERIC;
1318     }
1319
1320     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1321     video_format_t fmt;
1322     int x, y, i_offset, i_pitch;
1323     uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1324
1325     // Create a new subpicture region
1326     memset( &fmt, 0, sizeof(video_format_t) );
1327     fmt.i_chroma = VLC_FOURCC('Y','U','V','A');
1328     fmt.i_aspect = 0;
1329     fmt.i_width = fmt.i_visible_width = i_width;
1330     fmt.i_height = fmt.i_visible_height = i_textblock_height + VERTICAL_MARGIN * 2;
1331     fmt.i_x_offset = fmt.i_y_offset = 0;
1332
1333     p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect );
1334     if( !p_region->p_picture )
1335         return VLC_EGENERIC;
1336     p_region->fmt = fmt;
1337
1338     p_dst_y = p_region->p_picture->Y_PIXELS;
1339     p_dst_u = p_region->p_picture->U_PIXELS;
1340     p_dst_v = p_region->p_picture->V_PIXELS;
1341     p_dst_a = p_region->p_picture->A_PIXELS;
1342     i_pitch = p_region->p_picture->A_PITCH;
1343
1344     i_offset = VERTICAL_MARGIN *i_pitch;
1345     for( y=0; y<i_textblock_height; y++)
1346     {
1347         for( x=0; x<i_width; x++)
1348         {
1349             int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel     ];
1350             int i_red   = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1351             int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1352             int i_blue  = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1353
1354             i_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
1355                               802 * i_blue + 4096 + 131072 ) >> 13, 235);
1356             i_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
1357                              3598 * i_blue + 4096 + 1048576) >> 13, 240);
1358             i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1359                               -585 * i_blue + 4096 + 1048576) >> 13, 240);
1360
1361             p_dst_y[ i_offset + x ] = i_y;
1362             p_dst_u[ i_offset + x ] = i_u;
1363             p_dst_v[ i_offset + x ] = i_v;
1364             p_dst_a[ i_offset + x ] = i_alpha;
1365         }
1366         i_offset += i_pitch;
1367     }
1368
1369     free( p_offScreen->p_data );
1370     free( p_offScreen );
1371
1372     return VLC_SUCCESS;
1373 }