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