Coverage Report

Created: 2025-10-12 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/codec/ttml/substtml.c
Line
Count
Source
1
/*****************************************************************************
2
 * substtml.c : TTML subtitles decoder
3
 *****************************************************************************
4
 * Copyright (C) 2015-2017 VLC authors and VideoLAN
5
 *
6
 * Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
7
 *          Sushma Reddy <sushma.reddy@research.iiit.ac.in>
8
 *
9
 * This program is free software; you can redistribute it and/or modify it
10
 * under the terms of the GNU Lesser General Public License as published by
11
 * the Free Software Foundation; either version 2.1 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public License
20
 * along with this program; if not, write to the Free Software Foundation,
21
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22
 *****************************************************************************/
23
#ifdef HAVE_CONFIG_H
24
# include "config.h"
25
#endif
26
27
#include <vlc_common.h>
28
#include <vlc_codec.h>
29
#include <vlc_xml.h>
30
#include <vlc_stream.h>
31
#include <vlc_text_style.h>
32
#include <vlc_charset.h>
33
#include <vlc_image.h>
34
#include <vlc_memstream.h>
35
36
#include <ctype.h>
37
#include <assert.h>
38
39
#include "../substext.h"
40
#include "ttml.h"
41
#include "imageupdater.h"
42
#include "ttmlpes.h"
43
44
//#define TTML_DEBUG
45
46
/*****************************************************************************
47
 * Local prototypes
48
 *****************************************************************************/
49
typedef struct
50
{
51
    float       i_value;
52
    enum
53
    {
54
        TTML_UNIT_UNKNOWN = 0,
55
        TTML_UNIT_PERCENT,
56
        TTML_UNIT_CELL,
57
        TTML_UNIT_PIXELS,
58
    } unit;
59
} ttml_length_t;
60
61
9.42k
#define TTML_DEFAULT_CELL_RESOLUTION_H 32
62
9.42k
#define TTML_DEFAULT_CELL_RESOLUTION_V 15
63
21.9k
#define TTML_LINE_TO_HEIGHT_RATIO      1.06
64
65
66
typedef struct
67
{
68
    text_style_t*   font_style;
69
    ttml_length_t   font_size;
70
    /* sizes override */
71
    ttml_length_t   extent_h, extent_v;
72
    ttml_length_t   origin_h, origin_v;
73
    int             i_text_align;
74
    bool            b_text_align_set;
75
    int             i_direction;
76
    bool            b_direction_set;
77
    bool            b_preserve_space;
78
    enum
79
    {
80
        TTML_DISPLAY_UNKNOWN = 0,
81
        TTML_DISPLAY_AUTO,
82
        TTML_DISPLAY_NONE,
83
    } display;
84
}  ttml_style_t;
85
86
typedef struct
87
{
88
    vlc_dictionary_t regions;
89
    tt_node_t *      p_rootnode; /* for now. FIXME: split header */
90
    ttml_length_t    root_extent_h, root_extent_v;
91
    unsigned         i_cell_resolution_v;
92
    unsigned         i_cell_resolution_h;
93
} ttml_context_t;
94
95
typedef struct
96
{
97
    substext_updater_region_t updt;
98
    text_segment_t **pp_last_segment;
99
    struct
100
    {
101
        uint8_t *p_bytes;
102
        size_t   i_bytes;
103
    } bgbitmap; /* SMPTE-TT */
104
} ttml_region_t;
105
106
typedef struct
107
{
108
    int                     i_align;
109
    struct ttml_in_pes_ctx  pes;
110
} decoder_sys_t;
111
112
enum
113
{
114
    UNICODE_BIDI_LTR = 0,
115
    UNICODE_BIDI_RTL = 1,
116
    UNICODE_BIDI_EMBEDDED = 2,
117
    UNICODE_BIDI_OVERRIDE = 4,
118
};
119
120
/*
121
 * TTML Parsing and inheritance order:
122
 * Each time a text node is found and belongs to out time interval,
123
 * we backward merge attributes dictionary up to root.
124
 * Then we convert attributes, merging with style by id or region
125
 * style, and sets from parent node.
126
 */
127
static tt_node_t *ParseTTML( decoder_t *, tt_namespaces_t *,
128
                             const uint8_t *, size_t );
129
130
static void ttml_style_Delete( ttml_style_t* p_ttml_style )
131
23.1k
{
132
23.1k
    text_style_Delete( p_ttml_style->font_style );
133
23.1k
    free( p_ttml_style );
134
23.1k
}
135
136
static ttml_style_t * ttml_style_New( void )
137
23.1k
{
138
23.1k
    ttml_style_t *p_ttml_style = calloc( 1, sizeof( ttml_style_t ) );
139
23.1k
    if( unlikely( !p_ttml_style ) )
140
0
        return NULL;
141
142
23.1k
    p_ttml_style->extent_h.unit = TTML_UNIT_UNKNOWN;
143
23.1k
    p_ttml_style->extent_v.unit = TTML_UNIT_UNKNOWN;
144
23.1k
    p_ttml_style->origin_h.unit = TTML_UNIT_UNKNOWN;
145
23.1k
    p_ttml_style->origin_v.unit = TTML_UNIT_UNKNOWN;
146
23.1k
    p_ttml_style->font_size.i_value = 1.0;
147
23.1k
    p_ttml_style->font_size.unit = TTML_UNIT_CELL;
148
23.1k
    p_ttml_style->font_style = text_style_Create( STYLE_NO_DEFAULTS );
149
23.1k
    if( unlikely( !p_ttml_style->font_style ) )
150
0
    {
151
0
        free( p_ttml_style );
152
0
        return NULL;
153
0
    }
154
23.1k
    return p_ttml_style;
155
23.1k
}
156
157
static void ttml_region_Delete( ttml_region_t *p_region )
158
20.2k
{
159
20.2k
    SubpictureUpdaterSysRegionClean( &p_region->updt );
160
20.2k
    free( p_region->bgbitmap.p_bytes );
161
20.2k
    free( p_region );
162
20.2k
}
163
164
static ttml_style_t * ttml_style_Duplicate( const ttml_style_t *p_src )
165
0
{
166
0
    ttml_style_t *p_dup = ttml_style_New( );
167
0
    if( p_dup )
168
0
    {
169
0
        *p_dup = *p_src;
170
0
        p_dup->font_style = text_style_Duplicate( p_src->font_style );
171
0
    }
172
0
    return p_dup;
173
0
}
174
175
static void ttml_style_Merge( const ttml_style_t *p_src, ttml_style_t *p_dst )
176
0
{
177
0
    if( p_src && p_dst )
178
0
    {
179
0
        if( p_src->font_style )
180
0
        {
181
0
            if( p_dst->font_style )
182
0
                text_style_Merge( p_dst->font_style, p_src->font_style, true );
183
0
            else
184
0
                p_dst->font_style = text_style_Duplicate( p_src->font_style );
185
0
        }
186
187
0
        if( p_src->b_direction_set )
188
0
        {
189
0
            p_dst->b_direction_set = true;
190
0
            p_dst->i_direction = p_src->i_direction;
191
0
        }
192
193
0
        if( p_src->display != TTML_DISPLAY_UNKNOWN )
194
0
            p_dst->display = p_src->display;
195
0
    }
196
0
}
197
198
static ttml_region_t *ttml_region_New( bool b_root )
199
20.2k
{
200
20.2k
    ttml_region_t *p_ttml_region = calloc( 1, sizeof( ttml_region_t ) );
201
20.2k
    if( unlikely( !p_ttml_region ) )
202
0
        return NULL;
203
204
20.2k
    SubpictureUpdaterSysRegionInit( &p_ttml_region->updt );
205
20.2k
    p_ttml_region->pp_last_segment = &p_ttml_region->updt.p_segments;
206
    /* Align to top by default. !Warn: center align is obtained with NO flags */
207
20.2k
    p_ttml_region->updt.align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
208
20.2k
    if( b_root )
209
4.98k
    {
210
4.98k
        p_ttml_region->updt.inner_align = SUBPICTURE_ALIGN_BOTTOM;
211
4.98k
        p_ttml_region->updt.extent.x = 1.0;
212
4.98k
        p_ttml_region->updt.extent.y = 1.0;
213
4.98k
        p_ttml_region->updt.flags = UPDT_REGION_EXTENT_X_IS_RATIO|UPDT_REGION_EXTENT_Y_IS_RATIO;
214
4.98k
    }
215
15.2k
    else
216
15.2k
    {
217
15.2k
        p_ttml_region->updt.inner_align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
218
15.2k
    }
219
220
20.2k
    return p_ttml_region;
221
20.2k
}
222
223
static ttml_length_t ttml_read_length( const char *psz )
224
26.2k
{
225
26.2k
    ttml_length_t len = { 0.0, TTML_UNIT_UNKNOWN };
226
227
26.2k
    char* psz_end = NULL;
228
26.2k
    float size = vlc_strtof_c( psz, &psz_end );
229
26.2k
    len.i_value = size;
230
26.2k
    if( psz_end )
231
26.2k
    {
232
26.2k
        if( *psz_end == 'c' || *psz_end == 'r' )
233
2.24k
            len.unit = TTML_UNIT_CELL;
234
23.9k
        else if( *psz_end == '%' )
235
4.88k
            len.unit = TTML_UNIT_PERCENT;
236
19.1k
        else if( *psz_end == 'p' && *(psz_end + 1) == 'x' )
237
13.3k
            len.unit = TTML_UNIT_PIXELS;
238
26.2k
    }
239
26.2k
    return len;
240
26.2k
}
241
242
static ttml_length_t ttml_rebase_length( unsigned i_cell_resolution,
243
                                         ttml_length_t value,
244
                                         ttml_length_t reference )
