]> git.sesse.net Git - vlc/blob - modules/misc/quartztext.c
quartztext: don't mangle const string
[vlc] / modules / misc / quartztext.c
1 /*****************************************************************************
2  * quartztext.c : Put text on the video, using Mac OS X Quartz Engine
3  *****************************************************************************
4  * Copyright (C) 2007, 2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Bernie Purcell <bitmap@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 //////////////////////////////////////////////////////////////////////////////
25 // Preamble
26 //////////////////////////////////////////////////////////////////////////////
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_osd.h>
35 #include <vlc_stream.h>
36 #include <vlc_xml.h>
37 #include <vlc_input.h>
38
39 #include <TargetConditionals.h>
40
41 #ifdef TARGET_OS_IPHONE
42 #include <CoreText/CoreText.h>
43 #include <CoreGraphics/CoreGraphics.h>
44
45 #else
46 // Fix ourselves ColorSync headers that gets included in ApplicationServices.
47 #define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
48 #define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
49 #define __MACHINEEXCEPTIONS__
50 #include <ApplicationServices/ApplicationServices.h>
51 #endif
52
53 #define DEFAULT_FONT           "Arial Black"
54 #define DEFAULT_FONT_COLOR     0xffffff
55 #define DEFAULT_REL_FONT_SIZE  16
56
57 #define VERTICAL_MARGIN 3
58 #define HORIZONTAL_MARGIN 10
59
60 //////////////////////////////////////////////////////////////////////////////
61 // Local prototypes
62 //////////////////////////////////////////////////////////////////////////////
63 static int  Create ( vlc_object_t * );
64 static void Destroy( vlc_object_t * );
65
66 static int LoadFontsFromAttachments( filter_t *p_filter );
67
68 static int RenderText( filter_t *, subpicture_region_t *,
69                        subpicture_region_t * );
70 static int RenderHtml( filter_t *, subpicture_region_t *,
71                        subpicture_region_t * );
72
73 static int GetFontSize( filter_t *p_filter );
74 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
75                        CFMutableAttributedStringRef p_attrString  );
76
77 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
78                               bool b_bold, bool b_italic, bool b_underline,
79                               CFRange p_range, CFMutableAttributedStringRef p_attrString );
80
81 //////////////////////////////////////////////////////////////////////////////
82 // Module descriptor
83 //////////////////////////////////////////////////////////////////////////////
84
85 // The preferred way to set font style information is for it to come from the
86 // subtitle file, and for it to be rendered with RenderHtml instead of
87 // RenderText.
88 #define FONT_TEXT N_("Font")
89 #define FONT_LONGTEXT N_("Name for the font you want to use")
90 #define FONTSIZER_TEXT N_("Relative font size")
91 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
92     "fonts that will be rendered on the video. If absolute font size is set, "\
93     "relative size will be overridden." )
94 #define COLOR_TEXT N_("Text default color")
95 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
96     "the video. This must be an hexadecimal (like HTML colors). The first two "\
97     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
98     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
99
100 static const int pi_color_values[] = {
101   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
102   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
103   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
104
105 static const char *const ppsz_color_descriptions[] = {
106   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
107   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
108   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
109
110 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
111 static const char *const ppsz_sizes_text[] = {
112     N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
113
114 vlc_module_begin ()
115     set_shortname( N_("Text renderer for Mac"))
116     set_description( N_("CoreText font renderer") )
117     set_category( CAT_VIDEO )
118     set_subcategory( SUBCAT_VIDEO_SUBPIC )
119
120     add_string( "quartztext-font", DEFAULT_FONT, FONT_TEXT, FONT_LONGTEXT,
121               false )
122     add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, FONTSIZER_TEXT,
123                  FONTSIZER_LONGTEXT, false )
124         change_integer_list( pi_sizes, ppsz_sizes_text )
125     add_integer( "quartztext-color", 0x00FFFFFF, COLOR_TEXT,
126                  COLOR_LONGTEXT, false )
127         change_integer_list( pi_color_values, ppsz_color_descriptions )
128     set_capability( "text renderer", 150 )
129     add_shortcut( "text" )
130     set_callbacks( Create, Destroy )
131 vlc_module_end ()
132
133 typedef struct font_stack_t font_stack_t;
134 struct font_stack_t
135 {
136     char          *psz_name;
137     int            i_size;
138     uint32_t       i_color;            // ARGB
139
140     font_stack_t  *p_next;
141 };
142
143 typedef struct
144 {
145     int         i_font_size;
146     uint32_t    i_font_color;         /* ARGB */
147     bool  b_italic;
148     bool  b_bold;
149     bool  b_underline;
150     char       *psz_fontname;
151 } ft_style_t;
152
153 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
154 struct offscreen_bitmap_t
155 {
156     uint8_t       *p_data;
157     int            i_bitsPerChannel;
158     int            i_bitsPerPixel;
159     int            i_bytesPerPixel;
160     int            i_bytesPerRow;
161 };
162
163 //////////////////////////////////////////////////////////////////////////////
164 // filter_sys_t: quartztext local data
165 //////////////////////////////////////////////////////////////////////////////
166 // This structure is part of the video output thread descriptor.
167 // It describes the freetype specific properties of an output thread.
168 //////////////////////////////////////////////////////////////////////////////
169 struct filter_sys_t
170 {
171     char          *psz_font_name;
172     uint8_t        i_font_opacity;
173     int            i_font_color;
174     int            i_font_size;
175
176 #ifndef TARGET_OS_IPHONE
177     ATSFontContainerRef    *p_fonts;
178     int                     i_fonts;
179 #endif
180 };
181
182 //////////////////////////////////////////////////////////////////////////////
183 // Create: allocates osd-text video thread output method
184 //////////////////////////////////////////////////////////////////////////////
185 // This function allocates and initializes a Clone vout method.
186 //////////////////////////////////////////////////////////////////////////////
187 static int Create( vlc_object_t *p_this )
188 {
189     filter_t *p_filter = (filter_t *)p_this;
190     filter_sys_t *p_sys;
191
192     // Allocate structure
193     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
194     if( !p_sys )
195         return VLC_ENOMEM;
196     p_sys->psz_font_name  = var_CreateGetString( p_this, "quartztext-font" );
197     p_sys->i_font_opacity = 255;
198     p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
199     p_sys->i_font_size    = GetFontSize( p_filter );
200
201     p_filter->pf_render_text = RenderText;
202     p_filter->pf_render_html = RenderHtml;
203
204 #ifndef TARGET_OS_IPHONE
205     p_sys->p_fonts = NULL;
206     p_sys->i_fonts = 0;
207 #endif
208
209     LoadFontsFromAttachments( p_filter );
210
211     return VLC_SUCCESS;
212 }
213
214 //////////////////////////////////////////////////////////////////////////////
215 // Destroy: destroy Clone video thread output method
216 //////////////////////////////////////////////////////////////////////////////
217 // Clean up all data and library connections
218 //////////////////////////////////////////////////////////////////////////////
219 static void Destroy( vlc_object_t *p_this )
220 {
221     filter_t *p_filter = (filter_t *)p_this;
222     filter_sys_t *p_sys = p_filter->p_sys;
223 #ifndef TARGET_OS_IPHONE
224     if( p_sys->p_fonts )
225     {
226         int   k;
227
228         for( k = 0; k < p_sys->i_fonts; k++ )
229         {
230             ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
231         }
232
233         free( p_sys->p_fonts );
234     }
235 #endif
236     free( p_sys->psz_font_name );
237     free( p_sys );
238 }
239
240 //////////////////////////////////////////////////////////////////////////////
241 // Make any TTF/OTF fonts present in the attachments of the media file
242 // available to the Quartz engine for text rendering
243 //////////////////////////////////////////////////////////////////////////////
244 static int LoadFontsFromAttachments( filter_t *p_filter )
245 {
246 #ifdef TARGET_OS_IPHONE
247     VLC_UNUSED(p_filter);
248     return VLC_SUCCESS;
249 #else
250     filter_sys_t         *p_sys = p_filter->p_sys;
251     input_attachment_t  **pp_attachments;
252     int                   i_attachments_cnt;
253
254     if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
255         return VLC_EGENERIC;
256
257     p_sys->i_fonts = 0;
258     p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
259     if(! p_sys->p_fonts )
260         return VLC_ENOMEM;
261
262     for( int k = 0; k < i_attachments_cnt; k++ )
263     {
264         input_attachment_t *p_attach = pp_attachments[k];
265
266         if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
267               !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) &&    // OTF
268             p_attach->i_data > 0 && p_attach->p_data )
269         {
270             ATSFontContainerRef  container;
271
272             if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
273                                                     p_attach->i_data,
274                                                     kATSFontContextLocal,
275                                                     kATSFontFormatUnspecified,
276                                                     NULL,
277                                                     kATSOptionFlagsDefault,
278                                                     &container ))
279             {
280                 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
281             }
282         }
283         vlc_input_attachment_Delete( p_attach );
284     }
285     free( pp_attachments );
286     return VLC_SUCCESS;
287 #endif
288 }
289
290 static char *EliminateCRLF( char *psz_string )
291 {
292     char *p;
293     char *q;
294
295     for( p = psz_string; p && *p; p++ )
296     {
297         if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
298         {
299             for( q = p + 1; *q; q++ )
300                 *( q - 1 ) = *q;
301
302             *( q - 1 ) = '\0';
303         }
304     }
305     return psz_string;
306 }
307
308 // Renders a text subpicture region into another one.
309 // It is used as pf_add_string callback in the vout method by this module
310 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
311                        subpicture_region_t *p_region_in )
312 {
313     filter_sys_t *p_sys = p_filter->p_sys;
314     char         *psz_string;
315     int           i_font_alpha, i_font_size;
316     uint32_t      i_font_color;
317     bool          b_bold, b_uline, b_italic;
318     vlc_value_t val;
319     int i_scale = 1000;
320     b_bold = b_uline = b_italic = FALSE;
321
322     p_sys->i_font_size    = GetFontSize( p_filter );
323
324     // Sanity check
325     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
326     psz_string = p_region_in->psz_text;
327     if( !psz_string || !*psz_string ) return VLC_EGENERIC;
328
329     if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
330         i_scale = val.i_int;
331
332     if( p_region_in->p_style )
333     {
334         i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
335         i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
336         i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
337         if( p_region_in->p_style->i_style_flags )
338         {
339             if( p_region_in->p_style->i_style_flags & STYLE_BOLD )
340                 b_bold = TRUE;
341             if( p_region_in->p_style->i_style_flags & STYLE_ITALIC )
342                 b_italic = TRUE;
343             if( p_region_in->p_style->i_style_flags & STYLE_UNDERLINE )
344                 b_uline = TRUE;
345         }
346     }
347     else
348     {
349         i_font_color = p_sys->i_font_color;
350         i_font_alpha = 255 - p_sys->i_font_opacity;
351         i_font_size  = p_sys->i_font_size;
352     }
353
354     if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
355
356     if( i_font_size <= 0 )
357     {
358         msg_Warn( p_filter, "invalid fontsize, using 12" );
359         if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
360             i_font_size = 12 * val.i_int / 1000;
361         else
362             i_font_size = 12;
363     }
364
365     p_region_out->i_x = p_region_in->i_x;
366     p_region_out->i_y = p_region_in->i_y;
367
368     CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
369
370     if( p_attrString )
371     {
372         CFStringRef   p_cfString;
373         int           len;
374
375         EliminateCRLF( psz_string);
376         p_cfString = CFStringCreateWithCString( NULL, psz_string, kCFStringEncodingUTF8 );
377         CFAttributedStringReplaceString( p_attrString, CFRangeMake(0, 0), p_cfString );
378         CFRelease( p_cfString );
379         len = CFAttributedStringGetLength( p_attrString );
380
381         setFontAttibutes( p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline,
382                                              CFRangeMake( 0, len ), p_attrString);
383
384         RenderYUVA( p_filter, p_region_out, p_attrString );
385         CFRelease( p_attrString );
386     }
387
388     return VLC_SUCCESS;
389 }
390
391
392 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
393                      uint32_t i_color )
394 {
395     font_stack_t *p_new;
396
397     if( !p_font )
398         return VLC_EGENERIC;
399
400     p_new = malloc( sizeof( font_stack_t ) );
401     if( ! p_new )
402         return VLC_ENOMEM;
403
404     p_new->p_next = NULL;
405
406     if( psz_name )
407         p_new->psz_name = strdup( psz_name );
408     else
409         p_new->psz_name = NULL;
410
411     p_new->i_size   = i_size;
412     p_new->i_color  = i_color;
413
414     if( !*p_font )
415     {
416         *p_font = p_new;
417     }
418     else
419     {
420         font_stack_t *p_last;
421
422         for( p_last = *p_font;
423              p_last->p_next;
424              p_last = p_last->p_next )
425         ;
426
427         p_last->p_next = p_new;
428     }
429     return VLC_SUCCESS;
430 }
431
432 static int PopFont( font_stack_t **p_font )
433 {
434     font_stack_t *p_last, *p_next_to_last;
435
436     if( !p_font || !*p_font )
437         return VLC_EGENERIC;
438
439     p_next_to_last = NULL;
440     for( p_last = *p_font;
441          p_last->p_next;
442          p_last = p_last->p_next )
443     {
444         p_next_to_last = p_last;
445     }
446
447     if( p_next_to_last )
448         p_next_to_last->p_next = NULL;
449     else
450         *p_font = NULL;
451
452     free( p_last->psz_name );
453     free( p_last );
454
455     return VLC_SUCCESS;
456 }
457
458 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
459                      uint32_t *i_color )
460 {
461     font_stack_t *p_last;
462
463     if( !p_font || !*p_font )
464         return VLC_EGENERIC;
465
466     for( p_last=*p_font;
467          p_last->p_next;
468          p_last=p_last->p_next )
469     ;
470
471     *psz_name = p_last->psz_name;
472     *i_size   = p_last->i_size;
473     *i_color  = p_last->i_color;
474
475     return VLC_SUCCESS;
476 }
477
478 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
479                                   font_stack_t **p_fonts, int i_scale )
480 {
481     int        rv;
482     char      *psz_fontname = NULL;
483     uint32_t   i_font_color = 0xffffff;
484     int        i_font_alpha = 0;
485     int        i_font_size  = 24;
486     const char *attr;
487
488     // Default all attributes to the top font in the stack -- in case not
489     // all attributes are specified in the sub-font
490     if( VLC_SUCCESS == PeekFont( p_fonts,
491                                  &psz_fontname,
492                                  &i_font_size,
493                                  &i_font_color ))
494     {
495         psz_fontname = strdup( psz_fontname );
496         i_font_size = i_font_size * 1000 / i_scale;
497     }
498     i_font_alpha = (i_font_color >> 24) & 0xff;
499     i_font_color &= 0x00ffffff;
500
501     while ( (attr = xml_ReaderNextAttr( p_xml_reader )) )
502     {
503         char *psz_value = xml_ReaderValue( p_xml_reader );
504
505         if( !psz_value )
506             continue;
507
508         if( !strcasecmp( "face", attr ) )
509         {
510             free( psz_fontname );
511             psz_fontname = strdup( psz_value );
512         }
513         else if( !strcasecmp( "size", attr ) )
514         {
515             if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
516             {
517                 int i_value = atoi( psz_value );
518
519                 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
520                     i_font_size += ( i_value * i_font_size ) / 10;
521                 else if( i_value < -5 )
522                     i_font_size = - i_value;
523                 else if( i_value > 5 )
524                     i_font_size = i_value;
525             }
526             else
527                 i_font_size = atoi( psz_value );
528         }
529         else if( !strcasecmp( "color", attr )  &&
530                  ( psz_value[0] == '#' ) )
531         {
532             i_font_color = strtol( psz_value + 1, NULL, 16 );
533             i_font_color &= 0x00ffffff;
534         }
535         else if( !strcasecmp( "alpha", attr ) &&
536                  ( psz_value[0] == '#' ) )
537         {
538             i_font_alpha = strtol( psz_value + 1, NULL, 16 );
539             i_font_alpha &= 0xff;
540         }
541
542         free( psz_value );
543     }
544     rv = PushFont( p_fonts,
545                    psz_fontname,
546                    i_font_size * i_scale / 1000,
547                    (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
548
549     free( psz_fontname );
550
551     return rv;
552 }
553
554 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
555         bool b_bold, bool b_italic, bool b_underline,
556         CFRange p_range, CFMutableAttributedStringRef p_attrString )
557 {
558     CFStringRef p_cfString;
559     CTFontRef   p_font;
560
561     // Handle font name and size
562     p_cfString = CFStringCreateWithCString( NULL,
563                                             psz_fontname,
564                                             kCFStringEncodingUTF8 );
565     p_font     = CTFontCreateWithName( p_cfString,
566                                        (float)i_font_size,
567                                        NULL );
568     CFRelease( p_cfString );
569     CFAttributedStringSetAttribute( p_attrString,
570                                     p_range,
571                                     kCTFontAttributeName,
572                                     p_font );
573     CFRelease( p_font );
574
575     // Handle Underline
576     SInt32 _uline;
577     if( b_underline )
578         _uline = kCTUnderlineStyleSingle;
579     else
580         _uline = kCTUnderlineStyleNone;
581
582     CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
583     CFAttributedStringSetAttribute( p_attrString,
584                                     p_range,
585                                     kCTUnderlineStyleAttributeName,
586                                     underline );
587     CFRelease( underline );
588
589     // Handle Bold
590     float _weight;
591     if( b_bold )
592         _weight = 0.5;
593     else
594         _weight = 0.0;
595
596     CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
597     CFAttributedStringSetAttribute( p_attrString,
598                                     p_range,
599                                     kCTFontWeightTrait,
600                                     weight );
601     CFRelease( weight );
602
603     // Handle Italic
604     float _slant;
605     if( b_italic )
606         _slant = 1.0;
607     else
608         _slant = 0.0;
609
610     CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
611     CFAttributedStringSetAttribute( p_attrString,
612                                     p_range,
613                                     kCTFontSlantTrait,
614                                     slant );
615     CFRelease( slant );
616
617     // Handle foreground colour
618     CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
619     CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
620                              (float)((i_font_color & 0x0000ff00) >>  8) / 255.0,
621                              (float)((i_font_color & 0x000000ff)      ) / 255.0,
622                              (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
623     CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
624     CGColorSpaceRelease(rgbColorSpace);
625
626     CFAttributedStringSetAttribute( p_attrString,
627                                     p_range,
628                                     kCTForegroundColorAttributeName,
629                                     fg_text );
630     CFRelease( fg_text );
631
632 }
633
634 static void GetAttrStrFromFontStack( font_stack_t **p_fonts,
635         bool b_bold, bool b_italic, bool b_uline,
636         CFRange p_range, CFMutableAttributedStringRef p_attrString )
637 {
638     char       *psz_fontname = NULL;
639     int         i_font_size  = 0;
640     uint32_t    i_font_color = 0;
641
642     if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
643                                  &i_font_color ))
644     {
645         setFontAttibutes( psz_fontname,
646                           i_font_size,
647                           i_font_color,
648                           b_bold, b_italic, b_uline,
649                           p_range,
650                           p_attrString );
651     }
652 }
653
654 static int ProcessNodes( filter_t *p_filter,
655                          xml_reader_t *p_xml_reader,
656                          text_style_t *p_font_style,
657                          CFMutableAttributedStringRef p_attrString )
658 {
659     int           rv             = VLC_SUCCESS;
660     filter_sys_t *p_sys          = p_filter->p_sys;
661     font_stack_t *p_fonts        = NULL;
662     vlc_value_t   val;
663     int           i_scale        = 1000;
664
665     int type;
666     const char *node;
667
668     bool b_italic = false;
669     bool b_bold   = false;
670     bool b_uline  = false;
671
672     if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
673         i_scale = val.i_int;
674
675     if( p_font_style )
676     {
677         rv = PushFont( &p_fonts,
678                p_font_style->psz_fontname,
679                p_font_style->i_font_size * i_scale / 1000,
680                (p_font_style->i_font_color & 0xffffff) |
681                    ((p_font_style->i_font_alpha & 0xff) << 24) );
682
683         if( p_font_style->i_style_flags & STYLE_BOLD )
684             b_bold = true;
685         if( p_font_style->i_style_flags & STYLE_ITALIC )
686             b_italic = true;
687         if( p_font_style->i_style_flags & STYLE_UNDERLINE )
688             b_uline = true;
689     }
690     else
691     {
692         rv = PushFont( &p_fonts,
693                        p_sys->psz_font_name,
694                        p_sys->i_font_size,
695                        p_sys->i_font_color );
696     }
697     if( rv != VLC_SUCCESS )
698         return rv;
699
700     while ( ( type = xml_ReaderNextNode( p_xml_reader, &node ) ) > 0 )
701     {
702         switch ( type )
703         {
704             case XML_READER_ENDELEM:
705                 if( !strcasecmp( "font", node ) )
706                     PopFont( &p_fonts );
707                 else if( !strcasecmp( "b", node ) )
708                     b_bold   = false;
709                 else if( !strcasecmp( "i", node ) )
710                     b_italic = false;
711                 else if( !strcasecmp( "u", node ) )
712                     b_uline  = false;
713
714                 break;
715             case XML_READER_STARTELEM:
716                 if( !strcasecmp( "font", node ) )
717                     rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
718                 else if( !strcasecmp( "b", node ) )
719                     b_bold = true;
720                 else if( !strcasecmp( "i", node ) )
721                     b_italic = true;
722                 else if( !strcasecmp( "u", node ) )
723                     b_uline = true;
724                 else if( !strcasecmp( "br", node ) )
725                 {
726                     CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
727                     CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), CFSTR("\n") );
728
729                     GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
730                                              CFRangeMake( 0, 1 ),
731                                              p_attrnode );
732                     CFAttributedStringReplaceAttributedString( p_attrString,
733                                     CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
734                                     p_attrnode);
735                     CFRelease( p_attrnode );
736                 }
737                 break;
738             case XML_READER_TEXT:
739             {
740                 CFStringRef   p_cfString;
741                 int           len;
742
743                 // Turn any multiple-whitespaces into single spaces
744                 char *dup = strdup( node );
745                 if( !dup )
746                     break;
747                 char *s = strpbrk( dup, "\t\r\n " );
748                 while( s )
749                 {
750                     int i_whitespace = strspn( s, "\t\r\n " );
751
752                     if( i_whitespace > 1 )
753                         memmove( &s[1],
754                                  &s[i_whitespace],
755                                  strlen( s ) - i_whitespace + 1 );
756                     *s++ = ' ';
757
758                     s = strpbrk( s, "\t\r\n " );
759                 }
760
761
762                 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
763                 p_cfString = CFStringCreateWithCString( NULL, dup, kCFStringEncodingUTF8 );
764                 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), p_cfString );
765                 CFRelease( p_cfString );
766                 len = CFAttributedStringGetLength( p_attrnode );
767
768                 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
769                                          CFRangeMake( 0, len ),
770                                          p_attrnode );
771
772                 CFAttributedStringReplaceAttributedString( p_attrString,
773                                 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
774                                 p_attrnode);
775                 CFRelease( p_attrnode );
776
777                 free( dup );
778                 break;
779             }
780         }
781     }
782
783     while( VLC_SUCCESS == PopFont( &p_fonts ) );
784
785     return rv;
786 }
787
788 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
789                        subpicture_region_t *p_region_in )
790 {
791     int          rv = VLC_SUCCESS;
792     stream_t     *p_sub = NULL;
793     xml_t        *p_xml = NULL;
794     xml_reader_t *p_xml_reader = NULL;
795
796     if( !p_region_in || !p_region_in->psz_html )
797         return VLC_EGENERIC;
798
799     /* Reset the default fontsize in case screen metrics have changed */
800     p_filter->p_sys->i_font_size = GetFontSize( p_filter );
801
802     p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
803                               (uint8_t *) p_region_in->psz_html,
804                               strlen( p_region_in->psz_html ),
805                               true );
806     if( p_sub )
807     {
808         p_xml = xml_Create( p_filter );
809         if( p_xml )
810         {
811             bool b_karaoke = false;
812
813             p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
814             if( p_xml_reader )
815             {
816                 /* Look for Root Node */
817                 const char *name;
818                 if( xml_ReaderNextNode( p_xml_reader, &name )
819                         == XML_READER_STARTELEM )
820                 {
821                     if( !strcasecmp( "karaoke", name ) )
822                     {
823                         /* We're going to have to render the text a number
824                          * of times to show the progress marker on the text.
825                          */
826                         var_SetBool( p_filter, "text-rerender", true );
827                         b_karaoke = true;
828                     }
829                     else if( !strcasecmp( "text", name ) )
830                     {
831                         b_karaoke = false;
832                     }
833                     else
834                     {
835                         /* Only text and karaoke tags are supported */
836                         xml_ReaderDelete( p_xml_reader );
837                         p_xml_reader = NULL;
838                         rv = VLC_EGENERIC;
839                     }
840                 }
841             }
842
843             if( p_xml_reader )
844             {
845                 int         i_len;
846
847                 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
848                 rv = ProcessNodes( p_filter, p_xml_reader,
849                               p_region_in->p_style, p_attrString );
850
851                 i_len = CFAttributedStringGetLength( p_attrString );
852
853                 p_region_out->i_x = p_region_in->i_x;
854                 p_region_out->i_y = p_region_in->i_y;
855
856                 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
857                 {
858                     RenderYUVA( p_filter, p_region_out, p_attrString );
859                 }
860                 CFRelease(p_attrString);
861
862                 xml_ReaderDelete( p_xml_reader );
863             }
864             xml_Delete( p_xml );
865         }
866         stream_Delete( p_sub );
867     }
868
869     return rv;
870 }
871
872 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
873                          offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
874 {
875     offscreen_bitmap_t *p_bitmap;
876     CGContextRef        p_context = NULL;
877
878     p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
879     if( p_bitmap )
880     {
881         p_bitmap->i_bitsPerChannel = 8;
882         p_bitmap->i_bitsPerPixel   = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
883         p_bitmap->i_bytesPerPixel  = p_bitmap->i_bitsPerPixel / 8;
884         p_bitmap->i_bytesPerRow    = i_width * p_bitmap->i_bytesPerPixel;
885
886         p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
887
888         *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
889
890         if( p_bitmap->p_data && *pp_colorSpace )
891         {
892             p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
893                                 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
894                                 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
895         }
896         if( p_context )
897         {
898             if( CGContextSetAllowsAntialiasing != NULL )
899             {
900                 CGContextSetAllowsAntialiasing( p_context, true );
901             }
902         }
903         *pp_memory = p_bitmap;
904     }
905
906     return p_context;
907 }
908
909 static offscreen_bitmap_t *Compose( int i_text_align,
910                                     CFMutableAttributedStringRef p_attrString,
911                                     unsigned i_width,
912                                     unsigned i_height,
913                                     unsigned *pi_textblock_height )
914 {
915     offscreen_bitmap_t  *p_offScreen  = NULL;
916     CGColorSpaceRef      p_colorSpace = NULL;
917     CGContextRef         p_context = NULL;
918
919     p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
920
921     *pi_textblock_height = 0;
922     if( p_context )
923     {
924         float horiz_flush;
925
926         CGContextSetTextMatrix( p_context, CGAffineTransformIdentity );
927
928         if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
929             horiz_flush = 1.0;
930         else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
931             horiz_flush = 0.5;
932         else
933             horiz_flush = 0.0;
934
935         // Create the framesetter with the attributed string.
936         CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
937         if( framesetter )
938         {
939             CTFrameRef frame;
940             CGMutablePathRef p_path = CGPathCreateMutable();
941             CGRect p_bounds = CGRectMake( (float)HORIZONTAL_MARGIN,
942                                           (float)VERTICAL_MARGIN,
943                                           (float)(i_width  - HORIZONTAL_MARGIN*2),
944                                           (float)(i_height - VERTICAL_MARGIN  *2));
945             CGPathAddRect( p_path, NULL, p_bounds );
946
947             // Create the frame and draw it into the graphics context
948             frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
949
950             CGPathRelease(p_path);
951
952             // Set up black outlining of the text --
953             CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
954             CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
955
956             if( frame != NULL )
957             {
958                 CFArrayRef lines;
959                 CGPoint    penPosition;
960
961                 lines = CTFrameGetLines( frame );
962                 penPosition.y = i_height;
963                 for (int i=0; i<CFArrayGetCount( lines ); i++)
964                 {
965                     CGFloat  ascent, descent, leading;
966
967                     CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
968                     CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
969
970                     // Set the outlining for this line to be dependant on the size of the line -
971                     // make it about 5% of the ascent, with a minimum at 1.0
972                     float f_thickness = ascent * 0.05;
973                     CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
974
975                     double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width  - HORIZONTAL_MARGIN*2));
976                     penPosition.x = HORIZONTAL_MARGIN + penOffset;
977                     penPosition.y -= ascent;
978                     CGContextSetTextPosition( p_context, penPosition.x, penPosition.y );
979                     CTLineDraw( line, p_context );
980                     penPosition.y -= descent + leading;
981
982                 }
983                 *pi_textblock_height = i_height - penPosition.y;
984
985                 CFRelease(frame);
986             }
987             CFRelease(framesetter);
988         }
989         CGContextFlush( p_context );
990         CGContextRelease( p_context );
991     }
992     if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
993
994     return p_offScreen;
995 }
996
997 static int GetFontSize( filter_t *p_filter )
998 {
999     return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
1000 }
1001
1002 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
1003                        CFMutableAttributedStringRef p_attrString )
1004 {
1005     offscreen_bitmap_t *p_offScreen = NULL;
1006     unsigned      i_textblock_height = 0;
1007
1008     unsigned i_width = p_filter->fmt_out.video.i_visible_width;
1009     unsigned i_height = p_filter->fmt_out.video.i_visible_height;
1010     unsigned i_text_align = p_region->i_align & 0x3;
1011
1012     if( !p_attrString )
1013     {
1014         msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1015         return VLC_EGENERIC;
1016     }
1017
1018     p_offScreen = Compose( i_text_align, p_attrString,
1019                            i_width, i_height, &i_textblock_height );
1020
1021     if( !p_offScreen )
1022     {
1023         msg_Err( p_filter, "No offscreen buffer" );
1024         return VLC_EGENERIC;
1025     }
1026
1027     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1028     video_format_t fmt;
1029     int i_offset;
1030     unsigned x, y, i_pitch;
1031     uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1032
1033     // Create a new subpicture region
1034     memset( &fmt, 0, sizeof(video_format_t) );
1035     fmt.i_chroma = VLC_CODEC_YUVA;
1036     fmt.i_width = fmt.i_visible_width = i_width;
1037     fmt.i_height = fmt.i_visible_height = __MIN( i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1038     fmt.i_x_offset = fmt.i_y_offset = 0;
1039
1040     p_region->p_picture = picture_NewFromFormat( &fmt );
1041     if( !p_region->p_picture )
1042         return VLC_EGENERIC;
1043     p_region->fmt = fmt;
1044
1045     p_dst_y = p_region->p_picture->Y_PIXELS;
1046     p_dst_u = p_region->p_picture->U_PIXELS;
1047     p_dst_v = p_region->p_picture->V_PIXELS;
1048     p_dst_a = p_region->p_picture->A_PIXELS;
1049     i_pitch = p_region->p_picture->A_PITCH;
1050
1051     i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1052     for( y = 0; y < fmt.i_height; y++)
1053     {
1054         for( x = 0; x < fmt.i_width; x++)
1055         {
1056             int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel     ];
1057             int i_red   = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1058             int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1059             int i_blue  = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1060
1061             i_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
1062                               802 * i_blue + 4096 + 131072 ) >> 13, 235);
1063             i_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
1064                              3598 * i_blue + 4096 + 1048576) >> 13, 240);
1065             i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1066                               -585 * i_blue + 4096 + 1048576) >> 13, 240);
1067
1068             p_dst_y[ i_offset + x ] = i_y;
1069             p_dst_u[ i_offset + x ] = i_u;
1070             p_dst_v[ i_offset + x ] = i_v;
1071             p_dst_a[ i_offset + x ] = i_alpha;
1072         }
1073         i_offset += i_pitch;
1074     }
1075
1076     free( p_offScreen->p_data );
1077     free( p_offScreen );
1078
1079     return VLC_SUCCESS;
1080 }