Skip to content

Commit bac28fe

Browse files
author
zhangjipeng
committed
docs(svg): add more animation features
Signed-off-by: zhangjipeng <zhangjipeng@xiaomi.com>
1 parent f3a84f1 commit bac28fe

3 files changed

Lines changed: 887 additions & 9 deletions

File tree

ext/svg/psx_svg_anim_state.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ typedef struct {
8181
float repeat_dur_sec; // optional explicit repeat duration, 0 => unspecified
8282
uint32_t fill_mode; // SVG_ANIMATION_*
8383
uint32_t additive_mode; // SVG_ANIMATION_ADDITIVE_REPLACE or SVG_ANIMATION_ADDITIVE_SUM
84+
uint32_t restart_mode; // SVG_ANIMATION_RESTART_ALWAYS / WHEN_NOT_ACTIVE / NEVER
85+
float min_sec; // minimum active duration (0 = unspecified)
86+
float max_sec; // maximum active duration (0 = indefinite/unspecified)
8487

8588
// Minimal Tiny 1.2 external event trigger support.
8689
// If begin is specified as a non-numeric token (e.g. begin="click"), we

ext/svg/psx_svg_player.cpp

Lines changed: 129 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,21 @@ static INLINE const char* _dup_cstr(const char* s)
571571
return d;
572572
}
573573