245
35.3k
{
246
35.3k
    if( value.unit == TTML_UNIT_PERCENT )
247
3.68k
    {
248
3.68k
        value.i_value *= reference.i_value / 100.0;
249
3.68k
        value.unit = reference.unit;
250
3.68k
    }
251
31.6k
    else if( value.unit == TTML_UNIT_CELL )
252
23.4k
    {
253
23.4k
        value.i_value *= reference.i_value / i_cell_resolution;
254
23.4k
        value.unit = reference.unit;
255
23.4k
    }
256
    // pixels as-is
257
35.3k
    return value;
258
35.3k
}
259
260
static bool ttml_read_coords( const char *value, ttml_length_t *h, ttml_length_t *v )
261
11.5k
{
262
11.5k
    ttml_length_t vals[2] = { { 0.0, TTML_UNIT_UNKNOWN },
263
11.5k
                              { 0.0, TTML_UNIT_UNKNOWN } };
264
11.5k
    char *dup = strdup( value );
265
11.5k
    char* psz_saveptr = NULL;
266
11.5k
    char* token = (dup) ? strtok_r( dup, " ", &psz_saveptr ) : NULL;
267
33.9k
    for(int i=0; i<2 && token != NULL; i++)
268
22.4k
    {
269
22.4k
        vals[i] = ttml_read_length( token );
270
22.4k
        token = strtok_r( NULL, " ", &psz_saveptr );
271
22.4k
    }
272
11.5k
    free( dup );
273
274
11.5k
    if( vals[0].unit != TTML_UNIT_UNKNOWN &&
275
7.74k
        vals[1].unit != TTML_UNIT_UNKNOWN )
276
6.69k
    {
277
6.69k
        *h = vals[0];
278
6.69k
        *v = vals[1];
279
6.69k
        return true;
280
6.69k
    }
281
4.88k
    return false;
282
11.5k
}
283
284
static tt_node_t * FindNode( tt_namespaces_t *p_nss, tt_node_t *p_node,
285
                             const char *psz_nodename, const char *psz_namespace,
286
                             size_t i_maxdepth, const char *psz_id )
287
3.49M
{
288
3.49M
    if( tt_node_Match( p_node, psz_nodename, psz_namespace ) )
289
81.2k
    {
290
81.2k
        if( psz_id != NULL )
291
71.8k
        {
292
71.8k
            const char *psz = tt_node_GetAttribute( p_nss, p_node, "id", TT_NS_XML );
293
71.8k
            if( !psz ) /* People can't do xml properly */
294
2.99k
                psz = tt_node_GetAttribute( p_nss, p_node, "id", NULL );
295
71.8k
            if( psz && !strcmp( psz, psz_id ) )
296
37.4k
                return p_node;
297
71.8k
        }
298
9.42k
        else return p_node;
299
81.2k
    }
300
301
3.45M
    if( i_maxdepth == 0 )
302
5.47k
        return NULL;
303
304
3.44M
    for( tt_basenode_t *p_child = p_node->p_child;
305
6.78M
                        p_child; p_child = p_child->p_next )
306
3.83M
    {
307
3.83M
        if( p_child->i_type == TT_NODE_TYPE_TEXT )
308
422k
            continue;
309
310
3.41M
        p_node = FindNode( p_nss, (tt_node_t *) p_child,
311
3.41M
                           psz_nodename, psz_namespace,
312
3.41M
                           i_maxdepth - 1, psz_id );
313
3.41M
        if( p_node )
314
496k
            return p_node;
315
3.41M
    }
316
317
2.94M
    return NULL;
318
3.44M
}
319
320
static void FillTextStyle( const char *psz_attr, const char *psz_val,
321
                           text_style_t *p_text_style )
322
38.9k
{
323
38.9k
    if( !strcasecmp ( "fontFamily", psz_attr ) )
324
3.37k
    {
325
3.37k
        free( p_text_style->psz_fontname );
326
3.37k
        p_text_style->psz_fontname = strdup( psz_val );
327
3.37k
    }
328
35.5k
    else if( !strcasecmp( "opacity", psz_attr ) )
329
210
    {
330
210
        p_text_style->i_background_alpha = atoi( psz_val );
331
210
        p_text_style->i_font_alpha = atoi( psz_val );
332
210
        p_text_style->i_features |= STYLE_HAS_BACKGROUND_ALPHA | STYLE_HAS_FONT_ALPHA;
333
210
    }
334
35.3k
    else if( !strcasecmp( "color", psz_attr ) )
335
4.84k
    {
336
4.84k
        unsigned int i_color = vlc_html_color( psz_val, NULL );
337
4.84k
        p_text_style->i_font_color = (i_color & 0xffffff);
338
4.84k
        p_text_style->i_font_alpha = (i_color & 0xFF000000) >> 24;
339
4.84k
        p_text_style->i_features |= STYLE_HAS_FONT_COLOR | STYLE_HAS_FONT_ALPHA;
340
4.84k
    }
341
30.4k
    else if( !strcasecmp( "backgroundColor", psz_attr ) )
342
8.37k
    {
343
8.37k
        unsigned int i_color = vlc_html_color( psz_val, NULL );
344
8.37k
        p_text_style->i_background_color = i_color & 0xFFFFFF;
345
8.37k
        p_text_style->i_background_alpha = (i_color & 0xFF000000) >> 24;
346
8.37k
        p_text_style->i_features |= STYLE_HAS_BACKGROUND_COLOR
347
8.37k
                                                  | STYLE_HAS_BACKGROUND_ALPHA;
348
8.37k
        p_text_style->i_style_flags |= STYLE_BACKGROUND;
349
8.37k
    }
350
22.0k
    else if( !strcasecmp( "fontStyle", psz_attr ) )
351
0
    {
352
0
        if( !strcasecmp ( "italic", psz_val ) || !strcasecmp ( "oblique", psz_val ) )
353
0
            p_text_style->i_style_flags |= STYLE_ITALIC;
354
0
        else
355
0
            p_text_style->i_style_flags &= ~STYLE_ITALIC;
356
0
        p_text_style->i_features |= STYLE_HAS_FLAGS;
357
0
    }
358
22.0k
    else if( !strcasecmp ( "fontWeight", psz_attr ) )
359
88
    {
360
88
        if( !strcasecmp ( "bold", psz_val ) )
361
0
            p_text_style->i_style_flags |= STYLE_BOLD;
362
88
        else
363
88
            p_text_style->i_style_flags &= ~STYLE_BOLD;
364
88
        p_text_style->i_features |= STYLE_HAS_FLAGS;
365
88
    }
366
22.0k
    else if( !strcasecmp ( "textDecoration", psz_attr ) )
367
5
    {
368
5
        if( !strcasecmp ( "underline", psz_val ) )
369
0
            p_text_style->i_style_flags |= STYLE_UNDERLINE;
370
5
        else if( !strcasecmp ( "noUnderline", psz_val ) )
371
0
            p_text_style->i_style_flags &= ~STYLE_UNDERLINE;
372
5
        if( !strcasecmp ( "lineThrough", psz_val ) )
373
0
            p_text_style->i_style_flags |= STYLE_STRIKEOUT;
374
5
        else if( !strcasecmp ( "noLineThrough", psz_val ) )
375
0
            p_text_style->i_style_flags &= ~STYLE_STRIKEOUT;
376
5
        p_text_style->i_features |= STYLE_HAS_FLAGS;
377
5
    }
378
22.0k
    else if( !strcasecmp( "textOutline", psz_attr ) )
379
0
    {
380
0
        char *value = strdup( psz_val );
381
0
        char* psz_saveptr = NULL;
382
0
        char* token = (value) ? strtok_r( value, " ", &psz_saveptr ) : NULL;
383
        // <color>? <length> <length>?
384
0
        if( token != NULL )
385
0
        {
386
0
            bool b_ok = false;
387
0
            unsigned int color = vlc_html_color( token, &b_ok );
388
0
            if( b_ok )
389
0
            {
390
0
                p_text_style->i_outline_color = color & 0xFFFFFF;
391
0
                p_text_style->i_outline_alpha = (color & 0xFF000000) >> 24;
392
0
                token = strtok_r( NULL, " ", &psz_saveptr );
393
0
                if( token != NULL )
394
0
                {
395
0
                    char* psz_end = NULL;
396
0
                    int i_outline_width = strtol( token, &psz_end, 10 );
397
0
                    if( psz_end != token )
398
0
                    {
399
                        // Assume unit is pixel, and ignore border radius
400
0
                        p_text_style->i_outline_width = i_outline_width;
401
0
                    }
402
0
                }
403
0
            }
404
0
        }
405
0
        free( value );
406
0
    }
407
38.9k
}
408
409
static void FillCoord( ttml_length_t v, int i_flag, float *p_val, int *pi_flags )
410
13.3k
{
411
13.3k
    *p_val = v.i_value;
412
13.3k
    if( v.unit == TTML_UNIT_PERCENT )
413
5.25k
    {
414
5.25k
        *p_val /= 100.0;
415
5.25k
        *pi_flags |= i_flag;
416
5.25k
    }
417
8.13k
    else *pi_flags &= ~i_flag;
418
13.3k
}
419
420
static void FillUpdaterCoords( ttml_context_t *p_ctx, ttml_length_t h, ttml_length_t v,
421
                               bool b_origin, substext_updater_region_t *p_updt )
