quartztext.c 38.8 KB
Newer Older
1
2
3
/*****************************************************************************
 * quartztext.c : Put text on the video, using Mac OS X Quartz Engine
 *****************************************************************************
4
 * Copyright (C) 2007, 2009, 2012 VLC authors and VideoLAN
5
6
 * $Id$
 *
7
 * Authors: Bernie Purcell <bitmap@videolan.org>
8
9
 *          Pierre d'Herbemont <pdherbemont # videolan dot>
 *          Felix Paul Kühne <fkuehne # videolan # org>
10
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
11
12
13
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
14
15
16
17
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
18
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
20
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
21
22
23
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24
25
 *****************************************************************************/

26
27
28
/*****************************************************************************
 * Preamble
 *****************************************************************************/
29

30
31
32
33
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

34
#include <vlc_common.h>
35
#include <vlc_plugin.h>
36
37
#include <vlc_stream.h>
#include <vlc_xml.h>
38
#include <vlc_input.h>
39
#include <vlc_filter.h>
40

41
#include <TargetConditionals.h>
Pierre's avatar
Pierre committed
42

43
#if TARGET_OS_IPHONE
Pierre's avatar
Pierre committed
44
45
46
#include <CoreText/CoreText.h>
#include <CoreGraphics/CoreGraphics.h>

Pierre's avatar
Pierre committed
47
#else
48
49
50
// Fix ourselves ColorSync headers that gets included in ApplicationServices.
#define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
#define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
Pierre's avatar
Pierre committed
51
#define __MACHINEEXCEPTIONS__
52
#include <ApplicationServices/ApplicationServices.h>
Pierre's avatar
Pierre committed
53
#endif
54

55
#define DEFAULT_FONT           "Helvetica-Neue"
56
57
58
59
60
61
#define DEFAULT_FONT_COLOR     0xffffff
#define DEFAULT_REL_FONT_SIZE  16

#define VERTICAL_MARGIN 3
#define HORIZONTAL_MARGIN 10

62
63
64
65
66
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int  Create (vlc_object_t *);
static void Destroy(vlc_object_t *);
67

68
static int LoadFontsFromAttachments(filter_t *p_filter);
69

70
static int RenderText(filter_t *, subpicture_region_t *,
71
                       subpicture_region_t *,
72
73
                       const vlc_fourcc_t *);
static int RenderHtml(filter_t *, subpicture_region_t *,
74
                       subpicture_region_t *,
75
                       const vlc_fourcc_t *);
76

77
78
79
static int GetFontSize(filter_t *p_filter);
static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
                       CFMutableAttributedStringRef p_attrString);
80

81
static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
82
                              bool b_bold, bool b_italic, bool b_underline, bool b_halfwidth,
83
                              int i_spacing,
84
                              CFRange p_range, CFMutableAttributedStringRef p_attrString);
85

86
87
88
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
89

90
91
92
/* The preferred way to set font style information is for it to come from the
 * subtitle file, and for it to be rendered with RenderHtml instead of
 * RenderText. */
93
94
95
96
97
#define FONT_TEXT N_("Font")
#define FONT_LONGTEXT N_("Name for the font you want to use")
#define FONTSIZER_TEXT N_("Relative font size")
#define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
    "fonts that will be rendered on the video. If absolute font size is set, "\
98
    "relative size will be overridden.")
99
100
101
102
#define COLOR_TEXT N_("Text default color")
#define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
    "the video. This must be an hexadecimal (like HTML colors). The first two "\
    "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
103
    " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white")
104
105
#define OUTLINE_TEXT N_("Add outline")
#define SHADOW_TEXT N_("Add shadow")
106
107
108
109
110
111

static const int pi_color_values[] = {
  0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
  0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
  0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };

112
113
114
115
116
static const char *const ppsz_color_names[] = {
    "black", "gray", "silver", "white", "maroon",
    "red", "fuchsia", "yellow", "olive", "green",
    "teal", "lime", "purple", "navy", "blue", "aqua" };

117
118
119
120
121
122
123
124
125
static const char *const ppsz_color_descriptions[] = {
  N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
  N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
  N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };

static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
static const char *const ppsz_sizes_text[] = {
    N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };

