Coverage Report

Created: 2026-01-17 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpac/src/compositor/svg_text.c
Line
Count
Source
1
/*
2
 *      GPAC - Multimedia Framework C SDK
3
 *
4
 *      Authors: Cyril Concolato
5
 *      Copyright (c) Telecom ParisTech 2004-2023
6
 *          All rights reserved
7
 *
8
 *  This file is part of GPAC / Scene Compositor sub-project
9
 *
10
 *  GPAC is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU Lesser General Public License as published by
12
 *  the Free Software Foundation; either version 2, or (at your option)
13
 *  any later version.
14
 *
15
 *  GPAC is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU Lesser General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU Lesser General Public
21
 *  License along with this library; see the file COPYING.  If not, write to
22
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
 *
24
 */
25
26
#include <gpac/utf.h>
27
28
#if !defined(GPAC_DISABLE_SVG) &&  !defined(GPAC_DISABLE_COMPOSITOR)
29
30
#include "visual_manager.h"
31
#include "nodes_stacks.h"
32
33
typedef struct
34
{
35
  Drawable *drawable;
36
  Fixed prev_size;
37
  u32 prev_flags;
38
  u32 prev_anchor;
39
  GF_List *spans;
40
  GF_Rect bounds;
41
} SVG_TextStack;
42
43
static void svg_reset_text_stack(SVG_TextStack *st)
44
0
{
45
0
  while (gf_list_count(st->spans)) {
46
0
    GF_TextSpan *span = (GF_TextSpan*)gf_list_get(st->spans, 0);
47
0
    gf_list_rem(st->spans, 0);
48
0
    gf_font_manager_delete_span(NULL, span);
49
0
  }
50
0
}
51
52
static void svg_update_bounds(SVG_TextStack *st)
53
0
{
54
0
  u32 i=0;
55
0
  GF_TextSpan *span;
56
  /*finally compute text bounds*/
57
0
  st->bounds.width = st->bounds.height = 0;
58
0
  st->bounds.x = st->bounds.y = 0;
59
0
  while ( (span = (GF_TextSpan*)gf_list_enum(st->spans, &i)) ) {
60
0
    gf_font_manager_refresh_span_bounds(span);
61
0
    gf_rect_union(&st->bounds, &span->bounds);
62
0
  }
63
0
}
64
65
static void svg_finalize_sort(DrawableContext *ctx, SVG_TextStack *st, GF_TraverseState * tr_state)
66
0
{
67
#ifndef GPAC_DISABLE_3D
68
  if (tr_state->visual->type_3d) {
69
    gf_font_spans_draw_3d(st->spans, tr_state, &ctx->aspect, 0, GF_FALSE);
70
71
    drawable_check_focus_highlight(ctx->drawable->node, tr_state, &st->bounds);
72
    ctx->drawable = NULL;
73
    return;
74
  }
75
#endif
76
  /*if text selection mode, we must force redraw of the entire text span because we don't
77
  if glyphs have been (un)selected*/
78
0
  if (!tr_state->immediate_draw &&
79
          /*text selection on*/
80
0
          (tr_state->visual->compositor->text_selection
81
           /*text sel release*/
82
0
           || (tr_state->visual->compositor->store_text_state==GF_SC_TSEL_RELEASED))
83
0
     ) {
84
0
    GF_TextSpan *span;
85
0
    u32 i = 0;
86
0
    Bool unselect = (tr_state->visual->compositor->store_text_state==GF_SC_TSEL_RELEASED) ? GF_TRUE : GF_FALSE;
87
0
    while ((span = (GF_TextSpan*)gf_list_enum(st->spans, &i))) {
88
0
      if (span->flags & GF_TEXT_SPAN_SELECTED) {
89
0
        if (unselect) span->flags &= ~GF_TEXT_SPAN_SELECTED;
90
0
        ctx->flags |= CTX_APP_DIRTY;
91
0
      }
92
0
    }
93
0
  }
94
0
  drawable_finalize_sort(ctx, tr_state, &st->bounds);
95
0
}
96
97
/*@styles indicates font styles (PLAIN, BOLD, ITALIC, BOLDITALIC and UNDERLINED, STRIKEOUT)*/
98
static u32 svg_get_font_styles(GF_TraverseState * tr_state)
99
0
{
100
0
  u32 styles = 0;
101
0
  switch(*tr_state->svg_props->font_style) {
102
0
  case SVG_FONTSTYLE_ITALIC:
103
0
    styles = GF_FONT_ITALIC;
104
0
    break;
105
0
  case SVG_FONTSTYLE_OBLIQUE:
106
0
    styles = GF_FONT_OBLIQUE;
107
0
    break;
108
0
  }
109
0
  if (*tr_state->svg_props->font_variant==SVG_FONTVARIANT_SMALLCAPS)
110
0
    styles |= GF_FONT_SMALLCAPS;
111
112
0
  switch(*tr_state->svg_props->font_weight) {
113
0
  case SVG_FONTWEIGHT_100:
114
0
    styles |= GF_FONT_WEIGHT_100;
115
0
    break;
116
0
  case SVG_FONTWEIGHT_LIGHTER:
117
0
    styles |= GF_FONT_WEIGHT_LIGHTER;
118
0
    break;
119
0
  case SVG_FONTWEIGHT_200:
120
0
    styles |= GF_FONT_WEIGHT_200;
121
0
    break;
122
0
  case SVG_FONTWEIGHT_300:
123
0
    styles |= GF_FONT_WEIGHT_300;
124
0
    break;
125
0
  case SVG_FONTWEIGHT_400:
126
0
    styles |= GF_FONT_WEIGHT_400;
127
0
    break;
128
0
  case SVG_FONTWEIGHT_NORMAL:
129
0
    styles |= GF_FONT_WEIGHT_NORMAL;
130
0
    break;
131
0
  case SVG_FONTWEIGHT_500:
132
0
    styles |= GF_FONT_WEIGHT_500;
133
0
    break;
134
0
  case SVG_FONTWEIGHT_600:
135
0
    styles |= GF_FONT_WEIGHT_600;
136
0
    break;
137
0
  case SVG_FONTWEIGHT_700:
138
0
    styles |= GF_FONT_WEIGHT_700;
139
0
    break;
140
0
  case SVG_FONTWEIGHT_BOLD:
141
0
    styles |= GF_FONT_WEIGHT_BOLD;
142
0
    break;
143
0
  case SVG_FONTWEIGHT_800:
144
0
    styles |= GF_FONT_WEIGHT_800;
145
0
    break;
146
0
  case SVG_FONTWEIGHT_900:
147
0
    styles |= GF_FONT_WEIGHT_900;
148
0
    break;
149
0
  case SVG_FONTWEIGHT_BOLDER:
150
0
    styles |= GF_FONT_WEIGHT_BOLDER;
151
0
    break;
152
0
  }
153
154
0
  return styles;
155
0
}
156
157
158
GF_Font *gf_compositor_svg_set_font(GF_FontManager *fm, char *a_font, u32 styles, Bool check_only)
159
0
{
160
0
  GF_Font *font = NULL;
161
0
  char *fonts[50];
162
0
  u32 nb_fonts = 0;
163
164
0
  while (a_font) {
165
0
    char *sep;
166
0
    while (strchr("\t\r\n ", a_font[0])) a_font++;
167
168
0
    sep = strchr(a_font, ',');
169
0
    if (sep) sep[0] = 0;
170
171
0
    if (a_font[0] == '\'') {
172
0
      char *sep_end = strchr(a_font+1, '\'');
173
0
      if (sep_end) sep_end[0] = 0;
174
0
      a_font++;
175
0
      fonts[nb_fonts] = gf_strdup(a_font);
176
0
      nb_fonts++;
177
0
      if (sep_end) sep_end[0] = '\'';
178
0
    } else {
179
0
      u32 skip = 0;
180
0
      size_t len = strlen(a_font)-1;
181
182
0
      while (a_font[len-skip] == ' ') skip++;
183
0
      if (skip) a_font[len-skip+1] = 0;
184
0
      fonts[nb_fonts] = gf_strdup(a_font);
185
0
      nb_fonts++;
186
0
      if (skip) a_font[len-skip] = ' ';
187
0
    }
188
189
0
    if (sep) {
190
0
      sep[0] = ',';
191
0
      a_font = sep+1;
192
0
    } else {
193
0
      a_font = NULL;
194
0
    }
195
0
    if (nb_fonts==50) break;
196
0
  }
197
0
  font = gf_font_manager_set_font_ex(fm, fonts, nb_fonts, styles, check_only);
198
0
  while (nb_fonts) {
199
0
    gf_free(fonts[nb_fonts-1]);
200
0
    nb_fonts--;
201
0
  }
202
0
  return font;
203
0
}
204
205
static GF_Font *svg_set_font(GF_TraverseState * tr_state, GF_FontManager *fm)
206
0
{
207
0
  return gf_compositor_svg_set_font(fm, tr_state->svg_props->font_family->value, svg_get_font_styles(tr_state), GF_FALSE);
208
0
}
209
210
211
212
static void svg_apply_text_anchor(GF_TraverseState * tr_state, Fixed *width)
213
0
{
214
0
  Bool reversed = GF_FALSE;
215
0
  if (!tr_state->svg_props->text_anchor) {
216
0
    *width = 0;
217
0
    return;
218
0
  }
219
0
  if (*width < 0) {
220
0
    *width *= -1;
221
0
    reversed = GF_TRUE;
222
0
  }
223
0
  switch(*tr_state->svg_props->text_anchor) {
224
0
  case SVG_TEXTANCHOR_MIDDLE:
225
0
    *width = -(*width)/2;
226
0
    break;
227
0
  case SVG_TEXTANCHOR_END:
228
0
    *width = reversed ? 0 : -(*width);
229
0
    break;
230
0
  case SVG_TEXTANCHOR_START:
231
0
  default:
232
0
    *width = reversed ? -(*width) : 0;
233
0
    break;
234
0
  }
235
0
}
236
237
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)
238
0
{
239
0
  GF_TextSpan *span = NULL;
240
0
  char *dup_text;
241
0
  u32 i, j, len;
242
0
  char prev;
243
244
0
  Bool preserve = (atts->xml_space && (*atts->xml_space==XML_SPACE_PRESERVE)) ? GF_TRUE : GF_FALSE;
245
246
0
  len = (u32) strlen(textContent);
247
0
  dup_text = (char*)gf_malloc(len+1);
248
249
0
  switch (tr_state->last_char_type) {
250
0
  case 2:
251
0
    prev = 0;
252
0
    break;
253
0
  case 0:
254
0
  case 1:
255
0
  default:
256
0
    prev = ' ';
257
0
    break;
258
0
  }
259
0
  for (i = 0, j = 0; i < len; i++) {
260
0
    if (textContent[i] == ' ') {
261
0
      if (prev == ' ' && !preserve) {
262
        /* ignore space */
263
0
      } else {
264
0
        dup_text[j] = textContent[i];
265
0
        prev = dup_text[j];
266
0
        j++;
267
0
      }
268
0
    } else if (textContent[i] == '\t') {
269
0
      if (prev == ' ' && !preserve) {
270
        /* ignore space */
271
0
      } else {
272
0
        dup_text[j] = ' ';
273
0
        prev = dup_text[j];
274
0
        j++;
275
0
      }
276
0
    } else if ((textContent[i] == '\n') ||
277
0
               (textContent[i] == '\r')
278
0
              ) {
279
0
      if (prev == ' ' && preserve) {
280
0
        dup_text[j] = ' ';
281
0
        prev = dup_text[j];
282
0
        j++;
283
0
      } else if (!i && !prev) {
284
0
        prev = dup_text[j] = ' ';
285
0
        j++;
286
0
      }
287
0
    } else if (
288
0
        (((u8) textContent[i] == 0xc2) && ((u8) textContent[i+1] == 0xa0))
289
0
    ) {
290
0
      if (prev == ' ' && !preserve) {
291
        /* ignore space */
292
0
      } else {
293
0
        dup_text[j] = ' ';
294
0
        prev = dup_text[j];
295
0
        j++;
296
0
      }
297
0
      i++;
298
0
    } else {
299
0
      dup_text[j] = textContent[i];
300
0
      prev = dup_text[j];
301
0
      j++;
302
0
    }
303
0
  }
304
0
  dup_text[j] = 0;
305
0
  if (!j) tr_state->last_char_type = 1;
306
0
  else tr_state->last_char_type = (dup_text[j-1]==' ') ? 1 : 2;
307
  /*SVG text is fliped by default (text y-axis is the inverse of SVG y-axis*/
308
0
  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);
