@allyhub/sdk 0.0.0-stage → 0.1.0-alpha.6
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/LICENSE +241 -0
- package/NOTICE +81 -0
- package/README.md +16 -2
- package/client.d.ts +86 -0
- package/dist/anim.d.ts +41 -0
- package/dist/anim.d.ts.map +1 -0
- package/dist/anim.js +75 -0
- package/dist/anim.js.map +1 -0
- package/dist/define.d.ts +75 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +95 -0
- package/dist/define.js.map +1 -0
- package/dist/dom.d.ts +210 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +457 -0
- package/dist/dom.js.map +1 -0
- package/dist/fit.d.ts +24 -0
- package/dist/fit.d.ts.map +1 -0
- package/dist/fit.js +60 -0
- package/dist/fit.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/media.d.ts +9 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +27 -0
- package/dist/media.js.map +1 -0
- package/dist/messages.d.ts +39 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +2 -0
- package/dist/messages.js.map +1 -0
- package/dist/noise.d.ts +15 -0
- package/dist/noise.d.ts.map +1 -0
- package/dist/noise.js +233 -0
- package/dist/noise.js.map +1 -0
- package/dist/parts.d.ts +140 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +414 -0
- package/dist/parts.js.map +1 -0
- package/dist/paths.d.ts +85 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +720 -0
- package/dist/paths.js.map +1 -0
- package/dist/piece.d.ts +64 -0
- package/dist/piece.d.ts.map +1 -0
- package/dist/piece.js +38 -0
- package/dist/piece.js.map +1 -0
- package/dist/props.d.ts +81 -0
- package/dist/props.d.ts.map +1 -0
- package/dist/props.js +35 -0
- package/dist/props.js.map +1 -0
- package/dist/text.d.ts +13 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +38 -0
- package/dist/text.js.map +1 -0
- package/package.json +61 -3
package/dist/paths.js
ADDED
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
// SVG paths measured without a browser: a path's length, the point and the
|
|
2
|
+
// direction at a length, the stroke-dash values that draw it on, the stretch
|
|
3
|
+
// of it between two fractions of its length, and the path between two paths
|
|
4
|
+
// that have the same commands. Pure functions of the path's text, so they
|
|
5
|
+
// give the same numbers in mount, in render and in a test.
|
|
6
|
+
//
|
|
7
|
+
// After Remotion's @remotion/paths
|
|
8
|
+
// (https://github.com/remotion-dev/remotion/tree/main/packages/paths,
|
|
9
|
+
// Copyright (c) 2023 JonnyBurger, MIT): getLength, getPointAtLength,
|
|
10
|
+
// getTangentAtLength, evolvePath and interpolatePath, written again and
|
|
11
|
+
// smaller. Arcs become cubic curves by the SVG specification's endpoint to
|
|
12
|
+
// centre conversion (SVG 1.1, appendix F.6). See NOTICE.
|
|
13
|
+
// ---- reading a path ---------------------------------------------------------
|
|
14
|
+
const NUMBER = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/y;
|
|
15
|
+
const COMMAND = /[MmLlHhVvCcSsQqTtAaZz]/;
|
|
16
|
+
const SEPARATOR = /[\s,]/;
|
|
17
|
+
class Reader {
|
|
18
|
+
i = 0;
|
|
19
|
+
d;
|
|
20
|
+
constructor(d) {
|
|
21
|
+
this.d = d;
|
|
22
|
+
}
|
|
23
|
+
skip() {
|
|
24
|
+
while (this.i < this.d.length && SEPARATOR.test(this.d.charAt(this.i)))
|
|
25
|
+
this.i++;
|
|
26
|
+
}
|
|
27
|
+
done() {
|
|
28
|
+
this.skip();
|
|
29
|
+
return this.i >= this.d.length;
|
|
30
|
+
}
|
|
31
|
+
command() {
|
|
32
|
+
this.skip();
|
|
33
|
+
const c = this.d.charAt(this.i);
|
|
34
|
+
if (!COMMAND.test(c))
|
|
35
|
+
return undefined;
|
|
36
|
+
this.i++;
|
|
37
|
+
return c;
|
|
38
|
+
}
|
|
39
|
+
number() {
|
|
40
|
+
this.skip();
|
|
41
|
+
NUMBER.lastIndex = this.i;
|
|
42
|
+
const m = NUMBER.exec(this.d);
|
|
43
|
+
if (!m)
|
|
44
|
+
throw this.error("a number");
|
|
45
|
+
this.i = NUMBER.lastIndex;
|
|
46
|
+
return Number(m[0]);
|
|
47
|
+
}
|
|
48
|
+
flag() {
|
|
49
|
+
this.skip();
|
|
50
|
+
const c = this.d.charAt(this.i);
|
|
51
|
+
if (c !== "0" && c !== "1")
|
|
52
|
+
throw this.error("an arc flag, 0 or 1");
|
|
53
|
+
this.i++;
|
|
54
|
+
return c === "1" ? 1 : 0;
|
|
55
|
+
}
|
|
56
|
+
error(what) {
|
|
57
|
+
const at = this.d.slice(this.i, this.i + 12);
|
|
58
|
+
return new Error(`Path "${this.d}": expected ${what} at character ${String(this.i)}${at ? ` ("${at}")` : ""}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** A path's commands, absolute: H and V as L, S as C, T as Q. */
|
|
62
|
+
function parse(d) {
|
|
63
|
+
const r = new Reader(d);
|
|
64
|
+
const out = [];
|
|
65
|
+
let cx = 0;
|
|
66
|
+
let cy = 0;
|
|
67
|
+
let sx = 0;
|
|
68
|
+
let sy = 0;
|
|
69
|
+
// The last control point of a C or Q, for S and T to reflect.
|
|
70
|
+
let ctrl = null;
|
|
71
|
+
let letter;
|
|
72
|
+
while (!r.done()) {
|
|
73
|
+
const read = r.command();
|
|
74
|
+
if (read !== undefined)
|
|
75
|
+
letter = read;
|
|
76
|
+
else if (letter === undefined || letter === "Z" || letter === "z")
|
|
77
|
+
throw r.error("a command");
|
|
78
|
+
else if (letter === "M")
|
|
79
|
+
letter = "L";
|
|
80
|
+
else if (letter === "m")
|
|
81
|
+
letter = "l";
|
|
82
|
+
if (letter === undefined)
|
|
83
|
+
break;
|
|
84
|
+
if (out.length === 0 && letter !== "M" && letter !== "m")
|
|
85
|
+
throw new Error(`Path "${d}": a path starts with M`);
|
|
86
|
+
const rel = letter === letter.toLowerCase();
|
|
87
|
+
const ox = rel ? cx : 0;
|
|
88
|
+
const oy = rel ? cy : 0;
|
|
89
|
+
switch (letter.toUpperCase()) {
|
|
90
|
+
case "M": {
|
|
91
|
+
cx = sx = ox + r.number();
|
|
92
|
+
cy = sy = oy + r.number();
|
|
93
|
+
out.push({ type: "M", x: cx, y: cy });
|
|
94
|
+
ctrl = null;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case "L":
|
|
98
|
+
case "H":
|
|
99
|
+
case "V": {
|
|
100
|
+
const up = letter.toUpperCase();
|
|
101
|
+
if (up === "L") {
|
|
102
|
+
cx = ox + r.number();
|
|
103
|
+
cy = oy + r.number();
|
|
104
|
+
}
|
|
105
|
+
else if (up === "H")
|
|
106
|
+
cx = ox + r.number();
|
|
107
|
+
else
|
|
108
|
+
cy = oy + r.number();
|
|
109
|
+
out.push({ type: "L", x: cx, y: cy });
|
|
110
|
+
ctrl = null;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "C":
|
|
114
|
+
case "S": {
|
|
115
|
+
let x1;
|
|
116
|
+
let y1;
|
|
117
|
+
if (letter.toUpperCase() === "C") {
|
|
118
|
+
x1 = ox + r.number();
|
|
119
|
+
y1 = oy + r.number();
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
x1 = ctrl?.type === "C" ? 2 * cx - ctrl.x : cx;
|
|
123
|
+
y1 = ctrl?.type === "C" ? 2 * cy - ctrl.y : cy;
|
|
124
|
+
}
|
|
125
|
+
const x2 = ox + r.number();
|
|
126
|
+
const y2 = oy + r.number();
|
|
127
|
+
cx = ox + r.number();
|
|
128
|
+
cy = oy + r.number();
|
|
129
|
+
out.push({ type: "C", x1, y1, x2, y2, x: cx, y: cy });
|
|
130
|
+
ctrl = { type: "C", x: x2, y: y2 };
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "Q":
|
|
134
|
+
case "T": {
|
|
135
|
+
let x1;
|
|
136
|
+
let y1;
|
|
137
|
+
if (letter.toUpperCase() === "Q") {
|
|
138
|
+
x1 = ox + r.number();
|
|
139
|
+
y1 = oy + r.number();
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
x1 = ctrl?.type === "Q" ? 2 * cx - ctrl.x : cx;
|
|
143
|
+
y1 = ctrl?.type === "Q" ? 2 * cy - ctrl.y : cy;
|
|
144
|
+
}
|
|
145
|
+
cx = ox + r.number();
|
|
146
|
+
cy = oy + r.number();
|
|
147
|
+
out.push({ type: "Q", x1, y1, x: cx, y: cy });
|
|
148
|
+
ctrl = { type: "Q", x: x1, y: y1 };
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case "A": {
|
|
152
|
+
const rx = r.number();
|
|
153
|
+
const ry = r.number();
|
|
154
|
+
const angle = r.number();
|
|
155
|
+
const large = r.flag();
|
|
156
|
+
const sweep = r.flag();
|
|
157
|
+
cx = ox + r.number();
|
|
158
|
+
cy = oy + r.number();
|
|
159
|
+
out.push({ type: "A", rx, ry, angle, large, sweep, x: cx, y: cy });
|
|
160
|
+
ctrl = null;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
default: {
|
|
164
|
+
out.push({ type: "Z" });
|
|
165
|
+
cx = sx;
|
|
166
|
+
cy = sy;
|
|
167
|
+
ctrl = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
/** A number as a path writes it: at most 4 decimals, no `-0`. */
|
|
174
|
+
function fmt(n) {
|
|
175
|
+
const r = Math.round(n * 1e4) / 1e4;
|
|
176
|
+
return String(r === 0 ? 0 : r);
|
|
177
|
+
}
|
|
178
|
+
function write(commands) {
|
|
179
|
+
return commands
|
|
180
|
+
.map((c) => {
|
|
181
|
+
switch (c.type) {
|
|
182
|
+
case "M":
|
|
183
|
+
case "L":
|
|
184
|
+
return `${c.type}${fmt(c.x)} ${fmt(c.y)}`;
|
|
185
|
+
case "C":
|
|
186
|
+
return `C${fmt(c.x1)} ${fmt(c.y1)} ${fmt(c.x2)} ${fmt(c.y2)} ${fmt(c.x)} ${fmt(c.y)}`;
|
|
187
|
+
case "Q":
|
|
188
|
+
return `Q${fmt(c.x1)} ${fmt(c.y1)} ${fmt(c.x)} ${fmt(c.y)}`;
|
|
189
|
+
case "A":
|
|
190
|
+
return `A${fmt(c.rx)} ${fmt(c.ry)} ${fmt(c.angle)} ${String(c.large)} ${String(c.sweep)} ${fmt(c.x)} ${fmt(c.y)}`;
|
|
191
|
+
default:
|
|
192
|
+
return "Z";
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
.join(" ");
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* A path written with absolute commands only (H and V as L, S as C, T as Q,
|
|
199
|
+
* numbers to 4 decimals): what `interpolatePath` compares. Throws on a path
|
|
200
|
+
* it cannot read.
|
|
201
|
+
*/
|
|
202
|
+
export function normalizePath(d) {
|
|
203
|
+
return write(parse(d));
|
|
204
|
+
}
|
|
205
|
+
// ---- measuring ----------------------------------------------------------------
|
|
206
|
+
// Gauss–Legendre nodes and weights on [-1, 1] (Newton's method on the
|
|
207
|
+
// Legendre polynomial), for the length of a curve.
|
|
208
|
+
const GAUSS = ((n) => {
|
|
209
|
+
const xs = new Float64Array(n);
|
|
210
|
+
const ws = new Float64Array(n);
|
|
211
|
+
for (let i = 0; i < Math.ceil(n / 2); i++) {
|
|
212
|
+
let z = Math.cos((Math.PI * (i + 0.75)) / (n + 0.5));
|
|
213
|
+
let pp = 1;
|
|
214
|
+
for (let iter = 0; iter < 100; iter++) {
|
|
215
|
+
let p1 = 1;
|
|
216
|
+
let p2 = 0;
|
|
217
|
+
for (let j = 1; j <= n; j++) {
|
|
218
|
+
const p3 = p2;
|
|
219
|
+
p2 = p1;
|
|
220
|
+
p1 = ((2 * j - 1) * z * p2 - (j - 1) * p3) / j;
|
|
221
|
+
}
|
|
222
|
+
pp = (n * (z * p1 - p2)) / (z * z - 1);
|
|
223
|
+
const z1 = z;
|
|
224
|
+
z = z1 - p1 / pp;
|
|
225
|
+
if (Math.abs(z - z1) < 1e-15)
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
xs[i] = -z;
|
|
229
|
+
xs[n - 1 - i] = z;
|
|
230
|
+
ws[i] = ws[n - 1 - i] = 2 / ((1 - z * z) * pp * pp);
|
|
231
|
+
}
|
|
232
|
+
return { xs, ws };
|
|
233
|
+
})(16);
|
|
234
|
+
/** Steps a curve's length table is kept at. */
|
|
235
|
+
const STEPS = 16;
|
|
236
|
+
function cubicAt(p, t) {
|
|
237
|
+
const mt = 1 - t;
|
|
238
|
+
const a = mt * mt * mt;
|
|
239
|
+
const b = 3 * mt * mt * t;
|
|
240
|
+
const c = 3 * mt * t * t;
|
|
241
|
+
const d = t * t * t;
|
|
242
|
+
return {
|
|
243
|
+
x: a * n(p, 0) + b * n(p, 2) + c * n(p, 4) + d * n(p, 6),
|
|
244
|
+
y: a * n(p, 1) + b * n(p, 3) + c * n(p, 5) + d * n(p, 7),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function cubicDerivative(p, t) {
|
|
248
|
+
const mt = 1 - t;
|
|
249
|
+
const a = 3 * mt * mt;
|
|
250
|
+
const b = 6 * mt * t;
|
|
251
|
+
const c = 3 * t * t;
|
|
252
|
+
return {
|
|
253
|
+
x: a * (n(p, 2) - n(p, 0)) +
|
|
254
|
+
b * (n(p, 4) - n(p, 2)) +
|
|
255
|
+
c * (n(p, 6) - n(p, 4)),
|
|
256
|
+
y: a * (n(p, 3) - n(p, 1)) +
|
|
257
|
+
b * (n(p, 5) - n(p, 3)) +
|
|
258
|
+
c * (n(p, 7) - n(p, 5)),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/** p[i], which is always there. */
|
|
262
|
+
const n = (p, i) => p[i] ?? 0;
|
|
263
|
+
/** A cubic's length from t = a to t = b. */
|
|
264
|
+
function arcLength(p, a, b) {
|
|
265
|
+
const half = (b - a) / 2;
|
|
266
|
+
const mid = (a + b) / 2;
|
|
267
|
+
let sum = 0;
|
|
268
|
+
for (let i = 0; i < GAUSS.xs.length; i++) {
|
|
269
|
+
const d = cubicDerivative(p, mid + half * (GAUSS.xs[i] ?? 0));
|
|
270
|
+
sum += (GAUSS.ws[i] ?? 0) * Math.hypot(d.x, d.y);
|
|
271
|
+
}
|
|
272
|
+
return sum * half;
|
|
273
|
+
}
|
|
274
|
+
function cubicPiece(p, sub) {
|
|
275
|
+
const cum = new Float64Array(STEPS + 1);
|
|
276
|
+
for (let k = 0; k < STEPS; k++)
|
|
277
|
+
cum[k + 1] = (cum[k] ?? 0) + arcLength(p, k / STEPS, (k + 1) / STEPS);
|
|
278
|
+
return { p, cubic: true, length: cum[STEPS] ?? 0, cum, sub, close: false };
|
|
279
|
+
}
|
|
280
|
+
function linePiece(p, sub, close) {
|
|
281
|
+
return {
|
|
282
|
+
p,
|
|
283
|
+
cubic: false,
|
|
284
|
+
length: Math.hypot(n(p, 2) - n(p, 0), n(p, 3) - n(p, 1)),
|
|
285
|
+
cum: null,
|
|
286
|
+
sub,
|
|
287
|
+
close,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The most of a turn one cubic of an arc covers: a quarter, as Chrome draws
|
|
292
|
+
* and measures arcs, so a length here is the browser's (a circle comes out
|
|
293
|
+
* 1.4e-4 longer than 2πr in both).
|
|
294
|
+
*/
|
|
295
|
+
const ARC_STEP = Math.PI / 2;
|
|
296
|
+
/**
|
|
297
|
+
* An arc as cubics of at most a quarter turn each (SVG 1.1, F.6.5).
|
|
298
|
+
*/
|
|
299
|
+
function arcCubics(x1, y1, a) {
|
|
300
|
+
let rx = Math.abs(a.rx);
|
|
301
|
+
let ry = Math.abs(a.ry);
|
|
302
|
+
if (rx === 0 || ry === 0)
|
|
303
|
+
return null;
|
|
304
|
+
const phi = (a.angle * Math.PI) / 180;
|
|
305
|
+
const cos = Math.cos(phi);
|
|
306
|
+
const sin = Math.sin(phi);
|
|
307
|
+
const dx = (x1 - a.x) / 2;
|
|
308
|
+
const dy = (y1 - a.y) / 2;
|
|
309
|
+
const xp = cos * dx + sin * dy;
|
|
310
|
+
const yp = -sin * dx + cos * dy;
|
|
311
|
+
const lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry);
|
|
312
|
+
if (lambda > 1) {
|
|
313
|
+
rx *= Math.sqrt(lambda);
|
|
314
|
+
ry *= Math.sqrt(lambda);
|
|
315
|
+
}
|
|
316
|
+
const num = rx * rx * ry * ry - rx * rx * yp * yp - ry * ry * xp * xp;
|
|
317
|
+
const den = rx * rx * yp * yp + ry * ry * xp * xp;
|
|
318
|
+
const coef = (a.large !== a.sweep ? 1 : -1) * Math.sqrt(Math.max(0, num / den));
|
|
319
|
+
const cxp = (coef * rx * yp) / ry;
|
|
320
|
+
const cyp = (-coef * ry * xp) / rx;
|
|
321
|
+
const cx = cos * cxp - sin * cyp + (x1 + a.x) / 2;
|
|
322
|
+
const cy = sin * cxp + cos * cyp + (y1 + a.y) / 2;
|
|
323
|
+
const angle = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
324
|
+
const ux = (xp - cxp) / rx;
|
|
325
|
+
const uy = (yp - cyp) / ry;
|
|
326
|
+
const theta = angle(1, 0, ux, uy);
|
|
327
|
+
let delta = angle(ux, uy, (-xp - cxp) / rx, (-yp - cyp) / ry);
|
|
328
|
+
if (!a.sweep && delta > 0)
|
|
329
|
+
delta -= 2 * Math.PI;
|
|
330
|
+
if (a.sweep && delta < 0)
|
|
331
|
+
delta += 2 * Math.PI;
|
|
332
|
+
const count = Math.max(1, Math.ceil(Math.abs(delta) / ARC_STEP - 1e-9));
|
|
333
|
+
const step = delta / count;
|
|
334
|
+
const k = (4 / 3) * Math.tan(step / 4);
|
|
335
|
+
const map = (u, v) => [
|
|
336
|
+
cx + cos * rx * u - sin * ry * v,
|
|
337
|
+
cy + sin * rx * u + cos * ry * v,
|
|
338
|
+
];
|
|
339
|
+
const out = [];
|
|
340
|
+
let px = x1;
|
|
341
|
+
let py = y1;
|
|
342
|
+
for (let i = 0; i < count; i++) {
|
|
343
|
+
const a1 = theta + i * step;
|
|
344
|
+
const a2 = a1 + step;
|
|
345
|
+
const c1 = map(Math.cos(a1) - k * Math.sin(a1), Math.sin(a1) + k * Math.cos(a1));
|
|
346
|
+
const c2 = map(Math.cos(a2) + k * Math.sin(a2), Math.sin(a2) - k * Math.cos(a2));
|
|
347
|
+
const end = i === count - 1 ? [a.x, a.y] : map(Math.cos(a2), Math.sin(a2));
|
|
348
|
+
const ex = end[0] ?? 0;
|
|
349
|
+
const ey = end[1] ?? 0;
|
|
350
|
+
out.push([px, py, ...c1, ...c2, ex, ey]);
|
|
351
|
+
px = ex;
|
|
352
|
+
py = ey;
|
|
353
|
+
}
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
function measureCommands(commands) {
|
|
357
|
+
const pieces = [];
|
|
358
|
+
let x = 0;
|
|
359
|
+
let y = 0;
|
|
360
|
+
let sx = 0;
|
|
361
|
+
let sy = 0;
|
|
362
|
+
let sub = -1;
|
|
363
|
+
for (const c of commands) {
|
|
364
|
+
switch (c.type) {
|
|
365
|
+
case "M":
|
|
366
|
+
x = sx = c.x;
|
|
367
|
+
y = sy = c.y;
|
|
368
|
+
sub++;
|
|
369
|
+
continue;
|
|
370
|
+
case "L":
|
|
371
|
+
pieces.push(linePiece([x, y, c.x, c.y], sub, false));
|
|
372
|
+
break;
|
|
373
|
+
case "C":
|
|
374
|
+
pieces.push(cubicPiece([x, y, c.x1, c.y1, c.x2, c.y2, c.x, c.y], sub));
|
|
375
|
+
break;
|
|
376
|
+
case "Q":
|
|
377
|
+
pieces.push(cubicPiece([
|
|
378
|
+
x,
|
|
379
|
+
y,
|
|
380
|
+
x + (2 / 3) * (c.x1 - x),
|
|
381
|
+
y + (2 / 3) * (c.y1 - y),
|
|
382
|
+
c.x + (2 / 3) * (c.x1 - c.x),
|
|
383
|
+
c.y + (2 / 3) * (c.y1 - c.y),
|
|
384
|
+
c.x,
|
|
385
|
+
c.y,
|
|
386
|
+
], sub));
|
|
387
|
+
break;
|
|
388
|
+
case "A": {
|
|
389
|
+
if (x === c.x && y === c.y)
|
|
390
|
+
break;
|
|
391
|
+
const cubics = arcCubics(x, y, c);
|
|
392
|
+
if (cubics)
|
|
393
|
+
for (const p of cubics)
|
|
394
|
+
pieces.push(cubicPiece(p, sub));
|
|
395
|
+
else
|
|
396
|
+
pieces.push(linePiece([x, y, c.x, c.y], sub, false));
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
default:
|
|
400
|
+
pieces.push(linePiece([x, y, sx, sy], sub, true));
|
|
401
|
+
x = sx;
|
|
402
|
+
y = sy;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
x = c.x;
|
|
406
|
+
y = c.y;
|
|
407
|
+
}
|
|
408
|
+
const offsets = [];
|
|
409
|
+
let total = 0;
|
|
410
|
+
for (const piece of pieces) {
|
|
411
|
+
offsets.push(total);
|
|
412
|
+
total += piece.length;
|
|
413
|
+
}
|
|
414
|
+
return { pieces, offsets, total, last: { x, y } };
|
|
415
|
+
}
|
|
416
|
+
const MEASURED = new Map();
|
|
417
|
+
const MEASURED_CACHE = 64;
|
|
418
|
+
function measured(d) {
|
|
419
|
+
const found = MEASURED.get(d);
|
|
420
|
+
if (found)
|
|
421
|
+
return found;
|
|
422
|
+
const m = measureCommands(parse(d));
|
|
423
|
+
if (MEASURED.size >= MEASURED_CACHE) {
|
|
424
|
+
const oldest = MEASURED.keys().next();
|
|
425
|
+
if (!oldest.done)
|
|
426
|
+
MEASURED.delete(oldest.value);
|
|
427
|
+
}
|
|
428
|
+
MEASURED.set(d, m);
|
|
429
|
+
return m;
|
|
430
|
+
}
|
|
431
|
+
/** The curve parameter t at a length s from a cubic's start. */
|
|
432
|
+
function cubicT(piece, s) {
|
|
433
|
+
const cum = piece.cum;
|
|
434
|
+
if (!cum || piece.length <= 0)
|
|
435
|
+
return 0;
|
|
436
|
+
if (s <= 0)
|
|
437
|
+
return 0;
|
|
438
|
+
if (s >= piece.length)
|
|
439
|
+
return 1;
|
|
440
|
+
let k = 0;
|
|
441
|
+
let hi = STEPS;
|
|
442
|
+
while (hi - k > 1) {
|
|
443
|
+
const mid = (k + hi) >> 1;
|
|
444
|
+
if ((cum[mid] ?? 0) <= s)
|
|
445
|
+
k = mid;
|
|
446
|
+
else
|
|
447
|
+
hi = mid;
|
|
448
|
+
}
|
|
449
|
+
const base = k / STEPS;
|
|
450
|
+
const want = s - (cum[k] ?? 0);
|
|
451
|
+
let lo = base;
|
|
452
|
+
let up = (k + 1) / STEPS;
|
|
453
|
+
const span = (cum[k + 1] ?? 0) - (cum[k] ?? 0);
|
|
454
|
+
let t = span > 0 ? base + (up - lo) * (want / span) : base;
|
|
455
|
+
for (let i = 0; i < 12; i++) {
|
|
456
|
+
const f = arcLength(piece.p, base, t) - want;
|
|
457
|
+
if (Math.abs(f) < 1e-9)
|
|
458
|
+
break;
|
|
459
|
+
if (f > 0)
|
|
460
|
+
up = t;
|
|
461
|
+
else
|
|
462
|
+
lo = t;
|
|
463
|
+
const d = cubicDerivative(piece.p, t);
|
|
464
|
+
const speed = Math.hypot(d.x, d.y);
|
|
465
|
+
let next = speed > 0 ? t - f / speed : (lo + up) / 2;
|
|
466
|
+
if (next <= lo || next >= up)
|
|
467
|
+
next = (lo + up) / 2;
|
|
468
|
+
t = next;
|
|
469
|
+
}
|
|
470
|
+
return t;
|
|
471
|
+
}
|
|
472
|
+
/** The piece at a length along the path, and the length into it. */
|
|
473
|
+
function pieceAt(m, length) {
|
|
474
|
+
const s = Math.min(Math.max(length, 0), m.total);
|
|
475
|
+
for (let i = 0; i < m.pieces.length; i++) {
|
|
476
|
+
const piece = m.pieces[i];
|
|
477
|
+
const offset = m.offsets[i] ?? 0;
|
|
478
|
+
if (piece && piece.length > 0 && offset + piece.length >= s)
|
|
479
|
+
return { piece, s: s - offset };
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
/** The path's length, in its own units. Throws on a path it cannot read. */
|
|
484
|
+
export function pathLength(d) {
|
|
485
|
+
return measured(d).total;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* The point at a length along the path (clamped to 0 – its length). A path
|
|
489
|
+
* with no length gives its last point.
|
|
490
|
+
*/
|
|
491
|
+
export function pointAtLength(d, length) {
|
|
492
|
+
const m = measured(d);
|
|
493
|
+
const at = pieceAt(m, length);
|
|
494
|
+
if (!at)
|
|
495
|
+
return { ...m.last };
|
|
496
|
+
const { piece, s } = at;
|
|
497
|
+
if (piece.cubic)
|
|
498
|
+
return cubicAt(piece.p, cubicT(piece, s));
|
|
499
|
+
const f = s / piece.length;
|
|
500
|
+
return {
|
|
501
|
+
x: n(piece.p, 0) + (n(piece.p, 2) - n(piece.p, 0)) * f,
|
|
502
|
+
y: n(piece.p, 1) + (n(piece.p, 3) - n(piece.p, 1)) * f,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* The direction the path runs in at a length along it, a vector of length 1
|
|
507
|
+
* (`Math.atan2(v.y, v.x)` is its angle in radians). A path with no length
|
|
508
|
+
* gives `{ x: 1, y: 0 }`.
|
|
509
|
+
*/
|
|
510
|
+
export function tangentAtLength(d, length) {
|
|
511
|
+
const at = pieceAt(measured(d), length);
|
|
512
|
+
if (!at)
|
|
513
|
+
return { x: 1, y: 0 };
|
|
514
|
+
const { piece, s } = at;
|
|
515
|
+
const p = piece.p;
|
|
516
|
+
let v;
|
|
517
|
+
if (piece.cubic) {
|
|
518
|
+
const t = cubicT(piece, s);
|
|
519
|
+
v = cubicDerivative(p, t);
|
|
520
|
+
if (Math.hypot(v.x, v.y) < 1e-9) {
|
|
521
|
+
// A control point on its end: the direction the curve leaves it in.
|
|
522
|
+
const a = cubicAt(p, Math.max(0, t - 1e-4));
|
|
523
|
+
const b = cubicAt(p, Math.min(1, t + 1e-4));
|
|
524
|
+
v = { x: b.x - a.x, y: b.y - a.y };
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
else
|
|
528
|
+
v = { x: n(p, 2) - n(p, 0), y: n(p, 3) - n(p, 1) };
|
|
529
|
+
const len = Math.hypot(v.x, v.y);
|
|
530
|
+
return len > 0 ? { x: v.x / len, y: v.y / len } : { x: 1, y: 0 };
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* The stroke-dash values that show the first `progress` (0–1, clamped) of
|
|
534
|
+
* the path's stroke: 0 shows none of it, 1 all of it. Set them in render
|
|
535
|
+
* from an eased progress: `css(path, drawOn(d, ease.out(progress(t, 0.2, 1))))`.
|
|
536
|
+
* The gap is twice the length, so a browser that measures the path a little
|
|
537
|
+
* longer never shows the next dash.
|
|
538
|
+
*/
|
|
539
|
+
export function drawOn(d, progress) {
|
|
540
|
+
const length = measured(d).total;
|
|
541
|
+
if (length <= 0)
|
|
542
|
+
return { strokeDasharray: "none", strokeDashoffset: 0 };
|
|
543
|
+
const p = Math.min(Math.max(progress, 0), 1);
|
|
544
|
+
return {
|
|
545
|
+
strokeDasharray: `${fmt(length)} ${fmt(2 * length)}`,
|
|
546
|
+
// At 0 the dash is put wholly before the start, so no cap shows there.
|
|
547
|
+
strokeDashoffset: p <= 0 ? 1.5 * length : length * (1 - p),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
/** A cubic split at t: the curve before and after. */
|
|
551
|
+
function split(p, t) {
|
|
552
|
+
const lerp = (a, b) => a + (b - a) * t;
|
|
553
|
+
const [x0, y0, x1, y1, x2, y2, x3, y3] = [0, 1, 2, 3, 4, 5, 6, 7].map((i) => n(p, i));
|
|
554
|
+
const ax = lerp(x0, x1);
|
|
555
|
+
const ay = lerp(y0, y1);
|
|
556
|
+
const bx = lerp(x1, x2);
|
|
557
|
+
const by = lerp(y1, y2);
|
|
558
|
+
const cx = lerp(x2, x3);
|
|
559
|
+
const cy = lerp(y2, y3);
|
|
560
|
+
const dx = lerp(ax, bx);
|
|
561
|
+
const dy = lerp(ay, by);
|
|
562
|
+
const ex = lerp(bx, cx);
|
|
563
|
+
const ey = lerp(by, cy);
|
|
564
|
+
const fx = lerp(dx, ex);
|
|
565
|
+
const fy = lerp(dy, ey);
|
|
566
|
+
return [
|
|
567
|
+
[x0, y0, ax, ay, dx, dy, fx, fy],
|
|
568
|
+
[fx, fy, ex, ey, cx, cy, x3, y3],
|
|
569
|
+
];
|
|
570
|
+
}
|
|
571
|
+
/** A cubic between t0 and t1. */
|
|
572
|
+
function between(p, t0, t1) {
|
|
573
|
+
const head = t1 < 1 ? split(p, t1)[0] : [...p];
|
|
574
|
+
if (t0 <= 0)
|
|
575
|
+
return head;
|
|
576
|
+
return split(head, t1 > 0 ? t0 / t1 : 0)[1];
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* The stretch of the path from `from` to `to`, fractions of its length
|
|
580
|
+
* (0–1, clamped), as a path: lines and cubic curves (Q and A become cubics),
|
|
581
|
+
* a move at the start of each subpath it crosses, and Z where it keeps a
|
|
582
|
+
* whole subpath. `""` when `to` is not after `from`. Animate `to` to grow a
|
|
583
|
+
* line, both to send a stretch along it.
|
|
584
|
+
*/
|
|
585
|
+
export function trimPath(d, from, to) {
|
|
586
|
+
const m = measured(d);
|
|
587
|
+
const a = Math.min(Math.max(from, 0), 1) * m.total;
|
|
588
|
+
const b = Math.min(Math.max(to, 0), 1) * m.total;
|
|
589
|
+
if (b <= a)
|
|
590
|
+
return "";
|
|
591
|
+
const out = [];
|
|
592
|
+
let pen = null;
|
|
593
|
+
let sub = -1;
|
|
594
|
+
// Whether the output's current subpath began at its subpath's start.
|
|
595
|
+
let whole = false;
|
|
596
|
+
for (let i = 0; i < m.pieces.length; i++) {
|
|
597
|
+
const piece = m.pieces[i];
|
|
598
|
+
if (!piece)
|
|
599
|
+
continue;
|
|
600
|
+
const o = m.offsets[i] ?? 0;
|
|
601
|
+
const s0 = Math.max(a, o);
|
|
602
|
+
const s1 = Math.min(b, o + piece.length);
|
|
603
|
+
if (piece.length <= 0) {
|
|
604
|
+
// A Z that adds no length closes a subpath kept whole.
|
|
605
|
+
if (piece.close && whole && piece.sub === sub && pen && o <= b)
|
|
606
|
+
out.push({ type: "Z" });
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (s1 <= s0)
|
|
610
|
+
continue;
|
|
611
|
+
let seg;
|
|
612
|
+
if (piece.cubic)
|
|
613
|
+
seg = between(piece.p, cubicT(piece, s0 - o), cubicT(piece, s1 - o));
|
|
614
|
+
else {
|
|
615
|
+
const f0 = (s0 - o) / piece.length;
|
|
616
|
+
const f1 = (s1 - o) / piece.length;
|
|
617
|
+
const p = piece.p;
|
|
618
|
+
seg = [f0, f1].flatMap((f) => [
|
|
619
|
+
n(p, 0) + (n(p, 2) - n(p, 0)) * f,
|
|
620
|
+
n(p, 1) + (n(p, 3) - n(p, 1)) * f,
|
|
621
|
+
]);
|
|
622
|
+
}
|
|
623
|
+
const start = { x: n(seg, 0), y: n(seg, 1) };
|
|
624
|
+
if (!pen ||
|
|
625
|
+
piece.sub !== sub ||
|
|
626
|
+
Math.hypot(start.x - pen.x, start.y - pen.y) > 1e-9) {
|
|
627
|
+
out.push({ type: "M", ...start });
|
|
628
|
+
whole =
|
|
629
|
+
s0 === o &&
|
|
630
|
+
m.pieces.findIndex((q) => q.sub === piece.sub && q.length > 0) === i;
|
|
631
|
+
sub = piece.sub;
|
|
632
|
+
}
|
|
633
|
+
const kept = s0 === o && s1 === o + piece.length;
|
|
634
|
+
if (piece.close && whole && kept)
|
|
635
|
+
out.push({ type: "Z" });
|
|
636
|
+
else if (piece.cubic)
|
|
637
|
+
out.push({
|
|
638
|
+
type: "C",
|
|
639
|
+
x1: n(seg, 2),
|
|
640
|
+
y1: n(seg, 3),
|
|
641
|
+
x2: n(seg, 4),
|
|
642
|
+
y2: n(seg, 5),
|
|
643
|
+
x: n(seg, 6),
|
|
644
|
+
y: n(seg, 7),
|
|
645
|
+
});
|
|
646
|
+
else
|
|
647
|
+
out.push({ type: "L", x: n(seg, 2), y: n(seg, 3) });
|
|
648
|
+
pen = { x: n(seg, seg.length - 2), y: n(seg, seg.length - 1) };
|
|
649
|
+
}
|
|
650
|
+
return write(out);
|
|
651
|
+
}
|
|
652
|
+
// ---- between two paths ----------------------------------------------------------
|
|
653
|
+
/**
|
|
654
|
+
* The path `x` of the way from `a` to `b` (0 is `a`, 1 is `b`; values
|
|
655
|
+
* outside 0–1 go on past them, for an easing that overshoots). Both paths
|
|
656
|
+
* must have the same commands once absolute (`normalizePath`), and an arc's
|
|
657
|
+
* flags must match; otherwise it throws, naming the first command that
|
|
658
|
+
* differs. Draw the two shapes with the same commands to morph between them.
|
|
659
|
+
*/
|
|
660
|
+
export function interpolatePath(a, b, x) {
|
|
661
|
+
const ca = parse(a);
|
|
662
|
+
const cb = parse(b);
|
|
663
|
+
if (ca.length !== cb.length)
|
|
664
|
+
throw new Error(`interpolatePath: the paths have ${String(ca.length)} and ${String(cb.length)} commands ("${write(ca)}", "${write(cb)}")`);
|
|
665
|
+
const lerp = (u, v) => u + (v - u) * x;
|
|
666
|
+
const out = ca.map((u, i) => {
|
|
667
|
+
const v = cb[i];
|
|
668
|
+
const differ = () => new Error(`interpolatePath: command ${String(i + 1)} differs: ${write([u])} and ${v ? write([v]) : "nothing"}`);
|
|
669
|
+
if (v?.type !== u.type)
|
|
670
|
+
throw differ();
|
|
671
|
+
switch (u.type) {
|
|
672
|
+
case "Z":
|
|
673
|
+
return u;
|
|
674
|
+
case "M":
|
|
675
|
+
case "L": {
|
|
676
|
+
const w = v;
|
|
677
|
+
return { type: u.type, x: lerp(u.x, w.x), y: lerp(u.y, w.y) };
|
|
678
|
+
}
|
|
679
|
+
case "C": {
|
|
680
|
+
const w = v;
|
|
681
|
+
return {
|
|
682
|
+
type: "C",
|
|
683
|
+
x1: lerp(u.x1, w.x1),
|
|
684
|
+
y1: lerp(u.y1, w.y1),
|
|
685
|
+
x2: lerp(u.x2, w.x2),
|
|
686
|
+
y2: lerp(u.y2, w.y2),
|
|
687
|
+
x: lerp(u.x, w.x),
|
|
688
|
+
y: lerp(u.y, w.y),
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
case "Q": {
|
|
692
|
+
const w = v;
|
|
693
|
+
return {
|
|
694
|
+
type: "Q",
|
|
695
|
+
x1: lerp(u.x1, w.x1),
|
|
696
|
+
y1: lerp(u.y1, w.y1),
|
|
697
|
+
x: lerp(u.x, w.x),
|
|
698
|
+
y: lerp(u.y, w.y),
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
default: {
|
|
702
|
+
const w = v;
|
|
703
|
+
if (w.large !== u.large || w.sweep !== u.sweep)
|
|
704
|
+
throw differ();
|
|
705
|
+
return {
|
|
706
|
+
type: "A",
|
|
707
|
+
rx: lerp(u.rx, w.rx),
|
|
708
|
+
ry: lerp(u.ry, w.ry),
|
|
709
|
+
angle: lerp(u.angle, w.angle),
|
|
710
|
+
large: u.large,
|
|
711
|
+
sweep: u.sweep,
|
|
712
|
+
x: lerp(u.x, w.x),
|
|
713
|
+
y: lerp(u.y, w.y),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
return write(out);
|
|
719
|
+
}
|
|
720
|
+
//# sourceMappingURL=paths.js.map
|