422
6.69k
{
423
6.69k
    ttml_length_t base = { 100.0, TTML_UNIT_PERCENT };
424
6.69k
    ttml_length_t x = ttml_rebase_length( p_ctx->i_cell_resolution_h, h, base );
425
6.69k
    ttml_length_t y = ttml_rebase_length( p_ctx->i_cell_resolution_v, v, base );
426
6.69k
    if( b_origin )
427
824
    {
428
824
        FillCoord( x, UPDT_REGION_ORIGIN_X_IS_RATIO, &p_updt->origin.x, &p_updt->flags );
429
824
        FillCoord( y, UPDT_REGION_ORIGIN_Y_IS_RATIO, &p_updt->origin.y, &p_updt->flags );
430
824
        p_updt->align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
431
824
    }
432
5.86k
    else
433
5.86k
    {
434
5.86k
        FillCoord( x, UPDT_REGION_EXTENT_X_IS_RATIO, &p_updt->extent.x, &p_updt->flags );
435
5.86k
        FillCoord( y, UPDT_REGION_EXTENT_Y_IS_RATIO, &p_updt->extent.y, &p_updt->flags );
436
5.86k
    }
437
6.69k
}
438
439
static void FillRegionStyle( ttml_context_t *p_ctx,
440
                             const char *psz_attr, const char *psz_namespace,
441
                             const char *psz_val, ttml_region_t *p_region )
442
14.4k
{
443
14.4k
    if( strcmp( psz_namespace, TT_NS_STYLING ) )
444
4.23k
        return;
445
446
10.2k
    if( !strcasecmp( "displayAlign", psz_attr ) )
447
1.61k
    {
448
1.61k
        p_region->updt.inner_align &= ~(SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_BOTTOM);
449
1.61k
        if( !strcasecmp( "after", psz_val ) )
450
1.23k
            p_region->updt.inner_align |= SUBPICTURE_ALIGN_BOTTOM;
451
376
        else if( strcasecmp( "center", psz_val ) )
452
            /* "before" */
453
376
            p_region->updt.inner_align |= SUBPICTURE_ALIGN_TOP;
454
1.61k
    }
455
8.62k
    else if( !strcasecmp ( "origin", psz_attr ) ||
456
8.36k
             !strcasecmp ( "extent", psz_attr ) )
457
2.11k
    {
458
2.11k
        ttml_length_t x, y;
459
2.11k
        if( ttml_read_coords( psz_val, &x, &y ) )
460
1.41k
            FillUpdaterCoords( p_ctx, x, y, (psz_attr[0] == 'o'), &p_region->updt );
461
2.11k
    }
462
10.2k
}
463
464
static void ComputeTTMLStyles( ttml_context_t *p_ctx, const vlc_dictionary_t *p_dict,
465
                               ttml_style_t *p_ttml_style )
466
23.1k
{
467
23.1k
    VLC_UNUSED(p_dict);
468
    /* Values depending on multiple others are converted last
469
     * Default value conversion must also not depend on attribute presence */
470
23.1k
    text_style_t *p_text_style = p_ttml_style->font_style;
471
23.1k
    ttml_length_t len = p_ttml_style->font_size;
472
473
    /* font size is pixels, cells or, % of cell */
474
23.1k
    if( len.unit == TTML_UNIT_PERCENT )
475
841
    {
476
841
        len.i_value /= 100.0;
477
841
        len.unit = TTML_UNIT_CELL;
478
841
    }
479
480
    /* font size is now pixels or cells */
481
    /* if cell (and indirectly cell %), rebase as line height depending on resolution */
482
23.1k
    if( len.unit == TTML_UNIT_CELL )
483
21.9k
        len = ttml_rebase_length( p_ctx->i_cell_resolution_v, len, p_ctx->root_extent_v );
484
485
    /* font size is root_extent height % or pixels */
486
23.1k
    if( len.unit == TTML_UNIT_PERCENT )
487
21.9k
        p_text_style->f_font_relsize = len.i_value / TTML_LINE_TO_HEIGHT_RATIO;
488
1.23k
    else
489
1.23k
    if( len.unit == TTML_UNIT_PIXELS )
490
1.23k
        p_text_style->i_font_size = len.i_value;
491
23.1k
}
492
493
static void FillTTMLStyle( const char *psz_attr, const char *psz_namespace,
494
                           const char *psz_val, ttml_style_t *p_ttml_style )
495
105k
{
496
105k
    if( !strcmp( psz_namespace, TT_NS_XML ) )
497
18.9k
    {
498
18.9k
        if( !strcasecmp( "space", psz_attr ) )
499
0
            p_ttml_style->b_preserve_space = !strcmp( "preserve", psz_val );
500
18.9k
        return;
501
18.9k
    }
502
503
86.0k
    if( strcmp( psz_namespace, TT_NS_STYLING ) )
504
29.1k
        return;
505
506
56.8k
    if( !strcasecmp( "extent", psz_attr ) )
507
8.67k
    {
508
8.67k
        ttml_read_coords( psz_val, &p_ttml_style->extent_h,
509
8.67k
                                   &p_ttml_style->extent_v );
510
8.67k
    }
511
48.2k
    else if( !strcasecmp( "origin", psz_attr ) )
512
781
    {
513
781
        ttml_read_coords( psz_val, &p_ttml_style->origin_h,
514
781
                                   &p_ttml_style->origin_v );
515
781
    }
516
47.4k
    else if( !strcasecmp( "textAlign", psz_attr ) )
517
4.61k
    {
518
4.61k
        p_ttml_style->i_text_align &= ~(SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT);
519
4.61k
        if( !strcasecmp ( "left", psz_val ) )
520
0
            p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_LEFT;
521
4.61k
        else if( !strcasecmp ( "right", psz_val ) )
522
0
            p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_RIGHT;
523
4.61k
        else if( !strcasecmp ( "end", psz_val ) )  /* FIXME: should be BIDI based */
524
1.52k
            p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_RIGHT;
525
3.08k
        else if( strcasecmp ( "center", psz_val ) )
526
            /* == "start" FIXME: should be BIDI based */
527
844
            p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_LEFT;
528
4.61k
        p_ttml_style->b_text_align_set = true;
529
#ifdef TTML_DEBUG
530
        printf("**%s %x\n", psz_val, p_ttml_style->i_text_align);
531
#endif
532
4.61k
    }
533
42.8k
    else if( !strcasecmp( "fontSize", psz_attr ) )
534
3.82k
    {
535
3.82k
        ttml_length_t len = ttml_read_length( psz_val );
536
3.82k
        if( len.unit != TTML_UNIT_UNKNOWN && len.i_value > 0.0 )
537
2.62k
            p_ttml_style->font_size = len;
538
3.82k
    }
539
38.9k
    else if( !strcasecmp( "direction", psz_attr ) )
540
0
    {
541
0
        if( !strcasecmp( "rtl", psz_val ) )
542
0
        {
543
0
            p_ttml_style->i_direction |= UNICODE_BIDI_RTL;
544
0
            p_ttml_style->b_direction_set = true;
545
0
        }
546
0
        else if( !strcasecmp( "ltr", psz_val ) )
547
0
        {
548
0
            p_ttml_style->i_direction |= UNICODE_BIDI_LTR;
549
0
            p_ttml_style->b_direction_set = true;
550
0
        }
551
0
    }
552
38.9k
    else if( !strcasecmp( "unicodeBidi", psz_attr ) )
553
0
    {
554
0
            if( !strcasecmp( "bidiOverride", psz_val ) )
555
0
                p_ttml_style->i_direction |= UNICODE_BIDI_OVERRIDE & ~UNICODE_BIDI_EMBEDDED;
556
0
            else if( !strcasecmp( "embed", psz_val ) )
557
0
                p_ttml_style->i_direction |= UNICODE_BIDI_EMBEDDED & ~UNICODE_BIDI_OVERRIDE;
558
0
    }
559
38.9k
    else if( !strcasecmp( "writingMode", psz_attr ) )
560
0
    {
561
0
        if( !strcasecmp( "rl", psz_val ) || !strcasecmp( "rltb", psz_val ) )
562
0
        {
563
0
            p_ttml_style->i_direction = UNICODE_BIDI_RTL | UNICODE_BIDI_OVERRIDE;
564
            //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
565
0
            p_ttml_style->b_direction_set = true;
566
0
        }
567
0
        else if( !strcasecmp( "lr", psz_val ) || !strcasecmp( "lrtb", psz_val ) )
568
0
        {
569
0
            p_ttml_style->i_direction = UNICODE_BIDI_LTR | UNICODE_BIDI_OVERRIDE;
570
            //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
571
0
            p_ttml_style->b_direction_set = true;
572
0
        }
573
0
    }
574
38.9k
    else if( !strcmp( "display", psz_attr ) )
575
88
    {
576
88
        if( !strcmp( "none", psz_val ) )
577
0
            p_ttml_style->display = TTML_DISPLAY_NONE;
578
88
        else
579
88
            p_ttml_style->display = TTML_DISPLAY_AUTO;
580
88
    }
581
38.9k
    else FillTextStyle( psz_attr, psz_val, p_ttml_style->font_style );
582
56.8k
}
583
584
static void DictionaryMerge( const vlc_dictionary_t *p_src, vlc_dictionary_t *p_dst,
585
                             bool b_override )