126
vlc_module_begin ()
127
128
129
130
131
132
133
134
135
136
137
138
139
    set_shortname(N_("Text renderer for Mac"))
    set_description(N_("CoreText font renderer"))
    set_category(CAT_VIDEO)
    set_subcategory(SUBCAT_VIDEO_SUBPIC)

    add_string("quartztext-font", DEFAULT_FONT, FONT_TEXT, FONT_LONGTEXT,
              false)
    add_integer("quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, FONTSIZER_TEXT,
                 FONTSIZER_LONGTEXT, false)
        change_integer_list(pi_sizes, ppsz_sizes_text)
    add_integer("quartztext-color", 0x00FFFFFF, COLOR_TEXT,
                 COLOR_LONGTEXT, false)
        change_integer_list(pi_color_values, ppsz_color_descriptions)
140
141
    add_bool("quartztext-outline", false, OUTLINE_TEXT, NULL, false)
    add_bool("quartztext-shadow", true, SHADOW_TEXT, NULL, false)
142
143
144
    set_capability("text renderer", 50)
    add_shortcut("text")
    set_callbacks(Create, Destroy)
145
vlc_module_end ()
146

147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
typedef struct font_stack_t font_stack_t;
struct font_stack_t
{
    char          *psz_name;
    int            i_size;
    uint32_t       i_color;            // ARGB

    font_stack_t  *p_next;
};

typedef struct
{
    int         i_font_size;
    uint32_t    i_font_color;         /* ARGB */
    bool  b_italic;
    bool  b_bold;
    bool  b_underline;
    char       *psz_fontname;
} ft_style_t;

167
168
169
170
171
172
173
174
175
176
typedef struct offscreen_bitmap_t offscreen_bitmap_t;
struct offscreen_bitmap_t
{
    uint8_t       *p_data;
    int            i_bitsPerChannel;
    int            i_bitsPerPixel;
    int            i_bytesPerPixel;
    int            i_bytesPerRow;
};

177
178
179
180
181
182
/*****************************************************************************
 * filter_sys_t: quartztext local data
 *****************************************************************************
 * This structure is part of the video output thread descriptor.
 * It describes the freetype specific properties of an output thread.
 *****************************************************************************/
183
184
185
186
187
188
struct filter_sys_t
{
    char          *psz_font_name;
    uint8_t        i_font_opacity;
    int            i_font_color;
    int            i_font_size;
189
190
    bool           b_outline;
    bool           b_shadow;
191

Pierre's avatar
Pierre committed
192
#ifndef TARGET_OS_IPHONE
193
194
    ATSFontContainerRef    *p_fonts;
    int                     i_fonts;
Pierre's avatar
Pierre committed
195
#endif
196
197
};

198
199
200
201
202
203
/*****************************************************************************
 * Create: allocates osd-text video thread output method
 *****************************************************************************
 * This function allocates and initializes a Clone vout method.
 *****************************************************************************/
static int Create(vlc_object_t *p_this)
204
205
206
207
208
{
    filter_t *p_filter = (filter_t *)p_this;
    filter_sys_t *p_sys;

    // Allocate structure
209
210
    p_filter->p_sys = p_sys = malloc(sizeof(filter_sys_t));
    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
216
    p_sys->b_outline = var_InheritBool(p_this, "quartztext-outline");
    p_sys->b_shadow = var_InheritBool(p_this, "quartztext-shadow");
217
    p_sys->i_font_size = GetFontSize(p_filter);
218
219
220
221

    p_filter->pf_render_text = RenderText;
    p_filter->pf_render_html = RenderHtml;

Pierre's avatar
Pierre committed
222
#ifndef TARGET_OS_IPHONE
223
224
    p_sys->p_fonts = NULL;
    p_sys->i_fonts = 0;
Pierre's avatar
Pierre committed
225
#endif
226

227
    LoadFontsFromAttachments(p_filter);
228

229
230
231
    return VLC_SUCCESS;
}

232
233
234
235
236
237
/*****************************************************************************
 * Destroy: destroy Clone video thread output method
 *****************************************************************************
 * Clean up all data and library connections
 *****************************************************************************/
