]> git.sesse.net Git - vlc/blob - modules/misc/quartztext.c
all: Subtitle improvment patch by Bernie Purcell.
[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 <b dot purcell at adbglobal dot com>
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 #include <stdlib.h>                                      // malloc(), free()
28 #include <string.h>
29
30 #include <vlc/vlc.h>
31 #include <vlc_vout.h>
32 #include <vlc_osd.h>
33 #include <vlc_block.h>
34 #include <vlc_filter.h>
35 #include <vlc_stream.h>
36 #include <vlc_xml.h>
37
38 #include <math.h>
39
40 #include <Carbon/Carbon.h>
41
42 #define DEFAULT_FONT           "Verdana"
43 #define DEFAULT_FONT_COLOR     0xffffff
44 #define DEFAULT_REL_FONT_SIZE  16
45
46 #define VERTICAL_MARGIN 3
47 #define HORIZONTAL_MARGIN 10
48
49 //////////////////////////////////////////////////////////////////////////////
50 // Local prototypes
51 //////////////////////////////////////////////////////////////////////////////
52 static int  Create ( vlc_object_t * );
53 static void Destroy( vlc_object_t * );
54
55 static int RenderText( filter_t *, subpicture_region_t *,
56                        subpicture_region_t * );
57 static int RenderHtml( filter_t *, subpicture_region_t *,
58                        subpicture_region_t * );
59
60 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
61                        UniChar *psz_utfString, uint32_t i_text_len,
62                        uint32_t i_runs, uint32_t *pi_run_lengths,
63                        ATSUStyle *pp_styles );
64 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size,
65                               int i_font_color, int i_font_alpha,
66                               vlc_bool_t b_bold, vlc_bool_t b_italic,
67                               vlc_bool_t b_uline );
68 //////////////////////////////////////////////////////////////////////////////
69 // Module descriptor
70 //////////////////////////////////////////////////////////////////////////////
71
72 // The preferred way to set font style information is for it to come from the
73 // subtitle file, and for it to be rendered with RenderHtml instead of
74 // RenderText. This module, unlike Freetype, doesn't provide any options to
75 // override the fallback font selection used when this style information is
76 // absent.
77 vlc_module_begin();
78     set_shortname( _("Mac Text renderer"));
79     set_description( _("Quartz font renderer") );
80     set_category( CAT_VIDEO );
81     set_subcategory( SUBCAT_VIDEO_SUBPIC );
82
83     set_capability( "text renderer", 120 );
84     add_shortcut( "text" );
85     set_callbacks( Create, Destroy );
86 vlc_module_end();
87
88 typedef struct font_stack_t font_stack_t;
89 struct font_stack_t
90 {
91     char          *psz_name;
92     int            i_size;
93     int            i_color;
94     int            i_alpha;
95
96     font_stack_t  *p_next;
97 };
98
99 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
100 struct offscreen_bitmap_t
101 {
102     uint8_t       *p_data;
103     int            i_bitsPerChannel;
104     int            i_bitsPerPixel;
105     int            i_bytesPerPixel;
106     int            i_bytesPerRow;
107 };
108
109 //////////////////////////////////////////////////////////////////////////////
110 // filter_sys_t: quartztext local data
111 //////////////////////////////////////////////////////////////////////////////
112 // This structure is part of the video output thread descriptor.
113 // It describes the freetype specific properties of an output thread.
114 //////////////////////////////////////////////////////////////////////////////
115 struct filter_sys_t
116 {
117     char          *psz_font_name;
118     uint8_t        i_font_opacity;
119     int            i_font_color;
120     int            i_font_size;
121 };
122
123 //////////////////////////////////////////////////////////////////////////////
124 // Create: allocates osd-text video thread output method
125 //////////////////////////////////////////////////////////////////////////////
126 // This function allocates and initializes a Clone vout method.
127 //////////////////////////////////////////////////////////////////////////////
128 static int Create( vlc_object_t *p_this )
129 {
130     filter_t *p_filter = (filter_t *)p_this;
131     filter_sys_t *p_sys;
132
133     // Allocate structure
134     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
135     if( !p_sys )
136     {
137         msg_Err( p_filter, "out of memory" );
138         return VLC_ENOMEM;
139     }
140     p_sys->psz_font_name  = strdup( DEFAULT_FONT );
141     p_sys->i_font_opacity = 255;
142     p_sys->i_font_color   = DEFAULT_FONT_COLOR;
143     p_sys->i_font_size    = p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
144
145     p_filter->pf_render_text = RenderText;
146     p_filter->pf_render_html = RenderHtml;
147
148     return VLC_SUCCESS;
149 }
150
151 //////////////////////////////////////////////////////////////////////////////
152 // Destroy: destroy Clone video thread output method
153 //////////////////////////////////////////////////////////////////////////////
154 // Clean up all data and library connections
155 //////////////////////////////////////////////////////////////////////////////
156 static void Destroy( vlc_object_t *p_this )
157 {
158     filter_t *p_filter = (filter_t *)p_this;
159     filter_sys_t *p_sys = p_filter->p_sys;
160
161     free( p_sys->psz_font_name );
162     free( p_sys );
163 }
164
165 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
166 // Original version of these functions available on:
167 // http://developer.apple.com/documentation/Carbon/Conceptual/QuickDrawToQuartz2D/tq_color/chapter_4_section_3.html
168
169 #define kGenericRGBProfilePathStr "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc" 
170  
171 static CMProfileRef OpenGenericProfile( void )
172 {
173     static CMProfileRef cached_rgb_prof = NULL;
174  
175     // Create the profile reference only once
176     if( cached_rgb_prof == NULL )
177     {
178         OSStatus            err;
179         CMProfileLocation   loc;
180  
181         loc.locType = cmPathBasedProfile;
182         strcpy( loc.u.pathLoc.path, kGenericRGBProfilePathStr );
183  
184         err = CMOpenProfile( &cached_rgb_prof, &loc );
185  
186         if( err != noErr )
187         {
188             cached_rgb_prof = NULL;
189         }
190     }
191  
192     if( cached_rgb_prof )
193     {
194         // Clone the profile reference so that the caller has 
195         // their own reference, not our cached one.
196         CMCloneProfileRef( cached_rgb_prof );   
197     }
198  
199     return cached_rgb_prof;
200 }
201
202 static CGColorSpaceRef CreateGenericRGBColorSpace( void )
203 {
204     static CGColorSpaceRef p_generic_rgb_cs = NULL;
205  
206     if( p_generic_rgb_cs == NULL )
207     {
208         CMProfileRef generic_rgb_prof = OpenGenericProfile();
209  
210         if( generic_rgb_prof )
211         {
212             p_generic_rgb_cs = CGColorSpaceCreateWithPlatformColorSpace( generic_rgb_prof );
213  
214             CMCloseProfile( generic_rgb_prof ); 
215         }
216     }
217
218     return p_generic_rgb_cs;
219 }
220 #endif
221
222 static char *EliminateCRLF( char *psz_string )
223 {
224     char *p;
225     char *q;
226
227     for( p = psz_string; p && *p; p++ )
228     {
229         if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
230         {
231             for( q = p + 1; *q; q++ )
232                 *( q - 1 ) = *q;
233
234             *( q - 1 ) = '\0';
235         }
236     }
237     return psz_string;
238 }
239
240 // Convert UTF-8 string to UTF-16 character array -- internal Mac Endian-ness ;
241 // we don't need to worry about bidirectional text conversion as ATSUI should
242 // handle that for us automatically
243 static void ConvertToUTF16( const char *psz_utf8_str, uint32_t *pi_strlen, UniChar **ppsz_utf16_str )
244 {
245     CFStringRef   p_cfString;
246     int           i_string_length;
247
248     p_cfString = CFStringCreateWithCString( NULL, psz_utf8_str, kCFStringEncodingUTF8 );
249     i_string_length = CFStringGetLength( p_cfString );
250
251     if( pi_strlen )
252         *pi_strlen = i_string_length;
253
254     if( !*ppsz_utf16_str )
255         *ppsz_utf16_str = (UniChar *) calloc( i_string_length, sizeof( UniChar ) );
256
257     CFStringGetCharacters( p_cfString, CFRangeMake( 0, i_string_length ), *ppsz_utf16_str );
258     
259     CFRelease( p_cfString );
260 }
261
262 // Renders a text subpicture region into another one.
263 // It is used as pf_add_string callback in the vout method by this module
264 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
265                        subpicture_region_t *p_region_in )
266 {
267     filter_sys_t *p_sys = p_filter->p_sys;
268     UniChar      *psz_utf16_str = NULL;
269     uint32_t      i_string_length;
270     char         *psz_string;
271     int           i_font_color, i_font_alpha, i_font_size;
272
273     // Sanity check
274     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
275     psz_string = p_region_in->psz_text;
276     if( !psz_string || !*psz_string ) return VLC_EGENERIC;
277     
278     if( p_region_in->p_style )
279     {
280         i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
281         i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
282         i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 );
283     }
284     else
285     {
286         i_font_color = p_sys->i_font_color;
287         i_font_alpha = 255 - p_sys->i_font_opacity;
288         i_font_size  = p_sys->i_font_size;
289     }
290
291     if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
292     
293     ConvertToUTF16( EliminateCRLF( psz_string ), &i_string_length, &psz_utf16_str );
294
295     p_region_out->i_x = p_region_in->i_x;
296     p_region_out->i_y = p_region_in->i_y;
297
298     if( psz_utf16_str != NULL )
299     {
300         ATSUStyle p_style = CreateStyle( p_sys->psz_font_name, i_font_size,
301                                          i_font_color, i_font_alpha,
302                                          VLC_FALSE, VLC_FALSE, VLC_FALSE );
303         if( p_style )
304         {
305             RenderYUVA( p_filter, p_region_out, psz_utf16_str, i_string_length,
306                         1, &i_string_length, &p_style );
307         }
308
309         ATSUDisposeStyle( p_style );
310         free( psz_utf16_str );
311     }
312
313     return VLC_SUCCESS;
314 }
315
316
317 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size, int i_font_color, int i_font_alpha,
318                               vlc_bool_t b_bold, vlc_bool_t b_italic, vlc_bool_t b_uline )
319 {
320     ATSUStyle   p_style;
321     OSStatus    status;
322     uint32_t    i_tag_cnt;
323
324     float f_red   = (float)(( i_font_color & 0x00FF0000 ) >> 16) / 255.0;
325     float f_green = (float)(( i_font_color & 0x0000FF00 ) >>  8) / 255.0;
326     float f_blue  = (float)(  i_font_color & 0x000000FF        ) / 255.0;
327     float f_alpha = ( 255.0 - (float)i_font_alpha) / 255.0;
328
329     ATSUFontID           font;
330     Fixed                font_size  = IntToFixed( i_font_size );
331     ATSURGBAlphaColor    font_color = { f_red, f_green, f_blue, f_alpha };
332     Boolean              bold       = b_bold;
333     Boolean              italic     = b_italic;
334     Boolean              uline      = b_uline;
335
336     ATSUAttributeTag tags[]        = { kATSUSizeTag, kATSURGBAlphaColorTag, kATSUQDItalicTag,
337                                        kATSUQDBoldfaceTag, kATSUQDUnderlineTag, kATSUFontTag };
338     ByteCount sizes[]              = { sizeof( Fixed ), sizeof( ATSURGBAlphaColor ), sizeof( Boolean ),
339                                        sizeof( Boolean ), sizeof( Boolean ), sizeof( ATSUFontID )};
340     ATSUAttributeValuePtr values[] = { &font_size, &font_color, &italic, &bold, &uline, &font };
341
342     i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
343
344     status = ATSUFindFontFromName( psz_fontname,
345                                    strlen( psz_fontname ),
346                                    kFontFullName,
347                                    kFontNoPlatform,
348                                    kFontNoScript,
349                                    kFontNoLanguageCode,
350                                    &font );
351
352     if( status != noErr )
353     {
354         // If we can't find a suitable font, just do everything else
355         i_tag_cnt--;
356     }
357
358     if( noErr == ATSUCreateStyle( &p_style ) )
359     {
360         if( noErr == ATSUSetAttributes( p_style, i_tag_cnt, tags, sizes, values ) )
361         {
362             return p_style;
363         }
364         ATSUDisposeStyle( p_style );
365     }
366     return NULL;
367 }
368
369 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
370                      int i_color, int i_alpha )
371 {
372     font_stack_t *p_new;
373
374     if( !p_font )
375         return VLC_EGENERIC;
376
377     p_new = malloc( sizeof( font_stack_t ) );
378     p_new->p_next = NULL;
379
380     if( psz_name )
381         p_new->psz_name = strdup( psz_name );
382     else
383         p_new->psz_name = NULL;
384
385     p_new->i_size   = i_size;
386     p_new->i_color  = i_color;
387     p_new->i_alpha  = i_alpha;
388
389     if( !*p_font )
390     {
391         *p_font = p_new;
392     }
393     else
394     {
395         font_stack_t *p_last;
396
397         for( p_last = *p_font;
398              p_last->p_next;
399              p_last = p_last->p_next )
400         ;
401
402         p_last->p_next = p_new;
403     }
404     return VLC_SUCCESS;
405 }
406
407 static int PopFont( font_stack_t **p_font )
408 {
409     font_stack_t *p_last, *p_next_to_last;
410
411     if( !p_font || !*p_font )
412         return VLC_EGENERIC;
413     
414     p_next_to_last = NULL;
415     for( p_last = *p_font;
416          p_last->p_next;
417          p_last = p_last->p_next )
418     {
419         p_next_to_last = p_last;
420     }
421
422     if( p_next_to_last )
423         p_next_to_last->p_next = NULL;
424     else
425         *p_font = NULL;
426
427     free( p_last->psz_name );
428     free( p_last );
429
430     return VLC_SUCCESS;
431 }
432
433 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
434                      int *i_color, int *i_alpha )
435 {
436     font_stack_t *p_last;
437
438     if( !p_font || !*p_font )
439         return VLC_EGENERIC;
440     
441     for( p_last=*p_font;
442          p_last->p_next;
443          p_last=p_last->p_next )
444     ;
445
446     *psz_name = p_last->psz_name;
447     *i_size   = p_last->i_size;
448     *i_color  = p_last->i_color;
449     *i_alpha  = p_last->i_alpha;
450
451     return VLC_SUCCESS;
452 }
453
454 static ATSUStyle GetStyleFromFontStack( filter_sys_t *p_sys, font_stack_t **p_fonts,
455                               vlc_bool_t b_bold, vlc_bool_t b_italic, vlc_bool_t b_uline )
456 {
457     ATSUStyle   p_style = NULL;
458
459     char  *psz_fontname = NULL;
460     int    i_font_color = p_sys->i_font_color;
461     int    i_font_alpha = 0;
462     int    i_font_size  = p_sys->i_font_size;
463
464     if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size, &i_font_color, &i_font_alpha ) )
465     {
466         p_style = CreateStyle( psz_fontname, i_font_size, i_font_color, i_font_alpha,
467                                b_bold, b_italic, b_uline );
468     }
469     return p_style;
470 }
471
472 static void ProcessNodes( filter_t *p_filter, xml_reader_t *p_xml_reader,
473                           text_style_t *p_font_style, UniChar *psz_text, int *pi_len,
474                           uint32_t *pi_runs, uint32_t **ppi_run_lengths,
475                           ATSUStyle **ppp_styles)
476 {
477     filter_sys_t *p_sys          = p_filter->p_sys;
478     UniChar      *psz_text_orig  = psz_text;
479     font_stack_t *p_fonts        = NULL;
480
481     char *psz_node  = NULL;
482
483     vlc_bool_t b_italic = VLC_FALSE;
484     vlc_bool_t b_bold   = VLC_FALSE;
485     vlc_bool_t b_uline  = VLC_FALSE;
486
487     if( p_font_style )
488     {
489         PushFont( &p_fonts, 
490                   p_font_style->psz_fontname,
491                   p_font_style->i_font_size,
492                   p_font_style->i_font_color,
493                   p_font_style->i_font_alpha );
494         
495         if( p_font_style->i_style_flags & STYLE_BOLD )
496             b_bold = VLC_TRUE;
497         if( p_font_style->i_style_flags & STYLE_ITALIC )
498             b_italic = VLC_TRUE;
499         if( p_font_style->i_style_flags & STYLE_UNDERLINE )
500             b_uline = VLC_TRUE;
501     }
502     else
503     {
504         PushFont( &p_fonts, p_sys->psz_font_name, p_sys->i_font_size, p_sys->i_font_color, 0 );
505     }
506
507     while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
508     {
509         switch ( xml_ReaderNodeType( p_xml_reader ) )
510         {
511             case XML_READER_NONE:
512                 break;
513             case XML_READER_ENDELEM:
514                 psz_node = xml_ReaderName( p_xml_reader );
515                 
516                 if( psz_node )
517                 {
518                     if( !strcasecmp( "font", psz_node ) )
519                         PopFont( &p_fonts );
520                     else if( !strcasecmp( "b", psz_node ) )
521                         b_bold   = VLC_FALSE;
522                     else if( !strcasecmp( "i", psz_node ) )
523                         b_italic = VLC_FALSE;
524                     else if( !strcasecmp( "u", psz_node ) )
525                         b_uline  = VLC_FALSE;
526                     
527                     free( psz_node );
528                 }
529                 break;
530             case XML_READER_STARTELEM:
531                 psz_node = xml_ReaderName( p_xml_reader );
532                 if( psz_node )
533                 {
534                     if( !strcasecmp( "font", psz_node ) )
535                     {
536                         char *psz_fontname = NULL;
537                         int   i_font_color = 0xffffff;
538                         int   i_font_alpha = 0;
539                         int   i_font_size  = 24;
540
541                         // Default all attributes to the top font in the stack -- in case not
542                         // all attributes are specified in the sub-font
543                         if( VLC_SUCCESS == PeekFont( &p_fonts, &psz_fontname, &i_font_size, &i_font_color, &i_font_alpha ))
544                         {
545                             psz_fontname = strdup( psz_fontname );
546                         }
547
548                         while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
549                         {
550                             char *psz_name = xml_ReaderName ( p_xml_reader );
551                             char *psz_value = xml_ReaderValue ( p_xml_reader );
552
553                             if( psz_name && psz_value )
554                             {
555                                 if( !strcasecmp( "face", psz_name ) )
556                                 {
557                                     if( psz_fontname ) free( psz_fontname );
558                                     psz_fontname = strdup( psz_value );
559                                 }
560                                 else if( !strcasecmp( "size", psz_name ) )
561                                 {
562                                     if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
563                                     {
564                                         int i_value = atoi( psz_value );
565
566                                         if( ( i_value >= -5 ) && ( i_value <= 5 ) )
567                                             i_font_size += ( i_value * i_font_size ) / 10;
568                                         else if( i_value < -5 )
569                                             i_font_size = - i_value;
570                                         else if( i_value > 5 )
571                                             i_font_size = i_value;
572                                     }
573                                     else
574                                         i_font_size = atoi( psz_value );
575                                 }
576                                 else if( !strcasecmp( "color", psz_name )  &&
577                                          ( psz_value[0] == '#' ) )
578                                 {
579                                     i_font_color = strtol( psz_value+1, NULL, 16 );
580                                     i_font_color &= 0x00ffffff;
581                                 }
582                                 else if( !strcasecmp( "alpha", psz_name ) &&
583                                          ( psz_value[0] == '#' ) )
584                                 {
585                                     i_font_alpha = strtol( psz_value+1, NULL, 16 );
586                                     i_font_alpha &= 0xff;
587                                 }
588                                 free( psz_name );
589                                 free( psz_value );
590                             }
591                         }
592                         PushFont( &p_fonts, psz_fontname, i_font_size, i_font_color, i_font_alpha );
593                         free( psz_fontname );
594                     }
595                     else if( !strcasecmp( "b", psz_node ) )
596                     {
597                         b_bold = VLC_TRUE;
598                     }
599                     else if( !strcasecmp( "i", psz_node ) )
600                     {
601                         b_italic = VLC_TRUE;
602                     }
603                     else if( !strcasecmp( "u", psz_node ) )
604                     {
605                         b_uline = VLC_TRUE;
606                     }
607                     else if( !strcasecmp( "br", psz_node ) )
608                     {
609                         uint32_t i_string_length;
610
611                         ConvertToUTF16( "\n", &i_string_length, &psz_text );
612                         psz_text += i_string_length;
613
614                         (*pi_runs)++;
615
616                         if( *ppp_styles )
617                             *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
618                         else
619                             *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
620
621                         (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
622
623                         if( *ppi_run_lengths )
624                             *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
625                         else
626                             *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
627
628                         (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
629                     }
630                     free( psz_node );
631                 }
632                 break;
633             case XML_READER_TEXT:
634                 psz_node = xml_ReaderValue( p_xml_reader );
635                 if( psz_node )
636                 {
637                     uint32_t i_string_length;
638
639                     // Turn any multiple-whitespaces into single spaces
640                     char *s = strpbrk( psz_node, "\t\r\n " );
641                     while( s )
642                     {
643                         int i_whitespace = strspn( s, "\t\r\n " );
644
645                         if( i_whitespace > 1 )
646                             memmove( &s[1],
647                                      &s[i_whitespace],
648                                      strlen( s ) - i_whitespace + 1 );
649                         *s++ = ' ';
650
651                         s = strpbrk( s, "\t\r\n " );
652                     }
653
654                     ConvertToUTF16( psz_node, &i_string_length, &psz_text );
655                     psz_text += i_string_length;
656
657                     (*pi_runs)++;
658
659                     if( *ppp_styles )
660                         *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
661                     else
662                         *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
663
664                     (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
665
666                     if( *ppi_run_lengths )
667                         *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
668                     else
669                         *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
670
671                     (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
672
673                     free( psz_node );
674                 }
675                 break;
676         }
677     }
678
679     *pi_len = psz_text - psz_text_orig;
680
681     while( VLC_SUCCESS == PopFont( &p_fonts ) );
682 }
683
684 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
685                        subpicture_region_t *p_region_in )
686 {
687     int          rv = VLC_SUCCESS;
688     stream_t     *p_sub = NULL;
689     xml_t        *p_xml = NULL;
690     xml_reader_t *p_xml_reader = NULL;
691
692     if( !p_region_in || !p_region_in->psz_html )
693         return VLC_EGENERIC;
694
695     p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
696                               (uint8_t *) p_region_in->psz_html,
697                               strlen( p_region_in->psz_html ),
698                               VLC_TRUE );
699     if( p_sub )
700     {
701         p_xml = xml_Create( p_filter );
702         if( p_xml )
703         {
704             p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
705             if( p_xml_reader )
706             {
707                 UniChar    *psz_text;
708                 int         i_len;
709                 uint32_t    i_runs = 0;
710                 uint32_t   *pi_run_lengths = NULL;
711                 ATSUStyle  *pp_styles = NULL;
712
713                 psz_text = (UniChar *) calloc( strlen( p_region_in->psz_html ), sizeof( UniChar ) );
714                 if( psz_text )
715                 {
716                     uint32_t k;
717
718                     ProcessNodes( p_filter, p_xml_reader, p_region_in->p_style, psz_text,
719                                   &i_len, &i_runs, &pi_run_lengths, &pp_styles );
720
721                     p_region_out->i_x = p_region_in->i_x;
722                     p_region_out->i_y = p_region_in->i_y;
723
724                     RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs, pi_run_lengths, pp_styles);
725
726                     for( k=0; k<i_runs; k++)
727                         ATSUDisposeStyle( pp_styles[k] );
728                     free( pp_styles );
729                     free( pi_run_lengths );
730
731                     free( psz_text );
732                 }
733
734                 xml_ReaderDelete( p_xml, p_xml_reader );
735             }
736             xml_Delete( p_xml );
737         }
738         stream_Delete( p_sub );
739     }
740
741     return rv;
742 }
743
744 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
745                          offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
746 {
747     offscreen_bitmap_t *p_bitmap;
748     CGContextRef        p_context = NULL;
749
750     p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
751     if( p_bitmap )
752     {
753         p_bitmap->i_bitsPerChannel = 8;
754         p_bitmap->i_bitsPerPixel   = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
755         p_bitmap->i_bytesPerPixel  = p_bitmap->i_bitsPerPixel / 8;
756         p_bitmap->i_bytesPerRow    = i_width * p_bitmap->i_bytesPerPixel;
757
758         p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
759
760 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
761         *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
762 #else
763         *pp_colorSpace = CreateGenericRGBColorSpace();
764 #endif
765
766         if( p_bitmap->p_data && *pp_colorSpace )
767         {
768             p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
769                                 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
770                                 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
771         }
772         if( p_context )
773         {
774 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_1
775             // OS X 10.1 doesn't support weak linking of this call which is only available
776             // int 10.4 and later
777             if( CGContextSetAllowsAntialiasing != NULL )
778             {
779                 CGContextSetAllowsAntialiasing( p_context, true );
780             }
781 #endif
782         }
783         *pp_memory = p_bitmap;
784     }
785
786     return p_context;
787 }
788
789 static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len,
790                                     uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles,
791                                     int i_width, int i_height, int *pi_textblock_height )
792 {
793     offscreen_bitmap_t  *p_offScreen  = NULL;
794     CGColorSpaceRef      p_colorSpace = NULL;
795     CGContextRef         p_context = NULL;
796     
797     p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
798
799     if( p_context )
800     {
801         ATSUTextLayout p_textLayout;
802         OSStatus status = noErr;
803
804         status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
805                                                   i_runs,
806                                                   (const UniCharCount *) pi_run_lengths,
807                                                   pp_styles,
808                                                   &p_textLayout );
809         if( status == noErr )
810         {
811             // Attach our offscreen Image Graphics Context to the text style
812             // and setup the line alignment (have to specify the line width
813             // also in order for our chosen alignment to work)
814
815             Fract   alignment  = kATSUStartAlignment;
816             Fixed   line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
817
818             ATSUAttributeTag tags[]        = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
819             ByteCount sizes[]              = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
820             ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
821
822             int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
823
824             if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
825             {
826                 alignment = kATSUEndAlignment;
827             }
828             else if( i_text_align != SUBPICTURE_ALIGN_LEFT ) 
829             {
830                 alignment = kATSUCenterAlignment;
831             }
832
833             ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
834
835             // let ATSUI deal with characters not-in-our-specified-font
836             ATSUSetTransientFontMatching( p_textLayout, true );
837
838             Fixed x = Long2Fix( HORIZONTAL_MARGIN );
839             Fixed y = Long2Fix( i_height );
840
841             // Set the line-breaks and draw individual lines
842             uint32_t i_start = 0;
843             uint32_t i_end = i_text_len;
844
845             // Set up black outlining of the text -- 
846             CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
847             CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
848
849             do
850             {
851                 // ATSUBreakLine will automatically pick up any manual '\n's also
852                 status = ATSUBreakLine( p_textLayout, i_start, line_width, true, (UniCharArrayOffset *) &i_end );
853                 if( ( status == noErr ) || ( status == kATSULineBreakInWord ) )
854                 {
855                     Fixed     ascent;
856                     Fixed     descent;
857                     uint32_t  i_actualSize;
858
859                     // Come down far enough to fit the height of this line --
860                     ATSUGetLineControl( p_textLayout, i_start, kATSULineAscentTag,
861                                     sizeof( Fixed ), &ascent, (ByteCount *) &i_actualSize );
862
863                     // Quartz uses an upside-down co-ordinate space -> y values decrease as
864                     // you move down the page
865                     y -= ascent;
866
867                     // Set the outlining for this line to be dependant on the size of the line -
868                     // make it about 5% of the ascent, with a minimum at 1.0
869                     float f_thickness = FixedToFloat( ascent ) * 0.05;
870                     CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
871
872                     ATSUDrawText( p_textLayout, i_start, i_end - i_start, x, y );
873
874                     // and now prepare for the next line by coming down far enough for our
875                     // descent
876                     ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag,
877                                     sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize );
878                     y -= descent;
879
880                     i_start = i_end;
881                 }
882                 else
883                     break;
884             }
885             while( i_end < i_text_len );
886
887             *pi_textblock_height = i_height - Fix2Long( y );
888             CGContextFlush( p_context );
889
890             ATSUDisposeTextLayout( p_textLayout );
891         }
892
893         CGContextRelease( p_context );
894     }
895     if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
896
897     return p_offScreen;
898 }
899
900 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniChar *psz_utf16_str,
901                        uint32_t i_text_len, uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles )
902 {
903     offscreen_bitmap_t *p_offScreen = NULL;
904     int      i_textblock_height = 0;
905
906     int i_width = p_filter->fmt_out.video.i_visible_width;
907     int i_height = p_filter->fmt_out.video.i_visible_height;
908
909     if( psz_utf16_str != NULL )
910     {
911         int i_text_align = p_region->i_align & 0x3;
912
913         p_offScreen = Compose( i_text_align, psz_utf16_str, i_text_len,
914                                i_runs, pi_run_lengths, pp_styles,
915                                i_width, i_height, &i_textblock_height );
916     }
917
918     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
919     video_format_t fmt;
920     int x, y, i_offset, i_pitch;
921     uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
922     subpicture_region_t *p_region_tmp;
923
924     // Create a new subpicture region
925     memset( &fmt, 0, sizeof(video_format_t) );
926     fmt.i_chroma = VLC_FOURCC('Y','U','V','A');
927     fmt.i_aspect = 0;
928     fmt.i_width = fmt.i_visible_width = i_width;
929     fmt.i_height = fmt.i_visible_height = i_textblock_height + VERTICAL_MARGIN * 2;
930     fmt.i_x_offset = fmt.i_y_offset = 0;
931     p_region_tmp = spu_CreateRegion( p_filter, &fmt );
932     if( !p_region_tmp )
933     {
934         msg_Err( p_filter, "cannot allocate SPU region" );
935         return VLC_EGENERIC;
936     }
937     p_region->fmt = p_region_tmp->fmt;
938     p_region->picture = p_region_tmp->picture;
939     free( p_region_tmp );
940
941     p_dst_y = p_region->picture.Y_PIXELS;
942     p_dst_u = p_region->picture.U_PIXELS;
943     p_dst_v = p_region->picture.V_PIXELS;
944     p_dst_a = p_region->picture.A_PIXELS;
945     i_pitch = p_region->picture.A_PITCH;
946
947     i_offset = VERTICAL_MARGIN *i_pitch;
948     for( y=0; y<i_textblock_height; y++)
949     {
950         for( x=0; x<i_width; x++)
951         {
952             int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel     ];
953             int i_red   = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
954             int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
955             int i_blue  = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
956
957             i_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
958                               802 * i_blue + 4096 + 131072 ) >> 13, 235);
959             i_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
960                              3598 * i_blue + 4096 + 1048576) >> 13, 240);
961             i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
962                               -585 * i_blue + 4096 + 1048576) >> 13, 240);
963
964             p_dst_y[ i_offset + x ] = i_y;
965             p_dst_u[ i_offset + x ] = i_u;
966             p_dst_v[ i_offset + x ] = i_v;
967             p_dst_a[ i_offset + x ] = i_alpha;
968         }
969         i_offset += i_pitch;
970     }
971
972     free( p_offScreen->p_data );
973     free( p_offScreen );
974
975     return VLC_SUCCESS;
976 }