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