/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) |