@phin-tech/dottie 0.5.0 → 0.5.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/PARITY.md +7 -2
- package/README.md +10 -3
- package/dist/compile.js +103 -5
- package/dist/dottie-compile.mjs +104 -6
- package/dist/dottie-player.js +1188 -655
- package/dist/dottie-player.min.js +61 -41
- package/dist/dottie.js +1187 -655
- package/dist/types/clicks.d.ts +30 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/model.d.ts +39 -1
- package/dist/types/player.d.ts +35 -0
- package/dist/types/symbols.d.ts +23 -0
- package/dist/types/timeline.d.ts +4 -1
- package/package.json +1 -1
- package/dist/types/cli.d.ts +0 -1
package/dist/dottie.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
/*! dottie.js 0.5.
|
|
1
|
+
/*! dottie.js 0.5.2 | Apache-2.0 | https://github.com/phin-tech/dottie */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
|
|
4
4
|
var __publicField = (obj, key, value2) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2);
|
|
5
5
|
|
|
6
6
|
// src/model.ts
|
|
7
7
|
var SUPPORTED_VERSION = 2;
|
|
8
|
-
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image"];
|
|
8
|
+
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
9
9
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
10
10
|
var KNOWN_FIELDS = {
|
|
11
11
|
file: ["version", "pages", "images"],
|
|
12
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline"],
|
|
12
|
+
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols"],
|
|
13
|
+
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
13
14
|
layer: ["id", "name", "visible", "locked"],
|
|
14
15
|
rgba: ["r", "g", "b", "a"],
|
|
15
16
|
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "gridSize", "opacity", "dashOffset"],
|
|
@@ -33,6 +34,12 @@ var KNOWN_FIELDS = {
|
|
|
33
34
|
"rotation",
|
|
34
35
|
"link",
|
|
35
36
|
"anchor",
|
|
37
|
+
"click",
|
|
38
|
+
"hover",
|
|
39
|
+
"symbol",
|
|
40
|
+
"playback",
|
|
41
|
+
"offset",
|
|
42
|
+
"args",
|
|
36
43
|
"image",
|
|
37
44
|
"reveal"
|
|
38
45
|
],
|
|
@@ -47,7 +54,9 @@ var KNOWN_FIELDS = {
|
|
|
47
54
|
follow: ["path", "start", "end", "ease", "reverse", "orient", "laps"],
|
|
48
55
|
repeat: ["start", "end", "count"],
|
|
49
56
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
50
|
-
marker: ["t", "name"]
|
|
57
|
+
marker: ["t", "name"],
|
|
58
|
+
click: ["scene", "url"],
|
|
59
|
+
hover: ["stroke", "fill", "opacity"]
|
|
51
60
|
};
|
|
52
61
|
var MAX_DURATION = 3600;
|
|
53
62
|
var MAX_MARCH = 1e4;
|
|
@@ -61,6 +70,7 @@ var DottieFormatError = class extends Error {
|
|
|
61
70
|
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
62
71
|
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
63
72
|
var str = (v) => typeof v === "string" ? v : void 0;
|
|
73
|
+
var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
|
|
64
74
|
var bool = (v) => typeof v === "boolean" ? v : void 0;
|
|
65
75
|
var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
|
|
66
76
|
function clampTime(t) {
|
|
@@ -70,6 +80,11 @@ function clampTime(t) {
|
|
|
70
80
|
function clampFPS(f) {
|
|
71
81
|
return Number.isFinite(f) ? Math.min(Math.max(f, 1), 60) : 12;
|
|
72
82
|
}
|
|
83
|
+
function allowedURL(url) {
|
|
84
|
+
const u = url.trim();
|
|
85
|
+
if (!/^(https?:\/\/|mailto:)/i.test(u) || [...u].length <= 7 || /[\u0000-\u001f\u007f-\u009f]/.test(u)) return null;
|
|
86
|
+
return u;
|
|
87
|
+
}
|
|
73
88
|
function rgba(v) {
|
|
74
89
|
if (!isObj(v)) return void 0;
|
|
75
90
|
const r = num(v.r), g = num(v.g), b = num(v.b);
|
|
@@ -184,10 +199,63 @@ var Reader = class {
|
|
|
184
199
|
rotation: num(v.rotation) ?? null,
|
|
185
200
|
link: uuid(v.link) ?? null,
|
|
186
201
|
anchor: str(v.anchor) ?? null,
|
|
202
|
+
click: this.click(v.click),
|
|
203
|
+
hover: this.hover(v.hover),
|
|
204
|
+
symbol: uuid(v.symbol) ?? null,
|
|
205
|
+
playback: this.playback(v.playback),
|
|
206
|
+
offset: num(v.offset) ?? null,
|
|
207
|
+
args: strings(v.args),
|
|
187
208
|
image: this.image(v.image, images),
|
|
188
209
|
reveal: num(v.reveal) ?? null
|
|
189
210
|
};
|
|
190
211
|
}
|
|
212
|
+
click(v) {
|
|
213
|
+
if (!isObj(v)) return null;
|
|
214
|
+
this.fields(v, "click");
|
|
215
|
+
const url = str(v.url);
|
|
216
|
+
const safe = url === void 0 ? null : allowedURL(url);
|
|
217
|
+
if (url !== void 0 && safe === null) this.warn("Skipped a click link that isn't http, https or mailto");
|
|
218
|
+
return { scene: str(v.scene) ?? null, url: safe };
|
|
219
|
+
}
|
|
220
|
+
playback(v) {
|
|
221
|
+
const s = str(v);
|
|
222
|
+
if (s === void 0) return null;
|
|
223
|
+
return ["loop", "once", "still", "click"].includes(s) ? s : "loop";
|
|
224
|
+
}
|
|
225
|
+
symbol(v, images) {
|
|
226
|
+
if (!isObj(v)) return null;
|
|
227
|
+
this.fields(v, "symbol");
|
|
228
|
+
const id = uuid(v.id);
|
|
229
|
+
if (!id) {
|
|
230
|
+
this.warn("Skipped a symbol without an id");
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
const size = (x) => {
|
|
234
|
+
const n = num(x);
|
|
235
|
+
return Math.max(1, n ?? 100);
|
|
236
|
+
};
|
|
237
|
+
const layers = (Array.isArray(v.layers) ? v.layers : []).filter(isObj).flatMap((l) => {
|
|
238
|
+
const lid = uuid(l.id);
|
|
239
|
+
return lid ? [{ id: lid, name: str(l.name) ?? "", visible: bool(l.visible) ?? true, locked: bool(l.locked) ?? false }] : [];
|
|
240
|
+
});
|
|
241
|
+
if (layers.length === 0) layers.push({ id: `${id}-LAYER`, name: "Layer 1", visible: true, locked: false });
|
|
242
|
+
return {
|
|
243
|
+
id,
|
|
244
|
+
name: str(v.name) ?? "Symbol",
|
|
245
|
+
width: size(v.width),
|
|
246
|
+
height: size(v.height),
|
|
247
|
+
layers,
|
|
248
|
+
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
249
|
+
timeline: this.timeline(v.timeline),
|
|
250
|
+
button: bool(v.button) ?? null,
|
|
251
|
+
params: strings(v.params)
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
hover(v) {
|
|
255
|
+
if (!isObj(v)) return null;
|
|
256
|
+
this.fields(v, "hover");
|
|
257
|
+
return { stroke: rgba(v.stroke) ?? null, fill: rgba(v.fill) ?? null, opacity: num(v.opacity) ?? null };
|
|
258
|
+
}
|
|
191
259
|
ease(v) {
|
|
192
260
|
const s = str(v);
|
|
193
261
|
if (s === void 0) return null;
|
|
@@ -280,7 +348,8 @@ var Reader = class {
|
|
|
280
348
|
layers,
|
|
281
349
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
282
350
|
theme: str(v.theme) ?? null,
|
|
283
|
-
timeline: this.timeline(v.timeline)
|
|
351
|
+
timeline: this.timeline(v.timeline),
|
|
352
|
+
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null
|
|
284
353
|
};
|
|
285
354
|
}
|
|
286
355
|
};
|
|
@@ -922,6 +991,190 @@ function hachureLines(polygons, angle2, gap) {
|
|
|
922
991
|
return out;
|
|
923
992
|
}
|
|
924
993
|
|
|
994
|
+
// src/dungeon.ts
|
|
995
|
+
var SUB = 2;
|
|
996
|
+
var BAND = 0.75;
|
|
997
|
+
var GROW = 0.8;
|
|
998
|
+
var JITTER = 0.6;
|
|
999
|
+
function gridLines(polygons, cell) {
|
|
1000
|
+
return [...scan(polygons, cell, false), ...scan(polygons, cell, true)];
|
|
1001
|
+
}
|
|
1002
|
+
function scan(polygons, cell, transposed) {
|
|
1003
|
+
if (!(cell > 0)) return [];
|
|
1004
|
+
const flip = (p) => transposed ? { x: p.y, y: p.x } : p;
|
|
1005
|
+
const polys = polygons.filter((p) => p.length >= 3).map((p) => p.map(flip));
|
|
1006
|
+
const ys = polys.flat().map((p) => p.y);
|
|
1007
|
+
if (ys.length === 0) return [];
|
|
1008
|
+
const minY = Math.min(...ys), maxY2 = Math.max(...ys);
|
|
1009
|
+
const out = [];
|
|
1010
|
+
for (let y = Math.floor(minY / cell) * cell; y < maxY2 - 0.5; y += cell) {
|
|
1011
|
+
if (!(y > minY + 0.5)) continue;
|
|
1012
|
+
const xs = [];
|
|
1013
|
+
for (const poly of polys) {
|
|
1014
|
+
for (let i = 0; i < poly.length; i++) {
|
|
1015
|
+
const a = poly[i], b = poly[(i + 1) % poly.length];
|
|
1016
|
+
if (a.y <= y && y < b.y || b.y <= y && y < a.y) xs.push(a.x + (y - a.y) / (b.y - a.y) * (b.x - a.x));
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
xs.sort((p, q) => p - q);
|
|
1020
|
+
for (let k = 0; k + 1 < xs.length; k += 2) {
|
|
1021
|
+
if (xs[k + 1] - xs[k] > 0.5) out.push([flip({ x: xs[k], y }), flip({ x: xs[k + 1], y })]);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return out;
|
|
1025
|
+
}
|
|
1026
|
+
function lineColor(floor) {
|
|
1027
|
+
if (0.2126 * floor.r + 0.7152 * floor.g + 0.0722 * floor.b > 0.5) {
|
|
1028
|
+
return { r: floor.r * 0.84, g: floor.g * 0.84, b: floor.b * 0.8, a: floor.a };
|
|
1029
|
+
}
|
|
1030
|
+
return { r: floor.r + (1 - floor.r) * 0.25, g: floor.g + (1 - floor.g) * 0.25, b: floor.b + (1 - floor.b) * 0.25, a: floor.a };
|
|
1031
|
+
}
|
|
1032
|
+
function isFloor(e) {
|
|
1033
|
+
return e.style.fillStyle === "grid" && e.style.fill.a > 0 && ["rectangle", "ellipse", "diamond", "path"].includes(e.kind);
|
|
1034
|
+
}
|
|
1035
|
+
function hash01(i, j, salt) {
|
|
1036
|
+
let h = (Math.imul(i, 668265261) ^ Math.imul(j, 374761393) ^ Math.imul(salt, 2654435761)) >>> 0;
|
|
1037
|
+
h = Math.imul(h ^ h >>> 15, 2246822507) >>> 0;
|
|
1038
|
+
h = Math.imul(h ^ h >>> 13, 3266489909) >>> 0;
|
|
1039
|
+
h = (h ^ h >>> 16) >>> 0;
|
|
1040
|
+
return h / 4294967296;
|
|
1041
|
+
}
|
|
1042
|
+
function spot(i, j, cell) {
|
|
1043
|
+
const s = cell / SUB;
|
|
1044
|
+
return { x: (i + 0.5 + (hash01(i, j, 1) - 0.5) * JITTER) * s, y: (j + 0.5 + (hash01(i, j, 2) - 0.5) * JITTER) * s };
|
|
1045
|
+
}
|
|
1046
|
+
function clusterSize(i, j, at, cell) {
|
|
1047
|
+
let nearest2 = Infinity;
|
|
1048
|
+
for (let di = -1; di <= 1; di++) {
|
|
1049
|
+
for (let dj = -1; dj <= 1; dj++) {
|
|
1050
|
+
if (di === 0 && dj === 0) continue;
|
|
1051
|
+
const o = spot(i + di, j + dj, cell);
|
|
1052
|
+
nearest2 = Math.min(nearest2, Math.hypot(o.x - at.x, o.y - at.y));
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return nearest2 * GROW;
|
|
1056
|
+
}
|
|
1057
|
+
function clusterStrokes(c, angle2, size) {
|
|
1058
|
+
const dx = Math.cos(angle2), dy = Math.sin(angle2);
|
|
1059
|
+
const half = size * 0.8, gap = size * 0.5;
|
|
1060
|
+
return [-1, 0, 1].map((k) => {
|
|
1061
|
+
const cx = c.x - dy * gap * k, cy = c.y + dx * gap * k;
|
|
1062
|
+
return [{ x: cx - dx * half, y: cy - dy * half }, { x: cx + dx * half, y: cy + dy * half }];
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
function contains(contours, p) {
|
|
1066
|
+
let inside = false;
|
|
1067
|
+
for (const poly of contours) {
|
|
1068
|
+
if (poly.length < 3) continue;
|
|
1069
|
+
let k = poly.length - 1;
|
|
1070
|
+
for (let i = 0; i < poly.length; i++) {
|
|
1071
|
+
const a = poly[i], b = poly[k];
|
|
1072
|
+
if (a.y > p.y !== b.y > p.y && p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x) inside = !inside;
|
|
1073
|
+
k = i;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return inside;
|
|
1077
|
+
}
|
|
1078
|
+
function distance(p, contours) {
|
|
1079
|
+
let best = Infinity;
|
|
1080
|
+
for (const poly of contours) {
|
|
1081
|
+
if (poly.length < 2) continue;
|
|
1082
|
+
for (let i = 0; i < poly.length; i++) {
|
|
1083
|
+
const a = poly[i], b = poly[(i + 1) % poly.length];
|
|
1084
|
+
const dx = b.x - a.x, dy = b.y - a.y;
|
|
1085
|
+
const len2 = dx * dx + dy * dy;
|
|
1086
|
+
const t = len2 > 0 ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2)) : 0;
|
|
1087
|
+
best = Math.min(best, Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy)));
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return best;
|
|
1091
|
+
}
|
|
1092
|
+
var containsPoint = (r, p) => p.x >= r.x && p.x < r.x + r.width && p.y >= r.y && p.y < r.y + r.height;
|
|
1093
|
+
var rockCache = /* @__PURE__ */ new Map();
|
|
1094
|
+
function rock(floors) {
|
|
1095
|
+
if (floors.length === 0) return [];
|
|
1096
|
+
const key = floors.map((e) => [
|
|
1097
|
+
e.kind,
|
|
1098
|
+
e.x,
|
|
1099
|
+
e.y,
|
|
1100
|
+
e.width,
|
|
1101
|
+
e.height,
|
|
1102
|
+
e.rotation ?? 0,
|
|
1103
|
+
cellSize(e),
|
|
1104
|
+
JSON.stringify(e.subpaths ?? null),
|
|
1105
|
+
e.style.stroke.r,
|
|
1106
|
+
e.style.stroke.g,
|
|
1107
|
+
e.style.stroke.b,
|
|
1108
|
+
e.style.stroke.a,
|
|
1109
|
+
e.style.roughness,
|
|
1110
|
+
e.style.strokeWidth
|
|
1111
|
+
].join(",")).join(";");
|
|
1112
|
+
const hit = rockCache.get(key);
|
|
1113
|
+
if (hit) return hit;
|
|
1114
|
+
const v = computeRock(floors);
|
|
1115
|
+
if (rockCache.size >= 16) rockCache.clear();
|
|
1116
|
+
rockCache.set(key, v);
|
|
1117
|
+
return v;
|
|
1118
|
+
}
|
|
1119
|
+
function computeRock(elements) {
|
|
1120
|
+
const floors = elements.map((e) => {
|
|
1121
|
+
const contours = fillPolygons(e).map((c) => c.map((p) => rotated(e, p)));
|
|
1122
|
+
const pts = contours.flat();
|
|
1123
|
+
const xs = pts.map((p) => p.x), ys = pts.map((p) => p.y);
|
|
1124
|
+
const x0 = xs.length ? Math.min(...xs) : 0, y0 = ys.length ? Math.min(...ys) : 0;
|
|
1125
|
+
const box = { x: x0, y: y0, width: (xs.length ? Math.max(...xs) : 0) - x0, height: (ys.length ? Math.max(...ys) : 0) - y0 };
|
|
1126
|
+
return { contours, box, cell: cellSize(e), element: e };
|
|
1127
|
+
});
|
|
1128
|
+
const strokes = floors.map(() => []);
|
|
1129
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1130
|
+
for (const f of floors) {
|
|
1131
|
+
const subSize = f.cell / SUB;
|
|
1132
|
+
const around = inset(f.box, -f.cell, -f.cell);
|
|
1133
|
+
const i0 = Math.floor(around.x / subSize), i1 = Math.ceil((around.x + around.width) / subSize);
|
|
1134
|
+
const j0 = Math.floor(around.y / subSize), j1 = Math.ceil((around.y + around.height) / subSize);
|
|
1135
|
+
const reach = BAND * f.cell;
|
|
1136
|
+
for (let i = i0; i < i1; i++) {
|
|
1137
|
+
for (let j = j0; j < j1; j++) {
|
|
1138
|
+
const k = `${f.cell}|${i}|${j}`;
|
|
1139
|
+
if (seen.has(k)) continue;
|
|
1140
|
+
seen.add(k);
|
|
1141
|
+
const at = spot(i, j, f.cell);
|
|
1142
|
+
let nearest2 = Infinity, owner = -1;
|
|
1143
|
+
for (let n = 0; n < floors.length; n++) {
|
|
1144
|
+
const g = floors[n];
|
|
1145
|
+
if (!containsPoint(inset(g.box, -reach, -reach), at)) continue;
|
|
1146
|
+
if (contains(g.contours, at)) {
|
|
1147
|
+
nearest2 = 0;
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
const d = distance(at, g.contours);
|
|
1151
|
+
if (d < nearest2) {
|
|
1152
|
+
nearest2 = d;
|
|
1153
|
+
owner = n;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (!(nearest2 > 0 && nearest2 <= reach && owner >= 0)) continue;
|
|
1157
|
+
const angle2 = hash01(i, j, 3) * Math.PI;
|
|
1158
|
+
const size = clusterSize(i, j, at, f.cell);
|
|
1159
|
+
const seed = Math.trunc(hash01(i, j, 4) * 4294967296) >>> 0;
|
|
1160
|
+
for (const [a, b] of clusterStrokes(at, angle2, size)) strokes[owner].push([a, b, seed]);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
const out = [];
|
|
1165
|
+
floors.forEach((f, n) => {
|
|
1166
|
+
if (strokes[n].length === 0) return;
|
|
1167
|
+
const e = f.element;
|
|
1168
|
+
const path = new Path();
|
|
1169
|
+
for (const [a, b, seed] of strokes[n]) {
|
|
1170
|
+
if (e.style.roughness <= 0) path.moveTo(a).lineTo(b);
|
|
1171
|
+
else line(a, b, new SeededRNG(seed), e.style.roughness * 0.35, 0.2, false, path);
|
|
1172
|
+
}
|
|
1173
|
+
out.push({ ink: { ...e.style.stroke, a: e.style.stroke.a * 0.6 }, width: Math.max(0.8, e.style.strokeWidth * 0.45), path });
|
|
1174
|
+
});
|
|
1175
|
+
return out;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
925
1178
|
// src/shapes.ts
|
|
926
1179
|
var Cache = class {
|
|
927
1180
|
constructor(limit = 6e3) {
|
|
@@ -1174,196 +1427,566 @@ function computeConnector(e, index) {
|
|
|
1174
1427
|
return [path, solid];
|
|
1175
1428
|
}
|
|
1176
1429
|
|
|
1177
|
-
// src/
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
for (const poly of polys) {
|
|
1197
|
-
for (let i = 0; i < poly.length; i++) {
|
|
1198
|
-
const a = poly[i], b = poly[(i + 1) % poly.length];
|
|
1199
|
-
if (a.y <= y && y < b.y || b.y <= y && y < a.y) xs.push(a.x + (y - a.y) / (b.y - a.y) * (b.x - a.x));
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
xs.sort((p, q) => p - q);
|
|
1203
|
-
for (let k = 0; k + 1 < xs.length; k += 2) {
|
|
1204
|
-
if (xs[k + 1] - xs[k] > 0.5) out.push([flip({ x: xs[k], y }), flip({ x: xs[k + 1], y })]);
|
|
1205
|
-
}
|
|
1430
|
+
// src/timeline.ts
|
|
1431
|
+
function applyEase(ease, u) {
|
|
1432
|
+
switch (ease) {
|
|
1433
|
+
case "linear":
|
|
1434
|
+
return u;
|
|
1435
|
+
case "easeIn":
|
|
1436
|
+
return u * u * u;
|
|
1437
|
+
case "easeOut":
|
|
1438
|
+
return 1 - Math.pow(1 - u, 3);
|
|
1439
|
+
case "easeInOut":
|
|
1440
|
+
return u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
|
|
1441
|
+
case "hold":
|
|
1442
|
+
return 0;
|
|
1443
|
+
case "spring":
|
|
1444
|
+
return spring(u);
|
|
1445
|
+
case "bounce":
|
|
1446
|
+
return bounce(u);
|
|
1447
|
+
case "elastic":
|
|
1448
|
+
return elastic(u);
|
|
1206
1449
|
}
|
|
1207
|
-
return out;
|
|
1208
1450
|
}
|
|
1209
|
-
function
|
|
1210
|
-
if (
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
return
|
|
1451
|
+
function spring(u) {
|
|
1452
|
+
if (u <= 0) return 0;
|
|
1453
|
+
if (u >= 1) return 1;
|
|
1454
|
+
const s = (u2) => 1 - Math.exp(-6 * u2) * (Math.cos(3 * Math.PI * u2) + 6 / (3 * Math.PI) * Math.sin(3 * Math.PI * u2));
|
|
1455
|
+
return s(u) + u * (1 - s(1));
|
|
1214
1456
|
}
|
|
1215
|
-
function
|
|
1216
|
-
|
|
1457
|
+
function bounce(u) {
|
|
1458
|
+
if (u <= 0) return 0;
|
|
1459
|
+
if (u >= 1) return 1;
|
|
1460
|
+
const n = 7.5625, d = 2.75;
|
|
1461
|
+
if (u < 1 / d) return n * u * u;
|
|
1462
|
+
if (u < 2 / d) {
|
|
1463
|
+
const v2 = u - 1.5 / d;
|
|
1464
|
+
return n * v2 * v2 + 0.75;
|
|
1465
|
+
}
|
|
1466
|
+
if (u < 2.5 / d) {
|
|
1467
|
+
const v2 = u - 2.25 / d;
|
|
1468
|
+
return n * v2 * v2 + 0.9375;
|
|
1469
|
+
}
|
|
1470
|
+
const v = u - 2.625 / d;
|
|
1471
|
+
return n * v * v + 0.984375;
|
|
1217
1472
|
}
|
|
1218
|
-
function
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
h = (h ^ h >>> 16) >>> 0;
|
|
1223
|
-
return h / 4294967296;
|
|
1473
|
+
function elastic(u) {
|
|
1474
|
+
if (u <= 0) return 0;
|
|
1475
|
+
if (u >= 1) return 1;
|
|
1476
|
+
return Math.pow(2, -10 * u) * Math.sin((u * 10 - 0.75) * (2 * Math.PI / 3)) + 1;
|
|
1224
1477
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1478
|
+
var rem = (a, b) => a % b;
|
|
1479
|
+
function localTime(track, t) {
|
|
1480
|
+
const reps = (track.repeats ?? []).filter(repeatIsValid).slice().sort((a, b) => a.start - b.start);
|
|
1481
|
+
if (reps.length === 0) return t;
|
|
1482
|
+
for (const r of reps) {
|
|
1483
|
+
if (!(t >= r.end)) continue;
|
|
1484
|
+
const length = r.end - r.start;
|
|
1485
|
+
if (r.count === null) return r.start + rem(t - r.start, length);
|
|
1486
|
+
const played = length * r.count;
|
|
1487
|
+
if (t < r.start + played) return r.start + rem(t - r.start, length);
|
|
1488
|
+
t -= played - length;
|
|
1489
|
+
}
|
|
1490
|
+
return t;
|
|
1228
1491
|
}
|
|
1229
|
-
function
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1492
|
+
function value(track, t, get, base, lerp) {
|
|
1493
|
+
t = localTime(track, t);
|
|
1494
|
+
let before, after;
|
|
1495
|
+
for (const k of track.keys) {
|
|
1496
|
+
if (get(k) === void 0) continue;
|
|
1497
|
+
if (k.t <= t) before = k;
|
|
1498
|
+
else {
|
|
1499
|
+
after = k;
|
|
1500
|
+
break;
|
|
1236
1501
|
}
|
|
1237
1502
|
}
|
|
1238
|
-
|
|
1503
|
+
if (!before && !after) return base;
|
|
1504
|
+
if (before && !after) return get(before);
|
|
1505
|
+
if (!before && after) return get(after);
|
|
1506
|
+
const b = before, a = after;
|
|
1507
|
+
const u = a.t - b.t > 0 ? (t - b.t) / (a.t - b.t) : 1;
|
|
1508
|
+
return lerp(get(b), get(a), applyEase(b.ease ?? "linear", u));
|
|
1239
1509
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1510
|
+
var lerpNum = (a, b, u) => a + (b - a) * u;
|
|
1511
|
+
var mix01 = (a, b, u) => Math.min(Math.max(a + (b - a) * u, 0), 1);
|
|
1512
|
+
var lerpColor = (a, b, u) => ({
|
|
1513
|
+
r: mix01(a.r, b.r, u),
|
|
1514
|
+
g: mix01(a.g, b.g, u),
|
|
1515
|
+
b: mix01(a.b, b.b, u),
|
|
1516
|
+
a: mix01(a.a, b.a, u)
|
|
1517
|
+
});
|
|
1518
|
+
function num2(track, t, p, base) {
|
|
1519
|
+
return value(track, t, (k) => k[p], base, lerpNum);
|
|
1247
1520
|
}
|
|
1248
|
-
function
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
return inside;
|
|
1521
|
+
function isChanging(track, t) {
|
|
1522
|
+
t = localTime(track, t);
|
|
1523
|
+
const changing = (get, eq) => {
|
|
1524
|
+
let b;
|
|
1525
|
+
for (const k of track.keys) if (k.t <= t && get(k) !== void 0) b = k;
|
|
1526
|
+
const a = track.keys.find((k) => k.t > t && get(k) !== void 0);
|
|
1527
|
+
if (!b || !a) return false;
|
|
1528
|
+
return b.ease !== "hold" && !eq(get(b), get(a));
|
|
1529
|
+
};
|
|
1530
|
+
const n = (p) => changing((k) => k[p], (a, b) => a === b);
|
|
1531
|
+
return n("x") || n("y") || n("width") || n("height") || n("rotation") || n("strokeWidth") || changing((k) => k.stroke, sameColor) || changing((k) => k.fill, sameColor) || n("reveal") || n("opacity");
|
|
1260
1532
|
}
|
|
1261
|
-
function
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1533
|
+
function isVisible(track, t) {
|
|
1534
|
+
t = localTime(track, t);
|
|
1535
|
+
const keyed = track.keys.filter((k) => k.visible !== void 0);
|
|
1536
|
+
if (keyed.length === 0) return true;
|
|
1537
|
+
let last;
|
|
1538
|
+
for (const k of keyed) if (k.t <= t) last = k;
|
|
1539
|
+
return last ? last.visible : !keyed[0].visible;
|
|
1540
|
+
}
|
|
1541
|
+
function applyTrack(track, e, t) {
|
|
1542
|
+
e = cloneElement(e);
|
|
1543
|
+
if (track.keys.some((k) => k.reveal !== void 0)) {
|
|
1544
|
+
const r = num2(track, t, "reveal", e.reveal ?? 1);
|
|
1545
|
+
e.reveal = r >= 1 ? null : Math.max(0, r);
|
|
1272
1546
|
}
|
|
1273
|
-
|
|
1547
|
+
if (track.keys.some((k) => k.opacity !== void 0)) {
|
|
1548
|
+
const o = Math.min(Math.max(num2(track, t, "opacity", e.style.opacity ?? 1), 0), 1);
|
|
1549
|
+
e.style.opacity = o >= 1 ? null : o;
|
|
1550
|
+
}
|
|
1551
|
+
if (e.kind === "connector") return e;
|
|
1552
|
+
e.x = num2(track, t, "x", e.x);
|
|
1553
|
+
e.y = num2(track, t, "y", e.y);
|
|
1554
|
+
e.width = Math.max(0, num2(track, t, "width", e.width));
|
|
1555
|
+
e.height = Math.max(0, num2(track, t, "height", e.height));
|
|
1556
|
+
if (track.keys.some((k) => k.rotation !== void 0)) e.rotation = num2(track, t, "rotation", angle(e));
|
|
1557
|
+
e.style.strokeWidth = Math.max(0, num2(track, t, "strokeWidth", e.style.strokeWidth));
|
|
1558
|
+
e.style.stroke = value(track, t, (k) => k.stroke, e.style.stroke, lerpColor);
|
|
1559
|
+
e.style.fill = value(track, t, (k) => k.fill, e.style.fill, lerpColor);
|
|
1560
|
+
return e;
|
|
1274
1561
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
if (
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
e.width,
|
|
1284
|
-
e.height,
|
|
1285
|
-
e.rotation ?? 0,
|
|
1286
|
-
cellSize(e),
|
|
1287
|
-
JSON.stringify(e.subpaths ?? null),
|
|
1288
|
-
e.style.stroke.r,
|
|
1289
|
-
e.style.stroke.g,
|
|
1290
|
-
e.style.stroke.b,
|
|
1291
|
-
e.style.stroke.a,
|
|
1292
|
-
e.style.roughness,
|
|
1293
|
-
e.style.strokeWidth
|
|
1294
|
-
].join(",")).join(";");
|
|
1295
|
-
const hit = rockCache.get(key);
|
|
1296
|
-
if (hit) return hit;
|
|
1297
|
-
const v = computeRock(floors);
|
|
1298
|
-
if (rockCache.size >= 16) rockCache.clear();
|
|
1299
|
-
rockCache.set(key, v);
|
|
1300
|
-
return v;
|
|
1562
|
+
function followSpan(track, t) {
|
|
1563
|
+
t = localTime(track, t);
|
|
1564
|
+
let f = null;
|
|
1565
|
+
for (const s of track.follow ?? []) if (s.start <= t && (f === null || s.start > f.start)) f = s;
|
|
1566
|
+
if (!f) return null;
|
|
1567
|
+
const span = f;
|
|
1568
|
+
if (track.keys.some((k) => k.t >= span.end && k.t <= t && (k.x !== void 0 || k.y !== void 0))) return null;
|
|
1569
|
+
return f;
|
|
1301
1570
|
}
|
|
1302
|
-
function
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
const box = { x: x0, y: y0, width: (xs.length ? Math.max(...xs) : 0) - x0, height: (ys.length ? Math.max(...ys) : 0) - y0 };
|
|
1309
|
-
return { contours, box, cell: cellSize(e), element: e };
|
|
1310
|
-
});
|
|
1311
|
-
const strokes = floors.map(() => []);
|
|
1312
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1313
|
-
for (const f of floors) {
|
|
1314
|
-
const subSize = f.cell / SUB;
|
|
1315
|
-
const around = inset(f.box, -f.cell, -f.cell);
|
|
1316
|
-
const i0 = Math.floor(around.x / subSize), i1 = Math.ceil((around.x + around.width) / subSize);
|
|
1317
|
-
const j0 = Math.floor(around.y / subSize), j1 = Math.ceil((around.y + around.height) / subSize);
|
|
1318
|
-
const reach = BAND * f.cell;
|
|
1319
|
-
for (let i = i0; i < i1; i++) {
|
|
1320
|
-
for (let j = j0; j < j1; j++) {
|
|
1321
|
-
const k = `${f.cell}|${i}|${j}`;
|
|
1322
|
-
if (seen.has(k)) continue;
|
|
1323
|
-
seen.add(k);
|
|
1324
|
-
const at = spot(i, j, f.cell);
|
|
1325
|
-
let nearest2 = Infinity, owner = -1;
|
|
1326
|
-
for (let n = 0; n < floors.length; n++) {
|
|
1327
|
-
const g = floors[n];
|
|
1328
|
-
if (!containsPoint(inset(g.box, -reach, -reach), at)) continue;
|
|
1329
|
-
if (contains(g.contours, at)) {
|
|
1330
|
-
nearest2 = 0;
|
|
1331
|
-
break;
|
|
1332
|
-
}
|
|
1333
|
-
const d = distance(at, g.contours);
|
|
1334
|
-
if (d < nearest2) {
|
|
1335
|
-
nearest2 = d;
|
|
1336
|
-
owner = n;
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
if (!(nearest2 > 0 && nearest2 <= reach && owner >= 0)) continue;
|
|
1340
|
-
const angle2 = hash01(i, j, 3) * Math.PI;
|
|
1341
|
-
const size = clusterSize(i, j, at, f.cell);
|
|
1342
|
-
const seed = Math.trunc(hash01(i, j, 4) * 4294967296) >>> 0;
|
|
1343
|
-
for (const [a, b] of clusterStrokes(at, angle2, size)) strokes[owner].push([a, b, seed]);
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
const out = [];
|
|
1348
|
-
floors.forEach((f, n) => {
|
|
1349
|
-
if (strokes[n].length === 0) return;
|
|
1350
|
-
const e = f.element;
|
|
1351
|
-
const path = new Path();
|
|
1352
|
-
for (const [a, b, seed] of strokes[n]) {
|
|
1353
|
-
if (e.style.roughness <= 0) path.moveTo(a).lineTo(b);
|
|
1354
|
-
else line(a, b, new SeededRNG(seed), e.style.roughness * 0.35, 0.2, false, path);
|
|
1355
|
-
}
|
|
1356
|
-
out.push({ ink: { ...e.style.stroke, a: e.style.stroke.a * 0.6 }, width: Math.max(0.8, e.style.strokeWidth * 0.45), path });
|
|
1357
|
-
});
|
|
1358
|
-
return out;
|
|
1571
|
+
function tracing(e, page) {
|
|
1572
|
+
if (e.kind === "connector") return new Polyline(routePoints(route(e, indexOf(page))));
|
|
1573
|
+
const clean = cloneElement(e);
|
|
1574
|
+
clean.style.roughness = 0;
|
|
1575
|
+
const pts = outline(clean).contours()[0] ?? [];
|
|
1576
|
+
return new Polyline(angle(e) === 0 ? pts : pts.map((p) => rotated(e, p)));
|
|
1359
1577
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1578
|
+
function following(track, e, t, page) {
|
|
1579
|
+
const f = followSpan(track, t);
|
|
1580
|
+
if (!f) return e;
|
|
1581
|
+
t = localTime(track, t);
|
|
1582
|
+
const pathElement = page.elements.find((x) => x.id === f.path);
|
|
1583
|
+
if (!pathElement || pathElement.id === e.id) return e;
|
|
1584
|
+
const line2 = tracing(pathElement, page);
|
|
1585
|
+
const span = f.end - f.start;
|
|
1586
|
+
let u = Math.min(Math.max(applyEase(f.ease ?? "easeInOut", span > 0 ? Math.min(1, (t - f.start) / span) : 1), 0), 1) * (f.laps ?? 1);
|
|
1587
|
+
if (u > 1) u = rem(u, 1) === 0 ? 1 : rem(u, 1);
|
|
1588
|
+
if (f.reverse === true) u = 1 - u;
|
|
1589
|
+
const p = line2.point(u);
|
|
1590
|
+
const moved = cloneElement(e);
|
|
1591
|
+
moved.x = p.x - e.width / 2;
|
|
1592
|
+
moved.y = p.y - e.height / 2;
|
|
1593
|
+
if (f.orient === true) {
|
|
1594
|
+
const step = f.reverse === true ? -0.01 : 0.01;
|
|
1595
|
+
const a = line2.point(u - step / 2), b = line2.point(u + step / 2);
|
|
1596
|
+
if (a.x !== b.x || a.y !== b.y) moved.rotation = Math.atan2(b.y - a.y, b.x - a.x);
|
|
1597
|
+
}
|
|
1598
|
+
return moved;
|
|
1599
|
+
}
|
|
1600
|
+
function marchOffset(tl, speed, period, t) {
|
|
1601
|
+
if (!(period > 0)) return 0;
|
|
1602
|
+
const d = clampTime(tl.duration);
|
|
1603
|
+
if (d > 0) {
|
|
1604
|
+
const laps = roundHalfAway(speed * d / period);
|
|
1605
|
+
if (laps !== 0) speed = laps * period / d;
|
|
1606
|
+
}
|
|
1607
|
+
const r = rem(speed * t, period);
|
|
1608
|
+
return r < 0 ? -r : r === 0 ? 0 : period - r;
|
|
1609
|
+
}
|
|
1610
|
+
function trackMap(tl) {
|
|
1611
|
+
const m = /* @__PURE__ */ new Map();
|
|
1612
|
+
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
1613
|
+
return m;
|
|
1614
|
+
}
|
|
1615
|
+
function resolved(page, t) {
|
|
1616
|
+
return expandingSymbols(posed(page, t), clampTime(t));
|
|
1617
|
+
}
|
|
1618
|
+
function posed(page, t) {
|
|
1619
|
+
const timeline = page.timeline;
|
|
1620
|
+
if (!timeline) return page;
|
|
1621
|
+
t = clampTime(t);
|
|
1622
|
+
const tracks = trackMap(timeline);
|
|
1623
|
+
const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
|
|
1624
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
1625
|
+
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
1626
|
+
let moving = null;
|
|
1627
|
+
if (timeline.settle === true) {
|
|
1628
|
+
moving = /* @__PURE__ */ new Set();
|
|
1629
|
+
for (const tr of tracks.values()) if (isChanging(tr, t)) moving.add(tr.element);
|
|
1630
|
+
}
|
|
1631
|
+
const boils = (e) => {
|
|
1632
|
+
const override = tracks.get(e.id)?.boil;
|
|
1633
|
+
if (override !== void 0 && override !== null) return override;
|
|
1634
|
+
if (!moving) return true;
|
|
1635
|
+
if (e.connector) return [e.connector.start.elementID, e.connector.end.elementID].some((id) => id !== null && moving.has(id));
|
|
1636
|
+
return moving.has(e.id);
|
|
1637
|
+
};
|
|
1638
|
+
let elements = page.elements.map((e) => {
|
|
1639
|
+
const track = tracks.get(e.id);
|
|
1640
|
+
let out = track ? applyTrack(track, e, t) : e;
|
|
1641
|
+
if (reseed !== null && reseed > 0 && boils(e)) {
|
|
1642
|
+
if (out === e) out = cloneElement(e);
|
|
1643
|
+
out.seed = boiled(e.seed, reseed);
|
|
1644
|
+
}
|
|
1645
|
+
const pattern = track?.march != null ? dashPattern(out.style.dash, out.style.strokeWidth) : null;
|
|
1646
|
+
if (track?.march != null && pattern) {
|
|
1647
|
+
if (out === e) out = cloneElement(e);
|
|
1648
|
+
out.style.dashOffset = marchOffset(timeline, track.march, pattern.reduce((a, b) => a + b, 0), t);
|
|
1649
|
+
}
|
|
1650
|
+
return out;
|
|
1651
|
+
});
|
|
1652
|
+
elements = elements.map((e, i) => hidden.has(e.id) ? page.elements[i] : e);
|
|
1653
|
+
const posed2 = { ...page, elements, timeline: null };
|
|
1654
|
+
elements = elements.map((e) => {
|
|
1655
|
+
const track = tracks.get(e.id);
|
|
1656
|
+
return track ? following(track, e, t, posed2) : e;
|
|
1657
|
+
});
|
|
1658
|
+
const current = { ...page, elements, timeline: null };
|
|
1659
|
+
const index = indexOf(current);
|
|
1660
|
+
const gone = new Set(hidden);
|
|
1661
|
+
elements = elements.map((e) => {
|
|
1662
|
+
const c = e.connector;
|
|
1663
|
+
if (!c || ![c.start.elementID, c.end.elementID].some((id) => id !== null && hidden.has(id))) return e;
|
|
1664
|
+
if (!(tracks.get(e.id)?.keys.some((k) => k.visible !== void 0) ?? false)) {
|
|
1665
|
+
gone.add(e.id);
|
|
1666
|
+
return e;
|
|
1667
|
+
}
|
|
1668
|
+
const [a, b] = endpoints(e, index);
|
|
1669
|
+
const pinned = cloneElement(e);
|
|
1670
|
+
pinned.connector.start = { elementID: null, port: c.start.port, x: a.x, y: a.y };
|
|
1671
|
+
pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
|
|
1672
|
+
return pinned;
|
|
1673
|
+
});
|
|
1674
|
+
return { ...page, elements: elements.filter((e) => !gone.has(e.id)), timeline: null };
|
|
1675
|
+
}
|
|
1676
|
+
function posterTime(page) {
|
|
1677
|
+
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
1678
|
+
}
|
|
1679
|
+
function still(page) {
|
|
1680
|
+
return page.timeline ? resolved(page, posterTime(page)) : expandingSymbols(page, 0);
|
|
1681
|
+
}
|
|
1682
|
+
function cameraRect(tl, t) {
|
|
1683
|
+
const keys = tl.camera ?? [];
|
|
1684
|
+
if (keys.length === 0) return null;
|
|
1685
|
+
let before;
|
|
1686
|
+
for (const k of keys) if (k.t <= t) before = k;
|
|
1687
|
+
const after = keys.find((k) => k.t > t);
|
|
1688
|
+
if (!before) return rectOf(keys[0]);
|
|
1689
|
+
if (!after) return rectOf(before);
|
|
1690
|
+
const u = applyEase(before.ease ?? "easeInOut", (t - before.t) / Math.max(after.t - before.t, 1e-9));
|
|
1691
|
+
const mix3 = (p, q) => p + (q - p) * u;
|
|
1692
|
+
return { x: mix3(before.x, after.x), y: mix3(before.y, after.y), width: Math.max(1, mix3(before.width, after.width)), height: Math.max(1, mix3(before.height, after.height)) };
|
|
1693
|
+
}
|
|
1694
|
+
var rectOf = (k) => ({ x: k.x, y: k.y, width: k.width, height: k.height });
|
|
1695
|
+
function cameraView(tl, t) {
|
|
1696
|
+
const first = tl.camera?.[0];
|
|
1697
|
+
const r = cameraRect(tl, t);
|
|
1698
|
+
if (!first || !r || !(r.width > 0 && r.height > 0)) return null;
|
|
1699
|
+
return fit(r, first.width / first.height);
|
|
1700
|
+
}
|
|
1701
|
+
function fit(r, aspect) {
|
|
1702
|
+
if (!(r.width > 0 && r.height > 0 && aspect > 0)) return r;
|
|
1703
|
+
let w = r.width, h = r.height;
|
|
1704
|
+
if (r.width / r.height < aspect) w = r.height * aspect;
|
|
1705
|
+
else h = r.width / aspect;
|
|
1706
|
+
return { x: r.x + r.width / 2 - w / 2, y: r.y + r.height / 2 - h / 2, width: w, height: h };
|
|
1707
|
+
}
|
|
1708
|
+
function frameCount(tl) {
|
|
1709
|
+
return Math.max(1, Math.ceil(clampTime(tl.duration) * clampFPS(tl.fps)));
|
|
1710
|
+
}
|
|
1711
|
+
function frameTimes(tl) {
|
|
1712
|
+
const fps = clampFPS(tl.fps);
|
|
1713
|
+
const times = [];
|
|
1714
|
+
for (let i = 0; i < frameCount(tl); i++) times.push(i / fps);
|
|
1715
|
+
const last = times[times.length - 1];
|
|
1716
|
+
if (tl.duration - last > 1e-6) times.push(tl.duration);
|
|
1717
|
+
return times;
|
|
1718
|
+
}
|
|
1719
|
+
function sceneAt(tl, t) {
|
|
1720
|
+
let s = -1;
|
|
1721
|
+
(tl.markers ?? []).forEach((m, i) => {
|
|
1722
|
+
if (m.t <= t) s = i;
|
|
1723
|
+
});
|
|
1724
|
+
return s;
|
|
1725
|
+
}
|
|
1726
|
+
function transientElements(page) {
|
|
1727
|
+
const tl = page.timeline;
|
|
1728
|
+
if (!tl) return /* @__PURE__ */ new Set();
|
|
1729
|
+
const end = tl.poster ?? tl.duration;
|
|
1730
|
+
return new Set(tl.tracks.filter((tr) => !isVisible(tr, 0) && !isVisible(tr, end)).map((tr) => tr.element));
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
// src/symbols.ts
|
|
1734
|
+
var MAX_DEPTH = 8;
|
|
1735
|
+
var MAX_ELEMENTS = 2e4;
|
|
1736
|
+
var EPSILON = 1e-9;
|
|
1737
|
+
function sceneTime(s, state) {
|
|
1738
|
+
const m = (s.timeline?.markers ?? []).find((x) => x.name.toLowerCase() === state);
|
|
1739
|
+
return m ? Math.min(Math.max(m.t, 0), clampTime(s.timeline?.duration ?? 0)) : null;
|
|
1740
|
+
}
|
|
1741
|
+
function buttonTime(s, state) {
|
|
1742
|
+
switch (state) {
|
|
1743
|
+
case "up":
|
|
1744
|
+
return sceneTime(s, "up") ?? 0;
|
|
1745
|
+
case "over":
|
|
1746
|
+
return sceneTime(s, "over") ?? buttonTime(s, "up");
|
|
1747
|
+
case "down":
|
|
1748
|
+
return sceneTime(s, "down") ?? buttonTime(s, "over");
|
|
1749
|
+
default:
|
|
1750
|
+
return sceneTime(s, "hit");
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
function symbolTime(s, t, playback, offset2) {
|
|
1754
|
+
const d = clampTime(s.timeline?.duration ?? 0);
|
|
1755
|
+
if (s.button === true) return buttonTime(s, "up") ?? 0;
|
|
1756
|
+
const o = offset2 !== null && Number.isFinite(offset2) ? offset2 : 0;
|
|
1757
|
+
switch (playback ?? "loop") {
|
|
1758
|
+
case "still":
|
|
1759
|
+
return Math.min(s.timeline?.poster ?? d, d);
|
|
1760
|
+
case "click":
|
|
1761
|
+
return Math.min(Math.max(o, 0), d);
|
|
1762
|
+
case "once":
|
|
1763
|
+
return Math.min(Math.max(t + o, 0), d);
|
|
1764
|
+
default: {
|
|
1765
|
+
if (d <= 0) return 0;
|
|
1766
|
+
const x = t + o;
|
|
1767
|
+
let r = x % d;
|
|
1768
|
+
if (r < 0) r += d;
|
|
1769
|
+
if (x > EPSILON && (r < EPSILON || d - r < EPSILON)) return d;
|
|
1770
|
+
return r;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
function filled(s, args) {
|
|
1775
|
+
const params = s.params;
|
|
1776
|
+
if (!params || Object.keys(params).length === 0) return s;
|
|
1777
|
+
const values = new Map(Object.entries(params));
|
|
1778
|
+
for (const [k, v] of Object.entries(args ?? {})) if (values.has(k)) values.set(k, v);
|
|
1779
|
+
const fill = (x) => !x.includes("{{") ? x : x.replace(/\{\{\s*([^{}]*?)\s*\}\}/g, (m, name) => values.get(name) ?? m);
|
|
1780
|
+
return { ...s, elements: s.elements.map((x) => {
|
|
1781
|
+
if (x.text === null && x.args === null) return x;
|
|
1782
|
+
const e = cloneElement(x);
|
|
1783
|
+
if (e.text !== null) e.text = fill(e.text);
|
|
1784
|
+
if (e.args !== null) e.args = Object.fromEntries(Object.entries(e.args).map(([k, v]) => [k, fill(v)]));
|
|
1785
|
+
return e;
|
|
1786
|
+
}) };
|
|
1787
|
+
}
|
|
1788
|
+
function symbolPage(s) {
|
|
1789
|
+
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null };
|
|
1790
|
+
}
|
|
1791
|
+
function hasInstances(page) {
|
|
1792
|
+
return page.elements.some((e) => e.kind === "symbol");
|
|
1793
|
+
}
|
|
1794
|
+
function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map()) {
|
|
1795
|
+
if (!hasInstances(page)) return page;
|
|
1796
|
+
const budget = { left: MAX_ELEMENTS };
|
|
1797
|
+
return { ...page, elements: expand(page.elements, page, t, symbolTable(page), [], posing, budget, times) };
|
|
1798
|
+
}
|
|
1799
|
+
function symbolTable(page) {
|
|
1800
|
+
const table = /* @__PURE__ */ new Map();
|
|
1801
|
+
for (const s of page.symbols ?? []) if (!table.has(s.id)) table.set(s.id, s);
|
|
1802
|
+
return table;
|
|
1803
|
+
}
|
|
1804
|
+
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map()) {
|
|
1805
|
+
const instances = new Set(elements.filter((e) => e.kind === "symbol").map((e) => e.id));
|
|
1806
|
+
if (instances.size === 0) {
|
|
1807
|
+
budget.left -= elements.length;
|
|
1808
|
+
return elements;
|
|
1809
|
+
}
|
|
1810
|
+
const index = indexOf(page);
|
|
1811
|
+
const out = [];
|
|
1812
|
+
for (const e of elements) {
|
|
1813
|
+
if (budget.left <= 0) break;
|
|
1814
|
+
const c = e.connector;
|
|
1815
|
+
if (e.kind === "connector" && c && [c.start.elementID, c.end.elementID].some((id2) => id2 !== null && instances.has(id2))) {
|
|
1816
|
+
const [a, b] = endpoints(e, index);
|
|
1817
|
+
const pinned = cloneElement(e);
|
|
1818
|
+
if (c.start.elementID !== null && instances.has(c.start.elementID)) pinned.connector.start = { elementID: null, port: c.start.port, x: a.x, y: a.y };
|
|
1819
|
+
if (c.end.elementID !== null && instances.has(c.end.elementID)) pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
|
|
1820
|
+
out.push(pinned);
|
|
1821
|
+
budget.left -= 1;
|
|
1822
|
+
continue;
|
|
1823
|
+
}
|
|
1824
|
+
if (e.kind !== "symbol") {
|
|
1825
|
+
out.push(e);
|
|
1826
|
+
budget.left -= 1;
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
budget.left -= 1;
|
|
1830
|
+
const id = e.symbol;
|
|
1831
|
+
const symbol = id === null ? void 0 : table.get(id);
|
|
1832
|
+
if (id === null || !symbol || stack.includes(id) || stack.length >= MAX_DEPTH) continue;
|
|
1833
|
+
const override = stack.length === 0 ? times.get(e.id) : void 0;
|
|
1834
|
+
const u = override !== void 0 ? Math.min(Math.max(override, 0), clampTime(symbol.timeline?.duration ?? 0)) : symbolTime(symbol, t, e.playback, e.offset);
|
|
1835
|
+
const page2 = symbolPage(filled(symbol, e.args));
|
|
1836
|
+
const inner = posing ? posed(page2, u) : page2;
|
|
1837
|
+
const visible = new Set(inner.layers.filter((l) => l.visible).map((l) => l.id));
|
|
1838
|
+
const drawn = {
|
|
1839
|
+
...inner,
|
|
1840
|
+
elements: orderedElements(inner).filter((x) => visible.has(x.layerID)),
|
|
1841
|
+
layers: [{ id: e.layerID, name: "", visible: true, locked: false }]
|
|
1842
|
+
};
|
|
1843
|
+
for (const x of expand(drawn.elements, drawn, u, table, [...stack, id], posing, budget)) out.push(placed(x, e, symbol));
|
|
1844
|
+
}
|
|
1845
|
+
return out;
|
|
1846
|
+
}
|
|
1847
|
+
function placed(inner, instance, symbol) {
|
|
1848
|
+
const f = { x: instance.x, y: instance.y, width: instance.width, height: instance.height };
|
|
1849
|
+
const sx = f.width / symbol.width, sy = f.height / symbol.height;
|
|
1850
|
+
const s = Math.sqrt(Math.abs(sx * sy));
|
|
1851
|
+
const center2 = { x: f.x + f.width / 2, y: f.y + f.height / 2 };
|
|
1852
|
+
const a = angle(instance);
|
|
1853
|
+
const point = (p) => rotate({ x: f.x + p.x * sx, y: f.y + p.y * sy }, center2, a);
|
|
1854
|
+
const e = cloneElement(inner);
|
|
1855
|
+
e.id = derivedID(instance.id, inner.id);
|
|
1856
|
+
e.layerID = instance.layerID;
|
|
1857
|
+
e.groupID = null;
|
|
1858
|
+
e.link = inner.link === null ? null : derivedID(instance.id, inner.link);
|
|
1859
|
+
e.seed = boiled(inner.seed, instance.seed);
|
|
1860
|
+
e.click = null;
|
|
1861
|
+
e.hover = null;
|
|
1862
|
+
const scaled = { x: f.x + inner.x * sx, y: f.y + inner.y * sy, width: inner.width * sx, height: inner.height * sy };
|
|
1863
|
+
const moved = rotate({ x: scaled.x + scaled.width / 2, y: scaled.y + scaled.height / 2 }, center2, a);
|
|
1864
|
+
e.x = moved.x - scaled.width / 2;
|
|
1865
|
+
e.y = moved.y - scaled.height / 2;
|
|
1866
|
+
e.width = scaled.width;
|
|
1867
|
+
e.height = scaled.height;
|
|
1868
|
+
if (a !== 0 || inner.rotation !== null) e.rotation = angle(inner) + a;
|
|
1869
|
+
e.style.strokeWidth = inner.style.strokeWidth * s;
|
|
1870
|
+
e.style.fontSize = inner.style.fontSize * s;
|
|
1871
|
+
if (inner.style.gridSize !== null) e.style.gridSize = inner.style.gridSize * s;
|
|
1872
|
+
if (instance.style.opacity !== null) e.style.opacity = (inner.style.opacity ?? 1) * instance.style.opacity;
|
|
1873
|
+
if (inner.connector) {
|
|
1874
|
+
const end = (p) => {
|
|
1875
|
+
if (p.elementID !== null) return { elementID: derivedID(instance.id, p.elementID), port: p.port, x: p.x, y: p.y };
|
|
1876
|
+
const q = point(p);
|
|
1877
|
+
return { elementID: null, port: p.port, x: q.x, y: q.y };
|
|
1878
|
+
};
|
|
1879
|
+
e.connector = { ...inner.connector, start: end(inner.connector.start), end: end(inner.connector.end) };
|
|
1880
|
+
}
|
|
1881
|
+
return e;
|
|
1882
|
+
}
|
|
1883
|
+
var MASK = (1n << 64n) - 1n;
|
|
1884
|
+
function mix2(x) {
|
|
1885
|
+
let z = x + 0x9e3779b97f4a7c15n & MASK;
|
|
1886
|
+
z = (z ^ z >> 30n) * 0xbf58476d1ce4e5b9n & MASK;
|
|
1887
|
+
z = (z ^ z >> 27n) * 0x94d049bb133111ebn & MASK;
|
|
1888
|
+
return z ^ z >> 31n;
|
|
1889
|
+
}
|
|
1890
|
+
function halves(id) {
|
|
1891
|
+
const hex = id.replace(/-/g, "").padStart(32, "0").slice(-32);
|
|
1892
|
+
return [BigInt(`0x${hex.slice(0, 16)}`), BigInt(`0x${hex.slice(16)}`)];
|
|
1893
|
+
}
|
|
1894
|
+
function derivedID(instance, inner) {
|
|
1895
|
+
const [a, b] = halves(instance), [c, d] = halves(inner);
|
|
1896
|
+
const hi = mix2(mix2(mix2(mix2(a) ^ b) ^ c) ^ d);
|
|
1897
|
+
const lo = mix2(hi ^ 0x6a09e667f3bcc909n);
|
|
1898
|
+
const h = hi.toString(16).padStart(16, "0") + lo.toString(16).padStart(16, "0");
|
|
1899
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`.toUpperCase();
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
// src/clicks.ts
|
|
1903
|
+
function sceneNamed(name, markers) {
|
|
1904
|
+
const exact = markers.findIndex((m) => m.name === name);
|
|
1905
|
+
return exact >= 0 ? exact : markers.findIndex((m) => m.name.toLowerCase() === name.toLowerCase());
|
|
1906
|
+
}
|
|
1907
|
+
function clickTargets(page, file) {
|
|
1908
|
+
const markers = page.timeline?.markers ?? [];
|
|
1909
|
+
const out = /* @__PURE__ */ new Map();
|
|
1910
|
+
let pageOf = null;
|
|
1911
|
+
for (const e of page.elements) {
|
|
1912
|
+
const c = e.click;
|
|
1913
|
+
if (c?.scene) {
|
|
1914
|
+
const i = sceneNamed(c.scene, markers);
|
|
1915
|
+
if (i >= 0) {
|
|
1916
|
+
out.set(e.id, { scene: i });
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
const url = c?.url ? allowedURL(c.url) : null;
|
|
1921
|
+
if (url) {
|
|
1922
|
+
out.set(e.id, { url });
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
if (e.link && file && file.pages.length > 1) {
|
|
1926
|
+
if (!pageOf) {
|
|
1927
|
+
pageOf = /* @__PURE__ */ new Map();
|
|
1928
|
+
file.pages.forEach((p, i) => {
|
|
1929
|
+
for (const x of p.elements) if (!pageOf.has(x.id)) pageOf.set(x.id, i);
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
const target = pageOf.get(e.link);
|
|
1933
|
+
if (target !== void 0 && file.pages[target] !== page) out.set(e.id, { page: target });
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
return out;
|
|
1937
|
+
}
|
|
1938
|
+
function hoveredElement(e) {
|
|
1939
|
+
const h = e.hover;
|
|
1940
|
+
if (!h) return e;
|
|
1941
|
+
const style = { ...e.style };
|
|
1942
|
+
if (h.stroke) style.stroke = h.stroke;
|
|
1943
|
+
if (h.fill) style.fill = h.fill;
|
|
1944
|
+
if (h.opacity !== null && Number.isFinite(h.opacity)) {
|
|
1945
|
+
const o = Math.min(Math.max(h.opacity, 0), 1);
|
|
1946
|
+
style.opacity = o >= 1 ? null : o;
|
|
1947
|
+
}
|
|
1948
|
+
return { ...e, style };
|
|
1949
|
+
}
|
|
1950
|
+
function hovered(page, ids) {
|
|
1951
|
+
return { ...page, elements: page.elements.map((e) => ids.has(e.id) ? hoveredElement(e) : e) };
|
|
1952
|
+
}
|
|
1953
|
+
function hasHover(e) {
|
|
1954
|
+
return !!e.hover && (e.hover.stroke !== null || e.hover.fill !== null || e.hover.opacity !== null);
|
|
1955
|
+
}
|
|
1956
|
+
function buttonLabel(e, symbolName) {
|
|
1957
|
+
const line2 = (e.text ?? "").split("\n").find((l) => l.length > 0);
|
|
1958
|
+
if (line2) return line2;
|
|
1959
|
+
if (e.anchor) return e.anchor;
|
|
1960
|
+
if (symbolName) return symbolName;
|
|
1961
|
+
return e.kind;
|
|
1962
|
+
}
|
|
1963
|
+
function partsOf(page, instances) {
|
|
1964
|
+
const out = /* @__PURE__ */ new Map();
|
|
1965
|
+
for (const e of page.elements) {
|
|
1966
|
+
if (!instances.has(e.id) || e.kind !== "symbol") continue;
|
|
1967
|
+
const alone = { ...page, layers: [{ id: e.layerID, name: "", visible: true, locked: false }], elements: [e] };
|
|
1968
|
+
for (const part of expandingSymbols(alone, 0, false).elements) out.set(part.id, e.id);
|
|
1969
|
+
}
|
|
1970
|
+
return out;
|
|
1971
|
+
}
|
|
1972
|
+
var CLICK_STYLE = `
|
|
1973
|
+
.dottie-a { cursor: pointer; outline: none; }
|
|
1974
|
+
.dottie-a .dottie-h { display: none; }
|
|
1975
|
+
.dottie-a:hover .dottie-h, .dottie-a.dottie-hover .dottie-h, .dottie-a:focus-visible .dottie-h { display: inline; }
|
|
1976
|
+
.dottie-a:hover .dottie-n, .dottie-a.dottie-hover .dottie-n, .dottie-a:focus-visible .dottie-n { display: none; }
|
|
1977
|
+
.dottie-a:focus-visible { filter: drop-shadow(0 0 3px var(--dottie-focus, #3b82f6)); }
|
|
1978
|
+
.dottie-a:not(.dottie-b):active, .dottie-a.dottie-press:not(.dottie-b) { transform: translate(1px, 1px); }
|
|
1979
|
+
.dottie-a .dottie-d { display: none; }
|
|
1980
|
+
.dottie-b:active .dottie-n, .dottie-b:active .dottie-h, .dottie-b.dottie-press .dottie-n, .dottie-b.dottie-press .dottie-h { display: none; }
|
|
1981
|
+
.dottie-b:active .dottie-d, .dottie-b.dottie-press .dottie-d { display: inline; }
|
|
1982
|
+
`;
|
|
1983
|
+
|
|
1984
|
+
// src/metrics.ts
|
|
1985
|
+
var ascent = 0.951995849609375;
|
|
1986
|
+
var descent = 0.2129974365234375;
|
|
1987
|
+
var leading = 0.0279998779296875;
|
|
1988
|
+
var boldAscent = 0.975006103515625;
|
|
1989
|
+
var boldDescent = 0.2169952392578125;
|
|
1367
1990
|
var boldLeading = 0.0290069580078125;
|
|
1368
1991
|
var italicAscent = 0.957000732421875;
|
|
1369
1992
|
var italicDescent = 0.2129974365234375;
|
|
@@ -1883,476 +2506,177 @@ function measureRun(r) {
|
|
|
1883
2506
|
return w;
|
|
1884
2507
|
}
|
|
1885
2508
|
var TAB_INTERVAL = 28;
|
|
1886
|
-
function layoutLines(paras, width, height, fontSize) {
|
|
1887
|
-
const lines = [];
|
|
1888
|
-
let baseline = 0;
|
|
1889
|
-
let prev = null;
|
|
1890
|
-
paras.forEach((p, pi) => {
|
|
1891
|
-
const runs = pi < paras.length - 1 ? [...p.runs, { text: "\n", size: fontSize, bold: false, italic: false, mono: false, strike: false, underline: false, alpha: 1 }] : p.runs;
|
|
1892
|
-
const chars = [];
|
|
1893
|
-
runs.forEach((r, ri) => {
|
|
1894
|
-
for (const ch of r.text) chars.push({ ch, run: ri });
|
|
1895
|
-
});
|
|
1896
|
-
let k = 0;
|
|
1897
|
-
let firstLine = true;
|
|
1898
|
-
while (k < chars.length || firstLine && chars.length === 0) {
|
|
1899
|
-
const start = firstLine ? p.firstIndent : p.indent;
|
|
1900
|
-
const maxX2 = width;
|
|
1901
|
-
const glyphs = [];
|
|
1902
|
-
let x = start, lastSpace = -1, end = k;
|
|
1903
|
-
for (; end < chars.length; end++) {
|
|
1904
|
-
const { ch, run } = chars[end];
|
|
1905
|
-
const r = runs[run];
|
|
1906
|
-
if (ch === SEP || ch === "\n") {
|
|
1907
|
-
glyphs.push({ ch, run, x, w: 0 });
|
|
1908
|
-
end++;
|
|
1909
|
-
break;
|
|
1910
|
-
}
|
|
1911
|
-
if (ch === " ") {
|
|
1912
|
-
const stop = p.tabs.find((t) => t.x > x + 0.01);
|
|
1913
|
-
let tx;
|
|
1914
|
-
if (stop) {
|
|
1915
|
-
if (stop.align === "left") tx = stop.x;
|
|
1916
|
-
else {
|
|
1917
|
-
let seg = 0, prevCh = "";
|
|
1918
|
-
for (let q = end + 1; q < chars.length && chars[q].ch !== " " && chars[q].ch !== "\n" && chars[q].ch !== SEP; q++) {
|
|
1919
|
-
const rr = runs[chars[q].run];
|
|
1920
|
-
seg += charWidth(chars[q].ch, rr) + (prevCh ? kern(prevCh, chars[q].ch, rr) : 0);
|
|
1921
|
-
prevCh = chars[q].ch;
|
|
1922
|
-
}
|
|
1923
|
-
tx = Math.max(x, stop.align === "right" ? stop.x - seg : stop.x - seg / 2);
|
|
1924
|
-
}
|
|
1925
|
-
} else tx = (Math.floor(x / TAB_INTERVAL) + 1) * TAB_INTERVAL;
|
|
1926
|
-
glyphs.push({ ch, run, x, w: tx - x });
|
|
1927
|
-
x = tx;
|
|
1928
|
-
continue;
|
|
1929
|
-
}
|
|
1930
|
-
const prevGlyph = glyphs[glyphs.length - 1];
|
|
1931
|
-
const kx = prevGlyph && prevGlyph.run === run && prevGlyph.ch !== " " ? kern(prevGlyph.ch, ch, r) : 0;
|
|
1932
|
-
if (prevGlyph) {
|
|
1933
|
-
prevGlyph.w += kx;
|
|
1934
|
-
x += kx;
|
|
1935
|
-
}
|
|
1936
|
-
const w = charWidth(ch, r);
|
|
1937
|
-
if (ch !== " " && x + w > maxX2 + 0.01 && glyphs.some((g) => g.ch !== " ")) {
|
|
1938
|
-
if (lastSpace >= 0) {
|
|
1939
|
-
glyphs.length = lastSpace + 1;
|
|
1940
|
-
end = k + lastSpace + 1;
|
|
1941
|
-
}
|
|
1942
|
-
if (prevGlyph) prevGlyph.w -= kx;
|
|
1943
|
-
break;
|
|
1944
|
-
}
|
|
1945
|
-
glyphs.push({ ch, run, x, w });
|
|
1946
|
-
x += w;
|
|
1947
|
-
if (breaksAfter(ch, chars[end + 1]?.ch ?? "")) lastSpace = glyphs.length - 1;
|
|
1948
|
-
}
|
|
1949
|
-
if (glyphs.length === 0 && end === k && k < chars.length) {
|
|
1950
|
-
const { ch, run } = chars[k];
|
|
1951
|
-
glyphs.push({ ch, run, x: start, w: charWidth(ch, runs[run]) });
|
|
1952
|
-
end = k + 1;
|
|
1953
|
-
}
|
|
1954
|
-
let asc = 0, desc = 0, lead = 0;
|
|
1955
|
-
for (const g of glyphs) {
|
|
1956
|
-
const [a, d, l] = metrics(g.ch, runs[g.run]);
|
|
1957
|
-
asc = Math.max(asc, a);
|
|
1958
|
-
desc = Math.max(desc, d);
|
|
1959
|
-
lead = Math.max(lead, l);
|
|
1960
|
-
}
|
|
1961
|
-
if (glyphs.length === 0) [asc, desc, lead] = metrics(" ", runs[0] ?? { text: " ", size: fontSize, bold: false, italic: false, mono: false, strike: false, underline: false, alpha: 1 });
|
|
1962
|
-
baseline = prev ? baseline + Math.round(prev.desc) + Math.round(prev.lead) + (firstLine ? p.spacing : 0) + Math.round(asc) : Math.round(asc);
|
|
1963
|
-
if (baseline + desc > height + 0.5) return;
|
|
1964
|
-
const line2 = { glyphs, asc, desc, lead, baseline, para: p, first: firstLine };
|
|
1965
|
-
line2.glyphs.forEach((g) => {
|
|
1966
|
-
g.r = runs[g.run];
|
|
1967
|
-
});
|
|
1968
|
-
lines.push(line2);
|
|
1969
|
-
prev = line2;
|
|
1970
|
-
firstLine = false;
|
|
1971
|
-
k = end;
|
|
1972
|
-
if (chars.length === 0) break;
|
|
1973
|
-
}
|
|
1974
|
-
});
|
|
1975
|
-
return lines;
|
|
1976
|
-
}
|
|
1977
|
-
function noteMarkup(source, e) {
|
|
1978
|
-
const rect2 = textRect({ x: e.x, y: e.y, width: e.width, height: e.height });
|
|
1979
|
-
if (!(rect2.width > 1 && rect2.height > 1)) return "";
|
|
1980
|
-
const b = new Builder(e.style.fontSize, rect2.width);
|
|
1981
|
-
for (const block of parseBlocks(source)) b.block(block, { indent: 0, marker: null, quote: null, listDepth: 0 });
|
|
1982
|
-
const lines = layoutLines(b.paras, rect2.width, rect2.height, e.style.fontSize);
|
|
1983
|
-
const ink = color(e.style.stroke);
|
|
1984
|
-
let s = "";
|
|
1985
|
-
const marks = [];
|
|
1986
|
-
for (const l of lines) {
|
|
1987
|
-
const p = l.para;
|
|
1988
|
-
if (p.quote !== null) marks.push([rect2.x + p.quote, rect2.y + l.baseline - l.asc, rect2.x + p.quote, rect2.y + l.baseline + l.desc]);
|
|
1989
|
-
if (p.rule !== null) marks.push([rect2.x + p.rule, rect2.y + l.baseline - 3, rect2.x + rect2.width, rect2.y + l.baseline - 3]);
|
|
1990
|
-
if (p.tableRule) {
|
|
1991
|
-
const y = rect2.y + l.baseline + l.desc + 2;
|
|
1992
|
-
marks.push([rect2.x + p.tableRule[0], y, Math.min(rect2.x + rect2.width, rect2.x + p.tableRule[0] + p.tableRule[1]), y]);
|
|
1993
|
-
}
|
|
1994
|
-
let spans = "";
|
|
1995
|
-
let i = 0;
|
|
1996
|
-
const gs = l.glyphs;
|
|
1997
|
-
while (i < gs.length) {
|
|
1998
|
-
const r = gs[i].r;
|
|
1999
|
-
let j = i;
|
|
2000
|
-
let text = "";
|
|
2001
|
-
const font = fontOf(gs[i].ch);
|
|
2002
|
-
while (j < gs.length && sameStyle(gs[j].r, r) && gs[j].ch !== " " && fontOf(gs[j].ch) === font) {
|
|
2003
|
-
text += gs[j].ch;
|
|
2004
|
-
j++;
|
|
2005
|
-
}
|
|
2006
|
-
const shown = text.replaceAll("\n", "").replaceAll(SEP, "");
|
|
2007
|
-
if (shown.trim() !== "") {
|
|
2008
|
-
let a = `font-size="${fmt(r.size)}"`;
|
|
2009
|
-
if (r.mono) a += ` font-family="Menlo, Consolas, monospace"`;
|
|
2010
|
-
if (r.bold) a += ` font-weight="bold"`;
|
|
2011
|
-
if (r.italic) a += ` font-style="italic"`;
|
|
2012
|
-
if (r.underline) a += ` text-decoration="underline"`;
|
|
2013
|
-
if (r.alpha < 1) a += ` fill-opacity="${fmt(r.alpha * e.style.stroke.a)}"`;
|
|
2014
|
-
spans += `<tspan x="${fmt(rect2.x + gs[i].x)}" ${a}>${escapeXML(shown)}</tspan>`;
|
|
2015
|
-
}
|
|
2016
|
-
if (r.strike && j > i) {
|
|
2017
|
-
const y = l.baseline - metrics(gs[i].ch, r)[0] * 0.32;
|
|
2018
|
-
marks.push([rect2.x + gs[i].x, rect2.y + y, rect2.x + gs[j - 1].x + gs[j - 1].w, rect2.y + y]);
|
|
2019
|
-
}
|
|
2020
|
-
if (j === i) j++;
|
|
2021
|
-
i = j;
|
|
2022
|
-
}
|
|
2023
|
-
if (spans) s += `<text y="${fmt(rect2.y + l.baseline)}" font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="${ink}" xml:space="preserve">${spans}</text>
|
|
2024
|
-
`;
|
|
2025
|
-
}
|
|
2026
|
-
const width = fmt(Math.max(1, e.style.fontSize / 14));
|
|
2027
|
-
for (const [x1, y1, x2, y2] of marks) {
|
|
2028
|
-
s += `<line x1="${fmt(x1)}" y1="${fmt(y1)}" x2="${fmt(x2)}" y2="${fmt(y2)}" stroke="${ink}" stroke-width="${width}"/>
|
|
2029
|
-
`;
|
|
2030
|
-
}
|
|
2031
|
-
return s;
|
|
2032
|
-
}
|
|
2033
|
-
function fontOf(ch) {
|
|
2034
|
-
return fallbackMetrics[ch] ? `fb:${ch}` : "";
|
|
2035
|
-
}
|
|
2036
|
-
function sameStyle(a, b) {
|
|
2037
|
-
return a === b || a.size === b.size && a.bold === b.bold && a.italic === b.italic && a.mono === b.mono && a.strike === b.strike && a.underline === b.underline && a.alpha === b.alpha;
|
|
2038
|
-
}
|
|
2039
|
-
|
|
2040
|
-
// src/theme.ts
|
|
2041
|
-
var rgb = (r, g, b) => ({ r, g, b, a: 1 });
|
|
2042
|
-
var themes = [
|
|
2043
|
-
{ id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13) },
|
|
2044
|
-
{ id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13) },
|
|
2045
|
-
{ id: "sepia", name: "Sepia", background: rgb(0.953, 0.914, 0.824), ink: rgb(0.23, 0.18, 0.13) },
|
|
2046
|
-
{ id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984) },
|
|
2047
|
-
{ id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918) },
|
|
2048
|
-
{ id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94) }
|
|
2049
|
-
];
|
|
2050
|
-
function themeNamed(id) {
|
|
2051
|
-
return themes.find((t) => t.id === id) ?? null;
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
// src/timeline.ts
|
|
2055
|
-
function applyEase(ease, u) {
|
|
2056
|
-
switch (ease) {
|
|
2057
|
-
case "linear":
|
|
2058
|
-
return u;
|
|
2059
|
-
case "easeIn":
|
|
2060
|
-
return u * u * u;
|
|
2061
|
-
case "easeOut":
|
|
2062
|
-
return 1 - Math.pow(1 - u, 3);
|
|
2063
|
-
case "easeInOut":
|
|
2064
|
-
return u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
|
|
2065
|
-
case "hold":
|
|
2066
|
-
return 0;
|
|
2067
|
-
case "spring":
|
|
2068
|
-
return spring(u);
|
|
2069
|
-
case "bounce":
|
|
2070
|
-
return bounce(u);
|
|
2071
|
-
case "elastic":
|
|
2072
|
-
return elastic(u);
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
function spring(u) {
|
|
2076
|
-
if (u <= 0) return 0;
|
|
2077
|
-
if (u >= 1) return 1;
|
|
2078
|
-
const s = (u2) => 1 - Math.exp(-6 * u2) * (Math.cos(3 * Math.PI * u2) + 6 / (3 * Math.PI) * Math.sin(3 * Math.PI * u2));
|
|
2079
|
-
return s(u) + u * (1 - s(1));
|
|
2080
|
-
}
|
|
2081
|
-
function bounce(u) {
|
|
2082
|
-
if (u <= 0) return 0;
|
|
2083
|
-
if (u >= 1) return 1;
|
|
2084
|
-
const n = 7.5625, d = 2.75;
|
|
2085
|
-
if (u < 1 / d) return n * u * u;
|
|
2086
|
-
if (u < 2 / d) {
|
|
2087
|
-
const v2 = u - 1.5 / d;
|
|
2088
|
-
return n * v2 * v2 + 0.75;
|
|
2089
|
-
}
|
|
2090
|
-
if (u < 2.5 / d) {
|
|
2091
|
-
const v2 = u - 2.25 / d;
|
|
2092
|
-
return n * v2 * v2 + 0.9375;
|
|
2093
|
-
}
|
|
2094
|
-
const v = u - 2.625 / d;
|
|
2095
|
-
return n * v * v + 0.984375;
|
|
2096
|
-
}
|
|
2097
|
-
function elastic(u) {
|
|
2098
|
-
if (u <= 0) return 0;
|
|
2099
|
-
if (u >= 1) return 1;
|
|
2100
|
-
return Math.pow(2, -10 * u) * Math.sin((u * 10 - 0.75) * (2 * Math.PI / 3)) + 1;
|
|
2101
|
-
}
|
|
2102
|
-
var rem = (a, b) => a % b;
|
|
2103
|
-
function localTime(track, t) {
|
|
2104
|
-
const reps = (track.repeats ?? []).filter(repeatIsValid).slice().sort((a, b) => a.start - b.start);
|
|
2105
|
-
if (reps.length === 0) return t;
|
|
2106
|
-
for (const r of reps) {
|
|
2107
|
-
if (!(t >= r.end)) continue;
|
|
2108
|
-
const length = r.end - r.start;
|
|
2109
|
-
if (r.count === null) return r.start + rem(t - r.start, length);
|
|
2110
|
-
const played = length * r.count;
|
|
2111
|
-
if (t < r.start + played) return r.start + rem(t - r.start, length);
|
|
2112
|
-
t -= played - length;
|
|
2113
|
-
}
|
|
2114
|
-
return t;
|
|
2115
|
-
}
|
|
2116
|
-
function value(track, t, get, base, lerp) {
|
|
2117
|
-
t = localTime(track, t);
|
|
2118
|
-
let before, after;
|
|
2119
|
-
for (const k of track.keys) {
|
|
2120
|
-
if (get(k) === void 0) continue;
|
|
2121
|
-
if (k.t <= t) before = k;
|
|
2122
|
-
else {
|
|
2123
|
-
after = k;
|
|
2124
|
-
break;
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
if (!before && !after) return base;
|
|
2128
|
-
if (before && !after) return get(before);
|
|
2129
|
-
if (!before && after) return get(after);
|
|
2130
|
-
const b = before, a = after;
|
|
2131
|
-
const u = a.t - b.t > 0 ? (t - b.t) / (a.t - b.t) : 1;
|
|
2132
|
-
return lerp(get(b), get(a), applyEase(b.ease ?? "linear", u));
|
|
2133
|
-
}
|
|
2134
|
-
var lerpNum = (a, b, u) => a + (b - a) * u;
|
|
2135
|
-
var mix01 = (a, b, u) => Math.min(Math.max(a + (b - a) * u, 0), 1);
|
|
2136
|
-
var lerpColor = (a, b, u) => ({
|
|
2137
|
-
r: mix01(a.r, b.r, u),
|
|
2138
|
-
g: mix01(a.g, b.g, u),
|
|
2139
|
-
b: mix01(a.b, b.b, u),
|
|
2140
|
-
a: mix01(a.a, b.a, u)
|
|
2141
|
-
});
|
|
2142
|
-
function num2(track, t, p, base) {
|
|
2143
|
-
return value(track, t, (k) => k[p], base, lerpNum);
|
|
2144
|
-
}
|
|
2145
|
-
function isChanging(track, t) {
|
|
2146
|
-
t = localTime(track, t);
|
|
2147
|
-
const changing = (get, eq) => {
|
|
2148
|
-
let b;
|
|
2149
|
-
for (const k of track.keys) if (k.t <= t && get(k) !== void 0) b = k;
|
|
2150
|
-
const a = track.keys.find((k) => k.t > t && get(k) !== void 0);
|
|
2151
|
-
if (!b || !a) return false;
|
|
2152
|
-
return b.ease !== "hold" && !eq(get(b), get(a));
|
|
2153
|
-
};
|
|
2154
|
-
const n = (p) => changing((k) => k[p], (a, b) => a === b);
|
|
2155
|
-
return n("x") || n("y") || n("width") || n("height") || n("rotation") || n("strokeWidth") || changing((k) => k.stroke, sameColor) || changing((k) => k.fill, sameColor) || n("reveal") || n("opacity");
|
|
2156
|
-
}
|
|
2157
|
-
function isVisible(track, t) {
|
|
2158
|
-
t = localTime(track, t);
|
|
2159
|
-
const keyed = track.keys.filter((k) => k.visible !== void 0);
|
|
2160
|
-
if (keyed.length === 0) return true;
|
|
2161
|
-
let last;
|
|
2162
|
-
for (const k of keyed) if (k.t <= t) last = k;
|
|
2163
|
-
return last ? last.visible : !keyed[0].visible;
|
|
2164
|
-
}
|
|
2165
|
-
function applyTrack(track, e, t) {
|
|
2166
|
-
e = cloneElement(e);
|
|
2167
|
-
if (track.keys.some((k) => k.reveal !== void 0)) {
|
|
2168
|
-
const r = num2(track, t, "reveal", e.reveal ?? 1);
|
|
2169
|
-
e.reveal = r >= 1 ? null : Math.max(0, r);
|
|
2170
|
-
}
|
|
2171
|
-
if (track.keys.some((k) => k.opacity !== void 0)) {
|
|
2172
|
-
const o = Math.min(Math.max(num2(track, t, "opacity", e.style.opacity ?? 1), 0), 1);
|
|
2173
|
-
e.style.opacity = o >= 1 ? null : o;
|
|
2174
|
-
}
|
|
2175
|
-
if (e.kind === "connector") return e;
|
|
2176
|
-
e.x = num2(track, t, "x", e.x);
|
|
2177
|
-
e.y = num2(track, t, "y", e.y);
|
|
2178
|
-
e.width = Math.max(0, num2(track, t, "width", e.width));
|
|
2179
|
-
e.height = Math.max(0, num2(track, t, "height", e.height));
|
|
2180
|
-
if (track.keys.some((k) => k.rotation !== void 0)) e.rotation = num2(track, t, "rotation", angle(e));
|
|
2181
|
-
e.style.strokeWidth = Math.max(0, num2(track, t, "strokeWidth", e.style.strokeWidth));
|
|
2182
|
-
e.style.stroke = value(track, t, (k) => k.stroke, e.style.stroke, lerpColor);
|
|
2183
|
-
e.style.fill = value(track, t, (k) => k.fill, e.style.fill, lerpColor);
|
|
2184
|
-
return e;
|
|
2185
|
-
}
|
|
2186
|
-
function followSpan(track, t) {
|
|
2187
|
-
t = localTime(track, t);
|
|
2188
|
-
let f = null;
|
|
2189
|
-
for (const s of track.follow ?? []) if (s.start <= t && (f === null || s.start > f.start)) f = s;
|
|
2190
|
-
if (!f) return null;
|
|
2191
|
-
const span = f;
|
|
2192
|
-
if (track.keys.some((k) => k.t >= span.end && k.t <= t && (k.x !== void 0 || k.y !== void 0))) return null;
|
|
2193
|
-
return f;
|
|
2194
|
-
}
|
|
2195
|
-
function tracing(e, page) {
|
|
2196
|
-
if (e.kind === "connector") return new Polyline(routePoints(route(e, indexOf(page))));
|
|
2197
|
-
const clean = cloneElement(e);
|
|
2198
|
-
clean.style.roughness = 0;
|
|
2199
|
-
const pts = outline(clean).contours()[0] ?? [];
|
|
2200
|
-
return new Polyline(angle(e) === 0 ? pts : pts.map((p) => rotated(e, p)));
|
|
2201
|
-
}
|
|
2202
|
-
function following(track, e, t, page) {
|
|
2203
|
-
const f = followSpan(track, t);
|
|
2204
|
-
if (!f) return e;
|
|
2205
|
-
t = localTime(track, t);
|
|
2206
|
-
const pathElement = page.elements.find((x) => x.id === f.path);
|
|
2207
|
-
if (!pathElement || pathElement.id === e.id) return e;
|
|
2208
|
-
const line2 = tracing(pathElement, page);
|
|
2209
|
-
const span = f.end - f.start;
|
|
2210
|
-
let u = Math.min(Math.max(applyEase(f.ease ?? "easeInOut", span > 0 ? Math.min(1, (t - f.start) / span) : 1), 0), 1) * (f.laps ?? 1);
|
|
2211
|
-
if (u > 1) u = rem(u, 1) === 0 ? 1 : rem(u, 1);
|
|
2212
|
-
if (f.reverse === true) u = 1 - u;
|
|
2213
|
-
const p = line2.point(u);
|
|
2214
|
-
const moved = cloneElement(e);
|
|
2215
|
-
moved.x = p.x - e.width / 2;
|
|
2216
|
-
moved.y = p.y - e.height / 2;
|
|
2217
|
-
if (f.orient === true) {
|
|
2218
|
-
const step = f.reverse === true ? -0.01 : 0.01;
|
|
2219
|
-
const a = line2.point(u - step / 2), b = line2.point(u + step / 2);
|
|
2220
|
-
if (a.x !== b.x || a.y !== b.y) moved.rotation = Math.atan2(b.y - a.y, b.x - a.x);
|
|
2221
|
-
}
|
|
2222
|
-
return moved;
|
|
2223
|
-
}
|
|
2224
|
-
function marchOffset(tl, speed, period, t) {
|
|
2225
|
-
if (!(period > 0)) return 0;
|
|
2226
|
-
const d = clampTime(tl.duration);
|
|
2227
|
-
if (d > 0) {
|
|
2228
|
-
const laps = roundHalfAway(speed * d / period);
|
|
2229
|
-
if (laps !== 0) speed = laps * period / d;
|
|
2230
|
-
}
|
|
2231
|
-
const r = rem(speed * t, period);
|
|
2232
|
-
return r < 0 ? -r : r === 0 ? 0 : period - r;
|
|
2233
|
-
}
|
|
2234
|
-
function trackMap(tl) {
|
|
2235
|
-
const m = /* @__PURE__ */ new Map();
|
|
2236
|
-
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
2237
|
-
return m;
|
|
2238
|
-
}
|
|
2239
|
-
function resolved(page, t) {
|
|
2240
|
-
const timeline = page.timeline;
|
|
2241
|
-
if (!timeline) return page;
|
|
2242
|
-
t = clampTime(t);
|
|
2243
|
-
const tracks = trackMap(timeline);
|
|
2244
|
-
const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
|
|
2245
|
-
const hidden = /* @__PURE__ */ new Set();
|
|
2246
|
-
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
2247
|
-
let moving = null;
|
|
2248
|
-
if (timeline.settle === true) {
|
|
2249
|
-
moving = /* @__PURE__ */ new Set();
|
|
2250
|
-
for (const tr of tracks.values()) if (isChanging(tr, t)) moving.add(tr.element);
|
|
2251
|
-
}
|
|
2252
|
-
const boils = (e) => {
|
|
2253
|
-
const override = tracks.get(e.id)?.boil;
|
|
2254
|
-
if (override !== void 0 && override !== null) return override;
|
|
2255
|
-
if (!moving) return true;
|
|
2256
|
-
if (e.connector) return [e.connector.start.elementID, e.connector.end.elementID].some((id) => id !== null && moving.has(id));
|
|
2257
|
-
return moving.has(e.id);
|
|
2258
|
-
};
|
|
2259
|
-
let elements = page.elements.map((e) => {
|
|
2260
|
-
const track = tracks.get(e.id);
|
|
2261
|
-
let out = track ? applyTrack(track, e, t) : e;
|
|
2262
|
-
if (reseed !== null && reseed > 0 && boils(e)) {
|
|
2263
|
-
if (out === e) out = cloneElement(e);
|
|
2264
|
-
out.seed = boiled(e.seed, reseed);
|
|
2265
|
-
}
|
|
2266
|
-
const pattern = track?.march != null ? dashPattern(out.style.dash, out.style.strokeWidth) : null;
|
|
2267
|
-
if (track?.march != null && pattern) {
|
|
2268
|
-
if (out === e) out = cloneElement(e);
|
|
2269
|
-
out.style.dashOffset = marchOffset(timeline, track.march, pattern.reduce((a, b) => a + b, 0), t);
|
|
2270
|
-
}
|
|
2271
|
-
return out;
|
|
2272
|
-
});
|
|
2273
|
-
elements = elements.map((e, i) => hidden.has(e.id) ? page.elements[i] : e);
|
|
2274
|
-
const posed = { ...page, elements, timeline: null };
|
|
2275
|
-
elements = elements.map((e) => {
|
|
2276
|
-
const track = tracks.get(e.id);
|
|
2277
|
-
return track ? following(track, e, t, posed) : e;
|
|
2278
|
-
});
|
|
2279
|
-
const current = { ...page, elements, timeline: null };
|
|
2280
|
-
const index = indexOf(current);
|
|
2281
|
-
const gone = new Set(hidden);
|
|
2282
|
-
elements = elements.map((e) => {
|
|
2283
|
-
const c = e.connector;
|
|
2284
|
-
if (!c || ![c.start.elementID, c.end.elementID].some((id) => id !== null && hidden.has(id))) return e;
|
|
2285
|
-
if (!(tracks.get(e.id)?.keys.some((k) => k.visible !== void 0) ?? false)) {
|
|
2286
|
-
gone.add(e.id);
|
|
2287
|
-
return e;
|
|
2509
|
+
function layoutLines(paras, width, height, fontSize) {
|
|
2510
|
+
const lines = [];
|
|
2511
|
+
let baseline = 0;
|
|
2512
|
+
let prev = null;
|
|
2513
|
+
paras.forEach((p, pi) => {
|
|
2514
|
+
const runs = pi < paras.length - 1 ? [...p.runs, { text: "\n", size: fontSize, bold: false, italic: false, mono: false, strike: false, underline: false, alpha: 1 }] : p.runs;
|
|
2515
|
+
const chars = [];
|
|
2516
|
+
runs.forEach((r, ri) => {
|
|
2517
|
+
for (const ch of r.text) chars.push({ ch, run: ri });
|
|
2518
|
+
});
|
|
2519
|
+
let k = 0;
|
|
2520
|
+
let firstLine = true;
|
|
2521
|
+
while (k < chars.length || firstLine && chars.length === 0) {
|
|
2522
|
+
const start = firstLine ? p.firstIndent : p.indent;
|
|
2523
|
+
const maxX2 = width;
|
|
2524
|
+
const glyphs = [];
|
|
2525
|
+
let x = start, lastSpace = -1, end = k;
|
|
2526
|
+
for (; end < chars.length; end++) {
|
|
2527
|
+
const { ch, run } = chars[end];
|
|
2528
|
+
const r = runs[run];
|
|
2529
|
+
if (ch === SEP || ch === "\n") {
|
|
2530
|
+
glyphs.push({ ch, run, x, w: 0 });
|
|
2531
|
+
end++;
|
|
2532
|
+
break;
|
|
2533
|
+
}
|
|
2534
|
+
if (ch === " ") {
|
|
2535
|
+
const stop = p.tabs.find((t) => t.x > x + 0.01);
|
|
2536
|
+
let tx;
|
|
2537
|
+
if (stop) {
|
|
2538
|
+
if (stop.align === "left") tx = stop.x;
|
|
2539
|
+
else {
|
|
2540
|
+
let seg = 0, prevCh = "";
|
|
2541
|
+
for (let q = end + 1; q < chars.length && chars[q].ch !== " " && chars[q].ch !== "\n" && chars[q].ch !== SEP; q++) {
|
|
2542
|
+
const rr = runs[chars[q].run];
|
|
2543
|
+
seg += charWidth(chars[q].ch, rr) + (prevCh ? kern(prevCh, chars[q].ch, rr) : 0);
|
|
2544
|
+
prevCh = chars[q].ch;
|
|
2545
|
+
}
|
|
2546
|
+
tx = Math.max(x, stop.align === "right" ? stop.x - seg : stop.x - seg / 2);
|
|
2547
|
+
}
|
|
2548
|
+
} else tx = (Math.floor(x / TAB_INTERVAL) + 1) * TAB_INTERVAL;
|
|
2549
|
+
glyphs.push({ ch, run, x, w: tx - x });
|
|
2550
|
+
x = tx;
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
const prevGlyph = glyphs[glyphs.length - 1];
|
|
2554
|
+
const kx = prevGlyph && prevGlyph.run === run && prevGlyph.ch !== " " ? kern(prevGlyph.ch, ch, r) : 0;
|
|
2555
|
+
if (prevGlyph) {
|
|
2556
|
+
prevGlyph.w += kx;
|
|
2557
|
+
x += kx;
|
|
2558
|
+
}
|
|
2559
|
+
const w = charWidth(ch, r);
|
|
2560
|
+
if (ch !== " " && x + w > maxX2 + 0.01 && glyphs.some((g) => g.ch !== " ")) {
|
|
2561
|
+
if (lastSpace >= 0) {
|
|
2562
|
+
glyphs.length = lastSpace + 1;
|
|
2563
|
+
end = k + lastSpace + 1;
|
|
2564
|
+
}
|
|
2565
|
+
if (prevGlyph) prevGlyph.w -= kx;
|
|
2566
|
+
break;
|
|
2567
|
+
}
|
|
2568
|
+
glyphs.push({ ch, run, x, w });
|
|
2569
|
+
x += w;
|
|
2570
|
+
if (breaksAfter(ch, chars[end + 1]?.ch ?? "")) lastSpace = glyphs.length - 1;
|
|
2571
|
+
}
|
|
2572
|
+
if (glyphs.length === 0 && end === k && k < chars.length) {
|
|
2573
|
+
const { ch, run } = chars[k];
|
|
2574
|
+
glyphs.push({ ch, run, x: start, w: charWidth(ch, runs[run]) });
|
|
2575
|
+
end = k + 1;
|
|
2576
|
+
}
|
|
2577
|
+
let asc = 0, desc = 0, lead = 0;
|
|
2578
|
+
for (const g of glyphs) {
|
|
2579
|
+
const [a, d, l] = metrics(g.ch, runs[g.run]);
|
|
2580
|
+
asc = Math.max(asc, a);
|
|
2581
|
+
desc = Math.max(desc, d);
|
|
2582
|
+
lead = Math.max(lead, l);
|
|
2583
|
+
}
|
|
2584
|
+
if (glyphs.length === 0) [asc, desc, lead] = metrics(" ", runs[0] ?? { text: " ", size: fontSize, bold: false, italic: false, mono: false, strike: false, underline: false, alpha: 1 });
|
|
2585
|
+
baseline = prev ? baseline + Math.round(prev.desc) + Math.round(prev.lead) + (firstLine ? p.spacing : 0) + Math.round(asc) : Math.round(asc);
|
|
2586
|
+
if (baseline + desc > height + 0.5) return;
|
|
2587
|
+
const line2 = { glyphs, asc, desc, lead, baseline, para: p, first: firstLine };
|
|
2588
|
+
line2.glyphs.forEach((g) => {
|
|
2589
|
+
g.r = runs[g.run];
|
|
2590
|
+
});
|
|
2591
|
+
lines.push(line2);
|
|
2592
|
+
prev = line2;
|
|
2593
|
+
firstLine = false;
|
|
2594
|
+
k = end;
|
|
2595
|
+
if (chars.length === 0) break;
|
|
2288
2596
|
}
|
|
2289
|
-
const [a, b] = endpoints(e, index);
|
|
2290
|
-
const pinned = cloneElement(e);
|
|
2291
|
-
pinned.connector.start = { elementID: null, port: c.start.port, x: a.x, y: a.y };
|
|
2292
|
-
pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
|
|
2293
|
-
return pinned;
|
|
2294
2597
|
});
|
|
2295
|
-
return
|
|
2296
|
-
}
|
|
2297
|
-
function posterTime(page) {
|
|
2298
|
-
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
2299
|
-
}
|
|
2300
|
-
function still(page) {
|
|
2301
|
-
return page.timeline ? resolved(page, posterTime(page)) : page;
|
|
2302
|
-
}
|
|
2303
|
-
function cameraRect(tl, t) {
|
|
2304
|
-
const keys = tl.camera ?? [];
|
|
2305
|
-
if (keys.length === 0) return null;
|
|
2306
|
-
let before;
|
|
2307
|
-
for (const k of keys) if (k.t <= t) before = k;
|
|
2308
|
-
const after = keys.find((k) => k.t > t);
|
|
2309
|
-
if (!before) return rectOf(keys[0]);
|
|
2310
|
-
if (!after) return rectOf(before);
|
|
2311
|
-
const u = applyEase(before.ease ?? "easeInOut", (t - before.t) / Math.max(after.t - before.t, 1e-9));
|
|
2312
|
-
const mix2 = (p, q) => p + (q - p) * u;
|
|
2313
|
-
return { x: mix2(before.x, after.x), y: mix2(before.y, after.y), width: Math.max(1, mix2(before.width, after.width)), height: Math.max(1, mix2(before.height, after.height)) };
|
|
2314
|
-
}
|
|
2315
|
-
var rectOf = (k) => ({ x: k.x, y: k.y, width: k.width, height: k.height });
|
|
2316
|
-
function cameraView(tl, t) {
|
|
2317
|
-
const first = tl.camera?.[0];
|
|
2318
|
-
const r = cameraRect(tl, t);
|
|
2319
|
-
if (!first || !r || !(r.width > 0 && r.height > 0)) return null;
|
|
2320
|
-
return fit(r, first.width / first.height);
|
|
2321
|
-
}
|
|
2322
|
-
function fit(r, aspect) {
|
|
2323
|
-
if (!(r.width > 0 && r.height > 0 && aspect > 0)) return r;
|
|
2324
|
-
let w = r.width, h = r.height;
|
|
2325
|
-
if (r.width / r.height < aspect) w = r.height * aspect;
|
|
2326
|
-
else h = r.width / aspect;
|
|
2327
|
-
return { x: r.x + r.width / 2 - w / 2, y: r.y + r.height / 2 - h / 2, width: w, height: h };
|
|
2598
|
+
return lines;
|
|
2328
2599
|
}
|
|
2329
|
-
function
|
|
2330
|
-
|
|
2600
|
+
function noteMarkup(source, e) {
|
|
2601
|
+
const rect2 = textRect({ x: e.x, y: e.y, width: e.width, height: e.height });
|
|
2602
|
+
if (!(rect2.width > 1 && rect2.height > 1)) return "";
|
|
2603
|
+
const b = new Builder(e.style.fontSize, rect2.width);
|
|
2604
|
+
for (const block of parseBlocks(source)) b.block(block, { indent: 0, marker: null, quote: null, listDepth: 0 });
|
|
2605
|
+
const lines = layoutLines(b.paras, rect2.width, rect2.height, e.style.fontSize);
|
|
2606
|
+
const ink = color(e.style.stroke);
|
|
2607
|
+
let s = "";
|
|
2608
|
+
const marks = [];
|
|
2609
|
+
for (const l of lines) {
|
|
2610
|
+
const p = l.para;
|
|
2611
|
+
if (p.quote !== null) marks.push([rect2.x + p.quote, rect2.y + l.baseline - l.asc, rect2.x + p.quote, rect2.y + l.baseline + l.desc]);
|
|
2612
|
+
if (p.rule !== null) marks.push([rect2.x + p.rule, rect2.y + l.baseline - 3, rect2.x + rect2.width, rect2.y + l.baseline - 3]);
|
|
2613
|
+
if (p.tableRule) {
|
|
2614
|
+
const y = rect2.y + l.baseline + l.desc + 2;
|
|
2615
|
+
marks.push([rect2.x + p.tableRule[0], y, Math.min(rect2.x + rect2.width, rect2.x + p.tableRule[0] + p.tableRule[1]), y]);
|
|
2616
|
+
}
|
|
2617
|
+
let spans = "";
|
|
2618
|
+
let i = 0;
|
|
2619
|
+
const gs = l.glyphs;
|
|
2620
|
+
while (i < gs.length) {
|
|
2621
|
+
const r = gs[i].r;
|
|
2622
|
+
let j = i;
|
|
2623
|
+
let text = "";
|
|
2624
|
+
const font = fontOf(gs[i].ch);
|
|
2625
|
+
while (j < gs.length && sameStyle(gs[j].r, r) && gs[j].ch !== " " && fontOf(gs[j].ch) === font) {
|
|
2626
|
+
text += gs[j].ch;
|
|
2627
|
+
j++;
|
|
2628
|
+
}
|
|
2629
|
+
const shown = text.replaceAll("\n", "").replaceAll(SEP, "");
|
|
2630
|
+
if (shown.trim() !== "") {
|
|
2631
|
+
let a = `font-size="${fmt(r.size)}"`;
|
|
2632
|
+
if (r.mono) a += ` font-family="Menlo, Consolas, monospace"`;
|
|
2633
|
+
if (r.bold) a += ` font-weight="bold"`;
|
|
2634
|
+
if (r.italic) a += ` font-style="italic"`;
|
|
2635
|
+
if (r.underline) a += ` text-decoration="underline"`;
|
|
2636
|
+
if (r.alpha < 1) a += ` fill-opacity="${fmt(r.alpha * e.style.stroke.a)}"`;
|
|
2637
|
+
spans += `<tspan x="${fmt(rect2.x + gs[i].x)}" ${a}>${escapeXML(shown)}</tspan>`;
|
|
2638
|
+
}
|
|
2639
|
+
if (r.strike && j > i) {
|
|
2640
|
+
const y = l.baseline - metrics(gs[i].ch, r)[0] * 0.32;
|
|
2641
|
+
marks.push([rect2.x + gs[i].x, rect2.y + y, rect2.x + gs[j - 1].x + gs[j - 1].w, rect2.y + y]);
|
|
2642
|
+
}
|
|
2643
|
+
if (j === i) j++;
|
|
2644
|
+
i = j;
|
|
2645
|
+
}
|
|
2646
|
+
if (spans) s += `<text y="${fmt(rect2.y + l.baseline)}" font-family="Helvetica Neue, Helvetica, Arial, sans-serif" fill="${ink}" xml:space="preserve">${spans}</text>
|
|
2647
|
+
`;
|
|
2648
|
+
}
|
|
2649
|
+
const width = fmt(Math.max(1, e.style.fontSize / 14));
|
|
2650
|
+
for (const [x1, y1, x2, y2] of marks) {
|
|
2651
|
+
s += `<line x1="${fmt(x1)}" y1="${fmt(y1)}" x2="${fmt(x2)}" y2="${fmt(y2)}" stroke="${ink}" stroke-width="${width}"/>
|
|
2652
|
+
`;
|
|
2653
|
+
}
|
|
2654
|
+
return s;
|
|
2331
2655
|
}
|
|
2332
|
-
function
|
|
2333
|
-
|
|
2334
|
-
const times = [];
|
|
2335
|
-
for (let i = 0; i < frameCount(tl); i++) times.push(i / fps);
|
|
2336
|
-
const last = times[times.length - 1];
|
|
2337
|
-
if (tl.duration - last > 1e-6) times.push(tl.duration);
|
|
2338
|
-
return times;
|
|
2656
|
+
function fontOf(ch) {
|
|
2657
|
+
return fallbackMetrics[ch] ? `fb:${ch}` : "";
|
|
2339
2658
|
}
|
|
2340
|
-
function
|
|
2341
|
-
|
|
2342
|
-
(tl.markers ?? []).forEach((m, i) => {
|
|
2343
|
-
if (m.t <= t) s = i;
|
|
2344
|
-
});
|
|
2345
|
-
return s;
|
|
2659
|
+
function sameStyle(a, b) {
|
|
2660
|
+
return a === b || a.size === b.size && a.bold === b.bold && a.italic === b.italic && a.mono === b.mono && a.strike === b.strike && a.underline === b.underline && a.alpha === b.alpha;
|
|
2346
2661
|
}
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2662
|
+
|
|
2663
|
+
// src/theme.ts
|
|
2664
|
+
var rgb = (r, g, b) => ({ r, g, b, a: 1 });
|
|
2665
|
+
var themes = [
|
|
2666
|
+
{ id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13) },
|
|
2667
|
+
{ id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13) },
|
|
2668
|
+
{ id: "sepia", name: "Sepia", background: rgb(0.953, 0.914, 0.824), ink: rgb(0.23, 0.18, 0.13) },
|
|
2669
|
+
{ id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984) },
|
|
2670
|
+
{ id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918) },
|
|
2671
|
+
{ id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94) }
|
|
2672
|
+
];
|
|
2673
|
+
function themeNamed(id) {
|
|
2674
|
+
return themes.find((t) => t.id === id) ?? null;
|
|
2352
2675
|
}
|
|
2353
2676
|
|
|
2354
2677
|
// src/svg.ts
|
|
2355
2678
|
function bounds(page, padding = 24) {
|
|
2679
|
+
if (hasInstances(page)) return bounds(expandingSymbols(page, 0), padding);
|
|
2356
2680
|
const index = indexOf(page);
|
|
2357
2681
|
const box = (e) => {
|
|
2358
2682
|
if (e.kind === "connector") return connectorBounds(e, index);
|
|
@@ -2394,6 +2718,7 @@ function svgOpen(b, background, paint) {
|
|
|
2394
2718
|
return out;
|
|
2395
2719
|
}
|
|
2396
2720
|
function svgParts(page, background, fixed) {
|
|
2721
|
+
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed);
|
|
2397
2722
|
const theme = themeNamed(page.theme);
|
|
2398
2723
|
const bg = background ? theme?.background ?? background : null;
|
|
2399
2724
|
const ink = theme?.ink ?? black;
|
|
@@ -2461,10 +2786,10 @@ function svgElement(e, index, labelBackground) {
|
|
|
2461
2786
|
}
|
|
2462
2787
|
switch (e.kind) {
|
|
2463
2788
|
case "connector": {
|
|
2464
|
-
const [line2,
|
|
2789
|
+
const [line2, filled2] = connectorParts(e, index);
|
|
2465
2790
|
let s = `<path d="${line2.data()}" fill="none" ${strokeAttrs}/>
|
|
2466
2791
|
`;
|
|
2467
|
-
if (!
|
|
2792
|
+
if (!filled2.isEmpty) s += `<path d="${filled2.data()}" fill="${stroke}" stroke="none"/>
|
|
2468
2793
|
`;
|
|
2469
2794
|
if (text) {
|
|
2470
2795
|
const lr = labelRect(e, index);
|
|
@@ -2574,8 +2899,9 @@ input[type=range] { flex: 1; min-width: 60px; accent-color: currentColor; }
|
|
|
2574
2899
|
output { font-variant-numeric: tabular-nums; min-width: 4.5em; text-align: right; }
|
|
2575
2900
|
.scene { min-width: 0; flex: 0 1 auto; text-align: center; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2576
2901
|
.error { padding: 16px; font-family: ui-monospace, Menlo, monospace; white-space: pre-wrap; }
|
|
2577
|
-
`;
|
|
2902
|
+
${CLICK_STYLE}`;
|
|
2578
2903
|
var Playback = class {
|
|
2904
|
+
/** `file` is the drawing `page` belongs to, so links to its other pages are clickable. */
|
|
2579
2905
|
constructor(page, into, options = {}) {
|
|
2580
2906
|
__publicField(this, "page");
|
|
2581
2907
|
__publicField(this, "plan");
|
|
@@ -2583,6 +2909,22 @@ var Playback = class {
|
|
|
2583
2909
|
__publicField(this, "sceneNames");
|
|
2584
2910
|
__publicField(this, "background");
|
|
2585
2911
|
__publicField(this, "ink");
|
|
2912
|
+
/** What clicking each clickable element does, by element id. */
|
|
2913
|
+
__publicField(this, "clicks");
|
|
2914
|
+
/** Copies of symbols that play through once when clicked (playback "click"), by id, with their clip's length. */
|
|
2915
|
+
__publicField(this, "replays", /* @__PURE__ */ new Map());
|
|
2916
|
+
/** When each replaying copy was clicked (performance.now() milliseconds). */
|
|
2917
|
+
__publicField(this, "replayStarts", /* @__PURE__ */ new Map());
|
|
2918
|
+
__publicField(this, "hoverIDs");
|
|
2919
|
+
__publicField(this, "labels", /* @__PURE__ */ new Map());
|
|
2920
|
+
/** Each drawn part of a clickable copy, to that copy's id; and each copy's parts in drawing order. */
|
|
2921
|
+
__publicField(this, "owner", /* @__PURE__ */ new Map());
|
|
2922
|
+
__publicField(this, "partsOfOwner", /* @__PURE__ */ new Map());
|
|
2923
|
+
/** The moments of the button copies' over, down and hit states. */
|
|
2924
|
+
__publicField(this, "overTimes", /* @__PURE__ */ new Map());
|
|
2925
|
+
__publicField(this, "downTimes", /* @__PURE__ */ new Map());
|
|
2926
|
+
__publicField(this, "hitTimes", /* @__PURE__ */ new Map());
|
|
2927
|
+
__publicField(this, "last", { t: 0, i: 0 });
|
|
2586
2928
|
__publicField(this, "svg");
|
|
2587
2929
|
__publicField(this, "groups", /* @__PURE__ */ new Map());
|
|
2588
2930
|
__publicField(this, "order", []);
|
|
@@ -2590,6 +2932,30 @@ var Playback = class {
|
|
|
2590
2932
|
__publicField(this, "onFrame", null);
|
|
2591
2933
|
this.page = options.clean ? withRoughness(page, 0) : page;
|
|
2592
2934
|
this.plan = plan(this.page);
|
|
2935
|
+
this.clicks = clickTargets(page, options.file);
|
|
2936
|
+
const table = symbolTable(this.page);
|
|
2937
|
+
for (const e of this.page.elements) {
|
|
2938
|
+
const s = e.kind === "symbol" && e.symbol !== null ? table.get(e.symbol) : void 0;
|
|
2939
|
+
if (!s) continue;
|
|
2940
|
+
if (s.button === true) {
|
|
2941
|
+
for (const [state, times] of [["over", this.overTimes], ["down", this.downTimes], ["hit", this.hitTimes]]) {
|
|
2942
|
+
const at = buttonTime(s, state);
|
|
2943
|
+
if (at !== null) times.set(e.id, at);
|
|
2944
|
+
}
|
|
2945
|
+
} else if (e.playback === "click" && (s.timeline?.duration ?? 0) > 0) {
|
|
2946
|
+
this.replays.set(e.id, s.timeline.duration);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
const clickable2 = /* @__PURE__ */ new Set([...this.clicks.keys(), ...this.replays.keys()]);
|
|
2950
|
+
this.hoverIDs = new Set(this.page.elements.filter((e) => clickable2.has(e.id) && e.kind !== "symbol" && hasHover(e)).map((e) => e.id));
|
|
2951
|
+
const instances = new Set(this.page.elements.filter((e) => clickable2.has(e.id) && e.kind === "symbol").map((e) => e.id));
|
|
2952
|
+
for (const e of this.page.elements) {
|
|
2953
|
+
if (!clickable2.has(e.id)) continue;
|
|
2954
|
+
this.labels.set(e.id, buttonLabel(e, e.symbol !== null ? table.get(e.symbol)?.name : void 0));
|
|
2955
|
+
if (e.kind !== "symbol") this.owner.set(e.id, e.id);
|
|
2956
|
+
}
|
|
2957
|
+
for (const [part, id] of partsOf(this.page, instances)) this.owner.set(part, id);
|
|
2958
|
+
for (const [part, id] of this.owner) this.partsOfOwner.set(id, [...this.partsOfOwner.get(id) ?? [], part]);
|
|
2593
2959
|
const near = (t) => {
|
|
2594
2960
|
let best = 0;
|
|
2595
2961
|
this.plan.times.forEach((x, i) => {
|
|
@@ -2628,24 +2994,73 @@ var Playback = class {
|
|
|
2628
2994
|
this.frame = i;
|
|
2629
2995
|
this.onFrame?.(i);
|
|
2630
2996
|
}
|
|
2997
|
+
/** Starts copy `id` replaying from `at` (performance.now() milliseconds; earlier to start partway). */
|
|
2998
|
+
replay(id, at) {
|
|
2999
|
+
if (this.replays.has(id)) this.replayStarts.set(id, at);
|
|
3000
|
+
}
|
|
3001
|
+
/** Whether a copy is still playing through after a click. */
|
|
3002
|
+
replaying(now = performance.now()) {
|
|
3003
|
+
for (const [id, start] of this.replayStarts) if (now - start < (this.replays.get(id) ?? 0) * 1e3) return true;
|
|
3004
|
+
return false;
|
|
3005
|
+
}
|
|
3006
|
+
/** Draws the last moment drawn again, for copies replaying while the page stands still. */
|
|
3007
|
+
redraw() {
|
|
3008
|
+
this.drawAt(this.last.t, this.last.i);
|
|
3009
|
+
}
|
|
2631
3010
|
/** Draws the moment `t` (seconds) in the frame-`i` view. */
|
|
2632
3011
|
drawAt(t, i = 0) {
|
|
3012
|
+
this.last = { t, i };
|
|
2633
3013
|
const r = this.plan.rects[Math.min(i, this.plan.rects.length - 1)];
|
|
2634
|
-
const
|
|
3014
|
+
const now = typeof performance === "undefined" ? 0 : performance.now();
|
|
3015
|
+
const times = /* @__PURE__ */ new Map();
|
|
3016
|
+
for (const [id, start] of this.replayStarts) times.set(id, Math.min((now - start) / 1e3, this.replays.get(id) ?? 0));
|
|
3017
|
+
const tc = clampTime(t);
|
|
3018
|
+
const pose = posed(this.page, tc);
|
|
3019
|
+
const frame2 = expandingSymbols(pose, tc, true, times);
|
|
3020
|
+
const { parts } = svgParts(frame2, this.background, r);
|
|
3021
|
+
const owned = (m, parts2) => {
|
|
3022
|
+
for (const p of parts2) if (this.owner.has(p.key)) m.set(p.key, p.markup);
|
|
3023
|
+
return m;
|
|
3024
|
+
};
|
|
3025
|
+
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(expandingSymbols(pose, tc, true, new Map([...times, ...st])), this.background, r).parts.filter((p) => st.has(this.owner.get(p.key) ?? "")));
|
|
3026
|
+
const hover = this.hoverIDs.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(hovered(frame2, this.hoverIDs), this.background, r).parts.filter((p) => this.hoverIDs.has(p.key)));
|
|
3027
|
+
const over = state(this.overTimes), down = state(this.downTimes), hit = state(this.hitTimes);
|
|
3028
|
+
const normal = owned(/* @__PURE__ */ new Map(), parts);
|
|
3029
|
+
const merged = [];
|
|
3030
|
+
const done = /* @__PURE__ */ new Set();
|
|
3031
|
+
for (const p of parts) {
|
|
3032
|
+
const o = this.owner.get(p.key);
|
|
3033
|
+
if (o === void 0) {
|
|
3034
|
+
merged.push(p);
|
|
3035
|
+
continue;
|
|
3036
|
+
}
|
|
3037
|
+
if (done.has(o)) continue;
|
|
3038
|
+
done.add(o);
|
|
3039
|
+
merged.push(this.buttonPart(o, normal, hover, over, down, hit, pose));
|
|
3040
|
+
}
|
|
2635
3041
|
this.svg.setAttribute("viewBox", `${fmt(r.x)} ${fmt(r.y)} ${fmt(r.width)} ${fmt(r.height)}`);
|
|
2636
3042
|
const ns = "http://www.w3.org/2000/svg";
|
|
2637
|
-
const keys =
|
|
2638
|
-
for (const p of
|
|
3043
|
+
const keys = merged.map((p) => p.key);
|
|
3044
|
+
for (const p of merged) {
|
|
3045
|
+
const markup = p.markup;
|
|
2639
3046
|
const have = this.groups.get(p.key);
|
|
2640
3047
|
if (have) {
|
|
2641
|
-
if (have.markup !==
|
|
2642
|
-
have.g.innerHTML =
|
|
2643
|
-
have.markup =
|
|
3048
|
+
if (have.markup !== markup) {
|
|
3049
|
+
have.g.innerHTML = markup;
|
|
3050
|
+
have.markup = markup;
|
|
2644
3051
|
}
|
|
3052
|
+
if (p.button !== void 0) have.g.classList.toggle("dottie-b", p.button);
|
|
2645
3053
|
} else {
|
|
2646
3054
|
const g = document.createElementNS(ns, "g");
|
|
2647
|
-
g.innerHTML =
|
|
2648
|
-
|
|
3055
|
+
g.innerHTML = markup;
|
|
3056
|
+
if (p.button !== void 0) {
|
|
3057
|
+
g.setAttribute("class", p.button ? "dottie-a dottie-b" : "dottie-a");
|
|
3058
|
+
g.setAttribute("data-a", p.key);
|
|
3059
|
+
g.setAttribute("tabindex", "0");
|
|
3060
|
+
g.setAttribute("role", "button");
|
|
3061
|
+
g.setAttribute("aria-label", this.labels.get(p.key) ?? "");
|
|
3062
|
+
}
|
|
3063
|
+
this.groups.set(p.key, { g, markup });
|
|
2649
3064
|
}
|
|
2650
3065
|
}
|
|
2651
3066
|
const live = new Set(keys);
|
|
@@ -2654,10 +3069,66 @@ var Playback = class {
|
|
|
2654
3069
|
this.groups.delete(k);
|
|
2655
3070
|
}
|
|
2656
3071
|
if (keys.length !== this.order.length || keys.some((k, j) => k !== this.order[j])) {
|
|
2657
|
-
|
|
3072
|
+
let at = this.svg.firstChild;
|
|
3073
|
+
for (const k of keys) {
|
|
3074
|
+
const g = this.groups.get(k).g;
|
|
3075
|
+
if (g === at) at = at.nextSibling;
|
|
3076
|
+
else this.svg.insertBefore(g, at);
|
|
3077
|
+
}
|
|
3078
|
+
while (at) {
|
|
3079
|
+
const next = at.nextSibling;
|
|
3080
|
+
at.remove();
|
|
3081
|
+
at = next;
|
|
3082
|
+
}
|
|
2658
3083
|
this.order = keys;
|
|
2659
3084
|
}
|
|
2660
3085
|
}
|
|
3086
|
+
/** What a clickable owner draws as: its normal look, and its hover, over and down looks where they differ. A
|
|
3087
|
+
* symbol's copy keeps its drawing out of the pointer's way and takes clicks on a layer above: its hit state drawn
|
|
3088
|
+
* invisibly, or its frame. `button` is whether it has a down look (the stylesheet nudges the others). */
|
|
3089
|
+
buttonPart(o, normal, hover, over, down, hit, pose) {
|
|
3090
|
+
const keys = this.partsOfOwner.get(o) ?? [o];
|
|
3091
|
+
const join = (m2) => {
|
|
3092
|
+
const s = keys.map((k) => m2.get(k) ?? "").join("");
|
|
3093
|
+
return s === "" ? null : s;
|
|
3094
|
+
};
|
|
3095
|
+
const base = join(normal) ?? "";
|
|
3096
|
+
const instance = pose.elements.find((e) => e.id === o && e.kind === "symbol");
|
|
3097
|
+
const overLook = instance ? join(over) : join(hover);
|
|
3098
|
+
const downLook = instance ? join(down) : null;
|
|
3099
|
+
const shownOver = overLook !== null && overLook !== base ? overLook : null;
|
|
3100
|
+
const shownDown = downLook !== null && downLook !== (overLook ?? base) ? downLook : null;
|
|
3101
|
+
if (!instance && shownOver === null) return { key: o, markup: base, button: false };
|
|
3102
|
+
const still2 = instance ? ` pointer-events="none"` : "";
|
|
3103
|
+
let m = `<g class="dottie-n"${still2}>
|
|
3104
|
+
${base}</g>
|
|
3105
|
+
`;
|
|
3106
|
+
if (shownOver !== null) m += `<g class="dottie-h"${still2}>
|
|
3107
|
+
${shownOver}</g>
|
|
3108
|
+
`;
|
|
3109
|
+
if (shownDown !== null) m += `<g class="dottie-d"${still2}>
|
|
3110
|
+
${shownDown}</g>
|
|
3111
|
+
`;
|
|
3112
|
+
if (instance) {
|
|
3113
|
+
const h = join(hit);
|
|
3114
|
+
if (h !== null) m += `<g class="dottie-hit" opacity="0" pointer-events="all">
|
|
3115
|
+
${h}</g>
|
|
3116
|
+
`;
|
|
3117
|
+
else {
|
|
3118
|
+
const c = { x: instance.x + instance.width / 2, y: instance.y + instance.height / 2 }, a = instance.rotation ?? 0;
|
|
3119
|
+
const corners2 = [
|
|
3120
|
+
[instance.x, instance.y],
|
|
3121
|
+
[instance.x + instance.width, instance.y],
|
|
3122
|
+
[instance.x + instance.width, instance.y + instance.height],
|
|
3123
|
+
[instance.x, instance.y + instance.height]
|
|
3124
|
+
].map(([x, y]) => rotate({ x, y }, c, a));
|
|
3125
|
+
const path = new Path().moveTo(corners2[0]).lineTo(corners2[1]).lineTo(corners2[2]).lineTo(corners2[3]).close();
|
|
3126
|
+
m += `<path class="dottie-hit" d="${path.data()}" fill="none" stroke="none" pointer-events="all"/>
|
|
3127
|
+
`;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return { key: o, markup: m, button: shownDown !== null };
|
|
3131
|
+
}
|
|
2661
3132
|
/** The scene frame `i` is in, or -1 before the first. */
|
|
2662
3133
|
sceneAt(i) {
|
|
2663
3134
|
let s = -1;
|
|
@@ -2687,6 +3158,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2687
3158
|
__publicField(this, "last", 0);
|
|
2688
3159
|
__publicField(this, "acc", 0);
|
|
2689
3160
|
__publicField(this, "raf", 0);
|
|
3161
|
+
__publicField(this, "replayRaf", 0);
|
|
2690
3162
|
__publicField(this, "loadToken", 0);
|
|
2691
3163
|
__publicField(this, "visible", true);
|
|
2692
3164
|
__publicField(this, "observer", null);
|
|
@@ -2720,6 +3192,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2720
3192
|
};
|
|
2721
3193
|
this.next.onclick = () => this.nextScene();
|
|
2722
3194
|
this.prev.onclick = () => this.prevScene();
|
|
3195
|
+
this.stage.addEventListener("click", (e) => {
|
|
3196
|
+
const b = clickable(e.target);
|
|
3197
|
+
if (b) this.activate(b.getAttribute("data-a") ?? "");
|
|
3198
|
+
});
|
|
2723
3199
|
this.addEventListener("keydown", (e) => this.key(e));
|
|
2724
3200
|
}
|
|
2725
3201
|
connectedCallback() {
|
|
@@ -2781,7 +3257,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2781
3257
|
mount() {
|
|
2782
3258
|
const file = this.file;
|
|
2783
3259
|
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
2784
|
-
const p = new Playback(page, this.stage, { clean: this.hasAttribute("clean") });
|
|
3260
|
+
const p = new Playback(page, this.stage, { clean: this.hasAttribute("clean"), file });
|
|
2785
3261
|
this.playback = p;
|
|
2786
3262
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
2787
3263
|
this.style.setProperty("--dottie-ink", color(p.ink));
|
|
@@ -2856,6 +3332,50 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2856
3332
|
this.setPlaying(false);
|
|
2857
3333
|
p.show(s >= 0 ? p.scenes[s] : 0);
|
|
2858
3334
|
}
|
|
3335
|
+
/** Does what clicking element `id` does: jumps to its scene and plays on to the next (with reduced motion, or in a
|
|
3336
|
+
* still, straight to where that stops), opens its link in a new tab, or shows its page. */
|
|
3337
|
+
activate(id) {
|
|
3338
|
+
const p = this.playback;
|
|
3339
|
+
const click = p?.clicks.get(id);
|
|
3340
|
+
const replay = p?.replays.has(id) ?? false;
|
|
3341
|
+
if (!p || !click && !replay) return;
|
|
3342
|
+
const detail = { element: id, ...click ?? {}, ...replay ? { replay: true } : {} };
|
|
3343
|
+
if (!this.dispatchEvent(new CustomEvent("action", { detail, cancelable: true }))) return;
|
|
3344
|
+
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
3345
|
+
if (replay) {
|
|
3346
|
+
const now = performance.now();
|
|
3347
|
+
p.replay(id, reduced ? now - p.replays.get(id) * 1e3 : now);
|
|
3348
|
+
p.redraw();
|
|
3349
|
+
this.scheduleReplay();
|
|
3350
|
+
}
|
|
3351
|
+
if (!click) return;
|
|
3352
|
+
if ("url" in click) {
|
|
3353
|
+
if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
|
|
3354
|
+
} else if ("page" in click) {
|
|
3355
|
+
this.setAttribute("page", String(click.page + 1));
|
|
3356
|
+
} else if (click.scene >= 0 && click.scene < p.scenes.length) {
|
|
3357
|
+
if (reduced || this.hasAttribute("at") || p.frameCount < 2) {
|
|
3358
|
+
this.setPlaying(false);
|
|
3359
|
+
p.show(click.scene + 1 < p.scenes.length ? p.scenes[click.scene + 1] : p.frameCount - 1);
|
|
3360
|
+
} else {
|
|
3361
|
+
p.show(p.scenes[click.scene]);
|
|
3362
|
+
this.setPlaying(true);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
/** Redraws, while the page itself stands still, as long as a clicked copy is playing through. */
|
|
3367
|
+
scheduleReplay() {
|
|
3368
|
+
if (this.replayRaf) return;
|
|
3369
|
+
const step = () => {
|
|
3370
|
+
this.replayRaf = 0;
|
|
3371
|
+
const p = this.playback;
|
|
3372
|
+
if (!p || !this.isConnected) return;
|
|
3373
|
+
if (!this.playing) p.redraw();
|
|
3374
|
+
if (p.replaying()) this.replayRaf = requestAnimationFrame(step);
|
|
3375
|
+
else if (!this.playing) p.redraw();
|
|
3376
|
+
};
|
|
3377
|
+
this.replayRaf = requestAnimationFrame(step);
|
|
3378
|
+
}
|
|
2859
3379
|
setPlaying(on) {
|
|
2860
3380
|
const p = this.playback;
|
|
2861
3381
|
this.playing = on && !!p && p.frameCount > 1 && p.page.timeline !== null;
|
|
@@ -2897,8 +3417,15 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2897
3417
|
}
|
|
2898
3418
|
key(e) {
|
|
2899
3419
|
const p = this.playback;
|
|
3420
|
+
const target = e.composedPath()[0];
|
|
3421
|
+
const b = clickable(target);
|
|
3422
|
+
if (p && b && (e.key === "Enter" || e.key === " ")) {
|
|
3423
|
+
e.preventDefault();
|
|
3424
|
+
this.activate(b.getAttribute("data-a") ?? "");
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
2900
3427
|
if (!p || p.page.timeline === null || p.frameCount <= 1 || this.hasAttribute("at")) return;
|
|
2901
|
-
if (
|
|
3428
|
+
if (target === this.scrub && e.key !== " ") return;
|
|
2902
3429
|
const scenes = p.scenes.length > 0;
|
|
2903
3430
|
let handled = true;
|
|
2904
3431
|
if (e.key === " ") scenes ? this.playing ? this.setPlaying(false) : this.nextScene() : this.setPlaying(!this.playing);
|
|
@@ -2925,6 +3452,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2925
3452
|
}
|
|
2926
3453
|
};
|
|
2927
3454
|
__publicField(DottiePlayer, "observedAttributes", ["src", "page", "at", "clean", "no-controls", "label"]);
|
|
3455
|
+
function clickable(target) {
|
|
3456
|
+
const el = target;
|
|
3457
|
+
return el && typeof el.closest === "function" ? el.closest(".dottie-a") : null;
|
|
3458
|
+
}
|
|
2928
3459
|
function nearest(p, t) {
|
|
2929
3460
|
let best = 0;
|
|
2930
3461
|
p.plan.times.forEach((x, i) => {
|
|
@@ -3024,7 +3555,7 @@ function render(source, options = {}) {
|
|
|
3024
3555
|
function isFile(v) {
|
|
3025
3556
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
3026
3557
|
}
|
|
3027
|
-
var VERSION = "0.5.
|
|
3558
|
+
var VERSION = "0.5.2";
|
|
3028
3559
|
export {
|
|
3029
3560
|
BOIL_DRAWINGS,
|
|
3030
3561
|
DottieFormatError,
|
|
@@ -3036,6 +3567,7 @@ export {
|
|
|
3036
3567
|
Playback,
|
|
3037
3568
|
SUPPORTED_VERSION,
|
|
3038
3569
|
VERSION,
|
|
3570
|
+
allowedURL,
|
|
3039
3571
|
black,
|
|
3040
3572
|
bounds,
|
|
3041
3573
|
cameraView,
|