substtml.c 33.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
/*****************************************************************************
 * substtml.c : TTML subtitles decoder
 *****************************************************************************
 * Copyright (C) 2015-2017 VLC authors and VideoLAN
 *
 * Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
 *          Sushma Reddy <sushma.reddy@research.iiit.ac.in>
 *
 * 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
 * (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
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * 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.
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>
#include <vlc_codec.h>
#include <vlc_xml.h>
#include <vlc_stream.h>
#include <vlc_text_style.h>
#include <vlc_charset.h>

#include <ctype.h>
#include <assert.h>

#include "substext.h"
#include "ttml.h"

40 41
//#define TTML_DEBUG

42 43 44 45 46 47 48
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/

typedef struct
{
    text_style_t*   font_style;
49
    unsigned        i_cell_height;
50
    int             i_text_align;
51 52
    int             i_direction;
    bool            b_direction_set;
53
    bool            b_preserve_space;
54 55 56 57 58 59
    enum
    {
        TTML_DISPLAY_UNKNOWN = 0,
        TTML_DISPLAY_AUTO,
        TTML_DISPLAY_NONE,
    } display;
60 61 62 63
}  ttml_style_t;

typedef struct
{
64 65
    vlc_dictionary_t regions;
    tt_node_t *      p_rootnode; /* for now. FIXME: split header */
66 67
} ttml_context_t;

68 69 70 71 72 73
typedef struct
{
    subpicture_updater_sys_region_t updt;
    text_segment_t **pp_last_segment;
} ttml_region_t;

74 75 76 77 78 79 80 81 82 83 84 85 86
struct decoder_sys_t
{
    int                     i_align;
};

enum
{
    UNICODE_BIDI_LTR = 0,
    UNICODE_BIDI_RTL = 1,
    UNICODE_BIDI_EMBEDDED = 2,
    UNICODE_BIDI_OVERRIDE = 4,
};

87 88 89 90 91 92 93
/*
 * TTML Parsing and inheritance order:
 * Each time a text node is found and belongs to out time interval,
 * we backward merge attributes dictionnary up to root.
 * Then we convert attributes, merging with style by id or region
 * style, and sets from parent node.
 */
94
static tt_node_t *ParseTTML( decoder_t *, const uint8_t *, size_t );
95 96 97 98 99 100 101 102 103 104 105 106 107

static void ttml_style_Delete( ttml_style_t* p_ttml_style )
{
    text_style_Delete( p_ttml_style->font_style );
    free( p_ttml_style );
}

static ttml_style_t * ttml_style_New( )
{
    ttml_style_t *p_ttml_style = calloc( 1, sizeof( ttml_style_t ) );
    if( unlikely( !p_ttml_style ) )
        return NULL;

108
    p_ttml_style->i_cell_height = 15;
109 110 111 112 113 114 115 116 117
    p_ttml_style->font_style = text_style_Create( STYLE_NO_DEFAULTS );
    if( unlikely( !p_ttml_style->font_style ) )
    {
        free( p_ttml_style );
        return NULL;
    }
    return p_ttml_style;
}

118 119 120 121 122 123
static void ttml_region_Delete( ttml_region_t *p_region )
{
    SubpictureUpdaterSysRegionClean( &p_region->updt );
    free( p_region );
}

124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
static ttml_style_t * ttml_style_Duplicate( const ttml_style_t *p_src )
{
    ttml_style_t *p_dup = ttml_style_New( );
    if( p_dup )
    {
        *p_dup = *p_src;
        p_dup->font_style = text_style_Duplicate( p_src->font_style );
    }
    return p_dup;
}

static void ttml_style_Merge( const ttml_style_t *p_src, ttml_style_t *p_dst )
{
    if( p_src && p_dst )
    {
        if( p_src->font_style )
        {
            if( p_dst->font_style )
                text_style_Merge( p_dst->font_style, p_src->font_style, true );
            else
                p_dst->font_style = text_style_Duplicate( p_src->font_style );
        }

        if( p_src->b_direction_set )
        {
            p_dst->b_direction_set = true;
            p_dst->i_direction = p_src->i_direction;
        }
152 153 154

        if( p_src->display != TTML_DISPLAY_UNKNOWN )
            p_dst->display = p_src->display;
155 156 157
    }
}

