A little more detail regarding using my github copies of the code with where it's...
[vlc/adversarial.git] / modules / text_renderer / quartztext.c
blob3b8e6cac6e26abbf8b91e3dc875c996604c1797c
1 /*****************************************************************************
2 * quartztext.c : Put text on the video, using Mac OS X Quartz Engine
3 *****************************************************************************
4 * Copyright (C) 2007, 2009, 2012 VLC authors and VideoLAN
5 * $Id$
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 /*****************************************************************************
27 * Preamble
28 *****************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_stream.h>
37 #include <vlc_xml.h>
38 #include <vlc_input.h>
39 #include <vlc_filter.h>
41 #include <TargetConditionals.h>
43 #if TARGET_OS_IPHONE
44 #include <CoreText/CoreText.h>
45 #include <CoreGraphics/CoreGraphics.h>
47 #else
48 // Fix ourselves ColorSync headers that gets included in ApplicationServices.
49 #define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
50 #define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
51 #define __MACHINEEXCEPTIONS__
52 #include <ApplicationServices/ApplicationServices.h>
53 #endif
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 /*****************************************************************************
63 * Local prototypes
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,
83 int i_spacing,
84 CFRange p_range, CFMutableAttributedStringRef p_attrString);
86 /*****************************************************************************
87 * Module descriptor
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
92 * RenderText. */
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") };
126 vlc_module_begin ()
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,
133 false)
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)
143 add_shortcut("text")
144 set_callbacks(Create, Destroy)
145 vlc_module_end ()
147 typedef struct font_stack_t font_stack_t;
148 struct font_stack_t
150 char *psz_name;
151 int i_size;
152 uint32_t i_color; // ARGB
154 font_stack_t *p_next;
157 typedef struct
159 int i_font_size;
160 uint32_t i_font_color; /* ARGB */
161 bool b_italic;
162 bool b_bold;
163 bool b_underline;
164 char *psz_fontname;
165 } ft_style_t;
167 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
168 struct offscreen_bitmap_t
170 uint8_t *p_data;
171 int i_bitsPerChannel;
172 int i_bitsPerPixel;
173 int i_bytesPerPixel;
174 int i_bytesPerRow;
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 *****************************************************************************/
183 struct filter_sys_t
185 char *psz_font_name;
186 uint8_t i_font_opacity;
187 int i_font_color;
188 int i_font_size;
189 bool b_outline;
190 bool b_shadow;
192 #ifndef TARGET_OS_IPHONE
193 ATSFontContainerRef *p_fonts;
194 int i_fonts;
195 #endif
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;
206 filter_sys_t *p_sys;
208 // Allocate structure
209 p_filter->p_sys = p_sys = malloc(sizeof(filter_sys_t));
210 if (!p_sys)
211 return VLC_ENOMEM;
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;
224 p_sys->i_fonts = 0;
225 #endif
227 LoadFontsFromAttachments(p_filter);
229 return VLC_SUCCESS;
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);
248 #endif
249 free(p_sys->psz_font_name);
250 free(p_sys);
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);
261 return VLC_SUCCESS;
262 #else
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))
268 return VLC_EGENERIC;
270 p_sys->i_fonts = 0;
271 p_sys->p_fonts = malloc(i_attachments_cnt * sizeof(ATSFontContainerRef));
272 if (! p_sys->p_fonts)
273 return VLC_ENOMEM;
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,
284 p_attach->i_data,
285 kATSFontContextLocal,
286 kATSFontFormatUnspecified,
287 NULL,
288 kATSOptionFlagsDefault,
289 &container))
290 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
292 vlc_input_attachment_Delete(p_attach);
294 free(pp_attachments);
295 return VLC_SUCCESS;
296 #endif
299 static char *EliminateCRLF(char *psz_string)
301 char *q;
303 for (char * p = psz_string; p && *p; p++) {
304 if ((*p == '\r') && (*(p+1) == '\n')) {
305 for (q = p + 1; *q; q++)
306 *(q - 1) = *q;
308 *(q - 1) = '\0';
311 return psz_string;
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;
321 char *psz_string;
322 char *psz_fontname;
323 int i_font_size;
324 int i_spacing = 0;
325 int i_font_alpha;
326 uint32_t i_font_color;
327 bool b_bold, b_uline, b_italic, b_halfwidth;
328 vlc_value_t val;
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);
334 // Sanity check
335 if (!p_region_in || !p_region_out)
336 return VLC_EGENERIC;
338 psz_string = p_region_in->psz_text;
339 if (!psz_string || !*psz_string)
340 return VLC_EGENERIC;
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)
349 b_bold = TRUE;
350 if (p_region_in->p_style->i_style_flags & STYLE_ITALIC)
351 b_italic = TRUE;
352 if (p_region_in->p_style->i_style_flags & STYLE_UNDERLINE)
353 b_uline = TRUE;
354 if (p_region_in->p_style->i_style_flags & STYLE_HALFWIDTH)
355 b_halfwidth = TRUE;
357 i_spacing = VLC_CLIP(p_region_in->p_style->i_spacing, 0, 255);
358 } else {
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;
368 else
369 i_font_size = 12;
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);
377 if (p_attrString) {
378 CFStringRef p_cfString;
379 int len;
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,
388 i_spacing,
389 CFRangeMake(0, len), p_attrString);
391 RenderYUVA(p_filter, p_region_out, p_attrString);
392 CFRelease(p_attrString);
395 return VLC_SUCCESS;
399 static int PushFont(font_stack_t **p_font, const char *psz_name, int i_size,
400 uint32_t i_color)
402 font_stack_t *p_new;
404 if (!p_font)
405 return VLC_EGENERIC;
407 p_new = malloc(sizeof(font_stack_t));
408 if (! p_new)
409 return VLC_ENOMEM;
411 p_new->p_next = NULL;
413 if (psz_name)
414 p_new->psz_name = strdup(psz_name);
415 else
416 p_new->psz_name = NULL;
418 p_new->i_size = i_size;
419 p_new->i_color = i_color;
421 if (!*p_font)
422 *p_font = p_new;
423 else {
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;
431 return VLC_SUCCESS;
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)
439 return VLC_EGENERIC;
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;
445 if (p_next_to_last)
446 p_next_to_last->p_next = NULL;
447 else
448 *p_font = NULL;
450 free(p_last->psz_name);
451 free(p_last);
453 return VLC_SUCCESS;
456 static int PeekFont(font_stack_t **p_font, char **psz_name, int *i_size,
457 uint32_t *i_color)
459 font_stack_t *p_last;
461 if (!p_font || !*p_font)
462 return VLC_EGENERIC;
464 for (p_last=*p_font;
465 p_last->p_next;
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;
473 return VLC_SUCCESS;
476 static int HandleFontAttributes(xml_reader_t *p_xml_reader,
477 font_stack_t **p_fonts)
479 int rv;
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,
489 &psz_fontname,
490 &i_font_size,
491 &i_font_color)) {
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)) {
500 free(psz_fontname);
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;
513 else
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;
519 } else {
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];
525 break;
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,
535 psz_fontname,
536 i_font_size,
537 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24));
539 free(psz_fontname);
541 return rv;
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,
546 int i_spacing,
547 CFRange p_range, CFMutableAttributedStringRef p_attrString)
549 CFStringRef p_cfString;
550 CTFontRef p_font;
552 int i_font_width = b_halfwidth ? i_font_size / 2 : i_font_size;
553 CGAffineTransform trans = CGAffineTransformMakeScale((float)i_font_width
554 / i_font_size, 1.0);
556 // fallback on default
557 if (!psz_fontname)
558 psz_fontname = (char *)DEFAULT_FONT;
560 p_cfString = CFStringCreateWithCString(kCFAllocatorDefault,
561 psz_fontname,
562 kCFStringEncodingUTF8);
563 p_font = CTFontCreateWithName(p_cfString,
564 (float)i_font_size,
565 &trans);
566 CFRelease(p_cfString);
567 CFAttributedStringSetAttribute(p_attrString,
568 p_range,
569 kCTFontAttributeName,
570 p_font);
571 CFRelease(p_font);
573 // Handle Underline
574 SInt32 _uline;
575 if (b_underline)
576 _uline = kCTUnderlineStyleSingle;
577 else
578 _uline = kCTUnderlineStyleNone;
580 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
581 CFAttributedStringSetAttribute(p_attrString,
582 p_range,
583 kCTUnderlineStyleAttributeName,
584 underline);
585 CFRelease(underline);
587 // Handle Bold
588 float _weight;
589 if (b_bold)
590 _weight = 0.5;
591 else
592 _weight = 0.0;
594 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
595 CFAttributedStringSetAttribute(p_attrString,
596 p_range,
597 kCTFontWeightTrait,
598 weight);
599 CFRelease(weight);
601 // Handle Italic
602 float _slant;
603 if (b_italic)
604 _slant = 1.0;
605 else
606 _slant = 0.0;
608 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
609 CFAttributedStringSetAttribute(p_attrString,
610 p_range,
611 kCTFontSlantTrait,
612 slant);
613 CFRelease(slant);
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,
629 p_range,
630 kCTForegroundColorAttributeName,
631 fg_text);
632 CFRelease(fg_text);
634 // spacing
635 if (i_spacing > 0)
637 CGFloat spacing = i_spacing;
638 CFNumberRef spacingCFNum = CFNumberCreate(NULL,
639 kCFNumberCGFloatType, &spacing);
640 CFAttributedStringSetAttribute(p_attrString,
641 p_range,
642 kCTKernAttributeName,
643 spacingCFNum);
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;
653 int i_font_size = 0;
654 uint32_t i_font_color = 0;
656 if (VLC_SUCCESS == PeekFont(p_fonts, &psz_fontname, &i_font_size,
657 &i_font_color)) {
658 setFontAttibutes(psz_fontname,
659 i_font_size,
660 i_font_color,
661 b_bold, b_italic, b_uline, FALSE,
663 p_range,
664 p_attrString);
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;
677 int type;
678 const char *node;
680 bool b_italic = false;
681 bool b_bold = false;
682 bool b_uline = false;
684 if (p_font_style) {
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)
692 b_bold = true;
693 if (p_font_style->i_style_flags & STYLE_ITALIC)
694 b_italic = true;
695 if (p_font_style->i_style_flags & STYLE_UNDERLINE)
696 b_uline = true;
697 } else {
698 rv = PushFont(&p_fonts,
699 p_sys->psz_font_name,
700 p_sys->i_font_size,
701 p_sys->i_font_color);
703 if (rv != VLC_SUCCESS)
704 return rv;
706 while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
707 switch (type) {
708 case XML_READER_ENDELEM:
709 if (!strcasecmp("font", node))
710 PopFont(&p_fonts);
711 else if (!strcasecmp("b", node))
712 b_bold = false;
713 else if (!strcasecmp("i", node))
714 b_italic = false;
715 else if (!strcasecmp("u", node))
716 b_uline = false;
718 break;
719 case XML_READER_STARTELEM:
720 if (!strcasecmp("font", node))
721 rv = HandleFontAttributes(p_xml_reader, &p_fonts);
722 else if (!strcasecmp("b", node))
723 b_bold = true;
724 else if (!strcasecmp("i", node))
725 b_italic = true;
726 else if (!strcasecmp("u", node))
727 b_uline = true;
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,
733 CFRangeMake(0, 1),
734 p_attrnode);
735 CFAttributedStringReplaceAttributedString(p_attrString,
736 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
737 p_attrnode);
738 CFRelease(p_attrnode);
740 break;
741 case XML_READER_TEXT:
743 CFStringRef p_cfString;
744 int len;
746 // Turn any multiple-whitespaces into single spaces
747 char *dup = strdup(node);
748 if (!dup)
749 break;
750 char *s = strpbrk(dup, "\t\r\n ");
751 while(s)
753 int i_whitespace = strspn(s, "\t\r\n ");
755 if (i_whitespace > 1)
756 memmove(&s[1],
757 &s[i_whitespace],
758 strlen(s) - i_whitespace + 1);
759 *s++ = ' ';
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,
772 CFRangeMake(0, len),
773 p_attrnode);
775 CFAttributedStringReplaceAttributedString(p_attrString,
776 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
777 p_attrnode);
778 CFRelease(p_attrnode);
780 free(dup);
781 break;
786 while(VLC_SUCCESS == PopFont(&p_fonts));
788 return rv;
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;
797 xml_t *p_xml = 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)
802 return VLC_EGENERIC;
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),
810 true);
811 if (p_sub) {
812 p_xml = xml_Create(p_filter);
813 if (p_xml) {
814 p_xml_reader = xml_ReaderCreate(p_xml, p_sub);
815 if (p_xml_reader) {
816 /* Look for Root Node */
817 const char *name;
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);
829 rv = VLC_EGENERIC;
831 } else {
832 msg_Err(p_filter, "Malformed HTML subtitle");
833 rv = VLC_EGENERIC;
836 if (rv != VLC_SUCCESS) {
837 xml_ReaderDelete(p_xml_reader);
838 p_xml_reader = NULL;
842 if (p_xml_reader) {
843 int i_len;
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);
861 xml_Delete(p_xml);
863 stream_Delete(p_sub);
866 return rv;
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));
876 if (p_bitmap) {
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);
891 if (p_context) {
892 if (CGContextSetAllowsAntialiasing != NULL)
893 CGContextSetAllowsAntialiasing(p_context, true);
895 *pp_memory = p_bitmap;
898 return p_context;
901 static offscreen_bitmap_t *Compose(filter_t *p_filter,
902 subpicture_region_t *p_region,
903 CFMutableAttributedStringRef p_attrString,
904 unsigned i_width,
905 unsigned i_height,
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;
916 if (p_context) {
917 float horiz_flush;
919 CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
921 if (p_region->i_align & SUBPICTURE_ALIGN_RIGHT)
922 horiz_flush = 1.0;
923 else if ((p_region->i_align & SUBPICTURE_ALIGN_LEFT) == 0)
924 horiz_flush = 0.5;
925 else
926 horiz_flush = 0.0;
928 // Create the framesetter with the attributed string.
929 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
930 if (framesetter) {
931 CTFrameRef frame;
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);
951 // Shadow
952 if (p_sys->b_shadow)
954 // TODO: Use CGContextSetShadowWithColor.
955 // TODO: Use user defined parrameters (color, distance, etc.)
956 CGContextSetShadow(p_context, CGSizeMake(3.0f, -3.0f), 2.0f);
959 if (frame != NULL) {
960 CFArrayRef lines;
961 CGPoint penPosition;
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;
988 CFRelease(frame);
990 CFRelease(framesetter);
992 CGContextFlush(p_context);
993 CGContextRelease(p_context);
995 if (p_colorSpace) CGColorSpaceRelease(p_colorSpace);
997 return p_offScreen;
1000 static int GetFontSize(filter_t *p_filter)
1002 int i_size = 0;
1004 int i_ratio = var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" );
1005 if( i_ratio > 0 )
1006 i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
1008 if( i_size <= 0 )
1010 msg_Warn( p_filter, "invalid fontsize, using 12" );
1011 i_size = 12;
1013 return i_size;
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);
1033 if (!p_offScreen) {
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;
1039 video_format_t fmt;
1040 int i_offset;
1041 unsigned i_pitch;
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;
1050 fmt.i_sar_num = 1;
1051 fmt.i_sar_den = 1;
1053 p_region->p_picture = picture_NewFromFormat(&fmt);
1054 if (!p_region->p_picture) {
1055 free(p_offScreen->p_data);
1056 free(p_offScreen);
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);
1091 free(p_offScreen);
1093 return VLC_SUCCESS;