quartztext.c 36.9 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           "Arial Black"
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,
83
                              CFRange p_range, CFMutableAttributedStringRef p_attrString);
84

85
86
87
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
88

89
90
91
/* 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. */
92
93
94
95
96
#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, "\
97
    "relative size will be overridden.")
98
99
100
101
#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,"\
102
    " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white")
103
104
105
106
107
108

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

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

114
115
116
117
118
119
120
121
122
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") };

123
vlc_module_begin ()
124
125
126
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)
    set_capability("text renderer", 50)
    add_shortcut("text")
    set_callbacks(Create, Destroy)
140
vlc_module_end ()
141

142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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;

162
163
164
165
166
167
168
169
170
171
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;
};

172
173
174
175
176
177
/*****************************************************************************
 * 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.
 *****************************************************************************/
178
179
180
181
182
183
struct filter_sys_t
{
    char          *psz_font_name;
    uint8_t        i_font_opacity;
    int            i_font_color;
    int            i_font_size;
184

Pierre's avatar
Pierre committed
185
#ifndef TARGET_OS_IPHONE
186
187
    ATSFontContainerRef    *p_fonts;
    int                     i_fonts;
Pierre's avatar
Pierre committed
188
#endif
189
190
};

191
192
193
194
195
196
/*****************************************************************************
 * 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)
197
198
199
200
201
{
    filter_t *p_filter = (filter_t *)p_this;
    filter_sys_t *p_sys;

    // Allocate structure
202
203
    p_filter->p_sys = p_sys = malloc(sizeof(filter_sys_t));
    if (!p_sys)
204
        return VLC_ENOMEM;
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);
209
210
211
212

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

Pierre's avatar
Pierre committed
213
#ifndef TARGET_OS_IPHONE
214
215
    p_sys->p_fonts = NULL;
    p_sys->i_fonts = 0;
Pierre's avatar
Pierre committed
216
#endif
217

218
    LoadFontsFromAttachments(p_filter);
219

220
221
222
    return VLC_SUCCESS;
}

223
224
225
226
227
228
/*****************************************************************************
 * Destroy: destroy Clone video thread output method
 *****************************************************************************
 * Clean up all data and library connections
 *****************************************************************************/
