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,
83 CFRange p_range, CFMutableAttributedStringRef p_attrString);
85 /*****************************************************************************
87 *****************************************************************************/
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
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")
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 };
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" };
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") };
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") };
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)
129 add_string("quartztext-font", DEFAULT_FONT, FONT_TEXT, FONT_LONGTEXT,
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)
139 set_callbacks(Create, Destroy)
142 typedef struct font_stack_t font_stack_t;
147 uint32_t i_color; // ARGB
149 font_stack_t *p_next;
155 uint32_t i_font_color; /* ARGB */
162 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
163 struct offscreen_bitmap_t
166 int i_bitsPerChannel;
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 *****************************************************************************/
181 uint8_t i_font_opacity;
185 #ifndef TARGET_OS_IPHONE
186 ATSFontContainerRef *p_fonts;
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)
198 filter_t *p_filter = (filter_t *)p_this;
201 // Allocate structure
202 p_filter->p_sys = p_sys = malloc(sizeof(filter_sys_t));
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);
210 p_filter->pf_render_text = RenderText;
211 p_filter->pf_render_html = RenderHtml;
213 #ifndef TARGET_OS_IPHONE
214 p_sys->p_fonts = NULL;
218 LoadFontsFromAttachments(p_filter);
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)
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);
237 free(p_sys->p_fonts);
240 free(p_sys->psz_font_name);
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)
250 #ifdef TARGET_OS_IPHONE
251 VLC_UNUSED(p_filter);
254 filter_sys_t *p_sys = p_filter->p_sys;
255 input_attachment_t **pp_attachments;
256 int i_attachments_cnt;
258 if (filter_GetInputAttachments(p_filter, &pp_attachments, &i_attachments_cnt))
262 p_sys->p_fonts = malloc(i_attachments_cnt * sizeof(ATSFontContainerRef));
263 if (! p_sys->p_fonts)
266 for (int k = 0; k < i_attachments_cnt; k++) {
267 input_attachment_t *p_attach = pp_attachments[k];
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;
274 if (noErr == ATSFontActivateFromMemory(p_attach->p_data,
276 kATSFontContextLocal,
277 kATSFontFormatUnspecified,
279 kATSOptionFlagsDefault,
281 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
283 vlc_input_attachment_Delete(p_attach);
285 free(pp_attachments);
290 static char *EliminateCRLF(char *psz_string)
294 for (char * p = psz_string; p && *p; p++) {
295 if ((*p == '\r') && (*(p+1) == '\n')) {
296 for (q = p + 1; *q; q++)
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)
311 filter_sys_t *p_sys = p_filter->p_sys;
313 int i_font_alpha, i_font_size;
314 uint32_t i_font_color;
315 bool b_bold, b_uline, b_italic;
317 b_bold = b_uline = b_italic = FALSE;
318 VLC_UNUSED(p_chroma_list);
320 p_sys->i_font_size = GetFontSize(p_filter);
323 if (!p_region_in || !p_region_out)
326 psz_string = p_region_in->psz_text;
327 if (!psz_string || !*psz_string)
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)
337 if (p_region_in->p_style->i_style_flags & STYLE_ITALIC)
339 if (p_region_in->p_style->i_style_flags & STYLE_UNDERLINE)
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;
349 i_font_alpha = 255 - p_sys->i_font_opacity;
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;
359 p_region_out->i_x = p_region_in->i_x;
360 p_region_out->i_y = p_region_in->i_y;
362 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
365 CFStringRef p_cfString;
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);
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);
377 RenderYUVA(p_filter, p_region_out, p_attrString);
378 CFRelease(p_attrString);
385 static int PushFont(font_stack_t **p_font, const char *psz_name, int i_size,
393 p_new = malloc(sizeof(font_stack_t));
397 p_new->p_next = NULL;
400 p_new->psz_name = strdup(psz_name);
402 p_new->psz_name = NULL;
404 p_new->i_size = i_size;
405 p_new->i_color = i_color;
410 font_stack_t *p_last;
412 for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
415 p_last->p_next = p_new;
420 static int PopFont(font_stack_t **p_font)
422 font_stack_t *p_last, *p_next_to_last;
424 if (!p_font || !*p_font)
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;
432 p_next_to_last->p_next = NULL;
436 free(p_last->psz_name);
442 static int PeekFont(font_stack_t **p_font, char **psz_name, int *i_size,
445 font_stack_t *p_last;
447 if (!p_font || !*p_font)
452 p_last=p_last->p_next)
455 *psz_name = p_last->psz_name;
456 *i_size = p_last->i_size;
457 *i_color = p_last->i_color;
462 static int HandleFontAttributes(xml_reader_t *p_xml_reader,
463 font_stack_t **p_fonts)
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;
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,
478 psz_fontname = strdup(psz_fontname);
479 i_font_size = i_font_size;
481 i_font_alpha = (i_font_color >> 24) & 0xff;
482 i_font_color &= 0x00ffffff;
484 while ((attr = xml_ReaderNextAttr(p_xml_reader, &value))) {
485 if (!strcasecmp("face", attr)) {
487 psz_fontname = strdup(value);
488 } else if (!strcasecmp("size", attr)) {
489 if ((*value == '+') || (*value == '-')) {
490 int i_value = atoi(value);
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;
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;
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];
515 } else if (!strcasecmp("alpha", attr) && (value[0] == '#')) {
516 i_font_alpha = strtol(value + 1, NULL, 16);
517 i_font_alpha &= 0xff;
520 rv = PushFont(p_fonts,
523 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24));
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)
534 CFStringRef p_cfString;
537 // fallback on default
539 psz_fontname = (char *)DEFAULT_FONT;
541 p_cfString = CFStringCreateWithCString(kCFAllocatorDefault,
543 kCFStringEncodingUTF8);
544 p_font = CTFontCreateWithName(p_cfString,
547 CFRelease(p_cfString);
548 CFAttributedStringSetAttribute(p_attrString,
550 kCTFontAttributeName,
557 _uline = kCTUnderlineStyleSingle;
559 _uline = kCTUnderlineStyleNone;
561 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
562 CFAttributedStringSetAttribute(p_attrString,
564 kCTUnderlineStyleAttributeName,
566 CFRelease(underline);
575 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
576 CFAttributedStringSetAttribute(p_attrString,
589 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
590 CFAttributedStringSetAttribute(p_attrString,
596 // fetch invalid colors
597 if (i_font_color == 0xFFFFFFFF)
598 i_font_color = 0x00FFFFFF;
600 // Handle foreground color
601 CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
602 CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
603 (float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
604 (float)((i_font_color & 0x000000ff)) / 255.0,
605 (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
606 CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
607 CGColorSpaceRelease(rgbColorSpace);
609 CFAttributedStringSetAttribute(p_attrString,
611 kCTForegroundColorAttributeName,
617 static void GetAttrStrFromFontStack(font_stack_t **p_fonts,
618 bool b_bold, bool b_italic, bool b_uline,
619 CFRange p_range, CFMutableAttributedStringRef p_attrString)
621 char *psz_fontname = NULL;
623 uint32_t i_font_color = 0;
625 if (VLC_SUCCESS == PeekFont(p_fonts, &psz_fontname, &i_font_size,
627 setFontAttibutes(psz_fontname,
630 b_bold, b_italic, b_uline,
636 static int ProcessNodes(filter_t *p_filter,
637 xml_reader_t *p_xml_reader,
638 text_style_t *p_font_style,
639 CFMutableAttributedStringRef p_attrString)
641 int rv = VLC_SUCCESS;
642 filter_sys_t *p_sys = p_filter->p_sys;
643 font_stack_t *p_fonts = NULL;
648 bool b_italic = false;
650 bool b_uline = false;
653 rv = PushFont(&p_fonts,
654 p_font_style->psz_fontname,
655 p_font_style->i_font_size,
656 (p_font_style->i_font_color & 0xffffff) |
657 ((p_font_style->i_font_alpha & 0xff) << 24));
659 if (p_font_style->i_style_flags & STYLE_BOLD)
661 if (p_font_style->i_style_flags & STYLE_ITALIC)
663 if (p_font_style->i_style_flags & STYLE_UNDERLINE)
666 rv = PushFont(&p_fonts,
667 p_sys->psz_font_name,
669 p_sys->i_font_color);
671 if (rv != VLC_SUCCESS)
674 while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
676 case XML_READER_ENDELEM:
677 if (!strcasecmp("font", node))
679 else if (!strcasecmp("b", node))
681 else if (!strcasecmp("i", node))
683 else if (!strcasecmp("u", node))
687 case XML_READER_STARTELEM:
688 if (!strcasecmp("font", node))
689 rv = HandleFontAttributes(p_xml_reader, &p_fonts);
690 else if (!strcasecmp("b", node))
692 else if (!strcasecmp("i", node))
694 else if (!strcasecmp("u", node))
696 else if (!strcasecmp("br", node)) {
697 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
698 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), CFSTR("\n"));
700 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
703 CFAttributedStringReplaceAttributedString(p_attrString,
704 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
706 CFRelease(p_attrnode);
709 case XML_READER_TEXT:
711 CFStringRef p_cfString;
714 // Turn any multiple-whitespaces into single spaces
715 char *dup = strdup(node);
718 char *s = strpbrk(dup, "\t\r\n ");
721 int i_whitespace = strspn(s, "\t\r\n ");
723 if (i_whitespace > 1)
726 strlen(s) - i_whitespace + 1);
729 s = strpbrk(s, "\t\r\n ");
733 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
734 p_cfString = CFStringCreateWithCString(NULL, dup, kCFStringEncodingUTF8);
735 CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), p_cfString);
736 CFRelease(p_cfString);
737 len = CFAttributedStringGetLength(p_attrnode);
739 GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
743 CFAttributedStringReplaceAttributedString(p_attrString,
744 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
746 CFRelease(p_attrnode);
754 while(VLC_SUCCESS == PopFont(&p_fonts));
759 static int RenderHtml(filter_t *p_filter, subpicture_region_t *p_region_out,
760 subpicture_region_t *p_region_in,
761 const vlc_fourcc_t *p_chroma_list)
763 int rv = VLC_SUCCESS;
764 stream_t *p_sub = NULL;
766 xml_reader_t *p_xml_reader = NULL;
767 VLC_UNUSED(p_chroma_list);
769 if (!p_region_in || !p_region_in->psz_html)
772 /* Reset the default fontsize in case screen metrics have changed */
773 p_filter->p_sys->i_font_size = GetFontSize(p_filter);
775 p_sub = stream_MemoryNew(VLC_OBJECT(p_filter),
776 (uint8_t *) p_region_in->psz_html,
777 strlen(p_region_in->psz_html),
780 p_xml = xml_Create(p_filter);
782 bool b_karaoke = false;
784 p_xml_reader = xml_ReaderCreate(p_xml, p_sub);
786 /* Look for Root Node */
788 if (xml_ReaderNextNode(p_xml_reader, &name)
789 == XML_READER_STARTELEM) {
790 if (!strcasecmp("karaoke", name)) {
791 /* We're going to have to render the text a number
792 * of times to show the progress marker on the text.
794 var_SetBool(p_filter, "text-rerender", true);
796 } else if (!strcasecmp("text", name))
799 /* Only text and karaoke tags are supported */
800 msg_Dbg(p_filter, "Unsupported top-level tag "
801 "<%s> ignored.", name);
805 msg_Err(p_filter, "Malformed HTML subtitle");
809 if (rv != VLC_SUCCESS) {
810 xml_ReaderDelete(p_xml_reader);
818 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
819 rv = ProcessNodes(p_filter, p_xml_reader,
820 p_region_in->p_style, p_attrString);
822 i_len = CFAttributedStringGetLength(p_attrString);
824 p_region_out->i_x = p_region_in->i_x;
825 p_region_out->i_y = p_region_in->i_y;
827 if ((rv == VLC_SUCCESS) && (i_len > 0))
828 RenderYUVA(p_filter, p_region_out, p_attrString);
830 CFRelease(p_attrString);
832 xml_ReaderDelete(p_xml_reader);
836 stream_Delete(p_sub);
842 static CGContextRef CreateOffScreenContext(int i_width, int i_height,
843 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace)
845 offscreen_bitmap_t *p_bitmap;
846 CGContextRef p_context = NULL;
848 p_bitmap = (offscreen_bitmap_t *) malloc(sizeof(offscreen_bitmap_t));
850 p_bitmap->i_bitsPerChannel = 8;
851 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
852 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
853 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
855 p_bitmap->p_data = calloc(i_height, p_bitmap->i_bytesPerRow);
857 *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
859 if (p_bitmap->p_data && *pp_colorSpace)
860 p_context = CGBitmapContextCreate(p_bitmap->p_data, i_width, i_height,
861 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
862 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
865 if (CGContextSetAllowsAntialiasing != NULL)
866 CGContextSetAllowsAntialiasing(p_context, true);
868 *pp_memory = p_bitmap;
874 static offscreen_bitmap_t *Compose(int i_text_align,
875 CFMutableAttributedStringRef p_attrString,
878 unsigned *pi_textblock_height)
880 offscreen_bitmap_t *p_offScreen = NULL;
881 CGColorSpaceRef p_colorSpace = NULL;
882 CGContextRef p_context = NULL;
884 p_context = CreateOffScreenContext(i_width, i_height, &p_offScreen, &p_colorSpace);
886 *pi_textblock_height = 0;
890 CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
892 if (i_text_align == SUBPICTURE_ALIGN_RIGHT)
894 else if (i_text_align != SUBPICTURE_ALIGN_LEFT)
899 // Create the framesetter with the attributed string.
900 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
903 CGMutablePathRef p_path = CGPathCreateMutable();
904 CGRect p_bounds = CGRectMake((float)HORIZONTAL_MARGIN,
905 (float)VERTICAL_MARGIN,
906 (float)(i_width - HORIZONTAL_MARGIN*2),
907 (float)(i_height - VERTICAL_MARGIN *2));
908 CGPathAddRect(p_path, NULL, p_bounds);
910 // Create the frame and draw it into the graphics context
911 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
913 CGPathRelease(p_path);
915 // Set up black outlining of the text --
916 CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
917 CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
923 lines = CTFrameGetLines(frame);
924 penPosition.y = i_height;
925 for (int i=0; i<CFArrayGetCount(lines); i++) {
926 CGFloat ascent, descent, leading;
928 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
929 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
931 // Set the outlining for this line to be dependant on the size of the line -
932 // make it about 5% of the ascent, with a minimum at 1.0
933 float f_thickness = ascent * 0.05;
934 CGContextSetLineWidth(p_context, ((f_thickness > 1.0) ? 1.0 : f_thickness));
936 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
937 penPosition.x = HORIZONTAL_MARGIN + penOffset;
938 penPosition.y -= ascent;
939 CGContextSetTextPosition(p_context, penPosition.x, penPosition.y);
940 CTLineDraw(line, p_context);
941 penPosition.y -= descent + leading;
944 *pi_textblock_height = i_height - penPosition.y;
948 CFRelease(framesetter);
950 CGContextFlush(p_context);
951 CGContextRelease(p_context);
953 if (p_colorSpace) CGColorSpaceRelease(p_colorSpace);
958 static int GetFontSize(filter_t *p_filter)
962 int i_ratio = var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" );
964 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
968 msg_Warn( p_filter, "invalid fontsize, using 12" );
974 static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
975 CFMutableAttributedStringRef p_attrString)
977 offscreen_bitmap_t *p_offScreen = NULL;
978 unsigned i_textblock_height = 0;
980 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
981 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
982 unsigned i_text_align = p_region->i_align & 0x3;
985 msg_Err(p_filter, "Invalid argument to RenderYUVA");
989 p_offScreen = Compose(i_text_align, p_attrString,
990 i_width, i_height, &i_textblock_height);
993 msg_Err(p_filter, "No offscreen buffer");
997 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1001 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1003 // Create a new subpicture region
1004 memset(&fmt, 0, sizeof(video_format_t));
1005 fmt.i_chroma = VLC_CODEC_YUVA;
1006 fmt.i_width = fmt.i_visible_width = i_width;
1007 fmt.i_height = fmt.i_visible_height = __MIN(i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1008 fmt.i_x_offset = fmt.i_y_offset = 0;
1012 p_region->p_picture = picture_NewFromFormat(&fmt);
1013 if (!p_region->p_picture)
1014 return VLC_EGENERIC;
1015 p_region->fmt = fmt;
1017 p_dst_y = p_region->p_picture->Y_PIXELS;
1018 p_dst_u = p_region->p_picture->U_PIXELS;
1019 p_dst_v = p_region->p_picture->V_PIXELS;
1020 p_dst_a = p_region->p_picture->A_PIXELS;
1021 i_pitch = p_region->p_picture->A_PITCH;
1023 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1024 for (unsigned y = 0; y < fmt.i_height; y++) {
1025 for (unsigned x = 0; x < fmt.i_width; x++) {
1026 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1027 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1028 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1029 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1031 i_y = (uint8_t)__MIN(abs(2104 * i_red + 4130 * i_green +
1032 802 * i_blue + 4096 + 131072) >> 13, 235);
1033 i_u = (uint8_t)__MIN(abs(-1214 * i_red + -2384 * i_green +
1034 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1035 i_v = (uint8_t)__MIN(abs(3598 * i_red + -3013 * i_green +
1036 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1038 p_dst_y[ i_offset + x ] = i_y;
1039 p_dst_u[ i_offset + x ] = i_u;
1040 p_dst_v[ i_offset + x ] = i_v;
1041 p_dst_a[ i_offset + x ] = i_alpha;
1043 i_offset += i_pitch;
1046 free(p_offScreen->p_data);