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