158 159 160 161 162 163 164 165 166 167 168 169 170 171
static ttml_region_t *ttml_region_New( )
{
    ttml_region_t *p_ttml_region = calloc( 1, sizeof( ttml_region_t ) );
    if( unlikely( !p_ttml_region ) )
        return NULL;

    SubpictureUpdaterSysRegionInit( &p_ttml_region->updt );
    p_ttml_region->pp_last_segment = &p_ttml_region->updt.p_segments;
    /* Align to bottom by default. !Warn: center align is obtained with NO flags */
    p_ttml_region->updt.align = SUBPICTURE_ALIGN_BOTTOM;

    return p_ttml_region;
}

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
static tt_node_t * FindNode( tt_node_t *p_node, const char *psz_nodename,
                             size_t i_maxdepth, const char *psz_id )
{
    if( !tt_node_NameCompare( p_node->psz_node_name, psz_nodename ) )
    {
        if( psz_id != NULL )
        {
            char *psz = vlc_dictionary_value_for_key( &p_node->attr_dict, "xml:id" );
            if( psz && !strcmp( psz, psz_id ) )
                return p_node;
        }
        else return p_node;
    }

    if( i_maxdepth == 0 )
        return NULL;

    for( tt_basenode_t *p_child = p_node->p_child;
                        p_child; p_child = p_child->p_next )
    {
        if( p_child->i_type == TT_NODE_TYPE_TEXT )
            continue;

        p_node = FindNode( (tt_node_t *) p_child, psz_nodename, i_maxdepth - 1, psz_id );
        if( p_node )
            return p_node;
    }

    return NULL;
}

static void FillTextStyle( const char *psz_attr, const char *psz_val,
204
                           const ttml_style_t *p_ttml_style, text_style_t *p_text_style )
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
{
    if( !strcasecmp ( "tts:fontFamily", psz_attr ) )
    {
        free( p_text_style->psz_fontname );
        p_text_style->psz_fontname = strdup( psz_val );
    }
    else if( !strcasecmp( "tts:opacity", psz_attr ) )
    {
        p_text_style->i_background_alpha = atoi( psz_val );
        p_text_style->i_font_alpha = atoi( psz_val );
        p_text_style->i_features |= STYLE_HAS_BACKGROUND_ALPHA | STYLE_HAS_FONT_ALPHA;
    }
    else if( !strcasecmp( "tts:fontSize", psz_attr ) )
    {
        char* psz_end = NULL;
        float size = us_strtof( psz_val, &psz_end );
221 222 223 224 225 226 227 228 229
        if( size > 0.0 )
        {
            if( *psz_end == 'c' )
                p_text_style->f_font_relsize = 100.0 * size / p_ttml_style->i_cell_height;
            else if( *psz_end == '%' )
                p_text_style->f_font_relsize = size / p_ttml_style->i_cell_height;
            else
                p_text_style->i_font_size = (int)( size + 0.5 );
        }
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    }
    else if( !strcasecmp( "tts:color", psz_attr ) )
    {
        unsigned int i_color = vlc_html_color( psz_val, NULL );
        p_text_style->i_font_color = (i_color & 0xffffff);
        p_text_style->i_font_alpha = (i_color & 0xFF000000) >> 24;
        p_text_style->i_features |= STYLE_HAS_FONT_COLOR | STYLE_HAS_FONT_ALPHA;
    }
    else if( !strcasecmp( "tts:backgroundColor", psz_attr ) )
    {
        unsigned int i_color = vlc_html_color( psz_val, NULL );
        p_text_style->i_background_color = i_color & 0xFFFFFF;
        p_text_style->i_background_alpha = (i_color & 0xFF000000) >> 24;
        p_text_style->i_features |= STYLE_HAS_BACKGROUND_COLOR
                                                  | STYLE_HAS_BACKGROUND_ALPHA;
        p_text_style->i_style_flags |= STYLE_BACKGROUND;
    }
    else if( !strcasecmp( "tts:fontStyle", psz_attr ) )
    {
        if( !strcasecmp ( "italic", psz_val ) || !strcasecmp ( "oblique", psz_val ) )
            p_text_style->i_style_flags |= STYLE_ITALIC;
        else
            p_text_style->i_style_flags &= ~STYLE_ITALIC;
        p_text_style->i_features |= STYLE_HAS_FLAGS;
    }
    else if( !strcasecmp ( "tts:fontWeight", psz_attr ) )
    {
        if( !strcasecmp ( "bold", psz_val ) )
            p_text_style->i_style_flags |= STYLE_BOLD;
        else
            p_text_style->i_style_flags &= ~STYLE_BOLD;
        p_text_style->i_features |= STYLE_HAS_FLAGS;
    }
    else if( !strcasecmp ( "tts:textDecoration", psz_attr ) )
    {
        if( !strcasecmp ( "underline", psz_val ) )
            p_text_style->i_style_flags |= STYLE_UNDERLINE;
        else if( !strcasecmp ( "noUnderline", psz_val ) )
            p_text_style->i_style_flags &= ~STYLE_UNDERLINE;
        if( !strcasecmp ( "lineThrough", psz_val ) )
            p_text_style->i_style_flags |= STYLE_STRIKEOUT;
        else if( !strcasecmp ( "noLineThrough", psz_val ) )
            p_text_style->i_style_flags &= ~STYLE_STRIKEOUT;
        p_text_style->i_features |= STYLE_HAS_FLAGS;
    }
    else if( !strcasecmp( "tts:textOutline", psz_attr ) )
    {
        char *value = strdup( psz_val );
        char* psz_saveptr = NULL;
279
        char* token = (value) ? strtok_r( value, " ", &psz_saveptr ) : NULL;
280
        // <color>? <length> <length>?
281
        if( token != NULL )
282
        {
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
            bool b_ok = false;
            unsigned int color = vlc_html_color( token, &b_ok );
            if( b_ok )
            {
                p_text_style->i_outline_color = color & 0xFFFFFF;
                p_text_style->i_outline_alpha = (color & 0xFF000000) >> 24;
                token = strtok_r( NULL, " ", &psz_saveptr );
                if( token != NULL )
                {
                    char* psz_end = NULL;
                    int i_outline_width = strtol( token, &psz_end, 10 );
                    if( psz_end != token )
                    {
                        // Assume unit is pixel, and ignore border radius
                        p_text_style->i_outline_width = i_outline_width;
                    }
                }
            }
301 302 303 304 305
        }
        free( value );
    }
}

