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