309
0
  gf_free(dup_text);
310
0
  if (span) span->flags |= GF_TEXT_SPAN_HORIZONTAL;
311
0
  return span;
312
0
}
313
314
315
316
typedef struct
317
{
318
  GF_TextSpan *span;
319
  u32 first_glyph, last_glyph;
320
} textArea_state;
321
322
static void svg_text_area_reset_state(GF_TraverseState *tr_state)
323
0
{
324
0
  Fixed remain = 0;
325
0
  u32 i, count;
326
0
  count = gf_list_count(tr_state->x_anchors);
327
328
0
  if (tr_state->svg_props->text_align && tr_state->text_end_x) {
329
0
    switch(*tr_state->svg_props->text_align) {
330
0
    case SVG_TEXTALIGN_CENTER:
331
0
      remain = (tr_state->max_length - tr_state->text_end_x) / 2;
332
0
      break;
333
0
    case SVG_TEXTALIGN_END:
334
0
      remain = tr_state->max_length - tr_state->text_end_x;
335
0
      break;
336
0
    default:
337
0
      remain = 0;
338
0
      break;
339
0
    }
340
0
  }
341
342
343
0
  for (i=0; i<count; i++) {
344
0
    textArea_state *st = (textArea_state*)gf_list_get(tr_state->x_anchors, i);
345
0
    if (remain) {
346
0
      u32 j;
347
0
      for (j=st->first_glyph; j<st->last_glyph; j++) {
348
0
        st->span->dx[j] += remain;
349
0
      }
350
0
      tr_state->refresh_children_bounds = 1;
351
0
    }
352
0
    gf_free(st);
353
0
  }
354
0
  gf_list_reset(tr_state->x_anchors);
355
0
}
356
357
static void svg_text_area_queue_state(GF_TraverseState *tr_state, GF_TextSpan *span, u32 first_glyph, u32 last_glyph)
358
0
{
359
0
  textArea_state *st;
360
0
  u32 i, count;
361
0
  count = gf_list_count(tr_state->x_anchors);
362
0
  for (i=0; i<count; i++) {
363
0
    st = (textArea_state*)gf_list_get(tr_state->x_anchors, i);
364
0
    if (st->span==span) {
365
0
      st->last_glyph = last_glyph;
366
0
      return;
367
0
    }
368
0
  }
369
0
  st = (textArea_state*)gf_malloc(sizeof(textArea_state));
370
0
  st->first_glyph = first_glyph;
371
0
  st->last_glyph = last_glyph;
372
0
  st->span = span;
373
0
  gf_list_add(tr_state->x_anchors, st);
374
375
0
}
376
377
static void svg_text_area_apply_diff_baselines(GF_TraverseState *tr_state, Fixed diff)
378
0
{
379
0
  u32 i, count, j;
380
0
  count = gf_list_count(tr_state->x_anchors);
381
0
  tr_state->refresh_children_bounds++;
382
383
0
  for (i=0; i<count; i++) {
384
0
    textArea_state *st = (textArea_state*)gf_list_get(tr_state->x_anchors, i);
385
386
0
    for (j=st->first_glyph; j<st->last_glyph; j++) {
387
0
      st->span->dy[j] += diff;
388
0
    }
389
0
  }
390
0
}
391
392
393
static void svg_traverse_dom_text_area(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state, GF_List *spans)
394
0
{
395
0
  GF_DOMText *dom_text = (GF_DOMText *)node;
396
0
  u32 word_start;
397
0
  u32 i, j;
398
0
  Fixed line_spacing;
399
0
  GF_Font *font;
400
0
  GF_FontManager *fm;
401
0
  GF_TextSpan *span;
402
403
0
  if (!dom_text->textContent) return;
404
0
  if (tr_state->svg_props->font_size->value > tr_state->max_height) return;
405
406
0
  fm = tr_state->visual->compositor->font_manager;
407
0
  if (!fm) return;
408
409
0
  font = svg_set_font(tr_state, fm);
410
0
  if (!font) return;
411
412
0
  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);
413
0
  if (!span) return;
414
415
  /*first run of the line, inc text y*/
416
0
  if (tr_state->line_spacing==0) {
417
0
    if (tr_state->svg_props->line_increment->type != SVG_NUMBER_AUTO) {
418
0
      tr_state->line_spacing = tr_state->svg_props->line_increment->value;
419
0
    } else {
420
0
      tr_state->line_spacing = gf_mulfix(span->font_size, FLT2FIX(1.0) );
421
0
    }
422
0
    tr_state->text_end_y += tr_state->line_spacing;
423
0
  }
