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