306 307
static void FillRegionStyle( const char *psz_attr, const char *psz_val,
                             ttml_region_t *p_region )
308
{
309
    if( !strcasecmp( "tts:displayAlign", psz_attr ) )
310
    {
311 312
        if( !strcasecmp ( "top", psz_val ) )
            p_region->updt.align = SUBPICTURE_ALIGN_TOP;
313
        else if( !strcasecmp ( "center", psz_val ) )
314 315 316
            p_region->updt.align = 0;
        else
            p_region->updt.align = SUBPICTURE_ALIGN_BOTTOM;
317 318 319 320 321 322 323 324 325 326 327 328
    }
    else if( !strcasecmp ( "tts:origin", psz_attr ) )
    {
        const char *psz_token = psz_val;
        while( isspace( *psz_token ) )
            psz_token++;

        const char *psz_separator = strchr( psz_token, ' ' );
        if( psz_separator == NULL )
            return;
        const char *psz_percent_sign = strchr( psz_token, '%' );

329
        p_region->updt.origin.x = atoi( psz_token );
330
        if( psz_percent_sign != NULL && psz_percent_sign < psz_separator )
331 332 333 334
        {
            p_region->updt.origin.x /= 100.0;
            p_region->updt.flags |= UPDT_REGION_ORIGIN_X_IS_PERCENTILE;
        }
335

336 337 338 339
        while( isspace( *psz_separator ) )
            psz_separator++;
        psz_token = psz_separator;
        psz_percent_sign = strchr( psz_token, '%' );
340 341

        p_region->updt.origin.y = atoi( psz_token );
342
        if( psz_percent_sign != NULL )
343 344 345 346 347
        {
            p_region->updt.origin.y /= 100.0;
            p_region->updt.flags |= UPDT_REGION_ORIGIN_Y_IS_PERCENTILE;
        }
        p_region->updt.align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
348 349 350
    }
}

351 352 353 354 355 356 357 358 359 360 361 362 363
static void FillTTMLStylePrio( const vlc_dictionary_t *p_dict,
                               ttml_style_t *p_ttml_style )
{
    void *value = vlc_dictionary_value_for_key( p_dict, "ttp:cellResolution" );
    if( value != kVLCDictionaryNotFound )
    {
        const char *psz_val = value;
        unsigned w, h;
        if( sscanf( psz_val, "%u %u", &w, &h) == 2 && w && h )
            p_ttml_style->i_cell_height = h;
    }
}