424
425
0
  line_spacing = gf_mulfix(span->font_size, FLT2FIX(1.0) );
426
427
0
  word_start = 0;
428
0
  i = 0;
429
  /* boucle principale: mot par mot */
430
0
  while (i<span->nb_glyphs) {
431
0
    Fixed word_size, last_char_size, offset, word_descent;
432
0
    u32 break_glyph = 0;
433
434
0
    word_descent = (-span->font->descent) * span->font_scale;
435
0
    word_start = i;
436
0
    word_size = last_char_size = 0;
437
0
    while (i<span->nb_glyphs) {
438
0
      Fixed glyph_size;
439
0
      if (span->glyphs[i]) {
440
        /*look for word boundaries*/
441
0
        if ( (span->glyphs[i]->utf_name==' ') || (span->glyphs[i]->utf_name=='-') ) {
442
0
          last_char_size = span->glyphs[i]->horiz_advance * span->font_scale;
443
0
          i++;
444
0
          break;
445
0
        }
446
0
        glyph_size = span->glyphs[i]->horiz_advance * span->font_scale;
447
0
        if (word_size + glyph_size> tr_state->max_length) {
448
0
          break_glyph = i;
449
0
          break;
450
0
        }
451
0
        word_size += glyph_size;
452
0
      } else {
453
        // no glyph;
454
0
        word_size += font->max_advance_h * span->font_scale;
455
0
      }
456
0
      i++;
457
0
    }
458
459
    /* word doesn't fit on line, escape*/
460
0
    if (!word_size && !last_char_size) break;
461
462
0
    if (tr_state->text_end_x + word_size > tr_state->max_length) {
463
      /* if the word doesn't fit on line, escape*/
464
0
      if (word_size > tr_state->max_length) {
465
0
        word_start=break_glyph;
466
0
        break;
467
0
      }
468
0
      svg_text_area_reset_state(tr_state);
469
0
      tr_state->text_end_x = 0;
470
0
      tr_state->line_spacing = line_spacing;
471
472
0
      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);
473
      /* out of area, abort processing*/
474
0
      if (tr_state->text_end_y > tr_state->max_height) break;
475
0
    } else {
476
      /* first word is too high for the area*/
477
0
      if (tr_state->text_end_y + word_descent > tr_state->max_height)
478
0
        break;
479
480
      /* stay on current line*/
481
0
      if (line_spacing > tr_state->line_spacing) {
482
0
        svg_text_area_apply_diff_baselines(tr_state, line_spacing - tr_state->line_spacing);
483
0
        tr_state->text_end_y -= tr_state->line_spacing;
484
0
        tr_state->text_end_y += line_spacing;
485
0
        tr_state->line_spacing = line_spacing;
486
0
      }
487
488
0
    }
489
0
    word_size += last_char_size;
490
491
492
0
    offset = tr_state->base_x + tr_state->text_end_x;
493
0
    for (j=word_start; j<i; j++) {
494
0
      span->dx[j] = offset;
495
0
      span->dy[j] = tr_state->base_y + tr_state->text_end_y;
496
0
      offset += (span->glyphs[j] ? span->glyphs[j]->horiz_advance : font->max_advance_h) * span->font_scale;
497
0
    }
498
0
    tr_state->text_end_x += word_size;
499
//    if (tr_state->y_step < word_height) tr_state->y_step = word_height;
500
501
0
    svg_text_area_queue_state(tr_state, span, word_start, i);
502
0
    word_start = i;
503
0
  }
504
0
  span->nb_glyphs = word_start;
505
  /*add span path to list of spans*/
506
0
  gf_list_add(spans, span);
507
0
}
508
509
static void get_domtext_width(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state)
510
0
{
511
0
  u32 i;
512
0
  GF_Font *font;
513
0
  Fixed block_width, *entry;
514
0
  GF_FontManager *fm;
515
0
  GF_TextSpan *span;
516
0
  GF_DOMText *dom_text = (GF_DOMText *)node;
517
518
0
  if (!dom_text->textContent) return;
519
520
0
  fm = tr_state->visual->compositor->font_manager;
521
0
  if (!fm) return;
522
523
0
  font = svg_set_font(tr_state, fm);
524
0
  if (!font) return;
525
526
0
  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);
527
0
  if (!span) return;
528
529
0
  i=0;
530
  //count_x, _y: number of x- (y-) position of characters to come in the text flow
531
0
  while ( (i<span->nb_glyphs)
532
0
          && ( (tr_state->count_x>1) || (tr_state->count_y>1) )
533
0
        ) {
534
0
    block_width = (span->glyphs[i] ? span->glyphs[i]->horiz_advance : font->max_advance_h) * span->font_scale;
535
536
    //store width in tr_state->x_anchors
537
0
    entry = (Fixed*)gf_malloc(sizeof(Fixed));
538
0
    if (span->flags & GF_TEXT_SPAN_RIGHT_TO_LEFT) *entry = -block_width;
539
0
    else *entry = block_width;
540
541
0
    gf_list_add(tr_state->x_anchors, entry);
542
543
0
    if (tr_state->count_x>0) tr_state->count_x--;
544
0
    if (tr_state->count_y>0) tr_state->count_y--;
545
0
    i++;
546
0
  }
547
548
  //chars are taken one by one while there are indicated positions, then remaining chars are treated as a block
549
0
  if (i<span->nb_glyphs) {
550
0
    block_width = 0;
551
0
    while (i<span->nb_glyphs) {
552
0
      block_width += (span->glyphs[i] ? span->glyphs[i]->horiz_advance : font->max_advance_h) * span->font_scale;
553
0
      i++;
554
0
    }
555
    //if last indicated position, create a new item
556
0
    if ((tr_state->count_x==1)||(tr_state->count_y==1)
557
0
            || !gf_list_count(tr_state->x_anchors) ) {
558
0
      entry = (Fixed*)gf_malloc(sizeof(Fixed));
559
560
0
      if (span->flags & GF_TEXT_SPAN_RIGHT_TO_LEFT) *entry = -block_width;
561
0
      else *entry = block_width;
562
563
0
      gf_list_add(tr_state->x_anchors, entry);
564
0
    } else { // (count_x == 0 && count_y == 0) otherwise increment last one
565
0
      Fixed *prec_lw = gf_list_last(tr_state->x_anchors);
566
0
      (*prec_lw) += block_width;
567
0
    }
568
    //force counters to 0 for next spans/DOM texts
569
0
    if (tr_state->count_x==1) tr_state->count_x = 0;
570
0
    if (tr_state->count_y==1) tr_state->count_y = 0;
571
0
  }
572
0
  gf_font_manager_delete_span(fm, span);
573
0
}
574
575
static void get_tspan_width(GF_Node *node, void *rs)
576
0
{
577
0
  SVGPropertiesPointers backup_props;
578
0
  u32 backup_flags;
579
0
  GF_TraverseState *tr_state = (GF_TraverseState *)rs;
580
0
  SVG_Element *tspan = (SVG_Element *)node;
581
0
  SVGAllAttributes atts;
582
0
  GF_ChildNodeItem *child;
583
584
0
  gf_svg_flatten_attributes(tspan, &atts);
585
0
  if (!compositor_svg_traverse_base(node, &atts, tr_state, &backup_props, &backup_flags))
586
0
    return;
587
588
0
  child = ((GF_ParentNode *) tspan)->children;
589
0
  while (child) {
590
0
    switch  (gf_node_get_tag(child->node)) {
591
0
    case TAG_DOMText:
592
0
      get_domtext_width(child->node, &atts, tr_state);
593
0
      break;
594
0
    case TAG_SVG_tspan:
595
0
      get_tspan_width(child->node, tr_state);
596
0
      break;
597
0
    default:
598
0
      break;
599
0
    }
600
0
    child=child->next;
601
0
  }
602
603
0
  memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
604
0
  tr_state->svg_flags = backup_flags;
605
0
}
606
607
void svg_traverse_domtext(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state, GF_List *spans, GF_Node *anchor_node)
608
0
{
609
0
  GF_DOMText *dom_text = (GF_DOMText *)node;
610
0
  Fixed x, y;
611
0
  u32 i;
612
0
  Fixed x_anchor, *ptr;
613
0
  GF_Font *font;
614
0
  Fixed block_width;
615
0
  GF_FontManager *fm;
616
0
  GF_TextSpan *span;
617
618
0
  if (!dom_text->textContent) return;
619
620
0
  if (tr_state->in_svg_text_area) {
621
0
    svg_traverse_dom_text_area(node, atts, tr_state, spans);
622
0
    return;
623
0
  }
624
625
0
  fm = tr_state->visual->compositor->font_manager;
626
0
  if (!fm) return;
627
628
0
  font = svg_set_font(tr_state, fm);
629
0
  if (!font) return;
630
0
  if (font->not_loaded) {
631
0
    tr_state->visual->compositor->reset_fonts = GF_TRUE;
632
0
    tr_state->visual->compositor->skip_flush = 1;
633
0
    gf_sc_next_frame_state(tr_state->visual->compositor, GF_SC_DRAW_FRAME);
634
0
    return;
635
0
  }
636
637
638
0
  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);
