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