]> git.sesse.net Git - vlc/blob - modules/misc/quartztext.c
Lua: fix #3492, and commas around the name and longname.
[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 overriden." )
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_thread_t       *p_input;
236     input_attachment_t  **pp_attachments;
237     int                   i_attachments_cnt;
238     int                   k;
239     int                   rv = VLC_SUCCESS;
240
241     p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
242     if( ! p_input )
243         return VLC_EGENERIC;
244
245     if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
246     {
247         vlc_object_release(p_input);
248         return VLC_EGENERIC;
249     }
250
251     p_sys->i_fonts = 0;
252     p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
253     if(! p_sys->p_fonts )
254         rv = VLC_ENOMEM;
255
256     for( k = 0; k < i_attachments_cnt; k++ )
257     {
258         input_attachment_t *p_attach = pp_attachments[k];
259
260         if( p_sys->p_fonts )
261         {
262             if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
263                  !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) &&    // OTF
264                ( p_attach->i_data > 0 ) &&
265                ( p_attach->p_data != NULL ) )
266             {
267                 ATSFontContainerRef  container;
268
269                 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
270                                                         p_attach->i_data,
271                                                         kATSFontContextLocal,
272                                                         kATSFontFormatUnspecified,
273                                                         NULL,
274                                                         kATSOptionFlagsDefault,
275                                                         &container ))
276                 {
277                     p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
278                 }
279             }
280         }
281         vlc_input_attachment_Delete( p_attach );
282     }
283     free( pp_attachments );
284
285     vlc_object_release(p_input);
286
287     return rv;
288 }
289
290 static char *EliminateCRLF( char *psz_string )
291 {
292     char *p;
293     char *q;
294
295     for( p = psz_string; p && *p; p++ )
296     {
297         if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
298         {
299             for( q = p + 1; *q; q++ )
300                 *( q - 1 ) = *q;
301
302             *( q - 1 ) = '\0';
303         }
304     }
305     return psz_string;
306 }
307
308 // Renders a text subpicture region into another one.
309 // It is used as pf_add_string callback in the vout method by this module
310 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
311                        subpicture_region_t *p_region_in )
312 {
313     filter_sys_t *p_sys = p_filter->p_sys;
314     char         *psz_string;
315     int           i_font_alpha, i_font_size;
316     uint32_t      i_font_color;
317     bool          b_bold, b_uline, b_italic;
318     vlc_value_t val;
319     int i_scale = 1000;
320     b_bold = b_uline = b_italic = FALSE;
321
322     p_sys->i_font_size    = GetFontSize( p_filter );
323
324     // Sanity check
325     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
326     psz_string = p_region_in->psz_text;
327     if( !psz_string || !*psz_string ) return VLC_EGENERIC;
328
329     if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
330         i_scale = val.i_int;
331
332     if( p_region_in->p_style )
333     {
334         i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
335         i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
336         i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
337         if( p_region_in->p_style->i_style_flags )
338         {
339             if( p_region_in->p_style->i_style_flags & STYLE_BOLD )
340                 b_bold = TRUE;
341             if( p_region_in->p_style->i_style_flags & STYLE_ITALIC )
342                 b_italic = TRUE;
343             if( p_region_in->p_style->i_style_flags & STYLE_UNDERLINE )
344                 b_uline = TRUE;
345         }
346     }
347     else
348     {
349         i_font_color = p_sys->i_font_color;
350         i_font_alpha = 255 - p_sys->i_font_opacity;
351         i_font_size  = p_sys->i_font_size;
352     }
353
354     if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
355
356     if( i_font_size <= 0 )
357     {
358         msg_Warn( p_filter, "invalid fontsize, using 12" );
359         if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
360             i_font_size = 12 * val.i_int / 1000;
361         else
362             i_font_size = 12;
363     }
364
365     p_region_out->i_x = p_region_in->i_x;
366     p_region_out->i_y = p_region_in->i_y;
367
368     CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
369
370     if( p_attrString )
371     {
372         CFStringRef   p_cfString;
373         int           len;
374
375         EliminateCRLF( psz_string);
376         p_cfString = CFStringCreateWithCString( NULL, psz_string, kCFStringEncodingUTF8 );
377         CFAttributedStringReplaceString( p_attrString, CFRangeMake(0, 0), p_cfString );
378         CFRelease( p_cfString );
379         len = CFAttributedStringGetLength( p_attrString );
380
381         setFontAttibutes( p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline,
382                                              CFRangeMake( 0, len ), p_attrString);
383
384         RenderYUVA( p_filter, p_region_out, p_attrString );
385     }
386     CFRelease(p_attrString);
387
388     return VLC_SUCCESS;
389 }
390
391
392 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
393                      uint32_t i_color )
394 {
395     font_stack_t *p_new;
396
397     if( !p_font )
398         return VLC_EGENERIC;
399
400     p_new = malloc( sizeof( font_stack_t ) );
401     if( ! p_new )
402         return VLC_ENOMEM;
403
404     p_new->p_next = NULL;
405
406     if( psz_name )
407         p_new->psz_name = strdup( psz_name );
408     else
409         p_new->psz_name = NULL;
410
411     p_new->i_size   = i_size;
412     p_new->i_color  = i_color;
413
414     if( !*p_font )
415     {
416         *p_font = p_new;
417     }
418     else
419     {
420         font_stack_t *p_last;
421
422         for( p_last = *p_font;
423              p_last->p_next;
424              p_last = p_last->p_next )
425         ;
426
427         p_last->p_next = p_new;
428     }
429     return VLC_SUCCESS;
430 }
431
432 static int PopFont( font_stack_t **p_font )
433 {
434     font_stack_t *p_last, *p_next_to_last;
435
436     if( !p_font || !*p_font )
437         return VLC_EGENERIC;
438
439     p_next_to_last = NULL;
440     for( p_last = *p_font;
441          p_last->p_next;
442          p_last = p_last->p_next )
443     {
444         p_next_to_last = p_last;
445     }
446
447     if( p_next_to_last )
448         p_next_to_last->p_next = NULL;
449     else
450         *p_font = NULL;
451
452     free( p_last->psz_name );
453     free( p_last );
454
455     return VLC_SUCCESS;
456 }
457
458 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
459                      uint32_t *i_color )
460 {
461     font_stack_t *p_last;
462
463     if( !p_font || !*p_font )
464         return VLC_EGENERIC;
465
466     for( p_last=*p_font;
467          p_last->p_next;
468          p_last=p_last->p_next )
469     ;
470
471     *psz_name = p_last->psz_name;
472     *i_size   = p_last->i_size;
473     *i_color  = p_last->i_color;
474
475     return VLC_SUCCESS;
476 }
477
478 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
479                                   font_stack_t **p_fonts, int i_scale )
480 {
481     int        rv;
482     char      *psz_fontname = NULL;
483     uint32_t   i_font_color = 0xffffff;
484     int        i_font_alpha = 0;
485     int        i_font_size  = 24;
486
487     // Default all attributes to the top font in the stack -- in case not
488     // all attributes are specified in the sub-font
489     if( VLC_SUCCESS == PeekFont( p_fonts,
490                                  &psz_fontname,
491                                  &i_font_size,
492                                  &i_font_color ))
493     {
494         psz_fontname = strdup( psz_fontname );
495         i_font_size = i_font_size * 1000 / i_scale;
496     }
497     i_font_alpha = (i_font_color >> 24) & 0xff;
498     i_font_color &= 0x00ffffff;
499
500     while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
501     {
502         char *psz_name = xml_ReaderName( p_xml_reader );
503         char *psz_value = xml_ReaderValue( p_xml_reader );
504
505         if( psz_name && psz_value )
506         {
507             if( !strcasecmp( "face", psz_name ) )
508             {
509                 free( psz_fontname );
510                 psz_fontname = strdup( psz_value );
511             }
512             else if( !strcasecmp( "size", psz_name ) )
513             {
514                 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
515                 {
516                     int i_value = atoi( psz_value );
517
518                     if( ( i_value >= -5 ) && ( i_value <= 5 ) )
519                         i_font_size += ( i_value * i_font_size ) / 10;
520                     else if( i_value < -5 )
521                         i_font_size = - i_value;
522                     else if( i_value > 5 )
523                         i_font_size = i_value;
524                 }
525                 else
526                     i_font_size = atoi( psz_value );
527             }
528             else if( !strcasecmp( "color", psz_name )  &&
529                      ( psz_value[0] == '#' ) )
530             {
531                 i_font_color = strtol( psz_value + 1, NULL, 16 );
532                 i_font_color &= 0x00ffffff;
533             }
534             else if( !strcasecmp( "alpha", psz_name ) &&
535                      ( psz_value[0] == '#' ) )
536             {
537                 i_font_alpha = strtol( psz_value + 1, NULL, 16 );
538                 i_font_alpha &= 0xff;
539             }
540             free( psz_name );
541             free( psz_value );
542         }
543     }
544     rv = PushFont( p_fonts,
545                    psz_fontname,
546                    i_font_size * i_scale / 1000,
547                    (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
548
549     free( psz_fontname );
550
551     return rv;
552 }
553
554 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
555         bool b_bold, bool b_italic, bool b_underline,
556         CFRange p_range, CFMutableAttributedStringRef p_attrString )
557 {
558     CFStringRef p_cfString;
559     CTFontRef   p_font;
560     
561     // Handle font name and size
562     p_cfString = CFStringCreateWithCString( NULL,
563                                             psz_fontname,
564                                             kCFStringEncodingUTF8 );
565     p_font     = CTFontCreateWithName( p_cfString,
566                                        (float)i_font_size,
567                                        NULL );
568     CFRelease( p_cfString );
569     CFAttributedStringSetAttribute( p_attrString,
570                                     p_range,
571                                     kCTFontAttributeName,
572                                     p_font );
573     CFRelease( p_font );
574
575     // Handle Underline
576     SInt32 _uline;
577     if( b_underline )
578         _uline = kCTUnderlineStyleSingle;
579     else
580         _uline = kCTUnderlineStyleNone;
581
582     CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
583     CFAttributedStringSetAttribute( p_attrString,
584                                     p_range,
585                                     kCTUnderlineStyleAttributeName,
586                                     underline );
587     CFRelease( underline );
588
589     // Handle Bold
590     float _weight;
591     if( b_bold )
592         _weight = 0.5;
593     else
594         _weight = 0.0;
595
596     CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
597     CFAttributedStringSetAttribute( p_attrString,
598                                     p_range,
599                                     kCTFontWeightTrait,
600                                     weight );
601     CFRelease( weight );
602
603     // Handle Italic
604     float _slant;
605     if( b_italic )
606         _slant = 1.0;
607     else
608         _slant = 0.0;
609
610     CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
611     CFAttributedStringSetAttribute( p_attrString,
612                                     p_range,
613                                     kCTFontSlantTrait,
614                                     slant );
615     CFRelease( slant );
616
617     // Handle foreground colour
618     CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
619     CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
620                              (float)((i_font_color & 0x0000ff00) >>  8) / 255.0,
621                              (float)((i_font_color & 0x000000ff)      ) / 255.0,
622                              (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
623     CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
624     CGColorSpaceRelease(rgbColorSpace);
625
626     CFAttributedStringSetAttribute( p_attrString,
627                                     p_range,
628                                     kCTForegroundColorAttributeName,
629                                     fg_text );
630     CFRelease( fg_text );
631
632 }
633
634 static void GetAttrStrFromFontStack( font_stack_t **p_fonts,
635         bool b_bold, bool b_italic, bool b_uline,
636         CFRange p_range, CFMutableAttributedStringRef p_attrString )
637 {
638     char       *psz_fontname = NULL;
639     int         i_font_size  = 0;
640     uint32_t    i_font_color = 0;
641
642     if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
643                                  &i_font_color ))
644     {
645         setFontAttibutes( psz_fontname,
646                           i_font_size,
647                           i_font_color,
648                           b_bold, b_italic, b_uline,
649                           p_range,
650                           p_attrString );
651     }
652 }
653
654 static int ProcessNodes( filter_t *p_filter,
655                          xml_reader_t *p_xml_reader,
656                          text_style_t *p_font_style,
657                          CFMutableAttributedStringRef p_attrString )
658 {
659     int           rv             = VLC_SUCCESS;
660     filter_sys_t *p_sys          = p_filter->p_sys;
661     font_stack_t *p_fonts        = NULL;
662     vlc_value_t   val;
663     int           i_scale        = 1000;
664
665     char *psz_node  = NULL;
666
667     bool b_italic = false;
668     bool b_bold   = false;
669     bool b_uline  = false;
670
671     if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
672         i_scale = val.i_int;
673
674     if( p_font_style )
675     {
676         rv = PushFont( &p_fonts,
677                p_font_style->psz_fontname,
678                p_font_style->i_font_size * i_scale / 1000,
679                (p_font_style->i_font_color & 0xffffff) |
680                    ((p_font_style->i_font_alpha & 0xff) << 24) );
681
682         if( p_font_style->i_style_flags & STYLE_BOLD )
683             b_bold = true;
684         if( p_font_style->i_style_flags & STYLE_ITALIC )
685             b_italic = true;
686         if( p_font_style->i_style_flags & STYLE_UNDERLINE )
687             b_uline = true;
688     }
689     else
690     {
691         rv = PushFont( &p_fonts,
692                        p_sys->psz_font_name,
693                        p_sys->i_font_size,
694                        p_sys->i_font_color );
695     }
696     if( rv != VLC_SUCCESS )
697         return rv;
698
699     while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
700     {
701         switch ( xml_ReaderNodeType( p_xml_reader ) )
702         {
703             case XML_READER_NONE:
704                 break;
705             case XML_READER_ENDELEM:
706                 psz_node = xml_ReaderName( p_xml_reader );
707
708                 if( psz_node )
709                 {
710                     if( !strcasecmp( "font", psz_node ) )
711                         PopFont( &p_fonts );
712                     else if( !strcasecmp( "b", psz_node ) )
713                         b_bold   = false;
714                     else if( !strcasecmp( "i", psz_node ) )
715                         b_italic = false;
716                     else if( !strcasecmp( "u", psz_node ) )
717                         b_uline  = false;
718
719                     free( psz_node );
720                 }
721                 break;
722             case XML_READER_STARTELEM:
723                 psz_node = xml_ReaderName( p_xml_reader );
724                 if( psz_node )
725                 {
726                     if( !strcasecmp( "font", psz_node ) )
727                         rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
728                     else if( !strcasecmp( "b", psz_node ) )
729                         b_bold = true;
730                     else if( !strcasecmp( "i", psz_node ) )
731                         b_italic = true;
732                     else if( !strcasecmp( "u", psz_node ) )
733                         b_uline = true;
734                     else if( !strcasecmp( "br", psz_node ) )
735                     {
736                         CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
737                         CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), CFSTR("\n") );
738
739                         GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
740                                                  CFRangeMake( 0, 1 ),
741                                                  p_attrnode );
742                         CFAttributedStringReplaceAttributedString( p_attrString,
743                                         CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
744                                         p_attrnode);
745                         CFRelease( p_attrnode );
746                     }
747                     free( psz_node );
748                 }
749                 break;
750             case XML_READER_TEXT:
751                 psz_node = xml_ReaderValue( p_xml_reader );
752                 if( psz_node )
753                 {
754                     CFStringRef   p_cfString;
755                     int           len;
756
757                     // Turn any multiple-whitespaces into single spaces
758                     char *s = strpbrk( psz_node, "\t\r\n " );
759                     while( s )
760                     {
761                         int i_whitespace = strspn( s, "\t\r\n " );
762
763                         if( i_whitespace > 1 )
764                             memmove( &s[1],
765                                      &s[i_whitespace],
766                                      strlen( s ) - i_whitespace + 1 );
767                         *s++ = ' ';
768
769                         s = strpbrk( s, "\t\r\n " );
770                     }
771
772
773                     CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
774                     p_cfString = CFStringCreateWithCString( NULL, psz_node, kCFStringEncodingUTF8 );
775                     CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), p_cfString );
776                     CFRelease( p_cfString );
777                     len = CFAttributedStringGetLength( p_attrnode );
778
779                     GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
780                                              CFRangeMake( 0, len ),
781                                              p_attrnode );
782
783                     CFAttributedStringReplaceAttributedString( p_attrString,
784                                     CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
785                                     p_attrnode);
786                     CFRelease( p_attrnode );
787                     free( psz_node );
788                 }
789                 break;
790         }
791     }
792
793     while( VLC_SUCCESS == PopFont( &p_fonts ) );
794
795     return rv;
796 }
797
798 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
799                        subpicture_region_t *p_region_in )
800 {
801     int          rv = VLC_SUCCESS;
802     stream_t     *p_sub = NULL;
803     xml_t        *p_xml = NULL;
804     xml_reader_t *p_xml_reader = NULL;
805
806     if( !p_region_in || !p_region_in->psz_html )
807         return VLC_EGENERIC;
808
809     /* Reset the default fontsize in case screen metrics have changed */
810     p_filter->p_sys->i_font_size = GetFontSize( p_filter );
811
812     p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
813                               (uint8_t *) p_region_in->psz_html,
814                               strlen( p_region_in->psz_html ),
815                               true );
816     if( p_sub )
817     {
818         p_xml = xml_Create( p_filter );
819         if( p_xml )
820         {
821             bool b_karaoke = false;
822
823             p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
824             if( p_xml_reader )
825             {
826                 /* Look for Root Node */
827                 if( xml_ReaderRead( p_xml_reader ) == 1 )
828                 {
829                     char *psz_node = xml_ReaderName( p_xml_reader );
830
831                     if( !strcasecmp( "karaoke", psz_node ) )
832                     {
833                         /* We're going to have to render the text a number
834                          * of times to show the progress marker on the text.
835                          */
836                         var_SetBool( p_filter, "text-rerender", true );
837                         b_karaoke = true;
838                     }
839                     else if( !strcasecmp( "text", psz_node ) )
840                     {
841                         b_karaoke = false;
842                     }
843                     else
844                     {
845                         /* Only text and karaoke tags are supported */
846                         xml_ReaderDelete( p_xml, p_xml_reader );
847                         p_xml_reader = NULL;
848                         rv = VLC_EGENERIC;
849                     }
850
851                     free( psz_node );
852                 }
853             }
854
855             if( p_xml_reader )
856             {
857                 int         i_len;
858
859                 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
860                 rv = ProcessNodes( p_filter, p_xml_reader,
861                               p_region_in->p_style, p_attrString );
862
863                 i_len = CFAttributedStringGetLength( p_attrString );
864
865                 p_region_out->i_x = p_region_in->i_x;
866                 p_region_out->i_y = p_region_in->i_y;
867
868                 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
869                 {
870                     RenderYUVA( p_filter, p_region_out, p_attrString );
871                 }
872                 CFRelease(p_attrString);
873
874                 xml_ReaderDelete( p_xml, p_xml_reader );
875             }
876             xml_Delete( p_xml );
877         }
878         stream_Delete( p_sub );
879     }
880
881     return rv;
882 }
883
884 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
885                          offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
886 {
887     offscreen_bitmap_t *p_bitmap;
888     CGContextRef        p_context = NULL;
889
890     p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
891     if( p_bitmap )
892     {
893         p_bitmap->i_bitsPerChannel = 8;
894         p_bitmap->i_bitsPerPixel   = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
895         p_bitmap->i_bytesPerPixel  = p_bitmap->i_bitsPerPixel / 8;
896         p_bitmap->i_bytesPerRow    = i_width * p_bitmap->i_bytesPerPixel;
897
898         p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
899
900         *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
901
902         if( p_bitmap->p_data && *pp_colorSpace )
903         {
904             p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
905                                 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
906                                 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
907         }
908         if( p_context )
909         {
910             if( CGContextSetAllowsAntialiasing != NULL )
911             {
912                 CGContextSetAllowsAntialiasing( p_context, true );
913             }
914         }
915         *pp_memory = p_bitmap;
916     }
917
918     return p_context;
919 }
920
921 static offscreen_bitmap_t *Compose( int i_text_align,
922                                     CFMutableAttributedStringRef p_attrString,
923                                     unsigned i_width,
924                                     unsigned i_height,
925                                     unsigned *pi_textblock_height )
926 {
927     offscreen_bitmap_t  *p_offScreen  = NULL;
928     CGColorSpaceRef      p_colorSpace = NULL;
929     CGContextRef         p_context = NULL;
930
931     p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
932
933     *pi_textblock_height = 0;
934     if( p_context )
935     {
936         float horiz_flush;
937
938         CGContextSetTextMatrix( p_context, CGAffineTransformIdentity );
939
940         if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
941             horiz_flush = 1.0;
942         else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
943             horiz_flush = 0.5;
944         else
945             horiz_flush = 0.0;
946
947         // Create the framesetter with the attributed string.
948         CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
949         if( framesetter )
950         {
951             CTFrameRef frame;
952             CGMutablePathRef p_path = CGPathCreateMutable();
953             CGRect p_bounds = CGRectMake( (float)HORIZONTAL_MARGIN,
954                                           (float)VERTICAL_MARGIN,
955                                           (float)(i_width  - HORIZONTAL_MARGIN*2),
956                                           (float)(i_height - VERTICAL_MARGIN  *2));
957             CGPathAddRect( p_path, NULL, p_bounds );
958
959             // Create the frame and draw it into the graphics context
960             frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
961
962             CGPathRelease(p_path);
963
964             // Set up black outlining of the text --
965             CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
966             CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
967
968             if( frame != NULL )
969             {
970                 CFArrayRef lines;
971                 CGPoint    penPosition;
972
973                 lines = CTFrameGetLines( frame );
974                 penPosition.y = i_height;
975                 for (int i=0; i<CFArrayGetCount( lines ); i++)
976                 {
977                     CGFloat  ascent, descent, leading;
978
979                     CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
980                     CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
981
982                     // Set the outlining for this line to be dependant on the size of the line -
983                     // make it about 5% of the ascent, with a minimum at 1.0
984                     float f_thickness = ascent * 0.05;
985                     CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
986
987                     double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width  - HORIZONTAL_MARGIN*2));
988                     penPosition.x = HORIZONTAL_MARGIN + penOffset;
989                     penPosition.y -= ascent;
990                     CGContextSetTextPosition( p_context, penPosition.x, penPosition.y );
991                     CTLineDraw( line, p_context );
992                     penPosition.y -= descent + leading;
993
994                 }
995                 *pi_textblock_height = i_height - penPosition.y;
996
997                 CFRelease(frame);
998             }
999             CFRelease(framesetter);
1000         }
1001         CGContextFlush( p_context );
1002         CGContextRelease( p_context );
1003     }
1004     if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
1005
1006     return p_offScreen;
1007 }
1008
1009 static int GetFontSize( filter_t *p_filter )
1010 {
1011     return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
1012 }
1013
1014 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
1015                        CFMutableAttributedStringRef p_attrString )
1016 {
1017     offscreen_bitmap_t *p_offScreen = NULL;
1018     unsigned      i_textblock_height = 0;
1019
1020     unsigned i_width = p_filter->fmt_out.video.i_visible_width;
1021     unsigned i_height = p_filter->fmt_out.video.i_visible_height;
1022     unsigned i_text_align = p_region->i_align & 0x3;
1023
1024     if( !p_attrString )
1025     {
1026         msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1027         return VLC_EGENERIC;
1028     }
1029
1030     p_offScreen = Compose( i_text_align, p_attrString,
1031                            i_width, i_height, &i_textblock_height );
1032
1033     if( !p_offScreen )
1034     {
1035         msg_Err( p_filter, "No offscreen buffer" );
1036         return VLC_EGENERIC;
1037     }
1038
1039     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1040     video_format_t fmt;
1041     int i_offset;
1042     unsigned x, y, i_pitch;
1043     uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1044
1045     // Create a new subpicture region
1046     memset( &fmt, 0, sizeof(video_format_t) );
1047     fmt.i_chroma = VLC_CODEC_YUVA;
1048     fmt.i_width = fmt.i_visible_width = i_width;
1049     fmt.i_height = fmt.i_visible_height = __MIN( i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1050     fmt.i_x_offset = fmt.i_y_offset = 0;
1051
1052     p_region->p_picture = picture_NewFromFormat( &fmt );
1053     if( !p_region->p_picture )
1054         return VLC_EGENERIC;
1055     p_region->fmt = fmt;
1056
1057     p_dst_y = p_region->p_picture->Y_PIXELS;
1058     p_dst_u = p_region->p_picture->U_PIXELS;
1059     p_dst_v = p_region->p_picture->V_PIXELS;
1060     p_dst_a = p_region->p_picture->A_PIXELS;
1061     i_pitch = p_region->p_picture->A_PITCH;
1062
1063     i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1064     for( y = 0; y < fmt.i_height; y++)
1065     {
1066         for( x = 0; x < fmt.i_width; x++)
1067         {
1068             int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel     ];
1069             int i_red   = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1070             int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1071             int i_blue  = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1072
1073             i_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
1074                               802 * i_blue + 4096 + 131072 ) >> 13, 235);
1075             i_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
1076                              3598 * i_blue + 4096 + 1048576) >> 13, 240);
1077             i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1078                               -585 * i_blue + 4096 + 1048576) >> 13, 240);
1079
1080             p_dst_y[ i_offset + x ] = i_y;
1081             p_dst_u[ i_offset + x ] = i_u;
1082             p_dst_v[ i_offset + x ] = i_v;
1083             p_dst_a[ i_offset + x ] = i_alpha;
1084         }
1085         i_offset += i_pitch;
1086     }
1087
1088     free( p_offScreen->p_data );
1089     free( p_offScreen );
1090
1091     return VLC_SUCCESS;
1092 }