static void Destroy(vlc_object_t *p_this)
238
239
240
{
    filter_t *p_filter = (filter_t *)p_this;
    filter_sys_t *p_sys = p_filter->p_sys;
Pierre's avatar
Pierre committed
241
#ifndef TARGET_OS_IPHONE
242
243
244
    if (p_sys->p_fonts) {
        for (int k = 0; k < p_sys->i_fonts; k++) {
            ATSFontDeactivate(p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault);
245

246
        free(p_sys->p_fonts);
247
    }
Pierre's avatar
Pierre committed
248
#endif
249
250
    free(p_sys->psz_font_name);
    free(p_sys);
251
252
}

253
254
255
256
257
/*****************************************************************************
 * Make any TTF/OTF fonts present in the attachments of the media file
 * available to the Quartz engine for text rendering
 *****************************************************************************/
static int LoadFontsFromAttachments(filter_t *p_filter)
258
{
Pierre's avatar
Pierre committed
259
#ifdef TARGET_OS_IPHONE
Pierre's avatar
Pierre committed
260
261
262
    VLC_UNUSED(p_filter);
    return VLC_SUCCESS;
#else
263
264
265
266
    filter_sys_t         *p_sys = p_filter->p_sys;
    input_attachment_t  **pp_attachments;
    int                   i_attachments_cnt;

267
    if (filter_GetInputAttachments(p_filter, &pp_attachments, &i_attachments_cnt))
268
        return VLC_EGENERIC;
bitmap's avatar
bitmap committed
269

270
    p_sys->i_fonts = 0;
271
272
    p_sys->p_fonts = malloc(i_attachments_cnt * sizeof(ATSFontContainerRef));
    if (! p_sys->p_fonts)
273
        return VLC_ENOMEM;
274

275
    for (int k = 0; k < i_attachments_cnt; k++) {
276
277
        input_attachment_t *p_attach = pp_attachments[k];

278
279
280
        if ((!strcmp(p_attach->psz_mime, "application/x-truetype-font") || // TTF
              !strcmp(p_attach->psz_mime, "application/x-font-otf")) &&    // OTF
            p_attach->i_data > 0 && p_attach->p_data) {
281
282
            ATSFontContainerRef  container;

283
            if (noErr == ATSFontActivateFromMemory(p_attach->p_data,
284
285
286
287
288
                                                    p_attach->i_data,
                                                    kATSFontContextLocal,
                                                    kATSFontFormatUnspecified,
                                                    NULL,
                                                    kATSOptionFlagsDefault,
289
                                                    &container))
290
                p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
291
        }
292
        vlc_input_attachment_Delete(p_attach);
293
    }
294
    free(pp_attachments);
295
    return VLC_SUCCESS;
Pierre's avatar
Pierre committed
296
#endif
297
298
}

299
static char *EliminateCRLF(char *psz_string)
300
301
302
{
    char *q;

303
304
305
306
    for (char * p = psz_string; p && *p; p++) {
        if ((*p == '\r') && (*(p+1) == '\n')) {
            for (q = p + 1; *q; q++)
                *(q - 1) = *q;
307

308
            *(q - 1) = '\0';
309
310
311
312
313
        }
    }
    return psz_string;
}

314
315
316
/* Renders a text subpicture region into another one.
 * It is used as pf_add_string callback in the vout method by this module */
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)
319
320
321
{
    filter_sys_t *p_sys = p_filter->p_sys;
    char         *psz_string;
322
    char         *psz_fontname;
323
    int           i_font_size;
324
    int           i_spacing = 0;
325
    uint32_t      i_font_color;
326
    bool          b_bold, b_uline, b_italic, b_halfwidth;
327
    vlc_value_t val;
328
    b_bold = b_uline = b_italic = b_halfwidth = FALSE;
329
    VLC_UNUSED(p_chroma_list);
330

331
    p_sys->i_font_size = GetFontSize(p_filter);
332
333

    // Sanity check
334
335
336
    if (!p_region_in || !p_region_out)
        return VLC_EGENERIC;

337
    psz_string = p_region_in->psz_text;
338
339
340
341
    if (!psz_string || !*psz_string)
        return VLC_EGENERIC;

    if (p_region_in->p_style) {
342
343
        psz_fontname = p_region_in->p_style->psz_fontname ?
            p_region_in->p_style->psz_fontname : p_sys->psz_font_name;
344
345
346
347
        i_font_color = VLC_CLIP(p_region_in->p_style->i_font_color, 0, 0xFFFFFF);
        i_font_size  = VLC_CLIP(p_region_in->p_style->i_font_size, 0, 255);
        if (p_region_in->p_style->i_style_flags) {
            if (p_region_in->p_style->i_style_flags & STYLE_BOLD)
348
                b_bold = TRUE;
349
            if (p_region_in->p_style->i_style_flags & STYLE_ITALIC)
350
                b_italic = TRUE;
351
            if (p_region_in->p_style->i_style_flags & STYLE_UNDERLINE)
352
                b_uline = TRUE;
353
354
            if (p_region_in->p_style->i_style_flags & STYLE_HALFWIDTH)
                b_halfwidth = TRUE;
355
        }
356
        i_spacing = VLC_CLIP(p_region_in->p_style->i_spacing, 0, 255);
357
    } else {
358
        psz_fontname = p_sys->psz_font_name;
359
360
361
362
        i_font_color = p_sys->i_font_color;
        i_font_size  = p_sys->i_font_size;
    }

363
364
365
    if (i_font_size <= 0) {
        msg_Warn(p_filter, "invalid fontsize, using 12");
        if (VLC_SUCCESS == var_Get(p_filter, "scale", &val))
366
367
368
369
370
            i_font_size = 12 * val.i_int / 1000;
        else
            i_font_size = 12;
    }

371
372
373
    p_region_out->i_x = p_region_in->i_x;
    p_region_out->i_y = p_region_in->i_y;

374
375
    CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);