639
0
  if (!span) return;
640
641
0
  i=0;
642
  /*
643
  if character position is given in (x, y) attributes, use it.
644
  Otherwise add text at tr_state->text_end_x.
645
  */
646
0
  while ((i<span->nb_glyphs)
647
0
          && ( (tr_state->count_x>1) || (tr_state->count_y>1) )
648
0
        ) {
649
    //get x and y positions
650
0
    if (tr_state->count_x==0) {
651
0
      x = tr_state->text_end_x;
652
0
    } else {
653
0
      SVG_Coordinate *xc = (SVG_Coordinate *) gf_list_get(*tr_state->text_x, tr_state->chunk_index);
654
0
      x = xc->value;
655
0
      (tr_state->count_x)--;
656
0
    }
657
0
    if (tr_state->count_y==0) {
658
0
      y = tr_state->text_end_y;
659
0
    } else {
660
0
      SVG_Coordinate *yc = (SVG_Coordinate *) gf_list_get(*tr_state->text_y, tr_state->chunk_index);
661
0
      y = yc->value;
662
0
      (tr_state->count_y)--;
663
0
    }
664
665
666
    /*apply x-anchor*/
667
0
    ptr = (Fixed *)gf_list_get(tr_state->x_anchors, tr_state->chunk_index);
668
0
    x_anchor = ptr ? *ptr : 0;
669
0
    if (span->dx) span->dx[i] = x_anchor + x;
670
0
    else if (!i) span->off_x = x_anchor + x;
671
0
    if (span->dy) span->dy[i] = y;
672
0
    else span->off_y = y;
673
674
0
    if (tr_state->count_rotate) {
675
0
      SVG_Coordinate *rc = (SVG_Coordinate *) gf_list_get(*tr_state->text_rotate, tr_state->idx_rotate);
676
0
      span->rot[i] = gf_mulfix(GF_PI/180, rc->value);
677
0
      if (tr_state->idx_rotate+1<tr_state->count_rotate) tr_state->idx_rotate++;
678
0
    }
679
680
    /*update last glyph position*/
681
0
    block_width = (span->glyphs[i] ? span->glyphs[i]->horiz_advance : font->max_advance_h) * span->font_scale;
682
0
    tr_state->text_end_x = x+block_width;
683
0
    tr_state->text_end_y = y;
684
0
    (tr_state->chunk_index)++;
685
0
    i++;
686
0
  }
687
688
  /* no more positions, add remaining glyphs as a block*/
689
0
  if (i<span->nb_glyphs) {
690
0
    Fixed offset;
691
0
    if ((tr_state->count_x==1) && tr_state->text_x) {
692
0
      SVG_Coordinate *xc = (SVG_Coordinate *) gf_list_get(*tr_state->text_x, tr_state->chunk_index);
693
0
      tr_state->text_end_x = xc->value;
694
0
      (tr_state->count_x)--;
695
0
    }
696
0
    if ((tr_state->count_y==1) && tr_state->text_y) {
697
0
      SVG_Coordinate *yc = (SVG_Coordinate *) gf_list_get(*tr_state->text_y, tr_state->chunk_index);
698
0
      tr_state->text_end_y = yc->value;
699
0
      (tr_state->count_y)--;
700
0
    }
701
702
0
    x = tr_state->text_end_x;
703
0
    y = tr_state->text_end_y;
704
705
    /*apply x anchor*/
706
0
    ptr = (Fixed *)gf_list_get(tr_state->x_anchors, tr_state->chunk_index);
707
0
    x_anchor = ptr ? *ptr : 0;
708
709
0
    offset = x_anchor + x - (span->dx ? span->dx[i] : span->off_x);
710
711
0
    if (!span->dx && (tr_state->text_x || x_anchor)) span->off_x = x_anchor + x;
712
0
    if (!span->dy && tr_state->text_y) span->off_y = y;
713
714
0
    block_width = 0;
715
0
    while (i<span->nb_glyphs) {
716
717
0
      if (span->rot) {
718
0
        SVG_Coordinate *rc = (SVG_Coordinate *) gf_list_get(*tr_state->text_rotate, tr_state->idx_rotate);
719
0
        span->rot[i] = gf_mulfix(GF_PI/180, rc->value);
720
0
        if (tr_state->idx_rotate+1<tr_state->count_rotate) tr_state->idx_rotate++;
721
0
      }
722
0
      if (span->dx) span->dx[i] = offset + block_width;
723
0
      if (span->dy) span->dy[i] = y;
724
0
      block_width += (span->glyphs[i] ? span->glyphs[i]->horiz_advance : font->max_advance_h) * span->font_scale;
725
726
0
      i++;
727
0
    }
728
0
    tr_state->text_end_x += block_width;
729
0
  }
730
731
  /*add span path to list of spans*/
732
0
  gf_list_add(spans, span);
733
0
  span->anchor = anchor_node;
