reframe-video 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/sfx/LICENSE.md +5 -0
- package/assets/sfx/bgm-song21.mp3 +0 -0
- package/assets/sfx/pop.wav +0 -0
- package/assets/sfx/rise.wav +0 -0
- package/assets/sfx/shimmer.wav +0 -0
- package/assets/sfx/thud.wav +0 -0
- package/assets/sfx/tick.wav +0 -0
- package/assets/sfx/whoosh.wav +0 -0
- package/dist/analyze.js +44 -1
- package/dist/bin.js +371 -35
- package/dist/browserEntry.js +311 -8
- package/dist/cli.js +305 -21
- package/dist/index.js +566 -16
- package/dist/renderer-canvas.js +58 -3
- package/dist/trace-cli.js +677 -0
- package/dist/types/assets.d.ts +9 -0
- package/dist/types/compile.d.ts +15 -1
- package/dist/types/compose.d.ts +12 -2
- package/dist/types/dsl.d.ts +30 -1
- package/dist/types/evaluate.d.ts +17 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/ir.d.ts +82 -1
- package/dist/types/motion.d.ts +54 -0
- package/dist/types/path.d.ts +15 -0
- package/dist/types/presets.d.ts +41 -0
- package/guides/edsl-guide.md +24 -0
- package/package.json +1 -1
- package/preview/src/main.ts +111 -6
- package/preview/src/panel.ts +37 -3
- package/preview/src/store.ts +28 -2
- package/preview/src/virtual.d.ts +9 -1
- package/preview/vite.config.ts +3 -2
package/dist/browserEntry.js
CHANGED
|
@@ -3,9 +3,88 @@
|
|
|
3
3
|
// ../core/src/ir.ts
|
|
4
4
|
var DEFAULT_TO_DURATION = 0.5;
|
|
5
5
|
var DEFAULT_TWEEN_DURATION = 0.5;
|
|
6
|
+
var DEFAULT_MOTIONPATH_DURATION = 1;
|
|
7
|
+
|
|
8
|
+
// ../core/src/path.ts
|
|
9
|
+
function locate(segCount, u) {
|
|
10
|
+
if (segCount <= 0) return { i: 0, t: 0 };
|
|
11
|
+
const clamped = Math.max(0, Math.min(1, u));
|
|
12
|
+
const scaled = clamped * segCount;
|
|
13
|
+
let i = Math.floor(scaled);
|
|
14
|
+
if (i >= segCount) i = segCount - 1;
|
|
15
|
+
return { i, t: scaled - i };
|
|
16
|
+
}
|
|
17
|
+
function controls(points, closed, i) {
|
|
18
|
+
const n = points.length;
|
|
19
|
+
const at = (k) => {
|
|
20
|
+
if (closed) return points[(k % n + n) % n];
|
|
21
|
+
return points[Math.max(0, Math.min(n - 1, k))];
|
|
22
|
+
};
|
|
23
|
+
return [at(i - 1), at(i), at(i + 1), at(i + 2)];
|
|
24
|
+
}
|
|
25
|
+
function segCountOf(points, closed) {
|
|
26
|
+
const n = points.length;
|
|
27
|
+
if (n < 2) return 0;
|
|
28
|
+
return closed ? n : n - 1;
|
|
29
|
+
}
|
|
30
|
+
function pathPoint(points, closed, u) {
|
|
31
|
+
const n = points.length;
|
|
32
|
+
if (n === 0) return [0, 0];
|
|
33
|
+
if (n === 1) return [points[0][0], points[0][1]];
|
|
34
|
+
const segs = segCountOf(points, closed);
|
|
35
|
+
const { i, t } = locate(segs, u);
|
|
36
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
37
|
+
const t2 = t * t;
|
|
38
|
+
const t3 = t2 * t;
|
|
39
|
+
const f = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t2 + (-a + 3 * b - 3 * c + d) * t3);
|
|
40
|
+
return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
|
|
41
|
+
}
|
|
42
|
+
function pathTangentAngle(points, closed, u) {
|
|
43
|
+
const n = points.length;
|
|
44
|
+
if (n < 2) return 0;
|
|
45
|
+
const segs = segCountOf(points, closed);
|
|
46
|
+
const { i, t } = locate(segs, u);
|
|
47
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
48
|
+
const t2 = t * t;
|
|
49
|
+
const d = (a, b, c, e) => 0.5 * (-a + c + 2 * (2 * a - 5 * b + 4 * c - e) * t + 3 * (-a + 3 * b - 3 * c + e) * t2);
|
|
50
|
+
const dx = d(p0[0], p1[0], p2[0], p3[0]);
|
|
51
|
+
const dy = d(p0[1], p1[1], p2[1], p3[1]);
|
|
52
|
+
if (dx === 0 && dy === 0) return 0;
|
|
53
|
+
return Math.atan2(dy, dx) * 180 / Math.PI;
|
|
54
|
+
}
|
|
6
55
|
|
|
7
56
|
// ../core/src/compile.ts
|
|
8
57
|
var key = (target, prop) => `${target}.${prop}`;
|
|
58
|
+
function scaleTimeline(tl, k) {
|
|
59
|
+
switch (tl.kind) {
|
|
60
|
+
case "seq":
|
|
61
|
+
case "par":
|
|
62
|
+
return { ...tl, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
63
|
+
case "stagger":
|
|
64
|
+
return { ...tl, interval: tl.interval * k, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
65
|
+
case "wait":
|
|
66
|
+
return { ...tl, duration: tl.duration * k };
|
|
67
|
+
case "tween":
|
|
68
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_TWEEN_DURATION) * k };
|
|
69
|
+
case "motionPath":
|
|
70
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_MOTIONPATH_DURATION) * k };
|
|
71
|
+
case "to":
|
|
72
|
+
return {
|
|
73
|
+
...tl,
|
|
74
|
+
duration: (tl.duration ?? DEFAULT_TO_DURATION) * k,
|
|
75
|
+
...tl.stagger !== void 0 && { stagger: tl.stagger * k }
|
|
76
|
+
};
|
|
77
|
+
case "beat":
|
|
78
|
+
return {
|
|
79
|
+
...tl,
|
|
80
|
+
children: tl.children.map((c) => scaleTimeline(c, k)),
|
|
81
|
+
...tl.gap !== void 0 && { gap: tl.gap * k }
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function orderBeats(children) {
|
|
86
|
+
return children.map((c, i) => ({ c, i, key: c.kind === "beat" && c.order !== void 0 ? c.order : i })).sort((a, b) => a.key - b.key || a.i - b.i).map((x) => x.c);
|
|
87
|
+
}
|
|
9
88
|
function compileScene(ir) {
|
|
10
89
|
const nodeById = /* @__PURE__ */ new Map();
|
|
11
90
|
const nodeOrder = [];
|
|
@@ -34,6 +113,7 @@
|
|
|
34
113
|
}
|
|
35
114
|
}
|
|
36
115
|
const segments = /* @__PURE__ */ new Map();
|
|
116
|
+
const motionPaths = /* @__PURE__ */ new Map();
|
|
37
117
|
const current = new Map(initialValues);
|
|
38
118
|
const pushSegment = (seg) => {
|
|
39
119
|
const k = key(seg.target, seg.prop);
|
|
@@ -50,6 +130,50 @@
|
|
|
50
130
|
throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
|
|
51
131
|
};
|
|
52
132
|
const labelTimes = /* @__PURE__ */ new Map();
|
|
133
|
+
const beatTimes = /* @__PURE__ */ new Map();
|
|
134
|
+
const durationOf = (tl, start) => {
|
|
135
|
+
switch (tl.kind) {
|
|
136
|
+
case "seq": {
|
|
137
|
+
let t = start;
|
|
138
|
+
for (const child of orderBeats(tl.children)) t = durationOf(child, t);
|
|
139
|
+
return t;
|
|
140
|
+
}
|
|
141
|
+
case "par": {
|
|
142
|
+
let end = start;
|
|
143
|
+
for (const child of tl.children) end = Math.max(end, durationOf(child, start));
|
|
144
|
+
return end;
|
|
145
|
+
}
|
|
146
|
+
case "stagger": {
|
|
147
|
+
let end = start;
|
|
148
|
+
tl.children.forEach((child, i) => {
|
|
149
|
+
end = Math.max(end, durationOf(child, start + i * tl.interval));
|
|
150
|
+
});
|
|
151
|
+
return end;
|
|
152
|
+
}
|
|
153
|
+
case "wait":
|
|
154
|
+
return start + tl.duration;
|
|
155
|
+
case "tween":
|
|
156
|
+
return start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
157
|
+
case "motionPath":
|
|
158
|
+
return start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
159
|
+
case "to": {
|
|
160
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
161
|
+
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
162
|
+
const si = tl.stagger ?? 0;
|
|
163
|
+
const targets = nodeOrder.filter(
|
|
164
|
+
(id) => id in override && (tl.filter === void 0 || tl.filter.includes(id))
|
|
165
|
+
);
|
|
166
|
+
return start + duration + Math.max(0, targets.length - 1) * si;
|
|
167
|
+
}
|
|
168
|
+
case "beat": {
|
|
169
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
170
|
+
const natural = durationOf(grouping, 0);
|
|
171
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
172
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
173
|
+
return beatStart + k * natural;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
53
177
|
const walk = (tl, start) => {
|
|
54
178
|
const end = walkInner(tl, start);
|
|
55
179
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -59,9 +183,19 @@
|
|
|
59
183
|
switch (tl.kind) {
|
|
60
184
|
case "seq": {
|
|
61
185
|
let t = start;
|
|
62
|
-
for (const child of tl.children) t = walk(child, t);
|
|
186
|
+
for (const child of orderBeats(tl.children)) t = walk(child, t);
|
|
63
187
|
return t;
|
|
64
188
|
}
|
|
189
|
+
case "beat": {
|
|
190
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
191
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
192
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
193
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
194
|
+
const end = walk(inner, beatStart);
|
|
195
|
+
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
196
|
+
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
197
|
+
return end;
|
|
198
|
+
}
|
|
65
199
|
case "par": {
|
|
66
200
|
let end = start;
|
|
67
201
|
for (const child of tl.children) end = Math.max(end, walk(child, start));
|
|
@@ -91,6 +225,23 @@
|
|
|
91
225
|
}
|
|
92
226
|
return start + duration;
|
|
93
227
|
}
|
|
228
|
+
case "motionPath": {
|
|
229
|
+
const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
|
|
230
|
+
const points = tl.points;
|
|
231
|
+
const closed = tl.closed ?? false;
|
|
232
|
+
const autoRotate = tl.autoRotate ?? false;
|
|
233
|
+
const rotateOffset = tl.rotateOffset ?? 0;
|
|
234
|
+
let list = motionPaths.get(tl.target);
|
|
235
|
+
if (!list) motionPaths.set(tl.target, list = []);
|
|
236
|
+
list.push({ t0: start, t1: start + duration, points, closed, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
|
|
237
|
+
if (points.length > 0) {
|
|
238
|
+
const [ex, ey] = pathPoint(points, closed, 1);
|
|
239
|
+
current.set(key(tl.target, "x"), ex);
|
|
240
|
+
current.set(key(tl.target, "y"), ey);
|
|
241
|
+
if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1) + rotateOffset);
|
|
242
|
+
}
|
|
243
|
+
return start + duration;
|
|
244
|
+
}
|
|
94
245
|
case "to": {
|
|
95
246
|
const override = ir.states?.[tl.state] ?? {};
|
|
96
247
|
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
@@ -119,14 +270,17 @@
|
|
|
119
270
|
};
|
|
120
271
|
const inferredEnd = ir.timeline ? walk(ir.timeline, 0) : 0;
|
|
121
272
|
for (const list of segments.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
273
|
+
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
122
274
|
return {
|
|
123
275
|
ir,
|
|
124
276
|
duration: ir.duration ?? inferredEnd,
|
|
125
277
|
segments,
|
|
278
|
+
motionPaths,
|
|
126
279
|
initialValues,
|
|
127
280
|
nodeById,
|
|
128
281
|
nodeOrder,
|
|
129
|
-
labelTimes
|
|
282
|
+
labelTimes,
|
|
283
|
+
beatTimes
|
|
130
284
|
};
|
|
131
285
|
}
|
|
132
286
|
|
|
@@ -137,9 +291,14 @@
|
|
|
137
291
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
138
292
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
139
293
|
text: [...COMMON_PROPS, "content", "contentDecimals", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
294
|
+
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
295
|
+
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
140
296
|
group: COMMON_PROPS
|
|
141
297
|
};
|
|
142
298
|
|
|
299
|
+
// ../core/src/presets.ts
|
|
300
|
+
var SET = 1 / 120;
|
|
301
|
+
|
|
143
302
|
// ../core/src/behaviors.ts
|
|
144
303
|
function sampleBehavior(b, t) {
|
|
145
304
|
switch (b.name) {
|
|
@@ -170,6 +329,19 @@
|
|
|
170
329
|
}
|
|
171
330
|
|
|
172
331
|
// ../core/src/interpolate.ts
|
|
332
|
+
var BACK_C1 = 1.70158;
|
|
333
|
+
var BACK_C2 = BACK_C1 * 1.525;
|
|
334
|
+
var BACK_C3 = BACK_C1 + 1;
|
|
335
|
+
var ELASTIC_C4 = 2 * Math.PI / 3;
|
|
336
|
+
var ELASTIC_C5 = 2 * Math.PI / 4.5;
|
|
337
|
+
function easeOutBounce(u) {
|
|
338
|
+
const n1 = 7.5625;
|
|
339
|
+
const d1 = 2.75;
|
|
340
|
+
if (u < 1 / d1) return n1 * u * u;
|
|
341
|
+
if (u < 2 / d1) return n1 * (u -= 1.5 / d1) * u + 0.75;
|
|
342
|
+
if (u < 2.5 / d1) return n1 * (u -= 2.25 / d1) * u + 0.9375;
|
|
343
|
+
return n1 * (u -= 2.625 / d1) * u + 0.984375;
|
|
344
|
+
}
|
|
173
345
|
var EASE_TABLE = {
|
|
174
346
|
linear: (u) => u,
|
|
175
347
|
easeInQuad: (u) => u * u,
|
|
@@ -183,7 +355,20 @@
|
|
|
183
355
|
easeInOutQuart: (u) => u < 0.5 ? 8 * u ** 4 : 1 - (-2 * u + 2) ** 4 / 2,
|
|
184
356
|
easeInExpo: (u) => u === 0 ? 0 : 2 ** (10 * u - 10),
|
|
185
357
|
easeOutExpo: (u) => u === 1 ? 1 : 1 - 2 ** (-10 * u),
|
|
186
|
-
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2
|
|
358
|
+
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2,
|
|
359
|
+
// --- expressive eases (GSAP's signature feel) — standard Penner equations ---
|
|
360
|
+
// back: overshoots past the target then settles (pop / snap)
|
|
361
|
+
easeInBack: (u) => BACK_C3 * u ** 3 - BACK_C1 * u * u,
|
|
362
|
+
easeOutBack: (u) => 1 + BACK_C3 * (u - 1) ** 3 + BACK_C1 * (u - 1) ** 2,
|
|
363
|
+
easeInOutBack: (u) => u < 0.5 ? (2 * u) ** 2 * ((BACK_C2 + 1) * 2 * u - BACK_C2) / 2 : ((2 * u - 2) ** 2 * ((BACK_C2 + 1) * (2 * u - 2) + BACK_C2) + 2) / 2,
|
|
364
|
+
// elastic: rings around the target before settling (playful spring)
|
|
365
|
+
easeInElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : -(2 ** (10 * u - 10)) * Math.sin((u * 10 - 10.75) * ELASTIC_C4),
|
|
366
|
+
easeOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : 2 ** (-10 * u) * Math.sin((u * 10 - 0.75) * ELASTIC_C4) + 1,
|
|
367
|
+
easeInOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? -(2 ** (20 * u - 10) * Math.sin((20 * u - 11.125) * ELASTIC_C5)) / 2 : 2 ** (-20 * u + 10) * Math.sin((20 * u - 11.125) * ELASTIC_C5) / 2 + 1,
|
|
368
|
+
// bounce: drops and bounces to rest (lands without overshoot)
|
|
369
|
+
easeInBounce: (u) => 1 - easeOutBounce(1 - u),
|
|
370
|
+
easeOutBounce,
|
|
371
|
+
easeInOutBounce: (u) => u < 0.5 ? (1 - easeOutBounce(1 - 2 * u)) / 2 : (1 + easeOutBounce(2 * u - 1)) / 2
|
|
187
372
|
};
|
|
188
373
|
var EASE_NAMES = Object.keys(EASE_TABLE);
|
|
189
374
|
function resolveEase(ease) {
|
|
@@ -298,6 +483,7 @@
|
|
|
298
483
|
const ops = [];
|
|
299
484
|
const valueAt = (target, prop, fallback) => {
|
|
300
485
|
let value = compiled2.initialValues.get(`${target}.${prop}`) ?? fallback;
|
|
486
|
+
let segStart = Number.NEGATIVE_INFINITY;
|
|
301
487
|
const segs = compiled2.segments.get(`${target}.${prop}`);
|
|
302
488
|
if (segs) {
|
|
303
489
|
let active;
|
|
@@ -306,6 +492,7 @@
|
|
|
306
492
|
else break;
|
|
307
493
|
}
|
|
308
494
|
if (active) {
|
|
495
|
+
segStart = active.t0;
|
|
309
496
|
if (t >= active.t1) {
|
|
310
497
|
value = active.to;
|
|
311
498
|
} else {
|
|
@@ -314,6 +501,23 @@
|
|
|
314
501
|
}
|
|
315
502
|
}
|
|
316
503
|
}
|
|
504
|
+
if (prop === "x" || prop === "y" || prop === "rotation") {
|
|
505
|
+
const drivers = compiled2.motionPaths.get(target);
|
|
506
|
+
if (drivers) {
|
|
507
|
+
let active;
|
|
508
|
+
for (const d of drivers) {
|
|
509
|
+
if (d.t0 <= t) active = d;
|
|
510
|
+
else break;
|
|
511
|
+
}
|
|
512
|
+
if (active && active.t0 >= segStart && (prop !== "rotation" || active.autoRotate) && active.points.length > 0) {
|
|
513
|
+
const span = active.t1 - active.t0;
|
|
514
|
+
const u = span <= 0 ? 1 : resolveEase(active.ease)(Math.max(0, Math.min(1, (t - active.t0) / span)));
|
|
515
|
+
if (prop === "x") value = pathPoint(active.points, active.closed, u)[0];
|
|
516
|
+
else if (prop === "y") value = pathPoint(active.points, active.closed, u)[1];
|
|
517
|
+
else value = pathTangentAngle(active.points, active.closed, u) + active.rotateOffset;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
317
521
|
for (const b of compiled2.ir.behaviors ?? []) {
|
|
318
522
|
if (b.target === target && b.prop === prop && typeof value === "number") {
|
|
319
523
|
const envelope = behaviorEnvelope(b, t);
|
|
@@ -394,6 +598,40 @@
|
|
|
394
598
|
});
|
|
395
599
|
return;
|
|
396
600
|
}
|
|
601
|
+
case "image": {
|
|
602
|
+
const width = num(id, "width", node.props.width);
|
|
603
|
+
const height = num(id, "height", node.props.height);
|
|
604
|
+
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
605
|
+
ops.push({
|
|
606
|
+
type: "image",
|
|
607
|
+
id,
|
|
608
|
+
transform: matrix,
|
|
609
|
+
opacity,
|
|
610
|
+
src: str(id, "src", node.props.src),
|
|
611
|
+
width,
|
|
612
|
+
height,
|
|
613
|
+
offsetX: -width * ax,
|
|
614
|
+
offsetY: -height * ay
|
|
615
|
+
});
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
case "path": {
|
|
619
|
+
const ox = num(id, "originX", node.props.originX ?? 0);
|
|
620
|
+
const oy = num(id, "originY", node.props.originY ?? 0);
|
|
621
|
+
const fill = opt(id, "fill", node.props.fill);
|
|
622
|
+
const stroke = opt(id, "stroke", node.props.stroke);
|
|
623
|
+
ops.push({
|
|
624
|
+
type: "path",
|
|
625
|
+
id,
|
|
626
|
+
transform: ox === 0 && oy === 0 ? matrix : multiply(matrix, [1, 0, 0, 1, -ox, -oy]),
|
|
627
|
+
opacity,
|
|
628
|
+
d: str(id, "d", node.props.d),
|
|
629
|
+
progress: Math.max(0, Math.min(1, num(id, "progress", node.props.progress ?? 1))),
|
|
630
|
+
...fill !== void 0 && { fill },
|
|
631
|
+
...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) }
|
|
632
|
+
});
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
397
635
|
case "text": {
|
|
398
636
|
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
399
637
|
const raw = valueAt(id, "content", node.props.content);
|
|
@@ -424,7 +662,7 @@
|
|
|
424
662
|
}
|
|
425
663
|
|
|
426
664
|
// ../renderer-canvas/src/index.ts
|
|
427
|
-
function renderFrame(ctx2, compiled2, t) {
|
|
665
|
+
function renderFrame(ctx2, compiled2, t, images2) {
|
|
428
666
|
const { size, background } = compiled2.ir;
|
|
429
667
|
ctx2.setTransform(1, 0, 0, 1, 0, 0);
|
|
430
668
|
ctx2.clearRect(0, 0, size.width, size.height);
|
|
@@ -432,9 +670,9 @@
|
|
|
432
670
|
ctx2.fillStyle = background;
|
|
433
671
|
ctx2.fillRect(0, 0, size.width, size.height);
|
|
434
672
|
}
|
|
435
|
-
drawDisplayList(ctx2, evaluate(compiled2, t));
|
|
673
|
+
drawDisplayList(ctx2, evaluate(compiled2, t), images2);
|
|
436
674
|
}
|
|
437
|
-
function drawDisplayList(ctx2, ops) {
|
|
675
|
+
function drawDisplayList(ctx2, ops, images2) {
|
|
438
676
|
for (const op of ops) {
|
|
439
677
|
ctx2.save();
|
|
440
678
|
ctx2.setTransform(...op.transform);
|
|
@@ -490,6 +728,48 @@
|
|
|
490
728
|
ctx2.stroke();
|
|
491
729
|
break;
|
|
492
730
|
}
|
|
731
|
+
case "image": {
|
|
732
|
+
const img = images2?.get(op.src);
|
|
733
|
+
if (img) {
|
|
734
|
+
ctx2.drawImage(img, op.offsetX, op.offsetY, op.width, op.height);
|
|
735
|
+
} else {
|
|
736
|
+
ctx2.fillStyle = "#2A2A30";
|
|
737
|
+
ctx2.fillRect(op.offsetX, op.offsetY, op.width, op.height);
|
|
738
|
+
ctx2.strokeStyle = "#FF00FF";
|
|
739
|
+
ctx2.lineWidth = 2;
|
|
740
|
+
ctx2.strokeRect(op.offsetX, op.offsetY, op.width, op.height);
|
|
741
|
+
ctx2.beginPath();
|
|
742
|
+
ctx2.moveTo(op.offsetX, op.offsetY);
|
|
743
|
+
ctx2.lineTo(op.offsetX + op.width, op.offsetY + op.height);
|
|
744
|
+
ctx2.moveTo(op.offsetX + op.width, op.offsetY);
|
|
745
|
+
ctx2.lineTo(op.offsetX, op.offsetY + op.height);
|
|
746
|
+
ctx2.stroke();
|
|
747
|
+
}
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
case "path": {
|
|
751
|
+
const p = new Path2D(op.d);
|
|
752
|
+
if (op.fill) {
|
|
753
|
+
ctx2.fillStyle = op.fill;
|
|
754
|
+
ctx2.fill(p);
|
|
755
|
+
}
|
|
756
|
+
if (op.stroke && (op.strokeWidth ?? 1) > 0) {
|
|
757
|
+
ctx2.strokeStyle = op.stroke;
|
|
758
|
+
ctx2.lineWidth = op.strokeWidth ?? 1;
|
|
759
|
+
ctx2.lineJoin = "round";
|
|
760
|
+
ctx2.lineCap = "round";
|
|
761
|
+
if (op.progress < 1) {
|
|
762
|
+
const len = pathLength(op.d);
|
|
763
|
+
if (len > 0) {
|
|
764
|
+
ctx2.setLineDash([len, len]);
|
|
765
|
+
ctx2.lineDashOffset = len * (1 - op.progress);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
ctx2.stroke(p);
|
|
769
|
+
ctx2.setLineDash([]);
|
|
770
|
+
}
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
493
773
|
case "text": {
|
|
494
774
|
ctx2.font = `${op.fontWeight} ${op.fontSize}px ${quoteFamily(op.fontFamily)}`;
|
|
495
775
|
if (op.letterSpacing) ctx2.letterSpacing = `${op.letterSpacing}px`;
|
|
@@ -507,13 +787,28 @@
|
|
|
507
787
|
function quoteFamily(family) {
|
|
508
788
|
return family.includes(" ") && !family.includes('"') ? `"${family}"` : family;
|
|
509
789
|
}
|
|
790
|
+
var pathLengthCache = /* @__PURE__ */ new Map();
|
|
791
|
+
function pathLength(d) {
|
|
792
|
+
const hit = pathLengthCache.get(d);
|
|
793
|
+
if (hit !== void 0) return hit;
|
|
794
|
+
let len = 0;
|
|
795
|
+
if (typeof document !== "undefined") {
|
|
796
|
+
const el = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
797
|
+
el.setAttribute("d", d);
|
|
798
|
+
len = el.getTotalLength();
|
|
799
|
+
}
|
|
800
|
+
pathLengthCache.set(d, len);
|
|
801
|
+
return len;
|
|
802
|
+
}
|
|
510
803
|
|
|
511
804
|
// ../render-cli/src/browserEntry.ts
|
|
512
805
|
var compiled = null;
|
|
513
806
|
var ctx = null;
|
|
514
807
|
var canvas = null;
|
|
808
|
+
var images = /* @__PURE__ */ new Map();
|
|
515
809
|
window.__reframe = {
|
|
516
|
-
|
|
810
|
+
// fully decode every image before the first frame — renderFrame is sync
|
|
811
|
+
async init(ir, assets = {}) {
|
|
517
812
|
compiled = compileScene(ir);
|
|
518
813
|
canvas = document.createElement("canvas");
|
|
519
814
|
canvas.width = ir.size.width;
|
|
@@ -521,11 +816,19 @@
|
|
|
521
816
|
document.body.appendChild(canvas);
|
|
522
817
|
ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
523
818
|
if (!ctx) throw new Error("could not create 2d context");
|
|
819
|
+
await Promise.all(
|
|
820
|
+
Object.entries(assets).map(async ([src, dataUrl]) => {
|
|
821
|
+
const img = new Image();
|
|
822
|
+
img.src = dataUrl;
|
|
823
|
+
await img.decode();
|
|
824
|
+
images.set(src, img);
|
|
825
|
+
})
|
|
826
|
+
);
|
|
524
827
|
return { duration: compiled.duration, fps: ir.fps ?? 30 };
|
|
525
828
|
},
|
|
526
829
|
renderFrame(t) {
|
|
527
830
|
if (!compiled || !ctx || !canvas) throw new Error("init() not called");
|
|
528
|
-
renderFrame(ctx, compiled, t);
|
|
831
|
+
renderFrame(ctx, compiled, t, images);
|
|
529
832
|
return canvas.toDataURL("image/png");
|
|
530
833
|
}
|
|
531
834
|
};
|