376
    if (p_attrString) {
377
378
379
        CFStringRef   p_cfString;
        int           len;

380
381
382
383
384
        EliminateCRLF(psz_string);
        p_cfString = CFStringCreateWithCString(NULL, psz_string, kCFStringEncodingUTF8);
        CFAttributedStringReplaceString(p_attrString, CFRangeMake(0, 0), p_cfString);
        CFRelease(p_cfString);
        len = CFAttributedStringGetLength(p_attrString);
385

386
        setFontAttibutes(psz_fontname, i_font_size, i_font_color, b_bold, b_italic, b_uline, b_halfwidth,
387
                                             i_spacing,
388
                                             CFRangeMake(0, len), p_attrString);
389

390
391
        RenderYUVA(p_filter, p_region_out, p_attrString);
        CFRelease(p_attrString);
392
393
394
395
396
397
    }

    return VLC_SUCCESS;
}


398
399
static int PushFont(font_stack_t **p_font, const char *psz_name, int i_size,
                     uint32_t i_color)
400
{
401
402
    font_stack_t *p_new;

403
    if (!p_font)
404
405
        return VLC_EGENERIC;

406
407
    p_new = malloc(sizeof(font_stack_t));
    if (! p_new)
408
409
410
411
        return VLC_ENOMEM;

    p_new->p_next = NULL;

412
413
    if (psz_name)
        p_new->psz_name = strdup(psz_name);
414
415
416
417
418
419
    else
        p_new->psz_name = NULL;

    p_new->i_size   = i_size;
    p_new->i_color  = i_color;

420
    if (!*p_font)
421
        *p_font = p_new;
422
    else {
423
424
        font_stack_t *p_last;

425
        for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
426
427
428
        ;

        p_last->p_next = p_new;
429
    }
430
431
432
    return VLC_SUCCESS;
}

433
static int PopFont(font_stack_t **p_font)
434
435
436
{
    font_stack_t *p_last, *p_next_to_last;

437
    if (!p_font || !*p_font)
438
        return VLC_EGENERIC;
439

440
    p_next_to_last = NULL;
441
    for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
442
443
        p_next_to_last = p_last;

444
    if (p_next_to_last)
445
446
447
448
        p_next_to_last->p_next = NULL;
    else
        *p_font = NULL;

449
450
    free(p_last->psz_name);
    free(p_last);
451
452
453
454

    return VLC_SUCCESS;
}

455
456
static int PeekFont(font_stack_t **p_font, char **psz_name, int *i_size,
                     uint32_t *i_color)
457
458
459
{
    font_stack_t *p_last;

460
    if (!p_font || !*p_font)
461
462
        return VLC_EGENERIC;

463
    for (p_last=*p_font;
464
         p_last->p_next;
465
         p_last=p_last->p_next)
466
467
468
469
470
471
472
473
474
    ;

    *psz_name = p_last->psz_name;
    *i_size   = p_last->i_size;
    *i_color  = p_last->i_color;

    return VLC_SUCCESS;
}

475
476
static int HandleFontAttributes(xml_reader_t *p_xml_reader,
                                  font_stack_t **p_fonts)