734
0
}
735
736
737
static void svg_compute_text_width(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state )
738
0
{
739
0
  GF_ChildNodeItem *child;
740
0
  Bool is_switch = GF_FALSE;
741
  /*compute length of all text blocks*/
742
0
  switch  (gf_node_get_tag(node)) {
743
0
  case TAG_DOMText:
744
0
    get_domtext_width(node, atts, tr_state);
745
0
    break;
746
0
  case TAG_SVG_tspan:
747
0
    get_tspan_width(node, tr_state);
748
0
    break;
749
0
  case TAG_SVG_switch:
750
0
    is_switch = GF_TRUE;
751
0
  case TAG_SVG_a:
752
0
    child = ((GF_ParentNode *)node)->children;
753
0
    while (child) {
754
0
      if (is_switch) {
755
0
        SVGAllAttributes a_atts;
756
0
        gf_svg_flatten_attributes((SVG_Element*)child->node, &a_atts);
757
0
        if (compositor_svg_evaluate_conditional(tr_state->visual->compositor, &a_atts)) {
758
0
          svg_compute_text_width(child->node, atts, tr_state);
759
0
          break;
760
0
        }
761
0
      } else {
762
0
        svg_compute_text_width(child->node, atts, tr_state);
763
0
      }
764
0
      child = child->next;
765
0
    }
766
0
    break;
767
0
  default:
768
0
    break;
769
0
  }
770
0
}
771
772
static void svg_traverse_text_block(GF_Node *node, SVGAllAttributes *atts, GF_TraverseState *tr_state, GF_List *spans)
773
0
{
774
0
  GF_ChildNodeItem *child;
775
0
  Bool is_switch = GF_FALSE;
776
0
  switch  (gf_node_get_tag(node)) {
777
0
  case TAG_DOMText:
778
0
    svg_traverse_domtext(node, atts, tr_state, spans, NULL);
779
0
    break;
780
0
  case TAG_SVG_tspan:
781
    /*mark tspan as dirty to force rebuild*/
782
0
    gf_node_dirty_set(node, 0, GF_FALSE);
783
0
    gf_node_traverse(node, tr_state);
784
0
    break;
785
0
  case TAG_SVG_switch:
786
0
    is_switch = GF_TRUE;
787
0
  case TAG_SVG_a:
788
0
    child = ((GF_ParentNode *)node)->children;
789
0
    while (child) {
790
0
      if (is_switch) {
791
0
        SVGAllAttributes a_atts;
792
0
        gf_svg_flatten_attributes((SVG_Element*)child->node, &a_atts);
793
0
        if (compositor_svg_evaluate_conditional(tr_state->visual->compositor, &a_atts)) {
794
0
          svg_traverse_text_block(child->node, atts, tr_state, spans);
795
0
          break;
796
0
        }
797
0
      } else if (gf_node_get_tag(child->node)==TAG_DOMText) {
798
0
        svg_traverse_domtext(child->node, atts, tr_state, spans, node);
799
0
      }
800
0
      child = child->next;
801
0
    }
802
0
    break;
803
0
  default:
804
0
    break;
805
0
  }
806
0
}
807
808
static void svg_text_draw_2d(SVG_TextStack *st, GF_TraverseState *tr_state)
809
0
{
810
0
  gf_font_spans_draw_2d(st->spans, tr_state, 0, GF_FALSE, &st->bounds);
811
0
}
812
813
814
static void svg_text_area_shift_bounds(SVG_TextStack *st, GF_TraverseState *tr_state)
815
0
{
816
0
  u32 i=0;
817
0
  GF_TextSpan *span;
818
  /*finally compute text bounds*/
819
0
  st->bounds.width = st->bounds.height = 0;
820
0
  st->bounds.x = st->bounds.y = 0;
821
0
  while ( (span = (GF_TextSpan*)gf_list_enum(st->spans, &i)) ) {
822
0
    u32 j;
823
0
    for (j=0; j<span->nb_glyphs; j++)
824
0
      span->dy[j] += tr_state->base_shift;
825
826
0
    gf_font_manager_refresh_span_bounds(span);
827
0
    gf_rect_union(&st->bounds, &span->bounds);
828
0
  }
829
0
}
830
831
832
static void svg_traverse_text(GF_Node *node, void *rs, Bool is_destroy)
833
0
{
834
0
  SVGPropertiesPointers backup_props;
835
0
  u32 backup_flags;
836
0
  GF_Matrix2D backup_matrix;
837
0
  GF_Matrix mx3d;
838
0
  GF_ChildNodeItem *child;
839
0
  DrawableContext *ctx;
840
0
  SVG_TextStack *st = (SVG_TextStack *)gf_node_get_private(node);
841
0
  GF_TraverseState *tr_state = (GF_TraverseState *)rs;
842
0
  SVG_Element *text = (SVG_Element *)node;
843
0
  SVGAllAttributes atts;
844
0
  u32 i,imax;
845
846
0
  if (is_destroy) {
847
0
    drawable_del(st->drawable);
848
0
    svg_reset_text_stack(st);
849
0
    gf_list_del(st->spans);
850
0
    gf_free(st);
851
0
    return;
852
0
  }
853
854
0
  if (tr_state->traversing_mode==TRAVERSE_DRAW_2D) {
855
0
    svg_text_draw_2d(st, tr_state);
856
0
    return;
857
0
  }
858
0
  else if (tr_state->traversing_mode==TRAVERSE_GET_TEXT) {
859
0
    tr_state->text_parent = node;
860
0
    gf_font_spans_get_selection(node, st->spans, tr_state);
861
    /*and browse children*/
862
0
    child = ((GF_ParentNode *) text)->children;
863
0
    while (child) {
864
0
      switch  (gf_node_get_tag(child->node)) {
865
0
      case TAG_SVG_tspan:
866
0
        gf_node_traverse(child->node, tr_state);
867
0
        break;
868
0
      }
869
0
      child = child->next;
870
0
    }
871
0
    tr_state->text_parent = NULL;
872
0
    return;
873
0
  }
874
875
0
  gf_svg_flatten_attributes(text, &atts);
876
0
  if (!compositor_svg_traverse_base(node, &atts, tr_state, &backup_props, &backup_flags))
877
0
    return;
878
879
0
  tr_state->in_svg_text++;
880
0
  tr_state->text_parent = node;
881
882
0
  if (tr_state->traversing_mode==TRAVERSE_PICK) {
883
0
    compositor_svg_apply_local_transformation(tr_state, &atts, &backup_matrix, &mx3d);
884
0
    if (*tr_state->svg_props->pointer_events!=SVG_POINTEREVENTS_NONE)
885
0
      gf_font_spans_pick(node, st->spans, tr_state, &st->bounds, 1, st->drawable);
886
887
    /*and browse children*/
888
0
    child = ((GF_ParentNode *) text)->children;
889
0
    while (child) {
890
0
      switch  (gf_node_get_tag(child->node)) {
891
0
      case TAG_SVG_tspan:
892
0
        gf_node_traverse(child->node, tr_state);
893
0
        break;
894
0
      }
895
0
      child = child->next;
896
0
    }
897
0
    memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
898
0
    compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx3d);
899
0
    tr_state->svg_flags = backup_flags;
900
0
    tr_state->text_parent = NULL;
901
0
    tr_state->in_svg_text--;
902
0
    return;
903
0
  }
904
0
  else if (tr_state->traversing_mode==TRAVERSE_GET_TEXT) {
905
0
    gf_font_spans_get_selection(node, st->spans, tr_state);
906
0
    memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
907
0
    tr_state->svg_flags = backup_flags;
908
0
    tr_state->text_parent = NULL;
909
0
    tr_state->in_svg_text--;
910
0
    return;
911
0
  }
912
913
0
  compositor_svg_apply_local_transformation(tr_state, &atts, &backup_matrix, &mx3d);
914
915
0
  if ( (st->prev_size != tr_state->svg_props->font_size->value) ||
916
0
          (st->prev_flags != *tr_state->svg_props->font_style) ||
917
0
          (st->prev_anchor != *tr_state->svg_props->text_anchor) ||
918
0
          (gf_node_dirty_get(node) & (GF_SG_SVG_GEOMETRY_DIRTY | GF_SG_CHILD_DIRTY) )
919
0
          || tr_state->visual->compositor->reset_fonts
920
0
     ) {
921
0
    u32 mode;
922
0
    child = ((GF_ParentNode *) text)->children;
923
924
0
    svg_reset_text_stack(st);
925
0
    tr_state->text_end_x = 0;
926
0
    tr_state->text_end_y = 0;
927
    /*init the xml:space algo*/
928
0
    tr_state->last_char_type = 0;
929
930
    /*initialize x and y counters - stored at the traverse level for handling tspan & co*/
931
0
    if (atts.text_x) tr_state->count_x = gf_list_count(*atts.text_x);
932
0
    else tr_state->count_x=0;
933
0
    if (atts.text_y) tr_state->count_y = gf_list_count(*atts.text_y);
934
0
    else tr_state->count_y=0;
935
0
    if (atts.text_rotate) tr_state->count_rotate = gf_list_count(*atts.text_rotate);
936
0
    else tr_state->count_rotate=0;
937
938
    /*horizontal justifiers container*/
939
0
    tr_state->x_anchors = gf_list_new();
940
941
    /*compute length of all text blocks*/
942
0
    while (child) {
943
0
      svg_compute_text_width(child->node, &atts, tr_state);
944
0
      child=child->next;
945
0
    }
946
947
    /*apply justification of all blocks*/
948
0
    imax=gf_list_count(tr_state->x_anchors);
949
0
    for (i=0; i<imax; i++) {
950
0
      Fixed *lw = gf_list_get(tr_state->x_anchors, i);
951
0
      svg_apply_text_anchor(tr_state, lw);
952
0
    }
953
954
    /*re-initialize x and y counters for final compute*/
955
0
    if (atts.text_x) tr_state->count_x = gf_list_count(*atts.text_x);
956
0
    else tr_state->count_x=0;
957
0
    if (atts.text_y) tr_state->count_y = gf_list_count(*atts.text_y);
958
0
    else tr_state->count_y=0;
959
0
    if (atts.text_rotate) tr_state->count_rotate = gf_list_count(*atts.text_rotate);
960
0
    else tr_state->count_rotate=0;
961
0
    tr_state->idx_rotate = 0;
962
0
    tr_state->chunk_index = 0;
963
964
    /*initialize current text position*/
965
0
    if (!tr_state->text_end_x) {
966
0
      SVG_Coordinate *xc = (atts.text_x ? (SVG_Coordinate *) gf_list_get(*atts.text_x, 0) : NULL);
967
0
      tr_state->text_end_x = (xc ? xc->value : 0);
968
0
    }
969
0
    if (!tr_state->text_end_y) {
970
0
      SVG_Coordinate *yc = (atts.text_y ? (SVG_Coordinate *) gf_list_get(*atts.text_y, 0) : NULL);
971
0
      tr_state->text_end_y = (yc ? yc->value : 0);
972
0
    }
973
974
    /*pass x and y to children*/
975
0
    tr_state->text_x = atts.text_x;
976
0
    tr_state->text_y = atts.text_y;
977
0
    tr_state->text_rotate = atts.text_rotate;
978
979
0
    drawable_reset_path(st->drawable);
980
981
    /*switch to bounds mode, and recompute children*/
982
0
    mode = tr_state->traversing_mode;
983
0
    tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
984
0
    tr_state->last_char_type = 0;
985
986
0
    child = ((GF_ParentNode *) text)->children;
987
0
    while (child) {
988
0
      svg_traverse_text_block(child->node, &atts, tr_state, st->spans);
989
0
      child = child->next;
990
0
    }
991
0
    tr_state->traversing_mode = mode;
992
0
    gf_node_dirty_clear(node, 0);
993
0
    drawable_mark_modified(st->drawable, tr_state);
994
0
    st->prev_size = tr_state->svg_props->font_size->value;
995
0
    st->prev_flags = *tr_state->svg_props->font_style;
996
0
    st->prev_anchor = *tr_state->svg_props->text_anchor;
997
998
0
    while (gf_list_count(tr_state->x_anchors)) {
999
0
      Fixed *f = gf_list_last(tr_state->x_anchors);
1000
0
      gf_list_rem_last(tr_state->x_anchors);
1001
0
      gf_free(f);
1002
0
    }
1003
0
    gf_list_del(tr_state->x_anchors);
1004
0
    tr_state->x_anchors = NULL;
1005
1006
0
    svg_update_bounds(st);
1007
0
  }
