codec: ttml: kill default spu margins (fix #19161)
[vlc.git] / modules / codec / ttml / substtml.c
blob9656aa75607ba3541168033b1247cdc48930ecd9
1 /*****************************************************************************
2 * substtml.c : TTML subtitles decoder
3 *****************************************************************************
4 * Copyright (C) 2015-2017 VLC authors and VideoLAN
6 * Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
7 * Sushma Reddy <sushma.reddy@research.iiit.ac.in>
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.
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.
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
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>
34 #include <ctype.h>
35 #include <assert.h>
37 #include "substext.h"
38 #include "ttml.h"
40 //#define TTML_DEBUG
42 /*****************************************************************************
43 * Local prototypes
44 *****************************************************************************/
45 typedef struct
47 float i_value;
48 enum
50 TTML_UNIT_UNKNOWN = 0,
51 TTML_UNIT_PERCENT,
52 TTML_UNIT_CELL,
53 TTML_UNIT_PIXELS,
54 } unit;
55 } ttml_length_t;
57 #define TTML_DEFAULT_CELL_RESOLUTION_H 32
58 #define TTML_DEFAULT_CELL_RESOLUTION_V 15
59 #define TTML_LINE_TO_HEIGHT_RATIO 1.06
61 typedef struct
63 text_style_t* font_style;
64 ttml_length_t font_size;
65 ttml_length_t extent_h, extent_v;
66 int i_text_align;
67 bool b_text_align_set;
68 int i_direction;
69 bool b_direction_set;
70 bool b_preserve_space;
71 enum
73 TTML_DISPLAY_UNKNOWN = 0,
74 TTML_DISPLAY_AUTO,
75 TTML_DISPLAY_NONE,
76 } display;
77 } ttml_style_t;
79 typedef struct
81 vlc_dictionary_t regions;
82 tt_node_t * p_rootnode; /* for now. FIXME: split header */
83 ttml_length_t root_extent_h, root_extent_v;
84 unsigned i_cell_resolution_v;
85 unsigned i_cell_resolution_h;
86 } ttml_context_t;
88 typedef struct
90 subpicture_updater_sys_region_t updt;
91 text_segment_t **pp_last_segment;
92 } ttml_region_t;
94 struct decoder_sys_t
96 int i_align;
99 enum
101 UNICODE_BIDI_LTR = 0,
102 UNICODE_BIDI_RTL = 1,
103 UNICODE_BIDI_EMBEDDED = 2,
104 UNICODE_BIDI_OVERRIDE = 4,
108 * TTML Parsing and inheritance order:
109 * Each time a text node is found and belongs to out time interval,
110 * we backward merge attributes dictionnary up to root.
111 * Then we convert attributes, merging with style by id or region
112 * style, and sets from parent node.
114 static tt_node_t *ParseTTML( decoder_t *, const uint8_t *, size_t );
116 static void ttml_style_Delete( ttml_style_t* p_ttml_style )
118 text_style_Delete( p_ttml_style->font_style );
119 free( p_ttml_style );
122 static ttml_style_t * ttml_style_New( )
124 ttml_style_t *p_ttml_style = calloc( 1, sizeof( ttml_style_t ) );
125 if( unlikely( !p_ttml_style ) )
126 return NULL;
128 p_ttml_style->extent_h.i_value = 100;
129 p_ttml_style->extent_h.unit = TTML_UNIT_PERCENT;
130 p_ttml_style->extent_v.i_value = 100;
131 p_ttml_style->extent_v.unit = TTML_UNIT_PERCENT;
132 p_ttml_style->font_size.i_value = 1.0;
133 p_ttml_style->font_size.unit = TTML_UNIT_CELL;
134 p_ttml_style->font_style = text_style_Create( STYLE_NO_DEFAULTS );
135 if( unlikely( !p_ttml_style->font_style ) )
137 free( p_ttml_style );
138 return NULL;
140 return p_ttml_style;
143 static void ttml_region_Delete( ttml_region_t *p_region )
145 SubpictureUpdaterSysRegionClean( &p_region->updt );
146 free( p_region );
149 static ttml_style_t * ttml_style_Duplicate( const ttml_style_t *p_src )
151 ttml_style_t *p_dup = ttml_style_New( );
152 if( p_dup )
154 *p_dup = *p_src;
155 p_dup->font_style = text_style_Duplicate( p_src->font_style );
157 return p_dup;
160 static void ttml_style_Merge( const ttml_style_t *p_src, ttml_style_t *p_dst )
162 if( p_src && p_dst )
164 if( p_src->font_style )
166 if( p_dst->font_style )
167 text_style_Merge( p_dst->font_style, p_src->font_style, true );
168 else
169 p_dst->font_style = text_style_Duplicate( p_src->font_style );
172 if( p_src->b_direction_set )
174 p_dst->b_direction_set = true;
175 p_dst->i_direction = p_src->i_direction;
178 if( p_src->display != TTML_DISPLAY_UNKNOWN )
179 p_dst->display = p_src->display;
183 static ttml_region_t *ttml_region_New( )
185 ttml_region_t *p_ttml_region = calloc( 1, sizeof( ttml_region_t ) );
186 if( unlikely( !p_ttml_region ) )
187 return NULL;
189 SubpictureUpdaterSysRegionInit( &p_ttml_region->updt );
190 p_ttml_region->pp_last_segment = &p_ttml_region->updt.p_segments;
191 /* Align to top by default. !Warn: center align is obtained with NO flags */
192 p_ttml_region->updt.align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
193 p_ttml_region->updt.inner_align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
195 return p_ttml_region;
198 static ttml_length_t ttml_read_length( const char *psz )
200 ttml_length_t len = { 0.0, TTML_UNIT_UNKNOWN };
202 char* psz_end = NULL;
203 float size = us_strtof( psz, &psz_end );
204 len.i_value = size;
205 if( psz_end )
207 if( *psz_end == 'c' || *psz_end == 'r' )
208 len.unit = TTML_UNIT_CELL;
209 else if( *psz_end == '%' )
210 len.unit = TTML_UNIT_PERCENT;
211 else if( *psz_end == 'p' && *(psz_end + 1) == 'x' )
212 len.unit = TTML_UNIT_PIXELS;
214 return len;
217 static ttml_length_t ttml_rebase_length( ttml_length_t value,
218 ttml_length_t reference,
219 unsigned i_cell_resolution )
221 if( value.unit == TTML_UNIT_PERCENT )
223 value.i_value *= reference.i_value / 100.0;
224 value.unit = reference.unit;
226 else if( value.unit == TTML_UNIT_CELL )
228 value.i_value *= reference.i_value / i_cell_resolution;
229 value.unit = reference.unit;
231 // pixels as-is
232 return value;
235 static tt_node_t * FindNode( tt_node_t *p_node, const char *psz_nodename,
236 size_t i_maxdepth, const char *psz_id )
238 if( !tt_node_NameCompare( p_node->psz_node_name, psz_nodename ) )
240 if( psz_id != NULL )
242 char *psz = vlc_dictionary_value_for_key( &p_node->attr_dict, "xml:id" );
243 if( psz && !strcmp( psz, psz_id ) )
244 return p_node;
246 else return p_node;
249 if( i_maxdepth == 0 )
250 return NULL;
252 for( tt_basenode_t *p_child = p_node->p_child;
253 p_child; p_child = p_child->p_next )
255 if( p_child->i_type == TT_NODE_TYPE_TEXT )
256 continue;
258 p_node = FindNode( (tt_node_t *) p_child, psz_nodename, i_maxdepth - 1, psz_id );
259 if( p_node )
260 return p_node;
263 return NULL;
266 static void FillTextStyle( const char *psz_attr, const char *psz_val,
267 text_style_t *p_text_style )
269 if( !strcasecmp ( "tts:fontFamily", psz_attr ) )
271 free( p_text_style->psz_fontname );
272 p_text_style->psz_fontname = strdup( psz_val );
274 else if( !strcasecmp( "tts:opacity", psz_attr ) )
276 p_text_style->i_background_alpha = atoi( psz_val );
277 p_text_style->i_font_alpha = atoi( psz_val );
278 p_text_style->i_features |= STYLE_HAS_BACKGROUND_ALPHA | STYLE_HAS_FONT_ALPHA;
280 else if( !strcasecmp( "tts:color", psz_attr ) )
282 unsigned int i_color = vlc_html_color( psz_val, NULL );
283 p_text_style->i_font_color = (i_color & 0xffffff);
284 p_text_style->i_font_alpha = (i_color & 0xFF000000) >> 24;
285 p_text_style->i_features |= STYLE_HAS_FONT_COLOR | STYLE_HAS_FONT_ALPHA;
287 else if( !strcasecmp( "tts:backgroundColor", psz_attr ) )
289 unsigned int i_color = vlc_html_color( psz_val, NULL );
290 p_text_style->i_background_color = i_color & 0xFFFFFF;
291 p_text_style->i_background_alpha = (i_color & 0xFF000000) >> 24;
292 p_text_style->i_features |= STYLE_HAS_BACKGROUND_COLOR
293 | STYLE_HAS_BACKGROUND_ALPHA;
294 p_text_style->i_style_flags |= STYLE_BACKGROUND;
296 else if( !strcasecmp( "tts:fontStyle", psz_attr ) )
298 if( !strcasecmp ( "italic", psz_val ) || !strcasecmp ( "oblique", psz_val ) )
299 p_text_style->i_style_flags |= STYLE_ITALIC;
300 else
301 p_text_style->i_style_flags &= ~STYLE_ITALIC;
302 p_text_style->i_features |= STYLE_HAS_FLAGS;
304 else if( !strcasecmp ( "tts:fontWeight", psz_attr ) )
306 if( !strcasecmp ( "bold", psz_val ) )
307 p_text_style->i_style_flags |= STYLE_BOLD;
308 else
309 p_text_style->i_style_flags &= ~STYLE_BOLD;
310 p_text_style->i_features |= STYLE_HAS_FLAGS;
312 else if( !strcasecmp ( "tts:textDecoration", psz_attr ) )
314 if( !strcasecmp ( "underline", psz_val ) )
315 p_text_style->i_style_flags |= STYLE_UNDERLINE;
316 else if( !strcasecmp ( "noUnderline", psz_val ) )
317 p_text_style->i_style_flags &= ~STYLE_UNDERLINE;
318 if( !strcasecmp ( "lineThrough", psz_val ) )
319 p_text_style->i_style_flags |= STYLE_STRIKEOUT;
320 else if( !strcasecmp ( "noLineThrough", psz_val ) )
321 p_text_style->i_style_flags &= ~STYLE_STRIKEOUT;
322 p_text_style->i_features |= STYLE_HAS_FLAGS;
324 else if( !strcasecmp( "tts:textOutline", psz_attr ) )
326 char *value = strdup( psz_val );
327 char* psz_saveptr = NULL;
328 char* token = (value) ? strtok_r( value, " ", &psz_saveptr ) : NULL;
329 // <color>? <length> <length>?
330 if( token != NULL )
332 bool b_ok = false;
333 unsigned int color = vlc_html_color( token, &b_ok );
334 if( b_ok )
336 p_text_style->i_outline_color = color & 0xFFFFFF;
337 p_text_style->i_outline_alpha = (color & 0xFF000000) >> 24;
338 token = strtok_r( NULL, " ", &psz_saveptr );
339 if( token != NULL )
341 char* psz_end = NULL;
342 int i_outline_width = strtol( token, &psz_end, 10 );
343 if( psz_end != token )
345 // Assume unit is pixel, and ignore border radius
346 p_text_style->i_outline_width = i_outline_width;
351 free( value );
355 static void FillRegionStyle( const char *psz_attr, const char *psz_val,
356 ttml_context_t *p_ctx, ttml_region_t *p_region )
358 if( !strcasecmp( "tts:displayAlign", psz_attr ) )
360 p_region->updt.inner_align &= ~(SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_BOTTOM);
361 if( !strcasecmp( "after", psz_val ) )
362 p_region->updt.inner_align |= SUBPICTURE_ALIGN_BOTTOM;
363 else if( strcasecmp( "center", psz_val ) )
364 /* "before" */
365 p_region->updt.inner_align |= SUBPICTURE_ALIGN_TOP;
367 else if( !strcasecmp ( "tts:origin", psz_attr ) ||
368 !strcasecmp ( "tts:extent", psz_attr ) )
370 const char *psz_token = psz_val;
371 while( isspace( *psz_token ) )
372 psz_token++;
374 ttml_length_t x = ttml_read_length( psz_token );
376 while( *psz_token && !isspace( *psz_token ) )
377 psz_token++;
378 while( *psz_token && isspace( *psz_token ) )
379 psz_token++;
381 ttml_length_t y = ttml_read_length( psz_token );
383 if ( x.unit != TTML_UNIT_UNKNOWN && y.unit != TTML_UNIT_UNKNOWN )
385 ttml_length_t base = { 100.0, TTML_UNIT_PERCENT };
386 x = ttml_rebase_length( x, base, p_ctx->i_cell_resolution_h );
387 y = ttml_rebase_length( y, base, p_ctx->i_cell_resolution_v );
388 if( psz_attr[4] == 'o' )
390 p_region->updt.origin.x = x.i_value / 100.0;
391 p_region->updt.flags |= UPDT_REGION_ORIGIN_X_IS_RATIO;
392 p_region->updt.origin.y = y.i_value / 100.0;
393 p_region->updt.flags |= UPDT_REGION_ORIGIN_Y_IS_RATIO;
394 p_region->updt.align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
396 else
398 p_region->updt.extent.x = x.i_value / 100.0;
399 p_region->updt.flags |= UPDT_REGION_EXTENT_X_IS_RATIO;
400 p_region->updt.extent.y = y.i_value / 100.0;
401 p_region->updt.flags |= UPDT_REGION_EXTENT_Y_IS_RATIO;
407 static void ReadTTMLExtent( const char *value, ttml_length_t *h, ttml_length_t *v )
409 ttml_length_t vals[2] = { { 0.0, TTML_UNIT_UNKNOWN },
410 { 0.0, TTML_UNIT_UNKNOWN } };
411 char *dup = strdup( value );
412 char* psz_saveptr = NULL;
413 char* token = (dup) ? strtok_r( dup, " ", &psz_saveptr ) : NULL;
414 for(int i=0; i<2 && token != NULL; i++)
416 token = strtok_r( NULL, " ", &psz_saveptr );
417 if( token != NULL )
418 vals[i] = ttml_read_length( token );
420 free( dup );
422 if( vals[0].unit != TTML_UNIT_UNKNOWN &&
423 vals[1].unit != TTML_UNIT_UNKNOWN )
425 *h = vals[0];
426 *v = vals[1];
430 static void ComputeTTMLStyles( ttml_context_t *p_ctx, const vlc_dictionary_t *p_dict,
431 ttml_style_t *p_ttml_style )
433 VLC_UNUSED(p_dict);
434 /* Values depending on multiple others are converted last
435 * Default value conversion must also not depend on attribute presence */
436 text_style_t *p_text_style = p_ttml_style->font_style;
437 ttml_length_t len = p_ttml_style->font_size;
438 len = ttml_rebase_length( len, p_ctx->root_extent_h,
439 p_ctx->i_cell_resolution_v );
440 if( len.unit == TTML_UNIT_CELL )
441 p_text_style->f_font_relsize = 100.0 * len.i_value /
442 (p_ctx->i_cell_resolution_v / TTML_LINE_TO_HEIGHT_RATIO);
443 else if( len.unit == TTML_UNIT_PERCENT )
444 p_text_style->f_font_relsize = len.i_value /
445 (p_ctx->i_cell_resolution_v / TTML_LINE_TO_HEIGHT_RATIO);
446 else if( len.unit == TTML_UNIT_PIXELS )
447 p_text_style->i_font_size = (int)( len.i_value + 0.5 );
450 static void FillTTMLStyle( const char *psz_attr, const char *psz_val,
451 ttml_style_t *p_ttml_style )
453 if( !strcasecmp( "tts:extent", psz_attr ) )
455 ReadTTMLExtent( psz_attr, &p_ttml_style->extent_h,
456 &p_ttml_style->extent_v );
458 else if( !strcasecmp( "tts:textAlign", psz_attr ) )
460 p_ttml_style->i_text_align &= ~(SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT);
461 if( !strcasecmp ( "left", psz_val ) )
462 p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_LEFT;
463 else if( !strcasecmp ( "right", psz_val ) )
464 p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_RIGHT;
465 else if( !strcasecmp ( "end", psz_val ) ) /* FIXME: should be BIDI based */
466 p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_RIGHT;
467 else if( strcasecmp ( "center", psz_val ) )
468 /* == "start" FIXME: should be BIDI based */
469 p_ttml_style->i_text_align |= SUBPICTURE_ALIGN_LEFT;
470 p_ttml_style->b_text_align_set = true;
471 #ifdef TTML_DEBUG
472 printf("**%s %x\n", psz_val, p_ttml_style->i_text_align);
473 #endif
475 else if( !strcasecmp( "tts:fontSize", psz_attr ) )
477 ttml_length_t len = ttml_read_length( psz_val );
478 if( len.unit != TTML_UNIT_UNKNOWN && len.i_value > 0.0 )
479 p_ttml_style->font_size = len;
481 else if( !strcasecmp( "tts:direction", psz_attr ) )
483 if( !strcasecmp( "rtl", psz_val ) )
485 p_ttml_style->i_direction |= UNICODE_BIDI_RTL;
486 p_ttml_style->b_direction_set = true;
488 else if( !strcasecmp( "ltr", psz_val ) )
490 p_ttml_style->i_direction |= UNICODE_BIDI_LTR;
491 p_ttml_style->b_direction_set = true;
494 else if( !strcasecmp( "tts:unicodeBidi", psz_attr ) )
496 if( !strcasecmp( "bidiOverride", psz_val ) )
497 p_ttml_style->i_direction |= UNICODE_BIDI_OVERRIDE & ~UNICODE_BIDI_EMBEDDED;
498 else if( !strcasecmp( "embed", psz_val ) )
499 p_ttml_style->i_direction |= UNICODE_BIDI_EMBEDDED & ~UNICODE_BIDI_OVERRIDE;
501 else if( !strcasecmp( "tts:writingMode", psz_attr ) )
503 if( !strcasecmp( "rl", psz_val ) || !strcasecmp( "rltb", psz_val ) )
505 p_ttml_style->i_direction = UNICODE_BIDI_RTL | UNICODE_BIDI_OVERRIDE;
506 //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
507 p_ttml_style->b_direction_set = true;
509 else if( !strcasecmp( "lr", psz_val ) || !strcasecmp( "lrtb", psz_val ) )
511 p_ttml_style->i_direction = UNICODE_BIDI_LTR | UNICODE_BIDI_OVERRIDE;
512 //p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
513 p_ttml_style->b_direction_set = true;
516 else if( !strcmp( "tts:display", psz_attr ) )
518 if( !strcmp( "none", psz_val ) )
519 p_ttml_style->display = TTML_DISPLAY_NONE;
520 else
521 p_ttml_style->display = TTML_DISPLAY_AUTO;
523 else if( !strcasecmp( "xml:space", psz_attr ) )
525 p_ttml_style->b_preserve_space = !strcmp( "preserve", psz_val );
527 else FillTextStyle( psz_attr, psz_val, p_ttml_style->font_style );
530 static void DictionaryMerge( const vlc_dictionary_t *p_src, vlc_dictionary_t *p_dst )
532 for( int i = 0; i < p_src->i_size; ++i )
534 for ( const vlc_dictionary_entry_t* p_entry = p_src->p_entries[i];
535 p_entry != NULL; p_entry = p_entry->p_next )
537 if( ( !strncmp( "tts:", p_entry->psz_key, 4 ) ||
538 !strncmp( "ttp:", p_entry->psz_key, 4 ) ||
539 !strcmp( "xml:space", p_entry->psz_key ) ) &&
540 !vlc_dictionary_has_key( p_dst, p_entry->psz_key ) )
541 vlc_dictionary_insert( p_dst, p_entry->psz_key, p_entry->p_value );
546 static void DictMergeWithStyleID( ttml_context_t *p_ctx, const char *psz_id,
547 vlc_dictionary_t *p_dst )
549 assert(p_ctx->p_rootnode);
550 if( psz_id && p_ctx->p_rootnode )
552 /* Lookup referenced style ID */
553 const tt_node_t *p_node = FindNode( p_ctx->p_rootnode,
554 "style", -1, psz_id );
555 if( p_node )
556 DictionaryMerge( &p_node->attr_dict, p_dst );
560 static void DictMergeWithRegionID( ttml_context_t *p_ctx, const char *psz_id,
561 vlc_dictionary_t *p_dst )
563 assert(p_ctx->p_rootnode);
564 if( psz_id && p_ctx->p_rootnode )
566 const tt_node_t *p_regionnode = FindNode( p_ctx->p_rootnode,
567 "region", -1, psz_id );
568 if( !p_regionnode )
569 return;
571 DictionaryMerge( &p_regionnode->attr_dict, p_dst );
573 const char *psz_styleid = (const char *)
574 vlc_dictionary_value_for_key( &p_regionnode->attr_dict, "style" );
575 if( psz_styleid )
576 DictMergeWithStyleID( p_ctx, psz_styleid, p_dst );
578 for( const tt_basenode_t *p_child = p_regionnode->p_child;
579 p_child; p_child = p_child->p_next )
581 if( unlikely( p_child->i_type == TT_NODE_TYPE_TEXT ) )
582 continue;
584 const tt_node_t *p_node = (const tt_node_t *) p_child;
585 if( !tt_node_NameCompare( p_node->psz_node_name, "style" ) )
587 DictionaryMerge( &p_node->attr_dict, p_dst );
593 static void DictToTTMLStyle( ttml_context_t *p_ctx, const vlc_dictionary_t *p_dict,
594 ttml_style_t *p_ttml_style )
596 for( int i = 0; i < p_dict->i_size; ++i )
598 for ( vlc_dictionary_entry_t* p_entry = p_dict->p_entries[i];
599 p_entry != NULL; p_entry = p_entry->p_next )
601 FillTTMLStyle( p_entry->psz_key, p_entry->p_value, p_ttml_style );
604 ComputeTTMLStyles( p_ctx, p_dict, p_ttml_style );
607 static ttml_style_t * InheritTTMLStyles( ttml_context_t *p_ctx, tt_node_t *p_node )
609 assert( p_node );
610 ttml_style_t *p_ttml_style = NULL;
611 vlc_dictionary_t merged;
612 vlc_dictionary_init( &merged, 0 );
614 /* Merge dics backwards without overwriting */
615 for( ; p_node; p_node = p_node->p_parent )
617 DictionaryMerge( &p_node->attr_dict, &merged );
619 const char *psz_styleid = (const char *)
620 vlc_dictionary_value_for_key( &p_node->attr_dict, "style" );
621 if( psz_styleid )
622 DictMergeWithStyleID( p_ctx, psz_styleid, &merged );
624 const char *psz_regionid = (const char *)
625 vlc_dictionary_value_for_key( &p_node->attr_dict, "region" );
626 if( psz_regionid )
627 DictMergeWithRegionID( p_ctx, psz_regionid, &merged );
630 if( !vlc_dictionary_is_empty( &merged ) && (p_ttml_style = ttml_style_New()) )
632 DictToTTMLStyle( p_ctx, &merged, p_ttml_style );
635 vlc_dictionary_clear( &merged, NULL, NULL );
637 return p_ttml_style;
640 static int ParseTTMLChunk( xml_reader_t *p_reader, tt_node_t **pp_rootnode )
642 const char* psz_node_name;
646 int i_type = xml_ReaderNextNode( p_reader, &psz_node_name );
648 if( i_type <= XML_READER_NONE )
649 break;
651 switch(i_type)
653 default:
654 break;
656 case XML_READER_STARTELEM:
657 if( tt_node_NameCompare( psz_node_name, "tt" ) ||
658 *pp_rootnode != NULL )
659 return VLC_EGENERIC;
661 *pp_rootnode = tt_node_New( p_reader, NULL, psz_node_name );
662 if( !*pp_rootnode ||
663 tt_nodes_Read( p_reader, *pp_rootnode ) != VLC_SUCCESS )
664 return VLC_EGENERIC;
665 break;
667 case XML_READER_ENDELEM:
668 if( !*pp_rootnode ||
669 tt_node_NameCompare( psz_node_name, (*pp_rootnode)->psz_node_name ) )
670 return VLC_EGENERIC;
671 break;
674 } while( 1 );
676 if( *pp_rootnode == NULL )
677 return VLC_EGENERIC;
679 return VLC_SUCCESS;
682 static void BIDIConvert( text_segment_t *p_segment, int i_direction )
685 * For bidirectionnal support, we use different enum
686 * to recognize different cases, en then we add the
687 * corresponding unicode character to the text of
688 * the text_segment.
690 static const struct
692 const char* psz_uni_start;
693 const char* psz_uni_end;
694 } p_bidi[] = {
695 { "\u2066", "\u2069" },
696 { "\u2067", "\u2069" },
697 { "\u202A", "\u202C" },
698 { "\u202B", "\u202C" },
699 { "\u202D", "\u202C" },
700 { "\u202E", "\u202C" },
703 if( unlikely((size_t)i_direction >= ARRAY_SIZE(p_bidi)) )
704 return;
706 char *psz_text = NULL;
707 if( asprintf( &psz_text, "%s%s%s", p_bidi[i_direction].psz_uni_start,
708 p_segment->psz_text, p_bidi[i_direction].psz_uni_end ) < 0 )
710 free( p_segment->psz_text );
711 p_segment->psz_text = psz_text;
715 static void StripSpacing( text_segment_t *p_segment )
717 /* Newlines must be replaced */
718 char *p = p_segment->psz_text;
719 while( (p = strchr( p, '\n' )) )
720 *p = ' ';
723 static ttml_region_t *GetTTMLRegion( ttml_context_t *p_ctx, const char *psz_region_id )
725 ttml_region_t *p_region = ( ttml_region_t * )
726 vlc_dictionary_value_for_key( &p_ctx->regions, psz_region_id ? psz_region_id : "" );
727 if( p_region == NULL )
729 if( psz_region_id && strcmp( psz_region_id, "" ) ) /* not default region */
731 /* Create region if if missing */
733 vlc_dictionary_t merged;
734 vlc_dictionary_init( &merged, 0 );
735 /* Get all attributes, including region > style */
736 DictMergeWithRegionID( p_ctx, psz_region_id, &merged );
737 if( (p_region = ttml_region_New()) )
739 /* Fill from its own attributes */
740 for( int i = 0; i < merged.i_size; ++i )
742 for ( vlc_dictionary_entry_t* p_entry = merged.p_entries[i];
743 p_entry != NULL; p_entry = p_entry->p_next )
745 FillRegionStyle( p_entry->psz_key, p_entry->p_value,
746 p_ctx, p_region );
750 vlc_dictionary_clear( &merged, NULL, NULL );
752 vlc_dictionary_insert( &p_ctx->regions, psz_region_id, p_region );
754 else if( (p_region = ttml_region_New()) ) /* create default */
756 vlc_dictionary_insert( &p_ctx->regions, "", p_region );
759 return p_region;
762 static void AppendLineBreakToRegion( ttml_region_t *p_region )
764 text_segment_t *p_segment = text_segment_New( "\n" );
765 if( p_segment )
767 *p_region->pp_last_segment = p_segment;
768 p_region->pp_last_segment = &p_segment->p_next;
772 static void AppendTextToRegion( ttml_context_t *p_ctx, const tt_textnode_t *p_ttnode,
773 const ttml_style_t *p_set_styles, ttml_region_t *p_region )
775 text_segment_t *p_segment;
777 if( p_region == NULL )
778 return;
780 p_segment = text_segment_New( p_ttnode->psz_text );
781 if( p_segment )
783 bool b_preserve_space = false;
784 ttml_style_t *s = InheritTTMLStyles( p_ctx, p_ttnode->p_parent );
785 if( s )
787 if( p_set_styles )
788 ttml_style_Merge( p_set_styles, s );
790 p_segment->style = s->font_style;
791 s->font_style = NULL;
793 b_preserve_space = s->b_preserve_space;
794 if( s->b_direction_set )
795 BIDIConvert( p_segment, s->i_direction );
797 if( s->display == TTML_DISPLAY_NONE )
799 /* Must not display, but still occupies space */
800 p_segment->style->i_features &= ~(STYLE_BACKGROUND|STYLE_OUTLINE|STYLE_STRIKEOUT|STYLE_SHADOW);
801 p_segment->style->i_font_alpha = STYLE_ALPHA_TRANSPARENT;
802 p_segment->style->i_features |= STYLE_HAS_FONT_ALPHA;
805 /* we don't have paragraph, so no per text line alignment.
806 * Text style brings horizontal textAlign to region.
807 * Region itself is styled with vertical displayAlign */
808 if( s->b_text_align_set )
810 p_region->updt.inner_align &= ~(SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT);
811 p_region->updt.inner_align |= s->i_text_align;
814 ttml_style_Delete( s );
817 if( !b_preserve_space )
818 StripSpacing( p_segment );
821 *p_region->pp_last_segment = p_segment;
822 p_region->pp_last_segment = &p_segment->p_next;
825 static void ConvertNodesToRegionContent( ttml_context_t *p_ctx, const tt_node_t *p_node,
826 ttml_region_t *p_region,
827 const ttml_style_t *p_upper_set_styles,
828 tt_time_t playbacktime )
830 if( tt_time_Valid( &playbacktime ) &&
831 !tt_timings_Contains( &p_node->timings, &playbacktime ) )
832 return;
834 const char *psz_regionid = (const char *)
835 vlc_dictionary_value_for_key( &p_node->attr_dict, "region" );
837 /* Region isn't set or is changing */
838 if( psz_regionid || p_region == NULL )
839 p_region = GetTTMLRegion( p_ctx, psz_regionid );
841 /* awkward paragraph handling */
842 if( !tt_node_NameCompare( p_node->psz_node_name, "p" ) &&
843 p_region->updt.p_segments )
845 AppendLineBreakToRegion( p_region );
848 /* Styles from <set> element */
849 ttml_style_t *p_set_styles = (p_upper_set_styles)
850 ? ttml_style_Duplicate( p_upper_set_styles )
851 : NULL;
853 for( const tt_basenode_t *p_child = p_node->p_child;
854 p_child; p_child = p_child->p_next )
856 if( p_child->i_type == TT_NODE_TYPE_TEXT )
858 AppendTextToRegion( p_ctx, (const tt_textnode_t *) p_child,
859 p_set_styles, p_region );
861 else if( !tt_node_NameCompare( ((const tt_node_t *)p_child)->psz_node_name, "set" ) )
863 const tt_node_t *p_set = (const tt_node_t *)p_child;
864 if( !tt_time_Valid( &playbacktime ) ||
865 tt_timings_Contains( &p_set->timings, &playbacktime ) )
867 if( p_set_styles != NULL || (p_set_styles = ttml_style_New()) )
869 /* Merge with or create a local set of styles to apply to following childs */
870 DictToTTMLStyle( p_ctx, &p_set->attr_dict, p_set_styles );
874 else if( !tt_node_NameCompare( ((const tt_node_t *)p_child)->psz_node_name, "br" ) )
876 AppendLineBreakToRegion( p_region );
878 else
880 ConvertNodesToRegionContent( p_ctx, (const tt_node_t *) p_child,
881 p_region, p_set_styles, playbacktime );
885 if( p_set_styles )
886 ttml_style_Delete( p_set_styles );
889 static tt_node_t *ParseTTML( decoder_t *p_dec, const uint8_t *p_buffer, size_t i_buffer )
891 stream_t* p_sub;
892 xml_reader_t* p_xml_reader;
894 p_sub = vlc_stream_MemoryNew( p_dec, (uint8_t*) p_buffer, i_buffer, true );
895 if( unlikely( p_sub == NULL ) )
896 return NULL;
898 p_xml_reader = xml_ReaderCreate( p_dec, p_sub );
899 if( unlikely( p_xml_reader == NULL ) )
901 vlc_stream_Delete( p_sub );
902 return NULL;
905 tt_node_t *p_rootnode = NULL;
906 if( ParseTTMLChunk( p_xml_reader, &p_rootnode ) != VLC_SUCCESS )
908 if( p_rootnode )
909 tt_node_RecursiveDelete( p_rootnode );
910 p_rootnode = NULL;
913 xml_ReaderDelete( p_xml_reader );
914 vlc_stream_Delete( p_sub );
916 return p_rootnode;
919 static void InitTTMLContext( tt_node_t *p_rootnode, ttml_context_t *p_ctx )
921 p_ctx->p_rootnode = p_rootnode;
922 /* set defaults required for size/cells computation */
923 p_ctx->root_extent_h.i_value = 100;
924 p_ctx->root_extent_h.unit = TTML_UNIT_PERCENT;
925 p_ctx->root_extent_v.i_value = 100;
926 p_ctx->root_extent_v.unit = TTML_UNIT_PERCENT;
927 p_ctx->i_cell_resolution_v = TTML_DEFAULT_CELL_RESOLUTION_V;
928 p_ctx->i_cell_resolution_h = TTML_DEFAULT_CELL_RESOLUTION_H;
929 /* and override them */
930 const char *value = vlc_dictionary_value_for_key( &p_rootnode->attr_dict,
931 "tts:extent" );
932 if( value != kVLCDictionaryNotFound )
934 ReadTTMLExtent( value, &p_ctx->root_extent_h,
935 &p_ctx->root_extent_v );
937 value = vlc_dictionary_value_for_key( &p_rootnode->attr_dict,
938 "ttp:cellResolution" );
939 if( value != kVLCDictionaryNotFound )
941 unsigned w, h;
942 if( sscanf( value, "%u %u", &w, &h) == 2 && w && h )
944 p_ctx->i_cell_resolution_h = w;
945 p_ctx->i_cell_resolution_v = h;
950 static ttml_region_t *GenerateRegions( tt_node_t *p_rootnode, tt_time_t playbacktime )
952 ttml_region_t* p_regions = NULL;
953 ttml_region_t** pp_region_last = &p_regions;
955 if( !tt_node_NameCompare( p_rootnode->psz_node_name, "tt" ) )
957 const tt_node_t *p_bodynode = FindNode( p_rootnode, "body", 1, NULL );
958 if( p_bodynode )
960 ttml_context_t context;
961 InitTTMLContext( p_rootnode, &context );
962 context.p_rootnode = p_rootnode;
964 vlc_dictionary_init( &context.regions, 1 );
965 ConvertNodesToRegionContent( &context, p_bodynode, NULL, NULL, playbacktime );
967 for( int i = 0; i < context.regions.i_size; ++i )
969 for ( const vlc_dictionary_entry_t* p_entry = context.regions.p_entries[i];
970 p_entry != NULL; p_entry = p_entry->p_next )
972 *pp_region_last = (ttml_region_t *) p_entry->p_value;
973 pp_region_last = (ttml_region_t **) &(*pp_region_last)->updt.p_next;
977 vlc_dictionary_clear( &context.regions, NULL, NULL );
980 else if ( !tt_node_NameCompare( p_rootnode->psz_node_name, "div" ) ||
981 !tt_node_NameCompare( p_rootnode->psz_node_name, "p" ) )
983 /* TODO */
986 return p_regions;
989 static int ParseBlock( decoder_t *p_dec, const block_t *p_block )
991 tt_time_t *p_timings_array = NULL;
992 size_t i_timings_count = 0;
994 /* We Only support absolute timings */
995 tt_timings_t temporal_extent;
996 temporal_extent.i_type = TT_TIMINGS_PARALLEL;
997 tt_time_Init( &temporal_extent.begin );
998 tt_time_Init( &temporal_extent.end );
999 tt_time_Init( &temporal_extent.dur );
1000 temporal_extent.begin.base = 0;
1002 if( p_block->i_flags & BLOCK_FLAG_CORRUPTED )
1003 return VLCDEC_SUCCESS;
1005 /* We cannot display a subpicture with no date */
1006 if( p_block->i_pts <= VLC_TS_INVALID )
1008 msg_Warn( p_dec, "subtitle without a date" );
1009 return VLCDEC_SUCCESS;
1012 tt_node_t *p_rootnode = ParseTTML( p_dec, p_block->p_buffer, p_block->i_buffer );
1013 if( !p_rootnode )
1014 return VLCDEC_SUCCESS;
1016 tt_timings_Resolve( (tt_basenode_t *) p_rootnode, &temporal_extent,
1017 &p_timings_array, &i_timings_count );
1019 #ifdef TTML_DEBUG
1020 for( size_t i=0; i<i_timings_count; i++ )
1021 printf("%ld ", tt_time_Convert( &p_timings_array[i] ) );
1022 printf("\n");
1023 #endif
1025 for( size_t i=0; i+1 < i_timings_count; i++ )
1027 /* We Only support absolute timings (2) */
1028 if( tt_time_Convert( &p_timings_array[i] ) + VLC_TS_0 < p_block->i_dts )
1029 continue;
1031 if( tt_time_Convert( &p_timings_array[i] ) + VLC_TS_0 > p_block->i_dts + p_block->i_length )
1032 break;
1034 subpicture_t *p_spu = NULL;
1035 ttml_region_t *p_regions = GenerateRegions( p_rootnode, p_timings_array[i] );
1036 if( p_regions && ( p_spu = decoder_NewSubpictureText( p_dec ) ) )
1038 p_spu->i_start = VLC_TS_0 + tt_time_Convert( &p_timings_array[i] );
1039 p_spu->i_stop = VLC_TS_0 + tt_time_Convert( &p_timings_array[i+1] ) - 1;
1040 p_spu->b_ephemer = true;
1041 p_spu->b_absolute = true;
1043 subpicture_updater_sys_t *p_spu_sys = p_spu->updater.p_sys;
1044 subpicture_updater_sys_region_t *p_updtregion = NULL;
1046 /* Create region update info from each ttml region */
1047 for( ttml_region_t *p_region = p_regions;
1048 p_region; p_region = (ttml_region_t *) p_region->updt.p_next )
1050 if( p_updtregion == NULL )
1052 p_updtregion = &p_spu_sys->region;
1054 else
1056 p_updtregion = SubpictureUpdaterSysRegionNew();
1057 if( p_updtregion == NULL )
1058 break;
1059 SubpictureUpdaterSysRegionAdd( &p_spu_sys->region, p_updtregion );
1062 /* broken legacy align var (can't handle center...) */
1063 if( p_dec->p_sys->i_align & SUBPICTURE_ALIGN_MASK )
1065 p_spu_sys->region.align = p_dec->p_sys->i_align & (SUBPICTURE_ALIGN_BOTTOM|SUBPICTURE_ALIGN_TOP);
1066 p_spu_sys->region.inner_align = p_dec->p_sys->i_align & (SUBPICTURE_ALIGN_LEFT|SUBPICTURE_ALIGN_RIGHT);
1068 p_spu_sys->margin_ratio = 0.0;
1070 /* copy and take ownership of pointeds */
1071 *p_updtregion = p_region->updt;
1072 p_updtregion->p_next = NULL;
1073 p_region->updt.p_region_style = NULL;
1074 p_region->updt.p_segments = NULL;
1079 /* cleanup */
1080 while( p_regions )
1082 ttml_region_t *p_nextregion = (ttml_region_t *) p_regions->updt.p_next;
1083 ttml_region_Delete( p_regions );
1084 p_regions = p_nextregion;
1087 if( p_spu )
1088 decoder_QueueSub( p_dec, p_spu );
1091 tt_node_RecursiveDelete( p_rootnode );
1093 free( p_timings_array );
1095 return VLCDEC_SUCCESS;
1100 /****************************************************************************
1101 * DecodeBlock: the whole thing
1102 ****************************************************************************/
1103 static int DecodeBlock( decoder_t *p_dec, block_t *p_block )
1105 if( p_block == NULL ) /* No Drain */
1106 return VLCDEC_SUCCESS;
1108 int ret = ParseBlock( p_dec, p_block );
1109 #ifdef TTML_DEBUG
1110 if( p_block->i_buffer )
1112 p_block->p_buffer[p_block->i_buffer - 1] = 0;
1113 msg_Dbg(p_dec,"time %ld %s", p_block->i_dts, p_block->p_buffer);
1115 #endif
1116 block_Release( p_block );
1117 return ret;
1120 /*****************************************************************************
1121 * tt_OpenDecoder: probe the decoder and return score
1122 *****************************************************************************/
1123 int tt_OpenDecoder( vlc_object_t *p_this )
1125 decoder_t *p_dec = (decoder_t*)p_this;
1126 decoder_sys_t *p_sys;
1128 if( p_dec->fmt_in.i_codec != VLC_CODEC_TTML )
1129 return VLC_EGENERIC;
1131 /* Allocate the memory needed to store the decoder's structure */
1132 p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
1133 if( unlikely( p_sys == NULL ) )
1134 return VLC_ENOMEM;
1136 p_dec->pf_decode = DecodeBlock;
1137 p_sys->i_align = var_InheritInteger( p_dec, "ttml-align" );
1139 return VLC_SUCCESS;
1142 /*****************************************************************************
1143 * tt_CloseDecoder: clean up the decoder
1144 *****************************************************************************/
1145 void tt_CloseDecoder( vlc_object_t *p_this )
1147 decoder_t *p_dec = (decoder_t *)p_this;
1148 decoder_sys_t *p_sys = p_dec->p_sys;
1150 free( p_sys );