text_style/text_renderer: add support for halfwidth font
[vlc.git] / 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, bool b_halfwidth,
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_size;
314     uint32_t      i_font_color;
315     bool          b_bold, b_uline, b_italic, b_halfwidth;
316     vlc_value_t val;
317     b_bold = b_uline = b_italic = b_halfwidth = 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_size  = VLC_CLIP(p_region_in->p_style->i_font_size, 0, 255);
333         if (p_region_in->p_style->i_style_flags) {
334             if (p_region_in->p_style->i_style_flags & STYLE_BOLD)
335                 b_bold = TRUE;
336             if (p_region_in->p_style->i_style_flags & STYLE_ITALIC)
337                 b_italic = TRUE;
338             if (p_region_in->p_style->i_style_flags & STYLE_UNDERLINE)
339                 b_uline = TRUE;
340             if (p_region_in->p_style->i_style_flags & STYLE_HALFWIDTH)
341                 b_halfwidth = TRUE;
342         }
343     } else {
344         i_font_color = p_sys->i_font_color;
345         i_font_size  = p_sys->i_font_size;
346     }
347
348     if (i_font_size <= 0) {
349         msg_Warn(p_filter, "invalid fontsize, using 12");
350         if (VLC_SUCCESS == var_Get(p_filter, "scale", &val))
351             i_font_size = 12 * val.i_int / 1000;
352         else
353             i_font_size = 12;
354     }
355
356     p_region_out->i_x = p_region_in->i_x;
357     p_region_out->i_y = p_region_in->i_y;
358
359     CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
360
361     if (p_attrString) {
362         CFStringRef   p_cfString;
363         int           len;
364
365         EliminateCRLF(psz_string);
366         p_cfString = CFStringCreateWithCString(NULL, psz_string, kCFStringEncodingUTF8);
367         CFAttributedStringReplaceString(p_attrString, CFRangeMake(0, 0), p_cfString);
368         CFRelease(p_cfString);
369         len = CFAttributedStringGetLength(p_attrString);
370
371         setFontAttibutes(p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline, b_halfwidth,
372                                              CFRangeMake(0, len), p_attrString);
373
374         RenderYUVA(p_filter, p_region_out, p_attrString);
375         CFRelease(p_attrString);
376     }
377
378     return VLC_SUCCESS;
379 }
380
381
382 static int PushFont(font_stack_t **p_font, const char *psz_name, int i_size,
383                      uint32_t i_color)
384 {
385     font_stack_t *p_new;
386
387     if (!p_font)
388         return VLC_EGENERIC;
389
390     p_new = malloc(sizeof(font_stack_t));
391     if (! p_new)
392         return VLC_ENOMEM;
393
394     p_new->p_next = NULL;
395
396     if (psz_name)
397         p_new->psz_name = strdup(psz_name);
398     else
399         p_new->psz_name = NULL;
400
401     p_new->i_size   = i_size;
402     p_new->i_color  = i_color;
403
404     if (!*p_font)
405         *p_font = p_new;
406     else {
407         font_stack_t *p_last;
408
409         for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
410         ;
411
412         p_last->p_next = p_new;
413     }
414     return VLC_SUCCESS;
415 }
416
417 static int PopFont(font_stack_t **p_font)
418 {
419     font_stack_t *p_last, *p_next_to_last;
420
421     if (!p_font || !*p_font)
422         return VLC_EGENERIC;
423
424     p_next_to_last = NULL;
425     for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
426         p_next_to_last = p_last;
427
428     if (p_next_to_last)
429         p_next_to_last->p_next = NULL;
430     else
431         *p_font = NULL;
432
433     free(p_last->psz_name);
434     free(p_last);
435
436     return VLC_SUCCESS;
437 }
438
439 static int PeekFont(font_stack_t **p_font, char **psz_name, int *i_size,
440                      uint32_t *i_color)
441 {
442     font_stack_t *p_last;
443
444     if (!p_font || !*p_font)
445         return VLC_EGENERIC;
446
447     for (p_last=*p_font;
448          p_last->p_next;
449          p_last=p_last->p_next)
450     ;
451
452     *psz_name = p_last->psz_name;
453     *i_size   = p_last->i_size;
454     *i_color  = p_last->i_color;
455
456     return VLC_SUCCESS;
457 }
458
459 static int HandleFontAttributes(xml_reader_t *p_xml_reader,
460                                   font_stack_t **p_fonts)
461 {
462     int        rv;
463     char      *psz_fontname = NULL;
464     uint32_t   i_font_color = 0xffffff;
465     int        i_font_alpha = 0;
466     int        i_font_size  = 24;
467     const char *attr, *value;
468
469     /* Default all attributes to the top font in the stack -- in case not
470      * all attributes are specified in the sub-font */
471     if (VLC_SUCCESS == PeekFont(p_fonts,
472                                 &psz_fontname,
473                                 &i_font_size,
474                                 &i_font_color)) {
475         psz_fontname = strdup(psz_fontname);
476         i_font_size = i_font_size;
477     }
478     i_font_alpha = (i_font_color >> 24) & 0xff;
479     i_font_color &= 0x00ffffff;
480
481     while ((attr = xml_ReaderNextAttr(p_xml_reader, &value))) {
482         if (!strcasecmp("face", attr)) {
483             free(psz_fontname);
484             psz_fontname = strdup(value);
485         } else if (!strcasecmp("size", attr)) {
486             if ((*value == '+') || (*value == '-')) {
487                 int i_value = atoi(value);
488
489                 if ((i_value >= -5) && (i_value <= 5))
490                     i_font_size += (i_value * i_font_size) / 10;
491                 else if (i_value < -5)
492                     i_font_size = - i_value;
493                 else if (i_value > 5)
494                     i_font_size = i_value;
495             }
496             else
497                 i_font_size = atoi(value);
498         } else if (!strcasecmp("color", attr)) {
499             if (value[0] == '#') {
500                 i_font_color = strtol(value + 1, NULL, 16);
501                 i_font_color &= 0x00ffffff;
502             } else {
503                 /* color detection fallback */
504                 unsigned int count = sizeof(ppsz_color_names);
505                 for (unsigned x = 0; x < count; x++) {
506                     if (!strcmp(value, ppsz_color_names[x])) {
507                         i_font_color = pi_color_values[x];
508                         break;
509                     }
510                 }
511             }
512         } else if (!strcasecmp("alpha", attr) && (value[0] == '#')) {
513             i_font_alpha = strtol(value + 1, NULL, 16);
514             i_font_alpha &= 0xff;
515         }
516     }
517     rv = PushFont(p_fonts,
518                   psz_fontname,
519                   i_font_size,
520                   (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24));
521
522     free(psz_fontname);
523
524     return rv;
525 }
526
527 static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
528         bool b_bold, bool b_italic, bool b_underline, bool b_halfwidth,
529         CFRange p_range, CFMutableAttributedStringRef p_attrString)
530 {
531     CFStringRef p_cfString;
532     CTFontRef   p_font;
533
534     int i_font_width = b_halfwidth ? i_font_size / 2 : i_font_size;
535     CGAffineTransform trans = CGAffineTransformMakeScale((float)i_font_width
536                                                          / i_font_size, 1.0);
537
538     // fallback on default
539     if (!psz_fontname)
540         psz_fontname = (char *)DEFAULT_FONT;
541
542     p_cfString = CFStringCreateWithCString(kCFAllocatorDefault,
543                                             psz_fontname,
544                                             kCFStringEncodingUTF8);
545     p_font     = CTFontCreateWithName(p_cfString,
546                                        (float)i_font_size,
547                                        &trans);
548     CFRelease(p_cfString);
549     CFAttributedStringSetAttribute(p_attrString,
550                                     p_range,
551                                     kCTFontAttributeName,
552                                     p_font);
553     CFRelease(p_font);
554
555     // Handle Underline
556     SInt32 _uline;
557     if (b_underline)
558         _uline = kCTUnderlineStyleSingle;
559     else
560         _uline = kCTUnderlineStyleNone;
561
562     CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
563     CFAttributedStringSetAttribute(p_attrString,
564                                     p_range,
565                                     kCTUnderlineStyleAttributeName,
566                                     underline);
567     CFRelease(underline);
568
569     // Handle Bold
570     float _weight;
571     if (b_bold)
572         _weight = 0.5;
573     else
574         _weight = 0.0;
575
576     CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
577     CFAttributedStringSetAttribute(p_attrString,
578                                     p_range,
579                                     kCTFontWeightTrait,
580                                     weight);
581     CFRelease(weight);
582
583     // Handle Italic
584     float _slant;
585     if (b_italic)
586         _slant = 1.0;
587     else
588         _slant = 0.0;
589
590     CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
591     CFAttributedStringSetAttribute(p_attrString,
592                                     p_range,
593                                     kCTFontSlantTrait,
594                                     slant);
595     CFRelease(slant);
596
597     // fetch invalid colors
598     if (i_font_color == 0xFFFFFFFF)
599         i_font_color = 0x00FFFFFF;
600
601     // Handle foreground color
602     CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
603     CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
604                              (float)((i_font_color & 0x0000ff00) >>  8) / 255.0,
605                              (float)((i_font_color & 0x000000ff)) / 255.0,
606                              (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
607     CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
608     CGColorSpaceRelease(rgbColorSpace);
609
610     CFAttributedStringSetAttribute(p_attrString,
611                                     p_range,
612                                     kCTForegroundColorAttributeName,
613                                     fg_text);
614     CFRelease(fg_text);
615
616 }
617
618 static void GetAttrStrFromFontStack(font_stack_t **p_fonts,
619         bool b_bold, bool b_italic, bool b_uline,
620         CFRange p_range, CFMutableAttributedStringRef p_attrString)
621 {
622     char       *psz_fontname = NULL;
623     int         i_font_size  = 0;
624     uint32_t    i_font_color = 0;
625
626     if (VLC_SUCCESS == PeekFont(p_fonts, &psz_fontname, &i_font_size,
627                                 &i_font_color)) {
628         setFontAttibutes(psz_fontname,
629                          i_font_size,
630                          i_font_color,
631                          b_bold, b_italic, b_uline, FALSE,
632                          p_range,
633                          p_attrString);
634     }
635 }
636
637 static int ProcessNodes(filter_t *p_filter,
638                          xml_reader_t *p_xml_reader,
639                          text_style_t *p_font_style,
640                          CFMutableAttributedStringRef p_attrString)
641 {
642     int           rv             = VLC_SUCCESS;
643     filter_sys_t *p_sys          = p_filter->p_sys;
644     font_stack_t *p_fonts        = NULL;
645
646     int type;
647     const char *node;
648
649     bool b_italic = false;
650     bool b_bold   = false;
651     bool b_uline  = false;
652
653     if (p_font_style) {
654         rv = PushFont(&p_fonts,
655                p_font_style->psz_fontname,
656                p_font_style->i_font_size,
657                (p_font_style->i_font_color & 0xffffff) |
658                    ((p_font_style->i_font_alpha & 0xff) << 24));
659
660         if (p_font_style->i_style_flags & STYLE_BOLD)
661             b_bold = true;
662         if (p_font_style->i_style_flags & STYLE_ITALIC)
663             b_italic = true;
664         if (p_font_style->i_style_flags & STYLE_UNDERLINE)
665             b_uline = true;
666     } else {
667         rv = PushFont(&p_fonts,
668                        p_sys->psz_font_name,
669                        p_sys->i_font_size,
670                        p_sys->i_font_color);
671     }
672     if (rv != VLC_SUCCESS)
673         return rv;
674
675     while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
676         switch (type) {
677             case XML_READER_ENDELEM:
678                 if (!strcasecmp("font", node))
679                     PopFont(&p_fonts);
680                 else if (!strcasecmp("b", node))
681                     b_bold   = false;
682                 else if (!strcasecmp("i", node))
683                     b_italic = false;
684                 else if (!strcasecmp("u", node))
685                     b_uline  = false;
686
687                 break;
688             case XML_READER_STARTELEM:
689                 if (!strcasecmp("font", node))
690                     rv = HandleFontAttributes(p_xml_reader, &p_fonts);
691                 else if (!strcasecmp("b", node))
692                     b_bold = true;
693                 else if (!strcasecmp("i", node))
694                     b_italic = true;
695                 else if (!strcasecmp("u", node))
696                     b_uline = true;
697                 else if (!strcasecmp("br", node)) {
698                     CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
699                     CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), CFSTR("\n"));
700
701                     GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
702                                              CFRangeMake(0, 1),
703                                              p_attrnode);
704                     CFAttributedStringReplaceAttributedString(p_attrString,
705                                     CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
706                                     p_attrnode);
707                     CFRelease(p_attrnode);
708                 }
709                 break;
710             case XML_READER_TEXT:
711             {
712                 CFStringRef   p_cfString;
713                 int           len;
714
715                 // Turn any multiple-whitespaces into single spaces
716                 char *dup = strdup(node);
717                 if (!dup)
718                     break;
719                 char *s = strpbrk(dup, "\t\r\n ");
720                 while(s)
721                 {
722                     int i_whitespace = strspn(s, "\t\r\n ");
723
724                     if (i_whitespace > 1)
725                         memmove(&s[1],
726                                  &s[i_whitespace],
727                                  strlen(s) - i_whitespace + 1);
728                     *s++ = ' ';
729
730                     s = strpbrk(s, "\t\r\n ");
731                 }
732
733
734                 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
735                 p_cfString = CFStringCreateWithCString(NULL, dup, kCFStringEncodingUTF8);
736                 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), p_cfString);
737                 CFRelease(p_cfString);
738                 len = CFAttributedStringGetLength(p_attrnode);
739
740                 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
741                                          CFRangeMake(0, len),
742                                          p_attrnode);
743
744                 CFAttributedStringReplaceAttributedString(p_attrString,
745                                 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
746                                 p_attrnode);
747                 CFRelease(p_attrnode);
748
749                 free(dup);
750                 break;
751             }
752         }
753     }
754
755     while(VLC_SUCCESS == PopFont(&p_fonts));
756
757     return rv;
758 }
759
760 static int RenderHtml(filter_t *p_filter, subpicture_region_t *p_region_out,
761                        subpicture_region_t *p_region_in,
762                        const vlc_fourcc_t *p_chroma_list)
763 {
764     int          rv = VLC_SUCCESS;
765     stream_t     *p_sub = NULL;
766     xml_t        *p_xml = NULL;
767     xml_reader_t *p_xml_reader = NULL;
768     VLC_UNUSED(p_chroma_list);
769
770     if (!p_region_in || !p_region_in->psz_html)
771         return VLC_EGENERIC;
772
773     /* Reset the default fontsize in case screen metrics have changed */
774     p_filter->p_sys->i_font_size = GetFontSize(p_filter);
775
776     p_sub = stream_MemoryNew(VLC_OBJECT(p_filter),
777                               (uint8_t *) p_region_in->psz_html,
778                               strlen(p_region_in->psz_html),
779                               true);
780     if (p_sub) {
781         p_xml = xml_Create(p_filter);
782         if (p_xml) {
783             p_xml_reader = xml_ReaderCreate(p_xml, p_sub);
784             if (p_xml_reader) {
785                 /* Look for Root Node */
786                 const char *name;
787                 if (xml_ReaderNextNode(p_xml_reader, &name)
788                         == XML_READER_STARTELEM) {
789                     if (!strcasecmp("karaoke", name)) {
790                         /* We're going to have to render the text a number
791                          * of times to show the progress marker on the text.
792                          */
793                         var_SetBool(p_filter, "text-rerender", true);
794                     } else if (strcasecmp("text", name)) {
795                         /* Only text and karaoke tags are supported */
796                         msg_Dbg(p_filter, "Unsupported top-level tag "
797                                            "<%s> ignored.", name);
798                         rv = VLC_EGENERIC;
799                     }
800                 } else {
801                     msg_Err(p_filter, "Malformed HTML subtitle");
802                     rv = VLC_EGENERIC;
803                 }
804
805                 if (rv != VLC_SUCCESS) {
806                     xml_ReaderDelete(p_xml_reader);
807                     p_xml_reader = NULL;
808                 }
809             }
810
811             if (p_xml_reader) {
812                 int         i_len;
813
814                 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
815                 rv = ProcessNodes(p_filter, p_xml_reader,
816                               p_region_in->p_style, p_attrString);
817
818                 i_len = CFAttributedStringGetLength(p_attrString);
819
820                 p_region_out->i_x = p_region_in->i_x;
821                 p_region_out->i_y = p_region_in->i_y;
822
823                 if ((rv == VLC_SUCCESS) && (i_len > 0))
824                     RenderYUVA(p_filter, p_region_out, p_attrString);
825
826                 CFRelease(p_attrString);
827
828                 xml_ReaderDelete(p_xml_reader);
829             }
830             xml_Delete(p_xml);
831         }
832         stream_Delete(p_sub);
833     }
834
835     return rv;
836 }
837
838 static CGContextRef CreateOffScreenContext(int i_width, int i_height,
839                          offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace)
840 {
841     offscreen_bitmap_t *p_bitmap;
842     CGContextRef        p_context = NULL;
843
844     p_bitmap = (offscreen_bitmap_t *) malloc(sizeof(offscreen_bitmap_t));
845     if (p_bitmap) {
846         p_bitmap->i_bitsPerChannel = 8;
847         p_bitmap->i_bitsPerPixel   = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
848         p_bitmap->i_bytesPerPixel  = p_bitmap->i_bitsPerPixel / 8;
849         p_bitmap->i_bytesPerRow    = i_width * p_bitmap->i_bytesPerPixel;
850
851         p_bitmap->p_data = calloc(i_height, p_bitmap->i_bytesPerRow);
852
853         *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
854
855         if (p_bitmap->p_data && *pp_colorSpace)
856             p_context = CGBitmapContextCreate(p_bitmap->p_data, i_width, i_height,
857                                 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
858                                 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
859
860         if (p_context) {
861             if (CGContextSetAllowsAntialiasing != NULL)
862                 CGContextSetAllowsAntialiasing(p_context, true);
863         }
864         *pp_memory = p_bitmap;
865     }
866
867     return p_context;
868 }
869
870 static offscreen_bitmap_t *Compose( subpicture_region_t *p_region,
871                                     CFMutableAttributedStringRef p_attrString,
872                                     unsigned i_width,
873                                     unsigned i_height,
874                                     unsigned *pi_textblock_height)
875 {
876     offscreen_bitmap_t  *p_offScreen  = NULL;
877     CGColorSpaceRef      p_colorSpace = NULL;
878     CGContextRef         p_context = NULL;
879
880     p_context = CreateOffScreenContext(i_width, i_height, &p_offScreen, &p_colorSpace);
881
882     *pi_textblock_height = 0;
883     if (p_context) {
884         float horiz_flush;
885
886         CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
887
888         if (p_region->i_align & SUBPICTURE_ALIGN_RIGHT)
889             horiz_flush = 1.0;
890         else if ((p_region->i_align & SUBPICTURE_ALIGN_LEFT) == 0)
891             horiz_flush = 0.5;
892         else
893             horiz_flush = 0.0;
894
895         // Create the framesetter with the attributed string.
896         CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
897         if (framesetter) {
898             CTFrameRef frame;
899             CGMutablePathRef p_path = CGPathCreateMutable();
900             CGRect p_bounds = CGRectMake((float)HORIZONTAL_MARGIN,
901                                           (float)VERTICAL_MARGIN,
902                                           (float)(i_width  - HORIZONTAL_MARGIN*2),
903                                           (float)(i_height - VERTICAL_MARGIN  *2));
904             CGPathAddRect(p_path, NULL, p_bounds);
905
906             // Create the frame and draw it into the graphics context
907             frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
908
909             CGPathRelease(p_path);
910
911             // Set up black outlining of the text --
912             CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
913             CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
914
915             if (frame != NULL) {
916                 CFArrayRef lines;
917                 CGPoint    penPosition;
918
919                 lines = CTFrameGetLines(frame);
920                 penPosition.y = i_height;
921                 for (int i=0; i<CFArrayGetCount(lines); i++) {
922                     CGFloat  ascent, descent, leading;
923
924                     CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
925                     CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
926
927                     // Set the outlining for this line to be dependant on the size of the line -
928                     // make it about 5% of the ascent, with a minimum at 1.0
929                     float f_thickness = ascent * 0.05;
930                     CGContextSetLineWidth(p_context, ((f_thickness > 1.0) ? 1.0 : f_thickness));
931
932                     double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width  - HORIZONTAL_MARGIN*2));
933                     penPosition.x = HORIZONTAL_MARGIN + penOffset;
934                     if (horiz_flush == 0.0)
935                         penPosition.x = p_region->i_x;
936                     penPosition.y -= ascent;
937                     CGContextSetTextPosition(p_context, penPosition.x, penPosition.y);
938                     CTLineDraw(line, p_context);
939                     penPosition.y -= descent + leading;
940
941                 }
942                 *pi_textblock_height = i_height - penPosition.y;
943
944                 CFRelease(frame);
945             }
946             CFRelease(framesetter);
947         }
948         CGContextFlush(p_context);
949         CGContextRelease(p_context);
950     }
951     if (p_colorSpace) CGColorSpaceRelease(p_colorSpace);
952
953     return p_offScreen;
954 }
955
956 static int GetFontSize(filter_t *p_filter)
957 {
958     int i_size = 0;
959
960     int i_ratio = var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" );
961     if( i_ratio > 0 )
962         i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
963
964     if( i_size <= 0 )
965     {
966         msg_Warn( p_filter, "invalid fontsize, using 12" );
967         i_size = 12;
968     }
969     return i_size;
970 }
971
972 static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
973                        CFMutableAttributedStringRef p_attrString)
974 {
975     offscreen_bitmap_t *p_offScreen = NULL;
976     unsigned      i_textblock_height = 0;
977
978     unsigned i_width = p_filter->fmt_out.video.i_visible_width;
979     unsigned i_height = p_filter->fmt_out.video.i_visible_height;
980
981     if (!p_attrString) {
982         msg_Err(p_filter, "Invalid argument to RenderYUVA");
983         return VLC_EGENERIC;
984     }
985
986     p_offScreen = Compose( p_region, p_attrString,
987                            i_width, i_height, &i_textblock_height);
988
989     if (!p_offScreen) {
990         msg_Err(p_filter, "No offscreen buffer");
991         return VLC_EGENERIC;
992     }
993
994     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
995     video_format_t fmt;
996     int i_offset;
997     unsigned i_pitch;
998     uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
999
1000     // Create a new subpicture region
1001     memset(&fmt, 0, sizeof(video_format_t));
1002     fmt.i_chroma = VLC_CODEC_YUVA;
1003     fmt.i_width = fmt.i_visible_width = i_width;
1004     fmt.i_height = fmt.i_visible_height = __MIN(i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1005     fmt.i_x_offset = fmt.i_y_offset = 0;
1006     fmt.i_sar_num = 1;
1007     fmt.i_sar_den = 1;
1008
1009     p_region->p_picture = picture_NewFromFormat(&fmt);
1010     if (!p_region->p_picture) {
1011         free(p_offScreen->p_data);
1012         free(p_offScreen);
1013         return VLC_EGENERIC;
1014     }
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 }