364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
static void FillTTMLStyle( const char *psz_attr, const char *psz_val,
                           ttml_style_t *p_ttml_style )
{
    if( !strcasecmp( "tts:textAlign", psz_attr ) )
    {
        if( !strcasecmp ( "left", psz_val ) )
            p_ttml_style->i_text_align = SUBPICTURE_ALIGN_LEFT;
        else if( !strcasecmp ( "right", psz_val ) )
            p_ttml_style->i_text_align = SUBPICTURE_ALIGN_RIGHT;
        else if( !strcasecmp ( "center", psz_val ) )
            p_ttml_style->i_text_align = 0;
        else if( !strcasecmp ( "start", psz_val ) ) /* FIXME: should be BIDI based */
            p_ttml_style->i_text_align = SUBPICTURE_ALIGN_LEFT;
        else if( !strcasecmp ( "end", psz_val ) )  /* FIXME: should be BIDI based */
            p_ttml_style->i_text_align = SUBPICTURE_ALIGN_RIGHT;
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
    }
    else if( !strcasecmp( "tts:direction", psz_attr ) )
    {
        if( !strcasecmp( "rtl", psz_val ) )
        {
            p_ttml_style->i_direction |= UNICODE_BIDI_RTL;
            p_ttml_style->b_direction_set = true;
        }
        else if( !strcasecmp( "ltr", psz_val ) )
        {
            p_ttml_style->i_direction |= UNICODE_BIDI_LTR;
            p_ttml_style->b_direction_set = true;
        }
    }
    else if( !strcasecmp( "tts:unicodeBidi", psz_attr ) )
    {
            if( !strcasecmp( "bidiOverride", psz_val ) )
                p_ttml_style->i_direction |= UNICODE_BIDI_OVERRIDE & ~UNICODE_BIDI_EMBEDDED;
            else if( !strcasecmp( "embed", psz_val ) )
                p_ttml_style->i_direction |= UNICODE_BIDI_EMBEDDED & ~UNICODE_BIDI_OVERRIDE;
    }
    else if( !strcasecmp( "tts:writingMode", psz_attr ) )
    {
        if( !strcasecmp( "rl", psz_val ) || !strcasecmp( "rltb", psz_val ) )
        {
            p_ttml_style->i_direction = UNICODE_BIDI_RTL | UNICODE_BIDI_OVERRIDE;
405
            //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
406 407 408 409 410
            p_ttml_style->b_direction_set = true;
        }
        else if( !strcasecmp( "lr", psz_val ) || !strcasecmp( "lrtb", psz_val ) )
        {
            p_ttml_style->i_direction = UNICODE_BIDI_LTR | UNICODE_BIDI_OVERRIDE;
411
            //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
412 413 414
            p_ttml_style->b_direction_set = true;
        }
    }
415 416 417 418 419 420 421
    else if( !strcmp( "tts:display", psz_attr ) )
    {
        if( !strcmp( "none", psz_val ) )
            p_ttml_style->display = TTML_DISPLAY_NONE;
        else
            p_ttml_style->display = TTML_DISPLAY_AUTO;
    }
422 423 424 425
    else if( !strcasecmp( "xml:space", psz_attr ) )
    {
        p_ttml_style->b_preserve_space = !strcmp( "preserve", psz_val );
    }
426
    else FillTextStyle( psz_attr, psz_val, p_ttml_style, p_ttml_style->font_style );
427 428
}

429
static void DictionaryMerge( const vlc_dictionary_t *p_src, vlc_dictionary_t *p_dst )
430 431 432 433 434 435
{
    for( int i = 0; i < p_src->i_size; ++i )
    {
        for ( const vlc_dictionary_entry_t* p_entry = p_src->p_entries[i];
                                            p_entry != NULL; p_entry = p_entry->p_next )
        {
436 437 438
            if( ( !strncmp( "tts:", p_entry->psz_key, 4 ) ||
                  !strncmp( "ttp:", p_entry->psz_key, 4 ) ||
                  !strcmp( "xml:space", p_entry->psz_key ) ) &&
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
                !vlc_dictionary_has_key( p_dst, p_entry->psz_key ) )
                vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value );
        }
    }
}

static void DictMergeWithStyleID( ttml_context_t *p_ctx, const char *psz_id,
                                  vlc_dictionary_t *p_dst )
{
    assert(p_ctx->p_rootnode);
    if( psz_id && p_ctx->p_rootnode )
    {
        /* Lookup referenced style ID */
        const tt_node_t *p_node = FindNode( p_ctx->p_rootnode,
                                            "style", -1, psz_id );
        if( p_node )
455
            DictionaryMerge( &p_node->attr_dict, p_dst );
456 457 458 459 460 461 462 463 464 465 466 467 468 469
    }
}

static void DictMergeWithRegionID( ttml_context_t *p_ctx, const char *psz_id,
                                   vlc_dictionary_t *p_dst )
{
    assert(p_ctx->p_rootnode);
    if( psz_id && p_ctx->p_rootnode )
    {
        const tt_node_t *p_regionnode = FindNode( p_ctx->p_rootnode,
                                                 "region", -1, psz_id );
        if( !p_regionnode )
            return;

470 471 472 473 474 475 476
        DictionaryMerge( &p_regionnode->attr_dict, p_dst );

        const char *psz_styleid = (const char *)
                vlc_dictionary_value_for_key( &p_regionnode->attr_dict, "style" );
        if( psz_styleid )
            DictMergeWithStyleID( p_ctx, psz_styleid, p_dst );

477 478 479 480 481 482 483 484 485
        for( const tt_basenode_t *p_child = p_regionnode->p_child;
                                  p_child; p_child = p_child->p_next )
        {
            if( unlikely( p_child->i_type == TT_NODE_TYPE_TEXT ) )
                continue;

            const tt_node_t *p_node = (const tt_node_t *) p_child;
            if( !tt_node_NameCompare( p_node->psz_node_name, "style" ) )
            {
486
                DictionaryMerge( &p_node->attr_dict, p_dst );
487 488 489 490 491
            }
        }
    }
}

492 493
static void DictToTTMLStyle( const vlc_dictionary_t *p_dict, ttml_style_t *p_ttml_style )
{
494 495
    /* Units, defaults, that must be set first to compute styles */
    FillTTMLStylePrio( p_dict, p_ttml_style );
496 497 498 499 500 501 502 503 504 505
    for( int i = 0; i < p_dict->i_size; ++i )
    {
        for ( vlc_dictionary_entry_t* p_entry = p_dict->p_entries[i];
              p_entry != NULL; p_entry = p_entry->p_next )
        {
            FillTTMLStyle( p_entry->psz_key, p_entry->p_value, p_ttml_style );
        }
    }
}

