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
7 * Authors: Bernie Purcell <bitmap@videolan.org>
8 * Pierre d'Herbemont <pdherbemont # videolan dot>
9 * Felix Paul Kühne <fkuehne # videolan # org>
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.
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.
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 *****************************************************************************/
26 /*****************************************************************************
28 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_stream.h>
38 #include <vlc_input.h>
39 #include <vlc_filter.h>
41 #include <TargetConditionals.h>
44 #include <CoreText/CoreText.h>
45 #include <CoreGraphics/CoreGraphics.h>
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>
55 #define DEFAULT_FONT "Helvetica-Neue"
56 #define DEFAULT_FONT_COLOR 0xffffff
57 #define DEFAULT_REL_FONT_SIZE 16
59 #define VERTICAL_MARGIN 3
60 #define HORIZONTAL_MARGIN 10
62 /*****************************************************************************
64 *****************************************************************************/
65 static int Create (vlc_object_t *);
66 static void Destroy(vlc_object_t *);
68 static int LoadFontsFromAttachments(filter_t *p_filter);
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 *);
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);
81 static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
82 bool b_bold, bool b_italic, bool b_underline, bool b_halfwidth,
84 CFRange p_range, CFMutableAttributedStringRef p_attrString);
86 /*****************************************************************************
88 *****************************************************************************/
90 /* The preferred way to set font style information is for it to come from the
91 * subtitle file, and for it to be rendered with RenderHtml instead of
93 #define FONT_TEXT N_("Font")
94 #define FONT_LONGTEXT N_("Name for the font you want to use")
95 #define FONTSIZER_TEXT N_("Relative font size")
96 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
97 "fonts that will be rendered on the video. If absolute font size is set, "\
98 "relative size will be overridden.")
99 #define COLOR_TEXT N_("Text default color")
100 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
101 "the video. This must be an hexadecimal (like HTML colors). The first two "\
102 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
103 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white")
104 #define OUTLINE_TEXT N_("Add outline")
105 #define SHADOW_TEXT N_("Add shadow")
107 static const int pi_color_values[] = {
108 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
109 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
110 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
112 static const char *const ppsz_color_names[] = {
113 "black", "gray", "silver", "white", "maroon",
114 "red", "fuchsia", "yellow", "olive", "green",
115 "teal", "lime", "purple", "navy", "blue", "aqua" };
117 static const char *const ppsz_color_descriptions[] = {
118 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
119 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
120 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
122 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
123 static const char *const ppsz_sizes_text[] = {
124 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
127 set_shortname(N_("Text renderer for Mac"))
128 set_description(N_("CoreText font renderer"))
129 set_category(CAT_VIDEO)
130 set_subcategory(SUBCAT_VIDEO_SUBPIC)
132 add_string("quartztext-font", DEFAULT_FONT, FONT_TEXT, FONT_LONGTEXT,
134 add_integer("quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, FONTSIZER_TEXT,
135 FONTSIZER_LONGTEXT, false)
136 change_integer_list(pi_sizes, ppsz_sizes_text)
137 add_integer("quartztext-color", 0x00FFFFFF, COLOR_TEXT,
138 COLOR_LONGTEXT, false)
139 change_integer_list(pi_color_values, ppsz_color_descriptions)
140 add_bool("quartztext-outline", false, OUTLINE_TEXT, NULL, false)
141 add_bool("quartztext-shadow", true, SHADOW_TEXT, NULL, false)
142 set_capability("text renderer", 50)
144 set_callbacks(Create, Destroy)
147 typedef struct font_stack_t font_stack_t;
152 uint32_t i_color; // ARGB
154 font_stack_t *p_next;
160 uint32_t i_font_color; /* ARGB */
167 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
168 struct offscreen_bitmap_t
171 int i_bitsPerChannel;
177 /*****************************************************************************
178 * filter_sys_t: quartztext local data
179 *****************************************************************************
180 * This structure is part of the video output thread descriptor.
181 * It describes the freetype specific properties of an output thread.
182 *****************************************************************************/
186 uint8_t i_font_opacity;
192 #ifndef TARGET_OS_IPHONE
193 ATSFontContainerRef *p_fonts;
198 /*****************************************************************************
199 * Create: allocates osd-text video thread output method
200 *****************************************************************************
201 * This function allocates and initializes a Clone vout method.
202 *****************************************************************************/
203 static int Create(vlc_object_t *p_this)
205 filter_t *p_filter = (filter_t *)p_this;
208 // Allocate structure
209 p_filter->p_sys = p_sys = malloc(sizeof(filter_sys_t));
212 p_sys->psz_font_name = var_CreateGetString(p_this, "quartztext-font");
213 p_sys->i_font_opacity = 255;
214 p_sys->i_font_color = VLC_CLIP(var_CreateGetInteger(p_this, "quartztext-color") , 0, 0xFFFFFF);
215 p_sys->b_outline = var_InheritBool(p_this, "quartztext-outline");
216 p_sys->b_shadow = var_InheritBool(p_this, "quartztext-shadow");
217 p_sys->i_font_size = GetFontSize(p_filter);
219 p_filter->pf_render_text = RenderText;
220 p_filter->pf_render_html = RenderHtml;
222 #ifndef TARGET_OS_IPHONE
223 p_sys->p_fonts = NULL;
227 LoadFontsFromAttachments(p_filter);
232 /*****************************************************************************
233 * Destroy: destroy Clone video thread output method
234 *****************************************************************************
235 * Clean up all data and library connections
236 *****************************************************************************/
237 static void Destroy(vlc_object_t *p_this)
239 filter_t *p_filter = (filter_t *)p_this;
240 filter_sys_t *p_sys = p_filter->p_sys;
241 #ifndef TARGET_OS_IPHONE
242 if (p_sys->p_fonts) {
243 for (int k = 0; k < p_sys->i_fonts; k++) {
244 ATSFontDeactivate(p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault);
246 free(p_sys->p_fonts);
249 free(p_sys->psz_font_name);
253 /*****************************************************************************
254 * Make any TTF/OTF fonts present in the attachments of the media file
255 * available to the Quartz engine for text rendering
256 *****************************************************************************/
257 static int LoadFontsFromAttachments(filter_t *p_filter)
259 #ifdef TARGET_OS_IPHONE
260 VLC_UNUSED(p_filter);
263 filter_sys_t *p_sys = p_filter->p_sys;
264 input_attachment_t **pp_attachments;
265 int i_attachments_cnt;
267 if (filter_GetInputAttachments(p_filter, &pp_attachments, &i_attachments_cnt))
271 p_sys->p_fonts = malloc(i_attachments_cnt * sizeof(ATSFontContainerRef));
272 if (! p_sys->p_fonts)
275 for (int k = 0; k < i_attachments_cnt; k++) {
276 input_attachment_t *p_attach = pp_attachments[k];
278 if ((!strcmp(p_attach->psz_mime, "application/x-truetype-font") || // TTF
279 !strcmp(p_attach->psz_mime, "application/x-font-otf")) && // OTF
280 p_attach->i_data > 0 && p_attach->p_data) {
281 ATSFontContainerRef container;
283 if (noErr == ATSFontActivateFromMemory(p_attach->p_data,
285 kATSFontContextLocal,
286 kATSFontFormatUnspecified,
288 kATSOptionFlagsDefault,
290 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
292 vlc_input_attachment_Delete(p_attach);
294 free(pp_attachments);
299 static char *EliminateCRLF(char *psz_string)
303 for (char * p = psz_string; p && *p; p++) {
304 if ((*p == '\r') && (*(p+1) == '\n')) {
305 for (q = p + 1; *q; q++)
314 /* Renders a text subpicture region into another one.
315 * It is used as pf_add_string callback in the vout method by this module */
316 static int RenderText(filter_t *p_filter, subpicture_region_t *p_region_out,
317 subpicture_region_t *p_region_in,
318 const vlc_fourcc_t *p_chroma_list)
320 filter_sys_t *p_sys = p_filter->p_sys;
326 uint32_t i_font_color;
327 bool b_bold, b_uline, b_italic, b_halfwidth;
329 b_bold = b_uline = b_italic = b_halfwidth = FALSE;
330 VLC_UNUSED(p_chroma_list);
332 p_sys->i_font_size = GetFontSize(p_filter);
335 if (!p_region_in || !p_region_out)
338 psz_string = p_region_in->psz_text;
339 if (!psz_string || !*psz_string)
342 if (p_region_in->p_style) {
343 psz_fontname = p_region_in->p_style->psz_fontname ?
344 p_region_in->p_style->psz_fontname : p_sys->psz_font_name;
345 i_font_color = VLC_CLIP(p_region_in->p_style->i_font_color, 0, 0xFFFFFF);
346 i_font_size = VLC_CLIP(p_region_in->p_style->i_font_size, 0, 255);
347 if (p_region_in->p_style->i_style_flags) {
348 if (p_region_in->p_style->i_style_flags & STYLE_BOLD)
350 if (p_region_in->p_style->i_style_flags & STYLE_ITALIC)
352 if (p_region_in->p_style->i_style_flags & STYLE_UNDERLINE)
354 if (p_region_in->p_style->i_style_flags & STYLE_HALFWIDTH)
357 i_spacing = VLC_CLIP(p_region_in->p_style->i_spacing, 0, 255);
359 psz_fontname = p_sys->psz_font_name;
360 i_font_color = p_sys->i_font_color;
361 i_font_size = p_sys->i_font_size;
364 if (i_font_size <= 0) {
365 msg_Warn(p_filter, "invalid fontsize, using 12");
366 if (VLC_SUCCESS == var_Get(p_filter, "scale", &val))
367 i_font_size = 12 * val.i_int / 1000;
372 p_region_out->i_x = p_region_in->i_x;
373 p_region_out->i_y = p_region_in->i_y;
375 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
378 CFStringRef p_cfString;
381 EliminateCRLF(psz_string);
382 p_cfString = CFStringCreateWithCString(NULL, psz_string, kCFStringEncodingUTF8);
383 CFAttributedStringReplaceString(p_attrString, CFRangeMake(0, 0), p_cfString);
384 CFRelease(p_cfString);
385 len = CFAttributedStringGetLength(p_attrString);
387 setFontAttibutes(psz_fontname, i_font_size, i_font_color, b_bold, b_italic, b_uline, b_halfwidth,
389 CFRangeMake(0, len), p_attrString);
391 RenderYUVA(p_filter, p_region_out, p_attrString);
392 CFRelease(p_attrString);
399 static int PushFont(font_stack_t **p_font, const char *psz_name, int i_size,
407 p_new = malloc(sizeof(font_stack_t));
411 p_new->p_next = NULL;
414 p_new->psz_name = strdup(psz_name);
416 p_new->psz_name = NULL;
418 p_new->i_size = i_size;
419 p_new->i_color = i_color;
424 font_stack_t *p_last;
426 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
429 p_last->p_next = p_new;
434 static int PopFont(font_stack_t **p_font)
436 font_stack_t *p_last, *p_next_to_last;
438 if (!p_font || !*p_font)
441 p_next_to_last = NULL;
442 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
443 p_next_to_last = p_last;
446 p_next_to_last->p_next = NULL;
450 free(p_last->psz_name);
456 static int PeekFont(font_stack_t **p_font, char **psz_name, int *i_size,
459 font_stack_t *p_last;
461 if (!p_font || !*p_font)
466 p_last=p_last->p_next)
469 *psz_name = p_last->psz_name;
470 *i_size = p_last->i_size;
471 *i_color = p_last->i_color;
476 static int HandleFontAttributes(xml_reader_t *p_xml_reader,
477 font_stack_t **p_fonts)
480 char *psz_fontname = NULL;
481 uint32_t i_font_color = 0xffffff;
482 int i_font_alpha = 0;
483 int i_font_size = 24;
484 const char *attr, *value;
486 /* Default all attributes to the top font in the stack -- in case not
487 * all attributes are specified in the sub-font */
488 if (VLC_SUCCESS == PeekFont(p_fonts,
492 psz_fontname = strdup(psz_fontname);
493 i_font_size = i_font_size;
495 i_font_alpha = (i_font_color >> 24) & 0xff;
496 i_font_color &= 0x00ffffff;
498 while ((attr = xml_ReaderNextAttr(p_xml_reader, &value))) {
499 if (!strcasecmp("face", attr)) {
501 psz_fontname = strdup(value);
502 } else if (!strcasecmp("size", attr)) {
503 if ((*value == '+') || (*value == '-')) {
504 int i_value = atoi(value);
506 if ((i_value >= -5) && (i_value <= 5))
507 i_font_size += (i_value * i_font_size) / 10;
508 else if (i_value < -5)
509 i_font_size = - i_value;
510 else if (i_value > 5)
511 i_font_size = i_value;
514 i_font_size = atoi(value);
515 } else if (!strcasecmp("color", attr)) {
516 if (value[0] == '#') {
517 i_font_color = strtol(value + 1, NULL, 16);
518 i_font_color &= 0x00ffffff;
520 /* color detection fallback */
521 unsigned int count = sizeof(ppsz_color_names);
522 for (unsigned x = 0; x < count; x++) {
523 if (!strcmp(value, ppsz_color_names[x])) {
524 i_font_color = pi_color_values[x];
529 } else if (!strcasecmp("alpha", attr) && (value[0] == '#')) {
530 i_font_alpha = strtol(value + 1, NULL, 16);
531 i_font_alpha &= 0xff;
534 rv = PushFont(p_fonts,
537 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24));
544 static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
545 bool b_bold, bool b_italic, bool b_underline, bool b_halfwidth,
547 CFRange p_range, CFMutableAttributedStringRef p_attrString)
549 CFStringRef p_cfString;
552 int i_font_width = b_halfwidth ? i_font_size / 2 : i_font_size;
553 CGAffineTransform trans = CGAffineTransformMakeScale((float)i_font_width
556 // fallback on default
558 psz_fontname = (char *)DEFAULT_FONT;
560 p_cfString = CFStringCreateWithCString(kCFAllocatorDefault,
562 kCFStringEncodingUTF8);
563 p_font = CTFontCreateWithName(p_cfString,
566 CFRelease(p_cfString);
567 CFAttributedStringSetAttribute(p_attrString,
569 kCTFontAttributeName,
576 _uline = kCTUnderlineStyleSingle;
578 _uline = kCTUnderlineStyleNone;
580 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
581 CFAttributedStringSetAttribute(p_attrString,
583 kCTUnderlineStyleAttributeName,
585 CFRelease(underline);
594 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
595 CFAttributedStringSetAttribute(p_attrString,
608 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
609 CFAttributedStringSetAttribute(p_attrString,
615 // fetch invalid colors
616 if (i_font_color == 0xFFFFFFFF)
617 i_font_color = 0x00FFFFFF;
619 // Handle foreground color
620 CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
621 CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
622 (float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
623 (float)((i_font_color & 0x000000ff)) / 255.0,
624 (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
625 CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
626 CGColorSpaceRelease(rgbColorSpace);
628 CFAttributedStringSetAttribute(p_attrString,
630 kCTForegroundColorAttributeName,
637 CGFloat spacing = i_spacing;
638 CFNumberRef spacingCFNum = CFNumberCreate(NULL,
639 kCFNumberCGFloatType, &spacing);
640 CFAttributedStringSetAttribute(p_attrString,
642 kCTKernAttributeName,
644 CFRelease(spacingCFNum);
648 static void GetAttrStrFromFontStack(font_stack_t **p_fonts,
649 bool b_bold, bool b_italic, bool b_uline,
650 CFRange p_range, CFMutableAttributedStringRef p_attrString)
652 char *psz_fontname = NULL;
654 uint32_t i_font_color = 0;
656 if (VLC_SUCCESS == PeekFont(p_fonts, &psz_fontname, &i_font_size,
658 setFontAttibutes(psz_fontname,
661 b_bold, b_italic, b_uline, FALSE,
668 static int ProcessNodes(filter_t *p_filter,
669 xml_reader_t *p_xml_reader,
670 text_style_t *p_font_style,
671 CFMutableAttributedStringRef p_attrString)
673 int rv = VLC_SUCCESS;
674 filter_sys_t *p_sys = p_filter->p_sys;
675 font_stack_t *p_fonts = NULL;
680 bool b_italic = false;
682 bool b_uline = false;
685 rv = PushFont(&p_fonts,
686 p_font_style->psz_fontname,
687 p_font_style->i_font_size,
688 (p_font_style->i_font_color & 0xffffff) |
689 ((p_font_style->i_font_alpha & 0xff) << 24));
691 if (p_font_style->i_style_flags & STYLE_BOLD)
693 if (p_font_style->i_style_flags & STYLE_ITALIC)
695 if (p_font_style->i_style_flags & STYLE_UNDERLINE)
698 rv = PushFont(&p_fonts,
699 p_sys->psz_font_name,
701 p_sys->i_font_color);
703 if (rv != VLC_SUCCESS)
706 while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
708 case XML_READER_ENDELEM:
709 if (!strcasecmp("font", node))
711 else if (!strcasecmp("b", node))
713 else if (!strcasecmp("i", node))
715 else if (!strcasecmp("u", node))
719 case XML_READER_STARTELEM:
720 if (!strcasecmp("font", node))
721 rv = HandleFontAttributes(p_xml_reader, &p_fonts);
722 else if (!strcasecmp("b", node))
724 else if (!strcasecmp("i", node))
726 else if (!strcasecmp("u", node))
728 else if (!strcasecmp("br", node)) {
729 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
730 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), CFSTR("\n"));
732 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
735 CFAttributedStringReplaceAttributedString(p_attrString,
736 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
738 CFRelease(p_attrnode);
741 case XML_READER_TEXT:
743 CFStringRef p_cfString;
746 // Turn any multiple-whitespaces into single spaces
747 char *dup = strdup(node);
750 char *s = strpbrk(dup, "\t\r\n ");
753 int i_whitespace = strspn(s, "\t\r\n ");
755 if (i_whitespace > 1)
758 strlen(s) - i_whitespace + 1);
761 s = strpbrk(s, "\t\r\n ");
765 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
766 p_cfString = CFStringCreateWithCString(NULL, dup, kCFStringEncodingUTF8);
767 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), p_cfString);
768 CFRelease(p_cfString);
769 len = CFAttributedStringGetLength(p_attrnode);
771 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
775 CFAttributedStringReplaceAttributedString(p_attrString,
776 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
778 CFRelease(p_attrnode);
786 while(VLC_SUCCESS == PopFont(&p_fonts));
791 static int RenderHtml(filter_t *p_filter, subpicture_region_t *p_region_out,
792 subpicture_region_t *p_region_in,
793 const vlc_fourcc_t *p_chroma_list)
795 int rv = VLC_SUCCESS;
796 stream_t *p_sub = NULL;
798 xml_reader_t *p_xml_reader = NULL;
799 VLC_UNUSED(p_chroma_list);
801 if (!p_region_in || !p_region_in->psz_html)
804 /* Reset the default fontsize in case screen metrics have changed */
805 p_filter->p_sys->i_font_size = GetFontSize(p_filter);
807 p_sub = stream_MemoryNew(VLC_OBJECT(p_filter),
808 (uint8_t *) p_region_in->psz_html,
809 strlen(p_region_in->psz_html),
812 p_xml = xml_Create(p_filter);
814 p_xml_reader = xml_ReaderCreate(p_xml, p_sub);
816 /* Look for Root Node */
818 if (xml_ReaderNextNode(p_xml_reader, &name)
819 == XML_READER_STARTELEM) {
820 if (!strcasecmp("karaoke", name)) {
821 /* We're going to have to render the text a number
822 * of times to show the progress marker on the text.
824 var_SetBool(p_filter, "text-rerender", true);
825 } else if (strcasecmp("text", name)) {
826 /* Only text and karaoke tags are supported */
827 msg_Dbg(p_filter, "Unsupported top-level tag "
828 "<%s> ignored.", name);
832 msg_Err(p_filter, "Malformed HTML subtitle");
836 if (rv != VLC_SUCCESS) {
837 xml_ReaderDelete(p_xml_reader);
845 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
846 rv = ProcessNodes(p_filter, p_xml_reader,
847 p_region_in->p_style, p_attrString);
849 i_len = CFAttributedStringGetLength(p_attrString);
851 p_region_out->i_x = p_region_in->i_x;
852 p_region_out->i_y = p_region_in->i_y;
854 if ((rv == VLC_SUCCESS) && (i_len > 0))
855 RenderYUVA(p_filter, p_region_out, p_attrString);
857 CFRelease(p_attrString);
859 xml_ReaderDelete(p_xml_reader);
863 stream_Delete(p_sub);
869 static CGContextRef CreateOffScreenContext(int i_width, int i_height,
870 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace)
872 offscreen_bitmap_t *p_bitmap;
873 CGContextRef p_context = NULL;
875 p_bitmap = (offscreen_bitmap_t *) malloc(sizeof(offscreen_bitmap_t));
877 p_bitmap->i_bitsPerChannel = 8;
878 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
879 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
880 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
882 p_bitmap->p_data = calloc(i_height, p_bitmap->i_bytesPerRow);
884 *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
886 if (p_bitmap->p_data && *pp_colorSpace)
887 p_context = CGBitmapContextCreate(p_bitmap->p_data, i_width, i_height,
888 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
889 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
892 if (CGContextSetAllowsAntialiasing != NULL)
893 CGContextSetAllowsAntialiasing(p_context, true);
895 *pp_memory = p_bitmap;
901 static offscreen_bitmap_t *Compose(filter_t *p_filter,
902 subpicture_region_t *p_region,
903 CFMutableAttributedStringRef p_attrString,
906 unsigned *pi_textblock_height)
908 filter_sys_t *p_sys = p_filter->p_sys;
909 offscreen_bitmap_t *p_offScreen = NULL;
910 CGColorSpaceRef p_colorSpace = NULL;
911 CGContextRef p_context = NULL;
913 p_context = CreateOffScreenContext(i_width, i_height, &p_offScreen, &p_colorSpace);
915 *pi_textblock_height = 0;
919 CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
921 if (p_region->i_align & SUBPICTURE_ALIGN_RIGHT)
923 else if ((p_region->i_align & SUBPICTURE_ALIGN_LEFT) == 0)
928 // Create the framesetter with the attributed string.
929 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
932 CGMutablePathRef p_path = CGPathCreateMutable();
933 CGRect p_bounds = CGRectMake((float)HORIZONTAL_MARGIN,
934 (float)VERTICAL_MARGIN,
935 (float)(i_width - HORIZONTAL_MARGIN*2),
936 (float)(i_height - VERTICAL_MARGIN *2));
937 CGPathAddRect(p_path, NULL, p_bounds);
939 // Create the frame and draw it into the graphics context
940 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
942 CGPathRelease(p_path);
944 // Set up black outlining of the text --
945 if (p_sys->b_outline)
947 CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
948 CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
954 // TODO: Use CGContextSetShadowWithColor.
955 // TODO: Use user defined parrameters (color, distance, etc.)
956 CGContextSetShadow(p_context, CGSizeMake(3.0f, -3.0f), 2.0f);
963 lines = CTFrameGetLines(frame);
964 penPosition.y = i_height;
965 for (int i=0; i<CFArrayGetCount(lines); i++) {
966 CGFloat ascent, descent, leading;
968 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
969 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
971 // Set the outlining for this line to be dependant on the size of the line -
972 // make it about 5% of the ascent, with a minimum at 1.0
973 float f_thickness = ascent * 0.05;
974 CGContextSetLineWidth(p_context, ((f_thickness > 1.0) ? 1.0 : f_thickness));
976 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
977 penPosition.x = HORIZONTAL_MARGIN + penOffset;
978 if (horiz_flush == 0.0)
979 penPosition.x = p_region->i_x;
980 penPosition.y -= ascent;
981 CGContextSetTextPosition(p_context, penPosition.x, penPosition.y);
982 CTLineDraw(line, p_context);
983 penPosition.y -= descent + leading;
986 *pi_textblock_height = i_height - penPosition.y;
990 CFRelease(framesetter);
992 CGContextFlush(p_context);
993 CGContextRelease(p_context);
995 if (p_colorSpace) CGColorSpaceRelease(p_colorSpace);
1000 static int GetFontSize(filter_t *p_filter)
1004 int i_ratio = var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" );
1006 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
1010 msg_Warn( p_filter, "invalid fontsize, using 12" );
1016 static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
1017 CFMutableAttributedStringRef p_attrString)
1019 offscreen_bitmap_t *p_offScreen = NULL;
1020 unsigned i_textblock_height = 0;
1022 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
1023 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
1025 if (!p_attrString) {
1026 msg_Err(p_filter, "Invalid argument to RenderYUVA");
1027 return VLC_EGENERIC;
1030 p_offScreen = Compose(p_filter, p_region, p_attrString,
1031 i_width, i_height, &i_textblock_height);
1034 msg_Err(p_filter, "No offscreen buffer");
1035 return VLC_EGENERIC;
1038 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1042 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1044 // Create a new subpicture region
1045 memset(&fmt, 0, sizeof(video_format_t));
1046 fmt.i_chroma = VLC_CODEC_YUVA;
1047 fmt.i_width = fmt.i_visible_width = i_width;
1048 fmt.i_height = fmt.i_visible_height = __MIN(i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1049 fmt.i_x_offset = fmt.i_y_offset = 0;
1053 p_region->p_picture = picture_NewFromFormat(&fmt);
1054 if (!p_region->p_picture) {
1055 free(p_offScreen->p_data);
1057 return VLC_EGENERIC;
1059 p_region->fmt = fmt;
1061 p_dst_y = p_region->p_picture->Y_PIXELS;
1062 p_dst_u = p_region->p_picture->U_PIXELS;
1063 p_dst_v = p_region->p_picture->V_PIXELS;
1064 p_dst_a = p_region->p_picture->A_PIXELS;
1065 i_pitch = p_region->p_picture->A_PITCH;
1067 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1068 for (unsigned y = 0; y < fmt.i_height; y++) {
1069 for (unsigned x = 0; x < fmt.i_width; x++) {
1070 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1071 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1072 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1073 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1075 i_y = (uint8_t)__MIN(abs(2104 * i_red + 4130 * i_green +
1076 802 * i_blue + 4096 + 131072) >> 13, 235);
1077 i_u = (uint8_t)__MIN(abs(-1214 * i_red + -2384 * i_green +
1078 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1079 i_v = (uint8_t)__MIN(abs(3598 * i_red + -3013 * i_green +
1080 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1082 p_dst_y[ i_offset + x ] = i_y;
1083 p_dst_u[ i_offset + x ] = i_u;
1084 p_dst_v[ i_offset + x ] = i_v;
1085 p_dst_a[ i_offset + x ] = i_alpha;
1087 i_offset += i_pitch;
1090 free(p_offScreen->p_data);