static void Destroy(vlc_object_t *p_this)
229
230
231
{
    filter_t *p_filter = (filter_t *)p_this;
    filter_sys_t *p_sys = p_filter->p_sys;
Pierre's avatar
Pierre committed
232
#ifndef TARGET_OS_IPHONE
233
234
235
    if (p_sys->p_fonts) {
        for (int k = 0; k < p_sys->i_fonts; k++) {
            ATSFontDeactivate(p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault);
236

237
        free(p_sys->p_fonts);
238
    }
Pierre's avatar
Pierre committed
239
#endif
240
241
    free(p_sys->psz_font_name);
    free(p_sys);
242
243
}

244
245
246
247
248
/*****************************************************************************
 * 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)
249
{
Pierre's avatar
Pierre committed
250
#ifdef TARGET_OS_IPHONE
Pierre's avatar
Pierre committed
251
252
253
    VLC_UNUSED(p_filter);
    return VLC_SUCCESS;
#else
254
255
256
257
    filter_sys_t         *p_sys = p_filter->p_sys;
    input_attachment_t  **pp_attachments;
    int                   i_attachments_cnt;

258
    if (filter_GetInputAttachments(p_filter, &pp_attachments, &i_attachments_cnt))
259
        return VLC_EGENERIC;
bitmap's avatar
bitmap committed
260

261
    p_sys->i_fonts = 0;
262
263
    p_sys->p_fonts = malloc(i_attachments_cnt * sizeof(ATSFontContainerRef));
    if (! p_sys->p_fonts)
264
        return VLC_ENOMEM;
265

266
    for (int k = 0; k < i_attachments_cnt; k++) {
267
268
        input_attachment_t *p_attach = pp_attachments[k];

269
270
271
        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) {
272
273
            ATSFontContainerRef  container;

274
            if (noErr == ATSFontActivateFromMemory(p_attach->p_data,
275
276
277
278
279
                                                    p_attach->i_data,
                                                    kATSFontContextLocal,
                                                    kATSFontFormatUnspecified,
                                                    NULL,
                                                    kATSOptionFlagsDefault,
280
                                                    &container))
281
                p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
282
        }
283
        vlc_input_attachment_Delete(p_attach);
284
    }
285
    free(pp_attachments);
286
    return VLC_SUCCESS;
Pierre's avatar
Pierre committed
287
#endif
288
289
}

290
static char *EliminateCRLF(char *psz_string)
291
292
293
{
    char *q;

294
295
296
297
    for (char * p = psz_string; p && *p; p++) {
        if ((*p == '\r') && (*(p+1) == '\n')) {
            for (q = p + 1; *q; q++)
                *(q - 1) = *q;
298

299
            *(q - 1) = '\0';
300
301
302
303
304
        }
    }
    return psz_string;
}

305
306
307
/* 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,
308
                       subpicture_region_t *p_region_in,
309
                       const vlc_fourcc_t *p_chroma_list)
310
311
312
{
    filter_sys_t *p_sys = p_filter->p_sys;
    char         *psz_string;
313
314
    int           i_font_alpha, i_font_size;
    uint32_t      i_font_color;
315
    bool          b_bold, b_uline, b_italic;
316
    vlc_value_t val;
317
    b_bold = b_uline = b_italic = FALSE;
318
    VLC_UNUSED(p_chroma_list);
319

320
    p_sys->i_font_size = GetFontSize(p_filter);
321
322

    // Sanity check
323
324
325
    if (!p_region_in || !p_region_out)
        return VLC_EGENERIC;

326
    psz_string = p_region_in->psz_text;
327
328
329
330
331
332
333
334
335
    if (!psz_string || !*psz_string)
        return VLC_EGENERIC;

    if (p_region_in->p_style) {
        i_font_color = VLC_CLIP(p_region_in->p_style->i_font_color, 0, 0xFFFFFF);
        i_font_alpha = VLC_CLIP(p_region_in->p_style->i_font_alpha, 0, 255);
        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)
336
                b_bold = TRUE;
337
            if (p_region_in->p_style->i_style_flags & STYLE_ITALIC)
338
                b_italic = TRUE;
339
            if (p_region_in->p_style->i_style_flags & STYLE_UNDERLINE)
340
341
                b_uline = TRUE;
        }
342
    } else {
343
344
345
346
347
        i_font_color = p_sys->i_font_color;
        i_font_alpha = 255 - p_sys->i_font_opacity;
        i_font_size  = p_sys->i_font_size;
    }

348
349
    if (!i_font_alpha)
        i_font_alpha = 255 - p_sys->i_font_opacity;
bitmap's avatar
bitmap committed
350

351
352
353
    if (i_font_size <= 0) {
        msg_Warn(p_filter, "invalid fontsize, using 12");
        if (VLC_SUCCESS == var_Get(p_filter, "scale", &val))
354
355
356
357
358
            i_font_size = 12 * val.i_int / 1000;
        else
            i_font_size = 12;
    }

359
360
361
    p_region_out->i_x = p_region_in->i_x;
    p_region_out->i_y = p_region_in->i_y;

362
363
    CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);

364
    if (p_attrString) {
365
366
367
        CFStringRef   p_cfString;
        int           len;

368
369
370
371
372
        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);
373

374
375
        setFontAttibutes(p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline,
                                             CFRangeMake(0, len), p_attrString);
376

377
378
        RenderYUVA(p_filter, p_region_out, p_attrString);
        CFRelease(p_attrString);
379
380
381
382
383
384
    }

    return VLC_SUCCESS;
}


385
386
static int PushFont(font_stack_t **p_font, const char *psz_name, int i_size,
                     uint32_t i_color)
387
{
388
389
    font_stack_t *p_new;

390
    if (!p_font)
391
392
        return VLC_EGENERIC;

393
394
    p_new = malloc(sizeof(font_stack_t));
    if (! p_new)
395
396
397
398
        return VLC_ENOMEM;

    p_new->p_next = NULL;

399
400
    if (psz_name)
        p_new->psz_name = strdup(psz_name);
401
402
403
404
405
406
    else
        p_new->psz_name = NULL;

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

407
    if (!*p_font)
408
        *p_font = p_new;
409
    else {
410
411
        font_stack_t *p_last;

412
        for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
413
414
415
        ;

        p_last->p_next = p_new;
416
    }
417
418
419
    return VLC_SUCCESS;
}

420
static int PopFont(font_stack_t **p_font)
421
422
423
{
    font_stack_t *p_last, *p_next_to_last;

424
    if (!p_font || !*p_font)
425
        return VLC_EGENERIC;
426

427
    p_next_to_last = NULL;
428
    for (p_last = *p_font; p_last->p_next; p_last = p_last->p_next)
429
430
        p_next_to_last = p_last;

431
    if (p_next_to_last)
432
433
434
435
        p_next_to_last->p_next = NULL;
    else
        *p_font = NULL;

436
437
    free(p_last->psz_name);
    free(p_last);
438
439
440
441

    return VLC_SUCCESS;
}

442
443
static int PeekFont(font_stack_t **p_font, char **psz_name, int *i_size,
                     uint32_t *i_color)
444
445
446
{
    font_stack_t *p_last;

447
    if (!p_font || !*p_font)
448
449
        return VLC_EGENERIC;

450
    for (p_last=*p_font;
451
         p_last->p_next;
452
         p_last=p_last->p_next)
453
454
455
456
457
458
459
460
461
    ;

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

    return VLC_SUCCESS;
}

462
463
static int HandleFontAttributes(xml_reader_t *p_xml_reader,
                                  font_stack_t **p_fonts)
464
465
466
467
468
469
{
    int        rv;
    char      *psz_fontname = NULL;
    uint32_t   i_font_color = 0xffffff;
    int        i_font_alpha = 0;
    int        i_font_size  = 24;
470
    const char *attr, *value;
471

472
473
474
475
476
477
478
    /* 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)) {
        psz_fontname = strdup(psz_fontname);
479
        i_font_size = i_font_size;
480
481
482
483
    }
    i_font_alpha = (i_font_color >> 24) & 0xff;
    i_font_color &= 0x00ffffff;

484
485
486
487
488
489
490
491
492
493
494
    while ((attr = xml_ReaderNextAttr(p_xml_reader, &value))) {
        if (!strcasecmp("face", attr)) {
            free(psz_fontname);
            psz_fontname = strdup(value);
        } 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
495
                    i_font_size = - i_value;
496
                else if (i_value > 5)
Pierre Ynard's avatar
Pierre Ynard committed
497
                    i_font_size = i_value;
498
            }
Pierre Ynard's avatar
Pierre Ynard committed
499
            else
500
                i_font_size = atoi(value);
501
502
503
504
505
506
507
508
509
510
511
512
513
514
        } 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;
                    }
                }
            }
515
516
        } else if (!strcasecmp("alpha", attr) && (value[0] == '#')) {
            i_font_alpha = strtol(value + 1, NULL, 16);
Pierre Ynard's avatar
Pierre Ynard committed
517
518
            i_font_alpha &= 0xff;
        }
519
    }
520
521
522
523
    rv = PushFont(p_fonts,
                  psz_fontname,
                  i_font_size,
                  (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24));
524

525
    free(psz_fontname);
526
527

    return rv;
528
529
}

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)
533
{
534
535
    CFStringRef p_cfString;
    CTFontRef   p_font;
Pierre's avatar
Pierre committed
536

537
    // Handle font name and size
538
    p_cfString = CFStringCreateWithCString(NULL,
539
                                            psz_fontname,
540
541
                                            kCFStringEncodingUTF8);
    p_font     = CTFontCreateWithName(p_cfString,
542
                                       (float)i_font_size,
543
544
545
                                       NULL);
    CFRelease(p_cfString);
    CFAttributedStringSetAttribute(p_attrString,
546
547
                                    p_range,
                                    kCTFontAttributeName,
548
549
                                    p_font);
    CFRelease(p_font);
550
551
552

    // Handle Underline
    SInt32 _uline;
553
    if (b_underline)
554
555
556
557
558
        _uline = kCTUnderlineStyleSingle;
    else
        _uline = kCTUnderlineStyleNone;

    CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
559
    CFAttributedStringSetAttribute(p_attrString,
560
561
                                    p_range,
                                    kCTUnderlineStyleAttributeName,
562
563
                                    underline);
    CFRelease(underline);
564
565
566

    // Handle Bold
    float _weight;
567
    if (b_bold)
568
569
570
571
572
        _weight = 0.5;
    else
        _weight = 0.0;

    CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
573
    CFAttributedStringSetAttribute(p_attrString,
574
575
                                    p_range,
                                    kCTFontWeightTrait,
576
577
                                    weight);
    CFRelease(weight);
578
579
580

    // Handle Italic
    float _slant;
581
    if (b_italic)
582
583
584
585
586
        _slant = 1.0;
    else
        _slant = 0.0;

    CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
587
    CFAttributedStringSetAttribute(p_attrString,
588
589
                                    p_range,
                                    kCTFontSlantTrait,
590
591
                                    slant);
    CFRelease(slant);
592
593
594
595
596

    // Handle foreground colour
    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
                             (float)((i_font_color & 0x0000ff00) >>  8) / 255.0,
597
                             (float)((i_font_color & 0x000000ff)) / 255.0,
598
599
600
601
                             (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
    CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
    CGColorSpaceRelease(rgbColorSpace);

602
    CFAttributedStringSetAttribute(p_attrString,
603
604
                                    p_range,
                                    kCTForegroundColorAttributeName,
605
606
                                    fg_text);
    CFRelease(fg_text);
607
608

}
609

610
static void GetAttrStrFromFontStack(font_stack_t **p_fonts,
611
        bool b_bold, bool b_italic, bool b_uline,
612
        CFRange p_range, CFMutableAttributedStringRef p_attrString)
613
614
615
616
{
    char       *psz_fontname = NULL;
    int         i_font_size  = 0;
    uint32_t    i_font_color = 0;
617

618
619
620
621
622
623
624
625
    if (VLC_SUCCESS == PeekFont(p_fonts, &psz_fontname, &i_font_size,
                                &i_font_color)) {
        setFontAttibutes(psz_fontname,
                         i_font_size,
                         i_font_color,
                         b_bold, b_italic, b_uline,
                         p_range,
                         p_attrString);
626
627
628
    }
}

629
static int ProcessNodes(filter_t *p_filter,
630
631
                         xml_reader_t *p_xml_reader,
                         text_style_t *p_font_style,
632
                         CFMutableAttributedStringRef p_attrString)
633
{
634
635
636
637
    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
638
639
    int type;
    const char *node;
640
641
642
643

    bool b_italic = false;
    bool b_bold   = false;
    bool b_uline  = false;
644

645
646
    if (p_font_style) {
        rv = PushFont(&p_fonts,
647
               p_font_style->psz_fontname,
648
               p_font_style->i_font_size,
649
               (p_font_style->i_font_color & 0xffffff) |
650
                   ((p_font_style->i_font_alpha & 0xff) << 24));
651

652
        if (p_font_style->i_style_flags & STYLE_BOLD)
653
            b_bold = true;
654
        if (p_font_style->i_style_flags & STYLE_ITALIC)
655
            b_italic = true;
656
        if (p_font_style->i_style_flags & STYLE_UNDERLINE)
657
            b_uline = true;
658
659
    } else {
        rv = PushFont(&p_fonts,
660
661
                       p_sys->psz_font_name,
                       p_sys->i_font_size,
662
                       p_sys->i_font_color);
663
    }
664
    if (rv != VLC_SUCCESS)
665
        return rv;
666

667
668
    while ((type = xml_ReaderNextNode(p_xml_reader, &node)) > 0) {
        switch (type) {
669
            case XML_READER_ENDELEM:
670
671
672
                if (!strcasecmp("font", node))
                    PopFont(&p_fonts);
                else if (!strcasecmp("b", node))
Pierre Ynard's avatar
Pierre Ynard committed
673
                    b_bold   = false;
674
                else if (!strcasecmp("i", node))
Pierre Ynard's avatar
Pierre Ynard committed
675
                    b_italic = false;
676
                else if (!strcasecmp("u", node))
Pierre Ynard's avatar
Pierre Ynard committed
677
                    b_uline  = false;
678

679
680
                break;
            case XML_READER_STARTELEM:
681
682
683
                if (!strcasecmp("font", node))
                    rv = HandleFontAttributes(p_xml_reader, &p_fonts);
                else if (!strcasecmp("b", node))
Pierre Ynard's avatar
Pierre Ynard committed
684
                    b_bold = true;
685
                else if (!strcasecmp("i", node))
Pierre Ynard's avatar
Pierre Ynard committed
686
                    b_italic = true;
687
                else if (!strcasecmp("u", node))
Pierre Ynard's avatar
Pierre Ynard committed
688
                    b_uline = true;
689
                else if (!strcasecmp("br", node)) {
Pierre Ynard's avatar
Pierre Ynard committed
690
                    CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
691
                    CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), CFSTR("\n"));
Pierre Ynard's avatar
Pierre Ynard committed
692

693
694
695
696
                    GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
                                             CFRangeMake(0, 1),
                                             p_attrnode);
                    CFAttributedStringReplaceAttributedString(p_attrString,
Pierre Ynard's avatar
Pierre Ynard committed
697
698
                                    CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
                                    p_attrnode);
699
                    CFRelease(p_attrnode);
700
701
702
                }
                break;
            case XML_READER_TEXT:
Pierre Ynard's avatar
Pierre Ynard committed
703
            {
Pierre Ynard's avatar
Pierre Ynard committed
704
705
706
707
                CFStringRef   p_cfString;
                int           len;

                // Turn any multiple-whitespaces into single spaces
708
709
                char *dup = strdup(node);
                if (!dup)
710
                    break;
711
712
                char *s = strpbrk(dup, "\t\r\n ");
                while(s)
713
                {
714
                    int i_whitespace = strspn(s, "\t\r\n ");
715

716
717
                    if (i_whitespace > 1)
                        memmove(&s[1],
Pierre Ynard's avatar
Pierre Ynard committed
718
                                 &s[i_whitespace],
719
                                 strlen(s) - i_whitespace + 1);
Pierre Ynard's avatar
Pierre Ynard committed
720
                    *s++ = ' ';
721

722
                    s = strpbrk(s, "\t\r\n ");
Pierre Ynard's avatar
Pierre Ynard committed
723
                }
724
725


Pierre Ynard's avatar
Pierre Ynard committed
726
                CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
727
728
729
730
                p_cfString = CFStringCreateWithCString(NULL, dup, kCFStringEncodingUTF8);
                CFAttributedStringReplaceString(p_attrnode, CFRangeMake(0, 0), p_cfString);
                CFRelease(p_cfString);
                len = CFAttributedStringGetLength(p_attrnode);
731

732
733
734
                GetAttrStrFromFontStack(&p_fonts, b_bold, b_italic, b_uline,
                                         CFRangeMake(0, len),
                                         p_attrnode);
735

736
                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
                free(dup);
742
                break;
Pierre Ynard's avatar
Pierre Ynard committed
743
            }
744
        }
745
    }
746

747
    while(VLC_SUCCESS == PopFont(&p_fonts));
748
749

    return rv;
750
751
}

752
static int RenderHtml(filter_t *p_filter, subpicture_region_t *p_region_out,
753
                       subpicture_region_t *p_region_in,
754
                       const vlc_fourcc_t *p_chroma_list)
755
756
757
758
759
{
    int          rv = VLC_SUCCESS;
    stream_t     *p_sub = NULL;
    xml_t        *p_xml = NULL;
    xml_reader_t *p_xml_reader = NULL;
760
    VLC_UNUSED(p_chroma_list);
761

762
    if (!p_region_in || !p_region_in->psz_html)
763
764
        return VLC_EGENERIC;

765
    /* Reset the default fontsize in case screen metrics have changed */
766
    p_filter->p_sys->i_font_size = GetFontSize(p_filter);
767

768
    p_sub = stream_MemoryNew(VLC_OBJECT(p_filter),
769
                              (uint8_t *) p_region_in->psz_html,
770
771
772
773
774
                              strlen(p_region_in->psz_html),
                              true);
    if (p_sub) {
        p_xml = xml_Create(p_filter);
        if (p_xml) {
775
            bool b_karaoke = false;
776

777
778
            p_xml_reader = xml_ReaderCreate(p_xml, p_sub);
            if (p_xml_reader) {
779
                /* Look for Root Node */
Pierre Ynard's avatar
Pierre Ynard committed
780
                const char *name;
781
782
783
                if (xml_ReaderNextNode(p_xml_reader, &name)
                        == XML_READER_STARTELEM) {
                    if (!strcasecmp("karaoke", name)) {
784
785
786
                        /* We're going to have to render the text a number
                         * of times to show the progress marker on the text.
                         */
787
                        var_SetBool(p_filter, "text-rerender", true);
788
                        b_karaoke = true;
789
                    } else if (!strcasecmp("text", name))
790
                        b_karaoke = false;
791
                    else {
792
                        /* Only text and karaoke tags are supported */
793
794
                        msg_Dbg(p_filter, "Unsupported top-level tag "
                                           "<%s> ignored.", name);
795
796
                        rv = VLC_EGENERIC;
                    }
797
798
                } else {
                    msg_Err(p_filter, "Malformed HTML subtitle");
799
800
801
                    rv = VLC_EGENERIC;
                }

802
803
                if (rv != VLC_SUCCESS) {
                    xml_ReaderDelete(p_xml_reader);
804
805
                    p_xml_reader = NULL;
                }
806
807
            }

808
            if (p_xml_reader) {
809
                int         i_len;
810

811
                CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
812
813
                rv = ProcessNodes(p_filter, p_xml_reader,
                              p_region_in->p_style, p_attrString);
814

815
                i_len = CFAttributedStringGetLength(p_attrString);
816

817
818
                p_region_out->i_x = p_region_in->i_x;
                p_region_out->i_y = p_region_in->i_y;
819

820
821
822
                if ((rv == VLC_SUCCESS) && (i_len > 0))
                    RenderYUVA(p_filter, p_region_out, p_attrString);

823
                CFRelease(p_attrString);
824

825
                xml_ReaderDelete(p_xml_reader);
826
            }
827
            xml_Delete(p_xml);
828
        }
829
        stream_Delete(p_sub);
830
831
832
833
834
    }

    return rv;
}