586
391k
{
587
853k
    for (size_t i = 0; i < p_src->i_size; ++i)
588
461k
    {
589
461k
        for ( const vlc_dictionary_entry_t* p_entry = p_src->p_entries[i];
590
1.15M
                                            p_entry != NULL; p_entry = p_entry->p_next )
591
688k
        {
592
688k
            if( vlc_dictionary_has_key( p_dst, p_entry->psz_key ) )
593
406k
            {
594
406k
                if( b_override )
595
0
                {
596
0
                    vlc_dictionary_remove_value_for_key( p_dst, p_entry->psz_key, NULL, NULL );
597
0
                    vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value );
598
0
                }
599
406k
            }
600
281k
            else
601
281k
                vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value );
602
688k
        }
603
461k
    }
604
391k
}
605
606
static void DictMergeWithStyleID( ttml_context_t *p_ctx, tt_namespaces_t *p_nss,
607
                                  const char *psz_styles, vlc_dictionary_t *p_dst )
608
25.6k
{
609
25.6k
    assert(p_ctx->p_rootnode);
610
25.6k
    char *psz_dup;
611
25.6k
    if( psz_styles && p_ctx->p_rootnode && (psz_dup = strdup( psz_styles )) )
612
25.6k
    {
613
        /* Use temp dict instead of reverse token processing to
614
         * resolve styles in specified order */
615
25.6k
        vlc_dictionary_t tempdict;
616
25.6k
        vlc_dictionary_init( &tempdict, 0 );
617
618
25.6k
        char *saveptr;
619
25.6k
        char *psz_id = strtok_r( psz_dup, " ", &saveptr );
620
51.3k
        while( psz_id )
621
25.6k
        {
622
            /* Lookup referenced style ID */
623
25.6k
            const tt_node_t *p_node = FindNode( p_nss,
624
25.6k
                                                p_ctx->p_rootnode,
625
25.6k
                                                "style", TT_NS,
626
25.6k
                                                -1, psz_id );
627
25.6k
            if( p_node )
628
15.9k
                DictionaryMerge( &p_node->attr_dict, &tempdict, true );
629
630
25.6k
            psz_id = strtok_r( NULL, " ", &saveptr );
631
25.6k
        }
632
633
25.6k
        if( !vlc_dictionary_is_empty( &tempdict ) )
634
15.9k
            DictionaryMerge( &tempdict, p_dst, false );
635
636
25.6k
        vlc_dictionary_clear( &tempdict, NULL, NULL );
637
25.6k
        free( psz_dup );
638
25.6k
    }
639
25.6k
}
640
641
static void DictMergeWithRegionID( ttml_context_t *p_ctx, tt_namespaces_t *p_nss,
642
                                   const char *psz_id, vlc_dictionary_t *p_dst )
643
48.7k
{
644
48.7k
    assert(p_ctx->p_rootnode);
645
48.7k
    if( psz_id && p_ctx->p_rootnode )
646
48.7k
    {
647
48.7k
        const tt_node_t *p_regionnode = FindNode( p_nss,
648
48.7k
                                                  p_ctx->p_rootnode,
649
48.7k
                                                 "region", TT_NS,
650
48.7k
                                                 -1, psz_id );
651
48.7k
        if( !p_regionnode )
652
27.2k
            return;
653
654
21.5k
        DictionaryMerge( &p_regionnode->attr_dict, p_dst, false );
655
656
21.5k
        const char *psz_styleid =
657
21.5k
            tt_node_GetAttribute( p_nss, p_regionnode, "style", NULL );
658
21.5k
        if( psz_styleid )
659
19.5k
            DictMergeWithStyleID( p_ctx, p_nss, psz_styleid, p_dst );
660
661
21.5k
        for( const tt_basenode_t *p_child = p_regionnode->p_child;
662
22.0k
                                  p_child; p_child = p_child->p_next )
663
549
        {
664
549
            if( unlikely( p_child->i_type == TT_NODE_TYPE_TEXT ) )
665
208
                continue;
666
667
341
            const tt_node_t *p_node = (const tt_node_t *) p_child;
668
341
            if( tt_node_Match( p_node, "style", TT_NS ) )
669
0
            {
670
0
                DictionaryMerge( &p_node->attr_dict, p_dst, false );
671
0
            }
672
341
        }
673
21.5k
    }
674
48.7k
}
675
676
static void DictToTTMLStyle( ttml_context_t *p_ctx, tt_namespaces_t *p_nss,
677
                             const vlc_dictionary_t *p_dict,
678
                             ttml_style_t *p_ttml_style )
679
23.1k
{
680
190k
    for (size_t i = 0; i < p_dict->i_size; ++i)
681
167k
    {
682
167k
        for ( vlc_dictionary_entry_t* p_entry = p_dict->p_entries[i];
683
374k
              p_entry != NULL; p_entry = p_entry->p_next )
684
207k
        {
685
207k
            const char *psz_namespace = tt_namespaces_GetURI( p_nss, p_entry->psz_key );
686
207k
            if( !psz_namespace )
687
102k
                continue;
688
105k
            const char *psz_name = tt_LocalName( p_entry->psz_key );
689
105k
            FillTTMLStyle( psz_name, psz_namespace, p_entry->p_value, p_ttml_style );
690
105k
        }
691
167k
    }
692
23.1k
    ComputeTTMLStyles( p_ctx, p_dict, p_ttml_style );
693
23.1k
}
694
695
static ttml_style_t * InheritTTMLStyles( ttml_context_t *p_ctx, tt_namespaces_t *p_nss,
696
                                         tt_node_t *p_node )
697
22.9k
{
698
22.9k
    assert( p_node );
699
22.9k
    ttml_style_t *p_ttml_style = NULL;
700
22.9k
    vlc_dictionary_t merged;
701
22.9k
    vlc_dictionary_init( &merged, 0 );
702
703
    /* Merge dics backwards without overwriting */
704
360k
    for( ; p_node; p_node = p_node->p_parent )
705
338k
    {
706
338k
        DictionaryMerge( &p_node->attr_dict, &merged, false );
707
708
338k
        const char *psz_styleid = tt_node_GetAttribute( p_nss, p_node, "style", NULL );
709
338k
        if( psz_styleid )
710
6.09k
            DictMergeWithStyleID( p_ctx, p_nss, psz_styleid, &merged );
711
712
338k
        const char *psz_regionid = tt_node_GetAttribute( p_nss, p_node, "region", NULL );
713
338k
        if( psz_regionid )
714
33.5k
            DictMergeWithRegionID( p_ctx, p_nss, psz_regionid, &merged );
715
338k
    }
716
717
22.9k
    if( !vlc_dictionary_is_empty( &merged ) && (p_ttml_style = ttml_style_New()) )
718
22.9k
    {
719
22.9k
        DictToTTMLStyle( p_ctx, p_nss, &merged, p_ttml_style );
720
22.9k
    }
721
722
22.9k
    vlc_dictionary_clear( &merged, NULL, NULL );
723
724
22.9k
    return p_ttml_style;
725
22.9k
}
726
727
static int ParseTTMLChunk( xml_reader_t *p_reader, tt_namespaces_t *p_nss,
728
                           tt_node_t **pp_rootnode )
