Skip to content

Commit 13943e6

Browse files
author
zhangjipeng
committed
feat: add more svg animation features
Signed-off-by: zhangjipeng <zhangjipeng@xiaomi.com>
1 parent bac28fe commit 13943e6

7 files changed

Lines changed: 3470 additions & 49 deletions

File tree

ext/svg/psx_svg_anim_state.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,18 @@
4242
extern "C" {
4343
#endif
4444

45+
typedef struct {
46+
const psx_svg_node* target;
47+
float* dashes;
48+
uint32_t count;
49+
} psx_svg_anim_dash_item;
50+
4551
// opaque animation override state passed to renderer.
4652
struct psx_svg_anim_state {
4753
psx_array overrides;
4854
psx_array transforms; // animateTransform overrides
4955
psx_array motion_transforms; // animateMotion overrides (independent layer)
56+
psx_array dash_overrides; // stroke-dasharray overrides (psx_svg_anim_dash_item[])
5057
ps_matrix* scratch_matrix; // reused each frame; owned by this struct
5158
};
5259

@@ -90,12 +97,32 @@ typedef struct {
9097
// store the trigger name and allow external callers to start the animation
9198
// via psx_svg_player_trigger(). Owned by the player.
9299
const char* begin_event;
100+
float begin_event_offset_sec; // event+offset: delay in seconds after trigger
101+
const char* begin_event_target_id; // id.event: "btn" from "btn.click", NULL if none
102+
103+
// End event trigger support (e.g. end="click").
104+
// When matched, current time is appended to ends_sec. Owned by the player.
105+
const char* end_event;
106+
const char* end_event_target_id; // id.event: target id for end event, NULL if none
93107

94108
// Begin list support: store begin times (sec) and choose the latest begin <= doc_t.
95109
psx_array begins_sec;
96110

97111
// End list support: store end times (sec) and choose the earliest end >= begin (per trigger).
98112
psx_array ends_sec;
113+
114+
uint32_t accumulate_mode; // SVG_ANIMATION_ACCUMULATE_NONE or _SUM
115+
116+
bool was_active;
117+
uint32_t last_iteration;
118+
119+
char access_key;
120+
121+
const char* syncbase_ref_id;
122+
uint32_t syncbase_type;
123+
float syncbase_offset_sec; // offset in seconds (e.g., +1s)
124+
bool syncbase_resolved; // true once begin time has been resolved
125+
bool syncbase_circular; // true if circular dependency detected
99126
} psx_svg_anim_item;
100127

101128
typedef struct {
@@ -138,6 +165,11 @@ const ps_matrix* psx_svg_anim_get_transform(const psx_svg_anim_state* s,
138165
const psx_svg_anim_transform_item* psx_svg_anim_state_find_transform(const psx_svg_anim_state* s,
139166
const psx_svg_node* target);
140167

168+
/* returns true if a dasharray override exists for target.
169+
* writes the dash array pointer and count into *out_dashes / *out_count. */
170+
bool psx_svg_anim_get_dash(const psx_svg_anim_state* s, const psx_svg_node* target,
171+
const float** out_dashes, uint32_t* out_count);
172+
141173
#ifdef __cplusplus
142174
}
143175
#endif

ext/svg/psx_svg_parser.cpp

Lines changed: 216 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ void psx_svg_timing_list_destroy(psx_svg_timing_list* tl)
9898
mem_free(tl->event_token);
9999
tl->event_token = NULL;
100100
}
101+
if (tl->event_target_id) {
102+
mem_free(tl->event_target_id);
103+
tl->event_target_id = NULL;
104+
}
105+
if (tl->syncbase_id) {
106+
mem_free(tl->syncbase_id);
107+
tl->syncbase_id = NULL;
108+
}
101109
mem_free(tl);
102110
}
103111

