/src/gpac/src/compositor/svg_geometry.c
Line | Count | Source |
1 | | /* |
2 | | * GPAC - Multimedia Framework C SDK |
3 | | * |
4 | | * Authors: Cyril Concolato - Jean le Feuvre |
5 | | * Copyright (c) Telecom ParisTech 2005-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 "visual_manager.h" |
27 | | #include "nodes_stacks.h" |
28 | | |
29 | | #if !defined(GPAC_DISABLE_COMPOSITOR) |
30 | | |
31 | | #ifdef GPAC_DISABLE_SVG |
32 | | |
33 | | Bool svg_drawable_is_over(Drawable *drawable, Fixed x, Fixed y, DrawAspect2D *asp, GF_TraverseState *tr_state, GF_Rect *glyph_rc) |
34 | | { |
35 | | return 0; |
36 | | } |
37 | | |
38 | | #else |
39 | | |
40 | | Bool svg_drawable_is_over(Drawable *drawable, Fixed x, Fixed y, DrawAspect2D *asp, GF_TraverseState *tr_state, GF_Rect *glyph_rc) |
41 | 0 | { |
42 | 0 | u32 check_fill, check_stroke; |
43 | 0 | Bool check_over, check_outline, check_vis, inside; |
44 | 0 | GF_Rect rc; |
45 | 0 | u8 ptr_evt; |
46 | |
|
47 | 0 | ptr_evt = *tr_state->svg_props->pointer_events; |
48 | |
|
49 | 0 | if (ptr_evt==SVG_POINTEREVENTS_NONE) { |
50 | 0 | return 0; |
51 | 0 | } |
52 | | |
53 | 0 | if (glyph_rc) { |
54 | 0 | rc = *glyph_rc; |
55 | 0 | } else { |
56 | 0 | gf_path_get_bounds(drawable->path, &rc); |
57 | 0 | } |
58 | 0 | inside = ( (x >= rc.x) && (y <= rc.y) && (x <= rc.x + rc.width) && (y >= rc.y - rc.height) ) ? 1 : 0; |
59 | |
|
60 | 0 | if (ptr_evt==SVG_POINTEREVENTS_BOUNDINGBOX) return inside; |
61 | | |
62 | 0 | check_fill = check_stroke = check_over = check_outline = check_vis = 0; |
63 | | /* |
64 | | check_vis: if set, return FALSE when visible property is not "visible" |
65 | | check_fill: |
66 | | if 1, checks whether point is over path, |
67 | | if 2, checks if the path is painted (even with fill-opacity=0) before |
68 | | check_stroke: |
69 | | if 1, checks whether point is over path outline, |
70 | | if 2, checks if the path outline is painted (even with stroke-opacity=0) before |
71 | | */ |
72 | 0 | switch (ptr_evt) { |
73 | 0 | case SVG_POINTEREVENTS_VISIBLE: |
74 | 0 | check_vis = 1; |
75 | 0 | check_fill = 1; |
76 | 0 | check_stroke = 1; |
77 | 0 | break; |
78 | 0 | case SVG_POINTEREVENTS_VISIBLEFILL: |
79 | 0 | check_vis = 1; |
80 | 0 | check_fill = 1; |
81 | 0 | break; |
82 | 0 | case SVG_POINTEREVENTS_VISIBLESTROKE: |
83 | 0 | check_vis = 1; |
84 | 0 | check_stroke = 1; |
85 | 0 | break; |
86 | 0 | case SVG_POINTEREVENTS_VISIBLEPAINTED: |
87 | 0 | check_vis = 1; |
88 | 0 | check_fill = 2; |
89 | 0 | check_stroke = 2; |
90 | 0 | break; |
91 | 0 | case SVG_POINTEREVENTS_FILL: |
92 | 0 | check_fill = 1; |
93 | 0 | break; |
94 | 0 | case SVG_POINTEREVENTS_STROKE: |
95 | 0 | check_stroke = 1; |
96 | 0 | break; |
97 | 0 | case SVG_POINTEREVENTS_ALL: |
98 | 0 | check_fill = 1; |
99 | 0 | check_stroke = 1; |
100 | 0 | break; |
101 | 0 | case SVG_POINTEREVENTS_PAINTED: |
102 | 0 | check_fill = 2; |
103 | 0 | check_stroke = 2; |
104 | 0 | break; |
105 | 0 | default: |
106 | 0 | return 0; |
107 | 0 | } |
108 | | |
109 | | /*!!watchout!! asp2D.width is 0 if stroke not visible due to painting properties - we must override this |
110 | | for picking*/ |
111 | 0 | if (check_stroke==1) { |
112 | 0 | asp->pen_props.width = tr_state->svg_props->stroke_width ? tr_state->svg_props->stroke_width->value : 0; |
113 | 0 | } |
114 | 0 | if (!asp->pen_props.width) check_stroke = 0; |
115 | |
|
116 | 0 | if (check_stroke) { |
117 | | /*rough estimation of stroke bounding box to avoid fetching the stroke each time*/ |
118 | 0 | if (!inside) { |
119 | 0 | Fixed width = asp->pen_props.width; |
120 | 0 | rc.x -= width; |
121 | 0 | rc.y += width; |
122 | 0 | rc.width += 2*width; |
123 | 0 | rc.height += 2*width; |
124 | 0 | inside = ( (x >= rc.x) && (y <= rc.y) && (x <= rc.x + rc.width) && (y >= rc.y - rc.height) ) ? 1 : 0; |
125 | 0 | if (!inside) return 0; |
126 | 0 | } |
127 | 0 | } else if (!inside) { |
128 | 0 | return 0; |
129 | 0 | } |
130 | | |
131 | 0 | if (check_vis) { |
132 | 0 | if (*tr_state->svg_props->visibility!=SVG_VISIBILITY_VISIBLE) return 0; |
133 | 0 | } |
134 | | |
135 | 0 | if (check_fill) { |
136 | | /*painted or don't care about fill*/ |
137 | 0 | if ((check_fill!=2) || asp->fill_texture || asp->fill_color) { |
138 | 0 | if (glyph_rc) return 1; |
139 | | /*point is over path*/ |
140 | 0 | if (gf_path_point_over(drawable->path, x, y)) return 1; |
141 | 0 | } |
142 | 0 | } |
143 | 0 | if (check_stroke) { |
144 | | /*not painted or don't care about stroke*/ |
145 | 0 | if ((check_stroke!=2) || asp->line_texture || asp->line_color) { |
146 | 0 | StrikeInfo2D *si; |
147 | 0 | if (glyph_rc) return 1; |
148 | 0 | si = drawable_get_strikeinfo(tr_state->visual->compositor, drawable, asp, tr_state->appear, NULL, 0, NULL); |
149 | | /*point is over outline*/ |
150 | 0 | if (si && si->outline && gf_path_point_over(si->outline, x, y)) |
151 | 0 | return 1; |
152 | 0 | } |
153 | 0 | } |
154 | 0 | return 0; |
155 | 0 | } |
156 | | |
157 | | |
158 | | void svg_clone_use_stack(GF_Compositor *compositor, GF_TraverseState *tr_state) |
159 | 0 | { |
160 | 0 | u32 i, count; |
161 | 0 | count = gf_list_count(tr_state->use_stack); |
162 | 0 | gf_list_reset(compositor->hit_use_stack); |
163 | 0 | for (i=0; i<count; i++) { |
164 | 0 | GF_Node *node = gf_list_get(tr_state->use_stack, i); |
165 | 0 | gf_list_add(compositor->hit_use_stack, node); |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | | #ifndef GPAC_DISABLE_3D |
170 | | |
171 | | void svg_drawable_3d_pick(Drawable *drawable, GF_TraverseState *tr_state, DrawAspect2D *asp) |
172 | | { |
173 | | SFVec3f local_pt, world_pt, vdiff; |
174 | | SFVec3f hit_normal; |
175 | | SFVec2f text_coords; |
176 | | u32 i, count; |
177 | | Fixed sqdist; |
178 | | Bool node_is_over; |
179 | | GF_Compositor *compositor; |
180 | | GF_Matrix mx; |
181 | | GF_Ray r; |
182 | | |
183 | | compositor = tr_state->visual->compositor; |
184 | | |
185 | | r = tr_state->ray; |
186 | | gf_mx_copy(mx, tr_state->model_matrix); |
187 | | gf_mx_inverse(&mx); |
188 | | gf_mx_apply_ray(&mx, &r); |
189 | | |
190 | | /*if we already have a hit point don't check anything below...*/ |
191 | | if (compositor->hit_square_dist && !compositor->grabbed_sensor && !tr_state->layer3d) { |
192 | | GF_Plane p; |
193 | | GF_BBox box; |
194 | | SFVec3f hit = compositor->hit_world_point; |
195 | | gf_mx_apply_vec(&mx, &hit); |
196 | | p.normal = r.dir; |
197 | | p.d = -1 * gf_vec_dot(p.normal, hit); |
198 | | gf_bbox_from_rect(&box, &drawable->path->bbox); |
199 | | |
200 | | if (gf_bbox_plane_relation(&box, &p) == GF_BBOX_FRONT) { |
201 | | GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[SVG Picking] bounding box of node %s (DEF %s) below current hit point - skipping\n", gf_node_get_class_name(drawable->node), gf_node_get_name(drawable->node))); |
202 | | return; |
203 | | } |
204 | | } |
205 | | node_is_over = 0; |
206 | | if (compositor_get_2d_plane_intersection(&r, &local_pt)) { |
207 | | node_is_over = svg_drawable_is_over(drawable, local_pt.x, local_pt.y, asp, tr_state, NULL); |
208 | | } |
209 | | |
210 | | if (!node_is_over) return; |
211 | | |
212 | | hit_normal.x = hit_normal.y = 0; |
213 | | hit_normal.z = FIX_ONE; |
214 | | text_coords.x = gf_divfix(local_pt.x, drawable->path->bbox.width) + FIX_ONE/2; |
215 | | text_coords.y = gf_divfix(local_pt.y, drawable->path->bbox.height) + FIX_ONE/2; |
216 | | |
217 | | /*check distance from user and keep the closest hitpoint*/ |
218 | | world_pt = local_pt; |
219 | | gf_mx_apply_vec(&tr_state->model_matrix, &world_pt); |
220 | | |
221 | | for (i=0; i<tr_state->num_clip_planes; i++) { |
222 | | if (gf_plane_get_distance(&tr_state->clip_planes[i], &world_pt) < 0) { |
223 | | GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[SVG Picking] node %s (def %s) is not in clipper half space\n", gf_node_get_class_name(drawable->node), gf_node_get_name(drawable->node))); |
224 | | return; |
225 | | } |
226 | | } |
227 | | |
228 | | gf_vec_diff(vdiff, world_pt, tr_state->ray.orig); |
229 | | sqdist = gf_vec_lensq(vdiff); |
230 | | if (compositor->hit_square_dist && (compositor->hit_square_dist+FIX_EPSILON<sqdist)) { |
231 | | GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[SVG Picking] node %s (def %s) is farther (%g) than current pick (%g)\n", gf_node_get_class_name(drawable->node), gf_node_get_name(drawable->node), FIX2FLT(sqdist), FIX2FLT(compositor->hit_square_dist))); |
232 | | return; |
233 | | } |
234 | | |
235 | | compositor->hit_square_dist = sqdist; |
236 | | |
237 | | /*also stack any VRML sensors present at the current level. If the event is not catched |
238 | | by a listener in the SVG tree, the event will be forwarded to the VRML tree*/ |
239 | | gf_list_reset(compositor->sensors); |
240 | | count = gf_list_count(tr_state->vrml_sensors); |
241 | | for (i=0; i<count; i++) { |
242 | | gf_list_add(compositor->sensors, gf_list_get(tr_state->vrml_sensors, i)); |
243 | | } |
244 | | |
245 | | gf_mx_copy(compositor->hit_world_to_local, tr_state->model_matrix); |
246 | | gf_mx_copy(compositor->hit_local_to_world, mx); |
247 | | compositor->hit_local_point = local_pt; |
248 | | compositor->hit_world_point = world_pt; |
249 | | compositor->hit_world_ray = tr_state->ray; |
250 | | compositor->hit_normal = hit_normal; |
251 | | compositor->hit_texcoords = text_coords; |
252 | | |
253 | | svg_clone_use_stack(compositor, tr_state); |
254 | | /*not use in SVG patterns*/ |
255 | | compositor->hit_appear = NULL; |
256 | | compositor->hit_node = drawable->node; |
257 | | compositor->hit_text = NULL; |
258 | | compositor->hit_use_dom_events = 1; |
259 | | |
260 | | GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[SVG Picking] node %s (def %s) is under mouse - hit %g %g %g\n", gf_node_get_class_name(drawable->node), gf_node_get_name(drawable->node), |
261 | | FIX2FLT(world_pt.x), FIX2FLT(world_pt.y), FIX2FLT(world_pt.z))); |
262 | | } |
263 | | |
264 | | #endif |
265 | | |
266 | | void svg_drawable_pick(GF_Node *node, Drawable *drawable, GF_TraverseState *tr_state) |
267 | 0 | { |
268 | 0 | DrawAspect2D asp; |
269 | 0 | GF_Matrix2D inv_2d; |
270 | 0 | Fixed x, y; |
271 | 0 | Bool picked = 0; |
272 | 0 | GF_Compositor *compositor = tr_state->visual->compositor; |
273 | 0 | SVGPropertiesPointers backup_props; |
274 | 0 | GF_Matrix2D backup_matrix; |
275 | 0 | GF_Matrix mx_3d; |
276 | 0 | SVGAllAttributes all_atts; |
277 | |
|
278 | 0 | if (!drawable->path) return; |
279 | | |
280 | 0 | gf_svg_flatten_attributes((SVG_Element *)node, &all_atts); |
281 | |
|
282 | 0 | memcpy(&backup_props, tr_state->svg_props, sizeof(SVGPropertiesPointers)); |
283 | 0 | gf_svg_apply_inheritance(&all_atts, tr_state->svg_props); |
284 | 0 | if (compositor_svg_is_display_off(tr_state->svg_props)) return; |
285 | | |
286 | 0 | compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &mx_3d); |
287 | |
|
288 | 0 | memset(&asp, 0, sizeof(DrawAspect2D)); |
289 | 0 | drawable_get_aspect_2d_svg(node, &asp, tr_state); |
290 | |
|
291 | | #ifndef GPAC_DISABLE_3D |
292 | | if (tr_state->visual->type_3d) { |
293 | | svg_drawable_3d_pick(drawable, tr_state, &asp); |
294 | | compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx_3d); |
295 | | memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers)); |
296 | | return; |
297 | | } |
298 | | #endif |
299 | 0 | gf_mx2d_copy(inv_2d, tr_state->transform); |
300 | 0 | gf_mx2d_inverse(&inv_2d); |
301 | 0 | x = tr_state->ray.orig.x; |
302 | 0 | y = tr_state->ray.orig.y; |
303 | 0 | gf_mx2d_apply_coords(&inv_2d, &x, &y); |
304 | |
|
305 | 0 | picked = svg_drawable_is_over(drawable, x, y, &asp, tr_state, NULL); |
306 | |
|
307 | 0 | if (picked) { |
308 | 0 | u32 count, i; |
309 | 0 | compositor->hit_local_point.x = x; |
310 | 0 | compositor->hit_local_point.y = y; |
311 | 0 | compositor->hit_local_point.z = 0; |
312 | |
|
313 | 0 | gf_mx_from_mx2d(&compositor->hit_world_to_local, &tr_state->transform); |
314 | 0 | gf_mx_from_mx2d(&compositor->hit_local_to_world, &inv_2d); |
315 | |
|
316 | 0 | compositor->hit_node = drawable->node; |
317 | 0 | compositor->hit_use_dom_events = 1; |
318 | 0 | compositor->hit_normal.x = compositor->hit_normal.y = 0; |
319 | 0 | compositor->hit_normal.z = FIX_ONE; |
320 | 0 | compositor->hit_texcoords.x = gf_divfix(x, drawable->path->bbox.width) + FIX_ONE/2; |
321 | 0 | compositor->hit_texcoords.y = gf_divfix(y, drawable->path->bbox.height) + FIX_ONE/2; |
322 | 0 | svg_clone_use_stack(compositor, tr_state); |
323 | | /*not use in SVG patterns*/ |
324 | 0 | compositor->hit_appear = NULL; |
325 | | |
326 | | /*also stack any VRML sensors present at the current level. If the event is not catched |
327 | | by a listener in the SVG tree, the event will be forwarded to the VRML tree*/ |
328 | 0 | gf_list_reset(tr_state->visual->compositor->sensors); |
329 | 0 | count = gf_list_count(tr_state->vrml_sensors); |
330 | 0 | for (i=0; i<count; i++) { |
331 | 0 | gf_list_add(tr_state->visual->compositor->sensors, gf_list_get(tr_state->vrml_sensors, i)); |
332 | 0 | } |
333 | |
|
334 | 0 | GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[SVG Picking] node %s is under mouse - hit %g %g 0\n", gf_node_get_log_name(drawable->node), FIX2FLT(x), FIX2FLT(y))); |
335 | 0 | } |
336 | |
|
337 | 0 | compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx_3d); |
338 | 0 | memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers)); |
339 | 0 | } |
340 | | |
341 | | static void svg_drawable_traverse(GF_Node *node, void *rs, Bool is_destroy, |
342 | | void (*rebuild_path)(GF_Node *, Drawable *, SVGAllAttributes *), |
343 | | Bool is_svg_rect, Bool is_svg_path) |
344 | 0 | { |
345 | 0 | GF_Matrix2D backup_matrix; |
346 | 0 | GF_Matrix mx_3d; |
347 | 0 | DrawableContext *ctx; |
348 | 0 | SVGPropertiesPointers backup_props; |
349 | 0 | u32 backup_flags; |
350 | 0 | Drawable *drawable = (Drawable *)gf_node_get_private(node); |
351 | 0 | GF_TraverseState *tr_state = (GF_TraverseState *)rs; |
352 | 0 | SVGAllAttributes all_atts; |
353 | |
|
354 | 0 | if (is_destroy) { |
355 | 0 | #if USE_GF_PATH |
356 | | /* The path is the same as the one in the SVG node, don't delete it here */ |
357 | 0 | if (is_svg_path) drawable->path = NULL; |
358 | 0 | #endif |
359 | 0 | drawable_node_del(node); |
360 | 0 | return; |
361 | 0 | } |
362 | 0 | gf_assert(tr_state->traversing_mode!=TRAVERSE_DRAW_2D); |
363 | | |
364 | |
|
365 | 0 | if (tr_state->traversing_mode==TRAVERSE_PICK) { |
366 | 0 | svg_drawable_pick(node, drawable, tr_state); |
367 | 0 | return; |
368 | 0 | } |
369 | | |
370 | | /*flatten attributes and apply animations + inheritance*/ |
371 | 0 | gf_svg_flatten_attributes((SVG_Element *)node, &all_atts); |
372 | 0 | if (!compositor_svg_traverse_base(node, &all_atts, (GF_TraverseState *)rs, &backup_props, &backup_flags)) |
373 | 0 | return; |
374 | | |
375 | | /* Recreates the path (i.e the shape) only if the node is dirty */ |
376 | 0 | if (gf_node_dirty_get(node) & GF_SG_SVG_GEOMETRY_DIRTY) { |
377 | | /*the rebuild function is responsible for cleaning the path*/ |
378 | 0 | rebuild_path(node, drawable, &all_atts); |
379 | 0 | gf_node_dirty_clear(node, GF_SG_SVG_GEOMETRY_DIRTY); |
380 | 0 | drawable_mark_modified(drawable, tr_state); |
381 | 0 | } |
382 | 0 | if (drawable->path) { |
383 | 0 | if (*(tr_state->svg_props->fill_rule)==GF_PATH_FILL_ZERO_NONZERO) { |
384 | 0 | if (!(drawable->path->flags & GF_PATH_FILL_ZERO_NONZERO)) { |
385 | 0 | drawable->path->flags |= GF_PATH_FILL_ZERO_NONZERO; |
386 | 0 | drawable_mark_modified(drawable, tr_state); |
387 | 0 | } |
388 | 0 | } else { |
389 | 0 | if (drawable->path->flags & GF_PATH_FILL_ZERO_NONZERO) { |
390 | 0 | drawable->path->flags &= ~GF_PATH_FILL_ZERO_NONZERO; |
391 | 0 | drawable_mark_modified(drawable, tr_state); |
392 | 0 | } |
393 | 0 | } |
394 | 0 | } |
395 | |
|
396 | 0 | if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) { |
397 | 0 | if (! compositor_svg_is_display_off(tr_state->svg_props)) { |
398 | 0 | DrawAspect2D asp; |
399 | 0 | gf_path_get_bounds(drawable->path, &tr_state->bounds); |
400 | 0 | if (!tr_state->ignore_strike) { |
401 | 0 | memset(&asp, 0, sizeof(DrawAspect2D)); |
402 | 0 | drawable_get_aspect_2d_svg(node, &asp, tr_state); |
403 | 0 | if (asp.pen_props.width) { |
404 | 0 | StrikeInfo2D *si = drawable_get_strikeinfo(tr_state->visual->compositor, drawable, &asp, NULL, drawable->path, 0, NULL); |
405 | 0 | if (si && si->outline) { |
406 | 0 | gf_path_get_bounds(si->outline, &tr_state->bounds); |
407 | 0 | } |
408 | 0 | } |
409 | 0 | } |
410 | 0 | compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, NULL); |
411 | 0 | if (!tr_state->abort_bounds_traverse) |
412 | 0 | gf_mx2d_apply_rect(&tr_state->transform, &tr_state->bounds); |
413 | 0 | gf_sc_get_nodes_bounds(node, NULL, tr_state, NULL); |
414 | |
|
415 | 0 | compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, NULL); |
416 | 0 | } |
417 | 0 | } else if (tr_state->traversing_mode == TRAVERSE_SORT) { |
418 | | /*reset our flags - this may break reuse of nodes and change-detection in dirty-rect algo */ |
419 | 0 | gf_node_dirty_clear(node, 0); |
420 | |
|
421 | 0 | if (!compositor_svg_is_display_off(tr_state->svg_props) && |
422 | 0 | ( *(tr_state->svg_props->visibility) != SVG_VISIBILITY_HIDDEN) ) { |
423 | |
|
424 | 0 | compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &mx_3d); |
425 | |
|
426 | 0 | ctx = drawable_init_context_svg(drawable, tr_state, all_atts.clip_path); |
427 | 0 | if (ctx) { |
428 | 0 | if (is_svg_rect) { |
429 | 0 | if (ctx->aspect.fill_texture && ctx->aspect.fill_texture->transparent) {} |
430 | 0 | else if (GF_COL_A(ctx->aspect.fill_color) != 0xFF) {} |
431 | 0 | else if (ctx->transform.m[1] || ctx->transform.m[3]) {} |
432 | 0 | else { |
433 | 0 | ctx->flags &= ~CTX_IS_TRANSPARENT; |
434 | 0 | if (!ctx->aspect.pen_props.width) |
435 | 0 | ctx->flags |= CTX_NO_ANTIALIAS; |
436 | 0 | } |
437 | 0 | } |
438 | |
|
439 | 0 | if (all_atts.pathLength && all_atts.pathLength->type==SVG_NUMBER_VALUE) |
440 | 0 | ctx->aspect.pen_props.path_length = all_atts.pathLength->value; |
441 | |
|
442 | | #ifndef GPAC_DISABLE_3D |
443 | | if (tr_state->visual->type_3d) { |
444 | | if (!drawable->mesh) { |
445 | | drawable->mesh = new_mesh(); |
446 | | if (drawable->path) mesh_from_path(drawable->mesh, drawable->path); |
447 | | } |
448 | | visual_3d_draw_from_context(ctx, tr_state); |
449 | | ctx->drawable = NULL; |
450 | | } else |
451 | | #endif |
452 | 0 | { |
453 | 0 | drawable_finalize_sort(ctx, tr_state, NULL); |
454 | 0 | } |
455 | 0 | } |
456 | 0 | compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx_3d); |
457 | 0 | } |
458 | 0 | } |
459 | |
|
460 | 0 | memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers)); |
461 | 0 | tr_state->svg_flags = backup_flags; |
462 | 0 | } |
463 | | |
464 | | static GF_Err svg_rect_add_arc(GF_Path *gp, Fixed end_x, Fixed end_y, Fixed cx, Fixed cy, Fixed rx, Fixed ry) |
465 | 0 | { |
466 | 0 | Fixed angle, start_angle, end_angle, sweep, _vx, _vy, start_x, start_y; |
467 | 0 | s32 i, num_steps; |
468 | |
|
469 | 0 | if (!gp->n_points) return GF_BAD_PARAM; |
470 | | |
471 | 0 | start_x = gp->points[gp->n_points-1].x; |
472 | 0 | start_y = gp->points[gp->n_points-1].y; |
473 | | |
474 | | //start angle and end angle |
475 | 0 | start_angle = gf_atan2(start_y-cy, start_x-cx); |
476 | 0 | end_angle = gf_atan2(end_y-cy, end_x-cx); |
477 | 0 | sweep = end_angle - start_angle; |
478 | |
|
479 | 0 | if (sweep<0) sweep += 2*GF_PI; |
480 | |
|
481 | 0 | num_steps = 16; |
482 | 0 | for (i=1; i<=num_steps; i++) { |
483 | 0 | angle = start_angle + sweep*i/num_steps; |
484 | 0 | _vx = cx + gf_mulfix(rx, gf_cos(angle)); |
485 | 0 | _vy = cy + gf_mulfix(ry, gf_sin(angle)); |
486 | 0 | gf_path_add_line_to(gp, _vx, _vy); |
487 | 0 | } |
488 | 0 | return GF_OK; |
489 | 0 | } |
490 | | |
491 | | static void svg_rect_rebuild(GF_Node *node, Drawable *stack, SVGAllAttributes *atts) |
492 | 0 | { |
493 | 0 | Fixed rx = (atts->rx ? atts->rx->value : 0); |
494 | 0 | Fixed ry = (atts->ry ? atts->ry->value : 0); |
495 | 0 | Fixed x = (atts->x ? atts->x->value : 0); |
496 | 0 | Fixed y = (atts->y ? atts->y->value : 0); |
497 | 0 | Fixed width = (atts->width ? atts->width->value : 0); |
498 | 0 | Fixed height = (atts->height ? atts->height->value : 0); |
499 | |
|
500 | 0 | drawable_reset_path(stack); |
501 | 0 | if (!width || !height) return; |
502 | | |
503 | | /*we follow SVG 1.1 and not 1.2 !!*/ |
504 | 0 | if (rx || ry) { |
505 | 0 | Fixed cx, cy; |
506 | 0 | if (rx >= width/2) rx = width/2; |
507 | 0 | if (ry >= height/2) ry = height/2; |
508 | 0 | if (rx == 0) rx = ry; |
509 | 0 | if (ry == 0) ry = rx; |
510 | 0 | gf_path_add_move_to(stack->path, x+rx, y); |
511 | |
|
512 | 0 | if (width-rx!=rx) |
513 | 0 | gf_path_add_line_to(stack->path, x+width-rx, y); |
514 | |
|
515 | 0 | cx = x+width-rx; |
516 | 0 | cy = y+ry; |
517 | 0 | svg_rect_add_arc(stack->path, x+width, y+ry, cx, cy, rx, ry); |
518 | |
|
519 | 0 | if (height-ry!=ry) |
520 | 0 | gf_path_add_line_to(stack->path, x+width, y+height-ry); |
521 | |
|
522 | 0 | cx = x+width-rx; |
523 | 0 | cy = y+height-ry; |
524 | 0 | svg_rect_add_arc(stack->path, x+width-rx, y+height, cx, cy, rx, ry); |
525 | |
|
526 | 0 | if (width-rx!=rx) |
527 | 0 | gf_path_add_line_to(stack->path, x+rx, y+height); |
528 | |
|
529 | 0 | cx = x+rx; |
530 | 0 | cy = y+height-ry; |
531 | 0 | svg_rect_add_arc(stack->path, x, y+height-ry, cx, cy, rx, ry); |
532 | |
|
533 | 0 | if (height-ry!=ry) |
534 | 0 | gf_path_add_line_to(stack->path, x, y+ry); |
535 | |
|
536 | 0 | cx = x+rx; |
537 | 0 | cy = y+ry; |
538 | 0 | svg_rect_add_arc(stack->path, x+rx, y, cx, cy, rx, ry); |
539 | |
|
540 | 0 | gf_path_close(stack->path); |
541 | 0 | } else { |
542 | 0 | gf_path_add_move_to(stack->path, x, y); |
543 | 0 | gf_path_add_line_to(stack->path, x+width, y); |
544 | 0 | gf_path_add_line_to(stack->path, x+width, y+height); |
545 | 0 | gf_path_add_line_to(stack->path, x, y+height); |
546 | 0 | gf_path_close(stack->path); |
547 | 0 | } |
548 | 0 | } |
549 | | |
550 | | static void svg_traverse_rect(GF_Node *node, void *rs, Bool is_destroy) |
551 | 0 | { |
552 | 0 | svg_drawable_traverse(node, rs, is_destroy, svg_rect_rebuild, 1, 0); |
553 | 0 | } |
554 | | |
555 | | void compositor_init_svg_rect(GF_Compositor *compositor, GF_Node *node) |
556 | 0 | { |
557 | 0 | drawable_stack_new(compositor, node); |
558 | 0 | gf_node_set_callback_function(node, svg_traverse_rect); |
559 | 0 | } |
560 | | |
561 | | static void svg_circle_rebuild(GF_Node *node, Drawable *stack, SVGAllAttributes *atts) |
562 | 0 | { |
563 | 0 | Fixed r = 2*(atts->r ? atts->r->value : 0); |
564 | 0 | drawable_reset_path(stack); |
565 | 0 | gf_path_add_ellipse(stack->path, (atts->cx ? atts->cx->value : 0), (atts->cy ? atts->cy->value : 0), r, r); |
566 | 0 | } |
567 | | |
568 | | static void svg_traverse_circle(GF_Node *node, void *rs, Bool is_destroy) |
569 | 0 | { |
570 | 0 | svg_drawable_traverse(node, rs, is_destroy, svg_circle_rebuild, 0, 0); |
571 | 0 | } |
572 | | |
573 | | void compositor_init_svg_circle(GF_Compositor *compositor, GF_Node *node) |
574 | 0 | { |
575 | 0 | drawable_stack_new(compositor, node); |
576 | 0 | gf_node_set_callback_function(node, svg_traverse_circle); |
577 | 0 | } |
578 | | |
579 | | static void svg_ellipse_rebuild(GF_Node *node, Drawable *stack, SVGAllAttributes *atts) |
580 | 0 | { |
581 | 0 | drawable_reset_path(stack); |
582 | 0 | gf_path_add_ellipse(stack->path, (atts->cx ? atts->cx->value : 0), |
583 | 0 | (atts->cy ? atts->cy->value : 0), |
584 | 0 | (atts->rx ? 2*atts->rx->value : 0), |
585 | 0 | (atts->ry ? 2*atts->ry->value : 0)); |
586 | 0 | } |
587 | | static void svg_traverse_ellipse(GF_Node *node, void *rs, Bool is_destroy) |
588 | 0 | { |
589 | 0 | svg_drawable_traverse(node, rs, is_destroy, svg_ellipse_rebuild, 0, 0); |
590 | 0 | } |
591 | | |
592 | | void compositor_init_svg_ellipse(GF_Compositor *compositor, GF_Node *node) |
593 | 0 | { |
594 | 0 | drawable_stack_new(compositor, node); |
595 | 0 | gf_node_set_callback_function(node, svg_traverse_ellipse); |
596 | 0 | } |
597 | | |
598 | | static void svg_line_rebuild(GF_Node *node, Drawable *stack, SVGAllAttributes *atts) |
599 | 0 | { |
600 | 0 | drawable_reset_path(stack); |
601 | 0 | gf_path_add_move_to(stack->path, (atts->x1 ? atts->x1->value : 0), (atts->y1 ? atts->y1->value : 0)); |
602 | 0 | gf_path_add_line_to(stack->path, (atts->x2 ? atts->x2->value : 0), (atts->y2 ? atts->y2->value : 0)); |
603 | 0 | } |
604 | | static void svg_traverse_line(GF_Node *node, void *rs, Bool is_destroy) |
605 | 0 | { |
606 | 0 | svg_drawable_traverse(node, rs, is_destroy, svg_line_rebuild, 0, 0); |
607 | 0 | } |
608 | | |
609 | | void compositor_init_svg_line(GF_Compositor *compositor, GF_Node *node) |
610 | 0 | { |
611 | 0 | drawable_stack_new(compositor, node); |
612 | 0 | gf_node_set_callback_function(node, svg_traverse_line); |
613 | 0 | } |
614 | | |
615 | | static void svg_polyline_rebuild(GF_Node *node, Drawable *stack, SVGAllAttributes *atts) |
616 | 0 | { |
617 | 0 | u32 i, nbPoints; |
618 | 0 | drawable_reset_path(stack); |
619 | 0 | if (atts->points) |
620 | 0 | nbPoints = gf_list_count(*atts->points); |
621 | 0 | else |
622 | 0 | nbPoints = 0; |
623 | |
|
624 | 0 | if (nbPoints) { |
625 | 0 | SVG_Point *p = (SVG_Point *)gf_list_get(*atts->points, 0); |
626 | 0 | gf_path_add_move_to(stack->path, p->x, p->y); |
627 | 0 | for (i = 1; i < nbPoints; i++) { |
628 | 0 | p = (SVG_Point *)gf_list_get(*atts->points, i); |
629 | 0 | gf_path_add_line_to(stack->path, p->x, p->y); |
630 | 0 | } |
631 | 0 | } else { |
632 | 0 | gf_path_add_move_to(stack->path, 0, 0); |
633 | 0 | } |
634 | 0 | } |
635 | | static void svg_traverse_polyline(GF_Node *node, void *rs, Bool is_destroy) |
636 | 0 | { |
637 | 0 | svg_drawable_traverse(node, rs, is_destroy, svg_polyline_rebuild, 0, 0); |
638 | 0 | } |
639 | | |
640 | | void compositor_init_svg_polyline(GF_Compositor *compositor, GF_Node *node) |
641 | 0 | { |
642 | 0 | drawable_stack_new(compositor, node); |
643 | 0 | gf_node_set_callback_function(node, svg_traverse_polyline); |
644 | 0 | } |
645 | | |
646 | | static void svg_polygon_rebuild(GF_Node *node, Drawable *stack, SVGAllAttributes *atts) |
647 | 0 | { |
648 | 0 | u32 i, nbPoints; |
649 | 0 | drawable_reset_path(stack); |
650 | 0 | if (atts->points) |
651 | 0 | nbPoints = gf_list_count(*atts->points); |
652 | 0 | else |
653 | 0 | nbPoints = 0; |
654 | |
|
655 | 0 | if (nbPoints) { |
656 | 0 | SVG_Point *p = (SVG_Point *)gf_list_get(*atts->points, 0); |
657 | 0 | gf_path_add_move_to(stack->path, p->x, p->y); |
658 | 0 | for (i = 1; i < nbPoints; i++) { |
659 | 0 | p = (SVG_Point *)gf_list_get(*atts->points, i); |
660 | 0 | gf_path_add_line_to(stack->path, p->x, p->y); |
661 | 0 | } |
662 | 0 | } else { |
663 | 0 | gf_path_add_move_to(stack->path, 0, 0); |
664 | 0 | } |
665 | | /*according to the spec, the polygon path is closed*/ |
666 | 0 | gf_path_close(stack->path); |
667 | 0 | } |
668 | | static void svg_traverse_polygon(GF_Node *node, void *rs, Bool is_destroy) |
669 | 0 | { |
670 | 0 | svg_drawable_traverse(node, rs, is_destroy, svg_polygon_rebuild, 0, 0); |
671 | 0 | } |
672 | | |
673 | | void compositor_init_svg_polygon(GF_Compositor *compositor, GF_Node *node) |
674 | 0 | { |
675 | 0 | drawable_stack_new(compositor, node); |
676 | 0 | gf_node_set_callback_function(node, svg_traverse_polygon); |
677 | 0 | } |
678 | | |
679 | | |
680 | | static void svg_path_rebuild(GF_Node *node, Drawable *stack, SVGAllAttributes *atts) |
681 | 0 | { |
682 | 0 | #if USE_GF_PATH |
683 | 0 | drawable_reset_path_outline(stack); |
684 | 0 | stack->path = atts->d; |
685 | | #else |
686 | | drawable_reset_path(stack); |
687 | | gf_svg_path_build(stack->path, atts->d->commands, atts->d->points); |
688 | | #endif |
689 | 0 | } |
690 | | |
691 | | static void svg_traverse_path(GF_Node *node, void *rs, Bool is_destroy) |
692 | 0 | { |
693 | 0 | svg_drawable_traverse(node, rs, is_destroy, svg_path_rebuild, 0, 1); |
694 | 0 | } |
695 | | |
696 | | void compositor_init_svg_path(GF_Compositor *compositor, GF_Node *node) |
697 | 0 | { |
698 | 0 | Drawable *dr = drawable_stack_new(compositor, node); |
699 | 0 | gf_path_del(dr->path); |
700 | | dr->path = NULL; |
701 | 0 | gf_node_set_callback_function(node, svg_traverse_path); |
702 | 0 | } |
703 | | |
704 | | #endif //defined(GPAC_DISABLE_SVG) |
705 | | |
706 | | #endif //!defined(GPAC_DISABLE_COMPOSITOR) |