1008
1009
0
  if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
1010
0
    if (!compositor_svg_is_display_off(tr_state->svg_props))
1011
0
      tr_state->bounds = st->bounds;
1012
1013
0
  } else if ((tr_state->traversing_mode == TRAVERSE_SORT)
1014
0
    && !compositor_svg_is_display_off(tr_state->svg_props)
1015
0
    && (*(tr_state->svg_props->visibility) != SVG_VISIBILITY_HIDDEN)
1016
0
    ) {
1017
0
    ctx = drawable_init_context_svg(st->drawable, tr_state, atts.clip_path);
1018
1019
0
    if (ctx) svg_finalize_sort(ctx, st, tr_state);
1020
1021
    /*and browse children*/
1022
0
    child = ((GF_ParentNode *) text)->children;
1023
0
    while (child) {
1024
0
      switch  (gf_node_get_tag(child->node)) {
1025
0
      case TAG_SVG_tspan:
1026
0
        gf_node_traverse(child->node, tr_state);
1027
0
        break;
1028
0
      case TAG_SVG_switch:
1029
0
        gf_node_traverse(child->node, tr_state);
1030
0
        break;
1031
0
      }
1032
0
      child = child->next;
1033
0
    }
1034
0
  }
1035
0
  tr_state->in_svg_text--;
1036
0
  tr_state->text_parent = NULL;
1037
1038
0
  compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx3d);
1039
0
  memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
1040
0
  tr_state->svg_flags = backup_flags;
1041
0
}
1042
1043
1044
void compositor_init_svg_text(GF_Compositor *compositor, GF_Node *node)
1045
0
{
1046
0
  SVG_TextStack *stack;
1047
0
  GF_SAFEALLOC(stack, SVG_TextStack);
1048
0
  if (!stack) {
1049
0
    GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg text stack\n"));
1050
0
    return;
1051
0
  }
1052
0
  stack->drawable = drawable_new();
1053
0
  stack->drawable->node = node;
1054
0
  stack->drawable->flags = DRAWABLE_USE_TRAVERSE_DRAW;
1055
0
  stack->spans = gf_list_new();
1056
0
  gf_node_set_private(node, stack);
1057
0
  gf_node_set_callback_function(node, svg_traverse_text);
1058
0
}
1059
1060
1061
static void svg_traverse_tspan(GF_Node *node, void *rs, Bool is_destroy)
1062
0
{
1063
0
  SVGPropertiesPointers backup_props;
1064
0
  u32 backup_flags;
1065
0
  GF_Matrix2D backup_matrix;
1066
0
  GF_Matrix mx3d;
1067
0
  DrawableContext *ctx;
1068
0
  SVG_TextStack *st = (SVG_TextStack *)gf_node_get_private(node);
1069
0
  GF_TraverseState *tr_state = (GF_TraverseState *)rs;
1070
0
  SVG_Element *tspan = (SVG_Element *)node;
1071
0
  SVGAllAttributes atts;
1072
0
  GF_ChildNodeItem *child;
1073
1074
0
  if (is_destroy) {
1075
0
    drawable_del(st->drawable);
1076
0
    svg_reset_text_stack(st);
1077
0
    gf_list_del(st->spans);
1078
0
    gf_free(st);
1079
0
    return;
1080
0
  }
1081
0
  if (tr_state->traversing_mode==TRAVERSE_DRAW_2D) {
1082
0
    svg_text_draw_2d(st, tr_state);
1083
0
    return;
1084
0
  }
1085
0
  else if (tr_state->traversing_mode==TRAVERSE_GET_TEXT) {
1086
0
    gf_font_spans_get_selection(node, st->spans, tr_state);
1087
    /*and browse children*/
1088
0
    child = ((GF_ParentNode *) tspan)->children;
1089
0
    while (child) {
1090
0
      switch  (gf_node_get_tag(child->node)) {
1091
0
      case TAG_SVG_tspan:
1092
0
        gf_node_traverse(child->node, tr_state);
1093
0
        break;
1094
0
      }
1095
0
      child = child->next;
1096
0
    }
1097
0
    return;
1098
0
  }
1099
1100
0
  if (!tr_state->in_svg_text && !tr_state->in_svg_text_area) return;
1101
1102
0
  gf_svg_flatten_attributes(tspan, &atts);
1103
0
  if (!compositor_svg_traverse_base(node, &atts, tr_state, &backup_props, &backup_flags))
1104
0
    return;
1105
1106
0
  if (tr_state->traversing_mode==TRAVERSE_PICK) {
1107
0
    if (*tr_state->svg_props->pointer_events!=SVG_POINTEREVENTS_NONE)
1108
0
      gf_font_spans_pick(node, st->spans, tr_state, &st->bounds, GF_TRUE, st->drawable);
1109
1110
    /*and browse children*/
1111
0
    child = ((GF_ParentNode *) tspan)->children;
1112
0
    while (child) {
1113
0
      switch  (gf_node_get_tag(child->node)) {
1114
0
      case TAG_SVG_tspan:
1115
0
        gf_node_traverse(child->node, tr_state);
1116
0
        break;
1117
0
      }
1118
0
      child = child->next;
1119
0
    }
1120
0
    memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
1121
0
    tr_state->svg_flags = backup_flags;
1122
0
    return;
1123
0
  }
1124
1125
0
  compositor_svg_apply_local_transformation(tr_state, &atts, &backup_matrix, &mx3d);
1126
1127
0
  if ( (st->prev_size != tr_state->svg_props->font_size->value) ||
1128
0
          (st->prev_flags != *tr_state->svg_props->font_style) ||
1129
0
          (st->prev_anchor != *tr_state->svg_props->text_anchor) ||
1130
0
          (gf_node_dirty_get(node) & (GF_SG_SVG_GEOMETRY_DIRTY | GF_SG_CHILD_DIRTY) )
1131
0
     ) {
1132
0
    u32 mode;
1133
1134
    /*tspan has been modified in the SORT stage, which means that an anim local to tspan has modified the node.
1135
    The result of the parent (text, textArea) will thus be wrong if we try to update the tspan. We therefore
1136
    keep the previous computed drawable, and invalidate the parent for next frame*/
1137
0
    if (tr_state->traversing_mode==TRAVERSE_SORT) {
1138
0
      gf_node_dirty_set(node, 0, GF_TRUE);
1139
0
      goto skip_changes;
1140
0
    }
1141
1142
    /*switch to bounds mode, and recompute children*/
1143
0
    mode = tr_state->traversing_mode;
1144
0
    tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
1145
1146
0
    svg_reset_text_stack(st);
1147
0
    child = ((GF_ParentNode *) tspan)->children;
1148
1149
0
    while (child) {
1150
0
      switch  (gf_node_get_tag(child->node)) {
1151
0
      case TAG_DOMText:
1152
0
        svg_traverse_domtext(child->node, &atts, tr_state, st->spans, NULL);
1153
0
        break;
1154
0
      case TAG_SVG_tspan:
1155
0
        gf_node_dirty_set(child->node, 0, GF_FALSE);
1156
0
        gf_node_traverse(child->node, tr_state);
1157
0
        break;
1158
0
      case TAG_SVG_switch:
1159
0
      case TAG_SVG_a:
1160
0
      case TAG_SVG_tbreak:
1161
0
        gf_node_traverse(child->node, tr_state);
1162
0
        break;
1163
0
      default:
1164
0
        break;
1165
0
      }
1166
0
      child = child->next;
1167
0
    }
1168
0
    tr_state->traversing_mode = mode;
1169
0
    gf_node_dirty_clear(node, 0);
1170
0
    drawable_mark_modified(st->drawable, tr_state);
1171
0
    st->prev_size = tr_state->svg_props->font_size->value;
1172
0
    st->prev_flags = *tr_state->svg_props->font_style;
1173
0
    st->prev_anchor = *tr_state->svg_props->text_anchor;
1174
1175
0
    svg_update_bounds(st);
1176
0
  }
1177
0
skip_changes:
1178
1179
0
  if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
1180
0
    if (tr_state->refresh_children_bounds) {
1181
0
      if (tr_state->base_shift)
1182
0
        svg_text_area_shift_bounds(st, tr_state);
1183
0
      else
1184
0
        svg_update_bounds(st);
1185
0
      child = ((GF_ParentNode *) tspan)->children;
1186
0
      while (child) {
1187
0
        switch  (gf_node_get_tag(child->node)) {
1188
0
        case TAG_SVG_tspan:
1189
0
        case TAG_SVG_switch:
1190
0
        case TAG_SVG_a:
1191
0
          gf_node_traverse(child->node, tr_state);
1192
0
          break;
1193
0
        default:
1194
0
          break;
1195
0
        }
1196
0
        child = child->next;
1197
0
      }
1198
0
    }
1199
0
    if (!compositor_svg_is_display_off(tr_state->svg_props))
1200
0
      tr_state->bounds = st->bounds;
1201
1202
0
  }
