This source file includes following definitions.
- svg_reset_text_stack
- svg_update_bounds
- svg_finalize_sort
- svg_get_font_styles
- gf_compositor_svg_set_font
- svg_set_font
- svg_apply_text_anchor
- svg_get_text_span
- svg_text_area_reset_state
- svg_text_area_queue_state
- svg_text_area_apply_diff_baselines
- svg_traverse_dom_text_area
- get_domtext_width
- get_tspan_width
- svg_traverse_domtext
- svg_compute_text_width
- svg_traverse_text_block
- svg_text_draw_2d
- svg_text_area_shift_bounds
- svg_traverse_text
- compositor_init_svg_text
- svg_traverse_tspan
- compositor_init_svg_tspan
- svg_traverse_textArea
- compositor_init_svg_textarea
- svg_traverse_tbreak
- compositor_init_svg_tbreak
#include <gpac/utf.h>
#ifndef GPAC_DISABLE_SVG
#include "visual_manager.h"
#include "nodes_stacks.h"
typedef struct
{
Drawable *drawable;
Fixed prev_size;
u32 prev_flags;
u32 prev_anchor;
GF_List *spans;
GF_Rect bounds;
} SVG_TextStack;
static void svg_reset_text_stack(SVG_TextStack *st)
{
while (gf_list_count(st->spans)) {
GF_TextSpan *span = (GF_TextSpan*)gf_list_get(st->spans, 0);
gf_list_rem(st->spans, 0);
gf_font_manager_delete_span(NULL, span);
}
}
static void svg_update_bounds(SVG_TextStack *st)
{
u32 i=0;
GF_TextSpan *span;
st->bounds.width = st->bounds.height = 0;
st->bounds.x = st->bounds.y = 0;
while ( (span = (GF_TextSpan*)gf_list_enum(st->spans, &i)) ) {
gf_font_manager_refresh_span_bounds(span);
gf_rect_union(&st->bounds, &span->bounds);
}
}
static void svg_finalize_sort(DrawableContext *ctx, SVG_TextStack *st, GF_TraverseState * tr_state)
{
#ifndef GPAC_DISABLE_3D
if (tr_state->visual->type_3d) {
gf_font_spans_draw_3d(st->spans, tr_state, &ctx->aspect, 0, GF_FALSE);
drawable_check_focus_highlight(ctx->drawable->node, tr_state, &st->bounds);
ctx->drawable = NULL;
return;
}
#endif
if (!tr_state->immediate_draw &&
(tr_state->visual->compositor->text_selection
|| (tr_state->visual->compositor->store_text_state==GF_SC_TSEL_RELEASED))
) {
GF_TextSpan *span;
u32 i = 0;
Bool unselect = (tr_state->visual->compositor->store_text_state==GF_SC_TSEL_RELEASED) ? GF_TRUE : GF_FALSE;
while ((span = (GF_TextSpan*)gf_list_enum(st->spans, &i))) {
if (span->flags & GF_TEXT_SPAN_SELECTED) {
if (unselect) span->flags &= ~GF_TEXT_SPAN_SELECTED;
ctx->flags |= CTX_APP_DIRTY;
}
}
}
drawable_finalize_sort(ctx, tr_state, &st->bounds);
}
static u32 svg_get_font_styles(GF_TraverseState * tr_state)
{
u32 styles = 0;
switch(*tr_state->svg_props->font_style) {
case SVG_FONTSTYLE_ITALIC:
styles = GF_FONT_ITALIC;
break;
case SVG_FONTSTYLE_OBLIQUE:
styles = GF_FONT_OBLIQUE;
break;
}
if (*tr_state->svg_props->font_variant==SVG_FONTVARIANT_SMALLCAPS)
styles |= GF_FONT_SMALLCAPS;
switch(*tr_state->svg_props->font_weight) {
case SVG_FONTWEIGHT_100:
styles |= GF_FONT_WEIGHT_100;
break;
case SVG_FONTWEIGHT_LIGHTER:
styles |= GF_FONT_WEIGHT_LIGHTER;
break;
case SVG_FONTWEIGHT_200:
styles |= GF_FONT_WEIGHT_200;
break;
case SVG_FONTWEIGHT_300:
styles |= GF_FONT_WEIGHT_300;
break;
case SVG_FONTWEIGHT_400:
styles |= GF_FONT_WEIGHT_400;
break;
case SVG_FONTWEIGHT_NORMAL:
styles |= GF_FONT_WEIGHT_NORMAL;
break;
case SVG_FONTWEIGHT_500:
styles |= GF_FONT_WEIGHT_500;
break;
case SVG_FONTWEIGHT_600:
styles |= GF_FONT_WEIGHT_600;
break;
case SVG_FONTWEIGHT_700:
styles |= GF_FONT_WEIGHT_700;
break;
case SVG_FONTWEIGHT_BOLD:
styles |= GF_FONT_WEIGHT_BOLD;
break;
case SVG_FONTWEIGHT_800:
styles |= GF_FONT_WEIGHT_800;
break;
case SVG_FONTWEIGHT_900:
styles |= GF_FONT_WEIGHT_900;
break;
case SVG_FONTWEIGHT_BOLDER:
styles |= GF_FONT_WEIGHT_BOLDER;
break;
}
return styles;
}
GF_Font *gf_compositor_svg_set_font(GF_FontManager *fm, char *a_font, u32 styles, Bool check_only)
{
GF_Font *font = NULL;
char *fonts[50];
u32 nb_fonts = 0;
while (a_font) {
char *sep;
while (strchr("\t\r\n ", a_font[0])) a_font++;
sep = strchr(a_font, ',');
if (sep) sep[0] = 0;
if (a_font[0] == '\'') {
char *sep_end = strchr(a_font+1, '\'');
if (sep_end) sep_end[0] = 0;
a_font++;
fonts[nb_fonts] = gf_strdup(a_font);
nb_fonts++;
if (sep_end) sep_end[0] = '\'';
} else {
u32 skip = 0;
size_t len = strlen(a_font)-1;
while (a_font[len-skip] == ' ') skip++;
if (skip) a_font[len-skip+1] = 0;
fonts[nb_fonts] = gf_strdup(a_font);
nb_fonts++;
if (skip) a_font[len-skip] = ' ';
}
if (sep) {
sep[0] = ',';
a_font = sep+1;
} else {
a_font = NULL;
}
if (nb_fonts==50) break;
}
font = gf_font_manager_set_font_ex(fm, fonts, nb_fonts, styles, check_only);
while (nb_fonts) {
gf_free(fonts[nb_fonts-1]);
nb_fonts--;
}
return font;
}
static GF_Font *svg_set_font(GF_TraverseState * tr_state, GF_FontManager *fm)
{
return gf_compositor_svg_set_font(fm, tr_state->svg_props->font_family->value, svg_get_font_styles(tr_state), GF_FALSE);
}
static void svg_apply_text_anchor(GF_TraverseState * tr_state, Fixed *width)
{
Bool reversed = GF_FALSE;
if (!tr_state->svg_props->text_anchor) {
*width = 0;
return;
}
if (*width < 0) {
*width *= -1;
reversed = GF_TRUE;
}
switch(*tr_state->svg_props->text_anchor) {
case SVG_TEXTANCHOR_MIDDLE:
*width = -(*width)/2;
break;
case SVG_TEXTANCHOR_END:
*width = reversed ? 0 : -(*width);
break;
case SVG_TEXTANCHOR_START:
default:
*width = reversed ? -(*width) : 0;
break;
}
}
static GF_TextSpan *svg_get_text_span(GF_FontManager *fm, GF_Font *font, Fixed font_size, Bool x_offsets, Bool y_offsets, Bool rotate, SVGAllAttributes *atts, char *textContent, const char *lang, GF_TraverseState *tr_state)
{
GF_TextSpan *span = NULL;
char *dup_text;
u32 i, j, len;
char prev;
Bool preserve = (atts->xml_space && (*atts->xml_space==XML_SPACE_PRESERVE)) ? GF_TRUE : GF_FALSE;
len = (u32) strlen(textContent);
dup_text = (char*)gf_malloc(len+1);
switch (tr_state->last_char_type) {
case 2:
prev = 0;
break;
case 0:
case 1:
default:
prev = ' ';
break;
}
for (i = 0, j = 0; i < len; i++) {
if (textContent[i] == ' ') {
if (prev == ' ' && !preserve) {
} else {
dup_text[j] = textContent[i];
prev = dup_text[j];
j++;
}
} else if (textContent[i] == '\t') {
if (prev == ' ' && !preserve) {
} else {
dup_text[j] = ' ';
prev = dup_text[j];
j++;
}
} else if ((textContent[i] == '\n') ||
(textContent[i] == '\r')
) {
if (prev == ' ' && preserve) {
dup_text[j] = ' ';
prev = dup_text[j];
j++;
} else if (!i && !prev) {
prev = dup_text[j] = ' ';
j++;
}
} else if (
(((u8) textContent[i] == 0xc2) && ((u8) textContent[i+1] == 0xa0))
) {
if (prev == ' ' && !preserve) {
} else {
dup_text[j] = ' ';
prev = dup_text[j];
j++;
}
i++;
} else {
dup_text[j] = textContent[i];
prev = dup_text[j];
j++;
}
}
dup_text[j] = 0;
if (!j) tr_state->last_char_type = 1;
else tr_state->last_char_type = (dup_text[j-1]==' ') ? 1 : 2;
span = gf_font_manager_create_span(fm, font, dup_text, font_size, x_offsets, y_offsets, rotate, lang, GF_TRUE, 0, tr_state->text_parent);
gf_free(dup_text);
if (span) span->flags |= GF_TEXT_SPAN_HORIZONTAL;
return span;
}
typedef struct
{
GF_TextSpan *span;
u32 first_glyph, last_glyph;
} textArea_state;
static void svg_text_area_reset_state(GF_TraverseState *tr_state)
{
Fixed remain = 0;
u32 i, count;
count = gf_list_count(tr_state->x_anchors);
if (tr_state->svg_props->text_align && tr_state->text_end_x) {
switch(*tr_state->svg_props->text_align) {
case SVG_TEXTALIGN_CENTER:
remain = (tr_state->max_length - tr_state->text_end_x) / 2;
break;
case SVG_TEXTALIGN_END:
remain = tr_state->max_length - tr_state->text_end_x;
break;
default:
remain = 0;
break;
}
}
for (i=0; i<count; i++) {
textArea_state *st = (textArea_state*)gf_list_get(tr_state->x_anchors, i);
if (remain) {
u32 j;
for (j=st->first_glyph; j<st->last_glyph; j++) {
st->span->dx[j] += remain;
}
tr_state->refresh_children_bounds = 1;
}
gf_free(st);
}
gf_list_reset(tr_state->x_anchors);
}
static void svg_text_area_queue_state(GF_TraverseState *tr_state, GF_TextSpan *span, u32 first_glyph, u32 last_glyph)
{
textArea_state *st;
u32 i, count;
count = gf_list_count(tr_state->x_anchors);
for (i=0; i<count; i++) {
textArea_state *st = (textArea_state*)gf_list_get(tr_state->x_anchors, i);
if (st->span==span) {
st->last_glyph = last_glyph;
return;
}
}
st = (textArea_state*)gf_malloc(sizeof(textArea_state));
st->first_glyph = first_glyph;
st->last_glyph = last_glyph;
st->span = span;
gf_list_add(tr_state->x_anchors, st);
}
static void svg_text_area_apply_diff_baselines(GF_TraverseState *tr_state, Fixed diff)
{
u32 i, count, j;
count = gf_list_count(tr_state->x_anchors);
tr_state->refresh_children_bounds++;
for (i=0; i<count; i++) {
textArea_state *st = (textArea_state*)gf_list_get(tr_state->x_anchors, i);
for (j=st->first_glyph; j<st->last_glyph; j++) {
st->span->dy[j] += diff;
}
}
}
static void svg_traverse_dom_text_area(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state, GF_List *spans)
{
GF_DOMText *dom_text = (GF_DOMText *)node;
u32 word_start, word_end;
u32 i, j;
Fixed line_spacing;
GF_Font *font;
GF_FontManager *fm;
GF_TextSpan *span;
if (!dom_text->textContent) return;
if (tr_state->svg_props->font_size->value > tr_state->max_height) return;
fm = tr_state->visual->compositor->font_manager;
if (!fm) return;
font = svg_set_font(tr_state, fm);
if (!font) return;
span = svg_get_text_span(fm, font, tr_state->svg_props->font_size->value, GF_TRUE, GF_TRUE, GF_FALSE, atts, dom_text->textContent, atts->xml_lang ? *atts->xml_lang : NULL, tr_state);
if (!span) return;
if (tr_state->line_spacing==0) {
if (tr_state->svg_props->line_increment->type != SVG_NUMBER_AUTO) {
tr_state->line_spacing = tr_state->svg_props->line_increment->value;
} else {
tr_state->line_spacing = gf_mulfix(span->font_size, FLT2FIX(1.0) );
}
tr_state->text_end_y += tr_state->line_spacing;
}
line_spacing = gf_mulfix(span->font_size, FLT2FIX(1.0) );
word_start = word_end = 0;
i = 0;
while (i<span->nb_glyphs) {
Fixed word_size, last_char_size, offset, word_descent;
u32 break_glyph = 0;
word_descent = (-span->font->descent) * span->font_scale;
word_start = i;
word_size = last_char_size = 0;
while (i<span->nb_glyphs) {
Fixed glyph_size;
if (span->glyphs[i]) {
if ( (span->glyphs[i]->utf_name==' ') || (span->glyphs[i]->utf_name=='-') ) {
last_char_size = span->glyphs[i]->horiz_advance * span->font_scale;
i++;
break;
}
glyph_size = span->glyphs[i]->horiz_advance * span->font_scale;
if (word_size + glyph_size> tr_state->max_length) {
break_glyph = i;
break;
}
word_size += glyph_size;
} else {
word_size += font->max_advance_h * span->font_scale;
}
i++;
}
if (!word_size && !last_char_size) break;
if (tr_state->text_end_x + word_size > tr_state->max_length) {
if (word_size > tr_state->max_length) {
word_start=break_glyph;
break;
}
svg_text_area_reset_state(tr_state);
tr_state->text_end_x = 0;
tr_state->line_spacing = line_spacing;
tr_state->text_end_y += (tr_state->svg_props->line_increment->type == SVG_NUMBER_AUTO ? tr_state->line_spacing : tr_state->svg_props->line_increment->value);
if (tr_state->text_end_y > tr_state->max_height) break;
} else {
if (tr_state->text_end_y + word_descent > tr_state->max_height)
break;
if (line_spacing > tr_state->line_spacing) {
svg_text_area_apply_diff_baselines(tr_state, line_spacing - tr_state->line_spacing);
tr_state->text_end_y -= tr_state->line_spacing;
tr_state->text_end_y += line_spacing;
tr_state->line_spacing = line_spacing;
}
}
word_size += last_char_size;
offset = tr_state->base_x + tr_state->text_end_x;
for (j=word_start; j<i; j++) {
span->dx[j] = offset;
span->dy[j] = tr_state->base_y + tr_state->text_end_y;
offset += (span->glyphs[j] ? span->glyphs[j]->horiz_advance : font->max_advance_h) * span->font_scale;
}
tr_state->text_end_x += word_size;
svg_text_area_queue_state(tr_state, span, word_start, i);
word_start = i;
}
span->nb_glyphs = word_start;
gf_list_add(spans, span);
}
static void get_domtext_width(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state)
{
u32 i;
GF_Font *font;
Fixed block_width, *entry;
GF_FontManager *fm;
GF_TextSpan *span;
GF_DOMText *dom_text = (GF_DOMText *)node;
if (!dom_text->textContent) return;
fm = tr_state->visual->compositor->font_manager;
if (!fm) return;
font = svg_set_font(tr_state, fm);
if (!font) return;
span = svg_get_text_span(fm, font, tr_state->svg_props->font_size->value, (tr_state->count_x>1), (tr_state->count_y>1), GF_FALSE, atts, dom_text->textContent, atts->xml_lang ? *atts->xml_lang : NULL, tr_state);
if (!span) return;
i=0;
while ( (i<span->nb_glyphs)
&& ( (tr_state->count_x>1) || (tr_state->count_y>1) )
) {
block_width = (span->glyphs[i] ? span->glyphs[i]->horiz_advance : font->max_advance_h) * span->font_scale;
entry = (Fixed*)gf_malloc(sizeof(Fixed));
if (span->flags & GF_TEXT_SPAN_RIGHT_TO_LEFT) *entry = -block_width;
else *entry = block_width;
gf_list_add(tr_state->x_anchors, entry);
if (tr_state->count_x>0) tr_state->count_x--;
if (tr_state->count_y>0) tr_state->count_y--;
i++;
}
if (i<span->nb_glyphs) {
block_width = 0;
while (i<span->nb_glyphs) {
block_width += (span->glyphs[i] ? span->glyphs[i]->horiz_advance : font->max_advance_h) * span->font_scale;
i++;
}
if ((tr_state->count_x==1)||(tr_state->count_y==1)
|| !gf_list_count(tr_state->x_anchors) ) {
entry = (Fixed*)gf_malloc(sizeof(Fixed));
*entry = block_width;
if (span->flags & GF_TEXT_SPAN_RIGHT_TO_LEFT) *entry = -block_width;
else *entry = block_width;
gf_list_add(tr_state->x_anchors, entry);
} else {
Fixed *prec_lw = gf_list_last(tr_state->x_anchors);
(*prec_lw) += block_width;
}
if (tr_state->count_x==1) tr_state->count_x = 0;
if (tr_state->count_y==1) tr_state->count_y = 0;
}
gf_font_manager_delete_span(fm, span);
}
static void get_tspan_width(GF_Node *node, void *rs)
{
SVGPropertiesPointers backup_props;
u32 backup_flags;
GF_TraverseState *tr_state = (GF_TraverseState *)rs;
SVG_Element *tspan = (SVG_Element *)node;
SVGAllAttributes atts;
GF_ChildNodeItem *child;
gf_svg_flatten_attributes(tspan, &atts);
if (!compositor_svg_traverse_base(node, &atts, tr_state, &backup_props, &backup_flags))
return;
child = ((GF_ParentNode *) tspan)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_DOMText:
get_domtext_width(child->node, &atts, tr_state);
break;
case TAG_SVG_tspan:
get_tspan_width(child->node, tr_state);
break;
default:
break;
}
child=child->next;
}
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
tr_state->svg_flags = backup_flags;
}
void svg_traverse_domtext(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state, GF_List *spans, GF_Node *anchor_node)
{
GF_DOMText *dom_text = (GF_DOMText *)node;
Fixed x, y;
u32 i;
Fixed x_anchor, *ptr;
GF_Font *font;
Fixed block_width;
GF_FontManager *fm;
GF_TextSpan *span;
if (!dom_text->textContent) return;
if (tr_state->in_svg_text_area) {
svg_traverse_dom_text_area(node, atts, tr_state, spans);
return;
}
fm = tr_state->visual->compositor->font_manager;
if (!fm) return;
font = svg_set_font(tr_state, fm);
if (!font) return;
if (font->not_loaded) {
tr_state->visual->compositor->reset_fonts = GF_TRUE;
tr_state->visual->compositor->skip_flush = 1;
gf_sc_next_frame_state(tr_state->visual->compositor, GF_SC_DRAW_FRAME);
return;
}
span = svg_get_text_span(fm, font, tr_state->svg_props->font_size->value, (tr_state->count_x>1), (tr_state->count_y>1), tr_state->count_rotate, atts, dom_text->textContent, atts->xml_lang ? *atts->xml_lang : NULL, tr_state);
if (!span) return;
i=0;
while ((i<span->nb_glyphs)
&& ( (tr_state->count_x>1) || (tr_state->count_y>1) )
) {
if (tr_state->count_x==0) {
x = tr_state->text_end_x;
} else {
SVG_Coordinate *xc = (SVG_Coordinate *) gf_list_get(*tr_state->text_x, tr_state->chunk_index);
x = xc->value;
(tr_state->count_x)--;
}
if (tr_state->count_y==0) {
y = tr_state->text_end_y;
} else {
SVG_Coordinate *yc = (SVG_Coordinate *) gf_list_get(*tr_state->text_y, tr_state->chunk_index);
y = yc->value;
(tr_state->count_y)--;
}
ptr = (Fixed *)gf_list_get(tr_state->x_anchors, tr_state->chunk_index);
x_anchor = ptr ? *ptr : 0;
if (span->dx) span->dx[i] = x_anchor + x;
else if (!i) span->off_x = x_anchor + x;
if (span->dy) span->dy[i] = y;
else span->off_y = y;
if (tr_state->count_rotate) {
SVG_Coordinate *rc = (SVG_Coordinate *) gf_list_get(*tr_state->text_rotate, tr_state->idx_rotate);
span->rot[i] = gf_mulfix(GF_PI/180, rc->value);
if (tr_state->idx_rotate+1<tr_state->count_rotate) tr_state->idx_rotate++;
}
block_width = (span->glyphs[i] ? span->glyphs[i]->horiz_advance : font->max_advance_h) * span->font_scale;
tr_state->text_end_x = x+block_width;
tr_state->text_end_y = y;
(tr_state->chunk_index)++;
i++;
}
if (i<span->nb_glyphs) {
Fixed offset;
if ((tr_state->count_x==1) && tr_state->text_x) {
SVG_Coordinate *xc = (SVG_Coordinate *) gf_list_get(*tr_state->text_x, tr_state->chunk_index);
tr_state->text_end_x = xc->value;
(tr_state->count_x)--;
}
if ((tr_state->count_y==1) && tr_state->text_y) {
SVG_Coordinate *yc = (SVG_Coordinate *) gf_list_get(*tr_state->text_y, tr_state->chunk_index);
tr_state->text_end_y = yc->value;
(tr_state->count_y)--;
}
x = tr_state->text_end_x;
y = tr_state->text_end_y;
ptr = (Fixed *)gf_list_get(tr_state->x_anchors, tr_state->chunk_index);
x_anchor = ptr ? *ptr : 0;
offset = x_anchor + x - (span->dx ? span->dx[i] : span->off_x);
if (!span->dx && (tr_state->text_x || x_anchor)) span->off_x = x_anchor + x;
if (!span->dy && tr_state->text_y) span->off_y = y;
block_width = 0;
while (i<span->nb_glyphs) {
if (span->rot) {
SVG_Coordinate *rc = (SVG_Coordinate *) gf_list_get(*tr_state->text_rotate, tr_state->idx_rotate);
span->rot[i] = gf_mulfix(GF_PI/180, rc->value);
if (tr_state->idx_rotate+1<tr_state->count_rotate) tr_state->idx_rotate++;
}
if (span->dx) span->dx[i] = offset + block_width;
if (span->dy) span->dy[i] = y;
block_width += (span->glyphs[i] ? span->glyphs[i]->horiz_advance : font->max_advance_h) * span->font_scale;
i++;
}
tr_state->text_end_x += block_width;
}
gf_list_add(spans, span);
span->anchor = anchor_node;
}
static void svg_compute_text_width(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state )
{
GF_ChildNodeItem *child;
Bool is_switch = GF_FALSE;
switch (gf_node_get_tag(node)) {
case TAG_DOMText:
get_domtext_width(node, atts, tr_state);
break;
case TAG_SVG_tspan:
get_tspan_width(node, tr_state);
break;
case TAG_SVG_switch:
is_switch = GF_TRUE;
case TAG_SVG_a:
child = ((GF_ParentNode *)node)->children;
while (child) {
if (is_switch) {
SVGAllAttributes a_atts;
gf_svg_flatten_attributes((SVG_Element*)child->node, &a_atts);
if (compositor_svg_evaluate_conditional(tr_state->visual->compositor, &a_atts)) {
svg_compute_text_width(child->node, atts, tr_state);
break;
}
} else {
svg_compute_text_width(child->node, atts, tr_state);
}
child = child->next;
}
break;
default:
break;
}
}
static void svg_traverse_text_block(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state, GF_List *spans)
{
GF_ChildNodeItem *child;
Bool is_switch = GF_FALSE;
switch (gf_node_get_tag(node)) {
case TAG_DOMText:
svg_traverse_domtext(node, atts, tr_state, spans, NULL);
break;
case TAG_SVG_tspan:
gf_node_dirty_set(node, 0, GF_FALSE);
gf_node_traverse(node, tr_state);
break;
case TAG_SVG_switch:
is_switch = GF_TRUE;
case TAG_SVG_a:
child = ((GF_ParentNode *)node)->children;
while (child) {
if (is_switch) {
SVGAllAttributes a_atts;
gf_svg_flatten_attributes((SVG_Element*)child->node, &a_atts);
if (compositor_svg_evaluate_conditional(tr_state->visual->compositor, &a_atts)) {
svg_traverse_text_block(child->node, atts, tr_state, spans);
break;
}
} else if (gf_node_get_tag(child->node)==TAG_DOMText) {
svg_traverse_domtext(child->node, atts, tr_state, spans, node);
}
child = child->next;
}
break;
default:
break;
}
}
static void svg_text_draw_2d(SVG_TextStack *st, GF_TraverseState *tr_state)
{
gf_font_spans_draw_2d(st->spans, tr_state, 0, GF_FALSE, &st->bounds);
}
static void svg_text_area_shift_bounds(SVG_TextStack *st, GF_TraverseState *tr_state)
{
u32 i=0;
GF_TextSpan *span;
st->bounds.width = st->bounds.height = 0;
st->bounds.x = st->bounds.y = 0;
while ( (span = (GF_TextSpan*)gf_list_enum(st->spans, &i)) ) {
u32 j;
for (j=0; j<span->nb_glyphs; j++)
span->dy[j] += tr_state->base_shift;
gf_font_manager_refresh_span_bounds(span);
gf_rect_union(&st->bounds, &span->bounds);
}
}
static void svg_traverse_text(GF_Node *node, void *rs, Bool is_destroy)
{
SVGPropertiesPointers backup_props;
u32 backup_flags;
GF_Matrix2D backup_matrix;
GF_Matrix mx3d;
GF_ChildNodeItem *child;
DrawableContext *ctx;
SVG_TextStack *st = (SVG_TextStack *)gf_node_get_private(node);
GF_TraverseState *tr_state = (GF_TraverseState *)rs;
SVG_Element *text = (SVG_Element *)node;
SVGAllAttributes atts;
u32 i,imax;
Fixed * lw;
if (is_destroy) {
drawable_del(st->drawable);
svg_reset_text_stack(st);
gf_list_del(st->spans);
gf_free(st);
return;
}
if (tr_state->traversing_mode==TRAVERSE_DRAW_2D) {
svg_text_draw_2d(st, tr_state);
return;
}
else if (tr_state->traversing_mode==TRAVERSE_GET_TEXT) {
tr_state->text_parent = node;
gf_font_spans_get_selection(node, st->spans, tr_state);
child = ((GF_ParentNode *) text)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_SVG_tspan:
gf_node_traverse(child->node, tr_state);
break;
}
child = child->next;
}
tr_state->text_parent = NULL;
return;
}
gf_svg_flatten_attributes(text, &atts);
if (!compositor_svg_traverse_base(node, &atts, tr_state, &backup_props, &backup_flags))
return;
tr_state->in_svg_text++;
tr_state->text_parent = node;
if (tr_state->traversing_mode==TRAVERSE_PICK) {
compositor_svg_apply_local_transformation(tr_state, &atts, &backup_matrix, &mx3d);
if (*tr_state->svg_props->pointer_events!=SVG_POINTEREVENTS_NONE)
gf_font_spans_pick(node, st->spans, tr_state, &st->bounds, 1, st->drawable);
child = ((GF_ParentNode *) text)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_SVG_tspan:
gf_node_traverse(child->node, tr_state);
break;
}
child = child->next;
}
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx3d);
tr_state->svg_flags = backup_flags;
tr_state->text_parent = NULL;
tr_state->in_svg_text--;
return;
}
else if (tr_state->traversing_mode==TRAVERSE_GET_TEXT) {
gf_font_spans_get_selection(node, st->spans, tr_state);
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
tr_state->svg_flags = backup_flags;
tr_state->text_parent = NULL;
tr_state->in_svg_text--;
return;
}
compositor_svg_apply_local_transformation(tr_state, &atts, &backup_matrix, &mx3d);
if ( (st->prev_size != tr_state->svg_props->font_size->value) ||
(st->prev_flags != *tr_state->svg_props->font_style) ||
(st->prev_anchor != *tr_state->svg_props->text_anchor) ||
(gf_node_dirty_get(node) & (GF_SG_SVG_GEOMETRY_DIRTY | GF_SG_CHILD_DIRTY) )
|| tr_state->visual->compositor->reset_fonts
) {
u32 mode;
child = ((GF_ParentNode *) text)->children;
svg_reset_text_stack(st);
tr_state->text_end_x = 0;
tr_state->text_end_y = 0;
tr_state->last_char_type = 0;
if (atts.text_x) tr_state->count_x = gf_list_count(*atts.text_x);
else tr_state->count_x=0;
if (atts.text_y) tr_state->count_y = gf_list_count(*atts.text_y);
else tr_state->count_y=0;
if (atts.text_rotate) tr_state->count_rotate = gf_list_count(*atts.text_rotate);
else tr_state->count_rotate=0;
tr_state->x_anchors = gf_list_new();
while (child) {
svg_compute_text_width(child->node, &atts, tr_state);
child=child->next;
}
imax=gf_list_count(tr_state->x_anchors);
for (i=0; i<imax; i++) {
lw=gf_list_get(tr_state->x_anchors, i);
svg_apply_text_anchor(tr_state, lw);
}
if (atts.text_x) tr_state->count_x = gf_list_count(*atts.text_x);
else tr_state->count_x=0;
if (atts.text_y) tr_state->count_y = gf_list_count(*atts.text_y);
else tr_state->count_y=0;
if (atts.text_rotate) tr_state->count_rotate = gf_list_count(*atts.text_rotate);
else tr_state->count_rotate=0;
tr_state->idx_rotate = 0;
tr_state->chunk_index = 0;
if (!tr_state->text_end_x) {
SVG_Coordinate *xc = (atts.text_x ? (SVG_Coordinate *) gf_list_get(*atts.text_x, 0) : NULL);
tr_state->text_end_x = (xc ? xc->value : 0);
}
if (!tr_state->text_end_y) {
SVG_Coordinate *yc = (atts.text_y ? (SVG_Coordinate *) gf_list_get(*atts.text_y, 0) : NULL);
tr_state->text_end_y = (yc ? yc->value : 0);
}
tr_state->text_x = atts.text_x;
tr_state->text_y = atts.text_y;
tr_state->text_rotate = atts.text_rotate;
drawable_reset_path(st->drawable);
mode = tr_state->traversing_mode;
tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
tr_state->last_char_type = 0;
child = ((GF_ParentNode *) text)->children;
while (child) {
svg_traverse_text_block(child->node, &atts, tr_state, st->spans);
child = child->next;
}
tr_state->traversing_mode = mode;
gf_node_dirty_clear(node, 0);
drawable_mark_modified(st->drawable, tr_state);
st->prev_size = tr_state->svg_props->font_size->value;
st->prev_flags = *tr_state->svg_props->font_style;
st->prev_anchor = *tr_state->svg_props->text_anchor;
while (gf_list_count(tr_state->x_anchors)) {
Fixed *f = gf_list_last(tr_state->x_anchors);
gf_list_rem_last(tr_state->x_anchors);
gf_free(f);
}
gf_list_del(tr_state->x_anchors);
tr_state->x_anchors = NULL;
svg_update_bounds(st);
}
if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
if (!compositor_svg_is_display_off(tr_state->svg_props))
tr_state->bounds = st->bounds;
} else if ((tr_state->traversing_mode == TRAVERSE_SORT)
&& !compositor_svg_is_display_off(tr_state->svg_props)
&& (*(tr_state->svg_props->visibility) != SVG_VISIBILITY_HIDDEN)
) {
ctx = drawable_init_context_svg(st->drawable, tr_state);
if (ctx) svg_finalize_sort(ctx, st, tr_state);
child = ((GF_ParentNode *) text)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_SVG_tspan:
gf_node_traverse(child->node, tr_state);
break;
case TAG_SVG_switch:
gf_node_traverse(child->node, tr_state);
break;
}
child = child->next;
}
}
tr_state->in_svg_text--;
tr_state->text_parent = NULL;
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx3d);
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
tr_state->svg_flags = backup_flags;
}
void compositor_init_svg_text(GF_Compositor *compositor, GF_Node *node)
{
SVG_TextStack *stack;
GF_SAFEALLOC(stack, SVG_TextStack);
if (!stack) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg text stack\n"));
return;
}
stack->drawable = drawable_new();
stack->drawable->node = node;
stack->drawable->flags = DRAWABLE_USE_TRAVERSE_DRAW;
stack->spans = gf_list_new();
gf_node_set_private(node, stack);
gf_node_set_callback_function(node, svg_traverse_text);
}
static void svg_traverse_tspan(GF_Node *node, void *rs, Bool is_destroy)
{
SVGPropertiesPointers backup_props;
u32 backup_flags;
GF_Matrix2D backup_matrix;
GF_Matrix mx3d;
DrawableContext *ctx;
SVG_TextStack *st = (SVG_TextStack *)gf_node_get_private(node);
GF_TraverseState *tr_state = (GF_TraverseState *)rs;
SVG_Element *tspan = (SVG_Element *)node;
SVGAllAttributes atts;
GF_ChildNodeItem *child;
if (is_destroy) {
drawable_del(st->drawable);
svg_reset_text_stack(st);
gf_list_del(st->spans);
gf_free(st);
return;
}
if (tr_state->traversing_mode==TRAVERSE_DRAW_2D) {
svg_text_draw_2d(st, tr_state);
return;
}
else if (tr_state->traversing_mode==TRAVERSE_GET_TEXT) {
gf_font_spans_get_selection(node, st->spans, tr_state);
child = ((GF_ParentNode *) tspan)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_SVG_tspan:
gf_node_traverse(child->node, tr_state);
break;
}
child = child->next;
}
return;
}
if (!tr_state->in_svg_text && !tr_state->in_svg_text_area) return;
gf_svg_flatten_attributes(tspan, &atts);
if (!compositor_svg_traverse_base(node, &atts, tr_state, &backup_props, &backup_flags))
return;
if (tr_state->traversing_mode==TRAVERSE_PICK) {
if (*tr_state->svg_props->pointer_events!=SVG_POINTEREVENTS_NONE)
gf_font_spans_pick(node, st->spans, tr_state, &st->bounds, GF_TRUE, st->drawable);
child = ((GF_ParentNode *) tspan)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_SVG_tspan:
gf_node_traverse(child->node, tr_state);
break;
}
child = child->next;
}
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
tr_state->svg_flags = backup_flags;
return;
}
compositor_svg_apply_local_transformation(tr_state, &atts, &backup_matrix, &mx3d);
if ( (st->prev_size != tr_state->svg_props->font_size->value) ||
(st->prev_flags != *tr_state->svg_props->font_style) ||
(st->prev_anchor != *tr_state->svg_props->text_anchor) ||
(gf_node_dirty_get(node) & (GF_SG_SVG_GEOMETRY_DIRTY | GF_SG_CHILD_DIRTY) )
) {
u32 mode;
if (tr_state->traversing_mode==TRAVERSE_SORT) {
gf_node_dirty_set(node, 0, GF_TRUE);
goto skip_changes;
}
mode = tr_state->traversing_mode;
tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
svg_reset_text_stack(st);
child = ((GF_ParentNode *) tspan)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_DOMText:
svg_traverse_domtext(child->node, &atts, tr_state, st->spans, NULL);
break;
case TAG_SVG_tspan:
gf_node_dirty_set(child->node, 0, GF_FALSE);
gf_node_traverse(child->node, tr_state);
break;
case TAG_SVG_switch:
case TAG_SVG_a:
case TAG_SVG_tbreak:
gf_node_traverse(child->node, tr_state);
break;
default:
break;
}
child = child->next;
}
tr_state->traversing_mode = mode;
gf_node_dirty_clear(node, 0);
drawable_mark_modified(st->drawable, tr_state);
st->prev_size = tr_state->svg_props->font_size->value;
st->prev_flags = *tr_state->svg_props->font_style;
st->prev_anchor = *tr_state->svg_props->text_anchor;
svg_update_bounds(st);
}
skip_changes:
if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
if (tr_state->refresh_children_bounds) {
if (tr_state->base_shift)
svg_text_area_shift_bounds(st, tr_state);
else
svg_update_bounds(st);
child = ((GF_ParentNode *) tspan)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_SVG_tspan:
case TAG_SVG_switch:
case TAG_SVG_a:
gf_node_traverse(child->node, tr_state);
break;
default:
break;
}
child = child->next;
}
}
if (!compositor_svg_is_display_off(tr_state->svg_props))
tr_state->bounds = st->bounds;
}
else if (
(tr_state->traversing_mode == TRAVERSE_SORT)
&& !compositor_svg_is_display_off(tr_state->svg_props)
&& ( *(tr_state->svg_props->visibility) != SVG_VISIBILITY_HIDDEN)
) {
child = ((GF_ParentNode *) tspan)->children;
ctx = drawable_init_context_svg(st->drawable, tr_state);
if (ctx) svg_finalize_sort(ctx, st, tr_state);
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_SVG_tspan:
case TAG_SVG_switch:
case TAG_SVG_a:
gf_node_traverse(child->node, tr_state);
break;
default:
break;
}
child = child->next;
}
}
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx3d);
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
tr_state->svg_flags = backup_flags;
}
void compositor_init_svg_tspan(GF_Compositor *compositor, GF_Node *node)
{
SVG_TextStack *stack;
GF_SAFEALLOC(stack, SVG_TextStack);
if (!stack) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg tspan stack\n"));
return;
}
stack->drawable = drawable_new();
stack->drawable->node = node;
stack->drawable->flags = DRAWABLE_USE_TRAVERSE_DRAW;
stack->spans = gf_list_new();
gf_node_set_private(node, stack);
gf_node_set_callback_function(node, svg_traverse_tspan);
}
static void svg_traverse_textArea(GF_Node *node, void *rs, Bool is_destroy)
{
SVGPropertiesPointers backup_props;
u32 backup_flags;
GF_Matrix mx3d;
GF_Matrix2D backup_matrix;
DrawableContext *ctx = NULL;
GF_ChildNodeItem *child;
SVG_TextStack *st = (SVG_TextStack *)gf_node_get_private(node);
GF_TraverseState *tr_state = (GF_TraverseState *)rs;
SVG_Element *text = (SVG_Element *)node;
SVGAllAttributes atts;
if (is_destroy) {
drawable_del(st->drawable);
svg_reset_text_stack(st);
gf_list_del(st->spans);
gf_free(st);
return;
}
if (tr_state->traversing_mode==TRAVERSE_DRAW_2D) {
svg_text_draw_2d(st, tr_state);
return;
}
else if (tr_state->traversing_mode==TRAVERSE_GET_TEXT) {
tr_state->text_parent = node;
gf_font_spans_get_selection(node, st->spans, tr_state);
child = ((GF_ParentNode *) text)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_SVG_tspan:
case TAG_SVG_a:
gf_node_traverse(child->node, tr_state);
break;
}
child = child->next;
}
tr_state->text_parent = NULL;
return;
}
gf_svg_flatten_attributes(text, &atts);
if (!compositor_svg_traverse_base(node, &atts, tr_state, &backup_props, &backup_flags))
return;
tr_state->text_parent = node;
tr_state->in_svg_text_area++;
if (tr_state->traversing_mode==TRAVERSE_PICK) {
if (*tr_state->svg_props->pointer_events!=SVG_POINTEREVENTS_NONE) {
compositor_svg_apply_local_transformation(tr_state, &atts, &backup_matrix, &mx3d);
gf_font_spans_pick(node, st->spans, tr_state, &st->bounds, GF_TRUE, st->drawable);
child = ((GF_ParentNode *) node)->children;
while (child) {
gf_node_traverse(child->node, tr_state);
child = child->next;
}
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx3d);
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
tr_state->svg_flags = backup_flags;
}
tr_state->in_svg_text_area--;
tr_state->text_parent = NULL;
return;
}
compositor_svg_apply_local_transformation(tr_state, &atts, &backup_matrix, &mx3d);
if ( (st->prev_size != tr_state->svg_props->font_size->value) ||
(st->prev_flags != *tr_state->svg_props->font_style) ||
(st->prev_anchor != *tr_state->svg_props->text_anchor) ||
(gf_node_dirty_get(node) & (GF_SG_SVG_GEOMETRY_DIRTY | GF_SG_CHILD_DIRTY) )
|| tr_state->visual->compositor->reset_fonts
) {
u32 mode;
svg_reset_text_stack(st);
gf_node_dirty_clear(node, 0);
drawable_mark_modified(st->drawable, tr_state);
drawable_reset_path(st->drawable);
tr_state->max_length = (atts.width ? (atts.width->type == SVG_NUMBER_AUTO ? FIX_MAX : atts.width->value) : FIX_MAX);
tr_state->max_height = (atts.height ? (atts.height->type == SVG_NUMBER_AUTO ? FIX_MAX : atts.height->value) : FIX_MAX);
tr_state->base_x = (atts.x ? atts.x->value : 0);
tr_state->base_y = (atts.y ? atts.y->value : 0);
tr_state->last_char_type = 0;
tr_state->line_spacing = 0;
tr_state->text_end_x = 0;
tr_state->text_end_y = (tr_state->svg_props->line_increment->type == SVG_NUMBER_AUTO ? 0 : tr_state->svg_props->line_increment->value);
tr_state->x_anchors = gf_list_new();
if (tr_state->svg_props->font_size && (tr_state->svg_props->font_size->value <= tr_state->max_height)) {
Fixed remain;
u32 c, refresh_to_idx, prev_refresh;
mode = tr_state->traversing_mode;
tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
prev_refresh = tr_state->refresh_children_bounds;
tr_state->refresh_children_bounds = 0;
c = refresh_to_idx = 0;
child = ((GF_ParentNode *) text)->children;
while (child) {
c++;
switch (gf_node_get_tag(child->node)) {
case TAG_DOMText:
svg_traverse_dom_text_area(child->node, &atts, tr_state, st->spans);
break;
case TAG_SVG_tspan:
gf_node_dirty_set(child->node, 0, GF_FALSE);
gf_node_traverse(child->node, tr_state);
break;
case TAG_SVG_switch:
case TAG_SVG_a:
case TAG_SVG_tbreak:
gf_node_traverse(child->node, tr_state);
break;
default:
break;
}
if (tr_state->refresh_children_bounds) {
tr_state->refresh_children_bounds=0;
refresh_to_idx=c;
}
child=child->next;
}
st->prev_size = tr_state->svg_props->font_size->value;
st->prev_flags = *tr_state->svg_props->font_style;
st->prev_anchor = *tr_state->svg_props->text_anchor;
svg_text_area_reset_state(tr_state);
gf_list_del(tr_state->x_anchors);
tr_state->x_anchors = NULL;
if (tr_state->refresh_children_bounds) {
refresh_to_idx = (u32) -1;
tr_state->base_shift = 0;
}
if (tr_state->svg_props->display_align) {
switch (*tr_state->svg_props->display_align) {
case SVG_DISPLAYALIGN_CENTER:
remain = (tr_state->max_height-tr_state->text_end_y) / 2;
break;
case SVG_DISPLAYALIGN_AFTER:
remain = tr_state->max_height - tr_state->text_end_y;
break;
default:
remain = 0;
break;
}
if (remain<0) remain=0;
if (remain) {
refresh_to_idx = (u32) -1;
tr_state->base_shift = remain;
svg_text_area_shift_bounds(st, tr_state);
}
}
if (refresh_to_idx) {
tr_state->refresh_children_bounds=1;
child = ((GF_ParentNode *) text)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_DOMText:
break;
case TAG_SVG_tspan:
case TAG_SVG_switch:
case TAG_SVG_a:
gf_node_traverse(child->node, tr_state);
break;
default:
break;
}
child=child->next;
refresh_to_idx--;
if (!refresh_to_idx) break;
}
tr_state->base_shift = 0;
}
tr_state->traversing_mode = mode;
tr_state->refresh_children_bounds = prev_refresh;
}
svg_update_bounds(st);
}
if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
if (!compositor_svg_is_display_off(tr_state->svg_props))
tr_state->bounds = st->bounds;
} else if ( (tr_state->traversing_mode == TRAVERSE_SORT)
&& !compositor_svg_is_display_off(tr_state->svg_props)
&& (*(tr_state->svg_props->visibility) != SVG_VISIBILITY_HIDDEN)
) {
ctx = drawable_init_context_svg(st->drawable, tr_state);
if (ctx) svg_finalize_sort(ctx, st, tr_state);
child = ((GF_ParentNode *) text)->children;
while (child) {
switch (gf_node_get_tag(child->node)) {
case TAG_DOMText:
break;
case TAG_SVG_tspan:
case TAG_SVG_switch:
case TAG_SVG_a:
gf_node_traverse(child->node, tr_state);
break;
default:
break;
}
child = child->next;
}
}
tr_state->in_svg_text_area--;
tr_state->text_parent = NULL;
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx3d);
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
tr_state->svg_flags = backup_flags;
}
void compositor_init_svg_textarea(GF_Compositor *compositor, GF_Node *node)
{
SVG_TextStack *stack;
GF_SAFEALLOC(stack, SVG_TextStack);
if (!stack) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg textarea stack\n"));
return;
}
stack->drawable = drawable_new();
stack->drawable->node = node;
stack->drawable->flags = DRAWABLE_USE_TRAVERSE_DRAW;
stack->spans = gf_list_new();
gf_node_set_private(node, stack);
gf_node_set_callback_function(node, svg_traverse_textArea);
}
static void svg_traverse_tbreak(GF_Node *node, void *rs, Bool is_destroy)
{
SVGPropertiesPointers backup_props;
u32 backup_flags;
GF_TraverseState *tr_state = (GF_TraverseState *)rs;
SVGAllAttributes atts;
if (is_destroy) return;
if (tr_state->traversing_mode!=TRAVERSE_GET_BOUNDS) return;
gf_svg_flatten_attributes((SVG_Element*)node, &atts);
if (!compositor_svg_traverse_base(node, &atts, tr_state, &backup_props, &backup_flags))
return;
svg_text_area_reset_state(tr_state);
if (!tr_state->text_end_x) {
if (tr_state->svg_props->line_increment->type != SVG_NUMBER_AUTO) {
tr_state->text_end_y += tr_state->svg_props->line_increment->value;
} else {
tr_state->text_end_y += tr_state->svg_props->font_size->value;
}
}
tr_state->line_spacing = 0;
tr_state->text_end_x = 0;
tr_state->last_char_type = 0;
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
tr_state->svg_flags = backup_flags;
}
void compositor_init_svg_tbreak(GF_Compositor *compositor, GF_Node *node)
{
gf_node_set_callback_function(node, svg_traverse_tbreak);
}
#endif