506 507
static ttml_style_t * InheritTTMLStyles( ttml_context_t *p_ctx, tt_node_t *p_node )
{
508
    assert( p_node );
509 510 511 512 513 514 515
    ttml_style_t *p_ttml_style = NULL;
    vlc_dictionary_t merged;
    vlc_dictionary_init( &merged, 0 );

    /* Merge dics backwards without overwriting */
    for( ; p_node; p_node = p_node->p_parent )
    {
516
        DictionaryMerge( &p_node->attr_dict, &merged );
517 518 519 520 521 522

        const char *psz_styleid = (const char *)
                vlc_dictionary_value_for_key( &p_node->attr_dict, "style" );
        if( psz_styleid )
            DictMergeWithStyleID( p_ctx, psz_styleid, &merged );

523 524 525 526
        const char *psz_regionid = (const char *)
                vlc_dictionary_value_for_key( &p_node->attr_dict, "region" );
        if( psz_regionid )
            DictMergeWithRegionID( p_ctx, psz_regionid, &merged );
527 528
    }

529
    if( !vlc_dictionary_is_empty( &merged ) && (p_ttml_style = ttml_style_New()) )
530
    {
531
        DictToTTMLStyle( &merged, p_ttml_style );
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
    }

    vlc_dictionary_clear( &merged, NULL, NULL );

    return p_ttml_style;
}

static int ParseTTMLChunk( xml_reader_t *p_reader, tt_node_t **pp_rootnode )
{
    const char* psz_node_name;

    do
    {
        int i_type = xml_ReaderNextNode( p_reader, &psz_node_name );

        if( i_type <= XML_READER_NONE )
            break;

        switch(i_type)
        {
            default:
                break;

            case XML_READER_STARTELEM:
                if( tt_node_NameCompare( psz_node_name, "tt" ) ||
                    *pp_rootnode != NULL )
                    return VLC_EGENERIC;

                *pp_rootnode = tt_node_New( p_reader, NULL, psz_node_name );
                if( !*pp_rootnode ||
                    tt_nodes_Read( p_reader, *pp_rootnode ) != VLC_SUCCESS )
                    return VLC_EGENERIC;
                break;

            case XML_READER_ENDELEM:
                if( !*pp_rootnode ||
                    tt_node_NameCompare( psz_node_name, (*pp_rootnode)->psz_node_name ) )
                    return VLC_EGENERIC;
                break;
        }

    } while( 1 );

    if( *pp_rootnode == NULL )
        return VLC_EGENERIC;

    return VLC_SUCCESS;
}

static void BIDIConvert( text_segment_t *p_segment, int i_direction )
{
    /*
    * For bidirectionnal support, we use different enum
    * to recognize different cases, en then we add the
    * corresponding unicode character to the text of
    * the text_segment.
    */
    static const struct
    {
        const char* psz_uni_start;
        const char* psz_uni_end;
    } p_bidi[] = {
        { "\u2066", "\u2069" },
        { "\u2067", "\u2069" },
        { "\u202A", "\u202C" },
        { "\u202B", "\u202C" },
        { "\u202D", "\u202C" },
        { "\u202E", "\u202C" },
    };

    if( unlikely((size_t)i_direction >= ARRAY_SIZE(p_bidi)) )
        return;

    char *psz_text = NULL;
    if( asprintf( &psz_text, "%s%s%s", p_bidi[i_direction].psz_uni_start,
                  p_segment->psz_text, p_bidi[i_direction].psz_uni_end ) < 0 )
    {
        free( p_segment->psz_text );
        p_segment->psz_text = psz_text;
    }
}

614 615 616 617 618 619 620 621
static void StripSpacing( text_segment_t *p_segment )
{
    /* Newlines must be replaced */
    char *p = p_segment->psz_text;
    while( (p = strchr( p, '\n' )) )
        *p = ' ';
}