729
24.7k
{
730
24.7k
    const char *psz_node_name, *psz_node_namespace;
731
732
24.7k
    do
733
39.7k
    {
734
39.7k
        int i_type = xml_ReaderNextNodeNS( p_reader, &psz_node_name, &psz_node_namespace );
735
736
39.7k
        if( i_type <= XML_READER_NONE )
737
24.1k
            break;
738
739
15.6k
        switch(i_type)
740
15.6k
        {
741
0
            default:
742
0
                break;
743
744
15.6k
            case XML_READER_STARTELEM:
745
15.6k
                if( strcmp( psz_node_namespace, TT_NS ) ||
746
15.6k
                    strcmp( tt_LocalName( psz_node_name ), "tt" ) ||
747
15.6k
                    *pp_rootnode != NULL )
748
0
                    return VLC_EGENERIC;
749
15.6k
                *pp_rootnode = tt_node_NewRead( p_reader, p_nss, NULL,
750
15.6k
                                                psz_node_name, psz_node_namespace );
751
15.6k
                if( !*pp_rootnode ||
752
15.6k
                    tt_nodes_Read( p_reader, p_nss, *pp_rootnode ) != VLC_SUCCESS )
753
595
                    return VLC_EGENERIC;
754
15.0k
                break;
755
756
15.0k
            case XML_READER_ENDELEM:
757
0
                if( !*pp_rootnode ||
758
0
                    strcmp( psz_node_name, (*pp_rootnode)->psz_node_name ) )
759
0
                    return VLC_EGENERIC;
760
0
                break;
761
15.6k
        }
762
763
15.6k
    } while( 1 );
764
765
24.1k
    if( *pp_rootnode == NULL )
766
9.03k
        return VLC_EGENERIC;
767
768
15.0k
    return VLC_SUCCESS;
769
24.1k
}
770
771
static void BIDIConvert( text_segment_t *p_segment, int i_direction )
772
0
{
773
    /*
774
    * For bidirectionnal support, we use different enum
775
    * to recognize different cases, en then we add the
776
    * corresponding unicode character to the text of
777
    * the text_segment.
778
    */
779
0
    static const struct
780
0
    {
781
0
        const char* psz_uni_start;
782
0
        const char* psz_uni_end;
783
0
    } p_bidi[] = {
784
0
        { "\u2066", "\u2069" },
785
0
        { "\u2067", "\u2069" },
786
0
        { "\u202A", "\u202C" },
787
0
        { "\u202B", "\u202C" },
788
0
        { "\u202D", "\u202C" },
789
0
        { "\u202E", "\u202C" },
790
0
    };
791
792
0
    if( unlikely((size_t)i_direction >= ARRAY_SIZE(p_bidi)) )
793
0
        return;
794
795
0
    char *psz_text = NULL;
796
0
    if( asprintf( &psz_text, "%s%s%s", p_bidi[i_direction].psz_uni_start,
797
0
                  p_segment->psz_text, p_bidi[i_direction].psz_uni_end ) != -1 )
798
0
    {
799
0
        free( p_segment->psz_text );
800
0
        p_segment->psz_text = psz_text;
801
0
    }
802
0
}
803
804
static void StripSpacing( text_segment_t *p_segment )
805
22.9k
{
806
    /* Newlines must be replaced */
807
22.9k
    char *p = p_segment->psz_text;
808
38.7k
    while( (p = strchr( p, '\n' )) )
809
15.7k
        *p = ' ';
810
22.9k
}
811
812
static ttml_region_t *GetTTMLRegion( ttml_context_t *p_ctx, tt_namespaces_t *p_nss,
813
                                     const char *psz_region_id )
814
34.6k
{
815
34.6k
    ttml_region_t *p_region = ( ttml_region_t * )
816
34.6k
            vlc_dictionary_value_for_key( &p_ctx->regions, psz_region_id ? psz_region_id : "" );
817
34.6k
    if( p_region == NULL )
818
20.2k
    {
819
20.2k
        if( psz_region_id && strcmp( psz_region_id, "" ) ) /* not default region */
820
15.2k
        {
821
            /* Create region if if missing */
822
823
15.2k
            vlc_dictionary_t merged;
824
15.2k
            vlc_dictionary_init( &merged, 0 );
825
            /* Get all attributes, including region > style */
826
15.2k
            DictMergeWithRegionID( p_ctx, p_nss, psz_region_id, &merged );
827
15.2k
            if( (p_region = ttml_region_New( false )) )
828
15.2k
            {
829
                /* Fill from its own attributes */
830
28.1k
                for (size_t i = 0; i < merged.i_size; ++i)
831
12.8k
                {
832
12.8k
                    for ( vlc_dictionary_entry_t* p_entry = merged.p_entries[i];
833
30.1k
                          p_entry != NULL; p_entry = p_entry->p_next )
834
17.2k
                    {
835
17.2k
                        const char *psz_namespace = tt_namespaces_GetURI( p_nss, p_entry->psz_key );
836
17.2k
                        if( !psz_namespace )
837
2.74k
                            continue;
838
14.4k
                        const char *psz_name = tt_LocalName( p_entry->psz_key );
839
14.4k
                        FillRegionStyle( p_ctx, psz_name, psz_namespace,
840
14.4k
                                         p_entry->p_value, p_region );
841
14.4k
                    }
842
12.8k
                }
843
15.2k
            }
844
15.2k
            vlc_dictionary_clear( &merged, NULL, NULL );
845
846
15.2k
            vlc_dictionary_insert( &p_ctx->regions, psz_region_id, p_region );
847
15.2k
        }
848
4.98k
        else if( (p_region = ttml_region_New( true )) ) /* create default */
849
4.98k
        {
850
4.98k
            vlc_dictionary_insert( &p_ctx->regions, "", p_region );
851
4.98k
        }
852
20.2k
    }
853
34.6k
    return p_region;
854
34.6k
}
855
856
static void AppendLineBreakToRegion( ttml_region_t *p_region )
857
73.5k
{
858
73.5k
    text_segment_t *p_segment = text_segment_New( "\n" );
859
73.5k
    if( p_segment )
860
73.5k
    {
861
73.5k
        *p_region->pp_last_segment = p_segment;
862
73.5k
        p_region->pp_last_segment = &p_segment->p_next;
863
73.5k
    }
864
73.5k
}
865
866
static void AppendTextToRegion( ttml_context_t *p_ctx, tt_namespaces_t *p_nss,
867
                                const tt_textnode_t *p_ttnode,
868
                                const ttml_style_t *p_set_styles, ttml_region_t *p_region )
869
22.9k
{
870
22.9k
    text_segment_t *p_segment;
871
872
22.9k
    if( p_region == NULL )
873
0
        return;
874
875
22.9k
    p_segment = text_segment_New( p_ttnode->psz_text );
876
22.9k
    if( p_segment )
877
22.9k
    {
878
22.9k
        bool b_preserve_space = false;
879
22.9k
        ttml_style_t *s = InheritTTMLStyles( p_ctx, p_nss, p_ttnode->p_parent );
880
22.9k
        if( s )
881
22.9k
        {
882
22.9k
            if( p_set_styles )
883
0
                ttml_style_Merge( p_set_styles, s );
884
885
22.9k
            p_segment->style = s->font_style;
886
22.9k
            s->font_style = NULL;
887
888
22.9k
            b_preserve_space = s->b_preserve_space;
889
22.9k
            if( s->b_direction_set )
890
0
                BIDIConvert( p_segment, s->i_direction );
891
892
22.9k
            if( s->display == TTML_DISPLAY_NONE )
893
0
            {
894
                /* Must not display, but still occupies space */
895
0
                p_segment->style->i_features &= ~(STYLE_BACKGROUND|STYLE_OUTLINE|STYLE_STRIKEOUT|STYLE_SHADOW);
896
0
                p_segment->style->i_font_alpha = STYLE_ALPHA_TRANSPARENT;
897
0
                p_segment->style->i_features |= STYLE_HAS_FONT_ALPHA;
898
0
            }
899
900
            /* we don't have paragraph, so no per text line alignment.
901
             * Text style brings horizontal textAlign to region.
902
             * Region itself is styled with vertical displayAlign */
903
22.9k
            if( s->b_text_align_set )
904
4.61k
            {
905
4.61k
                p_region->updt.inner_align &= ~(SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT);
906
4.61k
                p_region->updt.inner_align |= s->i_text_align;
907
4.61k
            }
908
909
22.9k
            if( s->extent_h.unit != TTML_UNIT_UNKNOWN )
910
4.65k
                FillUpdaterCoords( p_ctx, s->extent_h, s->extent_v, false, &p_region->updt );
911
912
22.9k
            if( s->origin_h.unit != TTML_UNIT_UNKNOWN )
913
623
                FillUpdaterCoords( p_ctx, s->origin_h, s->origin_v, true, &p_region->updt );
914
915
22.9k
            ttml_style_Delete( s );
916
22.9k
        }
917
918
22.9k
        if( !b_preserve_space )
919
22.9k
            StripSpacing( p_segment );
920
22.9k
    }
921
922
22.9k
    *p_region->pp_last_segment = p_segment;
923
22.9k
    p_region->pp_last_segment = &p_segment->p_next;
924
22.9k
}
925
926
static const char * GetSMPTEImage( ttml_context_t *p_ctx, tt_namespaces_t *p_nss,
927
                                   const char *psz_id )
928
0
{
929
0
    if( !p_ctx->p_rootnode )
930
0
        return NULL;
931
932
0
    tt_node_t *p_head = FindNode( p_nss, p_ctx->p_rootnode, "head", TT_NS, 1, NULL );
933
0
    if( !p_head )
934
0
        return NULL;
935
936
0
    for( tt_basenode_t *p_child = p_head->p_child;
937
0
                        p_child; p_child = p_child->p_next )
938
0
    {
939
0
        if( p_child->i_type == TT_NODE_TYPE_TEXT )
940
0
            continue;
941
942
0
        tt_node_t *p_node = (tt_node_t *) p_child;
943
0
        if( !tt_node_Match( p_node, "metadata", TT_NS ) )
944
0
            continue;
945
946
0
        tt_node_t *p_imagenode = FindNode( p_nss, p_node, "image", TT_NS_SMPTE_TT_EXT,
947
0
                                          1, psz_id );
948
0
        if( !p_imagenode )
949
0
            continue;
950
951
0
        if( !p_imagenode->p_child || p_imagenode->p_child->i_type != TT_NODE_TYPE_TEXT )
952
0
            return NULL; /* was found but empty or not text node */
953
954
0
        tt_textnode_t *p_textnode = (tt_textnode_t *) p_imagenode->p_child;
955
0
        const char *psz_text = p_textnode->psz_text;
956
0
        while( isspace( *psz_text ) )
957
0
            psz_text++;
958
0
        return psz_text;
959
0
    }
960
961
0
    return NULL;
962
0
}
963
964
static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, tt_namespaces_t *p_nss,
965
                                         const tt_node_t *p_node,
