Commit d86da185 authored by François Cartegnie's avatar François Cartegnie 🤞

decoder: ttml: rewrite and unify with demux

Following demux changes.
Now does style inheritance on the fly and
numerous other fixes.

Single module shared now with demux.
parent bc4c5726
......@@ -385,7 +385,6 @@ $Id$
* stream_out_transcode: audio & video transcoder
* subsdec: a codec to output textual subtitles
* subsdelay: subtitles delay filter
* substtml: TTML subtitles decoder
* substx3g: tx3g styled subtitles decoder
* subsusf: a demuxer for USF subtitles
* subtitle: a demuxer for subtitle files
......@@ -408,7 +407,7 @@ $Id$
* trivial_channel_mixer: Simple channel mixer plugin
* ts: MPEG-TS demuxer
* tta: Lossless True Audio parser
* ttml: a TTML subtitles demuxer
* ttml: a TTML subtitles demuxer and decoder
* twolame: a mp1 mp2 audio encoder based on twolame
* ty: TY demuxer
* udev: udev probing module
......
......@@ -215,8 +215,10 @@ codec_LTLIBRARIES += libsubsdec_plugin.la
libsubsusf_plugin_la_SOURCES = codec/subsusf.c
codec_LTLIBRARIES += libsubsusf_plugin.la
libsubsttml_plugin_la_SOURCES = codec/substtml.c
codec_LTLIBRARIES += libsubsttml_plugin.la
libttml_plugin_la_SOURCES = codec/ttml/substtml.c \
demux/ttml.c \
codec/ttml/ttml.h codec/ttml/ttml.c
codec_LTLIBRARIES += libttml_plugin.la
libsvcdsub_plugin_la_SOURCES = codec/svcdsub.c
codec_LTLIBRARIES += libsvcdsub_plugin.la
......
/*****************************************************************************
* substtml.c : TTML subtitles decoder
*****************************************************************************
* Copyright (C) 2015 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_plugin.h>
#include <vlc_modules.h>
#include <vlc_codec.h>
#include <vlc_xml.h>
#include <vlc_stream.h>
#include <vlc_text_style.h>
#include <vlc_charset.h>
#include "substext.h"
#include <ctype.h>
#define ALIGN_TEXT N_("Subtitle justification")
#define ALIGN_LONGTEXT N_("Set the justification of subtitles")
/*****************************************************************************
* Module descriptor.
*****************************************************************************/
static int OpenDecoder ( vlc_object_t * );
static void CloseDecoder ( vlc_object_t * );
static text_segment_t *ParseTTMLSubtitles( decoder_t *, subpicture_updater_sys_t *,
const uint8_t *, size_t );
vlc_module_begin ()
set_capability( "decoder", 10 )
set_shortname( N_("TTML decoder"))
set_description( N_("TTML subtitles decoder") )
set_callbacks( OpenDecoder, CloseDecoder )
set_category( CAT_INPUT )
set_subcategory( SUBCAT_INPUT_SCODEC )
add_integer( "ttml-align", 0, ALIGN_TEXT, ALIGN_LONGTEXT, false )
vlc_module_end ();
/*****************************************************************************
* Local prototypes
*****************************************************************************/
typedef struct
{
char* psz_styleid;
text_style_t* font_style;
int i_align;
int i_margin_h;
int i_margin_v;
int i_margin_percent_h;
int i_margin_percent_v;
int i_direction;
bool b_direction_set;
} ttml_style_t;
struct decoder_sys_t
{
int i_align;
ttml_style_t** pp_styles;
size_t i_styles;
};
enum
{
UNICODE_BIDI_LTR = 0,
UNICODE_BIDI_RTL = 1,
UNICODE_BIDI_EMBEDDED = 2,
UNICODE_BIDI_OVERRIDE = 4,
};
static int tagnamecmp( char const* tagname, char const* needle )
{
if( !strncasecmp( "tt:", tagname, 3 ) )
tagname += 3;
return strcasecmp( tagname, needle );
}
static void MergeTTMLStyle( ttml_style_t *p_dst, const ttml_style_t *p_src)
{
text_style_Merge( p_dst->font_style, p_src->font_style, false );
if( !( p_dst->i_align & SUBPICTURE_ALIGN_MASK ) )
p_dst->i_align |= p_src->i_align;
if( !p_dst->i_margin_h )
p_dst->i_margin_h = p_src->i_margin_h;
if( !p_dst->i_margin_v )
p_dst->i_margin_v = p_src->i_margin_v;
if( !p_dst->i_margin_percent_h )
p_dst->i_margin_percent_h = p_src->i_margin_percent_h;
if( !p_dst->i_margin_percent_v )
p_dst->i_margin_percent_v = p_src->i_margin_percent_v;
if( !p_dst->b_direction_set )
{
p_dst->i_direction = p_src->i_direction;
p_dst->b_direction_set = p_src->b_direction_set;
}
}
static ttml_style_t* DuplicateStyle( ttml_style_t* p_style_src )
{
ttml_style_t* p_style = calloc( 1, sizeof( *p_style ) );
if( unlikely( p_style == NULL ) )
return NULL;
*p_style = *p_style_src;
p_style->psz_styleid = strdup( p_style_src->psz_styleid );
if( unlikely( p_style->psz_styleid == NULL ) )
{
free( p_style );
return NULL;
}
p_style->font_style = text_style_Duplicate( p_style_src->font_style );
if( unlikely( p_style->font_style == NULL ) )
{
free( p_style->psz_styleid );
free( p_style );
return NULL;
}
return p_style;
}
static void CleanupStyle( ttml_style_t* p_ttml_style )
{
text_style_Delete( p_ttml_style->font_style );
free( p_ttml_style->psz_styleid );
free( p_ttml_style );
}
static ttml_style_t *FindTextStyle( decoder_t *p_dec, const char *psz_style )
{
decoder_sys_t *p_sys = p_dec->p_sys;
for( size_t i = 0; i < p_sys->i_styles; i++ )
{
if( !strcmp( p_sys->pp_styles[i]->psz_styleid, psz_style ) )
return DuplicateStyle( p_sys->pp_styles[i] );
}
return NULL;
}
typedef struct style_stack_t
{
ttml_style_t* p_style;
struct style_stack_t* p_next;
} style_stack_t ;
static bool PushStyle( style_stack_t **pp_stack, ttml_style_t* p_style )
{
style_stack_t* p_entry = malloc( sizeof( *p_entry ) );
if( unlikely( p_entry == NULL ) )
return false;
p_entry->p_style = p_style;
p_entry->p_next = *pp_stack;
*pp_stack = p_entry;
return true;
}
static void PopStyle( style_stack_t** pp_stack )
{
if( *pp_stack == NULL )
return;
style_stack_t* p_next = (*pp_stack)->p_next;
CleanupStyle( (*pp_stack)->p_style );
free( *pp_stack );
*pp_stack = p_next;
}
static void ClearStack( style_stack_t* p_stack )
{
while( p_stack != NULL )
{
style_stack_t* p_next = p_stack->p_next;
CleanupStyle( p_stack->p_style );
free( p_stack );
p_stack = p_next;
}
}
static text_style_t* CurrentStyle( style_stack_t* p_stack )
{
if( p_stack == NULL )
return text_style_Create( STYLE_NO_DEFAULTS );
return text_style_Duplicate( p_stack->p_style->font_style );
}
static ttml_style_t* ParseTTMLStyle( decoder_t *p_dec, xml_reader_t* p_reader, const char* psz_node_name )
{
decoder_sys_t* p_sys = p_dec->p_sys;
ttml_style_t *p_ttml_style = NULL;
ttml_style_t *p_base_style = NULL;
p_ttml_style = calloc( 1, sizeof( ttml_style_t ) );
if( unlikely( !p_ttml_style ) )
return NULL;
p_ttml_style->font_style = text_style_Create( STYLE_NO_DEFAULTS );
if( unlikely( !p_ttml_style->font_style ) )
{
free( p_ttml_style );
return NULL;
}
const char *attr, *val;
while( (attr = xml_ReaderNextAttr( p_reader, &val ) ) )
{
/* searching previous styles for inheritence */
if( !strcasecmp( attr, "style" ) || !strcasecmp( attr, "region" ) )
{
if( !tagnamecmp( psz_node_name, "style" ) || !tagnamecmp( psz_node_name, "region" ) )
{
for( size_t i = 0; i < p_sys->i_styles; i++ )
{
if( !strcasecmp( p_sys->pp_styles[i]->psz_styleid, val ) )
{
p_base_style = p_sys->pp_styles[i];
break;
}
}
}
/*
* In p nodes, style attribute has this format :
* style="style1 style2 style3" where style1 and style2 are
* style applied on the parents of p in that order.
*
* In span node, we can apply several styles in the same order than
* in p nodes with the same inheritance order.
*
* In order to preserve this style predominance, we merge the styles
* in the from right to left ( the right one being predominant ) .
*/
else if( !tagnamecmp( psz_node_name, "p" ) || !tagnamecmp( psz_node_name, "span" ) )
{
char *tmp;
char *value = strdup( val );
if( unlikely( value == NULL ) )
{
CleanupStyle( p_ttml_style );
return NULL;
}
char *token = strtok_r( value , " ", &tmp );
if( token == NULL )
{
msg_Warn( p_dec, "No IDREF specified in attribute "
"'%s' on tag '%s', ignoring.", attr,
psz_node_name );
free( value );
continue;
}
ttml_style_t* p_style = FindTextStyle( p_dec, token );
if( p_style == NULL )
{
msg_Warn( p_dec, "IDREF '%s' in '%s' not found", token, attr );
free( value );
break;
}
while( ( token = strtok_r( NULL, " ", &tmp) ) != NULL )
{
ttml_style_t* p_next_style = FindTextStyle( p_dec, token );
if( p_next_style == NULL )
{
msg_Warn( p_dec, "IDREF '%s' in '%s' not found", token, attr );
break;
}
MergeTTMLStyle( p_next_style, p_style );
CleanupStyle( p_style );
p_style = p_next_style;
}
MergeTTMLStyle( p_style, p_ttml_style );
free( value );
CleanupStyle( p_ttml_style );
p_ttml_style = p_style;
}
else
{
ttml_style_t* p_style = FindTextStyle( p_dec, val );
if( p_style == NULL )
{
msg_Warn( p_dec, "IDREF '%s' in '%s' not found", val, attr );
break;
}
MergeTTMLStyle( p_style , p_ttml_style );
CleanupStyle( p_ttml_style );
p_ttml_style = p_style;
}
}
else if( !strcasecmp( "xml:id", attr ) )
{
free( p_ttml_style->psz_styleid );
p_ttml_style->psz_styleid = strdup( val );
}
else if( !strcasecmp ( "tts:fontFamily", attr ) )
{
free( p_ttml_style->font_style->psz_fontname );
p_ttml_style->font_style->psz_fontname = strdup( val );
if( unlikely( p_ttml_style->font_style->psz_fontname == NULL ) )
{
CleanupStyle( p_ttml_style );
return NULL;
}
}
else if( !strcasecmp( "tts:opacity", attr ) )
{
p_ttml_style->font_style->i_background_alpha = atoi( val );
p_ttml_style->font_style->i_font_alpha = atoi( val );
p_ttml_style->font_style->i_features |= STYLE_HAS_BACKGROUND_ALPHA | STYLE_HAS_FONT_ALPHA;
}
else if( !strcasecmp( "tts:fontSize", attr ) )
{
char* psz_end = NULL;
float size = us_strtof( val, &psz_end );
if( *psz_end == '%' )
p_ttml_style->font_style->f_font_relsize = size;
else
p_ttml_style->font_style->i_font_size = (int)( size + 0.5 );
}
else if( !strcasecmp( "tts:color", attr ) )
{
unsigned int i_color = vlc_html_color( val, NULL );
p_ttml_style->font_style->i_font_color = (i_color & 0xffffff);
p_ttml_style->font_style->i_font_alpha = (i_color & 0xFF000000) >> 24;
p_ttml_style->font_style->i_features |= STYLE_HAS_FONT_COLOR | STYLE_HAS_FONT_ALPHA;
}
else if( !strcasecmp( "tts:backgroundColor", attr ) )
{
unsigned int i_color = vlc_html_color( val, NULL );
p_ttml_style->font_style->i_background_color = i_color & 0xFFFFFF;
p_ttml_style->font_style->i_background_alpha = (i_color & 0xFF000000) >> 24;
p_ttml_style->font_style->i_features |= STYLE_HAS_BACKGROUND_COLOR
| STYLE_HAS_BACKGROUND_ALPHA;
p_ttml_style->font_style->i_style_flags |= STYLE_BACKGROUND;
}
else if( !strcasecmp( "tts:textAlign", attr ) )
{
if( !strcasecmp ( "left", val ) )
p_ttml_style->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT;
else if( !strcasecmp ( "right", val ) )
p_ttml_style->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_RIGHT;
else if( !strcasecmp ( "center", val ) )
p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM;
else if( !strcasecmp ( "start", val ) )
p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
else if( !strcasecmp ( "end", val ) )
p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
}
else if( !strcasecmp( "tts:fontStyle", attr ) )
{
if( !strcasecmp ( "italic", val ) || !strcasecmp ( "oblique", val ) )
p_ttml_style->font_style->i_style_flags |= STYLE_ITALIC;
else
p_ttml_style->font_style->i_style_flags &= ~STYLE_ITALIC;
p_ttml_style->font_style->i_features |= STYLE_HAS_FLAGS;
}
else if( !strcasecmp ( "tts:fontWeight", attr ) )
{
if( !strcasecmp ( "bold", val ) )
p_ttml_style->font_style->i_style_flags |= STYLE_BOLD;
else
p_ttml_style->font_style->i_style_flags &= ~STYLE_BOLD;
p_ttml_style->font_style->i_features |= STYLE_HAS_FLAGS;
}
else if( !strcasecmp ( "tts:textDecoration", attr ) )
{
if( !strcasecmp ( "underline", val ) )
p_ttml_style->font_style->i_style_flags |= STYLE_UNDERLINE;
else if( !strcasecmp ( "noUnderline", val ) )
p_ttml_style->font_style->i_style_flags &= ~STYLE_UNDERLINE;
if( !strcasecmp ( "lineThrough", val ) )
p_ttml_style->font_style->i_style_flags |= STYLE_STRIKEOUT;
else if( !strcasecmp ( "noLineThrough", val ) )
p_ttml_style->font_style->i_style_flags &= ~STYLE_STRIKEOUT;
p_ttml_style->font_style->i_features |= STYLE_HAS_FLAGS;
}
else if( !strcasecmp ( "tts:origin", attr ) )
{
const char *psz_token = val;
while( isspace( *psz_token ) )
psz_token++;
const char *psz_separator = strchr( psz_token, ' ' );
if( psz_separator == NULL )
{
msg_Warn( p_dec, "Invalid origin attribute: \"%s\"", val );
continue;
}
const char *psz_percent_sign = strchr( psz_token, '%' );
if( psz_percent_sign != NULL && psz_percent_sign < psz_separator )
{
p_ttml_style->i_margin_h = 0;
p_ttml_style->i_margin_percent_h = atoi( psz_token );
}
else
{
p_ttml_style->i_margin_h = atoi( psz_token );
p_ttml_style->i_margin_percent_h = 0;
}
while( isspace( *psz_separator ) )
psz_separator++;
psz_token = psz_separator;
psz_percent_sign = strchr( psz_token, '%' );
if( psz_percent_sign != NULL )
{
p_ttml_style->i_margin_v = 0;
p_ttml_style->i_margin_percent_v = atoi( val );
}
else
{
p_ttml_style->i_margin_v = atoi( val );
p_ttml_style->i_margin_percent_v = 0;
}
}
else if( !strcasecmp( "tts:textOutline", attr ) )
{
char *value = strdup( val );
char* psz_saveptr = NULL;
char* token = strtok_r( value, " ", &psz_saveptr );
// <color>? <length> <length>?
bool b_ok = false;
unsigned int color = vlc_html_color( token, &b_ok );
if( b_ok )
{
p_ttml_style->font_style->i_outline_color = color & 0xFFFFFF;
p_ttml_style->font_style->i_outline_alpha = (color & 0xFF000000) >> 24;
token = strtok_r( NULL, " ", &psz_saveptr );
}
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_ttml_style->font_style->i_outline_width = i_outline_width;
}
free( value );
}
else if( !strcasecmp( "tts:direction", attr ) )
{
if( !strcasecmp( "rtl", val ) )
{
p_ttml_style->i_direction |= UNICODE_BIDI_RTL;
p_ttml_style->b_direction_set = true;
}
else if( !strcasecmp( "ltr", val ) )
{
p_ttml_style->i_direction |= UNICODE_BIDI_LTR;
p_ttml_style->b_direction_set = true;
}
}
else if( !strcasecmp( "tts:unicodeBidi", attr ) )
{
if( !strcasecmp( "bidiOverride", val ) )
p_ttml_style->i_direction |= UNICODE_BIDI_OVERRIDE & ~UNICODE_BIDI_EMBEDDED;
else if( !strcasecmp( "embed", val ) )
p_ttml_style->i_direction |= UNICODE_BIDI_EMBEDDED & ~UNICODE_BIDI_OVERRIDE;
}
else if( !strcasecmp( "tts:writingMode", attr ) )
{
if( !strcasecmp( "rl", val ) || !strcasecmp( "rltb", val ) )
{
p_ttml_style->i_direction = UNICODE_BIDI_RTL | UNICODE_BIDI_OVERRIDE;
p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_RIGHT;
p_ttml_style->b_direction_set = true;
}
else if( !strcasecmp( "lr", val ) || !strcasecmp( "lrtb", val ) )
{
p_ttml_style->i_direction = UNICODE_BIDI_LTR | UNICODE_BIDI_OVERRIDE;
p_ttml_style->i_align = SUBPICTURE_ALIGN_BOTTOM | SUBPICTURE_ALIGN_LEFT;
p_ttml_style->b_direction_set = true;
}
}
}
if( p_base_style != NULL )
{
MergeTTMLStyle( p_ttml_style, p_base_style );
}
if( p_ttml_style->psz_styleid == NULL )
{
CleanupStyle( p_ttml_style );
return NULL;
}
return p_ttml_style;
}
static void ParseTTMLStyles( decoder_t* p_dec )
{
stream_t* p_stream = vlc_stream_MemoryNew( p_dec, (uint8_t*)p_dec->fmt_in.p_extra, p_dec->fmt_in.i_extra, true );
if( unlikely( p_stream == NULL ) )
return ;
xml_reader_t* p_reader = xml_ReaderCreate( p_dec, p_stream );
if( unlikely( p_reader == NULL ) )
{
vlc_stream_Delete( p_stream );
return ;
}
const char* psz_node_name;
int i_type = xml_ReaderNextNode( p_reader, &psz_node_name );
if( i_type == XML_READER_STARTELEM && !tagnamecmp( psz_node_name, "tt" ) )
{
int i_type = xml_ReaderNextNode( p_reader, &psz_node_name );
while( i_type != XML_READER_STARTELEM || tagnamecmp( psz_node_name, "head" ) )
i_type = xml_ReaderNextNode( p_reader, &psz_node_name );
do
{
/* region and style tag are respectively inside layout and styling tags */
if( !tagnamecmp( psz_node_name, "styling" ) || !tagnamecmp( psz_node_name, "layout" ) )
{
i_type = xml_ReaderNextNode( p_reader, &psz_node_name );
while( i_type != XML_READER_ENDELEM )
{
ttml_style_t* p_ttml_style = ParseTTMLStyle( p_dec, p_reader, psz_node_name );
if ( p_ttml_style == NULL )
{
xml_ReaderDelete( p_reader );
vlc_stream_Delete( p_stream );
return;
}
decoder_sys_t* p_sys = p_dec->p_sys;
TAB_APPEND( p_sys->i_styles, p_sys->pp_styles, p_ttml_style );
i_type = xml_ReaderNextNode( p_reader, &psz_node_name );
}
}
i_type = xml_ReaderNextNode( p_reader, &psz_node_name );
}while( i_type != XML_READER_ENDELEM || tagnamecmp( psz_node_name, "head" ) );
}
xml_ReaderDelete( p_reader );
vlc_stream_Delete( p_stream );
}
static text_segment_t *ParseTTMLSubtitles( decoder_t *p_dec, subpicture_updater_sys_t *p_update_sys,
const uint8_t *p_buffer, size_t i_buffer )
{
stream_t* p_sub = NULL;
xml_reader_t* p_xml_reader = NULL;
text_segment_t* p_first_segment = NULL;
text_segment_t* p_current_segment = NULL;
style_stack_t* p_style_stack = NULL;
ttml_style_t* p_style = NULL;
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;
}
const char *node;
int i_type;
i_type = xml_ReaderNextNode( p_xml_reader, &node );
while( i_type != XML_READER_NONE && i_type > 0 )
{
/*
* We parse the styles and put them on the style stack
* until we reach a text node.
*/
if( i_type == XML_READER_STARTELEM && ( !tagnamecmp( node, "p") || !tagnamecmp( node, "span" ) ) )
{
p_style = ParseTTMLStyle( p_dec, p_xml_reader, node );
if( unlikely( p_style == NULL ) )
goto fail;
if( p_style_stack != NULL && p_style_stack->p_style != NULL )
MergeTTMLStyle( p_style, p_style_stack->p_style );
if( PushStyle( &p_style_stack, p_style ) == false )
{
CleanupStyle( p_style );
goto fail;
}
}
else if( i_type == XML_READER_TEXT )
{
/*
* Once we have a text node, we create a segment, apply the
* latest style put on the style stack and fill it with the
* content of the node.
*/
text_segment_t* p_segment = text_segment_New( NULL );
if( unlikely( p_segment == NULL ) )
goto fail;
p_segment->psz_text = strdup( node );
if( unlikely( p_segment->psz_text == NULL ) )
{
text_segment_Delete( p_segment );
goto fail;
}
vlc_xml_decode( p_segment->psz_text );
if( p_segment->style == NULL && p_style_stack == NULL )
{
p_segment->style = text_style_Create( STYLE_NO_DEFAULTS );
}
else if( p_segment->style == NULL )
{
p_segment->style = CurrentStyle( p_style_stack );
if( p_segment->style->f_font_relsize && !p_segment->style->i_font_size )
p_segment->style->i_font_size = (int)( ( p_segment->style->f_font_relsize * STYLE_DEFAULT_FONT_SIZE / 100 ) + 0.5 );
if( p_style_stack->p_style->i_margin_h )
p_update_sys->x = p_style_stack->p_style->i_margin_h;
else
p_update_sys->x = p_style_stack->p_style->i_margin_percent_h;
if( p_style_stack->p_style->i_margin_v )
p_update_sys->y = p_style_stack->p_style->i_margin_v;
else
p_update_sys->y = p_style_stack->p_style->i_margin_percent_v;
p_update_sys->align |= p_style_stack->p_style->i_align;
/*
* 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.
*/
int i_direction = p_style_stack->p_style->i_direction;
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( p_style_stack->p_style->b_direction_set )
{
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 )
{
text_segment_Delete( p_segment );
goto fail;
}
free( p_segment->psz_text );
p_segment->psz_text = psz_text;
}
}
if( p_first_segment == NULL )
{
p_first_segment = p_segment;
p_current_segment = p_segment;
}
else if( p_current_segment->psz_text != NULL )
{
p_current_segment->p_next = p_segment;