622 623 624 625 626 627 628 629
static ttml_region_t *GetTTMLRegion( ttml_context_t *p_ctx, const char *psz_region_id )
{
    ttml_region_t *p_region = ( ttml_region_t * )
            vlc_dictionary_value_for_key( &p_ctx->regions, psz_region_id ? psz_region_id : "" );
    if( p_region == NULL )
    {
        if( psz_region_id && strcmp( psz_region_id, "" ) ) /* not default region */
        {
630 631 632 633 634 635 636
            /* Create region if if missing */

            vlc_dictionary_t merged;
            vlc_dictionary_init( &merged, 0 );
            /* Get all attributes, including region > style */
            DictMergeWithRegionID( p_ctx, psz_region_id, &merged );
            if( (p_region = ttml_region_New()) )
637 638
            {
                /* Fill from its own attributes */
639
                for( int i = 0; i < merged.i_size; ++i )
640
                {
641
                    for ( vlc_dictionary_entry_t* p_entry = merged.p_entries[i];
642 643 644 645 646 647
                          p_entry != NULL; p_entry = p_entry->p_next )
                    {
                        FillRegionStyle( p_entry->psz_key, p_entry->p_value, p_region );
                    }
                }
            }
648 649
            vlc_dictionary_clear( &merged, NULL, NULL );

650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
            vlc_dictionary_insert( &p_ctx->regions, psz_region_id, p_region );
        }
        else if( (p_region = ttml_region_New()) ) /* create default */
        {
            vlc_dictionary_insert( &p_ctx->regions, "", p_region );
        }
    }
    return p_region;
}

static void AppendLineBreakToRegion( ttml_region_t *p_region )
{
    text_segment_t *p_segment = text_segment_New( "\n" );
    if( p_segment )
    {
        *p_region->pp_last_segment = p_segment;
        p_region->pp_last_segment = &p_segment->p_next;
    }
}

static void AppendTextToRegion( ttml_context_t *p_ctx, const tt_textnode_t *p_ttnode,
671
                                const ttml_style_t *p_set_styles, ttml_region_t *p_region )
672
{
673 674 675 676 677 678 679 680
    text_segment_t *p_segment;

    if( p_region == NULL )
        return;

    p_segment = text_segment_New( p_ttnode->psz_text );
    if( p_segment )
    {
681
        bool b_preserve_space = false;
682 683 684
        ttml_style_t *s = InheritTTMLStyles( p_ctx, p_ttnode->p_parent );
        if( s )
        {
685 686 687
            if( p_set_styles )
                ttml_style_Merge( p_set_styles, s );

688 689 690
            p_segment->style = s->font_style;
            s->font_style = NULL;

691
            b_preserve_space = s->b_preserve_space;
692 693 694
            if( s->b_direction_set )
                BIDIConvert( p_segment, s->i_direction );

695 696 697 698 699 700 701 702
            if( s->display == TTML_DISPLAY_NONE )
            {
                /* Must not display, but still occupies space */
                p_segment->style->i_features &= ~(STYLE_BACKGROUND|STYLE_OUTLINE|STYLE_STRIKEOUT|STYLE_SHADOW);
                p_segment->style->i_font_alpha = STYLE_ALPHA_TRANSPARENT;
                p_segment->style->i_features |= STYLE_HAS_FONT_ALPHA;
            }

703 704
            ttml_style_Delete( s );
        }
705 706 707

        if( !b_preserve_space )
            StripSpacing( p_segment );
708 709 710 711 712 713 714
    }

    *p_region->pp_last_segment = p_segment;
    p_region->pp_last_segment = &p_segment->p_next;
}

static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, const tt_node_t *p_node,
715 716
                                         ttml_region_t *p_region,
                                         const ttml_style_t *p_upper_set_styles,
717
                                         tt_time_t playbacktime )
718
{
719 720
    if( tt_time_Valid( &playbacktime ) &&
       !tt_timings_Contains( &p_node->timings, &playbacktime ) )
721 722
        return;

723 724 725 726 727 728
    const char *psz_regionid = (const char *)
        vlc_dictionary_value_for_key( &p_node->attr_dict, "region" );

    /* Region isn't set or is changing */
    if( psz_regionid || p_region == NULL )
        p_region = GetTTMLRegion( p_ctx, psz_regionid );
729 730

    /* awkward paragraph handling */
731 732
    if( !tt_node_NameCompare( p_node->psz_node_name, "p" ) &&
        p_region->updt.p_segments )
733
    {
734
        AppendLineBreakToRegion( p_region );
735 736
    }

737 738 739 740 741
    /* Styles from <set> element */
    ttml_style_t *p_set_styles = (p_upper_set_styles)
                               ? ttml_style_Duplicate( p_upper_set_styles )
                               : NULL;

742 743 744 745 746
    for( const tt_basenode_t *p_child = p_node->p_child;
                              p_child; p_child = p_child->p_next )
    {
        if( p_child->i_type == TT_NODE_TYPE_TEXT )
        {
747 748 749 750 751 752
            AppendTextToRegion( p_ctx, (const tt_textnode_t *) p_child,
                                p_set_styles, p_region );
        }
        else if( !tt_node_NameCompare( ((const tt_node_t *)p_child)->psz_node_name, "set" ) )
        {
            const tt_node_t *p_set = (const tt_node_t *)p_child;
753 754
            if( !tt_time_Valid( &playbacktime ) ||
                tt_timings_Contains( &p_set->timings, &playbacktime ) )
755 756 757 758 759 760 761
            {
                if( p_set_styles != NULL || (p_set_styles = ttml_style_New()) )
                {
                    /* Merge with or create a local set of styles to apply to following childs */
                    DictToTTMLStyle( &p_set->attr_dict, p_set_styles );
                }
            }
762 763 764 765
        }
        else if( !tt_node_NameCompare( ((const tt_node_t *)p_child)->psz_node_name, "br" ) )
        {
            AppendLineBreakToRegion( p_region );
766 767 768
        }
        else
        {
769
            ConvertNodesToRegionContent( p_ctx, (const tt_node_t *) p_child,
770
                                         p_region, p_set_styles, playbacktime );
771 772
        }
    }
773 774 775

    if( p_set_styles )
        ttml_style_Delete( p_set_styles );
776 777
}