966
                                         ttml_region_t *p_region,
967
                                         const ttml_style_t *p_upper_set_styles,
968
                                         tt_time_t playbacktime )
969
264k
{
970
264k
    if( tt_time_Valid( &playbacktime ) &&
971
264k
       !tt_timings_Contains( &p_node->timings, &playbacktime ) )
972
6.18k
        return;
973
974
258k
    const char *psz_regionid = tt_node_GetAttribute( p_nss, p_node, "region", NULL );
975
976
    /* Region isn't set or is changing */
977
258k
    if( psz_regionid || p_region == NULL )
978
34.6k
        p_region = GetTTMLRegion( p_ctx, p_nss, psz_regionid );
979
980
    /* Check for bitmap profile defined by ST2052 / SMPTE-TT */
981
258k
    if( tt_node_Match( p_node, "div", TT_NS ) )
982
8.17k
    {
983
8.17k
        const char *psz_id = tt_node_GetAttribute( p_nss, p_node, "backgroundImage",
984
8.17k
                                                   TT_NS_SMPTE_TT_EXT );
985
8.17k
        if( !p_region->bgbitmap.p_bytes && psz_id && *psz_id == '#' )
986
0
        {
987
            /* Seems SMPTE can't make diff between html and xml.. */
988
0
            const char *psz_base64 = GetSMPTEImage( p_ctx, p_nss, &psz_id[1] );
989
0
            if( psz_base64 )
990
0
                p_region->bgbitmap.i_bytes =
991
0
                    vlc_b64_decode_binary( &p_region->bgbitmap.p_bytes, psz_base64 );
992
0
        }
993
8.17k
    }
994
995
    /* awkward paragraph handling */
996
258k
    if( tt_node_Match( p_node, "p", TT_NS ) &&
997
91.2k
        p_region->updt.p_segments )
998
67.5k
    {
999
67.5k
        AppendLineBreakToRegion( p_region );
1000
67.5k
    }
1001
1002
    /* Styles from <set> element */
1003
258k
    ttml_style_t *p_set_styles = (p_upper_set_styles)
1004
258k
                               ? ttml_style_Duplicate( p_upper_set_styles )
1005
258k
                               : NULL;
1006
1007
258k
    for( const tt_basenode_t *p_child = p_node->p_child;
1008
543k
                              p_child; p_child = p_child->p_next )
1009
284k
    {
1010
284k
        if( p_child->i_type == TT_NODE_TYPE_TEXT )
1011
22.9k
        {
1012
22.9k
            AppendTextToRegion( p_ctx, p_nss, (const tt_textnode_t *) p_child,
1013
22.9k
                                p_set_styles, p_region );
1014
22.9k
        }
1015
261k
        else if( tt_node_Match( (const tt_node_t *)p_child, "set", TT_NS ) )
1016
237
        {
1017
237
            const tt_node_t *p_set = (const tt_node_t *)p_child;
1018
237
            if( !tt_time_Valid( &playbacktime ) ||
1019
237
                tt_timings_Contains( &p_set->timings, &playbacktime ) )
1020
237
            {
1021
237
                if( p_set_styles != NULL || (p_set_styles = ttml_style_New()) )
1022
237
                {
1023
                    /* Merge with or create a local set of styles to apply to following childs */
1024
237
                    DictToTTMLStyle( p_ctx, p_nss, &p_set->attr_dict, p_set_styles );
1025
237
                }
1026
237
            }
1027
237
        }
1028
261k
        else if( tt_node_Match( (const tt_node_t *)p_child, "br", TT_NS ) )
1029
6.00k
        {
1030
6.00k
            AppendLineBreakToRegion( p_region );
1031
6.00k
        }
1032
255k
        else
1033
255k
        {
1034
255k
            ConvertNodesToRegionContent( p_ctx, p_nss, (const tt_node_t *) p_child,
1035
255k
                                         p_region, p_set_styles, playbacktime );
1036
255k
        }
1037
284k
    }
1038
1039
258k
    if( p_set_styles )
1040
237
        ttml_style_Delete( p_set_styles );
1041
258k
}
1042
1043
static tt_node_t *ParseTTML( decoder_t *p_dec, tt_namespaces_t *p_nss,
1044
                             const uint8_t *p_buffer, size_t i_buffer )
1045
24.7k
{
1046
24.7k
    stream_t*       p_sub;
1047
24.7k
    xml_reader_t*   p_xml_reader;
1048
1049
24.7k
    p_sub = vlc_stream_MemoryNew( p_dec, (uint8_t*) p_buffer, i_buffer, true );
1050
24.7k
    if( unlikely( p_sub == NULL ) )
1051
0
        return NULL;
1052
1053
24.7k
    p_xml_reader = xml_ReaderCreate( p_dec, p_sub );
1054
24.7k
    if( unlikely( p_xml_reader == NULL ) )
1055
0
    {
1056
0
        vlc_stream_Delete( p_sub );
1057
0
        return NULL;
1058
0
    }
1059
1060
24.7k
    tt_node_t *p_rootnode = NULL;
1061
24.7k
    if( ParseTTMLChunk( p_xml_reader, p_nss, &p_rootnode ) != VLC_SUCCESS )
1062
9.62k
    {
1063
9.62k
        if( p_rootnode )
1064
595
            tt_node_RecursiveDelete( p_rootnode );
1065
9.62k
        p_rootnode = NULL;
1066
9.62k
    }
1067
1068
24.7k
    xml_ReaderDelete( p_xml_reader );
1069
24.7k
    vlc_stream_Delete( p_sub );
1070
1071
24.7k
    return p_rootnode;
1072
24.7k
}
1073
1074
static void InitTTMLContext( tt_namespaces_t *p_nss, tt_node_t *p_rootnode,
1075
                             ttml_context_t *p_ctx )
1076
9.42k
{
1077
9.42k
    p_ctx->p_rootnode = p_rootnode;
1078
    /* set defaults required for size/cells computation */
1079
9.42k
    p_ctx->root_extent_h.i_value = 100;
1080
9.42k
    p_ctx->root_extent_h.unit = TTML_UNIT_PERCENT;
1081
9.42k
    p_ctx->root_extent_v.i_value = 100;
1082
9.42k
    p_ctx->root_extent_v.unit = TTML_UNIT_PERCENT;
1083
9.42k
    p_ctx->i_cell_resolution_v = TTML_DEFAULT_CELL_RESOLUTION_V;
1084
9.42k
    p_ctx->i_cell_resolution_h = TTML_DEFAULT_CELL_RESOLUTION_H;
1085
    /* and override them */
1086
9.42k
    const char *value = tt_node_GetAttribute( p_nss, p_rootnode, "extent", TT_NS_STYLING );
1087
9.42k
    if( value )
1088
4
    {
1089
4
        ttml_read_coords( value, &p_ctx->root_extent_h,
1090
4
                               &p_ctx->root_extent_v );
1091
4
    }
1092
9.42k
    value = tt_node_GetAttribute( p_nss, p_rootnode, "cellResolution", TT_NS_PARAMETER );
1093
9.42k
    if( value )
1094
369
    {
1095
369
        unsigned w, h;
1096
369
        if( sscanf( value, "%u %u", &w, &h) == 2 && w && h )
1097
227
        {
1098
227
            p_ctx->i_cell_resolution_h = w;
1099
227
            p_ctx->i_cell_resolution_v = h;
1100
227
        }
1101
369
    }
1102
9.42k
}
1103
1104
static ttml_region_t *GenerateRegions( tt_namespaces_t *p_nss, tt_node_t *p_rootnode,
1105
                                       tt_time_t playbacktime )
1106
10.6k
{
1107
10.6k
    ttml_region_t*  p_regions = NULL;
1108
10.6k
    ttml_region_t** pp_region_last = &p_regions;
1109
1110
10.6k
    if( tt_node_Match( p_rootnode, "tt", TT_NS ) )
1111
10.6k
    {
1112
10.6k
        const tt_node_t *p_bodynode = FindNode( p_nss, p_rootnode, "body", TT_NS, 1, NULL );
1113
10.6k
        if( p_bodynode )
1114
9.42k
        {
1115
9.42k
            ttml_context_t context;
1116
9.42k
            InitTTMLContext( p_nss, p_rootnode, &context );
1117
9.42k
            context.p_rootnode = p_rootnode;
1118
1119
9.42k
            vlc_dictionary_init( &context.regions, 1 );
1120
9.42k
            ConvertNodesToRegionContent( &context, p_nss, p_bodynode, NULL, NULL, playbacktime );
1121
1122
29.9k
            for (size_t i = 0; i < context.regions.i_size; ++i)
1123
20.5k
            {
1124
20.5k
                for ( const vlc_dictionary_entry_t* p_entry = context.regions.p_entries[i];
1125
40.7k
                                                    p_entry != NULL; p_entry = p_entry->p_next )
1126
20.2k
                {
1127
20.2k
                    *pp_region_last = (ttml_region_t *) p_entry->p_value;
1128
20.2k
                    pp_region_last = (ttml_region_t **) &(*pp_region_last)->updt.p_next;
1129
20.2k
                }
1130
20.5k
            }
1131
1132
9.42k
            vlc_dictionary_clear( &context.regions, NULL, NULL );
1133
9.42k
        }
1134
10.6k
    }
1135
0
    else if ( tt_node_Match( p_rootnode, "div", TT_NS ) ||
1136
0
              tt_node_Match( p_rootnode, "p", TT_NS ) )
1137
0
    {
1138
        /* TODO */
1139
0
    }
1140
1141
10.6k
    return p_regions;
1142
10.6k
}
1143
1144
static void TTMLRegionsToSpuTextRegions( decoder_t *p_dec, subpicture_t *p_spu,
1145
                                         ttml_region_t *p_regions )