1203
0
  else if (
1204
0
      (tr_state->traversing_mode == TRAVERSE_SORT)
1205
0
      && !compositor_svg_is_display_off(tr_state->svg_props)
1206
0
      && ( *(tr_state->svg_props->visibility) != SVG_VISIBILITY_HIDDEN)
1207
0
  ) {
1208
0
    child = ((GF_ParentNode *) tspan)->children;
1209
1210
0
    ctx = drawable_init_context_svg(st->drawable, tr_state, atts.clip_path);
1211
1212
0
    if (ctx) svg_finalize_sort(ctx, st, tr_state);
1213
1214
0
    while (child) {
1215
0
      switch  (gf_node_get_tag(child->node)) {
1216
0
      case TAG_SVG_tspan:
1217
0
      case TAG_SVG_switch:
1218
0
      case TAG_SVG_a:
1219
0
        gf_node_traverse(child->node, tr_state);
1220
0
        break;
1221
0
      default:
1222
0
        break;
1223
0
      }
1224
0
      child = child->next;
1225
0
    }
1226
0
  }
1227
1228
0
  compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx3d);
1229
0
  memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
1230
0
  tr_state->svg_flags = backup_flags;
1231
0
}
1232
1233
void compositor_init_svg_tspan(GF_Compositor *compositor, GF_Node *node)
1234
0
{
1235
0
  SVG_TextStack *stack;
1236
0
  GF_SAFEALLOC(stack, SVG_TextStack);
1237
0
  if (!stack) {
1238
0
    GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg tspan stack\n"));
1239
0
    return;
1240
0
  }
1241
0
  stack->drawable = drawable_new();
1242
0
  stack->drawable->node = node;
1243
0
  stack->drawable->flags = DRAWABLE_USE_TRAVERSE_DRAW;
1244
0
  stack->spans = gf_list_new();
1245
0
  gf_node_set_private(node, stack);
1246
0
  gf_node_set_callback_function(node, svg_traverse_tspan);
1247
0
}
1248
1249
1250
static void svg_traverse_textArea(GF_Node *node, void *rs, Bool is_destroy)
1251
0
{
1252
0
  SVGPropertiesPointers backup_props;
1253
0
  u32 backup_flags;
1254
0
  GF_Matrix mx3d;
1255
0
  GF_Matrix2D backup_matrix;
1256
0
  DrawableContext *ctx = NULL;
1257
0
  GF_ChildNodeItem *child;
1258
0
  SVG_TextStack *st = (SVG_TextStack *)gf_node_get_private(node);
1259
0
  GF_TraverseState *tr_state = (GF_TraverseState *)rs;
1260
0
  SVG_Element *text = (SVG_Element *)node;
1261
0
  SVGAllAttributes atts;
1262
1263
0
  if (is_destroy) {
1264
0
    drawable_del(st->drawable);
1265
0
    svg_reset_text_stack(st);
1266
0
    gf_list_del(st->spans);
1267
0
    gf_free(st);
1268
0
    return;
1269
0
  }
1270
1271
0
  if (tr_state->traversing_mode==TRAVERSE_DRAW_2D) {
1272
0
    svg_text_draw_2d(st, tr_state);
1273
0
    return;
1274
0
  }
1275
0
  else if (tr_state->traversing_mode==TRAVERSE_GET_TEXT) {
1276
0
    tr_state->text_parent = node;
1277
0
    gf_font_spans_get_selection(node, st->spans, tr_state);
1278
    /*and browse children*/
1279
0
    child = ((GF_ParentNode *) text)->children;
1280
0
    while (child) {
1281
0
      switch  (gf_node_get_tag(child->node)) {
1282
0
      case TAG_SVG_tspan:
1283
0
      case TAG_SVG_a:
1284
0
        gf_node_traverse(child->node, tr_state);
1285
0
        break;
1286
0
      }
1287
0
      child = child->next;
1288
0
    }
1289
0
    tr_state->text_parent = NULL;
1290
0
    return;
1291
0
  }
1292
1293
1294
0
  gf_svg_flatten_attributes(text, &atts);
1295
0
  if (!compositor_svg_traverse_base(node, &atts, tr_state, &backup_props, &backup_flags))
1296
0
    return;
1297
1298
0
  tr_state->text_parent = node;
1299
0
  tr_state->in_svg_text_area++;
1300
1301
0
  if (tr_state->traversing_mode==TRAVERSE_PICK) {
1302
0
    if (*tr_state->svg_props->pointer_events!=SVG_POINTEREVENTS_NONE) {
1303
0
      compositor_svg_apply_local_transformation(tr_state, &atts, &backup_matrix, &mx3d);
1304
0
      gf_font_spans_pick(node, st->spans, tr_state, &st->bounds, GF_TRUE, st->drawable);
1305
1306
      /*and browse children*/
1307
0
      child = ((GF_ParentNode *) node)->children;
1308
0
      while (child) {
1309
0
        gf_node_traverse(child->node, tr_state);
1310
0
        child = child->next;
1311
0
      }
1312
0
      compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx3d);
1313
0
      memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
1314
0
      tr_state->svg_flags = backup_flags;
1315
0
    }
1316
0
    tr_state->in_svg_text_area--;
1317
0
    tr_state->text_parent = NULL;
1318
0
    return;
1319
0
  }
1320
1321
0
  compositor_svg_apply_local_transformation(tr_state, &atts, &backup_matrix, &mx3d);