477
478
479
480
481
482
{
    int        rv;
    char      *psz_fontname = NULL;
    uint32_t   i_font_color = 0xffffff;
    int        i_font_alpha = 0;
    int        i_font_size  = 24;
483
    const char *attr, *value;
484

485
486
487
488
489
490
    /* Default all attributes to the top font in the stack -- in case not
     * all attributes are specified in the sub-font */
    if (VLC_SUCCESS == PeekFont(p_fonts,
                                &psz_fontname,
                                &i_font_size,
                                &i_font_color)) {
491
492
        if (psz_fontname)
            psz_fontname = strdup(psz_fontname);
493
        i_font_size = i_font_size;
494
495
496
497
    }
    i_font_alpha = (i_font_color >> 24) & 0xff;
    i_font_color &= 0x00ffffff;

498
499
500
    while ((attr = xml_ReaderNextAttr(p_xml_reader, &value))) {
        if (!strcasecmp("face", attr)) {
            free(psz_fontname);
501
502
            if (value)
                psz_fontname = strdup(value);
503
504
505
506
507
508
509
        } else if (!strcasecmp("size", attr)) {
            if ((*value == '+') || (*value == '-')) {
                int i_value = atoi(value);

                if ((i_value >= -5) && (i_value <= 5))
                    i_font_size += (i_value * i_font_size) / 10;
                else if (i_value < -5)
Pierre Ynard's avatar
Pierre Ynard committed
510
                    i_font_size = - i_value;
511
                else if (i_value > 5)
Pierre Ynard's avatar
Pierre Ynard committed
512
                    i_font_size = i_value;
513
            }
Pierre Ynard's avatar
Pierre Ynard committed
514
            else
515
                i_font_size = atoi(value);
516
517
518
519
520
521
522
523
524
525
526
527
528
529
        } else if (!strcasecmp("color", attr)) {
            if (value[0] == '#') {
                i_font_color = strtol(value + 1, NULL, 16);
                i_font_color &= 0x00ffffff;
            } else {
                /* color detection fallback */
                unsigned int count = sizeof(ppsz_color_names);
                for (unsigned x = 0; x < count; x++) {
                    if (!strcmp(value, ppsz_color_names[x])) {
                        i_font_color = pi_color_values[x];
                        break;
                    }
                }
            }
530
531
        } else if (!strcasecmp("alpha", attr) && (value[0] == '#')) {
            i_font_alpha = strtol(value + 1, NULL, 16);
Pierre Ynard's avatar
Pierre Ynard committed
532
533
            i_font_alpha &= 0xff;
        }
534
    }
535
536
537
538
    rv = PushFont(p_fonts,
                  psz_fontname,
                  i_font_size,
                  (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24));
539

540
    free(psz_fontname);
541
542

    return rv;
543
544
}

545
static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
546
        bool b_bold, bool b_italic, bool b_underline, bool b_halfwidth,
547
        int i_spacing,
548
        CFRange p_range, CFMutableAttributedStringRef p_attrString)