1146
9.33k
{
1147
9.33k
    decoder_sys_t *p_dec_sys = p_dec->p_sys;
1148
9.33k
    subtext_updater_sys_t *p_spu_sys = p_spu->updater.sys;
1149
9.33k
    substext_updater_region_t *p_updtregion = NULL;
1150
1151
    /* Create region update info from each ttml region */
1152
9.33k
    for( ttml_region_t *p_region = p_regions;
1153
29.5k
         p_region; p_region = (ttml_region_t *) p_region->updt.p_next )
1154
20.2k
    {
1155
20.2k
        if( p_updtregion == NULL )
1156
9.33k
        {
1157
9.33k
            p_updtregion = &p_spu_sys->region;
1158
9.33k
        }
1159
10.8k
        else
1160
10.8k
        {
1161
10.8k
            p_updtregion = SubpictureUpdaterSysRegionNew();
1162
10.8k
            if( p_updtregion == NULL )
1163
0
                break;
1164
10.8k
            SubpictureUpdaterSysRegionAdd( &p_spu_sys->region, p_updtregion );
1165
10.8k
        }
1166
1167
        /* broken legacy align var (can't handle center...). Will change only regions content. */
1168
20.2k
        if( p_dec_sys->i_align & SUBPICTURE_ALIGN_MASK )
1169
0
            p_spu_sys->region.inner_align = p_dec_sys->i_align;
1170
20.2k
        p_updtregion->b_absolute = true; p_updtregion->b_in_window = false;
1171
1172
20.2k
        p_spu_sys->margin_ratio = 0.0;
1173
1174
        /* copy and take ownership of pointeds */
1175
20.2k
        *p_updtregion = p_region->updt;
1176
20.2k
        p_updtregion->p_next = NULL;
1177
20.2k
        p_region->updt.p_region_style = NULL;
1178
20.2k
        p_region->updt.p_segments = NULL;
1179
20.2k
    }
1180
9.33k
}
1181
1182
static bool isValidPixelsExtent( int flags, float extent_x, float extent_y )
1183
0
{
1184
0
    return !( flags & UPDT_REGION_EXTENT_X_IS_RATIO ) && !( flags & UPDT_REGION_EXTENT_Y_IS_RATIO )
1185
0
        && extent_x > 0 && extent_y > 0;
1186
0
}
1187
1188
static bool isValidPercentsExtent( int flags, float extent_x, float extent_y )
1189
0
{
1190
0
    return ( flags & UPDT_REGION_EXTENT_X_IS_RATIO ) && ( flags & UPDT_REGION_EXTENT_Y_IS_RATIO )
1191
0
        && extent_x > 0 && extent_y > 0;
1192
0
}
1193
1194
static void setPixelsWidthAndHeight( video_format_t* fmt, float extent_x, float extent_y )
1195
0
{
1196
0
    fmt->i_width = extent_x;
1197
0
    fmt->i_visible_width = extent_x;
1198
1199
0
    fmt->i_height = extent_y;
1200
0
    fmt->i_visible_height = extent_y;
1201
0
}
1202
1203
static void setPercentsWidthAndHeight( video_format_t* fmt, float extent_x, float extent_y )
1204
0
{
1205
0
    fmt->i_width = extent_x * fmt->i_width;
1206
0
    fmt->i_visible_width = fmt->i_width;
1207
1208
0
    fmt->i_height = extent_y * fmt->i_height;
1209
0
    fmt->i_visible_height = fmt->i_height;
1210
0
}
1211
1212
static picture_t * rescalePicture( image_handler_t *p_image, picture_t *p_picture, float extent_x, float extent_y )
1213
0
{
1214
0
    video_format_t fmt_out;
1215
0
    video_format_Copy( &fmt_out, &p_picture->format );
1216
0
    setPercentsWidthAndHeight( &fmt_out, extent_x, extent_y );
1217
1218
0
    picture_t *p_scaled_picture = image_Convert( p_image, p_picture,  &p_picture->format, &fmt_out );
1219
1220
0
    video_format_Clean( &fmt_out );
1221
1222
0
    return p_scaled_picture;
1223
0
}
1224
1225
static picture_t * picture_CreateFromPNG( decoder_t *p_dec,
1226
                                          const uint8_t *p_data, size_t i_data,
1227
                                          int flags, float extent_x, float extent_y )
1228
0
{
1229
0
    if( i_data < 16 )
1230
0
        return NULL;
1231
1232
0
    video_format_t fmt_out;
1233
0
    video_format_Init( &fmt_out, VLC_CODEC_RGBA );
1234
1235
0
    if ( isValidPixelsExtent( flags, extent_x, extent_y ) )
1236
0
    {
1237
0
        setPixelsWidthAndHeight( &fmt_out, extent_x, extent_y );
1238
0
    }
1239
1240
0
    es_format_t es_in;
1241
0
    es_format_Init( &es_in, VIDEO_ES, VLC_CODEC_PNG );
1242
0
    es_in.video.i_chroma = es_in.i_codec;
1243
1244
0
    block_t *p_block = block_Alloc( i_data );
1245
0
    if( !p_block )
1246
0
        return NULL;
1247
0
    memcpy( p_block->p_buffer, p_data, i_data );
1248
1249
0
    picture_t *p_pic = NULL;
1250
0
    struct vlc_logger *logger = p_dec->obj.logger;
1251
0
    bool no_interact = p_dec->obj.no_interact;
1252
0
    p_dec->obj.logger = NULL;
1253
0
    p_dec->obj.no_interact = true;
1254
0
    image_handler_t *p_image = image_HandlerCreate( p_dec );
1255
0
    if( p_image )
1256
0
    {
1257
0
        p_pic = image_Read( p_image, p_block, &es_in, &fmt_out );
1258
1259
0
        if ( p_pic && isValidPercentsExtent( flags, extent_x, extent_y ) )
1260
0
        {
1261
0
            picture_t *p_scaled_pic = rescalePicture( p_image, p_pic, extent_x, extent_y );
1262
1263
0
            if ( p_scaled_pic )
1264
0
            {
1265
0
                picture_Release( p_pic );
1266
0
                p_pic = p_scaled_pic;
1267
0
            }
1268
0
        }
1269
1270
0
        image_HandlerDelete( p_image );
1271
0
    }
1272
0
    else block_Release( p_block );
1273
0
    p_dec->obj.no_interact = no_interact;
1274
0
    p_dec->obj.logger = logger;
1275
0
    es_format_Clean( &es_in );
1276
0
    video_format_Clean( &fmt_out );
1277
1278
0
    return p_pic;
1279
0
}
1280
1281
static void TTMLRegionsToSpuBitmapRegions( decoder_t *p_dec, subpicture_t *p_spu,
1282
                                           ttml_region_t *p_regions )
