This source file includes following definitions.
- svg_recompute_viewport_transformation
- svg_traverse_svg
- compositor_init_svg_svg
- compositor_svg_get_viewport
- svg_traverse_g
- compositor_init_svg_g
- svg_traverse_defs
- compositor_init_svg_defs
- svg_traverse_switch
- compositor_init_svg_switch
- svg_traverse_a
- is_timing_target
- svg_a_set_view
- svg_a_handle_event
- compositor_init_svg_a
- svg_traverse_resource
- svg_traverse_use
- compositor_init_svg_use
- svg_animation_smil_update
- svg_reset_xlink_target
- svg_animation_smil_evaluate
- svg_traverse_animation
- compositor_init_svg_animation
- svg_pause_animation
- svg_traverse_foreign_object
- compositor_init_svg_foreign_object
- compositor_svg_get_xlink_resource_node
- gf_sc_animation_get_scenegraph
#include "visual_manager.h"
#ifndef GPAC_DISABLE_SVG
#include "nodes_stacks.h"
#include "offscreen_cache.h"
#include <gpac/internal/terminal_dev.h>
#include <gpac/internal/scenegraph_dev.h>
#include <gpac/mediaobject.h>
#include <gpac/nodes_svg.h>
#include <gpac/compositor.h>
#include "mpeg4_grouping.h"
typedef struct
{
Bool root_svg;
SVGPropertiesPointers *svg_props;
GF_Matrix2D viewbox_mx;
Drawable *vp_fill;
u32 prev_color;
SFVec2f parent_vp;
SFVec2f vp;
Fixed dx, dy, vpw, vph;
} SVGsvgStack;
static void svg_recompute_viewport_transformation(GF_Node *node, SVGsvgStack *stack, GF_TraverseState *tr_state, SVGAllAttributes *atts)
{
GF_Matrix2D mx;
SVG_ViewBox ext_vb, *vb;
SVG_PreserveAspectRatio par;
Fixed scale, vp_w, vp_h;
Fixed parent_width, parent_height, doc_width, doc_height;
if (atts->width && (atts->width->type==SVG_NUMBER_PERCENTAGE) ) {
parent_width = gf_mulfix(tr_state->vp_size.x, atts->width->value/100);
doc_width = 0;
} else if (!stack->root_svg) {
doc_width = parent_width = atts->width ? atts->width->value : 0;
} else {
parent_width = tr_state->vp_size.x;
doc_width = atts->width ? atts->width->value : 0;
}
if (atts->height && (atts->height->type==SVG_NUMBER_PERCENTAGE) ) {
parent_height = gf_mulfix(tr_state->vp_size.y, atts->height->value/100);
doc_height = 0;
} else if (!stack->root_svg) {
doc_height = parent_height = atts->height ? atts->height->value : 0;
} else {
parent_height = tr_state->vp_size.y;
doc_height = atts->height ? atts->height->value : 0;
}
stack->vp = stack->parent_vp = tr_state->vp_size;
vb = atts->viewBox;
gf_mx2d_init(mx);
if (stack->root_svg && !tr_state->parent_is_use) {
const char *frag_uri = gf_scene_get_fragment_uri(node);
if (frag_uri) {
if (!strncmp(frag_uri, "svgView", 7)) {
if (!strncmp(frag_uri, "svgView(viewBox(", 16)) {
Float x, y, w, h;
sscanf(frag_uri, "svgView(viewBox(%f,%f,%f,%f))", &x, &y, &w, &h);
ext_vb.x = FLT2FIX(x);
ext_vb.y = FLT2FIX(y);
ext_vb.width = FLT2FIX(w);
ext_vb.height = FLT2FIX(h);
ext_vb.is_set = 1;
vb = &ext_vb;
}
else if (!strncmp(frag_uri, "svgView(transform(", 18)) {
Bool ret = gf_svg_parse_transformlist(&mx, (char *) frag_uri+18);
if (!ret) {
GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[SVG Parsing] Error parsing SVG View transform component: %s\n", frag_uri+18));
}
}
}
else {
GF_Node *target = gf_sg_find_node_by_name(gf_node_get_graph(node), (char *) frag_uri);
if (target) {
GF_Matrix2D mx;
GF_TraverseState bounds_state;
memset(&bounds_state, 0, sizeof(bounds_state));
bounds_state.traversing_mode = TRAVERSE_GET_BOUNDS;
bounds_state.visual = tr_state->visual;
bounds_state.for_node = target;
bounds_state.svg_props = tr_state->svg_props;
gf_mx2d_init(bounds_state.transform);
gf_mx2d_init(bounds_state.mx_at_node);
gf_mx_init(tr_state->visual->compositor->hit_world_to_local);
gf_sc_get_nodes_bounds(node, ((GF_ParentNode *)node)->children, &bounds_state, NULL);
gf_mx2d_from_mx(&mx, &tr_state->visual->compositor->hit_world_to_local);
gf_mx2d_apply_rect(&mx, &bounds_state.bounds);
ext_vb.x = bounds_state.bounds.x;
ext_vb.y = bounds_state.bounds.y-bounds_state.bounds.height;
ext_vb.width = bounds_state.bounds.width;
ext_vb.height = bounds_state.bounds.height;
ext_vb.is_set = 1;
vb = &ext_vb;
}
}
}
}
gf_mx2d_init(stack->viewbox_mx);
if (!vb) {
if (!doc_width || !doc_height) {
gf_mx2d_copy(stack->viewbox_mx, mx);
return;
}
ext_vb.x = 0;
ext_vb.y = 0;
ext_vb.width = doc_width;
ext_vb.height = doc_height;
ext_vb.is_set = 1;
vb = &ext_vb;
}
if ((vb->width<=0) || (vb->height<=0) ) {
gf_mx2d_copy(stack->viewbox_mx, mx);
return;
}
stack->vp.x = vb->width;
stack->vp.y = vb->height;
par.defer = 0;
par.meetOrSlice = SVG_MEETORSLICE_MEET;
par.align = SVG_PRESERVEASPECTRATIO_XMIDYMID;
if (tr_state->parent_anim_atts) {
if (tr_state->parent_anim_atts->preserveAspectRatio) {
if (tr_state->parent_anim_atts->preserveAspectRatio->defer) {
if (atts->preserveAspectRatio)
par = *atts->preserveAspectRatio;
} else {
par = *tr_state->parent_anim_atts->preserveAspectRatio;
}
}
}
else if (atts->preserveAspectRatio) {
par = *atts->preserveAspectRatio;
}
if (par.meetOrSlice==SVG_MEETORSLICE_MEET) {
if (gf_divfix(parent_width, vb->width) > gf_divfix(parent_height, vb->height)) {
scale = gf_divfix(parent_height, vb->height);
vp_w = gf_mulfix(vb->width, scale);
vp_h = parent_height;
} else {
scale = gf_divfix(parent_width, vb->width);
vp_w = parent_width;
vp_h = gf_mulfix(vb->height, scale);
}
} else {
if (gf_divfix(parent_width, vb->width) < gf_divfix(parent_height, vb->height)) {
scale = gf_divfix(parent_height, vb->height);
vp_w = gf_mulfix(vb->width, scale);
vp_h = parent_height;
} else {
scale = gf_divfix(parent_width, vb->width);
vp_w = parent_width;
vp_h = gf_mulfix(vb->height, scale);
}
}
if (par.align==SVG_PRESERVEASPECTRATIO_NONE) {
stack->viewbox_mx.m[0] = gf_divfix(parent_width, vb->width);
stack->viewbox_mx.m[4] = gf_divfix(parent_height, vb->height);
stack->viewbox_mx.m[2] = - gf_muldiv(vb->x, parent_width, vb->width);
stack->viewbox_mx.m[5] = - gf_muldiv(vb->y, parent_height, vb->height);
} else {
Fixed dx, dy;
stack->viewbox_mx.m[0] = stack->viewbox_mx.m[4] = scale;
stack->viewbox_mx.m[2] = - gf_mulfix(vb->x, scale);
stack->viewbox_mx.m[5] = - gf_mulfix(vb->y, scale);
dx = dy = 0;
switch (par.align) {
case SVG_PRESERVEASPECTRATIO_XMINYMIN:
break;
case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
dx = ( parent_width - vp_w) / 2;
break;
case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
dx = parent_width - vp_w;
break;
case SVG_PRESERVEASPECTRATIO_XMINYMID:
dy = ( parent_height - vp_h) / 2;
break;
case SVG_PRESERVEASPECTRATIO_XMIDYMID:
dx = ( parent_width - vp_w) / 2;
dy = ( parent_height - vp_h) / 2;
break;
case SVG_PRESERVEASPECTRATIO_XMAXYMID:
dx = parent_width - vp_w;
dy = ( parent_height - vp_h) / 2;
break;
case SVG_PRESERVEASPECTRATIO_XMINYMAX:
dy = parent_height - vp_h;
break;
case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
dx = (parent_width - vp_w) / 2;
dy = parent_height - vp_h;
break;
case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
dx = parent_width - vp_w;
dy = parent_height - vp_h;
break;
}
gf_mx2d_add_translation(&stack->viewbox_mx, dx, dy);
stack->dx = dx;
stack->dy = dy;
stack->vpw = vp_w;
stack->vph = vp_h;
if (stack->root_svg && !tr_state->parent_anim_atts && (par.meetOrSlice==SVG_MEETORSLICE_SLICE)) {
GF_Rect rc;
rc.width = parent_width;
rc.height = parent_height;
if (!stack->root_svg) {
rc.x = 0;
rc.y = parent_height;
gf_mx2d_apply_rect(&stack->viewbox_mx, &rc);
} else {
rc.x = dx;
rc.y = dy + parent_height;
}
}
}
gf_mx2d_add_matrix(&stack->viewbox_mx, &mx);
}
static void svg_traverse_svg(GF_Node *node, void *rs, Bool is_destroy)
{
Bool rootmost_svg, send_resize;
u32 viewport_color;
SVGsvgStack *stack;
GF_Matrix2D backup_matrix, vb_bck;
#ifndef GPAC_DISABLE_3D
GF_Matrix bck_mx;
#endif
Bool is_dirty;
GF_IRect top_clip;
SFVec2f prev_vp;
SVGPropertiesPointers backup_props, *prev_props;
u32 backup_flags;
Bool invalidate_flag;
u32 styling_size = sizeof(SVGPropertiesPointers);
GF_TraverseState *tr_state = (GF_TraverseState *) rs;
SVGAllAttributes all_atts;
stack = gf_node_get_private(node);
if (is_destroy) {
if (stack->svg_props) {
gf_svg_properties_reset_pointers(stack->svg_props);
gf_free(stack->svg_props);
}
gf_sc_check_focus_upon_destroy(node);
if (stack->vp_fill) drawable_del(stack->vp_fill);
gf_free(stack);
return;
}
prev_props = tr_state->svg_props;
if (!tr_state->svg_props) {
tr_state->svg_props = stack->svg_props;
if (!tr_state->svg_props) return;
}
gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
if (!compositor_svg_traverse_base(node, &all_atts, tr_state, &backup_props, &backup_flags)) {
tr_state->svg_props = prev_props;
return;
}
tr_state->visual->compositor->navigation_disabled = (all_atts.zoomAndPan && *all_atts.zoomAndPan == SVG_ZOOMANDPAN_DISABLE) ? 1 : 0;
if (compositor_svg_is_display_off(tr_state->svg_props)) {
memcpy(tr_state->svg_props, &backup_props, styling_size);
tr_state->svg_flags = backup_flags;
return;
}
top_clip = tr_state->visual->top_clipper;
gf_mx2d_copy(backup_matrix, tr_state->transform);
gf_mx2d_copy(vb_bck, tr_state->vb_transform);
#ifndef GPAC_DISABLE_3D
gf_mx_copy(bck_mx, tr_state->model_matrix);
#endif
invalidate_flag = tr_state->invalidate_all;
is_dirty = gf_node_dirty_get(node);
if (is_dirty & GF_SG_CHILD_DIRTY) drawable_reset_group_highlight(tr_state, node);
gf_node_dirty_clear(node, 0);
send_resize = 0;
if ((stack->parent_vp.x != tr_state->vp_size.x) || (stack->parent_vp.y != tr_state->vp_size.y)) {
is_dirty = 1;
send_resize = 1;
}
if (is_dirty || tr_state->visual->compositor->recompute_ar) {
svg_recompute_viewport_transformation(node, stack, tr_state, &all_atts);
}
gf_mx2d_copy(tr_state->vb_transform, stack->viewbox_mx);
rootmost_svg = (stack->root_svg && !tr_state->parent_anim_atts) ? 1 : 0;
if (tr_state->traversing_mode == TRAVERSE_SORT) {
SVG_Paint *vp_fill = NULL;
Fixed vp_opacity;
if (tr_state->parent_anim_atts) {
vp_fill = tr_state->parent_anim_atts->viewport_fill;
vp_opacity = tr_state->parent_anim_atts->viewport_fill_opacity ? tr_state->parent_anim_atts->viewport_fill_opacity->value : FIX_ONE;
} else {
vp_fill = tr_state->svg_props->viewport_fill;
vp_opacity = tr_state->svg_props->viewport_fill_opacity ? tr_state->svg_props->viewport_fill_opacity->value : FIX_ONE;
}
if (vp_fill && (vp_fill->type != SVG_PAINT_NONE) && vp_opacity) {
Bool col_dirty = 0;
viewport_color = GF_COL_ARGB_FIXED(vp_opacity, vp_fill->color.red, vp_fill->color.green, vp_fill->color.blue);
if (stack->prev_color != viewport_color) {
stack->prev_color = viewport_color;
col_dirty = 1;
}
if (!rootmost_svg) {
DrawableContext *ctx;
Fixed width = tr_state->parent_anim_atts ? tr_state->parent_anim_atts->width->value : 0;
Fixed height = tr_state->parent_anim_atts ? tr_state->parent_anim_atts->height->value : 0;
if (!stack->vp_fill) {
stack->vp_fill = drawable_new();
stack->vp_fill->node = node;
}
if ((width != stack->vp_fill->path->bbox.width) || (height != stack->vp_fill->path->bbox.height)) {
drawable_reset_path(stack->vp_fill);
gf_path_add_rect(stack->vp_fill->path, 0, 0, width, -height);
}
ctx = drawable_init_context_svg(stack->vp_fill, tr_state);
if (ctx) {
ctx->flags &= ~CTX_IS_TRANSPARENT;
ctx->aspect.pen_props.width = 0;
ctx->aspect.fill_color = viewport_color;
ctx->aspect.fill_texture = NULL;
if (col_dirty) ctx->flags |= CTX_APP_DIRTY;
drawable_finalize_sort(ctx, tr_state, NULL);
}
} else if (col_dirty) {
tr_state->visual->compositor->back_color = viewport_color;
tr_state->invalidate_all = 1;
}
}
}
if (!stack->root_svg)
gf_mx2d_add_translation(&tr_state->vb_transform, all_atts.x ? all_atts.x->value : 0, all_atts.y ? all_atts.y->value : 0);
#ifndef GPAC_DISABLE_3D
if (tr_state->visual->type_3d) {
gf_mx_add_matrix_2d(&tr_state->model_matrix, &tr_state->vb_transform);
} else
#endif
{
gf_mx2d_pre_multiply(&tr_state->transform, &tr_state->vb_transform);
}
prev_vp = tr_state->vp_size;
tr_state->vp_size = stack->vp;
if (send_resize) {
GF_DOM_Event evt;
memset(&evt, 0, sizeof(GF_DOM_Event));
evt.bubbles = 1;
evt.type = GF_EVENT_RESIZE;
gf_dom_event_fire(node, &evt);
}
if ((stack->vp.x != prev_vp.x) || (stack->vp.y != prev_vp.y)) {
GF_Scene *scene = node->sgprivate->scenegraph->userpriv;
if (scene) {
GF_DOM_Event evt;
memset(&evt, 0, sizeof(GF_DOM_Event));
evt.bubbles = 0;
evt.screen_rect.width = stack->vpw;
evt.screen_rect.height = stack->vph;
evt.screen_rect.x = stack->dx;
evt.screen_rect.y = stack->dy;
evt.prev_translate.x = stack->vp.x;
evt.prev_translate.y = stack->vp.y;
evt.type = GF_EVENT_VP_RESIZE;
gf_scene_notify_event(scene, 0, NULL, &evt, GF_OK, GF_TRUE);
}
}
if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
gf_sc_get_nodes_bounds(node, ((SVG_Element *)node)->children, tr_state, NULL);
} else {
compositor_svg_traverse_children(((SVG_Element *)node)->children, tr_state);
}
tr_state->vp_size = prev_vp;
#ifndef GPAC_DISABLE_3D
if (tr_state->visual->type_3d) {
gf_mx_copy(tr_state->model_matrix, bck_mx);
}
#endif
gf_mx2d_copy(tr_state->transform, backup_matrix);
gf_mx2d_copy(tr_state->vb_transform, vb_bck);
memcpy(tr_state->svg_props, &backup_props, styling_size);
tr_state->svg_flags = backup_flags;
tr_state->visual->top_clipper = top_clip;
if (!stack->root_svg) {
tr_state->invalidate_all = invalidate_flag;
}
tr_state->svg_props = prev_props;
}
void compositor_init_svg_svg(GF_Compositor *compositor, GF_Node *node)
{
GF_Node *root;
SVGsvgStack *stack;
GF_SAFEALLOC(stack, SVGsvgStack);
if (!stack) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate svg stack\n"));
return;
}
root = gf_sg_get_root_node(gf_node_get_graph(node));
stack->root_svg = (root==node) ? 1 : 0;
if (stack->root_svg) {
GF_SAFEALLOC(stack->svg_props, SVGPropertiesPointers);
gf_svg_properties_init_pointers(stack->svg_props);
}
gf_mx2d_init(stack->viewbox_mx);
gf_node_set_private(node, stack);
gf_node_set_callback_function(node, svg_traverse_svg);
}
Bool compositor_svg_get_viewport(GF_Node *n, GF_Rect *rc)
{
SVGsvgStack *stack;
if (gf_node_get_tag(n) != TAG_SVG_svg) return 0;
stack = gf_node_get_private(n);
rc->width = stack->parent_vp.x;
rc->height = stack->parent_vp.y;
rc->x = rc->y = 0;
return 1;
}
typedef struct
{
GROUPING_NODE_STACK_2D
#ifndef GF_SR_USE_VIDEO_CACHE
struct _group_cache *cache;
#endif
} SVGgStack;
static void svg_traverse_g(GF_Node *node, void *rs, Bool is_destroy)
{
GF_Matrix2D backup_matrix;
GF_Matrix mx_3d;
SVGPropertiesPointers backup_props;
u32 backup_flags;
u32 styling_size = sizeof(SVGPropertiesPointers);
GF_TraverseState *tr_state = (GF_TraverseState *) rs;
SVGAllAttributes all_atts;
if (is_destroy) {
SVGgStack *group = gf_node_get_private(node);
#ifdef GF_SR_USE_VIDEO_CACHE
group_2d_destroy_svg(node, group);
#else
if (group->cache) group_cache_del(group->cache);
#endif
gf_free(group);
gf_sc_check_focus_upon_destroy(node);
return;
}
else if ((tr_state->traversing_mode == TRAVERSE_DRAW_2D)
#ifndef GPAC_DISABLE_3D
|| (tr_state->traversing_mode == TRAVERSE_DRAW_3D)
#endif
) {
SVGgStack *group = gf_node_get_private(node);
group_cache_draw(group->cache, tr_state);
return;
}
gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
if (!compositor_svg_traverse_base(node, &all_atts, tr_state, &backup_props, &backup_flags))
return;
if (compositor_svg_is_display_off(tr_state->svg_props)) {
memcpy(tr_state->svg_props, &backup_props, styling_size);
tr_state->svg_flags = backup_flags;
return;
}
compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &mx_3d);
if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
gf_sc_get_nodes_bounds(node, ((SVG_Element *)node)->children, tr_state, NULL);
} else if (tr_state->traversing_mode == TRAVERSE_SORT) {
#ifdef GF_SR_USE_DEPTH
Fixed scale, offset, dscale, doffset;
#endif
Fixed opacity = FIX_ONE;
Bool clear = 0;
SVGgStack *group;
if (!tr_state->in_svg_filter && all_atts.filter && all_atts.filter->iri.target) {
svg_draw_filter(all_atts.filter->iri.target, node, tr_state);
return;
}
group = gf_node_get_private(node);
if (tr_state->parent_use_opacity) {
opacity = tr_state->parent_use_opacity->value;
tr_state->parent_use_opacity = NULL;
}
if (all_atts.opacity) {
opacity = gf_mulfix(opacity, all_atts.opacity->value);
}
if (gf_node_dirty_get(node)&GF_SG_CHILD_DIRTY) {
drawable_reset_group_highlight(tr_state, node);
clear=1;
}
#ifdef GF_SR_USE_DEPTH
dscale = FIX_ONE;
doffset=0;
if (all_atts.gpac_depthGain && all_atts.gpac_depthGain->type==SVG_NUMBER_VALUE) dscale = all_atts.gpac_depthGain->value;
if (all_atts.gpac_depthOffset && all_atts.gpac_depthOffset->type==SVG_NUMBER_VALUE) doffset = all_atts.gpac_depthOffset->value;
scale = tr_state->depth_gain;
offset = tr_state->depth_offset;
tr_state->depth_offset = gf_mulfix(doffset, scale) + offset;
tr_state->depth_gain = gf_mulfix(scale, dscale);
#endif
if (!tr_state->override_appearance && (opacity < FIX_ONE)) {
if (!group->cache) {
group->cache = group_cache_new(tr_state->visual->compositor, node);
group->cache->force_recompute = 1;
}
group->cache->opacity = opacity;
if (tr_state->visual->compositor->zoom_changed)
group->cache->force_recompute = 1;
group->flags |= GROUP_IS_CACHED | GROUP_PERMANENT_CACHE;
#ifdef GF_SR_USE_VIDEO_CACHE
group_2d_cache_traverse(node, group, tr_state);
#else
group_cache_traverse(node, group->cache, tr_state, group->cache->force_recompute, 0, 0);
#endif
} else {
#ifdef GF_SR_USE_VIDEO_CACHE
Bool group_cached;
group_cached = group_2d_cache_traverse(node, group, tr_state);
gf_node_dirty_clear(node, GF_SG_CHILD_DIRTY);
if (!group_cached) {
GF_ChildNodeItem *child;
DrawableContext *first_ctx = tr_state->visual->cur_context;
u32 cache_too_small = 0;
Bool skip_first_ctx = (first_ctx && first_ctx->drawable) ? 1 : 0;
u32 traverse_time = gf_sys_clock();
u32 last_cache_idx = gf_list_count(tr_state->visual->compositor->cached_groups_queue);
tr_state->cache_too_small = 0;
child = ((GF_ParentNode *)node)->children;
while (child) {
gf_node_traverse(child->node, tr_state);
child = child->next;
if (tr_state->cache_too_small)
cache_too_small++;
}
if (cache_too_small) {
tr_state->cache_too_small = 1;
} else {
traverse_time = gf_sys_clock() - traverse_time;
group->traverse_time += traverse_time;
group_2d_cache_evaluate(node, group, tr_state, first_ctx, skip_first_ctx, last_cache_idx);
}
}
#else
compositor_svg_traverse_children(((SVG_Element *)node)->children, tr_state);
#endif
}
if (clear) gf_node_dirty_clear(node, 0);
drawable_check_focus_highlight(node, tr_state, NULL);
#ifdef GF_SR_USE_DEPTH
tr_state->depth_gain = scale;
tr_state->depth_offset = offset;
#endif
} else {
compositor_svg_traverse_children(((SVG_Element *)node)->children, tr_state);
}
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx_3d);
memcpy(tr_state->svg_props, &backup_props, styling_size);
tr_state->svg_flags = backup_flags;
}
void compositor_init_svg_g(GF_Compositor *compositor, GF_Node *node)
{
SVGgStack *stack;
GF_SAFEALLOC(stack, SVGgStack);
gf_node_set_private(node, stack);
gf_node_set_callback_function(node, svg_traverse_g);
}
static void svg_traverse_defs(GF_Node *node, void *rs, Bool is_destroy)
{
SVGPropertiesPointers backup_props;
u32 prev_flags, backup_flags;
u32 styling_size = sizeof(SVGPropertiesPointers);
GF_TraverseState *tr_state = (GF_TraverseState *) rs;
SVGAllAttributes all_atts;
if (is_destroy) {
gf_sc_check_focus_upon_destroy(node);
return;
}
gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
if (!compositor_svg_traverse_base(node, &all_atts, tr_state, &backup_props, &backup_flags))
return;
prev_flags = tr_state->switched_off;
tr_state->switched_off = 1;
compositor_svg_traverse_children(((SVG_Element *)node)->children, tr_state);
tr_state->switched_off = prev_flags;
memcpy(tr_state->svg_props, &backup_props, styling_size);
tr_state->svg_flags = backup_flags;
}
void compositor_init_svg_defs(GF_Compositor *compositor, GF_Node *node)
{
gf_node_set_callback_function(node, svg_traverse_defs);
}
static void svg_traverse_switch(GF_Node *node, void *rs, Bool is_destroy)
{
GF_Matrix2D backup_matrix;
GF_Matrix mx_3d;
SVGPropertiesPointers backup_props;
u32 backup_flags;
s32 *selected_idx = gf_node_get_private(node);
u32 styling_size = sizeof(SVGPropertiesPointers);
SVGAllAttributes all_atts;
GF_TraverseState *tr_state = (GF_TraverseState *) rs;
if (is_destroy) {
gf_free(selected_idx);
gf_sc_check_focus_upon_destroy(node);
return;
}
gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
if (gf_node_dirty_get(node)) {
u32 pos = 0;
GF_ChildNodeItem *child = ((SVG_Element*)node)->children;
*selected_idx = -1;
while (child) {
SVGAllAttributes atts;
gf_svg_flatten_attributes((SVG_Element *)child->node, &atts);
if (compositor_svg_evaluate_conditional(tr_state->visual->compositor, &atts)) {
*selected_idx = pos;
break;
}
pos++;
child = child->next;
}
drawable_reset_group_highlight(tr_state, node);
gf_node_dirty_clear(node, 0);
}
if (!compositor_svg_traverse_base(node, &all_atts, tr_state, &backup_props, &backup_flags))
return;
if (compositor_svg_is_display_off(tr_state->svg_props)) {
memcpy(tr_state->svg_props, &backup_props, styling_size);
tr_state->svg_flags = backup_flags;
return;
}
if (*selected_idx >= 0) {
compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &mx_3d);
if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
gf_sc_get_nodes_bounds(node, ((SVG_Element *)node)->children, tr_state, selected_idx);
} else if (*selected_idx >= 0) {
GF_Node *child = gf_node_list_get_child(((SVG_Element *)node)->children, *selected_idx);
gf_node_traverse(child, tr_state);
drawable_check_focus_highlight(node, tr_state, NULL);
}
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx_3d);
}
memcpy(tr_state->svg_props, &backup_props, styling_size);
tr_state->svg_flags = backup_flags;
}
void compositor_init_svg_switch(GF_Compositor *compositor, GF_Node *node)
{
s32 *selected_idx;
GF_SAFEALLOC(selected_idx, s32);
if (!selected_idx) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate font for svg switch stack\n"));
return;
}
*selected_idx = -1;
gf_node_set_private(node, selected_idx);
gf_node_set_callback_function(node, svg_traverse_switch);
}
static void svg_traverse_a(GF_Node *node, void *rs, Bool is_destroy)
{
GF_Matrix2D backup_matrix;
GF_Matrix mx_3d;
SVGPropertiesPointers backup_props;
u32 styling_size = sizeof(SVGPropertiesPointers);
u32 backup_flags;
GF_TraverseState *tr_state = (GF_TraverseState *)rs;
SVGAllAttributes all_atts;
if (is_destroy) {
gf_sc_check_focus_upon_destroy(node);
return;
}
gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
if (!compositor_svg_traverse_base(node, &all_atts, tr_state, &backup_props, &backup_flags))
return;
if (compositor_svg_is_display_off(tr_state->svg_props)) {
memcpy(tr_state->svg_props, &backup_props, styling_size);
tr_state->svg_flags = backup_flags;
return;
}
compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &mx_3d);
if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
gf_sc_get_nodes_bounds(node, ((SVG_Element *)node)->children, tr_state, NULL);
} else {
compositor_svg_traverse_children(((SVG_Element *)node)->children, tr_state);
if (tr_state->traversing_mode==TRAVERSE_SORT)
drawable_check_focus_highlight(node, tr_state, NULL);
}
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx_3d);
memcpy(tr_state->svg_props, &backup_props, styling_size);
tr_state->svg_flags = backup_flags;
}
static Bool is_timing_target(GF_Node *n)
{
if (!n) return 0;
switch (gf_node_get_tag(n)) {
case TAG_SVG_set:
case TAG_SVG_animate:
case TAG_SVG_animateColor:
case TAG_SVG_animateTransform:
case TAG_SVG_animateMotion:
case TAG_SVG_discard:
case TAG_SVG_animation:
case TAG_SVG_video:
case TAG_SVG_audio:
return 1;
}
return 0;
}
static void svg_a_set_view(GF_Node *handler, GF_Compositor *compositor, const char *url)
{
gf_scene_set_fragment_uri(handler, url);
gf_node_dirty_set(gf_sg_get_root_node(gf_node_get_graph(handler)), 0, 0);
compositor->trans_x = compositor->trans_y = 0;
compositor->rotation = 0;
compositor->zoom = FIX_ONE;
compositor_2d_set_user_transform(compositor, FIX_ONE, 0, 0, 0);
gf_sc_invalidate(compositor, NULL);
}
static void svg_a_handle_event(GF_Node *handler, GF_DOM_Event *event, GF_Node *observer)
{
GF_Compositor *compositor;
GF_Event evt;
SVG_Element *a;
SVGAllAttributes all_atts;
if (event->event_phase & GF_DOM_EVENT_PHASE_PREVENT) return;
assert(gf_node_get_tag((GF_Node*)event->currentTarget->ptr)==TAG_SVG_a);
a = (SVG_Element *) event->currentTarget->ptr;
gf_svg_flatten_attributes(a, &all_atts);
compositor = (GF_Compositor *)gf_node_get_private((GF_Node *)handler);
if (!compositor->user->EventProc) return;
if (!all_atts.xlink_href) return;
if (event->type==GF_EVENT_MOUSEOVER) {
evt.type = GF_EVENT_NAVIGATE_INFO;
if (all_atts.xlink_title) evt.navigate.to_url = *all_atts.xlink_title;
else if (all_atts.xlink_href->string) evt.navigate.to_url = all_atts.xlink_href->string;
else {
evt.navigate.to_url = gf_node_get_name(all_atts.xlink_href->target);
if (!evt.navigate.to_url) evt.navigate.to_url = "document internal link";
}
gf_term_send_event(compositor->term, &evt);
return;
}
evt.type = GF_EVENT_NAVIGATE;
if (all_atts.xlink_href->type == XMLRI_STRING) {
evt.navigate.to_url = gf_term_resolve_xlink(handler, all_atts.xlink_href->string);
if (evt.navigate.to_url) {
if (all_atts.target) {
evt.navigate.parameters = (const char **) &all_atts.target;
evt.navigate.param_count = 1;
} else {
evt.navigate.parameters = NULL;
evt.navigate.param_count = 0;
}
if (evt.navigate.to_url[0] != '#') {
if (compositor->term) {
gf_scene_process_anchor(handler, &evt);
} else {
gf_term_send_event(compositor->term, &evt);
}
gf_free((char *)evt.navigate.to_url);
return;
}
all_atts.xlink_href->target = gf_sg_find_node_by_name(gf_node_get_graph(handler), (char *) evt.navigate.to_url+1);
if (all_atts.xlink_href->target) {
all_atts.xlink_href->type = XMLRI_ELEMENTID;
gf_free((char *)evt.navigate.to_url);
} else {
svg_a_set_view(handler, compositor, evt.navigate.to_url + 1);
gf_free((char *)evt.navigate.to_url);
return;
}
}
}
if (!all_atts.xlink_href->target) {
return;
}
if (is_timing_target(all_atts.xlink_href->target)) {
gf_smil_timing_insert_clock(all_atts.xlink_href->target, 0, gf_node_get_scene_time((GF_Node *)handler) );
}
else {
svg_a_set_view(handler, compositor, gf_node_get_name(all_atts.xlink_href->target));
}
}
void compositor_init_svg_a(GF_Compositor *compositor, GF_Node *node)
{
SVG_handlerElement *handler;
gf_node_set_callback_function(node, svg_traverse_a);
handler = gf_dom_listener_build(node, GF_EVENT_CLICK, 0);
handler->handle_event = svg_a_handle_event;
gf_node_set_private((GF_Node *)handler, compositor);
handler = gf_dom_listener_build(node, GF_EVENT_ACTIVATE, 0);
handler->handle_event = svg_a_handle_event;
gf_node_set_private((GF_Node *)handler, compositor);
handler = gf_dom_listener_build(node, GF_EVENT_MOUSEOVER, 0);
handler->handle_event = svg_a_handle_event;
gf_node_set_private((GF_Node *)handler, compositor);
}
typedef struct
{
GF_MediaObject *resource;
GF_SceneGraph *inline_sg;
const char *fragment_id;
Bool needs_play;
u32 init_vis_state;
} SVGlinkStack;
static void svg_traverse_resource(GF_Node *node, void *rs, Bool is_destroy, Bool is_foreign_object)
{
GF_Matrix2D backup_matrix;
GF_Matrix mx_3d;
GF_Matrix2D translate;
SVGPropertiesPointers backup_props;
u32 backup_flags, dirty;
Bool is_fragment, parent_is_use;
GF_Node *used_node;
GF_TraverseState *tr_state = (GF_TraverseState *)rs;
SVGAllAttributes all_atts;
SVGlinkStack *stack = gf_node_get_private(node);
SFVec2f prev_vp;
SVG_Number *prev_opacity;
if (is_destroy) {
if (stack->resource) gf_mo_unload_xlink_resource(node, stack->resource);
gf_free(stack);
return;
}
gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
if (!all_atts.xlink_href) return;
if (!compositor_svg_traverse_base(node, &all_atts, tr_state, &backup_props, &backup_flags))
return;
dirty = gf_node_dirty_get(node);
if (dirty & GF_SG_CHILD_DIRTY) drawable_reset_group_highlight(tr_state, node);
if (dirty & GF_SG_SVG_XLINK_HREF_DIRTY) {
stack->fragment_id = NULL;
stack->inline_sg = NULL;
if (all_atts.xlink_href->string && (all_atts.xlink_href->string[0]=='#')) {
stack->fragment_id = all_atts.xlink_href->string;
stack->inline_sg = gf_node_get_graph(node);
} else {
GF_MediaObject *new_res = gf_mo_load_xlink_resource(node, is_foreign_object, 0, -1);
if (new_res != stack->resource) {
if (stack->resource) gf_mo_unload_xlink_resource(node, stack->resource);
stack->resource = new_res;
}
}
}
gf_node_dirty_clear(node, 0);
is_fragment = 0;
used_node = NULL;
if (!stack->inline_sg && !stack->fragment_id && all_atts.xlink_href) {
if (all_atts.xlink_href->type == XMLRI_ELEMENTID) {
used_node = all_atts.xlink_href->target;
is_fragment = 1;
} else if (stack->resource) {
stack->inline_sg = gf_mo_get_scenegraph(stack->resource);
if (!is_foreign_object) {
stack->fragment_id = strchr(all_atts.xlink_href->string, '#');
}
}
}
if (!used_node && stack->inline_sg) {
if (stack->fragment_id) {
used_node = gf_sg_find_node_by_name(stack->inline_sg, (char *) stack->fragment_id+1);
is_fragment = 1;
} else if (is_foreign_object) {
used_node = gf_sg_get_root_node(stack->inline_sg);
}
}
if (!used_node) goto end;
gf_list_add(tr_state->use_stack, used_node);
gf_list_add(tr_state->use_stack, node);
gf_mx2d_init(translate);
translate.m[2] = (all_atts.x ? all_atts.x->value : 0);
translate.m[5] = (all_atts.y ? all_atts.y->value : 0);
prev_vp = tr_state->vp_size;
if (all_atts.width && all_atts.height) {
tr_state->vp_size.x = gf_sc_svg_convert_length_to_display(tr_state->visual->compositor, all_atts.width);
tr_state->vp_size.y = gf_sc_svg_convert_length_to_display(tr_state->visual->compositor, all_atts.height);
}
prev_opacity = tr_state->parent_use_opacity;
tr_state->parent_use_opacity = all_atts.opacity;
parent_is_use = tr_state->parent_is_use;
tr_state->parent_is_use = is_foreign_object ? 0 : 1;
if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &mx_3d);
if (!compositor_svg_is_display_off(tr_state->svg_props)) {
gf_node_traverse(used_node, tr_state);
gf_mx2d_apply_rect(&translate, &tr_state->bounds);
}
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx_3d);
}
else if (!compositor_svg_is_display_off(tr_state->svg_props)
&& (*(tr_state->svg_props->visibility) != SVG_VISIBILITY_HIDDEN)) {
compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &mx_3d);
#ifndef GPAC_DISABLE_3D
if (tr_state->visual->type_3d) {
gf_mx_add_matrix_2d(&tr_state->model_matrix, &translate);
} else
#endif
gf_mx2d_pre_multiply(&tr_state->transform, &translate);
drawable_check_focus_highlight(node, tr_state, NULL);
if (is_fragment) {
gf_node_traverse(used_node, tr_state);
} else {
gf_sc_traverse_subscene(tr_state->visual->compositor, node, stack->inline_sg, tr_state);
}
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &mx_3d);
}
gf_list_rem_last(tr_state->use_stack);
gf_list_rem_last(tr_state->use_stack);
tr_state->vp_size = prev_vp;
tr_state->parent_is_use = parent_is_use;
tr_state->parent_use_opacity = prev_opacity;
end:
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
tr_state->svg_flags = backup_flags;
}
static void svg_traverse_use(GF_Node *node, void *rs, Bool is_destroy)
{
svg_traverse_resource(node, rs, is_destroy, 0);
}
void compositor_init_svg_use(GF_Compositor *compositor, GF_Node *node)
{
SVGlinkStack *stack;
GF_SAFEALLOC(stack, SVGlinkStack);
gf_node_set_private(node, stack);
gf_node_set_callback_function(node, svg_traverse_use);
gf_node_dirty_set(node, GF_SG_SVG_XLINK_HREF_DIRTY, 0);
}
static void svg_animation_smil_update(GF_Node *node, SVGlinkStack *stack, Fixed normalized_scene_time)
{
if (stack->init_vis_state == 3) {
stack->init_vis_state = 4;
gf_mo_resume(stack->resource);
} else if (stack->needs_play || (gf_node_dirty_get(node) & GF_SG_SVG_XLINK_HREF_DIRTY )) {
SVGAllAttributes all_atts;
Double clipBegin, clipEnd;
GF_MediaObject *new_res;
gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
clipBegin = all_atts.clipBegin ? *all_atts.clipBegin : 0;
clipEnd = all_atts.clipEnd ? *all_atts.clipEnd : -1;
if (stack->needs_play) {
gf_mo_play(stack->resource, clipBegin, clipEnd, 0);
stack->needs_play = 0;
} else {
Bool primary = all_atts.gpac_useAsPrimary ? *all_atts.gpac_useAsPrimary : 1;
new_res = gf_mo_load_xlink_resource(node, primary, clipBegin, clipEnd);
if (new_res != stack->resource) {
if (stack->resource) gf_mo_unload_xlink_resource(node, stack->resource);
if (all_atts.xlink_href) all_atts.xlink_href->target = NULL;
stack->resource = new_res;
stack->fragment_id = NULL;
stack->inline_sg = NULL;
}
gf_node_dirty_clear(node, 0);
}
}
}
static void svg_reset_xlink_target(GF_Node *node)
{
SVGAllAttributes all_atts;
gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
if (all_atts.xlink_href) all_atts.xlink_href->target=NULL;
}
static void svg_animation_smil_evaluate(SMIL_Timing_RTI *rti, Fixed normalized_scene_time, u32 status)
{
GF_Node *node = gf_smil_get_element(rti);
SVGlinkStack *stack = gf_node_get_private(node);
switch (status) {
case SMIL_TIMING_EVAL_UPDATE:
svg_animation_smil_update(node, stack, normalized_scene_time);
break;
case SMIL_TIMING_EVAL_FREEZE:
if (stack->resource) {
gf_mo_stop(stack->resource);
stack->needs_play = 1;
}
break;
case SMIL_TIMING_EVAL_REMOVE:
if (stack->resource) {
svg_reset_xlink_target(node);
gf_mo_unload_xlink_resource(node, stack->resource);
stack->resource = NULL;
stack->fragment_id = NULL;
stack->inline_sg = NULL;
gf_node_dirty_set(node, GF_SG_SVG_XLINK_HREF_DIRTY, 0);
}
break;
case SMIL_TIMING_EVAL_REPEAT:
if (stack->resource) {
svg_reset_xlink_target(node);
stack->fragment_id = NULL;
stack->inline_sg = NULL;
gf_mo_restart(stack->resource);
}
break;
}
}
static void svg_traverse_animation(GF_Node *node, void *rs, Bool is_destroy)
{
SVGAllAttributes all_atts;
GF_Matrix2D backup_matrix;
GF_Matrix backup_matrix3d;
SVGPropertiesPointers backup_props;
u32 backup_flags;
SFVec2f prev_vp;
GF_Rect rc;
GF_IRect clip, prev_clip;
SVGAllAttributes *prev_vp_atts;
GF_TraverseState *tr_state = (GF_TraverseState*)rs;
GF_Matrix2D translate;
SVGPropertiesPointers *old_props;
SVGlinkStack *stack = gf_node_get_private(node);
if (is_destroy) {
if (stack->resource) gf_mo_unload_xlink_resource(node, stack->resource);
gf_free(stack);
return;
}
gf_svg_flatten_attributes((SVG_Element *)node, &all_atts);
if (!stack->inline_sg && !stack->resource) {
if (!stack->init_vis_state) {
if (all_atts.initialVisibility && (*all_atts.initialVisibility==SVG_INITIALVISIBILTY_ALWAYS)) {
stack->init_vis_state = 2;
svg_animation_smil_update(node, stack, 0);
} else {
stack->init_vis_state = 1;
}
}
if (!stack->inline_sg && !stack->resource)
return;
}
if (!all_atts.width || !all_atts.height) return;
if (!all_atts.width->value || !all_atts.height->value) return;
if (!compositor_svg_traverse_base(node, &all_atts, tr_state, &backup_props, &backup_flags))
return;
if (compositor_svg_is_display_off(tr_state->svg_props) ||
*(tr_state->svg_props->visibility) == SVG_VISIBILITY_HIDDEN) {
goto end;
}
compositor_svg_apply_local_transformation(tr_state, &all_atts, &backup_matrix, &backup_matrix3d);
gf_mx2d_init(translate);
translate.m[2] = (all_atts.x ? all_atts.x->value : 0);
translate.m[5] = (all_atts.y ? all_atts.y->value : 0);
#ifndef GPAC_DISABLE_3D
if (tr_state->visual->type_3d) {
gf_mx_add_matrix_2d(&tr_state->model_matrix, &translate);
} else
#endif
gf_mx2d_pre_multiply(&tr_state->transform, &translate);
old_props = tr_state->svg_props;
tr_state->svg_props = NULL;
prev_vp_atts = tr_state->parent_anim_atts;
tr_state->parent_anim_atts = &all_atts;
prev_vp = tr_state->vp_size;
tr_state->vp_size.x = gf_sc_svg_convert_length_to_display(tr_state->visual->compositor, all_atts.width);
tr_state->vp_size.y = gf_sc_svg_convert_length_to_display(tr_state->visual->compositor, all_atts.height);
rc.width = tr_state->vp_size.x;
rc.height = tr_state->vp_size.y;
rc.x = 0;
rc.y = tr_state->vp_size.y;
gf_mx2d_apply_rect(&tr_state->transform, &rc);
prev_clip = tr_state->visual->top_clipper;
clip = gf_rect_pixelize(&rc);
gf_irect_intersect(&tr_state->visual->top_clipper, &clip);
if (!stack->inline_sg && stack->resource) {
stack->inline_sg = gf_mo_get_scenegraph(stack->resource);
}
if (tr_state->visual->compositor->focus_node==node) {
GF_Node *subroot = gf_sg_get_root_node(stack->inline_sg);
if (subroot) tr_state->visual->compositor->focus_node = subroot;
}
if (stack->inline_sg) {
gf_sc_traverse_subscene(tr_state->visual->compositor, node, stack->inline_sg, tr_state);
}
if (stack->init_vis_state == 2) {
stack->init_vis_state = 3;
gf_mo_pause(stack->resource);
}
tr_state->svg_props = old_props;
tr_state->visual->top_clipper = prev_clip;
tr_state->parent_anim_atts = prev_vp_atts;
tr_state->vp_size = prev_vp;
compositor_svg_restore_parent_transformation(tr_state, &backup_matrix, &backup_matrix3d);
end:
memcpy(tr_state->svg_props, &backup_props, sizeof(SVGPropertiesPointers));
tr_state->svg_flags = backup_flags;
}
void compositor_init_svg_animation(GF_Compositor *compositor, GF_Node *node)
{
SVGlinkStack *stack;
GF_SAFEALLOC(stack, SVGlinkStack);
gf_node_set_private(node, stack);
gf_node_set_callback_function(node, svg_traverse_animation);
gf_smil_set_evaluation_callback(node, svg_animation_smil_evaluate);
gf_node_dirty_set(node, GF_SG_SVG_XLINK_HREF_DIRTY, 0);
}
void svg_pause_animation(GF_Node *n, Bool pause)
{
SVGlinkStack *st = gf_node_get_private(n);
if (!st) return;
if (pause) gf_mo_pause(st->resource);
else gf_mo_resume(st->resource);
}
static void svg_traverse_foreign_object(GF_Node *node, void *rs, Bool is_destroy)
{
svg_traverse_resource(node, rs, is_destroy, 1);
}
void compositor_init_svg_foreign_object(GF_Compositor *compositor, GF_Node *node)
{
SVGlinkStack *stack;
GF_SAFEALLOC(stack, SVGlinkStack);
gf_node_set_private(node, stack);
gf_node_set_callback_function(node, svg_traverse_foreign_object);
gf_node_dirty_set(node, GF_SG_SVG_XLINK_HREF_DIRTY, 0);
}
GF_Node *compositor_svg_get_xlink_resource_node(GF_Node *node, XMLRI *xlink)
{
SVGlinkStack *stack;
switch (gf_node_get_tag(node)) {
case TAG_SVG_animation:
stack = gf_node_get_private(node);
return gf_sg_get_root_node(stack->inline_sg);
case TAG_SVG_use:
stack = gf_node_get_private(node);
if (stack && stack->fragment_id)
return gf_sg_find_node_by_name(stack->inline_sg, (char *) stack->fragment_id+1);
return xlink ? xlink->target : NULL;
}
return NULL;
}
GF_EXPORT
GF_SceneGraph *gf_sc_animation_get_scenegraph(GF_Node *node)
{
SVGlinkStack *stack;
if (node->sgprivate->tag!=TAG_SVG_animation) return NULL;
stack = gf_node_get_private(node);
return stack->inline_sg;
}
#endif