549
{
550
551
    CFStringRef p_cfString;
    CTFontRef   p_font;
Pierre's avatar
Pierre committed
552

553
554
555
556
    int i_font_width = b_halfwidth ? i_font_size / 2 : i_font_size;
    CGAffineTransform trans = CGAffineTransformMakeScale((float)i_font_width
                                                         / i_font_size, 1.0);

557
558
559
560
561
    // fallback on default
    if (!psz_fontname)
        psz_fontname = (char *)DEFAULT_FONT;

    p_cfString = CFStringCreateWithCString(kCFAllocatorDefault,
562
                                            psz_fontname,
563
564
                                            kCFStringEncodingUTF8);
    p_font     = CTFontCreateWithName(p_cfString,
565
                                       (float)i_font_size,
566
                                       &trans);
567
568
    CFRelease(p_cfString);
    CFAttributedStringSetAttribute(p_attrString,
569
570
                                    p_range,
                                    kCTFontAttributeName,
571
572
                                    p_font);
    CFRelease(p_font);
573
574
575

    // Handle Underline
    SInt32 _uline;
576
    if (b_underline)
577
578
579
580
581
        _uline = kCTUnderlineStyleSingle;
    else
        _uline = kCTUnderlineStyleNone;

    CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
582
    CFAttributedStringSetAttribute(p_attrString,
583
584
                                    p_range,
                                    kCTUnderlineStyleAttributeName,
585
586
                                    underline);
    CFRelease(underline);
587
588
589

    // Handle Bold
    float _weight;
590
    if (b_bold)
591
592
593
594
595
        _weight = 0.5;
    else
        _weight = 0.0;

    CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
596
    CFAttributedStringSetAttribute(p_attrString,
597
598
                                    p_range,
                                    kCTFontWeightTrait,
599
600
                                    weight);
    CFRelease(weight);
601
602
603

    // Handle Italic
    float _slant;
604
    if (b_italic)
605
606
607
608
609
        _slant = 1.0;
    else
        _slant = 0.0;

    CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
610
    CFAttributedStringSetAttribute(p_attrString,
611
612
                                    p_range,
                                    kCTFontSlantTrait,
613
614
                                    slant);
    CFRelease(slant);
615

616
617
618
619
620
    // fetch invalid colors
    if (i_font_color == 0xFFFFFFFF)
        i_font_color = 0x00FFFFFF;

    // Handle foreground color
621
622
623
    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
                             (float)((i_font_color & 0x0000ff00) >>  8) / 255.0,
624
                             (float)((i_font_color & 0x000000ff)) / 255.0,
625
626
627
628
                             (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
    CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
    CGColorSpaceRelease(rgbColorSpace);

629
    CFAttributedStringSetAttribute(p_attrString,
630
631
                                    p_range,
                                    kCTForegroundColorAttributeName,
632
633
                                    fg_text);
    CFRelease(fg_text);
634

635
636
637
638
639
640
641
642
643
644
645
646
    // spacing
    if (i_spacing > 0)
    {
        CGFloat spacing = i_spacing;
        CFNumberRef spacingCFNum = CFNumberCreate(NULL,
                kCFNumberCGFloatType, &spacing);
        CFAttributedStringSetAttribute(p_attrString,
                                        p_range,
                                        kCTKernAttributeName,
                                        spacingCFNum);
        CFRelease(spacingCFNum);
    }
647
}
648

649
static void GetAttrStrFromFontStack(font_stack_t **p_fonts,
650
        bool b_bold, bool b_italic, bool b_uline,
651
        CFRange p_range, CFMutableAttributedStringRef p_attrString)
652
653
654
655
{
    char       *psz_fontname = NULL;
    int         i_font_size  = 0;
    uint32_t    i_font_color = 0;
656

657
658
659
660
661
    if (VLC_SUCCESS == PeekFont(p_fonts, &psz_fontname, &i_font_size,
                                &i_font_color)) {
        setFontAttibutes(psz_fontname,
                         i_font_size,
                         i_font_color,
662
                         b_bold, b_italic, b_uline, FALSE,
663
                         0,
664
665
                         p_range,
                         p_attrString);
666
667
668
    }
}

669
static int ProcessNodes(filter_t *p_filter,
670
671
                         xml_reader_t *p_xml_reader,
                         text_style_t *p_font_style,
672
                         CFMutableAttributedStringRef p_attrString)
673
{
674
675
676
677
    int           rv             = VLC_SUCCESS;
    filter_sys_t *p_sys          = p_filter->p_sys;
    font_stack_t *p_fonts        = NULL;

Pierre Ynard's avatar
Pierre Ynard committed
678
679
    int type;
    const char *node;
680
681
682
683

    bool b_italic = false;
    bool b_bold   = false;
    bool b_uline  = false;
684

685
686
    if (p_font_style) {
        rv = PushFont(&p_fonts,
687
               p_font_style->psz_fontname,
688
               p_font_style->i_font_size,
689
               (p_font_style->i_font_color & 0xffffff) |
690
                   ((p_font_style->i_font_alpha & 0xff) << 24));
691

692
        if (p_font_style->i_style_flags & STYLE_BOLD)
693
            b_bold = true;
694
        if (p_font_style->i_style_flags & STYLE_ITALIC)
695
            b_italic = true;
696
        if (p_font_style->i_style_flags & STYLE_UNDERLINE)
697
            b_uline = true;
698
699
    } else {
        rv = PushFont(&p_fonts,
700
701
                       p_sys->psz_font_name,
                       p_sys->i_font_size,
702
                       p_sys->i_font_color);
703
    }
704
    if (rv != VLC_SUCCESS)
705
        return rv;
706

707
708
    while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
        switch (type) {
709
            case XML_READER_ENDELEM:
710
711
712
                if (!strcasecmp("font", node))
                    PopFont(&p_fonts);
                else if (!strcasecmp("b", node))
Pierre Ynard's avatar
Pierre Ynard committed
713
                    b_bold   = false;
714
                else if (!strcasecmp("i", node))
Pierre Ynard's avatar
Pierre Ynard committed
715
                    b_italic = false;
716
                else if (!strcasecmp("u", node))
Pierre Ynard's avatar
Pierre Ynard committed
717
                    b_uline  = false;
718

719
720
                break;
            case XML_READER_STARTELEM:
721
722
723
                if (!strcasecmp("font", node))
                    rv = HandleFontAttributes(p_xml_reader, &p_fonts);
                else if (!strcasecmp("b", node))
Pierre Ynard's avatar
Pierre Ynard committed
724
                    b_bold = true;
725
                else if (!strcasecmp("i", node))
Pierre Ynard's avatar
Pierre Ynard committed
726
                    b_italic = true;
727
                else if (!strcasecmp("u", node))
Pierre Ynard's avatar
Pierre Ynard committed
728
                    b_uline = true;
729
                else if (!strcasecmp("br", node)) {
Pierre Ynard's avatar
Pierre Ynard committed
730
                    CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
731
                    CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), CFSTR("\n"));
Pierre Ynard's avatar
Pierre Ynard committed
732

733
734
735
736
                    GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
                                             CFRangeMake(0, 1),
                                             p_attrnode);
                    CFAttributedStringReplaceAttributedString(p_attrString,
Pierre Ynard's avatar
Pierre Ynard committed
737
738
                                    CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
                                    p_attrnode);
739
                    CFRelease(p_attrnode);
740
741
742
                }
                break;
            case XML_READER_TEXT:
