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