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