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