778
static tt_node_t *ParseTTML( decoder_t *p_dec, const uint8_t *p_buffer, size_t i_buffer )
779
{
780 781
    stream_t*       p_sub;
    xml_reader_t*   p_xml_reader;
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798

    p_sub = vlc_stream_MemoryNew( p_dec, (uint8_t*) p_buffer, i_buffer, true );
    if( unlikely( p_sub == NULL ) )
        return NULL;

    p_xml_reader = xml_ReaderCreate( p_dec, p_sub );
    if( unlikely( p_xml_reader == NULL ) )
    {
        vlc_stream_Delete( p_sub );
        return NULL;
    }

    tt_node_t *p_rootnode = NULL;
    if( ParseTTMLChunk( p_xml_reader, &p_rootnode ) != VLC_SUCCESS )
    {
        if( p_rootnode )
            tt_node_RecursiveDelete( p_rootnode );
799
        p_rootnode = NULL;
800 801
    }

802 803 804 805 806 807
    xml_ReaderDelete( p_xml_reader );
    vlc_stream_Delete( p_sub );

    return p_rootnode;
}

808
static ttml_region_t *GenerateRegions( tt_node_t *p_rootnode, tt_time_t playbacktime )
809 810 811 812
{
    ttml_region_t*  p_regions = NULL;
    ttml_region_t** pp_region_last = &p_regions;

813 814 815 816 817 818 819
    if( !tt_node_NameCompare( p_rootnode->psz_node_name, "tt" ) )
    {
        const tt_node_t *p_bodynode = FindNode( p_rootnode, "body", 1, NULL );
        if( p_bodynode )
        {
            ttml_context_t context;
            context.p_rootnode = p_rootnode;
820
            vlc_dictionary_init( &context.regions, 1 );
821
            ConvertNodesToRegionContent( &context, p_bodynode, NULL, NULL, playbacktime );
822 823 824 825 826 827 828 829 830 831 832 833

            for( int i = 0; i < context.regions.i_size; ++i )
            {
                for ( const vlc_dictionary_entry_t* p_entry = context.regions.p_entries[i];
                                                    p_entry != NULL; p_entry = p_entry->p_next )
                {
                    *pp_region_last = (ttml_region_t *) p_entry->p_value;
                    pp_region_last = (ttml_region_t **) &(*pp_region_last)->updt.p_next;
                }
            }

            vlc_dictionary_clear( &context.regions, NULL, NULL );
834 835 836 837 838 839 840 841
        }
    }
    else if ( !tt_node_NameCompare( p_rootnode->psz_node_name, "div" ) ||
              !tt_node_NameCompare( p_rootnode->psz_node_name, "p" ) )
    {
        /* TODO */
    }

842
    return p_regions;
843 844
}