1283
0
{
1284
    /* Create region update info from each ttml region */
1285
0
    for( ttml_region_t *p_region = p_regions;
1286
0
                        p_region; p_region = (ttml_region_t *) p_region->updt.p_next )
1287
0
    {
1288
0
        picture_t *p_pic = picture_CreateFromPNG( p_dec, p_region->bgbitmap.p_bytes,
1289
0
                                                         p_region->bgbitmap.i_bytes,
1290
0
                                                         p_region->updt.flags,
1291
0
                                                         p_region->updt.extent.x,
1292
0
                                                         p_region->updt.extent.y );
1293
0
        if( p_pic )
1294
0
        {
1295
0
            ttml_image_updater_region_t *r = TTML_ImageUpdaterRegionNew( p_pic );
1296
0
            if( !r )
1297
0
            {
1298
0
                picture_Release( p_pic );
1299
0
                continue;
1300
0
            }
1301
            /* use text updt values/flags for ease */
1302
0
            static_assert((int)UPDT_REGION_ORIGIN_X_IS_RATIO == (int)ORIGIN_X_IS_RATIO,
1303
0
                          "flag enums values differs");
1304
0
            static_assert((int)UPDT_REGION_EXTENT_Y_IS_RATIO == (int)EXTENT_Y_IS_RATIO,
1305
0
                          "flag enums values differs");
1306
0
            r->i_flags = p_region->updt.flags;
1307
0
            r->origin.x = p_region->updt.origin.x;
1308
0
            r->origin.y = p_region->updt.origin.y;
1309
0
            r->extent.x = p_region->updt.extent.x;
1310
0
            r->extent.y = p_region->updt.extent.y;
1311
0
            TTML_ImageSpuAppendRegion( p_spu->updater.sys, r );
1312
0
        }
1313
0
    }
1314
0
}
1315
1316
static int ParseBlock( decoder_t *p_dec, const block_t *p_block )
1317
24.7k
{
1318
24.7k
    decoder_sys_t *p_sys = p_dec->p_sys;
1319
1320
24.7k
    tt_time_t *p_timings_array = NULL;
1321
24.7k
    size_t   i_timings_count = 0;
1322
1323
    /* We Only support absolute timings */
1324
24.7k
    tt_timings_t temporal_extent;
1325
24.7k
    temporal_extent.i_type = TT_TIMINGS_PARALLEL;
1326
24.7k
    tt_time_Init( &temporal_extent.begin );
1327
24.7k
    tt_time_Init( &temporal_extent.end );
1328
24.7k
    tt_time_Init( &temporal_extent.dur );
1329
24.7k
    temporal_extent.begin.base = 0;
1330
1331
24.7k
    if( p_block->i_flags & BLOCK_FLAG_CORRUPTED )
1332
0
        return VLCDEC_SUCCESS;
1333
1334
    /* We cannot display a subpicture with no date */
1335
24.7k
    if( p_block->i_pts == VLC_TICK_INVALID )
1336
0
    {
1337
0
        msg_Warn( p_dec, "subtitle without a date" );
1338
0
        return VLCDEC_SUCCESS;
1339
0
    }
1340
1341
24.7k
    tt_namespaces_t namespaces;
1342
24.7k
    tt_namespaces_Init( &namespaces );
1343
24.7k
    tt_node_t *p_rootnode = ParseTTML( p_dec, &namespaces, p_block->p_buffer, p_block->i_buffer );
1344
24.7k
    if( !p_rootnode )
1345
9.62k
    {
1346
9.62k
        tt_namespaces_Clean( &namespaces );
1347
9.62k
        return VLCDEC_SUCCESS;
1348
9.62k
    }
1349
1350
15.0k
    tt_timings_Resolve( (tt_basenode_t *) p_rootnode, &temporal_extent,
1351
15.0k
                        &p_timings_array, &i_timings_count );
1352
1353
#ifdef TTML_DEBUG
1354
    for( size_t i=0; i<i_timings_count; i++ )
1355
        printf("%ld ", tt_time_Convert( &p_timings_array[i] ) );
1356
    printf("\n");
1357
#endif
1358
15.0k
    vlc_tick_t i_block_start_time = p_block->i_dts - p_sys->pes.i_offset;
1359
1360
15.0k
    if(TTML_in_PES(p_dec) && i_block_start_time < p_sys->pes.i_prev_segment_start_time )
1361
0
        i_block_start_time = p_sys->pes.i_prev_segment_start_time;
1362
1363
160k
    for( size_t i=0; i+1 < i_timings_count; i++ )
1364
151k
    {
1365
        /* We Only support absolute timings (2) */
1366
151k
        if( tt_time_Convert( &p_timings_array[i] ) + VLC_TICK_0 < i_block_start_time )
1367
135k
            continue;
1368
1369
16.7k
        if( !TTML_in_PES(p_dec) &&
1370
16.7k
            tt_time_Convert( &p_timings_array[i] ) + VLC_TICK_0 > i_block_start_time + p_block->i_length )
1371
6.09k
            break;
1372
1373
10.6k
        if( TTML_in_PES(p_dec) && p_sys->pes.i_prev_segment_start_time < tt_time_Convert( &p_timings_array[i] ) )
1374
0
            p_sys->pes.i_prev_segment_start_time = tt_time_Convert( &p_timings_array[i] );
1375
1376
10.6k
        bool b_bitmap_regions = false;
1377
10.6k
        subpicture_t *p_spu = NULL;
1378
10.6k
        ttml_region_t *p_regions = GenerateRegions( &namespaces, p_rootnode, p_timings_array[i] );
1379
10.6k
        if( p_regions )
1380
9.33k
        {
1381
9.33k
            if( p_regions->bgbitmap.i_bytes > 0 && p_regions->updt.p_segments == NULL )
1382
0
            {
1383
0
                b_bitmap_regions = true;
1384
0
                p_spu = decoder_NewTTML_ImageSpu( p_dec );
1385
0
            }
1386
9.33k
            else
1387
9.33k
            {
1388
9.33k
                p_spu = decoder_NewSubpictureText( p_dec );
1389
9.33k
            }
1390
9.33k
        }
1391
1392
10.6k
        if( p_regions && p_spu )
1393
9.33k
        {
1394
9.33k
            p_spu->i_start    = p_sys->pes.i_offset +
1395
9.33k
                                VLC_TICK_0 + tt_time_Convert( &p_timings_array[i] );
1396
9.33k
            p_spu->i_stop     = p_sys->pes.i_offset +
1397
9.33k
                                VLC_TICK_0 + tt_time_Convert( &p_timings_array[i+1] ) - 1;
1398
9.33k
            p_spu->b_ephemer  = true;
1399
1400
9.33k
            if( !b_bitmap_regions ) /* TEXT regions */
1401
9.33k
                TTMLRegionsToSpuTextRegions( p_dec, p_spu, p_regions );
1402
0
            else /* BITMAP regions */
1403
0
                TTMLRegionsToSpuBitmapRegions( p_dec, p_spu, p_regions );
1404
9.33k
        }
1405
1406
        /* cleanup */
1407
30.8k
        while( p_regions )
1408
20.2k
        {
1409
20.2k
            ttml_region_t *p_nextregion = (ttml_region_t *) p_regions->updt.p_next;
1410
20.2k
            ttml_region_Delete( p_regions );
1411
20.2k
            p_regions = p_nextregion;
1412
20.2k
        }
1413
1414
10.6k
        if( p_spu )
1415
9.33k
            decoder_QueueSub( p_dec, p_spu );
1416
10.6k
    }
1417
1418
15.0k
    tt_node_RecursiveDelete( p_rootnode );
1419
15.0k
    tt_namespaces_Clean( &namespaces );
1420
1421
15.0k
    free( p_timings_array );
1422
1423
15.0k
    return VLCDEC_SUCCESS;
1424
24.7k
}
1425
1426
1427
/****************************************************************************
1428
 * DecodeBlock: the whole thing
1429
 ****************************************************************************/
1430
static int DecodeBlock( decoder_t *p_dec, block_t *p_block )
1431
52.7k
{
1432
52.7k
    if( p_block == NULL ) /* No Drain */
1433
28.0k
        return VLCDEC_SUCCESS;
1434
1435
24.7k
    int ret = ParseBlock( p_dec, p_block );
1436
#ifdef TTML_DEBUG
1437
    if( p_block->i_buffer )
1438
    {
1439
        p_block->p_buffer[p_block->i_buffer - 1] = 0;
1440
        msg_Dbg(p_dec,"time %ld %s", p_block->i_dts, p_block->p_buffer);
1441
    }
1442
#endif
1443
24.7k
    block_Release( p_block );
1444
24.7k
    return ret;
1445
52.7k
}
1446
1447
static int DecodePESBlock( decoder_t *p_dec, block_t *p_block )
1448
0
{
1449
0
    decoder_sys_t *p_sys = p_dec->p_sys;
1450
0
    return ParsePESEncap( p_dec, &p_sys->pes, DecodeBlock, p_block );
1451
0
}
1452
1453
/*****************************************************************************
1454
 * Flush state between seeks
1455
 *****************************************************************************/
1456
static void Flush( decoder_t *p_dec )
1457
0
{
1458
0
    decoder_sys_t *p_sys = p_dec->p_sys;
1459
0
    ttml_in_pes_Init( &p_sys->pes );
1460
0
}
1461
1462
/*****************************************************************************
1463
 * tt_OpenDecoder: probe the decoder and return score
1464
 *****************************************************************************/
1465
int tt_OpenDecoder( vlc_object_t *p_this )
1466
11.7k
{
1467
11.7k
    decoder_t *p_dec = (decoder_t*)p_this;
1468
11.7k
    decoder_sys_t *p_sys;
1469
1470
11.7k
    if( p_dec->fmt_in->i_codec != VLC_CODEC_TTML &&
1471
8.39k
        !TTML_in_PES(p_dec) )
1472
8.39k
        return VLC_EGENERIC;
1473
1474
    /* Allocate the memory needed to store the decoder's structure */
1475
3.35k
    p_dec->p_sys = p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );
1476
3.35k
    if( unlikely( p_sys == NULL ) )
1477
0
        return VLC_ENOMEM;
1478
1479
3.35k
    if( !TTML_in_PES( p_dec ) )
1480
3.35k
        p_dec->pf_decode = DecodeBlock;
1481
0
    else
1482
0
        p_dec->pf_decode = DecodePESBlock;
1483
3.35k
    p_dec->pf_flush = Flush;
1484
3.35k
    p_sys->i_align = var_InheritInteger( p_dec, "ttml-align" );
1485
3.35k
    ttml_in_pes_Init( &p_sys->pes );
1486
1487
3.35k
    return VLC_SUCCESS;
1488
3.35k
}