Pierre Ynard's avatar
Pierre Ynard committed
743
            {
Pierre Ynard's avatar
Pierre Ynard committed
744
745
746
747
                CFStringRef   p_cfString;
                int           len;

                // Turn any multiple-whitespaces into single spaces
748
749
                if (!node)
                    break;
750
751
                char *dup = strdup(node);
                if (!dup)
752
                    break;
753
754
                char *s = strpbrk(dup, "\t\r\n ");
                while(s)
755
                {
756
                    int i_whitespace = strspn(s, "\t\r\n ");
757

758
759
                    if (i_whitespace > 1)
                        memmove(&s[1],
Pierre Ynard's avatar
Pierre Ynard committed
760
                                 &s[i_whitespace],
761
                                 strlen(s) - i_whitespace + 1);
Pierre Ynard's avatar
Pierre Ynard committed
762
                    *s++ = ' ';
763

764
                    s = strpbrk(s, "\t\r\n ");
Pierre Ynard's avatar
Pierre Ynard committed
765
                }
766
767


Pierre Ynard's avatar
Pierre Ynard committed
768
                CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
769
770
771
772
                p_cfString = CFStringCreateWithCString(NULL, dup, kCFStringEncodingUTF8);
                CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), p_cfString);
                CFRelease(p_cfString);
                len = CFAttributedStringGetLength(p_attrnode);
773

774
775
776
                GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
                                         CFRangeMake(0, len),
                                         p_attrnode);
777

778
                CFAttributedStringReplaceAttributedString(p_attrString,
Pierre Ynard's avatar
Pierre Ynard committed
779
780
                                CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
                                p_attrnode);
781
                CFRelease(p_attrnode);
782

783
                free(dup);
784
                break;
Pierre Ynard's avatar
Pierre Ynard committed
785
            }
786
        }
787
    }
788

789
    while(VLC_SUCCESS == PopFont(&p_fonts));
790
791

    return rv;
792
793
}

794
static int RenderHtml(filter_t *p_filter, subpicture_region_t *p_region_out,
795
                       subpicture_region_t *p_region_in,
796
                       const vlc_fourcc_t *p_chroma_list)