845
static int ParseBlock( decoder_t *p_dec, const block_t *p_block )
846
{
847
    tt_time_t *p_timings_array = NULL;
848 849
    size_t   i_timings_count = 0;

850
    /* We Only support absolute timings */
851 852
    tt_timings_t temporal_extent;
    temporal_extent.i_type = TT_TIMINGS_PARALLEL;
853 854 855 856
    tt_time_Init( &temporal_extent.begin );
    tt_time_Init( &temporal_extent.end );
    tt_time_Init( &temporal_extent.dur );
    temporal_extent.begin.base = 0;
857 858

    if( p_block->i_flags & BLOCK_FLAG_CORRUPTED )
859
        return VLCDEC_SUCCESS;
860 861 862 863 864

    /* We cannot display a subpicture with no date */
    if( p_block->i_pts <= VLC_TS_INVALID )
    {
        msg_Warn( p_dec, "subtitle without a date" );
865
        return VLCDEC_SUCCESS;
866 867
    }

868 869
    tt_node_t *p_rootnode = ParseTTML( p_dec, p_block->p_buffer, p_block->i_buffer );
    if( !p_rootnode )
870
        return VLCDEC_SUCCESS;
871 872 873

    tt_timings_Resolve( (tt_basenode_t *) p_rootnode, &temporal_extent,
                        &p_timings_array, &i_timings_count );
874

875
#ifdef TTML_DEBUG
876
    for( size_t i=0; i<i_timings_count; i++ )
877
        printf("%ld ", tt_time_Convert( &p_timings_array[i] ) );
878
    printf("\n");
879
#endif
880

881 882
    for( size_t i=0; i+1 < i_timings_count; i++ )
    {
883
        /* We Only support absolute timings (2) */
884
        if( tt_time_Convert( &p_timings_array[i] ) + VLC_TS_0 < p_block->i_dts )
885 886
            continue;

887
        if( tt_time_Convert( &p_timings_array[i] ) + VLC_TS_0 > p_block->i_dts + p_block->i_length )
888 889
            break;

890 891 892
        subpicture_t *p_spu = NULL;
        ttml_region_t *p_regions = GenerateRegions( p_rootnode, p_timings_array[i] );
        if( p_regions && ( p_spu = decoder_NewSubpictureText( p_dec ) ) )
893
        {
894 895
            p_spu->i_start    = VLC_TS_0 + tt_time_Convert( &p_timings_array[i] );
            p_spu->i_stop     = VLC_TS_0 + tt_time_Convert( &p_timings_array[i+1] ) - 1;
896
            p_spu->b_ephemer  = true;
897 898 899 900 901 902 903 904
            p_spu->b_absolute = false;

            subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
            subpicture_updater_sys_region_t *p_updtregion = NULL;

            /* Create region update info from each ttml region */
            for( ttml_region_t *p_region = p_regions;
                 p_region; p_region = (ttml_region_t *) p_region->updt.p_next )
905
            {
906
                if( p_updtregion == NULL )
907 908 909 910 911 912 913 914 915 916
                {
                    p_updtregion = &p_spu_sys->region;
                }
                else
                {
                    p_updtregion = SubpictureUpdaterSysRegionNew();
                    if( p_updtregion == NULL )
                        break;
                    SubpictureUpdaterSysRegionAdd( &p_spu_sys->region, p_updtregion );
                }
917

918 919 920 921 922 923 924 925 926 927 928 929
                /* broken legacy align var (can't handle center...) */
                if( p_dec->p_sys->i_align & SUBPICTURE_ALIGN_MASK )
                {
                    p_spu_sys->region.align = p_dec->p_sys->i_align & (SUBPICTURE_ALIGN_BOTTOM|SUBPICTURE_ALIGN_TOP);
                    p_spu_sys->region.inner_align = p_dec->p_sys->i_align & (SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT);
                }

                /* copy and take ownership of pointeds */
                *p_updtregion = p_region->updt;
                p_updtregion->p_next = NULL;
                p_region->updt.p_region_style = NULL;
                p_region->updt.p_segments = NULL;
930
            }
931

932 933
        }

934 935 936 937 938 939 940
        /* cleanup */
        while( p_regions )
        {
            ttml_region_t *p_nextregion = (ttml_region_t *) p_regions->updt.p_next;
            ttml_region_Delete( p_regions );
            p_regions = p_nextregion;
        }
941

942
        if( p_spu )
943
            decoder_QueueSub( p_dec, p_spu );
944
    }
945

946 947 948 949
    tt_node_RecursiveDelete( p_rootnode );

    free( p_timings_array );

950
    return VLCDEC_SUCCESS;
951 952 953 954 955 956 957
}



/****************************************************************************
 * DecodeBlock: the whole thing
 ****************************************************************************/
958
static int DecodeBlock( decoder_t *p_dec, block_t *p_block )
959
{
960 961
    if( p_block == NULL ) /* No Drain */
        return VLCDEC_SUCCESS;
962

963
    int ret = ParseBlock( p_dec, p_block );
964 965 966 967 968 969 970
#ifdef TTML_DEBUG
    if( p_block->i_buffer )
    {
        p_block->p_buffer[p_block->i_buffer - 1] = 0;
        msg_Dbg(p_dec,"time %ld %s", p_block->i_dts, p_block->p_buffer);
    }
#endif
971
    block_Release( p_block );
972
    return ret;
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990
}

/*****************************************************************************
 * OpenDecoder: probe the decoder and return score
 *****************************************************************************/
int OpenDecoder( vlc_object_t *p_this )
{
    decoder_t *p_dec = (decoder_t*)p_this;
    decoder_sys_t *p_sys;

    if( p_dec->fmt_in.i_codec != VLC_CODEC_TTML )
        return VLC_EGENERIC;

    /* Allocate the memory needed to store the decoder's structure */
    p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
    if( unlikely( p_sys == NULL ) )
        return VLC_ENOMEM;

991
    p_dec->pf_decode = DecodeBlock;
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006
    p_sys->i_align = var_InheritInteger( p_dec, "ttml-align" );

    return VLC_SUCCESS;
}

/*****************************************************************************
 * CloseDecoder: clean up the decoder
 *****************************************************************************/
void CloseDecoder( vlc_object_t *p_this )
{
    decoder_t *p_dec = (decoder_t *)p_this;
    decoder_sys_t *p_sys = p_dec->p_sys;

    free( p_sys );
}