835
836
static CGContextRef CreateOffScreenContext(int i_width, int i_height,
                         offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace)
837
838
839
840
{
    offscreen_bitmap_t *p_bitmap;
    CGContextRef        p_context = NULL;

841
842
    p_bitmap = (offscreen_bitmap_t *) malloc(sizeof(offscreen_bitmap_t));
    if (p_bitmap) {
843
844
845
846
847
        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;

848
        p_bitmap->p_data = calloc(i_height, p_bitmap->i_bytesPerRow);
849

Pierre's avatar
Pierre committed
850
        *pp_colorSpace = CGColorSpaceCreateDeviceRGB();
851

852
853
        if (p_bitmap->p_data && *pp_colorSpace)
            p_context = CGBitmapContextCreate(p_bitmap->p_data, i_width, i_height,
854
855
                                p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
                                *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
856
857
858
859

        if (p_context) {
            if (CGContextSetAllowsAntialiasing != NULL)
                CGContextSetAllowsAntialiasing(p_context, true);
860
861
862
863
864
865
866
        }
        *pp_memory = p_bitmap;
    }

    return p_context;
}

867
static offscreen_bitmap_t *Compose(int i_text_align,
868
                                    CFMutableAttributedStringRef p_attrString,
Pierre's avatar
Pierre committed
869
870
                                    unsigned i_width,
                                    unsigned i_height,
871
                                    unsigned *pi_textblock_height)
872
873
874
875
{
    offscreen_bitmap_t  *p_offScreen  = NULL;
    CGColorSpaceRef      p_colorSpace = NULL;
    CGContextRef         p_context = NULL;
bitmap's avatar
bitmap committed
876

877
    p_context = CreateOffScreenContext(i_width, i_height, &p_offScreen, &p_colorSpace);
878

879
    *pi_textblock_height = 0;
880
    if (p_context) {
881
        float horiz_flush;
882

883
        CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
884

885
        if (i_text_align == SUBPICTURE_ALIGN_RIGHT)
886
            horiz_flush = 1.0;
887
        else if (i_text_align != SUBPICTURE_ALIGN_LEFT)
888
889
890
            horiz_flush = 0.5;
        else
            horiz_flush = 0.0;
891

892
893
        // Create the framesetter with the attributed string.
        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
894
        if (framesetter) {
895
896
            CTFrameRef frame;
            CGMutablePathRef p_path = CGPathCreateMutable();
897
            CGRect p_bounds = CGRectMake((float)HORIZONTAL_MARGIN,
898
899
900
                                          (float)VERTICAL_MARGIN,
                                          (float)(i_width  - HORIZONTAL_MARGIN*2),
                                          (float)(i_height - VERTICAL_MARGIN  *2));
901
            CGPathAddRect(p_path, NULL, p_bounds);
902

903
904
            // Create the frame and draw it into the graphics context
            frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
905

906
            CGPathRelease(p_path);
907

bitmap's avatar
bitmap committed
908
            // Set up black outlining of the text --
909
910
            CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
            CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
911

912
            if (frame != NULL) {
913
914
                CFArrayRef lines;
                CGPoint    penPosition;
915

916
                lines = CTFrameGetLines(frame);
917
                penPosition.y = i_height;
918
                for (int i=0; i<CFArrayGetCount(lines); i++) {
919
                    CGFloat  ascent, descent, leading;
920

921
922
                    CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
                    CTLineGetTypographicBounds(line, &ascent, &descent, &leading);