797
798
799
800
{
    int          rv = VLC_SUCCESS;
    stream_t     *p_sub = NULL;
    xml_reader_t *p_xml_reader = NULL;
801
    VLC_UNUSED(p_chroma_list);
802

803
    if (!p_region_in || !p_region_in->psz_html)
804
805
        return VLC_EGENERIC;

806
    /* Reset the default fontsize in case screen metrics have changed */
807
    p_filter->p_sys->i_font_size = GetFontSize(p_filter);
808

809
    p_sub = stream_MemoryNew(VLC_OBJECT(p_filter),
810
                              (uint8_t *) p_region_in->psz_html,
811
812
813
                              strlen(p_region_in->psz_html),
                              true);
    if (p_sub) {
814
815
816
817
818
819
820
821
822
823
824
825
826
827
        p_xml_reader = xml_ReaderCreate(p_filter, p_sub);
        if (p_xml_reader) {
            /* Look for Root Node */
            const char *name;
            if (xml_ReaderNextNode(p_xml_reader, &name)
                    == XML_READER_STARTELEM) {
                if (!strcasecmp("karaoke", name)) {
                    /* We're going to have to render the text a number
                     * of times to show the progress marker on the text. */
                    var_SetBool(p_filter, "text-rerender", true);
                } else if (strcasecmp("text", name)) {
                    /* Only text and karaoke tags are supported */
                    msg_Dbg(p_filter, "Unsupported top-level tag "
                                      "<%s> ignored.", name);
828
829
                    rv = VLC_EGENERIC;
                }
830
831
832
833
            } else {
                msg_Err(p_filter, "Malformed HTML subtitle");
                rv = VLC_EGENERIC;
            }
834

835
836
837
            if (rv != VLC_SUCCESS) {
                xml_ReaderDelete(p_xml_reader);
                p_xml_reader = NULL;
838
            }
839
        }
840

841
842
        if (p_xml_reader) {
            int         i_len;
843

844
845
            CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
            rv = ProcessNodes(p_filter, p_xml_reader,
846
                              p_region_in->p_style, p_attrString);
847

848
            i_len = CFAttributedStringGetLength(p_attrString);
849

850
851
            p_region_out->i_x = p_region_in->i_x;
            p_region_out->i_y = p_region_in->i_y;
852

853
854
            if ((rv == VLC_SUCCESS) && (i_len > 0))
                RenderYUVA(p_filter, p_region_out, p_attrString);
855

856
            CFRelease(p_attrString);
857

858
            xml_ReaderDelete(p_xml_reader);
859
        }
860
        stream_Delete(p_sub);
861
862
863
864
865
    }

    return rv;
}

866
867
static CGContextRef CreateOffScreenContext(int i_width, int i_height,
                         offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace)
868
869
870
871
{
    offscreen_bitmap_t *p_bitmap;
    CGContextRef        p_context = NULL;

872
873
    p_bitmap = (offscreen_bitmap_t *) malloc(sizeof(offscreen_bitmap_t));
    if (p_bitmap) {
874
875
876
877
878
        p_bitmap->i_bitsPerChannel = 8;
        p_bitmap->i_bitsPerPixel   = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
        p_bitmap->i_bytesPerPixel  = p_bitmap->i_bitsPerPixel / 8;
        p_bitmap->i_bytesPerRow    = i_width * p_bitmap->i_bytesPerPixel;

879
        p_bitmap->p_data = calloc(i_height, p_bitmap->i_bytesPerRow);
880

Pierre's avatar
Pierre committed
881
        *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
882

883
884
        if (p_bitmap->p_data && *pp_colorSpace)
            p_context = CGBitmapContextCreate(p_bitmap->p_data, i_width, i_height,
885
886
                                p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
                                *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
887
888

        if (p_context) {
889
            if (&CGContextSetAllowsAntialiasing != NULL)
890
                CGContextSetAllowsAntialiasing(p_context, true);
891
892
893
894
895
896
897
        }
        *pp_memory = p_bitmap;
    }

    return p_context;
}

898
899
static offscreen_bitmap_t *Compose(filter_t *p_filter,
                                    subpicture_region_t *p_region,
900
                                    CFMutableAttributedStringRef p_attrString,
Pierre's avatar
Pierre committed
901
902
                                    unsigned i_width,
                                    unsigned i_height,
903
                                    unsigned *pi_textblock_height)
904
{
905
    filter_sys_t *p_sys   = p_filter->p_sys;
906
907
908
    offscreen_bitmap_t  *p_offScreen  = NULL;
    CGColorSpaceRef      p_colorSpace = NULL;
    CGContextRef         p_context = NULL;
bitmap's avatar
bitmap committed
909

910
    p_context = CreateOffScreenContext(i_width, i_height, &p_offScreen, &p_colorSpace);
911

912
    *pi_textblock_height = 0;
913
    if (p_context) {
914
        float horiz_flush;
915

916
        CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
917

918
        if (p_region->i_align & SUBPICTURE_ALIGN_RIGHT)
919
            horiz_flush = 1.0;
920
        else if ((p_region->i_align & SUBPICTURE_ALIGN_LEFT) == 0)