1322
1323
0
  if ( (st->prev_size != tr_state->svg_props->font_size->value) ||
1324
0
          (st->prev_flags != *tr_state->svg_props->font_style) ||
1325
0
          (st->prev_anchor != *tr_state->svg_props->text_anchor) ||
1326
0
          (gf_node_dirty_get(node) & (GF_SG_SVG_GEOMETRY_DIRTY | GF_SG_CHILD_DIRTY) )
1327
0
          || tr_state->visual->compositor->reset_fonts
1328
0
     ) {
1329
0
    u32 mode;
1330
1331
0
    svg_reset_text_stack(st);
1332
0
    gf_node_dirty_clear(node, 0);
1333
0
    drawable_mark_modified(st->drawable, tr_state);
1334
0
    drawable_reset_path(st->drawable);
1335
1336
0
    tr_state->max_length = (atts.width ? (atts.width->type == SVG_NUMBER_AUTO ? FIX_MAX : atts.width->value) : FIX_MAX);
1337
0
    tr_state->max_height = (atts.height ? (atts.height->type == SVG_NUMBER_AUTO ? FIX_MAX : atts.height->value) : FIX_MAX);
1338
0
    tr_state->base_x = (atts.x ? atts.x->value : 0);
1339
0
    tr_state->base_y = (atts.y ? atts.y->value : 0);
1340
    /*init the xml:space algo*/
1341
0
    tr_state->last_char_type = 0;
1342
    /*let it initialize from first font*/
1343
0
    tr_state->line_spacing = 0;
1344
0
    tr_state->text_end_x = 0;
1345
0
    tr_state->text_end_y = (tr_state->svg_props->line_increment->type == SVG_NUMBER_AUTO ? 0 : tr_state->svg_props->line_increment->value);
1346
1347
0
    if (tr_state->svg_props->font_size && (tr_state->svg_props->font_size->value <= tr_state->max_height)) {
1348
0
      Fixed remain;
1349
0
      u32 c, refresh_to_idx, prev_refresh;
1350
0
      tr_state->x_anchors = gf_list_new();
1351
1352
      /*switch to bounds mode, and recompute children*/
1353
0
      mode = tr_state->traversing_mode;
1354
0
      tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
1355
1356
0
      prev_refresh = tr_state->refresh_children_bounds;
1357
0
      tr_state->refresh_children_bounds = 0;
1358
0
      c = refresh_to_idx = 0;
1359
0
      child = ((GF_ParentNode *) text)->children;
1360
0
      while (child) {
1361
0
        c++;
1362
0
        switch  (gf_node_get_tag(child->node)) {
1363
0
        case TAG_DOMText:
1364
0
          svg_traverse_dom_text_area(child->node, &atts, tr_state, st->spans);
1365
0
          break;
1366
0
        case TAG_SVG_tspan:
1367
          /*mark tspan as dirty to force rebuild*/
1368
0
          gf_node_dirty_set(child->node, 0, GF_FALSE);
1369
0
          gf_node_traverse(child->node, tr_state);
1370
0
          break;
1371
0
        case TAG_SVG_switch:
1372
0
        case TAG_SVG_a:
1373
0
        case TAG_SVG_tbreak:
1374
0
          gf_node_traverse(child->node, tr_state);
1375
0
          break;
1376
0
        default:
1377
0
          break;
1378
0
        }
1379
0
        if (tr_state->refresh_children_bounds) {
1380
0
          tr_state->refresh_children_bounds=0;
1381
0
          refresh_to_idx=c;
1382
0
        }
1383
1384
0
        child=child->next;
1385
0
      }
1386
0
      st->prev_size = tr_state->svg_props->font_size->value;
1387
0
      st->prev_flags = *tr_state->svg_props->font_style;
1388
0
      st->prev_anchor = *tr_state->svg_props->text_anchor;
1389
1390
0
      svg_text_area_reset_state(tr_state);
1391
0
      gf_list_del(tr_state->x_anchors);
1392
0
      tr_state->x_anchors = NULL;
1393
1394
0
      if (tr_state->refresh_children_bounds) {
1395
0
        refresh_to_idx = (u32) -1;
1396
0
        tr_state->base_shift = 0;
1397
0
      }
1398
0
      if (tr_state->svg_props->display_align) {
1399
0
        switch (*tr_state->svg_props->display_align) {
1400
0
        case SVG_DISPLAYALIGN_CENTER:
1401
0
          remain = (tr_state->max_height-tr_state->text_end_y) / 2;
1402
0
          break;
1403
0
        case SVG_DISPLAYALIGN_AFTER:
1404
0
          remain = tr_state->max_height - tr_state->text_end_y;
1405
0
          break;
1406
0
        default:
1407
0
          remain = 0;
1408
0
          break;
1409
0
        }
1410
0
        if (remain<0) remain=0;
1411
0
        if (remain) {
1412
0
          refresh_to_idx = (u32) -1;
1413
0
          tr_state->base_shift = remain;
1414
0
          svg_text_area_shift_bounds(st, tr_state);
1415
0
        }
1416
0
      }
1417
1418
0
      if (refresh_to_idx) {
1419
0
        tr_state->refresh_children_bounds=1;
1420
        /*and retraverse in case of bounds adjustements*/
1421
0
        child = ((GF_ParentNode *) text)->children;
1422
0
        while (child) {
1423
0
          switch (gf_node_get_tag(child->node)) {
1424
0
          case TAG_DOMText:
1425
0
            break;
1426
0
          case TAG_SVG_tspan:
1427
0
          case TAG_SVG_switch:
1428
0
          case TAG_SVG_a:
1429
0
            gf_node_traverse(child->node, tr_state);
1430
0
            break;
1431
0
          default:
1432
0
            break;
1433
0
          }
1434
0
          child=child->next;
1435
0
          refresh_to_idx--;
1436
0
          if (!refresh_to_idx) break;
1437
0
        }
1438
0
        tr_state->base_shift = 0;
1439
0
      }
1440
0
      tr_state->traversing_mode = mode;
1441
0
      tr_state->refresh_children_bounds = prev_refresh;
1442
0
    }
1443
0
    svg_update_bounds(st);
1444
0
  }
1445
1446
0
  if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
1447
0
    if (!compositor_svg_is_display_off(tr_state->svg_props))
1448
0
      tr_state->bounds = st->bounds;
1449
0
  } else if ( (tr_state->traversing_mode == TRAVERSE_SORT)
1450
0
              && !compositor_svg_is_display_off(tr_state->svg_props)
1451
0
              && (*(tr_state->svg_props->visibility) != SVG_VISIBILITY_HIDDEN)
1452
0
            ) {
1453
1454
0
    ctx = drawable_init_context_svg(st->drawable, tr_state, atts.clip_path);
1455
1456
0
    if (ctx) svg_finalize_sort(ctx, st, tr_state);
1457
1458
0
    child = ((GF_ParentNode *) text)->children;
1459
0
    while (child) {
1460
0
      switch  (gf_node_get_tag(child->node)) {
1461
0
      case TAG_DOMText:
1462
0
        break;
1463
0
      case TAG_SVG_tspan:
1464
0
      case TAG_SVG_switch:
1465
0
      case TAG_SVG_a:
1466
0
        gf_node_traverse(child->node, tr_state);
1467
0
        break;
1468
0
      default:
1469
0
        break;
1470
0
      }
1471
0
      child = child->next;
1472
0
    }
1473
0
  }
1474
0
  tr_state->in_svg_text_area--;
1475
0
  tr_state->text_parent = NULL;
1476
1477
1478
0
  compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx3d);
1479
0
  memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
1480
0
  tr_state->svg_flags = backup_flags;
1481
0
}
1482
1483
void compositor_init_svg_textarea(GF_Compositor *compositor, GF_Node *node)
1484
0
{
1485
0
  SVG_TextStack *stack;
1486
0
  GF_SAFEALLOC(stack, SVG_TextStack);
1487
0
  if (!stack) {
1488
0
    GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg textarea stack\n"));
1489
0
    return;
1490
0
  }
1491
0
  stack->drawable = drawable_new();
1492
0
  stack->drawable->node = node;
1493
0
  stack->drawable->flags = DRAWABLE_USE_TRAVERSE_DRAW;
1494
0
  stack->spans = gf_list_new();
1495
0
  gf_node_set_private(node, stack);
1496
0
  gf_node_set_callback_function(node, svg_traverse_textArea);
1497
0
}
1498
1499
static void svg_traverse_tbreak(GF_Node *node, void *rs, Bool is_destroy)
1500
0
{
1501
0
  SVGPropertiesPointers backup_props;
1502
0
  u32 backup_flags;
1503
0
  GF_TraverseState *tr_state = (GF_TraverseState *)rs;
1504
0
  SVGAllAttributes atts;
1505
1506
0
  if (is_destroy) return;
1507
0
  if (tr_state->traversing_mode!=TRAVERSE_GET_BOUNDS) return;
1508
1509
0
  gf_svg_flatten_attributes((SVG_Element*)node, &atts);
1510
0
  if (!compositor_svg_traverse_base(node, &atts, tr_state, &backup_props, &backup_flags))
1511
0
    return;
1512
1513
0
  svg_text_area_reset_state(tr_state);
1514
  /*beginning of a line, force a break of current fontSize*/
1515
0
  if (!tr_state->text_end_x) {
1516
0
    if (tr_state->svg_props->line_increment->type != SVG_NUMBER_AUTO) {
1517
0
      tr_state->text_end_y += tr_state->svg_props->line_increment->value;
1518
0
    } else {
1519
0
      tr_state->text_end_y += tr_state->svg_props->font_size->value;
1520
0
    }
1521
0
  }
1522
0
  tr_state->line_spacing = 0;
1523
0
  tr_state->text_end_x = 0;
1524
0
  tr_state->last_char_type = 0;
1525
1526
0
  memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
1527
0
  tr_state->svg_flags = backup_flags;
1528
0
}
1529
1530
void compositor_init_svg_tbreak(GF_Compositor *compositor, GF_Node *node)
1531
0
{
1532
0
  gf_node_set_callback_function(node, svg_traverse_tbreak);
1533
0
}
1534
1535
#endif //!defined(GPAC_DISABLE_SVG) &&  !defined(GPAC_DISABLE_COMPOSITOR)