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