vlc_arrays: add vlc_dictionary_is_empty
[vlc.git] / modules / codec / ttml / substtml.c
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
34 #include <ctype.h>
35 #include <assert.h>
36
37 #include "substext.h"
38 #include "ttml.h"
39
40 //#define TTML_DEBUG
41
42 /*****************************************************************************
43  * Local prototypes
44  *****************************************************************************/
45
46 typedef struct
47 {
48     text_style_t*   font_style;
49     unsigned        i_cell_height;
50     int             i_text_align;
51     int             i_direction;
52     bool            b_direction_set;
53     bool            b_preserve_space;
54     enum
55     {
56         TTML_DISPLAY_UNKNOWN = 0,
57         TTML_DISPLAY_AUTO,
58         TTML_DISPLAY_NONE,
59     } display;
60 }  ttml_style_t;
61
62 typedef struct
63 {
64     vlc_dictionary_t regions;
65     tt_node_t *      p_rootnode; /* for now. FIXME: split header */
66 } ttml_context_t;
67
68 typedef struct
69 {
70     subpicture_updater_sys_region_t updt;
71     text_segment_t **pp_last_segment;
72 } ttml_region_t;
73
74 struct decoder_sys_t
75 {
76     int                     i_align;
77 };
78
79 enum
80 {
81     UNICODE_BIDI_LTR = 0,
82     UNICODE_BIDI_RTL = 1,
83     UNICODE_BIDI_EMBEDDED = 2,
84     UNICODE_BIDI_OVERRIDE = 4,
85 };
86
87 /*
88  * TTML Parsing and inheritance order:
89  * Each time a text node is found and belongs to out time interval,
90  * we backward merge attributes dictionnary up to root.
91  * Then we convert attributes, merging with style by id or region
92  * style, and sets from parent node.
93  */
94 static tt_node_t *ParseTTML( decoder_t *, const uint8_t *, size_t );
95
96 static void ttml_style_Delete( ttml_style_t* p_ttml_style )
97 {
98     text_style_Delete( p_ttml_style->font_style );
99     free( p_ttml_style );
100 }
101
102 static ttml_style_t * ttml_style_New( )
103 {
104     ttml_style_t *p_ttml_style = calloc( 1, sizeof( ttml_style_t ) );
105     if( unlikely( !p_ttml_style ) )
106         return NULL;
107
108     p_ttml_style->i_cell_height = 15;
109     p_ttml_style->font_style = text_style_Create( STYLE_NO_DEFAULTS );
110     if( unlikely( !p_ttml_style->font_style ) )
111     {
112         free( p_ttml_style );
113         return NULL;
114     }
115     return p_ttml_style;
116 }
117
118 static void ttml_region_Delete( ttml_region_t *p_region )
119 {
120     SubpictureUpdaterSysRegionClean( &p_region->updt );
121     free( p_region );
122 }
123
124 static ttml_style_t * ttml_style_Duplicate( const ttml_style_t *p_src )
125 {
126     ttml_style_t *p_dup = ttml_style_New( );
127     if( p_dup )
128     {
129         *p_dup = *p_src;
130         p_dup->font_style = text_style_Duplicate( p_src->font_style );
131     }
132     return p_dup;
133 }
134
135 static void ttml_style_Merge( const ttml_style_t *p_src, ttml_style_t *p_dst )
136 {
137     if( p_src && p_dst )
138     {
139         if( p_src->font_style )
140         {
141             if( p_dst->font_style )
142                 text_style_Merge( p_dst->font_style, p_src->font_style, true );
143             else
144                 p_dst->font_style = text_style_Duplicate( p_src->font_style );
145         }
146
147         if( p_src->b_direction_set )
148         {
149             p_dst->b_direction_set = true;
150             p_dst->i_direction = p_src->i_direction;
151         }
152
153         if( p_src->display != TTML_DISPLAY_UNKNOWN )
154             p_dst->display = p_src->display;
155     }
156 }
157
158 static ttml_region_t *ttml_region_New( )
159 {
160     ttml_region_t *p_ttml_region = calloc( 1, sizeof( ttml_region_t ) );
161     if( unlikely( !p_ttml_region ) )
162         return NULL;
163
164     SubpictureUpdaterSysRegionInit( &p_ttml_region->updt );
165     p_ttml_region->pp_last_segment = &p_ttml_region->updt.p_segments;
166     /* Align to bottom by default. !Warn: center align is obtained with NO flags */
167     p_ttml_region->updt.align = SUBPICTURE_ALIGN_BOTTOM;
168
169     return p_ttml_region;
170 }
171
172 static tt_node_t * FindNode( tt_node_t *p_node, const char *psz_nodename,
173                              size_t i_maxdepth, const char *psz_id )
174 {
175     if( !tt_node_NameCompare( p_node->psz_node_name, psz_nodename ) )
176     {
177         if( psz_id != NULL )
178         {
179             char *psz = vlc_dictionary_value_for_key( &p_node->attr_dict, "xml:id" );
180             if( psz && !strcmp( psz, psz_id ) )
181                 return p_node;
182         }
183         else return p_node;
184     }
185
186     if( i_maxdepth == 0 )
187         return NULL;
188
189     for( tt_basenode_t *p_child = p_node->p_child;
190                         p_child; p_child = p_child->p_next )
191     {
192         if( p_child->i_type == TT_NODE_TYPE_TEXT )
193             continue;
194
195         p_node = FindNode( (tt_node_t *) p_child, psz_nodename, i_maxdepth - 1, psz_id );
196         if( p_node )
197             return p_node;
198     }
199
200     return NULL;
201 }
202
203 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     if( !strcasecmp ( "tts:fontFamily", psz_attr ) )
207     {
208         free( p_text_style->psz_fontname );
209         p_text_style->psz_fontname = strdup( psz_val );
210     }
211     else if( !strcasecmp( "tts:opacity", psz_attr ) )
212     {
213         p_text_style->i_background_alpha = atoi( psz_val );
214         p_text_style->i_font_alpha = atoi( psz_val );
215         p_text_style->i_features |= STYLE_HAS_BACKGROUND_ALPHA | STYLE_HAS_FONT_ALPHA;
216     }
217     else if( !strcasecmp( "tts:fontSize", psz_attr ) )
218     {
219         char* psz_end = NULL;
220         float size = us_strtof( psz_val, &psz_end );
221         if( size > 0.0 )
222         {
223             if( *psz_end == 'c' )
224                 p_text_style->f_font_relsize = 100.0 * size / p_ttml_style->i_cell_height;
225             else if( *psz_end == '%' )
226                 p_text_style->f_font_relsize = size / p_ttml_style->i_cell_height;
227             else
228                 p_text_style->i_font_size = (int)( size + 0.5 );
229         }
230     }
231     else if( !strcasecmp( "tts:color", psz_attr ) )
232     {
233         unsigned int i_color = vlc_html_color( psz_val, NULL );
234         p_text_style->i_font_color = (i_color & 0xffffff);
235         p_text_style->i_font_alpha = (i_color & 0xFF000000) >> 24;
236         p_text_style->i_features |= STYLE_HAS_FONT_COLOR | STYLE_HAS_FONT_ALPHA;
237     }
238     else if( !strcasecmp( "tts:backgroundColor", psz_attr ) )
239     {
240         unsigned int i_color = vlc_html_color( psz_val, NULL );
241         p_text_style->i_background_color = i_color & 0xFFFFFF;
242         p_text_style->i_background_alpha = (i_color & 0xFF000000) >> 24;
243         p_text_style->i_features |= STYLE_HAS_BACKGROUND_COLOR
244                                                   | STYLE_HAS_BACKGROUND_ALPHA;
245         p_text_style->i_style_flags |= STYLE_BACKGROUND;
246     }
247     else if( !strcasecmp( "tts:fontStyle", psz_attr ) )
248     {
249         if( !strcasecmp ( "italic", psz_val ) || !strcasecmp ( "oblique", psz_val ) )
250             p_text_style->i_style_flags |= STYLE_ITALIC;
251         else
252             p_text_style->i_style_flags &= ~STYLE_ITALIC;
253         p_text_style->i_features |= STYLE_HAS_FLAGS;
254     }
255     else if( !strcasecmp ( "tts:fontWeight", psz_attr ) )
256     {
257         if( !strcasecmp ( "bold", psz_val ) )
258             p_text_style->i_style_flags |= STYLE_BOLD;
259         else
260             p_text_style->i_style_flags &= ~STYLE_BOLD;
261         p_text_style->i_features |= STYLE_HAS_FLAGS;
262     }
263     else if( !strcasecmp ( "tts:textDecoration", psz_attr ) )
264     {
265         if( !strcasecmp ( "underline", psz_val ) )
266             p_text_style->i_style_flags |= STYLE_UNDERLINE;
267         else if( !strcasecmp ( "noUnderline", psz_val ) )
268             p_text_style->i_style_flags &= ~STYLE_UNDERLINE;
269         if( !strcasecmp ( "lineThrough", psz_val ) )
270             p_text_style->i_style_flags |= STYLE_STRIKEOUT;
271         else if( !strcasecmp ( "noLineThrough", psz_val ) )
272             p_text_style->i_style_flags &= ~STYLE_STRIKEOUT;
273         p_text_style->i_features |= STYLE_HAS_FLAGS;
274     }
275     else if( !strcasecmp( "tts:textOutline", psz_attr ) )
276     {
277         char *value = strdup( psz_val );
278         char* psz_saveptr = NULL;
279         char* token = (value) ? strtok_r( value, " ", &psz_saveptr ) : NULL;
280         // <color>? <length> <length>?
281         if( token != NULL )
282         {
283             bool b_ok = false;
284             unsigned int color = vlc_html_color( token, &b_ok );
285             if( b_ok )
286             {
287                 p_text_style->i_outline_color = color & 0xFFFFFF;
288                 p_text_style->i_outline_alpha = (color & 0xFF000000) >> 24;
289                 token = strtok_r( NULL, " ", &psz_saveptr );
290                 if( token != NULL )
291                 {
292                     char* psz_end = NULL;
293                     int i_outline_width = strtol( token, &psz_end, 10 );
294                     if( psz_end != token )
295                     {
296                         // Assume unit is pixel, and ignore border radius
297                         p_text_style->i_outline_width = i_outline_width;
298                     }
299                 }
300             }
301         }
302         free( value );
303     }
304 }
305
306 static void FillRegionStyle( const char *psz_attr, const char *psz_val,
307                              ttml_region_t *p_region )
308 {
309     if( !strcasecmp( "tts:displayAlign", psz_attr ) )
310     {
311         if( !strcasecmp ( "top", psz_val ) )
312             p_region->updt.align = SUBPICTURE_ALIGN_TOP;
313         else if( !strcasecmp ( "center", psz_val ) )
314             p_region->updt.align = 0;
315         else
316             p_region->updt.align = SUBPICTURE_ALIGN_BOTTOM;
317     }
318     else if( !strcasecmp ( "tts:origin", psz_attr ) )
319     {
320         const char *psz_token = psz_val;
321         while( isspace( *psz_token ) )
322             psz_token++;
323
324         const char *psz_separator = strchr( psz_token, ' ' );
325         if( psz_separator == NULL )
326             return;
327         const char *psz_percent_sign = strchr( psz_token, '%' );
328
329         p_region->updt.origin.x = atoi( psz_token );
330         if( psz_percent_sign != NULL && psz_percent_sign < psz_separator )
331             p_region->updt.flags |= UPDT_REGION_EXTENT_X_IS_PERCENTILE;
332
333         while( isspace( *psz_separator ) )
334             psz_separator++;
335         psz_token = psz_separator;
336         psz_percent_sign = strchr( psz_token, '%' );
337
338         p_region->updt.origin.y = atoi( psz_token );
339         if( psz_percent_sign != NULL )
340             p_region->updt.flags |= UPDT_REGION_EXTENT_Y_IS_PERCENTILE;
341     }
342 }
343
344 static void FillTTMLStylePrio( const vlc_dictionary_t *p_dict,
345                                ttml_style_t *p_ttml_style )
346 {
347     void *value = vlc_dictionary_value_for_key( p_dict, "ttp:cellResolution" );
348     if( value != kVLCDictionaryNotFound )
349     {
350         const char *psz_val = value;
351         unsigned w, h;
352         if( sscanf( psz_val, "%u %u", &w, &h) == 2 && w && h )
353             p_ttml_style->i_cell_height = h;
354     }
355 }
356
357 static void FillTTMLStyle( const char *psz_attr, const char *psz_val,
358                            ttml_style_t *p_ttml_style )
359 {
360     if( !strcasecmp( "tts:textAlign", psz_attr ) )
361     {
362         if( !strcasecmp ( "left", psz_val ) )
363             p_ttml_style->i_text_align = SUBPICTURE_ALIGN_LEFT;
364         else if( !strcasecmp ( "right", psz_val ) )
365             p_ttml_style->i_text_align = SUBPICTURE_ALIGN_RIGHT;
366         else if( !strcasecmp ( "center", psz_val ) )
367             p_ttml_style->i_text_align = 0;
368         else if( !strcasecmp ( "start", psz_val ) ) /* FIXME: should be BIDI based */
369             p_ttml_style->i_text_align = SUBPICTURE_ALIGN_LEFT;
370         else if( !strcasecmp ( "end", psz_val ) )  /* FIXME: should be BIDI based */
371             p_ttml_style->i_text_align = SUBPICTURE_ALIGN_RIGHT;
372     }
373     else if( !strcasecmp( "tts:direction", psz_attr ) )
374     {
375         if( !strcasecmp( "rtl", psz_val ) )
376         {
377             p_ttml_style->i_direction |= UNICODE_BIDI_RTL;
378             p_ttml_style->b_direction_set = true;
379         }
380         else if( !strcasecmp( "ltr", psz_val ) )
381         {
382             p_ttml_style->i_direction |= UNICODE_BIDI_LTR;
383             p_ttml_style->b_direction_set = true;
384         }
385     }
386     else if( !strcasecmp( "tts:unicodeBidi", psz_attr ) )
387     {
388             if( !strcasecmp( "bidiOverride", psz_val ) )
389                 p_ttml_style->i_direction |= UNICODE_BIDI_OVERRIDE & ~UNICODE_BIDI_EMBEDDED;
390             else if( !strcasecmp( "embed", psz_val ) )
391                 p_ttml_style->i_direction |= UNICODE_BIDI_EMBEDDED & ~UNICODE_BIDI_OVERRIDE;
392     }
393     else if( !strcasecmp( "tts:writingMode", psz_attr ) )
394     {
395         if( !strcasecmp( "rl", psz_val ) || !strcasecmp( "rltb", psz_val ) )
396         {
397             p_ttml_style->i_direction = UNICODE_BIDI_RTL | UNICODE_BIDI_OVERRIDE;
398             //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
399             p_ttml_style->b_direction_set = true;
400         }
401         else if( !strcasecmp( "lr", psz_val ) || !strcasecmp( "lrtb", psz_val ) )
402         {
403             p_ttml_style->i_direction = UNICODE_BIDI_LTR | UNICODE_BIDI_OVERRIDE;
404             //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
405             p_ttml_style->b_direction_set = true;
406         }
407     }
408     else if( !strcmp( "tts:display", psz_attr ) )
409     {
410         if( !strcmp( "none", psz_val ) )
411             p_ttml_style->display = TTML_DISPLAY_NONE;
412         else
413             p_ttml_style->display = TTML_DISPLAY_AUTO;
414     }
415     else if( !strcasecmp( "xml:space", psz_attr ) )
416     {
417         p_ttml_style->b_preserve_space = !strcmp( "preserve", psz_val );
418     }
419     else FillTextStyle( psz_attr, psz_val, p_ttml_style, p_ttml_style->font_style );
420 }
421
422 static void DictionaryMerge( const vlc_dictionary_t *p_src, vlc_dictionary_t *p_dst )
423 {
424     for( int i = 0; i < p_src->i_size; ++i )
425     {
426         for ( const vlc_dictionary_entry_t* p_entry = p_src->p_entries[i];
427                                             p_entry != NULL; p_entry = p_entry->p_next )
428         {
429             if( ( !strncmp( "tts:", p_entry->psz_key, 4 ) ||
430                   !strncmp( "ttp:", p_entry->psz_key, 4 ) ||
431                   !strcmp( "xml:space", p_entry->psz_key ) ) &&
432                 !vlc_dictionary_has_key( p_dst, p_entry->psz_key ) )
433                 vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value );
434         }
435     }
436 }
437
438 static void DictMergeWithStyleID( ttml_context_t *p_ctx, const char *psz_id,
439                                   vlc_dictionary_t *p_dst )
440 {
441     assert(p_ctx->p_rootnode);
442     if( psz_id && p_ctx->p_rootnode )
443     {
444         /* Lookup referenced style ID */
445         const tt_node_t *p_node = FindNode( p_ctx->p_rootnode,
446                                             "style", -1, psz_id );
447         if( p_node )
448             DictionaryMerge( &p_node->attr_dict, p_dst );
449     }
450 }
451
452 static void DictMergeWithRegionID( ttml_context_t *p_ctx, const char *psz_id,
453                                    vlc_dictionary_t *p_dst )
454 {
455     assert(p_ctx->p_rootnode);
456     if( psz_id && p_ctx->p_rootnode )
457     {
458         const tt_node_t *p_regionnode = FindNode( p_ctx->p_rootnode,
459                                                  "region", -1, psz_id );
460         if( !p_regionnode )
461             return;
462
463         DictionaryMerge( &p_regionnode->attr_dict, p_dst );
464
465         const char *psz_styleid = (const char *)
466                 vlc_dictionary_value_for_key( &p_regionnode->attr_dict, "style" );
467         if( psz_styleid )
468             DictMergeWithStyleID( p_ctx, psz_styleid, p_dst );
469
470         for( const tt_basenode_t *p_child = p_regionnode->p_child;
471                                   p_child; p_child = p_child->p_next )
472         {
473             if( unlikely( p_child->i_type == TT_NODE_TYPE_TEXT ) )
474                 continue;
475
476             const tt_node_t *p_node = (const tt_node_t *) p_child;
477             if( !tt_node_NameCompare( p_node->psz_node_name, "style" ) )
478             {
479                 DictionaryMerge( &p_node->attr_dict, p_dst );
480             }
481         }
482     }
483 }
484
485 static void DictToTTMLStyle( const vlc_dictionary_t *p_dict, ttml_style_t *p_ttml_style )
486 {
487     /* Units, defaults, that must be set first to compute styles */
488     FillTTMLStylePrio( p_dict, p_ttml_style );
489     for( int i = 0; i < p_dict->i_size; ++i )
490     {
491         for ( vlc_dictionary_entry_t* p_entry = p_dict->p_entries[i];
492               p_entry != NULL; p_entry = p_entry->p_next )
493         {
494             FillTTMLStyle( p_entry->psz_key, p_entry->p_value, p_ttml_style );
495         }
496     }
497 }
498
499 static ttml_style_t * InheritTTMLStyles( ttml_context_t *p_ctx, tt_node_t *p_node )
500 {
501     assert( p_node );
502     ttml_style_t *p_ttml_style = NULL;
503     vlc_dictionary_t merged;
504     vlc_dictionary_init( &merged, 0 );
505
506     /* Merge dics backwards without overwriting */
507     for( ; p_node; p_node = p_node->p_parent )
508     {
509         DictionaryMerge( &p_node->attr_dict, &merged );
510
511         const char *psz_styleid = (const char *)
512                 vlc_dictionary_value_for_key( &p_node->attr_dict, "style" );
513         if( psz_styleid )
514             DictMergeWithStyleID( p_ctx, psz_styleid, &merged );
515
516         const char *psz_regionid = (const char *)
517                 vlc_dictionary_value_for_key( &p_node->attr_dict, "region" );
518         if( psz_regionid )
519             DictMergeWithRegionID( p_ctx, psz_regionid, &merged );
520     }
521
522     if( !vlc_dictionary_is_empty( &merged ) && (p_ttml_style = ttml_style_New()) )
523     {
524         DictToTTMLStyle( &merged, p_ttml_style );
525     }
526
527     vlc_dictionary_clear( &merged, NULL, NULL );
528
529     return p_ttml_style;
530 }
531
532 static int ParseTTMLChunk( xml_reader_t *p_reader, tt_node_t **pp_rootnode )
533 {
534     const char* psz_node_name;
535
536     do
537     {
538         int i_type = xml_ReaderNextNode( p_reader, &psz_node_name );
539
540         if( i_type <= XML_READER_NONE )
541             break;
542
543         switch(i_type)
544         {
545             default:
546                 break;
547
548             case XML_READER_STARTELEM:
549                 if( tt_node_NameCompare( psz_node_name, "tt" ) ||
550                     *pp_rootnode != NULL )
551                     return VLC_EGENERIC;
552
553                 *pp_rootnode = tt_node_New( p_reader, NULL, psz_node_name );
554                 if( !*pp_rootnode ||
555                     tt_nodes_Read( p_reader, *pp_rootnode ) != VLC_SUCCESS )
556                     return VLC_EGENERIC;
557                 break;
558
559             case XML_READER_ENDELEM:
560                 if( !*pp_rootnode ||
561                     tt_node_NameCompare( psz_node_name, (*pp_rootnode)->psz_node_name ) )
562                     return VLC_EGENERIC;
563                 break;
564         }
565
566     } while( 1 );
567
568     if( *pp_rootnode == NULL )
569         return VLC_EGENERIC;
570
571     return VLC_SUCCESS;
572 }
573
574 static void BIDIConvert( text_segment_t *p_segment, int i_direction )
575 {
576     /*
577     * For bidirectionnal support, we use different enum
578     * to recognize different cases, en then we add the
579     * corresponding unicode character to the text of
580     * the text_segment.
581     */
582     static const struct
583     {
584         const char* psz_uni_start;
585         const char* psz_uni_end;
586     } p_bidi[] = {
587         { "\u2066", "\u2069" },
588         { "\u2067", "\u2069" },
589         { "\u202A", "\u202C" },
590         { "\u202B", "\u202C" },
591         { "\u202D", "\u202C" },
592         { "\u202E", "\u202C" },
593     };
594
595     if( unlikely((size_t)i_direction >= ARRAY_SIZE(p_bidi)) )
596         return;
597
598     char *psz_text = NULL;
599     if( asprintf( &psz_text, "%s%s%s", p_bidi[i_direction].psz_uni_start,
600                   p_segment->psz_text, p_bidi[i_direction].psz_uni_end ) < 0 )
601     {
602         free( p_segment->psz_text );
603         p_segment->psz_text = psz_text;
604     }
605 }
606
607 static void StripSpacing( text_segment_t *p_segment )
608 {
609     /* Newlines must be replaced */
610     char *p = p_segment->psz_text;
611     while( (p = strchr( p, '\n' )) )
612         *p = ' ';
613 }
614
615 static ttml_region_t *GetTTMLRegion( ttml_context_t *p_ctx, const char *psz_region_id )
616 {
617     ttml_region_t *p_region = ( ttml_region_t * )
618             vlc_dictionary_value_for_key( &p_ctx->regions, psz_region_id ? psz_region_id : "" );
619     if( p_region == NULL )
620     {
621         if( psz_region_id && strcmp( psz_region_id, "" ) ) /* not default region */
622         {
623             /* Create if missing and exists as node */
624             const tt_node_t *p_node = FindNode( p_ctx->p_rootnode, "region", -1, psz_region_id );
625             if( p_node && (p_region = ttml_region_New()) )
626             {
627                 /* Fill from its own attributes */
628                 for( int i = 0; i < p_node->attr_dict.i_size; ++i )
629                 {
630                     for ( vlc_dictionary_entry_t* p_entry = p_node->attr_dict.p_entries[i];
631                           p_entry != NULL; p_entry = p_entry->p_next )
632                     {
633                         FillRegionStyle( p_entry->psz_key, p_entry->p_value, p_region );
634                     }
635                 }
636             }
637             vlc_dictionary_insert( &p_ctx->regions, psz_region_id, p_region );
638         }
639         else if( (p_region = ttml_region_New()) ) /* create default */
640         {
641             vlc_dictionary_insert( &p_ctx->regions, "", p_region );
642         }
643     }
644     return p_region;
645 }
646
647 static void AppendLineBreakToRegion( ttml_region_t *p_region )
648 {
649     text_segment_t *p_segment = text_segment_New( "\n" );
650     if( p_segment )
651     {
652         *p_region->pp_last_segment = p_segment;
653         p_region->pp_last_segment = &p_segment->p_next;
654     }
655 }
656
657 static void AppendTextToRegion( ttml_context_t *p_ctx, const tt_textnode_t *p_ttnode,
658                                 const ttml_style_t *p_set_styles, ttml_region_t *p_region )
659 {
660     text_segment_t *p_segment;
661
662     if( p_region == NULL )
663         return;
664
665     p_segment = text_segment_New( p_ttnode->psz_text );
666     if( p_segment )
667     {
668         bool b_preserve_space = false;
669         ttml_style_t *s = InheritTTMLStyles( p_ctx, p_ttnode->p_parent );
670         if( s )
671         {
672             if( p_set_styles )
673                 ttml_style_Merge( p_set_styles, s );
674
675             p_segment->style = s->font_style;
676             s->font_style = NULL;
677
678             b_preserve_space = s->b_preserve_space;
679             if( s->b_direction_set )
680                 BIDIConvert( p_segment, s->i_direction );
681
682             if( s->display == TTML_DISPLAY_NONE )
683             {
684                 /* Must not display, but still occupies space */
685                 p_segment->style->i_features &= ~(STYLE_BACKGROUND|STYLE_OUTLINE|STYLE_STRIKEOUT|STYLE_SHADOW);
686                 p_segment->style->i_font_alpha = STYLE_ALPHA_TRANSPARENT;
687                 p_segment->style->i_features |= STYLE_HAS_FONT_ALPHA;
688             }
689
690             ttml_style_Delete( s );
691         }
692
693         if( !b_preserve_space )
694             StripSpacing( p_segment );
695     }
696
697     *p_region->pp_last_segment = p_segment;
698     p_region->pp_last_segment = &p_segment->p_next;
699 }
700
701 static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, const tt_node_t *p_node,
702                                          ttml_region_t *p_region,
703                                          const ttml_style_t *p_upper_set_styles,
704                                          tt_time_t playbacktime )
705 {
706     if( tt_time_Valid( &playbacktime ) &&
707        !tt_timings_Contains( &p_node->timings, &playbacktime ) )
708         return;
709
710     const char *psz_regionid = (const char *)
711         vlc_dictionary_value_for_key( &p_node->attr_dict, "region" );
712
713     /* Region isn't set or is changing */
714     if( psz_regionid || p_region == NULL )
715         p_region = GetTTMLRegion( p_ctx, psz_regionid );
716
717     /* awkward paragraph handling */
718     if( !tt_node_NameCompare( p_node->psz_node_name, "p" ) &&
719         p_region->updt.p_segments )
720     {
721         AppendLineBreakToRegion( p_region );
722     }
723
724     /* Styles from <set> element */
725     ttml_style_t *p_set_styles = (p_upper_set_styles)
726                                ? ttml_style_Duplicate( p_upper_set_styles )
727                                : NULL;
728
729     for( const tt_basenode_t *p_child = p_node->p_child;
730                               p_child; p_child = p_child->p_next )
731     {
732         if( p_child->i_type == TT_NODE_TYPE_TEXT )
733         {
734             AppendTextToRegion( p_ctx, (const tt_textnode_t *) p_child,
735                                 p_set_styles, p_region );
736         }
737         else if( !tt_node_NameCompare( ((const tt_node_t *)p_child)->psz_node_name, "set" ) )
738         {
739             const tt_node_t *p_set = (const tt_node_t *)p_child;
740             if( !tt_time_Valid( &playbacktime ) ||
741                 tt_timings_Contains( &p_set->timings, &playbacktime ) )
742             {
743                 if( p_set_styles != NULL || (p_set_styles = ttml_style_New()) )
744                 {
745                     /* Merge with or create a local set of styles to apply to following childs */
746                     DictToTTMLStyle( &p_set->attr_dict, p_set_styles );
747                 }
748             }
749         }
750         else if( !tt_node_NameCompare( ((const tt_node_t *)p_child)->psz_node_name, "br" ) )
751         {
752             AppendLineBreakToRegion( p_region );
753         }
754         else
755         {
756             ConvertNodesToRegionContent( p_ctx, (const tt_node_t *) p_child,
757                                          p_region, p_set_styles, playbacktime );
758         }
759     }
760
761     if( p_set_styles )
762         ttml_style_Delete( p_set_styles );
763 }
764
765 static tt_node_t *ParseTTML( decoder_t *p_dec, const uint8_t *p_buffer, size_t i_buffer )
766 {
767     stream_t*       p_sub;
768     xml_reader_t*   p_xml_reader;
769
770     p_sub = vlc_stream_MemoryNew( p_dec, (uint8_t*) p_buffer, i_buffer, true );
771     if( unlikely( p_sub == NULL ) )
772         return NULL;
773
774     p_xml_reader = xml_ReaderCreate( p_dec, p_sub );
775     if( unlikely( p_xml_reader == NULL ) )
776     {
777         vlc_stream_Delete( p_sub );
778         return NULL;
779     }
780
781     tt_node_t *p_rootnode = NULL;
782     if( ParseTTMLChunk( p_xml_reader, &p_rootnode ) != VLC_SUCCESS )
783     {
784         if( p_rootnode )
785             tt_node_RecursiveDelete( p_rootnode );
786         p_rootnode = NULL;
787     }
788
789     xml_ReaderDelete( p_xml_reader );
790     vlc_stream_Delete( p_sub );
791
792     return p_rootnode;
793 }
794
795 static ttml_region_t *GenerateRegions( tt_node_t *p_rootnode, tt_time_t playbacktime )
796 {
797     ttml_region_t*  p_regions = NULL;
798     ttml_region_t** pp_region_last = &p_regions;
799
800     if( !tt_node_NameCompare( p_rootnode->psz_node_name, "tt" ) )
801     {
802         const tt_node_t *p_bodynode = FindNode( p_rootnode, "body", 1, NULL );
803         if( p_bodynode )
804         {
805             ttml_context_t context;
806             context.p_rootnode = p_rootnode;
807             vlc_dictionary_init( &context.regions, 1 );
808             ConvertNodesToRegionContent( &context, p_bodynode, NULL, NULL, playbacktime );
809
810             for( int i = 0; i < context.regions.i_size; ++i )
811             {
812                 for ( const vlc_dictionary_entry_t* p_entry = context.regions.p_entries[i];
813                                                     p_entry != NULL; p_entry = p_entry->p_next )
814                 {
815                     *pp_region_last = (ttml_region_t *) p_entry->p_value;
816                     pp_region_last = (ttml_region_t **) &(*pp_region_last)->updt.p_next;
817                 }
818             }
819
820             vlc_dictionary_clear( &context.regions, NULL, NULL );
821         }
822     }
823     else if ( !tt_node_NameCompare( p_rootnode->psz_node_name, "div" ) ||
824               !tt_node_NameCompare( p_rootnode->psz_node_name, "p" ) )
825     {
826         /* TODO */
827     }
828
829     return p_regions;
830 }
831
832 static int ParseBlock( decoder_t *p_dec, const block_t *p_block )
833 {
834     tt_time_t *p_timings_array = NULL;
835     size_t   i_timings_count = 0;
836
837     /* We Only support absolute timings */
838     tt_timings_t temporal_extent;
839     temporal_extent.i_type = TT_TIMINGS_PARALLEL;
840     tt_time_Init( &temporal_extent.begin );
841     tt_time_Init( &temporal_extent.end );
842     tt_time_Init( &temporal_extent.dur );
843     temporal_extent.begin.base = 0;
844
845     if( p_block->i_flags & BLOCK_FLAG_CORRUPTED )
846         return VLCDEC_SUCCESS;
847
848     /* We cannot display a subpicture with no date */
849     if( p_block->i_pts <= VLC_TS_INVALID )
850     {
851         msg_Warn( p_dec, "subtitle without a date" );
852         return VLCDEC_SUCCESS;
853     }
854
855     tt_node_t *p_rootnode = ParseTTML( p_dec, p_block->p_buffer, p_block->i_buffer );
856     if( !p_rootnode )
857         return VLCDEC_SUCCESS;
858
859     tt_timings_Resolve( (tt_basenode_t *) p_rootnode, &temporal_extent,
860                         &p_timings_array, &i_timings_count );
861
862 #ifdef TTML_DEBUG
863     for( size_t i=0; i<i_timings_count; i++ )
864         printf("%ld ", tt_time_Convert( &p_timings_array[i] ) );
865     printf("\n");
866 #endif
867
868     for( size_t i=0; i+1 < i_timings_count; i++ )
869     {
870         /* We Only support absolute timings (2) */
871         if( tt_time_Convert( &p_timings_array[i] ) + VLC_TS_0 < p_block->i_dts )
872             continue;
873
874         if( tt_time_Convert( &p_timings_array[i] ) + VLC_TS_0 > p_block->i_dts + p_block->i_length )
875             break;
876
877         subpicture_t *p_spu = NULL;
878         ttml_region_t *p_regions = GenerateRegions( p_rootnode, p_timings_array[i] );
879         if( p_regions && ( p_spu = decoder_NewSubpictureText( p_dec ) ) )
880         {
881             p_spu->i_start    = VLC_TS_0 + tt_time_Convert( &p_timings_array[i] );
882             p_spu->i_stop     = VLC_TS_0 + tt_time_Convert( &p_timings_array[i+1] ) - 1;
883             p_spu->b_ephemer  = true;
884             p_spu->b_absolute = false;
885
886             subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
887             subpicture_updater_sys_region_t *p_updtregion = NULL;
888
889             /* Create region update info from each ttml region */
890             for( ttml_region_t *p_region = p_regions;
891                  p_region; p_region = (ttml_region_t *) p_region->updt.p_next )
892             {
893                 if( p_updtregion == NULL )
894                 {
895                     p_updtregion = &p_spu_sys->region;
896                 }
897                 else
898                 {
899                     p_updtregion = SubpictureUpdaterSysRegionNew();
900                     if( p_updtregion == NULL )
901                         break;
902                     SubpictureUpdaterSysRegionAdd( &p_spu_sys->region, p_updtregion );
903                 }
904
905                 /* broken legacy align var (can't handle center...) */
906                 if( p_dec->p_sys->i_align & SUBPICTURE_ALIGN_MASK )
907                 {
908                     p_spu_sys->region.align = p_dec->p_sys->i_align & (SUBPICTURE_ALIGN_BOTTOM|SUBPICTURE_ALIGN_TOP);
909                     p_spu_sys->region.inner_align = p_dec->p_sys->i_align & (SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT);
910                 }
911
912                 /* copy and take ownership of pointeds */
913                 *p_updtregion = p_region->updt;
914                 p_updtregion->p_next = NULL;
915                 p_region->updt.p_region_style = NULL;
916                 p_region->updt.p_segments = NULL;
917             }
918
919         }
920
921         /* cleanup */
922         while( p_regions )
923         {
924             ttml_region_t *p_nextregion = (ttml_region_t *) p_regions->updt.p_next;
925             ttml_region_Delete( p_regions );
926             p_regions = p_nextregion;
927         }
928
929         if( p_spu )
930             decoder_QueueSub( p_dec, p_spu );
931     }
932
933     tt_node_RecursiveDelete( p_rootnode );
934
935     free( p_timings_array );
936
937     return VLCDEC_SUCCESS;
938 }
939
940
941
942 /****************************************************************************
943  * DecodeBlock: the whole thing
944  ****************************************************************************/
945 static int DecodeBlock( decoder_t *p_dec, block_t *p_block )
946 {
947     if( p_block == NULL ) /* No Drain */
948         return VLCDEC_SUCCESS;
949
950     int ret = ParseBlock( p_dec, p_block );
951 #ifdef TTML_DEBUG
952     if( p_block->i_buffer )
953     {
954         p_block->p_buffer[p_block->i_buffer - 1] = 0;
955         msg_Dbg(p_dec,"time %ld %s", p_block->i_dts, p_block->p_buffer);
956     }
957 #endif
958     block_Release( p_block );
959     return ret;
960 }
961
962 /*****************************************************************************
963  * OpenDecoder: probe the decoder and return score
964  *****************************************************************************/
965 int OpenDecoder( vlc_object_t *p_this )
966 {
967     decoder_t *p_dec = (decoder_t*)p_this;
968     decoder_sys_t *p_sys;
969
970     if( p_dec->fmt_in.i_codec != VLC_CODEC_TTML )
971         return VLC_EGENERIC;
972
973     /* Allocate the memory needed to store the decoder's structure */
974     p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
975     if( unlikely( p_sys == NULL ) )
976         return VLC_ENOMEM;
977
978     p_dec->pf_decode = DecodeBlock;
979     p_dec->fmt_out.i_cat = SPU_ES;
980     p_sys->i_align = var_InheritInteger( p_dec, "ttml-align" );
981
982     return VLC_SUCCESS;
983 }
984
985 /*****************************************************************************
986  * CloseDecoder: clean up the decoder
987  *****************************************************************************/
988 void CloseDecoder( vlc_object_t *p_this )
989 {
990     decoder_t *p_dec = (decoder_t *)p_this;
991     decoder_sys_t *p_sys = p_dec->p_sys;
992
993     free( p_sys );
994 }