]> git.sesse.net Git - vlc/blob - modules/text_renderer/quartztext.c
BD module: no tabs in source code...
[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           "Arial Black"
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     // Handle font name and size
538     p_cfString = CFStringCreateWithCString(NULL,
539                                             psz_fontname,
540                                             kCFStringEncodingUTF8);
541     p_font     = CTFontCreateWithName(p_cfString,
542                                        (float)i_font_size,
543                                        NULL);
544     CFRelease(p_cfString);
545     CFAttributedStringSetAttribute(p_attrString,
546                                     p_range,
547                                     kCTFontAttributeName,
548                                     p_font);
549     CFRelease(p_font);
550
551     // Handle Underline
552     SInt32 _uline;
553     if (b_underline)
554         _uline = kCTUnderlineStyleSingle;
555     else
556         _uline = kCTUnderlineStyleNone;
557
558     CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
559     CFAttributedStringSetAttribute(p_attrString,
560                                     p_range,
561                                     kCTUnderlineStyleAttributeName,
562                                     underline);
563     CFRelease(underline);
564
565     // Handle Bold
566     float _weight;
567     if (b_bold)
568         _weight = 0.5;
569     else
570         _weight = 0.0;
571
572     CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
573     CFAttributedStringSetAttribute(p_attrString,
574                                     p_range,
575                                     kCTFontWeightTrait,
576                                     weight);
577     CFRelease(weight);
578
579     // Handle Italic
580     float _slant;
581     if (b_italic)
582         _slant = 1.0;
583     else
584         _slant = 0.0;
585
586     CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
587     CFAttributedStringSetAttribute(p_attrString,
588                                     p_range,
589                                     kCTFontSlantTrait,
590                                     slant);
591     CFRelease(slant);
592
593     // Handle foreground colour
594     CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
595     CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
596                              (float)((i_font_color & 0x0000ff00) >>  8) / 255.0,
597                              (float)((i_font_color & 0x000000ff)) / 255.0,
598                              (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
599     CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
600     CGColorSpaceRelease(rgbColorSpace);
601
602     CFAttributedStringSetAttribute(p_attrString,
603                                     p_range,
604                                     kCTForegroundColorAttributeName,
605                                     fg_text);
606     CFRelease(fg_text);
607
608 }
609
610 static void GetAttrStrFromFontStack(font_stack_t **p_fonts,
611         bool b_bold, bool b_italic, bool b_uline,
612         CFRange p_range, CFMutableAttributedStringRef p_attrString)
613 {
614     char       *psz_fontname = NULL;
615     int         i_font_size  = 0;
616     uint32_t    i_font_color = 0;
617
618     if (VLC_SUCCESS == PeekFont(p_fonts, &psz_fontname, &i_font_size,
619                                 &i_font_color)) {
620         setFontAttibutes(psz_fontname,
621                          i_font_size,
622                          i_font_color,
623                          b_bold, b_italic, b_uline,
624                          p_range,
625                          p_attrString);
626     }
627 }
628
629 static int ProcessNodes(filter_t *p_filter,
630                          xml_reader_t *p_xml_reader,
631                          text_style_t *p_font_style,
632                          CFMutableAttributedStringRef p_attrString)
633 {
634     int           rv             = VLC_SUCCESS;
635     filter_sys_t *p_sys          = p_filter->p_sys;
636     font_stack_t *p_fonts        = NULL;
637
638     int type;
639     const char *node;
640
641     bool b_italic = false;
642     bool b_bold   = false;
643     bool b_uline  = false;
644
645     if (p_font_style) {
646         rv = PushFont(&p_fonts,
647                p_font_style->psz_fontname,
648                p_font_style->i_font_size,
649                (p_font_style->i_font_color & 0xffffff) |
650                    ((p_font_style->i_font_alpha & 0xff) << 24));
651
652         if (p_font_style->i_style_flags & STYLE_BOLD)
653             b_bold = true;
654         if (p_font_style->i_style_flags & STYLE_ITALIC)
655             b_italic = true;
656         if (p_font_style->i_style_flags & STYLE_UNDERLINE)
657             b_uline = true;
658     } else {
659         rv = PushFont(&p_fonts,
660                        p_sys->psz_font_name,
661                        p_sys->i_font_size,
662                        p_sys->i_font_color);
663     }
664     if (rv != VLC_SUCCESS)
665         return rv;
666
667     while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
668         switch (type) {
669             case XML_READER_ENDELEM:
670                 if (!strcasecmp("font", node))
671                     PopFont(&p_fonts);
672                 else if (!strcasecmp("b", node))
673                     b_bold   = false;
674                 else if (!strcasecmp("i", node))
675                     b_italic = false;
676                 else if (!strcasecmp("u", node))
677                     b_uline  = false;
678
679                 break;
680             case XML_READER_STARTELEM:
681                 if (!strcasecmp("font", node))
682                     rv = HandleFontAttributes(p_xml_reader, &p_fonts);
683                 else if (!strcasecmp("b", node))
684                     b_bold = true;
685                 else if (!strcasecmp("i", node))
686                     b_italic = true;
687                 else if (!strcasecmp("u", node))
688                     b_uline = true;
689                 else if (!strcasecmp("br", node)) {
690                     CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
691                     CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), CFSTR("\n"));
692
693                     GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
694                                              CFRangeMake(0, 1),
695                                              p_attrnode);
696                     CFAttributedStringReplaceAttributedString(p_attrString,
697                                     CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
698                                     p_attrnode);
699                     CFRelease(p_attrnode);
700                 }
701                 break;
702             case XML_READER_TEXT:
703             {
704                 CFStringRef   p_cfString;
705                 int           len;
706
707                 // Turn any multiple-whitespaces into single spaces
708                 char *dup = strdup(node);
709                 if (!dup)
710                     break;
711                 char *s = strpbrk(dup, "\t\r\n ");
712                 while(s)
713                 {
714                     int i_whitespace = strspn(s, "\t\r\n ");
715
716                     if (i_whitespace > 1)
717                         memmove(&s[1],
718                                  &s[i_whitespace],
719                                  strlen(s) - i_whitespace + 1);
720                     *s++ = ' ';
721
722                     s = strpbrk(s, "\t\r\n ");
723                 }
724
725
726                 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
727                 p_cfString = CFStringCreateWithCString(NULL, dup, kCFStringEncodingUTF8);
728                 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), p_cfString);
729                 CFRelease(p_cfString);
730                 len = CFAttributedStringGetLength(p_attrnode);
731
732                 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
733                                          CFRangeMake(0, len),
734                                          p_attrnode);
735
736                 CFAttributedStringReplaceAttributedString(p_attrString,
737                                 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
738                                 p_attrnode);
739                 CFRelease(p_attrnode);
740
741                 free(dup);
742                 break;
743             }
744         }
745     }
746
747     while(VLC_SUCCESS == PopFont(&p_fonts));
748
749     return rv;
750 }
751
752 static int RenderHtml(filter_t *p_filter, subpicture_region_t *p_region_out,
753                        subpicture_region_t *p_region_in,
754                        const vlc_fourcc_t *p_chroma_list)
755 {
756     int          rv = VLC_SUCCESS;
757     stream_t     *p_sub = NULL;
758     xml_t        *p_xml = NULL;
759     xml_reader_t *p_xml_reader = NULL;
760     VLC_UNUSED(p_chroma_list);
761
762     if (!p_region_in || !p_region_in->psz_html)
763         return VLC_EGENERIC;
764
765     /* Reset the default fontsize in case screen metrics have changed */
766     p_filter->p_sys->i_font_size = GetFontSize(p_filter);
767
768     p_sub = stream_MemoryNew(VLC_OBJECT(p_filter),
769                               (uint8_t *) p_region_in->psz_html,
770                               strlen(p_region_in->psz_html),
771                               true);
772     if (p_sub) {
773         p_xml = xml_Create(p_filter);
774         if (p_xml) {
775             bool b_karaoke = false;
776
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                         b_karaoke = true;
789                     } else if (!strcasecmp("text", name))
790                         b_karaoke = false;
791                     else {
792                         /* Only text and karaoke tags are supported */
793                         msg_Dbg(p_filter, "Unsupported top-level tag "
794                                            "<%s> ignored.", name);
795                         rv = VLC_EGENERIC;
796                     }
797                 } else {
798                     msg_Err(p_filter, "Malformed HTML subtitle");
799                     rv = VLC_EGENERIC;
800                 }
801
802                 if (rv != VLC_SUCCESS) {
803                     xml_ReaderDelete(p_xml_reader);
804                     p_xml_reader = NULL;
805                 }
806             }
807
808             if (p_xml_reader) {
809                 int         i_len;
810
811                 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
812                 rv = ProcessNodes(p_filter, p_xml_reader,
813                               p_region_in->p_style, p_attrString);
814
815                 i_len = CFAttributedStringGetLength(p_attrString);
816
817                 p_region_out->i_x = p_region_in->i_x;
818                 p_region_out->i_y = p_region_in->i_y;
819
820                 if ((rv == VLC_SUCCESS) && (i_len > 0))
821                     RenderYUVA(p_filter, p_region_out, p_attrString);
822
823                 CFRelease(p_attrString);
824
825                 xml_ReaderDelete(p_xml_reader);
826             }
827             xml_Delete(p_xml);
828         }
829         stream_Delete(p_sub);
830     }
831
832     return rv;
833 }
834
835 static CGContextRef CreateOffScreenContext(int i_width, int i_height,
836                          offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace)
837 {
838     offscreen_bitmap_t *p_bitmap;
839     CGContextRef        p_context = NULL;
840
841     p_bitmap = (offscreen_bitmap_t *) malloc(sizeof(offscreen_bitmap_t));
842     if (p_bitmap) {
843         p_bitmap->i_bitsPerChannel = 8;
844         p_bitmap->i_bitsPerPixel   = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
845         p_bitmap->i_bytesPerPixel  = p_bitmap->i_bitsPerPixel / 8;
846         p_bitmap->i_bytesPerRow    = i_width * p_bitmap->i_bytesPerPixel;
847
848         p_bitmap->p_data = calloc(i_height, p_bitmap->i_bytesPerRow);
849
850         *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
851
852         if (p_bitmap->p_data && *pp_colorSpace)
853             p_context = CGBitmapContextCreate(p_bitmap->p_data, i_width, i_height,
854                                 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
855                                 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
856
857         if (p_context) {
858             if (CGContextSetAllowsAntialiasing != NULL)
859                 CGContextSetAllowsAntialiasing(p_context, true);
860         }
861         *pp_memory = p_bitmap;
862     }
863
864     return p_context;
865 }
866
867 static offscreen_bitmap_t *Compose(int i_text_align,
868                                     CFMutableAttributedStringRef p_attrString,
869                                     unsigned i_width,
870                                     unsigned i_height,
871                                     unsigned *pi_textblock_height)
872 {
873     offscreen_bitmap_t  *p_offScreen  = NULL;
874     CGColorSpaceRef      p_colorSpace = NULL;
875     CGContextRef         p_context = NULL;
876
877     p_context = CreateOffScreenContext(i_width, i_height, &p_offScreen, &p_colorSpace);
878
879     *pi_textblock_height = 0;
880     if (p_context) {
881         float horiz_flush;
882
883         CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
884
885         if (i_text_align == SUBPICTURE_ALIGN_RIGHT)
886             horiz_flush = 1.0;
887         else if (i_text_align != SUBPICTURE_ALIGN_LEFT)
888             horiz_flush = 0.5;
889         else
890             horiz_flush = 0.0;
891
892         // Create the framesetter with the attributed string.
893         CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
894         if (framesetter) {
895             CTFrameRef frame;
896             CGMutablePathRef p_path = CGPathCreateMutable();
897             CGRect p_bounds = CGRectMake((float)HORIZONTAL_MARGIN,
898                                           (float)VERTICAL_MARGIN,
899                                           (float)(i_width  - HORIZONTAL_MARGIN*2),
900                                           (float)(i_height - VERTICAL_MARGIN  *2));
901             CGPathAddRect(p_path, NULL, p_bounds);
902
903             // Create the frame and draw it into the graphics context
904             frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
905
906             CGPathRelease(p_path);
907
908             // Set up black outlining of the text --
909             CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
910             CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
911
912             if (frame != NULL) {
913                 CFArrayRef lines;
914                 CGPoint    penPosition;
915
916                 lines = CTFrameGetLines(frame);
917                 penPosition.y = i_height;
918                 for (int i=0; i<CFArrayGetCount(lines); i++) {
919                     CGFloat  ascent, descent, leading;
920
921                     CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
922                     CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
923
924                     // Set the outlining for this line to be dependant on the size of the line -
925                     // make it about 5% of the ascent, with a minimum at 1.0
926                     float f_thickness = ascent * 0.05;
927                     CGContextSetLineWidth(p_context, ((f_thickness > 1.0) ? 1.0 : f_thickness));
928
929                     double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width  - HORIZONTAL_MARGIN*2));
930                     penPosition.x = HORIZONTAL_MARGIN + penOffset;
931                     penPosition.y -= ascent;
932                     CGContextSetTextPosition(p_context, penPosition.x, penPosition.y);
933                     CTLineDraw(line, p_context);
934                     penPosition.y -= descent + leading;
935
936                 }
937                 *pi_textblock_height = i_height - penPosition.y;
938
939                 CFRelease(frame);
940             }
941             CFRelease(framesetter);
942         }
943         CGContextFlush(p_context);
944         CGContextRelease(p_context);
945     }
946     if (p_colorSpace) CGColorSpaceRelease(p_colorSpace);
947
948     return p_offScreen;
949 }
950
951 static int GetFontSize(filter_t *p_filter)
952 {
953     return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
954 }
955
956 static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
957                        CFMutableAttributedStringRef p_attrString)
958 {
959     offscreen_bitmap_t *p_offScreen = NULL;
960     unsigned      i_textblock_height = 0;
961
962     unsigned i_width = p_filter->fmt_out.video.i_visible_width;
963     unsigned i_height = p_filter->fmt_out.video.i_visible_height;
964     unsigned i_text_align = p_region->i_align & 0x3;
965
966     if (!p_attrString) {
967         msg_Err(p_filter, "Invalid argument to RenderYUVA");
968         return VLC_EGENERIC;
969     }
970
971     p_offScreen = Compose(i_text_align, p_attrString,
972                            i_width, i_height, &i_textblock_height);
973
974     if (!p_offScreen) {
975         msg_Err(p_filter, "No offscreen buffer");
976         return VLC_EGENERIC;
977     }
978
979     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
980     video_format_t fmt;
981     int i_offset;
982     unsigned i_pitch;
983     uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
984
985     // Create a new subpicture region
986     memset(&fmt, 0, sizeof(video_format_t));
987     fmt.i_chroma = VLC_CODEC_YUVA;
988     fmt.i_width = fmt.i_visible_width = i_width;
989     fmt.i_height = fmt.i_visible_height = __MIN(i_height, i_textblock_height + VERTICAL_MARGIN * 2);
990     fmt.i_x_offset = fmt.i_y_offset = 0;
991     fmt.i_sar_num = 1;
992     fmt.i_sar_den = 1;
993
994     p_region->p_picture = picture_NewFromFormat(&fmt);
995     if (!p_region->p_picture)
996         return VLC_EGENERIC;
997     p_region->fmt = fmt;
998
999     p_dst_y = p_region->p_picture->Y_PIXELS;
1000     p_dst_u = p_region->p_picture->U_PIXELS;
1001     p_dst_v = p_region->p_picture->V_PIXELS;
1002     p_dst_a = p_region->p_picture->A_PIXELS;
1003     i_pitch = p_region->p_picture->A_PITCH;
1004
1005     i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1006     for (unsigned y = 0; y < fmt.i_height; y++) {
1007         for (unsigned x = 0; x < fmt.i_width; x++) {
1008             int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel     ];
1009             int i_red   = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1010             int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1011             int i_blue  = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1012
1013             i_y = (uint8_t)__MIN(abs(2104 * i_red  + 4130 * i_green +
1014                               802 * i_blue + 4096 + 131072) >> 13, 235);
1015             i_u = (uint8_t)__MIN(abs(-1214 * i_red  + -2384 * i_green +
1016                              3598 * i_blue + 4096 + 1048576) >> 13, 240);
1017             i_v = (uint8_t)__MIN(abs(3598 * i_red + -3013 * i_green +
1018                               -585 * i_blue + 4096 + 1048576) >> 13, 240);
1019
1020             p_dst_y[ i_offset + x ] = i_y;
1021             p_dst_u[ i_offset + x ] = i_u;
1022             p_dst_v[ i_offset + x ] = i_v;
1023             p_dst_a[ i_offset + x ] = i_alpha;
1024         }
1025         i_offset += i_pitch;
1026     }
1027
1028     free(p_offScreen->p_data);
1029     free(p_offScreen);
1030
1031     return VLC_SUCCESS;
1032 }