dsh-remote-plugin 0.6.13 → 0.6.15
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/apk/dsh-remote.apk +0 -0
- package/gateway.cjs +261 -27
- package/package.json +1 -1
- package/public/announcements.json +25 -0
- package/public/app.js +570 -62
- package/public/desktop/desktop.css +42 -7
- package/public/desktop/desktop.html +30 -6
- package/public/desktop/desktop.js +395 -46
- package/public/index.html +57 -20
- package/public/md.js +71 -1
- package/public/morphicons-init.js +41 -0
- package/public/motion.js +469 -0
- package/public/plugin.html +4 -1
- package/public/plugin.js +1 -0
- package/public/styles.css +47 -6
- package/public/transcribe-core.js +74 -0
- package/public/update.json +12 -12
- package/public/vendor/gsap/NOTICE.md +10 -0
- package/public/vendor/gsap/gsap.min.js +11 -0
- package/public/vendor/morphicons/LICENSE +21 -0
- package/public/vendor/morphicons/README.md +10 -0
- package/public/vendor/morphicons/controller-CXZuwJ_M.js +152 -0
- package/public/vendor/morphicons/dom.js +206 -0
- package/public/vendor/morphicons/element.js +261 -0
- package/public/vendor/morphicons/normalize-CYnN3Npw.js +540 -0
- package/public/vendor/morphicons/spring-CFHloqPP.js +623 -0
- package/public/version.json +1 -1
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
//#region src/core/parse.ts
|
|
2
|
+
const COMMANDS = "MmLlHhVvCcSsQqTtAaZz";
|
|
3
|
+
function parsePath(d) {
|
|
4
|
+
const subs = [];
|
|
5
|
+
const n = d.length;
|
|
6
|
+
let i = 0;
|
|
7
|
+
let cx = 0;
|
|
8
|
+
let cy = 0;
|
|
9
|
+
let sx = 0;
|
|
10
|
+
let sy = 0;
|
|
11
|
+
let cur = null;
|
|
12
|
+
let cmd = "";
|
|
13
|
+
let px = 0;
|
|
14
|
+
let py = 0;
|
|
15
|
+
let prev = "";
|
|
16
|
+
let started = false;
|
|
17
|
+
const err = (msg) => {
|
|
18
|
+
throw new Error(`morphicons: ${msg} at d[${i}]`);
|
|
19
|
+
};
|
|
20
|
+
const isDigit = (c) => c >= 48 && c <= 57;
|
|
21
|
+
const skip = () => {
|
|
22
|
+
while (i < n) {
|
|
23
|
+
const c = d.charCodeAt(i);
|
|
24
|
+
if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12 || c === 44) i++;
|
|
25
|
+
else break;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const num = () => {
|
|
29
|
+
skip();
|
|
30
|
+
const start = i;
|
|
31
|
+
if (i < n && (d[i] === "+" || d[i] === "-")) i++;
|
|
32
|
+
let dig = false;
|
|
33
|
+
while (i < n && isDigit(d.charCodeAt(i))) {
|
|
34
|
+
i++;
|
|
35
|
+
dig = true;
|
|
36
|
+
}
|
|
37
|
+
if (i < n && d[i] === ".") {
|
|
38
|
+
i++;
|
|
39
|
+
while (i < n && isDigit(d.charCodeAt(i))) {
|
|
40
|
+
i++;
|
|
41
|
+
dig = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!dig) err("expected number");
|
|
45
|
+
if (i < n && (d[i] === "e" || d[i] === "E")) {
|
|
46
|
+
const save = i;
|
|
47
|
+
i++;
|
|
48
|
+
if (i < n && (d[i] === "+" || d[i] === "-")) i++;
|
|
49
|
+
let ed = false;
|
|
50
|
+
while (i < n && isDigit(d.charCodeAt(i))) {
|
|
51
|
+
i++;
|
|
52
|
+
ed = true;
|
|
53
|
+
}
|
|
54
|
+
if (!ed) i = save;
|
|
55
|
+
}
|
|
56
|
+
return Number(d.slice(start, i));
|
|
57
|
+
};
|
|
58
|
+
const flag = () => {
|
|
59
|
+
skip();
|
|
60
|
+
const c = d[i];
|
|
61
|
+
if (c === "0" || c === "1") {
|
|
62
|
+
i++;
|
|
63
|
+
return c === "1" ? 1 : 0;
|
|
64
|
+
}
|
|
65
|
+
return err("expected arc flag (0|1)");
|
|
66
|
+
};
|
|
67
|
+
const open = () => {
|
|
68
|
+
if (!started) err("path must start with M/m");
|
|
69
|
+
if (!cur) {
|
|
70
|
+
cur = {
|
|
71
|
+
x0: cx,
|
|
72
|
+
y0: cy,
|
|
73
|
+
segs: [],
|
|
74
|
+
closed: false
|
|
75
|
+
};
|
|
76
|
+
subs.push(cur);
|
|
77
|
+
}
|
|
78
|
+
return cur;
|
|
79
|
+
};
|
|
80
|
+
let rel = false;
|
|
81
|
+
const nx = () => num() + (rel ? cx : 0);
|
|
82
|
+
const ny = () => num() + (rel ? cy : 0);
|
|
83
|
+
while (true) {
|
|
84
|
+
skip();
|
|
85
|
+
if (i >= n) break;
|
|
86
|
+
const ch = d[i];
|
|
87
|
+
if (COMMANDS.includes(ch)) {
|
|
88
|
+
cmd = ch;
|
|
89
|
+
i++;
|
|
90
|
+
} else if (cmd === "") err("path must start with M/m");
|
|
91
|
+
else if (cmd === "M") cmd = "L";
|
|
92
|
+
else if (cmd === "m") cmd = "l";
|
|
93
|
+
else if (cmd === "Z" || cmd === "z") err("stray data after Z");
|
|
94
|
+
rel = cmd >= "a";
|
|
95
|
+
switch (rel ? cmd.toUpperCase() : cmd) {
|
|
96
|
+
case "M": {
|
|
97
|
+
started = true;
|
|
98
|
+
const x = nx();
|
|
99
|
+
const y = ny();
|
|
100
|
+
cx = x;
|
|
101
|
+
cy = y;
|
|
102
|
+
sx = x;
|
|
103
|
+
sy = y;
|
|
104
|
+
cur = {
|
|
105
|
+
x0: x,
|
|
106
|
+
y0: y,
|
|
107
|
+
segs: [],
|
|
108
|
+
closed: false
|
|
109
|
+
};
|
|
110
|
+
subs.push(cur);
|
|
111
|
+
prev = "";
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case "L": {
|
|
115
|
+
const x = nx();
|
|
116
|
+
const y = ny();
|
|
117
|
+
open().segs.push([
|
|
118
|
+
"L",
|
|
119
|
+
x,
|
|
120
|
+
y
|
|
121
|
+
]);
|
|
122
|
+
cx = x;
|
|
123
|
+
cy = y;
|
|
124
|
+
prev = "";
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case "H": {
|
|
128
|
+
const x = nx();
|
|
129
|
+
open().segs.push([
|
|
130
|
+
"L",
|
|
131
|
+
x,
|
|
132
|
+
cy
|
|
133
|
+
]);
|
|
134
|
+
cx = x;
|
|
135
|
+
prev = "";
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case "V": {
|
|
139
|
+
const y = ny();
|
|
140
|
+
open().segs.push([
|
|
141
|
+
"L",
|
|
142
|
+
cx,
|
|
143
|
+
y
|
|
144
|
+
]);
|
|
145
|
+
cy = y;
|
|
146
|
+
prev = "";
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "C":
|
|
150
|
+
case "S": {
|
|
151
|
+
let x1;
|
|
152
|
+
let y1;
|
|
153
|
+
if (cmd === "C" || cmd === "c") {
|
|
154
|
+
x1 = nx();
|
|
155
|
+
y1 = ny();
|
|
156
|
+
} else {
|
|
157
|
+
x1 = prev === "C" ? 2 * cx - px : cx;
|
|
158
|
+
y1 = prev === "C" ? 2 * cy - py : cy;
|
|
159
|
+
}
|
|
160
|
+
const x2 = nx();
|
|
161
|
+
const y2 = ny();
|
|
162
|
+
const x = nx();
|
|
163
|
+
const y = ny();
|
|
164
|
+
open().segs.push([
|
|
165
|
+
"C",
|
|
166
|
+
x1,
|
|
167
|
+
y1,
|
|
168
|
+
x2,
|
|
169
|
+
y2,
|
|
170
|
+
x,
|
|
171
|
+
y
|
|
172
|
+
]);
|
|
173
|
+
px = x2;
|
|
174
|
+
py = y2;
|
|
175
|
+
cx = x;
|
|
176
|
+
cy = y;
|
|
177
|
+
prev = "C";
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "Q":
|
|
181
|
+
case "T": {
|
|
182
|
+
let x1;
|
|
183
|
+
let y1;
|
|
184
|
+
if (cmd === "Q" || cmd === "q") {
|
|
185
|
+
x1 = nx();
|
|
186
|
+
y1 = ny();
|
|
187
|
+
} else {
|
|
188
|
+
x1 = prev === "Q" ? 2 * cx - px : cx;
|
|
189
|
+
y1 = prev === "Q" ? 2 * cy - py : cy;
|
|
190
|
+
}
|
|
191
|
+
const x = nx();
|
|
192
|
+
const y = ny();
|
|
193
|
+
open().segs.push([
|
|
194
|
+
"Q",
|
|
195
|
+
x1,
|
|
196
|
+
y1,
|
|
197
|
+
x,
|
|
198
|
+
y
|
|
199
|
+
]);
|
|
200
|
+
px = x1;
|
|
201
|
+
py = y1;
|
|
202
|
+
cx = x;
|
|
203
|
+
cy = y;
|
|
204
|
+
prev = "Q";
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case "A": {
|
|
208
|
+
const rx = num();
|
|
209
|
+
const ry = num();
|
|
210
|
+
const rot = num();
|
|
211
|
+
const large = flag();
|
|
212
|
+
const sweep = flag();
|
|
213
|
+
const x = nx();
|
|
214
|
+
const y = ny();
|
|
215
|
+
open().segs.push([
|
|
216
|
+
"A",
|
|
217
|
+
rx,
|
|
218
|
+
ry,
|
|
219
|
+
rot,
|
|
220
|
+
large,
|
|
221
|
+
sweep,
|
|
222
|
+
x,
|
|
223
|
+
y
|
|
224
|
+
]);
|
|
225
|
+
cx = x;
|
|
226
|
+
cy = y;
|
|
227
|
+
prev = "";
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "Z":
|
|
231
|
+
if (cur) {
|
|
232
|
+
cur.closed = true;
|
|
233
|
+
cur = null;
|
|
234
|
+
}
|
|
235
|
+
cx = sx;
|
|
236
|
+
cy = sy;
|
|
237
|
+
prev = "";
|
|
238
|
+
break;
|
|
239
|
+
default: err(`unsupported command "${cmd}"`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return subs.filter((s) => s.segs.length > 0);
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/core/serialize.ts
|
|
246
|
+
function fmt(v) {
|
|
247
|
+
return String(Math.round(v * 100) / 100);
|
|
248
|
+
}
|
|
249
|
+
/** Sampled subpaths → polyline `d` attribute. `closed[k]` appends Z to
|
|
250
|
+
* subpath k (closed loops in flight); without flags everything is open. */
|
|
251
|
+
function serialize(subs, closed) {
|
|
252
|
+
let d = "";
|
|
253
|
+
for (let k = 0; k < subs.length; k++) {
|
|
254
|
+
const o = subs[k];
|
|
255
|
+
const n = o.length / 2;
|
|
256
|
+
d += `M${fmt(o[0])} ${fmt(o[1])}`;
|
|
257
|
+
for (let i = 1; i < n; i++) d += `L${fmt(o[2 * i])} ${fmt(o[2 * i + 1])}`;
|
|
258
|
+
if (closed?.[k]) d += "Z";
|
|
259
|
+
}
|
|
260
|
+
return d;
|
|
261
|
+
}
|
|
262
|
+
function fmtCanon(v) {
|
|
263
|
+
return String(Math.round(v * 1e4) / 1e4);
|
|
264
|
+
}
|
|
265
|
+
/** Cubic subpaths → canonical `d`, quantized to 4 decimals (engine-stable
|
|
266
|
+
* bytes; see fmtCanon). */
|
|
267
|
+
function cubicsToPathD(paths) {
|
|
268
|
+
let d = "";
|
|
269
|
+
for (const { pts, closed } of paths) {
|
|
270
|
+
d += `M${fmtCanon(pts[0])} ${fmtCanon(pts[1])}`;
|
|
271
|
+
for (let i = 2; i < pts.length; i += 6) d += `C${fmtCanon(pts[i])} ${fmtCanon(pts[i + 1])} ${fmtCanon(pts[i + 2])} ${fmtCanon(pts[i + 3])} ${fmtCanon(pts[i + 4])} ${fmtCanon(pts[i + 5])}`;
|
|
272
|
+
if (closed) d += "Z";
|
|
273
|
+
}
|
|
274
|
+
return d;
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/core/normalize.ts
|
|
278
|
+
/** Control-point offset for a quarter circle: (4/3)·tan(π/8) ≈ 0.5523. */
|
|
279
|
+
const KAPPA = 4 / 3 * Math.tan(Math.PI / 8);
|
|
280
|
+
const TAU = 2 * Math.PI;
|
|
281
|
+
function builder(x0, y0) {
|
|
282
|
+
const pts = [x0, y0];
|
|
283
|
+
let cx = x0;
|
|
284
|
+
let cy = y0;
|
|
285
|
+
const cubic = (x1, y1, x2, y2, x, y) => {
|
|
286
|
+
pts.push(x1, y1, x2, y2, x, y);
|
|
287
|
+
cx = x;
|
|
288
|
+
cy = y;
|
|
289
|
+
};
|
|
290
|
+
const line = (x, y) => {
|
|
291
|
+
if (Math.abs(x - cx) < 1e-12 && Math.abs(y - cy) < 1e-12) return;
|
|
292
|
+
cubic(cx + (x - cx) / 3, cy + (y - cy) / 3, cx + 2 * (x - cx) / 3, cy + 2 * (y - cy) / 3, x, y);
|
|
293
|
+
};
|
|
294
|
+
const quad = (x1, y1, x, y) => {
|
|
295
|
+
cubic(cx + 2 / 3 * (x1 - cx), cy + 2 / 3 * (y1 - cy), x + 2 / 3 * (x1 - x), y + 2 / 3 * (y1 - y), x, y);
|
|
296
|
+
};
|
|
297
|
+
const arc = (rx0, ry0, rotDeg, large, sweep, x, y) => {
|
|
298
|
+
const x1 = cx;
|
|
299
|
+
const y1 = cy;
|
|
300
|
+
if (Math.abs(x - x1) < 1e-12 && Math.abs(y - y1) < 1e-12) return;
|
|
301
|
+
let rx = Math.abs(rx0);
|
|
302
|
+
let ry = Math.abs(ry0);
|
|
303
|
+
if (rx < 1e-12 || ry < 1e-12) {
|
|
304
|
+
line(x, y);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const phi = rotDeg * Math.PI / 180;
|
|
308
|
+
const cosP = Math.cos(phi);
|
|
309
|
+
const sinP = Math.sin(phi);
|
|
310
|
+
const hx = (x1 - x) / 2;
|
|
311
|
+
const hy = (y1 - y) / 2;
|
|
312
|
+
const x1p = cosP * hx + sinP * hy;
|
|
313
|
+
const y1p = -sinP * hx + cosP * hy;
|
|
314
|
+
const lam = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
|
|
315
|
+
if (lam > 1) {
|
|
316
|
+
const s = Math.sqrt(lam);
|
|
317
|
+
rx *= s;
|
|
318
|
+
ry *= s;
|
|
319
|
+
}
|
|
320
|
+
const rx2 = rx * rx;
|
|
321
|
+
const ry2 = ry * ry;
|
|
322
|
+
const xp2 = x1p * x1p;
|
|
323
|
+
const yp2 = y1p * y1p;
|
|
324
|
+
let rad = (rx2 * ry2 - rx2 * yp2 - ry2 * xp2) / (rx2 * yp2 + ry2 * xp2);
|
|
325
|
+
if (rad < 0) rad = 0;
|
|
326
|
+
const co = (large === sweep ? -1 : 1) * Math.sqrt(rad);
|
|
327
|
+
const cxp = co * rx * y1p / ry;
|
|
328
|
+
const cyp = -co * ry * x1p / rx;
|
|
329
|
+
const ccx = cosP * cxp - sinP * cyp + (x1 + x) / 2;
|
|
330
|
+
const ccy = sinP * cxp + cosP * cyp + (y1 + y) / 2;
|
|
331
|
+
const th1 = Math.atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
|
|
332
|
+
let dth = Math.atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx) - th1;
|
|
333
|
+
if (sweep === 0 && dth > 0) dth -= TAU;
|
|
334
|
+
else if (sweep === 1 && dth < 0) dth += TAU;
|
|
335
|
+
const slices = Math.max(1, Math.ceil(Math.abs(dth) / (Math.PI / 2) - 1e-9));
|
|
336
|
+
const delta = dth / slices;
|
|
337
|
+
const alpha = 4 / 3 * Math.tan(delta / 4);
|
|
338
|
+
const ex = (t) => ccx + rx * Math.cos(t) * cosP - ry * Math.sin(t) * sinP;
|
|
339
|
+
const ey = (t) => ccy + rx * Math.cos(t) * sinP + ry * Math.sin(t) * cosP;
|
|
340
|
+
const dx = (t) => -rx * Math.sin(t) * cosP - ry * Math.cos(t) * sinP;
|
|
341
|
+
const dy = (t) => -rx * Math.sin(t) * sinP + ry * Math.cos(t) * cosP;
|
|
342
|
+
let t0 = th1;
|
|
343
|
+
let p0x = x1;
|
|
344
|
+
let p0y = y1;
|
|
345
|
+
for (let s = 1; s <= slices; s++) {
|
|
346
|
+
const t1 = th1 + delta * s;
|
|
347
|
+
const p1x = s === slices ? x : ex(t1);
|
|
348
|
+
const p1y = s === slices ? y : ey(t1);
|
|
349
|
+
cubic(p0x + alpha * dx(t0), p0y + alpha * dy(t0), p1x - alpha * dx(t1), p1y - alpha * dy(t1), p1x, p1y);
|
|
350
|
+
t0 = t1;
|
|
351
|
+
p0x = p1x;
|
|
352
|
+
p0y = p1y;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
const finish = (closed) => {
|
|
356
|
+
if (closed) line(pts[0], pts[1]);
|
|
357
|
+
if (pts.length < 8) return null;
|
|
358
|
+
return {
|
|
359
|
+
pts: Float64Array.from(pts),
|
|
360
|
+
closed
|
|
361
|
+
};
|
|
362
|
+
};
|
|
363
|
+
return [
|
|
364
|
+
cubic,
|
|
365
|
+
line,
|
|
366
|
+
quad,
|
|
367
|
+
arc,
|
|
368
|
+
finish
|
|
369
|
+
];
|
|
370
|
+
}
|
|
371
|
+
function lowerSubpath(raw) {
|
|
372
|
+
const [cubic, line, quad, arc, finish] = builder(raw.x0, raw.y0);
|
|
373
|
+
for (const s of raw.segs) switch (s[0]) {
|
|
374
|
+
case "L":
|
|
375
|
+
line(s[1], s[2]);
|
|
376
|
+
break;
|
|
377
|
+
case "C":
|
|
378
|
+
cubic(s[1], s[2], s[3], s[4], s[5], s[6]);
|
|
379
|
+
break;
|
|
380
|
+
case "Q":
|
|
381
|
+
quad(s[1], s[2], s[3], s[4]);
|
|
382
|
+
break;
|
|
383
|
+
case "A": arc(s[1], s[2], s[3], s[4], s[5], s[6], s[7]);
|
|
384
|
+
}
|
|
385
|
+
return finish(raw.closed);
|
|
386
|
+
}
|
|
387
|
+
function attrNum(attrs, key, fallback = 0) {
|
|
388
|
+
const v = attrs[key];
|
|
389
|
+
if (v === void 0) return fallback;
|
|
390
|
+
const x = typeof v === "number" ? v : Number(v);
|
|
391
|
+
return Number.isFinite(x) ? x : fallback;
|
|
392
|
+
}
|
|
393
|
+
function parsePoints(v) {
|
|
394
|
+
const s = String(v ?? "").trim();
|
|
395
|
+
if (!s) return [];
|
|
396
|
+
const nums = s.split(/[\s,]+/).map(Number);
|
|
397
|
+
if (nums.some((x) => !Number.isFinite(x))) throw new Error(`morphicons: invalid points: "${s}"`);
|
|
398
|
+
return nums;
|
|
399
|
+
}
|
|
400
|
+
function polyPath(nums, closed) {
|
|
401
|
+
if (nums.length < 4) return null;
|
|
402
|
+
const [, line, , , finish] = builder(nums[0], nums[1]);
|
|
403
|
+
for (let i = 2; i + 1 < nums.length; i += 2) line(nums[i], nums[i + 1]);
|
|
404
|
+
return finish(closed);
|
|
405
|
+
}
|
|
406
|
+
function ellipsePath(cx, cy, rx, ry) {
|
|
407
|
+
if (rx < 1e-12 || ry < 1e-12) return null;
|
|
408
|
+
const kx = KAPPA * rx;
|
|
409
|
+
const ky = KAPPA * ry;
|
|
410
|
+
const e = cx + rx;
|
|
411
|
+
const w = cx - rx;
|
|
412
|
+
const s = cy + ry;
|
|
413
|
+
const n = cy - ry;
|
|
414
|
+
const [cubic, , , , finish] = builder(e, cy);
|
|
415
|
+
cubic(e, cy + ky, cx + kx, s, cx, s);
|
|
416
|
+
cubic(cx - kx, s, w, cy + ky, w, cy);
|
|
417
|
+
cubic(w, cy - ky, cx - kx, n, cx, n);
|
|
418
|
+
cubic(cx + kx, n, e, cy - ky, e, cy);
|
|
419
|
+
return finish(true);
|
|
420
|
+
}
|
|
421
|
+
function rectPath(attrs) {
|
|
422
|
+
const x = attrNum(attrs, "x");
|
|
423
|
+
const y = attrNum(attrs, "y");
|
|
424
|
+
const w = attrNum(attrs, "width");
|
|
425
|
+
const h = attrNum(attrs, "height");
|
|
426
|
+
if (w < 1e-12 || h < 1e-12) return null;
|
|
427
|
+
let rx = attrNum(attrs, "rx", NaN);
|
|
428
|
+
let ry = attrNum(attrs, "ry", NaN);
|
|
429
|
+
if (Number.isNaN(rx)) rx = Number.isNaN(ry) ? 0 : ry;
|
|
430
|
+
if (Number.isNaN(ry)) ry = rx;
|
|
431
|
+
rx = Math.min(Math.max(rx, 0), w / 2);
|
|
432
|
+
ry = Math.min(Math.max(ry, 0), h / 2);
|
|
433
|
+
if (rx < 1e-12 || ry < 1e-12) return polyPath([
|
|
434
|
+
x,
|
|
435
|
+
y,
|
|
436
|
+
x + w,
|
|
437
|
+
y,
|
|
438
|
+
x + w,
|
|
439
|
+
y + h,
|
|
440
|
+
x,
|
|
441
|
+
y + h
|
|
442
|
+
], true);
|
|
443
|
+
const xa = x + rx;
|
|
444
|
+
const xb = x + w - rx;
|
|
445
|
+
const xr = x + w;
|
|
446
|
+
const ya = y + ry;
|
|
447
|
+
const yb = y + h - ry;
|
|
448
|
+
const yd = y + h;
|
|
449
|
+
const kx = KAPPA * rx;
|
|
450
|
+
const ky = KAPPA * ry;
|
|
451
|
+
const [cubic, line, , , finish] = builder(xa, y);
|
|
452
|
+
line(xb, y);
|
|
453
|
+
cubic(xb + kx, y, xr, ya - ky, xr, ya);
|
|
454
|
+
line(xr, yb);
|
|
455
|
+
cubic(xr, yb + ky, xb + kx, yd, xb, yd);
|
|
456
|
+
line(xa, yd);
|
|
457
|
+
cubic(xa - kx, yd, x, yb + ky, x, yb);
|
|
458
|
+
line(x, ya);
|
|
459
|
+
cubic(x, ya - ky, xa - kx, y, xa, y);
|
|
460
|
+
return finish(true);
|
|
461
|
+
}
|
|
462
|
+
/** Icon (IconNode or `d` string) → list of cubic subpaths. */
|
|
463
|
+
function iconToCubics(input) {
|
|
464
|
+
const out = [];
|
|
465
|
+
const push = (p) => {
|
|
466
|
+
if (p) out.push(p);
|
|
467
|
+
};
|
|
468
|
+
if (typeof input === "string") {
|
|
469
|
+
for (const s of parsePath(input)) push(lowerSubpath(s));
|
|
470
|
+
return out;
|
|
471
|
+
}
|
|
472
|
+
for (const [tag, attrs] of input) switch (tag) {
|
|
473
|
+
case "path":
|
|
474
|
+
for (const s of parsePath(String(attrs.d ?? ""))) push(lowerSubpath(s));
|
|
475
|
+
break;
|
|
476
|
+
case "line": {
|
|
477
|
+
const [, line, , , finish] = builder(attrNum(attrs, "x1"), attrNum(attrs, "y1"));
|
|
478
|
+
line(attrNum(attrs, "x2"), attrNum(attrs, "y2"));
|
|
479
|
+
push(finish(false));
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
case "circle": {
|
|
483
|
+
const r = attrNum(attrs, "r");
|
|
484
|
+
push(ellipsePath(attrNum(attrs, "cx"), attrNum(attrs, "cy"), r, r));
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
case "ellipse":
|
|
488
|
+
push(ellipsePath(attrNum(attrs, "cx"), attrNum(attrs, "cy"), attrNum(attrs, "rx"), attrNum(attrs, "ry")));
|
|
489
|
+
break;
|
|
490
|
+
case "rect":
|
|
491
|
+
push(rectPath(attrs));
|
|
492
|
+
break;
|
|
493
|
+
case "polyline":
|
|
494
|
+
push(polyPath(parsePoints(attrs.points), false));
|
|
495
|
+
break;
|
|
496
|
+
case "polygon":
|
|
497
|
+
push(polyPath(parsePoints(attrs.points), true));
|
|
498
|
+
break;
|
|
499
|
+
default: throw new Error(`morphicons: unsupported tag <${tag}>`);
|
|
500
|
+
}
|
|
501
|
+
return out;
|
|
502
|
+
}
|
|
503
|
+
function parseViewBox(vb) {
|
|
504
|
+
const v = typeof vb === "number" ? [
|
|
505
|
+
0,
|
|
506
|
+
0,
|
|
507
|
+
vb,
|
|
508
|
+
vb
|
|
509
|
+
] : typeof vb === "string" ? vb.trim().split(/[\s,]+/).map(Number) : vb;
|
|
510
|
+
const [minX, minY, w, h] = v;
|
|
511
|
+
if (v.length !== 4 || !(w > 0) || !(h > 0) || !Number.isFinite(minX) || !Number.isFinite(minY)) throw new Error(`morphicons: invalid viewBox "${String(vb)}"`);
|
|
512
|
+
return [
|
|
513
|
+
minX,
|
|
514
|
+
minY,
|
|
515
|
+
w,
|
|
516
|
+
h
|
|
517
|
+
];
|
|
518
|
+
}
|
|
519
|
+
/** Re-grids an icon drawn on `viewBox` onto the shared `grid` (24 by default),
|
|
520
|
+
* centred and preserving aspect ratio — the SVG `xMidYMid meet` rule.
|
|
521
|
+
*
|
|
522
|
+
* Both endpoints of a morph must live on the same coordinate space. Lucide and
|
|
523
|
+
* Tabler already draw on 24×24; packs on 20 (Heroicons solid) or 32 (Carbon)
|
|
524
|
+
* do not, and mixing them unfitted makes Procrustes read the scale/offset gap
|
|
525
|
+
* as rotation. Apply once at module scope (not per render) and pass the
|
|
526
|
+
* resulting `d` anywhere an icon is accepted. */
|
|
527
|
+
function fitIcon(input, viewBox, grid = 24) {
|
|
528
|
+
const [minX, minY, w, h] = parseViewBox(viewBox);
|
|
529
|
+
const s = Math.min(grid / w, grid / h);
|
|
530
|
+
const tx = (grid - w * s) / 2 - minX * s;
|
|
531
|
+
const ty = (grid - h * s) / 2 - minY * s;
|
|
532
|
+
const paths = iconToCubics(input);
|
|
533
|
+
for (const { pts } of paths) for (let i = 0; i < pts.length; i += 2) {
|
|
534
|
+
pts[i] = pts[i] * s + tx;
|
|
535
|
+
pts[i + 1] = pts[i + 1] * s + ty;
|
|
536
|
+
}
|
|
537
|
+
return cubicsToPathD(paths);
|
|
538
|
+
}
|
|
539
|
+
//#endregion
|
|
540
|
+
export { serialize as i, iconToCubics as n, cubicsToPathD as r, fitIcon as t };
|