@@ -1727,6 +1735,68 @@ static INLINE void _parse_animation_value(psx_svg_node* node, psx_svg_attr* attr
17271735
attr->value.fval = 0.0f;
17281736
return;
17291737
}
1738+
// font-weight: "bold"
1739+
if (vlen == 4 && strncmp(val_start, "bold", 4) == 0) {
1740+
attr->value.fval = (float)FONT_WEIGHT_BOLD;
1741+
return;
1742+
}
1743+
// font-style: "italic"
1744+
if (vlen == 6 && strncmp(val_start, "italic", 6) == 0) {
1745+
attr->value.fval = 1.0f;
1746+
return;
1747+
}
1748+
// "normal": context-aware — font-weight maps to FONT_WEIGHT_REGULAR, font-style maps to 0
1749+
if (vlen == 6 && strncmp(val_start, "normal", 6) == 0) {
1750+
int32_t target_attr = SVG_ATTR_INVALID;
1751+
uint32_t na = node->attr_count();
1752+
for (uint32_t i = 0; i < na; i++) {
1753+
const psx_svg_attr* ta = node->attr_at(i);
1754+
if (ta && (psx_svg_attr_type)ta->attr_id == SVG_ATTR_ATTRIBUTE_NAME) {
1755+
target_attr = ta->value.ival;
1756+
break;
1757+
}
1758+
}
1759+
if (target_attr == SVG_ATTR_FONT_WEIGHT) {
1760+
attr->value.fval = (float)FONT_WEIGHT_REGULAR;
1761+
} else if (target_attr == SVG_ATTR_FONT_STYLE) {
1762+
attr->value.fval = 0.0f;
1763+
} else {
1764+
attr->value.fval = 0.0f;
1765+
}
1766+
return;
1767+
}
1768+
/* stroke-dasharray: parse as float array (comma/space separated). */
1769+
{
1770+
int32_t target_attr = SVG_ATTR_INVALID;
1771+
uint32_t na = node->attr_count();
1772+
for (uint32_t i = 0; i < na; i++) {
1773+
const psx_svg_attr* ta = node->attr_at(i);
1774+
if (ta && (psx_svg_attr_type)ta->attr_id == SVG_ATTR_ATTRIBUTE_NAME) {
1775+
target_attr = ta->value.ival;
1776+
break;
1777+
}
1778+
}
1779+
if (target_attr == SVG_ATTR_STROKE_DASH_ARRAY) {
1780+
attr->val_type = SVG_ATTR_VALUE_PTR;
1781+
uint32_t list_cap = 4;
1782+
psx_svg_attr_values_list* list = (psx_svg_attr_values_list*)mem_malloc(sizeof(float) * list_cap + sizeof(uint32_t));
1783+
uint32_t count = 0;
1784+
const char* ptr = val_start;
1785+
while (ptr < val_end) {
1786+
if (count == list_cap) {
1787+
list_cap = list_cap << 1;
1788+
list = (psx_svg_attr_values_list*)mem_realloc(list, sizeof(float) * list_cap + sizeof(uint32_t));
1789+
}
1790+
float* val = (float*)(&list->data[0]) + count;
1791+
ptr = _parse_number(ptr, val_end, val);
1792+
if (!ptr) { break; }
1793+
++count;
1794+
}
1795+
list->length = count;
1796+
attr->value.val = list;
1797+
return;
1798+
}
1799+
}
17301800
float val_number = 0.0f;
17311801
val_start = _parse_length(val_start, val_end, dpi, &val_number);
17321802
attr->value.fval = val_number;
@@ -1973,13 +2043,153 @@ static void _animation_begin_end_cb(psx_svg_node* node, psx_svg_attr* attr, cons
19732043
tl->offsets_ms[tl->offsets_len++] = ms;
19742044
} else if (!tl->event_token) {
19752045
uint32_t len = BUF_LEN(ts, te);
1976-
char* s = (char*)mem_malloc(len + 1);
1977-
if (!s) {
1978-
return;
2046+
/* begin="indefinite" → store sentinel so trigger() can match it */
2047+
if (len == 10 && strncmp(ts, "indefinite", 10) == 0) {
2048+
const char* sentinel = "__indefinite__";
2049+
uint32_t slen = 14; /* strlen("__indefinite__") */
2050+
char* s = (char*)mem_malloc(slen + 1);
2051+
if (!s) {
2052+
return;
2053+
}
2054+
mem_copy(s, sentinel, slen);
2055+
s[slen] = 0;
2056+
tl->event_token = s;
2057+
} else if (len >= 12 && strncmp(ts, "accessKey(", 10) == 0) {
2058+
/* accessKey(x) → extract single char, store sentinel event token */
2059+
char key_char = ts[10];
2060+
if (key_char && ts[11] == ')') {
2061+
tl->access_key = key_char;
2062+
const char* sentinel = "__accessKey__";
2063+
uint32_t slen = 13; /* strlen("__accessKey__") */
2064+
char* s = (char*)mem_malloc(slen + 1);
2065+
if (!s) {
2066+
return;
2067+
}
2068+
mem_copy(s, sentinel, slen);
2069+
s[slen] = 0;
2070+
tl->event_token = s;
2071+
}
2072+
} else {
2073+
/* Check for id.event syntax (e.g. "btn.click", "btn.click+2s").
2074+
* A dot is an id.event separator only if the first char of the
2075+
* token is a letter or underscore (avoids splitting numbers like
2076+
* "0.5s") and the char after the dot is also a letter. */
2077+
const char* dot = NULL;
2078+
if ((*ts >= 'a' && *ts <= 'z') || (*ts >= 'A' && *ts <= 'Z') || *ts == '_') {
2079+
for (const char* dc = ts + 1; dc < te; dc++) {
2080+
if (*dc == '.' && (dc + 1) < te
2081+
&& ((*(dc + 1) >= 'a' && *(dc + 1) <= 'z')
2082+
|| (*(dc + 1) >= 'A' && *(dc + 1) <= 'Z'))) {
2083+
dot = dc;
2084+
break;
2085+
}
2086+
}
2087+
}
2088+
2089+
const char* ev_start = ts;
2090+
if (dot) {
2091+
/* Store event_target_id = part before dot */
2092+
uint32_t id_len = (uint32_t)(dot - ts);
2093+
const char* after_dot = dot + 1;
2094+
uint32_t after_len = (uint32_t)(te - after_dot);
2095+
2096+
/* Check if this is a syncbase reference (id.begin or id.end) */
2097+
int is_syncbase = 0;
2098+
uint32_t sb_type = 0;
2099+
const char* sb_rest = NULL;
2100+
2101+
if (after_len >= 5 && strncmp(after_dot, "begin", 5) == 0) {
2102+
sb_rest = after_dot + 5;
2103+
/* "begin" must be followed by end-of-token, '+', or '-' */
2104+
if (sb_rest >= te || *sb_rest == '+' || *sb_rest == '-') {
2105+
is_syncbase = 1;
2106+
sb_type = 0;
2107+
}
2108+
} else if (after_len >= 3 && strncmp(after_dot, "end", 3) == 0) {
2109+
sb_rest = after_dot + 3;
2110+
/* "end" must be followed by end-of-token, '+', or '-' */
2111+
if (sb_rest >= te || *sb_rest == '+' || *sb_rest == '-') {
2112+
is_syncbase = 1;
2113+
sb_type = 1;
2114+
}
2115+
}
2116+
2117+
if (is_syncbase) {
2118+
/* Store syncbase_id = part before dot */
2119+
char* sid = (char*)mem_malloc(id_len + 1);
2120+
if (!sid) {
2121+
return;
2122+
}
2123+
mem_copy(sid, ts, id_len);
2124+
sid[id_len] = 0;
2125+
tl->syncbase_id = sid;
2126+
tl->syncbase_type = sb_type;
2127+
2128+
/* Check for optional +/-offset (e.g. "a1.end+1s") */
2129+
if (sb_rest < te && (*sb_rest == '+' || *sb_rest == '-')) {
2130+
float ms = 0.0f;
2131+
if (_parse_clock_time(sb_rest, te, &ms)) {
2132+
uint32_t new_len = tl->offsets_len + 1;
2133+
float* nbuf = (float*)mem_realloc(tl->offsets_ms, sizeof(float) * new_len);
2134+
if (nbuf) {
2135+
tl->offsets_ms = nbuf;
2136+
tl->offsets_ms[tl->offsets_len++] = ms;
2137+
}
2138+
}
2139+
}
2140+
return; /* syncbase handled, don't fall through to event handling */
2141+
}
2142+
2143+
/* Not syncbase — original id.event handling */
2144+
char* tid = (char*)mem_malloc(id_len + 1);
2145+
if (!tid) {
2146+
return;
2147+
}
2148+
mem_copy(tid, ts, id_len);
2149+
tid[id_len] = 0;
2150+
tl->event_target_id = tid;
2151+
ev_start = dot + 1; /* remainder is the event token (+ optional offset) */
2152+
}
2153+
2154+
/* Check for event+offset syntax (e.g. "click+2s", "click-1s") */
2155+
const char* sign = NULL;
2156+
for (const char* sc = ev_start + 1; sc < te; sc++) {
2157+
if (*sc == '+' || *sc == '-') {
2158+
sign = sc;
2159+
break;
2160+
}
2161+
}
2162+
if (sign) {
2163+
/* event name is before the sign */
2164+
uint32_t elen = (uint32_t)(sign - ev_start);
2165+
char* s = (char*)mem_malloc(elen + 1);
2166+
if (!s) {
2167+
return;
2168+
}
2169+
mem_copy(s, ev_start, elen);
2170+
s[elen] = 0;
2171+
tl->event_token = s;
2172+
/* offset is from sign to end */
2173+
float ms = 0.0f;
2174+
if (_parse_clock_time(sign, te, &ms)) {
2175+
uint32_t new_len = tl->offsets_len + 1;
2176+
float* nbuf = (float*)mem_realloc(tl->offsets_ms, sizeof(float) * new_len);
2177+
if (nbuf) {
2178+
tl->offsets_ms = nbuf;
2179+
tl->offsets_ms[tl->offsets_len++] = ms;
2180+
}
2181+
}
2182+
} else {
2183+
uint32_t elen = (uint32_t)(te - ev_start);
2184+
char* s = (char*)mem_malloc(elen + 1);
2185+
if (!s) {
2186+
return;
2187+
}
2188+
mem_copy(s, ev_start, elen);
2189+
s[elen] = 0;
2190+
tl->event_token = s;
2191+
}
19792192
}
1980-
mem_copy(s, ts, len);
1981-
s[len] = 0;
1982-
tl->event_token = s;
19832193
}
19842194
}
19852195

ext/svg/psx_svg_parser.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ typedef struct psx_svg_timing_list {
6767
uint32_t offsets_len;
6868
float* offsets_ms; // length offsets_len, may be NULL if 0
6969
char* event_token; // optional, NULL if none
70+
char* event_target_id; // optional, "btn" from "btn.click", NULL if none
71+
char access_key;
72+
char* syncbase_id;
73+
uint32_t syncbase_type;
7074
} psx_svg_timing_list;
7175

7276
// Helper for freeing a timing list allocated by the parser.

0 commit comments

Comments
 (0)