574+
/* Compute the active duration for an anim item (used by restart logic). */
575+
static INLINE float _anim_item_active_dur(const psx_svg_anim_item* it)
576+
{
577+
if (!it || it->dur_sec <= 0.0f) {
578+
return 0.0f;
579+
}
580+
if (it->repeat_dur_sec > 0.0f) {
581+
return it->repeat_dur_sec;
582+
}
583+
if (it->repeat_count == 0) {
584+
return 1e30f; /* indefinite */
585+
}
586+
return it->dur_sec * (float)it->repeat_count;
587+
}
588+
574589
static INLINE float _anim_item_begin_for_time(const psx_svg_anim_item* it, float doc_t)
575590
{
576591
if (!it) {
@@ -584,9 +599,54 @@ static INLINE float _anim_item_begin_for_time(const psx_svg_anim_item* it, float
584599
if (psx_array_empty((psx_array*)&it->begins_sec)) {
585600
return it->begin_sec;
586601
}
587-
// choose latest begin <= doc_t
588-
float best = -1.0f;
602+
589603
const uint32_t n = psx_array_size((psx_array*)&it->begins_sec);
604+
605+
// restart="never": only use the first (earliest) begin.
606+
if (it->restart_mode == SVG_ANIMATION_RESTART_NEVER) {
607+
const float* fp = psx_array_get((psx_array*)&it->begins_sec, 0, float);
608+
return fp ? *fp : 0.0f;
609+
}
610+
611+
// restart="whenNotActive": iterate forwards, skip begins that fall
612+
// within a previous begin's active interval.
613+
if (it->restart_mode == SVG_ANIMATION_RESTART_WHEN_NOT_ACTIVE) {
614+
float ad = _anim_item_active_dur(it);
615+
// Build valid begins by forward scan: a begin is valid only if it
616+
// does not fall within the active interval of the current valid begin.
617+
float cur_valid = -1.0f;
618+
float best = -1.0f;
619+
for (uint32_t i = 0; i < n; i++) {
620+
const float* bp = psx_array_get((psx_array*)&it->begins_sec, i, float);
621+
float b = bp ? *bp : 0.0f;
622+
if (cur_valid < 0.0f || b >= cur_valid + ad) {
623+
// This begin is valid (not inside a previous active interval).
624+
cur_valid = b;
625+
if (b <= doc_t && b > best) {
626+
best = b;
627+
}
628+
}
629+
// else: b falls within cur_valid's active interval => skip
630+
}
631+
if (best < 0.0f) {
632+
// all valid begins are in the future: return earliest valid
633+
cur_valid = -1.0f;
634+
for (uint32_t i = 0; i < n; i++) {
635+
const float* bp = psx_array_get((psx_array*)&it->begins_sec, i, float);
636+
float b = bp ? *bp : 0.0f;
637+
if (cur_valid < 0.0f || b >= cur_valid + ad) {
638+
cur_valid = b;
639+
return b;
640+
}
641+
}
642+
const float* fp = psx_array_get((psx_array*)&it->begins_sec, 0, float);
643+
return fp ? *fp : 0.0f;
644+
}
645+
return best;
646+
}
647+
648+
// restart="always" (default): choose latest begin <= doc_t
649+
float best = -1.0f;
590650
for (uint32_t i = 0; i < n; i++) {
591651
const float* bp = psx_array_get((psx_array*)&it->begins_sec, i, float);
592652
float b = bp ? *bp : 0.0f;
@@ -677,6 +737,7 @@ static INLINE bool _anim_resolve_local_t(const psx_svg_anim_item* it, float doc_
677737

678738
float total = 0.0f;
679739
bool has_total = false;
740+
float raw_total = 0.0f;
680741
if (it->repeat_dur_sec > 0.0f) {
681742
total = it->repeat_dur_sec;
682743
has_total = true;
@@ -686,6 +747,24 @@ static INLINE bool _anim_resolve_local_t(const psx_svg_anim_item* it, float doc_
686747
total = it->dur_sec * (float)it->repeat_count;
687748
has_total = true;
688749
}
750+
raw_total = total;
751+
752+
// Apply min/max active duration constraints (SVG Tiny 1.2 §16.3.3).
753+
// min > max => both ignored.
754+
if (has_total) {
755+
float min_s = it->min_sec;
756+
float max_s = it->max_sec;
757+
if (min_s > 0.0f && max_s > 0.0f && min_s > max_s) {
758+
// ignore both
759+
} else {
760+
if (max_s > 0.0f && total > max_s) {
761+
total = max_s;
762+
}
763+
if (min_s > 0.0f && total < min_s) {
764+
total = min_s;
765+
}
766+
}
767+
}
689768

690769
if (!has_total) {
691770
local = _anim_fmod(local, it->dur_sec);
@@ -696,6 +775,14 @@ static INLINE bool _anim_resolve_local_t(const psx_svg_anim_item* it, float doc_
696775
} else {
697776
return false;
698777
}
778+
} else if (raw_total > 0.0f && local >= raw_total) {
779+
// In min-extended region: past original duration but within min.
780+
// fill=freeze: hold final value; fill=remove: no override.
781+
if (it->fill_mode == SVG_ANIMATION_FREEZE) {
782+
local = it->dur_sec;
783+
} else {
784+
return false;
785+
}
699786
} else {
700787
local = _anim_fmod(local, it->dur_sec);
701788
}
@@ -968,14 +1055,23 @@ static INLINE bool _anim_eval_simple(const psx_svg_anim_item* it, float doc_t, f
9681055

9691056
const psx_svg_attr* afrom = _find_attr(it->anim_node, SVG_ATTR_FROM);
9701057
const psx_svg_attr* ato = _find_attr(it->anim_node, SVG_ATTR_TO);
971-
if (!afrom || !ato) {
972-
return false;
1058+
if (afrom && ato) {
1059+
float v0 = _attr_as_number(afrom);
1060+
float v1 = _attr_as_number(ato);
1061+
*out_v = _anim_lerp(v0, v1, t);
1062+
return true;
9731063
}
9741064

975-
float v0 = _attr_as_number(afrom);
976-
float v1 = _attr_as_number(ato);
977-
*out_v = _anim_lerp(v0, v1, t);
978-
return true;
1065+
// by attribute fallback: by alone => from=0 to=by; from+by => to=from+by
1066+
const psx_svg_attr* aby = _find_attr(it->anim_node, SVG_ATTR_BY);
1067+
if (aby && !ato) {
1068+
float by_val = _attr_as_number(aby);
1069+
float from_val = afrom ? _attr_as_number(afrom) : 0.0f;
1070+
*out_v = _anim_lerp(from_val, from_val + by_val, t);
1071+
return true;
1072+
}
1073+
1074+
return false;
9791075
}
9801076

9811077
static INLINE bool _anim_eval_transform_translate_discrete(const psx_svg_anim_item* it, float doc_t,
@@ -3102,7 +3198,7 @@ static void _collect_anims(psx_svg_player* p, const psx_svg_node* node)
31023198
|| node->type() == SVG_TAG_ANIMATE_TRANSFORM || node->type() == SVG_TAG_ANIMATE_MOTION)
31033199
&& (abegin->attr_id == SVG_ATTR_BEGIN)) {
31043200
const psx_svg_timing_list* tl = (const psx_svg_timing_list*)abegin->value.val;
3105-
if (t == SVG_TAG_SET && tl->event_token) {
3201+
if (tl->event_token) {
31063202
item.begin_event = _dup_cstr(tl->event_token);
31073203
psx_array_clear(&item.begins_sec);
31083204
item.begin_sec = 1e30f; // don't auto-activate until triggered
@@ -3182,6 +3278,15 @@ static void _collect_anims(psx_svg_player* p, const psx_svg_node* node)
31823278
item.additive_mode = (uint32_t)additive_attr->value.ival;
31833279
}
31843280

3281+
item.restart_mode = SVG_ANIMATION_RESTART_ALWAYS;
3282+
const psx_svg_attr* arestart = _find_attr(node, SVG_ATTR_RESTART);
3283+
if (arestart && arestart->val_type == SVG_ATTR_VALUE_DATA) {
3284+
item.restart_mode = (uint32_t)arestart->value.ival;
3285+
}
3286+
3287+
item.min_sec = _attr_as_time_sec(_find_attr(node, SVG_ATTR_MIN));
3288+
item.max_sec = _attr_as_time_sec(_find_attr(node, SVG_ATTR_MAX));
3289+
31853290
psx_array_append(&p->anims, NULL);
31863291
psx_svg_anim_item* dst = psx_array_get_last(&p->anims, psx_svg_anim_item);
31873292
*dst = item;
@@ -3651,6 +3756,21 @@ void psx_svg_player_trigger(psx_svg_player* p, const char* target_id, const char
36513756
}
36523757

36533758
float sec = p->time_sec;
3759+
3760+
// restart guard: check restart_mode before appending new begin.
3761+
if (it->restart_mode == SVG_ANIMATION_RESTART_NEVER) {
3762+
if (!psx_array_empty(&it->begins_sec)) {
3763+
continue; // already activated once; ignore
3764+
}
3765+
} else if (it->restart_mode == SVG_ANIMATION_RESTART_WHEN_NOT_ACTIVE) {
3766+
// Check if currently active: find current begin and see if sec < begin + active_dur.
3767+
float ad = _anim_item_active_dur(it);
3768+
float cur_begin = _anim_item_begin_for_time(it, sec);
3769+
if (cur_begin < 1e29f && sec >= cur_begin && sec < cur_begin + ad) {
3770+
continue; // currently active; ignore
3771+
}
3772+
}
3773+
36543774
psx_array_append(&it->begins_sec, &sec);
36553775
_anim_item_begin_list_normalize(it);
36563776
any = true;

0 commit comments

Comments
 (0)