@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-player.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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 __export = (target, all) => {
|
|
@@ -20,6 +20,7 @@ __export(index_exports, {
|
|
|
20
20
|
Playback: () => Playback,
|
|
21
21
|
SUPPORTED_VERSION: () => SUPPORTED_VERSION,
|
|
22
22
|
VERSION: () => VERSION,
|
|
23
|
+
allowedURL: () => allowedURL,
|
|
23
24
|
black: () => black,
|
|
24
25
|
bounds: () => bounds,
|
|
25
26
|
cameraView: () => cameraView,
|
|
@@ -60,11 +61,12 @@ __export(index_exports, {
|
|
|
60
61
|
|
|
61
62
|
// src/model.ts
|
|
62
63
|
var SUPPORTED_VERSION = 2;
|
|
63
|
-
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image"];
|
|
64
|
+
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
64
65
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
65
66
|
var KNOWN_FIELDS = {
|
|
66
67
|
file: ["version", "pages", "images"],
|
|
67
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline"],
|
|
68
|
+
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols"],
|
|
69
|
+
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
68
70
|
layer: ["id", "name", "visible", "locked"],
|
|
69
71
|
rgba: ["r", "g", "b", "a"],
|
|
70
72
|
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "gridSize", "opacity", "dashOffset"],
|
|
@@ -88,6 +90,12 @@ var KNOWN_FIELDS = {
|
|
|
88
90
|
"rotation",
|
|
89
91
|
"link",
|
|
90
92
|
"anchor",
|
|
93
|
+
"click",
|
|
94
|
+
"hover",
|
|
95
|
+
"symbol",
|
|
96
|
+
"playback",
|
|
97
|
+
"offset",
|
|
98
|
+
"args",
|
|
91
99
|
"image",
|
|
92
100
|
"reveal"
|
|
93
101
|
],
|
|
@@ -102,7 +110,9 @@ var KNOWN_FIELDS = {
|
|
|
102
110
|
follow: ["path", "start", "end", "ease", "reverse", "orient", "laps"],
|
|
103
111
|
repeat: ["start", "end", "count"],
|
|
104
112
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
105
|
-
marker: ["t", "name"]
|
|
113
|
+
marker: ["t", "name"],
|
|
114
|
+
click: ["scene", "url"],
|
|
115
|
+
hover: ["stroke", "fill", "opacity"]
|
|
106
116
|
};
|
|
107
117
|
var MAX_DURATION = 3600;
|
|
108
118
|
var MAX_MARCH = 1e4;
|
|
@@ -116,6 +126,7 @@ var DottieFormatError = class extends Error {
|
|
|
116
126
|
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
117
127
|
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
118
128
|
var str = (v) => typeof v === "string" ? v : void 0;
|
|
129
|
+
var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
|
|
119
130
|
var bool = (v) => typeof v === "boolean" ? v : void 0;
|
|
120
131
|
var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
|
|
121
132
|
function clampTime(t) {
|
|
@@ -125,6 +136,11 @@ function clampTime(t) {
|
|
|
125
136
|
function clampFPS(f) {
|
|
126
137
|
return Number.isFinite(f) ? Math.min(Math.max(f, 1), 60) : 12;
|
|
127
138
|
}
|
|
139
|
+
function allowedURL(url) {
|
|
140
|
+
const u = url.trim();
|
|
141
|
+
if (!/^(https?:\/\/|mailto:)/i.test(u) || [...u].length <= 7 || /[\u0000-\u001f\u007f-\u009f]/.test(u)) return null;
|
|
142
|
+
return u;
|
|
143
|
+
}
|
|
128
144
|
function rgba(v) {
|
|
129
145
|
if (!isObj(v)) return void 0;
|
|
130
146
|
const r = num(v.r), g = num(v.g), b = num(v.b);
|
|
@@ -239,10 +255,63 @@ var Reader = class {
|
|
|
239
255
|
rotation: num(v.rotation) ?? null,
|
|
240
256
|
link: uuid(v.link) ?? null,
|
|
241
257
|
anchor: str(v.anchor) ?? null,
|
|
258
|
+
click: this.click(v.click),
|
|
259
|
+
hover: this.hover(v.hover),
|
|
260
|
+
symbol: uuid(v.symbol) ?? null,
|
|
261
|
+
playback: this.playback(v.playback),
|
|
262
|
+
offset: num(v.offset) ?? null,
|
|
263
|
+
args: strings(v.args),
|
|
242
264
|
image: this.image(v.image, images),
|
|
243
265
|
reveal: num(v.reveal) ?? null
|
|
244
266
|
};
|
|
245
267
|
}
|
|
268
|
+
click(v) {
|
|
269
|
+
if (!isObj(v)) return null;
|
|
270
|
+
this.fields(v, "click");
|
|
271
|
+
const url = str(v.url);
|
|
272
|
+
const safe = url === void 0 ? null : allowedURL(url);
|
|
273
|
+
if (url !== void 0 && safe === null) this.warn("Skipped a click link that isn't http, https or mailto");
|
|
274
|
+
return { scene: str(v.scene) ?? null, url: safe };
|
|
275
|
+
}
|
|
276
|
+
playback(v) {
|
|
277
|
+
const s = str(v);
|
|
278
|
+
if (s === void 0) return null;
|
|
279
|
+
return ["loop", "once", "still", "click"].includes(s) ? s : "loop";
|
|
280
|
+
}
|
|
281
|
+
symbol(v, images) {
|
|
282
|
+
if (!isObj(v)) return null;
|
|
283
|
+
this.fields(v, "symbol");
|
|
284
|
+
const id = uuid(v.id);
|
|
285
|
+
if (!id) {
|
|
286
|
+
this.warn("Skipped a symbol without an id");
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const size = (x) => {
|
|
290
|
+
const n = num(x);
|
|
291
|
+
return Math.max(1, n ?? 100);
|
|
292
|
+
};
|
|
293
|
+
const layers = (Array.isArray(v.layers) ? v.layers : []).filter(isObj).flatMap((l) => {
|
|
294
|
+
const lid = uuid(l.id);
|
|
295
|
+
return lid ? [{ id: lid, name: str(l.name) ?? "", visible: bool(l.visible) ?? true, locked: bool(l.locked) ?? false }] : [];
|
|
296
|
+
});
|
|
297
|
+
if (layers.length === 0) layers.push({ id: `${id}-LAYER`, name: "Layer 1", visible: true, locked: false });
|
|
298
|
+
return {
|
|
299
|
+
id,
|
|
300
|
+
name: str(v.name) ?? "Symbol",
|
|
301
|
+
width: size(v.width),
|
|
302
|
+
height: size(v.height),
|
|
303
|
+
layers,
|
|
304
|
+
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
305
|
+
timeline: this.timeline(v.timeline),
|
|
306
|
+
button: bool(v.button) ?? null,
|
|
307
|
+
params: strings(v.params)
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
hover(v) {
|
|
311
|
+
if (!isObj(v)) return null;
|
|
312
|
+
this.fields(v, "hover");
|
|
313
|
+
return { stroke: rgba(v.stroke) ?? null, fill: rgba(v.fill) ?? null, opacity: num(v.opacity) ?? null };
|
|
314
|
+
}
|
|
246
315
|
ease(v) {
|
|
247
316
|
const s = str(v);
|
|
248
317
|
if (s === void 0) return null;
|
|
@@ -335,7 +404,8 @@ var Reader = class {
|
|
|
335
404
|
layers,
|
|
336
405
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
337
406
|
theme: str(v.theme) ?? null,
|
|
338
|
-
timeline: this.timeline(v.timeline)
|
|
407
|
+
timeline: this.timeline(v.timeline),
|
|
408
|
+
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null
|
|
339
409
|
};
|
|
340
410
|
}
|
|
341
411
|
};
|
|
@@ -977,6 +1047,190 @@ function hachureLines(polygons, angle2, gap) {
|
|
|
977
1047
|
return out;
|
|
978
1048
|
}
|
|
979
1049
|
|
|
1050
|
+
// src/dungeon.ts
|
|
1051
|
+
var SUB = 2;
|
|
1052
|
+
var BAND = 0.75;
|
|
1053
|
+
var GROW = 0.8;
|
|
1054
|
+
var JITTER = 0.6;
|
|
1055
|
+
function gridLines(polygons, cell) {
|
|
1056
|
+
return [...scan(polygons, cell, false), ...scan(polygons, cell, true)];
|
|
1057
|
+
}
|
|
1058
|
+
function scan(polygons, cell, transposed) {
|
|
1059
|
+
if (!(cell > 0)) return [];
|
|
1060
|
+
const flip = (p) => transposed ? { x: p.y, y: p.x } : p;
|
|
1061
|
+
const polys = polygons.filter((p) => p.length >= 3).map((p) => p.map(flip));
|
|
1062
|
+
const ys = polys.flat().map((p) => p.y);
|
|
1063
|
+
if (ys.length === 0) return [];
|
|
1064
|
+
const minY = Math.min(...ys), maxY2 = Math.max(...ys);
|
|
1065
|
+
const out = [];
|
|
1066
|
+
for (let y = Math.floor(minY / cell) * cell; y < maxY2 - 0.5; y += cell) {
|
|
1067
|
+
if (!(y > minY + 0.5)) continue;
|
|
1068
|
+
const xs = [];
|
|
1069
|
+
for (const poly of polys) {
|
|
1070
|
+
for (let i = 0; i < poly.length; i++) {
|
|
1071
|
+
const a = poly[i], b = poly[(i + 1) % poly.length];
|
|
1072
|
+
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));
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
xs.sort((p, q) => p - q);
|
|
1076
|
+
for (let k = 0; k + 1 < xs.length; k += 2) {
|
|
1077
|
+
if (xs[k + 1] - xs[k] > 0.5) out.push([flip({ x: xs[k], y }), flip({ x: xs[k + 1], y })]);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return out;
|
|
1081
|
+
}
|
|
1082
|
+
function lineColor(floor) {
|
|
1083
|
+
if (0.2126 * floor.r + 0.7152 * floor.g + 0.0722 * floor.b > 0.5) {
|
|
1084
|
+
return { r: floor.r * 0.84, g: floor.g * 0.84, b: floor.b * 0.8, a: floor.a };
|
|
1085
|
+
}
|
|
1086
|
+
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 };
|
|
1087
|
+
}
|
|
1088
|
+
function isFloor(e) {
|
|
1089
|
+
return e.style.fillStyle === "grid" && e.style.fill.a > 0 && ["rectangle", "ellipse", "diamond", "path"].includes(e.kind);
|
|
1090
|
+
}
|
|
1091
|
+
function hash01(i, j, salt) {
|
|
1092
|
+
let h = (Math.imul(i, 668265261) ^ Math.imul(j, 374761393) ^ Math.imul(salt, 2654435761)) >>> 0;
|
|
1093
|
+
h = Math.imul(h ^ h >>> 15, 2246822507) >>> 0;
|
|
1094
|
+
h = Math.imul(h ^ h >>> 13, 3266489909) >>> 0;
|
|
1095
|
+
h = (h ^ h >>> 16) >>> 0;
|
|
1096
|
+
return h / 4294967296;
|
|
1097
|
+
}
|
|
1098
|
+
function spot(i, j, cell) {
|
|
1099
|
+
const s = cell / SUB;
|
|
1100
|
+
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 };
|
|
1101
|
+
}
|
|
1102
|
+
function clusterSize(i, j, at, cell) {
|
|
1103
|
+
let nearest2 = Infinity;
|
|
1104
|
+
for (let di = -1; di <= 1; di++) {
|
|
1105
|
+
for (let dj = -1; dj <= 1; dj++) {
|
|
1106
|
+
if (di === 0 && dj === 0) continue;
|
|
1107
|
+
const o = spot(i + di, j + dj, cell);
|
|
1108
|
+
nearest2 = Math.min(nearest2, Math.hypot(o.x - at.x, o.y - at.y));
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return nearest2 * GROW;
|
|
1112
|
+
}
|
|
1113
|
+
function clusterStrokes(c, angle2, size) {
|
|
1114
|
+
const dx = Math.cos(angle2), dy = Math.sin(angle2);
|
|
1115
|
+
const half = size * 0.8, gap = size * 0.5;
|
|
1116
|
+
return [-1, 0, 1].map((k) => {
|
|
1117
|
+
const cx = c.x - dy * gap * k, cy = c.y + dx * gap * k;
|
|
1118
|
+
return [{ x: cx - dx * half, y: cy - dy * half }, { x: cx + dx * half, y: cy + dy * half }];
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
function contains(contours, p) {
|
|
1122
|
+
let inside = false;
|
|
1123
|
+
for (const poly of contours) {
|
|
1124
|
+
if (poly.length < 3) continue;
|
|
1125
|
+
let k = poly.length - 1;
|
|
1126
|
+
for (let i = 0; i < poly.length; i++) {
|
|
1127
|
+
const a = poly[i], b = poly[k];
|
|
1128
|
+
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;
|
|
1129
|
+
k = i;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
return inside;
|
|
1133
|
+
}
|
|
1134
|
+
function distance(p, contours) {
|
|
1135
|
+
let best = Infinity;
|
|
1136
|
+
for (const poly of contours) {
|
|
1137
|
+
if (poly.length < 2) continue;
|
|
1138
|
+
for (let i = 0; i < poly.length; i++) {
|
|
1139
|
+
const a = poly[i], b = poly[(i + 1) % poly.length];
|
|
1140
|
+
const dx = b.x - a.x, dy = b.y - a.y;
|
|
1141
|
+
const len2 = dx * dx + dy * dy;
|
|
1142
|
+
const t = len2 > 0 ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2)) : 0;
|
|
1143
|
+
best = Math.min(best, Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy)));
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return best;
|
|
1147
|
+
}
|
|
1148
|
+
var containsPoint = (r, p) => p.x >= r.x && p.x < r.x + r.width && p.y >= r.y && p.y < r.y + r.height;
|
|
1149
|
+
var rockCache = /* @__PURE__ */ new Map();
|
|
1150
|
+
function rock(floors) {
|
|
1151
|
+
if (floors.length === 0) return [];
|
|
1152
|
+
const key = floors.map((e) => [
|
|
1153
|
+
e.kind,
|
|
1154
|
+
e.x,
|
|
1155
|
+
e.y,
|
|
1156
|
+
e.width,
|
|
1157
|
+
e.height,
|
|
1158
|
+
e.rotation ?? 0,
|
|
1159
|
+
cellSize(e),
|
|
1160
|
+
JSON.stringify(e.subpaths ?? null),
|
|
1161
|
+
e.style.stroke.r,
|
|
1162
|
+
e.style.stroke.g,
|
|
1163
|
+
e.style.stroke.b,
|
|
1164
|
+
e.style.stroke.a,
|
|
1165
|
+
e.style.roughness,
|
|
1166
|
+
e.style.strokeWidth
|
|
1167
|
+
].join(",")).join(";");
|
|
1168
|
+
const hit = rockCache.get(key);
|
|
1169
|
+
if (hit) return hit;
|
|
1170
|
+
const v = computeRock(floors);
|
|
1171
|
+
if (rockCache.size >= 16) rockCache.clear();
|
|
1172
|
+
rockCache.set(key, v);
|
|
1173
|
+
return v;
|
|
1174
|
+
}
|
|
1175
|
+
function computeRock(elements) {
|
|
1176
|
+
const floors = elements.map((e) => {
|
|
1177
|
+
const contours = fillPolygons(e).map((c) => c.map((p) => rotated(e, p)));
|
|
1178
|
+
const pts = contours.flat();
|
|
1179
|
+
const xs = pts.map((p) => p.x), ys = pts.map((p) => p.y);
|
|
1180
|
+
const x0 = xs.length ? Math.min(...xs) : 0, y0 = ys.length ? Math.min(...ys) : 0;
|
|
1181
|
+
const box = { x: x0, y: y0, width: (xs.length ? Math.max(...xs) : 0) - x0, height: (ys.length ? Math.max(...ys) : 0) - y0 };
|
|
1182
|
+
return { contours, box, cell: cellSize(e), element: e };
|
|
1183
|
+
});
|
|
1184
|
+
const strokes = floors.map(() => []);
|
|
1185
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1186
|
+
for (const f of floors) {
|
|
1187
|
+
const subSize = f.cell / SUB;
|
|
1188
|
+
const around = inset(f.box, -f.cell, -f.cell);
|
|
1189
|
+
const i0 = Math.floor(around.x / subSize), i1 = Math.ceil((around.x + around.width) / subSize);
|
|
1190
|
+
const j0 = Math.floor(around.y / subSize), j1 = Math.ceil((around.y + around.height) / subSize);
|
|
1191
|
+
const reach = BAND * f.cell;
|
|
1192
|
+
for (let i = i0; i < i1; i++) {
|
|
1193
|
+
for (let j = j0; j < j1; j++) {
|
|
1194
|
+
const k = `${f.cell}|${i}|${j}`;
|
|
1195
|
+
if (seen.has(k)) continue;
|
|
1196
|
+
seen.add(k);
|
|
1197
|
+
const at = spot(i, j, f.cell);
|
|
1198
|
+
let nearest2 = Infinity, owner = -1;
|
|
1199
|
+
for (let n = 0; n < floors.length; n++) {
|
|
1200
|
+
const g = floors[n];
|
|
1201
|
+
if (!containsPoint(inset(g.box, -reach, -reach), at)) continue;
|
|
1202
|
+
if (contains(g.contours, at)) {
|
|
1203
|
+
nearest2 = 0;
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
const d = distance(at, g.contours);
|
|
1207
|
+
if (d < nearest2) {
|
|
1208
|
+
nearest2 = d;
|
|
1209
|
+
owner = n;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
if (!(nearest2 > 0 && nearest2 <= reach && owner >= 0)) continue;
|
|
1213
|
+
const angle2 = hash01(i, j, 3) * Math.PI;
|
|
1214
|
+
const size = clusterSize(i, j, at, f.cell);
|
|
1215
|
+
const seed = Math.trunc(hash01(i, j, 4) * 4294967296) >>> 0;
|
|
1216
|
+
for (const [a, b] of clusterStrokes(at, angle2, size)) strokes[owner].push([a, b, seed]);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
const out = [];
|
|
1221
|
+
floors.forEach((f, n) => {
|
|
1222
|
+
if (strokes[n].length === 0) return;
|
|
1223
|
+
const e = f.element;
|
|
1224
|
+
const path = new Path();
|
|
1225
|
+
for (const [a, b, seed] of strokes[n]) {
|
|
1226
|
+
if (e.style.roughness <= 0) path.moveTo(a).lineTo(b);
|
|
1227
|
+
else line(a, b, new SeededRNG(seed), e.style.roughness * 0.35, 0.2, false, path);
|
|
1228
|
+
}
|
|
1229
|
+
out.push({ ink: { ...e.style.stroke, a: e.style.stroke.a * 0.6 }, width: Math.max(0.8, e.style.strokeWidth * 0.45), path });
|
|
1230
|
+
});
|
|
1231
|
+
return out;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
980
1234
|
// src/shapes.ts
|
|
981
1235
|
var Cache = class {
|
|
982
1236
|
constructor(limit = 6e3) {
|
|
@@ -1229,196 +1483,566 @@ function computeConnector(e, index) {
|
|
|
1229
1483
|
return [path, solid];
|
|
1230
1484
|
}
|
|
1231
1485
|
|
|
1232
|
-
// src/
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
for (const poly of polys) {
|
|
1252
|
-
for (let i = 0; i < poly.length; i++) {
|
|
1253
|
-
const a = poly[i], b = poly[(i + 1) % poly.length];
|
|
1254
|
-
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));
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
xs.sort((p, q) => p - q);
|
|
1258
|
-
for (let k = 0; k + 1 < xs.length; k += 2) {
|
|
1259
|
-
if (xs[k + 1] - xs[k] > 0.5) out.push([flip({ x: xs[k], y }), flip({ x: xs[k + 1], y })]);
|
|
1260
|
-
}
|
|
1486
|
+
// src/timeline.ts
|
|
1487
|
+
function applyEase(ease, u) {
|
|
1488
|
+
switch (ease) {
|
|
1489
|
+
case "linear":
|
|
1490
|
+
return u;
|
|
1491
|
+
case "easeIn":
|
|
1492
|
+
return u * u * u;
|
|
1493
|
+
case "easeOut":
|
|
1494
|
+
return 1 - Math.pow(1 - u, 3);
|
|
1495
|
+
case "easeInOut":
|
|
1496
|
+
return u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
|
|
1497
|
+
case "hold":
|
|
1498
|
+
return 0;
|
|
1499
|
+
case "spring":
|
|
1500
|
+
return spring(u);
|
|
1501
|
+
case "bounce":
|
|
1502
|
+
return bounce(u);
|
|
1503
|
+
case "elastic":
|
|
1504
|
+
return elastic(u);
|
|
1261
1505
|
}
|
|
1262
|
-
return out;
|
|
1263
1506
|
}
|
|
1264
|
-
function
|
|
1265
|
-
if (
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
return
|
|
1507
|
+
function spring(u) {
|
|
1508
|
+
if (u <= 0) return 0;
|
|
1509
|
+
if (u >= 1) return 1;
|
|
1510
|
+
const s = (u2) => 1 - Math.exp(-6 * u2) * (Math.cos(3 * Math.PI * u2) + 6 / (3 * Math.PI) * Math.sin(3 * Math.PI * u2));
|
|
1511
|
+
return s(u) + u * (1 - s(1));
|
|
1269
1512
|
}
|
|
1270
|
-
function
|
|
1271
|
-
|
|
1513
|
+
function bounce(u) {
|
|
1514
|
+
if (u <= 0) return 0;
|
|
1515
|
+
if (u >= 1) return 1;
|
|
1516
|
+
const n = 7.5625, d = 2.75;
|
|
1517
|
+
if (u < 1 / d) return n * u * u;
|
|
1518
|
+
if (u < 2 / d) {
|
|
1519
|
+
const v2 = u - 1.5 / d;
|
|
1520
|
+
return n * v2 * v2 + 0.75;
|
|
1521
|
+
}
|
|
1522
|
+
if (u < 2.5 / d) {
|
|
1523
|
+
const v2 = u - 2.25 / d;
|
|
1524
|
+
return n * v2 * v2 + 0.9375;
|
|
1525
|
+
}
|
|
1526
|
+
const v = u - 2.625 / d;
|
|
1527
|
+
return n * v * v + 0.984375;
|
|
1272
1528
|
}
|
|
1273
|
-
function
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
h = (h ^ h >>> 16) >>> 0;
|
|
1278
|
-
return h / 4294967296;
|
|
1529
|
+
function elastic(u) {
|
|
1530
|
+
if (u <= 0) return 0;
|
|
1531
|
+
if (u >= 1) return 1;
|
|
1532
|
+
return Math.pow(2, -10 * u) * Math.sin((u * 10 - 0.75) * (2 * Math.PI / 3)) + 1;
|
|
1279
1533
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1534
|
+
var rem = (a, b) => a % b;
|
|
1535
|
+
function localTime(track, t) {
|
|
1536
|
+
const reps = (track.repeats ?? []).filter(repeatIsValid).slice().sort((a, b) => a.start - b.start);
|
|
1537
|
+
if (reps.length === 0) return t;
|
|
1538
|
+
for (const r of reps) {
|
|
1539
|
+
if (!(t >= r.end)) continue;
|
|
1540
|
+
const length = r.end - r.start;
|
|
1541
|
+
if (r.count === null) return r.start + rem(t - r.start, length);
|
|
1542
|
+
const played = length * r.count;
|
|
1543
|
+
if (t < r.start + played) return r.start + rem(t - r.start, length);
|
|
1544
|
+
t -= played - length;
|
|
1545
|
+
}
|
|
1546
|
+
return t;
|
|
1283
1547
|
}
|
|
1284
|
-
function
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1548
|
+
function value(track, t, get, base, lerp) {
|
|
1549
|
+
t = localTime(track, t);
|
|
1550
|
+
let before, after;
|
|
1551
|
+
for (const k of track.keys) {
|
|
1552
|
+
if (get(k) === void 0) continue;
|
|
1553
|
+
if (k.t <= t) before = k;
|
|
1554
|
+
else {
|
|
1555
|
+
after = k;
|
|
1556
|
+
break;
|
|
1291
1557
|
}
|
|
1292
1558
|
}
|
|
1293
|
-
|
|
1559
|
+
if (!before && !after) return base;
|
|
1560
|
+
if (before && !after) return get(before);
|
|
1561
|
+
if (!before && after) return get(after);
|
|
1562
|
+
const b = before, a = after;
|
|
1563
|
+
const u = a.t - b.t > 0 ? (t - b.t) / (a.t - b.t) : 1;
|
|
1564
|
+
return lerp(get(b), get(a), applyEase(b.ease ?? "linear", u));
|
|
1294
1565
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1566
|
+
var lerpNum = (a, b, u) => a + (b - a) * u;
|
|
1567
|
+
var mix01 = (a, b, u) => Math.min(Math.max(a + (b - a) * u, 0), 1);
|
|
1568
|
+
var lerpColor = (a, b, u) => ({
|
|
1569
|
+
r: mix01(a.r, b.r, u),
|
|
1570
|
+
g: mix01(a.g, b.g, u),
|
|
1571
|
+
b: mix01(a.b, b.b, u),
|
|
1572
|
+
a: mix01(a.a, b.a, u)
|
|
1573
|
+
});
|
|
1574
|
+
function num2(track, t, p, base) {
|
|
1575
|
+
return value(track, t, (k) => k[p], base, lerpNum);
|
|
1302
1576
|
}
|
|
1303
|
-
function
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
return inside;
|
|
1577
|
+
function isChanging(track, t) {
|
|
1578
|
+
t = localTime(track, t);
|
|
1579
|
+
const changing = (get, eq) => {
|
|
1580
|
+
let b;
|
|
1581
|
+
for (const k of track.keys) if (k.t <= t && get(k) !== void 0) b = k;
|
|
1582
|
+
const a = track.keys.find((k) => k.t > t && get(k) !== void 0);
|
|
1583
|
+
if (!b || !a) return false;
|
|
1584
|
+
return b.ease !== "hold" && !eq(get(b), get(a));
|
|
1585
|
+
};
|
|
1586
|
+
const n = (p) => changing((k) => k[p], (a, b) => a === b);
|
|
1587
|
+
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");
|
|
1315
1588
|
}
|
|
1316
|
-
function
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1589
|
+
function isVisible(track, t) {
|
|
1590
|
+
t = localTime(track, t);
|
|
1591
|
+
const keyed = track.keys.filter((k) => k.visible !== void 0);
|
|
1592
|
+
if (keyed.length === 0) return true;
|
|
1593
|
+
let last;
|
|
1594
|
+
for (const k of keyed) if (k.t <= t) last = k;
|
|
1595
|
+
return last ? last.visible : !keyed[0].visible;
|
|
1596
|
+
}
|
|
1597
|
+
function applyTrack(track, e, t) {
|
|
1598
|
+
e = cloneElement(e);
|
|
1599
|
+
if (track.keys.some((k) => k.reveal !== void 0)) {
|
|
1600
|
+
const r = num2(track, t, "reveal", e.reveal ?? 1);
|
|
1601
|
+
e.reveal = r >= 1 ? null : Math.max(0, r);
|
|
1327
1602
|
}
|
|
1328
|
-
|
|
1603
|
+
if (track.keys.some((k) => k.opacity !== void 0)) {
|
|
1604
|
+
const o = Math.min(Math.max(num2(track, t, "opacity", e.style.opacity ?? 1), 0), 1);
|
|
1605
|
+
e.style.opacity = o >= 1 ? null : o;
|
|
1606
|
+
}
|
|
1607
|
+
if (e.kind === "connector") return e;
|
|
1608
|
+
e.x = num2(track, t, "x", e.x);
|
|
1609
|
+
e.y = num2(track, t, "y", e.y);
|
|
1610
|
+
e.width = Math.max(0, num2(track, t, "width", e.width));
|
|
1611
|
+
e.height = Math.max(0, num2(track, t, "height", e.height));
|
|
1612
|
+
if (track.keys.some((k) => k.rotation !== void 0)) e.rotation = num2(track, t, "rotation", angle(e));
|
|
1613
|
+
e.style.strokeWidth = Math.max(0, num2(track, t, "strokeWidth", e.style.strokeWidth));
|
|
1614
|
+
e.style.stroke = value(track, t, (k) => k.stroke, e.style.stroke, lerpColor);
|
|
1615
|
+
e.style.fill = value(track, t, (k) => k.fill, e.style.fill, lerpColor);
|
|
1616
|
+
return e;
|
|
1329
1617
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
if (
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
e.width,
|
|
1339
|
-
e.height,
|
|
1340
|
-
e.rotation ?? 0,
|
|
1341
|
-
cellSize(e),
|
|
1342
|
-
JSON.stringify(e.subpaths ?? null),
|
|
1343
|
-
e.style.stroke.r,
|
|
1344
|
-
e.style.stroke.g,
|
|
1345
|
-
e.style.stroke.b,
|
|
1346
|
-
e.style.stroke.a,
|
|
1347
|
-
e.style.roughness,
|
|
1348
|
-
e.style.strokeWidth
|
|
1349
|
-
].join(",")).join(";");
|
|
1350
|
-
const hit = rockCache.get(key);
|
|
1351
|
-
if (hit) return hit;
|
|
1352
|
-
const v = computeRock(floors);
|
|
1353
|
-
if (rockCache.size >= 16) rockCache.clear();
|
|
1354
|
-
rockCache.set(key, v);
|
|
1355
|
-
return v;
|
|
1618
|
+
function followSpan(track, t) {
|
|
1619
|
+
t = localTime(track, t);
|
|
1620
|
+
let f = null;
|
|
1621
|
+
for (const s of track.follow ?? []) if (s.start <= t && (f === null || s.start > f.start)) f = s;
|
|
1622
|
+
if (!f) return null;
|
|
1623
|
+
const span = f;
|
|
1624
|
+
if (track.keys.some((k) => k.t >= span.end && k.t <= t && (k.x !== void 0 || k.y !== void 0))) return null;
|
|
1625
|
+
return f;
|
|
1356
1626
|
}
|
|
1357
|
-
function
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
const box = { x: x0, y: y0, width: (xs.length ? Math.max(...xs) : 0) - x0, height: (ys.length ? Math.max(...ys) : 0) - y0 };
|
|
1364
|
-
return { contours, box, cell: cellSize(e), element: e };
|
|
1365
|
-
});
|
|
1366
|
-
const strokes = floors.map(() => []);
|
|
1367
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1368
|
-
for (const f of floors) {
|
|
1369
|
-
const subSize = f.cell / SUB;
|
|
1370
|
-
const around = inset(f.box, -f.cell, -f.cell);
|
|
1371
|
-
const i0 = Math.floor(around.x / subSize), i1 = Math.ceil((around.x + around.width) / subSize);
|
|
1372
|
-
const j0 = Math.floor(around.y / subSize), j1 = Math.ceil((around.y + around.height) / subSize);
|
|
1373
|
-
const reach = BAND * f.cell;
|
|
1374
|
-
for (let i = i0; i < i1; i++) {
|
|
1375
|
-
for (let j = j0; j < j1; j++) {
|
|
1376
|
-
const k = `${f.cell}|${i}|${j}`;
|
|
1377
|
-
if (seen.has(k)) continue;
|
|
1378
|
-
seen.add(k);
|
|
1379
|
-
const at = spot(i, j, f.cell);
|
|
1380
|
-
let nearest2 = Infinity, owner = -1;
|
|
1381
|
-
for (let n = 0; n < floors.length; n++) {
|
|
1382
|
-
const g = floors[n];
|
|
1383
|
-
if (!containsPoint(inset(g.box, -reach, -reach), at)) continue;
|
|
1384
|
-
if (contains(g.contours, at)) {
|
|
1385
|
-
nearest2 = 0;
|
|
1386
|
-
break;
|
|
1387
|
-
}
|
|
1388
|
-
const d = distance(at, g.contours);
|
|
1389
|
-
if (d < nearest2) {
|
|
1390
|
-
nearest2 = d;
|
|
1391
|
-
owner = n;
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
if (!(nearest2 > 0 && nearest2 <= reach && owner >= 0)) continue;
|
|
1395
|
-
const angle2 = hash01(i, j, 3) * Math.PI;
|
|
1396
|
-
const size = clusterSize(i, j, at, f.cell);
|
|
1397
|
-
const seed = Math.trunc(hash01(i, j, 4) * 4294967296) >>> 0;
|
|
1398
|
-
for (const [a, b] of clusterStrokes(at, angle2, size)) strokes[owner].push([a, b, seed]);
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
const out = [];
|
|
1403
|
-
floors.forEach((f, n) => {
|
|
1404
|
-
if (strokes[n].length === 0) return;
|
|
1405
|
-
const e = f.element;
|
|
1406
|
-
const path = new Path();
|
|
1407
|
-
for (const [a, b, seed] of strokes[n]) {
|
|
1408
|
-
if (e.style.roughness <= 0) path.moveTo(a).lineTo(b);
|
|
1409
|
-
else line(a, b, new SeededRNG(seed), e.style.roughness * 0.35, 0.2, false, path);
|
|
1410
|
-
}
|
|
1411
|
-
out.push({ ink: { ...e.style.stroke, a: e.style.stroke.a * 0.6 }, width: Math.max(0.8, e.style.strokeWidth * 0.45), path });
|
|
1412
|
-
});
|
|
1413
|
-
return out;
|
|
1627
|
+
function tracing(e, page) {
|
|
1628
|
+
if (e.kind === "connector") return new Polyline(routePoints(route(e, indexOf(page))));
|
|
1629
|
+
const clean = cloneElement(e);
|
|
1630
|
+
clean.style.roughness = 0;
|
|
1631
|
+
const pts = outline(clean).contours()[0] ?? [];
|
|
1632
|
+
return new Polyline(angle(e) === 0 ? pts : pts.map((p) => rotated(e, p)));
|
|
1414
1633
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1634
|
+
function following(track, e, t, page) {
|
|
1635
|
+
const f = followSpan(track, t);
|
|
1636
|
+
if (!f) return e;
|
|
1637
|
+
t = localTime(track, t);
|
|
1638
|
+
const pathElement = page.elements.find((x) => x.id === f.path);
|
|
1639
|
+
if (!pathElement || pathElement.id === e.id) return e;
|
|
1640
|
+
const line2 = tracing(pathElement, page);
|
|
1641
|
+
const span = f.end - f.start;
|
|
1642
|
+
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);
|
|
1643
|
+
if (u > 1) u = rem(u, 1) === 0 ? 1 : rem(u, 1);
|
|
1644
|
+
if (f.reverse === true) u = 1 - u;
|
|
1645
|
+
const p = line2.point(u);
|
|
1646
|
+
const moved = cloneElement(e);
|
|
1647
|
+
moved.x = p.x - e.width / 2;
|
|
1648
|
+
moved.y = p.y - e.height / 2;
|
|
1649
|
+
if (f.orient === true) {
|
|
1650
|
+
const step = f.reverse === true ? -0.01 : 0.01;
|
|
1651
|
+
const a = line2.point(u - step / 2), b = line2.point(u + step / 2);
|
|
1652
|
+
if (a.x !== b.x || a.y !== b.y) moved.rotation = Math.atan2(b.y - a.y, b.x - a.x);
|
|
1653
|
+
}
|
|
1654
|
+
return moved;
|
|
1655
|
+
}
|
|
1656
|
+
function marchOffset(tl, speed, period, t) {
|
|
1657
|
+
if (!(period > 0)) return 0;
|
|
1658
|
+
const d = clampTime(tl.duration);
|
|
1659
|
+
if (d > 0) {
|
|
1660
|
+
const laps = roundHalfAway(speed * d / period);
|
|
1661
|
+
if (laps !== 0) speed = laps * period / d;
|
|
1662
|
+
}
|
|
1663
|
+
const r = rem(speed * t, period);
|
|
1664
|
+
return r < 0 ? -r : r === 0 ? 0 : period - r;
|
|
1665
|
+
}
|
|
1666
|
+
function trackMap(tl) {
|
|
1667
|
+
const m = /* @__PURE__ */ new Map();
|
|
1668
|
+
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
1669
|
+
return m;
|
|
1670
|
+
}
|
|
1671
|
+
function resolved(page, t) {
|
|
1672
|
+
return expandingSymbols(posed(page, t), clampTime(t));
|
|
1673
|
+
}
|
|
1674
|
+
function posed(page, t) {
|
|
1675
|
+
const timeline = page.timeline;
|
|
1676
|
+
if (!timeline) return page;
|
|
1677
|
+
t = clampTime(t);
|
|
1678
|
+
const tracks = trackMap(timeline);
|
|
1679
|
+
const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
|
|
1680
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
1681
|
+
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
1682
|
+
let moving = null;
|
|
1683
|
+
if (timeline.settle === true) {
|
|
1684
|
+
moving = /* @__PURE__ */ new Set();
|
|
1685
|
+
for (const tr of tracks.values()) if (isChanging(tr, t)) moving.add(tr.element);
|
|
1686
|
+
}
|
|
1687
|
+
const boils = (e) => {
|
|
1688
|
+
const override = tracks.get(e.id)?.boil;
|
|
1689
|
+
if (override !== void 0 && override !== null) return override;
|
|
1690
|
+
if (!moving) return true;
|
|
1691
|
+
if (e.connector) return [e.connector.start.elementID, e.connector.end.elementID].some((id) => id !== null && moving.has(id));
|
|
1692
|
+
return moving.has(e.id);
|
|
1693
|
+
};
|
|
1694
|
+
let elements = page.elements.map((e) => {
|
|
1695
|
+
const track = tracks.get(e.id);
|
|
1696
|
+
let out = track ? applyTrack(track, e, t) : e;
|
|
1697
|
+
if (reseed !== null && reseed > 0 && boils(e)) {
|
|
1698
|
+
if (out === e) out = cloneElement(e);
|
|
1699
|
+
out.seed = boiled(e.seed, reseed);
|
|
1700
|
+
}
|
|
1701
|
+
const pattern = track?.march != null ? dashPattern(out.style.dash, out.style.strokeWidth) : null;
|
|
1702
|
+
if (track?.march != null && pattern) {
|
|
1703
|
+
if (out === e) out = cloneElement(e);
|
|
1704
|
+
out.style.dashOffset = marchOffset(timeline, track.march, pattern.reduce((a, b) => a + b, 0), t);
|
|
1705
|
+
}
|
|
1706
|
+
return out;
|
|
1707
|
+
});
|
|
1708
|
+
elements = elements.map((e, i) => hidden.has(e.id) ? page.elements[i] : e);
|
|
1709
|
+
const posed2 = { ...page, elements, timeline: null };
|
|
1710
|
+
elements = elements.map((e) => {
|
|
1711
|
+
const track = tracks.get(e.id);
|
|
1712
|
+
return track ? following(track, e, t, posed2) : e;
|
|
1713
|
+
});
|
|
1714
|
+
const current = { ...page, elements, timeline: null };
|
|
1715
|
+
const index = indexOf(current);
|
|
1716
|
+
const gone = new Set(hidden);
|
|
1717
|
+
elements = elements.map((e) => {
|
|
1718
|
+
const c = e.connector;
|
|
1719
|
+
if (!c || ![c.start.elementID, c.end.elementID].some((id) => id !== null && hidden.has(id))) return e;
|
|
1720
|
+
if (!(tracks.get(e.id)?.keys.some((k) => k.visible !== void 0) ?? false)) {
|
|
1721
|
+
gone.add(e.id);
|
|
1722
|
+
return e;
|
|
1723
|
+
}
|
|
1724
|
+
const [a, b] = endpoints(e, index);
|
|
1725
|
+
const pinned = cloneElement(e);
|
|
1726
|
+
pinned.connector.start = { elementID: null, port: c.start.port, x: a.x, y: a.y };
|
|
1727
|
+
pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
|
|
1728
|
+
return pinned;
|
|
1729
|
+
});
|
|
1730
|
+
return { ...page, elements: elements.filter((e) => !gone.has(e.id)), timeline: null };
|
|
1731
|
+
}
|
|
1732
|
+
function posterTime(page) {
|
|
1733
|
+
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
1734
|
+
}
|
|
1735
|
+
function still(page) {
|
|
1736
|
+
return page.timeline ? resolved(page, posterTime(page)) : expandingSymbols(page, 0);
|
|
1737
|
+
}
|
|
1738
|
+
function cameraRect(tl, t) {
|
|
1739
|
+
const keys = tl.camera ?? [];
|
|
1740
|
+
if (keys.length === 0) return null;
|
|
1741
|
+
let before;
|
|
1742
|
+
for (const k of keys) if (k.t <= t) before = k;
|
|
1743
|
+
const after = keys.find((k) => k.t > t);
|
|
1744
|
+
if (!before) return rectOf(keys[0]);
|
|
1745
|
+
if (!after) return rectOf(before);
|
|
1746
|
+
const u = applyEase(before.ease ?? "easeInOut", (t - before.t) / Math.max(after.t - before.t, 1e-9));
|
|
1747
|
+
const mix3 = (p, q) => p + (q - p) * u;
|
|
1748
|
+
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)) };
|
|
1749
|
+
}
|
|
1750
|
+
var rectOf = (k) => ({ x: k.x, y: k.y, width: k.width, height: k.height });
|
|
1751
|
+
function cameraView(tl, t) {
|
|
1752
|
+
const first = tl.camera?.[0];
|
|
1753
|
+
const r = cameraRect(tl, t);
|
|
1754
|
+
if (!first || !r || !(r.width > 0 && r.height > 0)) return null;
|
|
1755
|
+
return fit(r, first.width / first.height);
|
|
1756
|
+
}
|
|
1757
|
+
function fit(r, aspect) {
|
|
1758
|
+
if (!(r.width > 0 && r.height > 0 && aspect > 0)) return r;
|
|
1759
|
+
let w = r.width, h = r.height;
|
|
1760
|
+
if (r.width / r.height < aspect) w = r.height * aspect;
|
|
1761
|
+
else h = r.width / aspect;
|
|
1762
|
+
return { x: r.x + r.width / 2 - w / 2, y: r.y + r.height / 2 - h / 2, width: w, height: h };
|
|
1763
|
+
}
|
|
1764
|
+
function frameCount(tl) {
|
|
1765
|
+
return Math.max(1, Math.ceil(clampTime(tl.duration) * clampFPS(tl.fps)));
|
|
1766
|
+
}
|
|
1767
|
+
function frameTimes(tl) {
|
|
1768
|
+
const fps = clampFPS(tl.fps);
|
|
1769
|
+
const times = [];
|
|
1770
|
+
for (let i = 0; i < frameCount(tl); i++) times.push(i / fps);
|
|
1771
|
+
const last = times[times.length - 1];
|
|
1772
|
+
if (tl.duration - last > 1e-6) times.push(tl.duration);
|
|
1773
|
+
return times;
|
|
1774
|
+
}
|
|
1775
|
+
function sceneAt(tl, t) {
|
|
1776
|
+
let s = -1;
|
|
1777
|
+
(tl.markers ?? []).forEach((m, i) => {
|
|
1778
|
+
if (m.t <= t) s = i;
|
|
1779
|
+
});
|
|
1780
|
+
return s;
|
|
1781
|
+
}
|
|
1782
|
+
function transientElements(page) {
|
|
1783
|
+
const tl = page.timeline;
|
|
1784
|
+
if (!tl) return /* @__PURE__ */ new Set();
|
|
1785
|
+
const end = tl.poster ?? tl.duration;
|
|
1786
|
+
return new Set(tl.tracks.filter((tr) => !isVisible(tr, 0) && !isVisible(tr, end)).map((tr) => tr.element));
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// src/symbols.ts
|
|
1790
|
+
var MAX_DEPTH = 8;
|
|
1791
|
+
var MAX_ELEMENTS = 2e4;
|
|
1792
|
+
var EPSILON = 1e-9;
|
|
1793
|
+
function sceneTime(s, state) {
|
|
1794
|
+
const m = (s.timeline?.markers ?? []).find((x) => x.name.toLowerCase() === state);
|
|
1795
|
+
return m ? Math.min(Math.max(m.t, 0), clampTime(s.timeline?.duration ?? 0)) : null;
|
|
1796
|
+
}
|
|
1797
|
+
function buttonTime(s, state) {
|
|
1798
|
+
switch (state) {
|
|
1799
|
+
case "up":
|
|
1800
|
+
return sceneTime(s, "up") ?? 0;
|
|
1801
|
+
case "over":
|
|
1802
|
+
return sceneTime(s, "over") ?? buttonTime(s, "up");
|
|
1803
|
+
case "down":
|
|
1804
|
+
return sceneTime(s, "down") ?? buttonTime(s, "over");
|
|
1805
|
+
default:
|
|
1806
|
+
return sceneTime(s, "hit");
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
function symbolTime(s, t, playback, offset2) {
|
|
1810
|
+
const d = clampTime(s.timeline?.duration ?? 0);
|
|
1811
|
+
if (s.button === true) return buttonTime(s, "up") ?? 0;
|
|
1812
|
+
const o = offset2 !== null && Number.isFinite(offset2) ? offset2 : 0;
|
|
1813
|
+
switch (playback ?? "loop") {
|
|
1814
|
+
case "still":
|
|
1815
|
+
return Math.min(s.timeline?.poster ?? d, d);
|
|
1816
|
+
case "click":
|
|
1817
|
+
return Math.min(Math.max(o, 0), d);
|
|
1818
|
+
case "once":
|
|
1819
|
+
return Math.min(Math.max(t + o, 0), d);
|
|
1820
|
+
default: {
|
|
1821
|
+
if (d <= 0) return 0;
|
|
1822
|
+
const x = t + o;
|
|
1823
|
+
let r = x % d;
|
|
1824
|
+
if (r < 0) r += d;
|
|
1825
|
+
if (x > EPSILON && (r < EPSILON || d - r < EPSILON)) return d;
|
|
1826
|
+
return r;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
function filled(s, args) {
|
|
1831
|
+
const params = s.params;
|
|
1832
|
+
if (!params || Object.keys(params).length === 0) return s;
|
|
1833
|
+
const values = new Map(Object.entries(params));
|
|
1834
|
+
for (const [k, v] of Object.entries(args ?? {})) if (values.has(k)) values.set(k, v);
|
|
1835
|
+
const fill = (x) => !x.includes("{{") ? x : x.replace(/\{\{\s*([^{}]*?)\s*\}\}/g, (m, name) => values.get(name) ?? m);
|
|
1836
|
+
return { ...s, elements: s.elements.map((x) => {
|
|
1837
|
+
if (x.text === null && x.args === null) return x;
|
|
1838
|
+
const e = cloneElement(x);
|
|
1839
|
+
if (e.text !== null) e.text = fill(e.text);
|
|
1840
|
+
if (e.args !== null) e.args = Object.fromEntries(Object.entries(e.args).map(([k, v]) => [k, fill(v)]));
|
|
1841
|
+
return e;
|
|
1842
|
+
}) };
|
|
1843
|
+
}
|
|
1844
|
+
function symbolPage(s) {
|
|
1845
|
+
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null };
|
|
1846
|
+
}
|
|
1847
|
+
function hasInstances(page) {
|
|
1848
|
+
return page.elements.some((e) => e.kind === "symbol");
|
|
1849
|
+
}
|
|
1850
|
+
function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map()) {
|
|
1851
|
+
if (!hasInstances(page)) return page;
|
|
1852
|
+
const budget = { left: MAX_ELEMENTS };
|
|
1853
|
+
return { ...page, elements: expand(page.elements, page, t, symbolTable(page), [], posing, budget, times) };
|
|
1854
|
+
}
|
|
1855
|
+
function symbolTable(page) {
|
|
1856
|
+
const table = /* @__PURE__ */ new Map();
|
|
1857
|
+
for (const s of page.symbols ?? []) if (!table.has(s.id)) table.set(s.id, s);
|
|
1858
|
+
return table;
|
|
1859
|
+
}
|
|
1860
|
+
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map()) {
|
|
1861
|
+
const instances = new Set(elements.filter((e) => e.kind === "symbol").map((e) => e.id));
|
|
1862
|
+
if (instances.size === 0) {
|
|
1863
|
+
budget.left -= elements.length;
|
|
1864
|
+
return elements;
|
|
1865
|
+
}
|
|
1866
|
+
const index = indexOf(page);
|
|
1867
|
+
const out = [];
|
|
1868
|
+
for (const e of elements) {
|
|
1869
|
+
if (budget.left <= 0) break;
|
|
1870
|
+
const c = e.connector;
|
|
1871
|
+
if (e.kind === "connector" && c && [c.start.elementID, c.end.elementID].some((id2) => id2 !== null && instances.has(id2))) {
|
|
1872
|
+
const [a, b] = endpoints(e, index);
|
|
1873
|
+
const pinned = cloneElement(e);
|
|
1874
|
+
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 };
|
|
1875
|
+
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 };
|
|
1876
|
+
out.push(pinned);
|
|
1877
|
+
budget.left -= 1;
|
|
1878
|
+
continue;
|
|
1879
|
+
}
|
|
1880
|
+
if (e.kind !== "symbol") {
|
|
1881
|
+
out.push(e);
|
|
1882
|
+
budget.left -= 1;
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
budget.left -= 1;
|
|
1886
|
+
const id = e.symbol;
|
|
1887
|
+
const symbol = id === null ? void 0 : table.get(id);
|
|
1888
|
+
if (id === null || !symbol || stack.includes(id) || stack.length >= MAX_DEPTH) continue;
|
|
1889
|
+
const override = stack.length === 0 ? times.get(e.id) : void 0;
|
|
1890
|
+
const u = override !== void 0 ? Math.min(Math.max(override, 0), clampTime(symbol.timeline?.duration ?? 0)) : symbolTime(symbol, t, e.playback, e.offset);
|
|
1891
|
+
const page2 = symbolPage(filled(symbol, e.args));
|
|
1892
|
+
const inner = posing ? posed(page2, u) : page2;
|
|
1893
|
+
const visible = new Set(inner.layers.filter((l) => l.visible).map((l) => l.id));
|
|
1894
|
+
const drawn = {
|
|
1895
|
+
...inner,
|
|
1896
|
+
elements: orderedElements(inner).filter((x) => visible.has(x.layerID)),
|
|
1897
|
+
layers: [{ id: e.layerID, name: "", visible: true, locked: false }]
|
|
1898
|
+
};
|
|
1899
|
+
for (const x of expand(drawn.elements, drawn, u, table, [...stack, id], posing, budget)) out.push(placed(x, e, symbol));
|
|
1900
|
+
}
|
|
1901
|
+
return out;
|
|
1902
|
+
}
|
|
1903
|
+
function placed(inner, instance, symbol) {
|
|
1904
|
+
const f = { x: instance.x, y: instance.y, width: instance.width, height: instance.height };
|
|
1905
|
+
const sx = f.width / symbol.width, sy = f.height / symbol.height;
|
|
1906
|
+
const s = Math.sqrt(Math.abs(sx * sy));
|
|
1907
|
+
const center2 = { x: f.x + f.width / 2, y: f.y + f.height / 2 };
|
|
1908
|
+
const a = angle(instance);
|
|
1909
|
+
const point = (p) => rotate({ x: f.x + p.x * sx, y: f.y + p.y * sy }, center2, a);
|
|
1910
|
+
const e = cloneElement(inner);
|
|
1911
|
+
e.id = derivedID(instance.id, inner.id);
|
|
1912
|
+
e.layerID = instance.layerID;
|
|
1913
|
+
e.groupID = null;
|
|
1914
|
+
e.link = inner.link === null ? null : derivedID(instance.id, inner.link);
|
|
1915
|
+
e.seed = boiled(inner.seed, instance.seed);
|
|
1916
|
+
e.click = null;
|
|
1917
|
+
e.hover = null;
|
|
1918
|
+
const scaled = { x: f.x + inner.x * sx, y: f.y + inner.y * sy, width: inner.width * sx, height: inner.height * sy };
|
|
1919
|
+
const moved = rotate({ x: scaled.x + scaled.width / 2, y: scaled.y + scaled.height / 2 }, center2, a);
|
|
1920
|
+
e.x = moved.x - scaled.width / 2;
|
|
1921
|
+
e.y = moved.y - scaled.height / 2;
|
|
1922
|
+
e.width = scaled.width;
|
|
1923
|
+
e.height = scaled.height;
|
|
1924
|
+
if (a !== 0 || inner.rotation !== null) e.rotation = angle(inner) + a;
|
|
1925
|
+
e.style.strokeWidth = inner.style.strokeWidth * s;
|
|
1926
|
+
e.style.fontSize = inner.style.fontSize * s;
|
|
1927
|
+
if (inner.style.gridSize !== null) e.style.gridSize = inner.style.gridSize * s;
|
|
1928
|
+
if (instance.style.opacity !== null) e.style.opacity = (inner.style.opacity ?? 1) * instance.style.opacity;
|
|
1929
|
+
if (inner.connector) {
|
|
1930
|
+
const end = (p) => {
|
|
1931
|
+
if (p.elementID !== null) return { elementID: derivedID(instance.id, p.elementID), port: p.port, x: p.x, y: p.y };
|
|
1932
|
+
const q = point(p);
|
|
1933
|
+
return { elementID: null, port: p.port, x: q.x, y: q.y };
|
|
1934
|
+
};
|
|
1935
|
+
e.connector = { ...inner.connector, start: end(inner.connector.start), end: end(inner.connector.end) };
|
|
1936
|
+
}
|
|
1937
|
+
return e;
|
|
1938
|
+
}
|
|
1939
|
+
var MASK = (1n << 64n) - 1n;
|
|
1940
|
+
function mix2(x) {
|
|
1941
|
+
let z = x + 0x9e3779b97f4a7c15n & MASK;
|
|
1942
|
+
z = (z ^ z >> 30n) * 0xbf58476d1ce4e5b9n & MASK;
|
|
1943
|
+
z = (z ^ z >> 27n) * 0x94d049bb133111ebn & MASK;
|
|
1944
|
+
return z ^ z >> 31n;
|
|
1945
|
+
}
|
|
1946
|
+
function halves(id) {
|
|
1947
|
+
const hex = id.replace(/-/g, "").padStart(32, "0").slice(-32);
|
|
1948
|
+
return [BigInt(`0x${hex.slice(0, 16)}`), BigInt(`0x${hex.slice(16)}`)];
|
|
1949
|
+
}
|
|
1950
|
+
function derivedID(instance, inner) {
|
|
1951
|
+
const [a, b] = halves(instance), [c, d] = halves(inner);
|
|
1952
|
+
const hi = mix2(mix2(mix2(mix2(a) ^ b) ^ c) ^ d);
|
|
1953
|
+
const lo = mix2(hi ^ 0x6a09e667f3bcc909n);
|
|
1954
|
+
const h = hi.toString(16).padStart(16, "0") + lo.toString(16).padStart(16, "0");
|
|
1955
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`.toUpperCase();
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// src/clicks.ts
|
|
1959
|
+
function sceneNamed(name, markers) {
|
|
1960
|
+
const exact = markers.findIndex((m) => m.name === name);
|
|
1961
|
+
return exact >= 0 ? exact : markers.findIndex((m) => m.name.toLowerCase() === name.toLowerCase());
|
|
1962
|
+
}
|
|
1963
|
+
function clickTargets(page, file) {
|
|
1964
|
+
const markers = page.timeline?.markers ?? [];
|
|
1965
|
+
const out = /* @__PURE__ */ new Map();
|
|
1966
|
+
let pageOf = null;
|
|
1967
|
+
for (const e of page.elements) {
|
|
1968
|
+
const c = e.click;
|
|
1969
|
+
if (c?.scene) {
|
|
1970
|
+
const i = sceneNamed(c.scene, markers);
|
|
1971
|
+
if (i >= 0) {
|
|
1972
|
+
out.set(e.id, { scene: i });
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
const url = c?.url ? allowedURL(c.url) : null;
|
|
1977
|
+
if (url) {
|
|
1978
|
+
out.set(e.id, { url });
|
|
1979
|
+
continue;
|
|
1980
|
+
}
|
|
1981
|
+
if (e.link && file && file.pages.length > 1) {
|
|
1982
|
+
if (!pageOf) {
|
|
1983
|
+
pageOf = /* @__PURE__ */ new Map();
|
|
1984
|
+
file.pages.forEach((p, i) => {
|
|
1985
|
+
for (const x of p.elements) if (!pageOf.has(x.id)) pageOf.set(x.id, i);
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
const target = pageOf.get(e.link);
|
|
1989
|
+
if (target !== void 0 && file.pages[target] !== page) out.set(e.id, { page: target });
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
return out;
|
|
1993
|
+
}
|
|
1994
|
+
function hoveredElement(e) {
|
|
1995
|
+
const h = e.hover;
|
|
1996
|
+
if (!h) return e;
|
|
1997
|
+
const style = { ...e.style };
|
|
1998
|
+
if (h.stroke) style.stroke = h.stroke;
|
|
1999
|
+
if (h.fill) style.fill = h.fill;
|
|
2000
|
+
if (h.opacity !== null && Number.isFinite(h.opacity)) {
|
|
2001
|
+
const o = Math.min(Math.max(h.opacity, 0), 1);
|
|
2002
|
+
style.opacity = o >= 1 ? null : o;
|
|
2003
|
+
}
|
|
2004
|
+
return { ...e, style };
|
|
2005
|
+
}
|
|
2006
|
+
function hovered(page, ids) {
|
|
2007
|
+
return { ...page, elements: page.elements.map((e) => ids.has(e.id) ? hoveredElement(e) : e) };
|
|
2008
|
+
}
|
|
2009
|
+
function hasHover(e) {
|
|
2010
|
+
return !!e.hover && (e.hover.stroke !== null || e.hover.fill !== null || e.hover.opacity !== null);
|
|
2011
|
+
}
|
|
2012
|
+
function buttonLabel(e, symbolName) {
|
|
2013
|
+
const line2 = (e.text ?? "").split("\n").find((l) => l.length > 0);
|
|
2014
|
+
if (line2) return line2;
|
|
2015
|
+
if (e.anchor) return e.anchor;
|
|
2016
|
+
if (symbolName) return symbolName;
|
|
2017
|
+
return e.kind;
|
|
2018
|
+
}
|
|
2019
|
+
function partsOf(page, instances) {
|
|
2020
|
+
const out = /* @__PURE__ */ new Map();
|
|
2021
|
+
for (const e of page.elements) {
|
|
2022
|
+
if (!instances.has(e.id) || e.kind !== "symbol") continue;
|
|
2023
|
+
const alone = { ...page, layers: [{ id: e.layerID, name: "", visible: true, locked: false }], elements: [e] };
|
|
2024
|
+
for (const part of expandingSymbols(alone, 0, false).elements) out.set(part.id, e.id);
|
|
2025
|
+
}
|
|
2026
|
+
return out;
|
|
2027
|
+
}
|
|
2028
|
+
var CLICK_STYLE = `
|
|
2029
|
+
.dottie-a { cursor: pointer; outline: none; }
|
|
2030
|
+
.dottie-a .dottie-h { display: none; }
|
|
2031
|
+
.dottie-a:hover .dottie-h, .dottie-a.dottie-hover .dottie-h, .dottie-a:focus-visible .dottie-h { display: inline; }
|
|
2032
|
+
.dottie-a:hover .dottie-n, .dottie-a.dottie-hover .dottie-n, .dottie-a:focus-visible .dottie-n { display: none; }
|
|
2033
|
+
.dottie-a:focus-visible { filter: drop-shadow(0 0 3px var(--dottie-focus, #3b82f6)); }
|
|
2034
|
+
.dottie-a:not(.dottie-b):active, .dottie-a.dottie-press:not(.dottie-b) { transform: translate(1px, 1px); }
|
|
2035
|
+
.dottie-a .dottie-d { display: none; }
|
|
2036
|
+
.dottie-b:active .dottie-n, .dottie-b:active .dottie-h, .dottie-b.dottie-press .dottie-n, .dottie-b.dottie-press .dottie-h { display: none; }
|
|
2037
|
+
.dottie-b:active .dottie-d, .dottie-b.dottie-press .dottie-d { display: inline; }
|
|
2038
|
+
`;
|
|
2039
|
+
|
|
2040
|
+
// src/metrics.ts
|
|
2041
|
+
var ascent = 0.951995849609375;
|
|
2042
|
+
var descent = 0.2129974365234375;
|
|
2043
|
+
var leading = 0.0279998779296875;
|
|
2044
|
+
var boldAscent = 0.975006103515625;
|
|
2045
|
+
var boldDescent = 0.2169952392578125;
|
|
1422
2046
|
var boldLeading = 0.0290069580078125;
|
|
1423
2047
|
var italicAscent = 0.957000732421875;
|
|
1424
2048
|
var italicDescent = 0.2129974365234375;
|
|
@@ -1938,476 +2562,177 @@ function measureRun(r) {
|
|
|
1938
2562
|
return w;
|
|
1939
2563
|
}
|
|
1940
2564
|
var TAB_INTERVAL = 28;
|
|
1941
|
-
function layoutLines(paras, width, height, fontSize) {
|
|
1942
|
-
const lines = [];
|
|
1943
|
-
let baseline = 0;
|
|
1944
|
-
let prev = null;
|
|
1945
|
-
paras.forEach((p, pi) => {
|
|
1946
|
-
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;
|
|
1947
|
-
const chars = [];
|
|
1948
|
-
runs.forEach((r, ri) => {
|
|
1949
|
-
for (const ch of r.text) chars.push({ ch, run: ri });
|
|
1950
|
-
});
|
|
1951
|
-
let k = 0;
|
|
1952
|
-
let firstLine = true;
|
|
1953
|
-
while (k < chars.length || firstLine && chars.length === 0) {
|
|
1954
|
-
const start = firstLine ? p.firstIndent : p.indent;
|
|
1955
|
-
const maxX2 = width;
|
|
1956
|
-
const glyphs = [];
|
|
1957
|
-
let x = start, lastSpace = -1, end = k;
|
|
1958
|
-
for (; end < chars.length; end++) {
|
|
1959
|
-
const { ch, run } = chars[end];
|
|
1960
|
-
const r = runs[run];
|
|
1961
|
-
if (ch === SEP || ch === "\n") {
|
|
1962
|
-
glyphs.push({ ch, run, x, w: 0 });
|
|
1963
|
-
end++;
|
|
1964
|
-
break;
|
|
1965
|
-
}
|
|
1966
|
-
if (ch === " ") {
|
|
1967
|
-
const stop = p.tabs.find((t) => t.x > x + 0.01);
|
|
1968
|
-
let tx;
|
|
1969
|
-
if (stop) {
|
|
1970
|
-
if (stop.align === "left") tx = stop.x;
|
|
1971
|
-
else {
|
|
1972
|
-
let seg = 0, prevCh = "";
|
|
1973
|
-
for (let q = end + 1; q < chars.length && chars[q].ch !== " " && chars[q].ch !== "\n" && chars[q].ch !== SEP; q++) {
|
|
1974
|
-
const rr = runs[chars[q].run];
|
|
1975
|
-
seg += charWidth(chars[q].ch, rr) + (prevCh ? kern(prevCh, chars[q].ch, rr) : 0);
|
|
1976
|
-
prevCh = chars[q].ch;
|
|
1977
|
-
}
|
|
1978
|
-
tx = Math.max(x, stop.align === "right" ? stop.x - seg : stop.x - seg / 2);
|
|
1979
|
-
}
|
|
1980
|
-
} else tx = (Math.floor(x / TAB_INTERVAL) + 1) * TAB_INTERVAL;
|
|
1981
|
-
glyphs.push({ ch, run, x, w: tx - x });
|
|
1982
|
-
x = tx;
|
|
1983
|
-
continue;
|
|
1984
|
-
}
|
|
1985
|
-
const prevGlyph = glyphs[glyphs.length - 1];
|
|
1986
|
-
const kx = prevGlyph && prevGlyph.run === run && prevGlyph.ch !== " " ? kern(prevGlyph.ch, ch, r) : 0;
|
|
1987
|
-
if (prevGlyph) {
|
|
1988
|
-
prevGlyph.w += kx;
|
|
1989
|
-
x += kx;
|
|
1990
|
-
}
|
|
1991
|
-
const w = charWidth(ch, r);
|
|
1992
|
-
if (ch !== " " && x + w > maxX2 + 0.01 && glyphs.some((g) => g.ch !== " ")) {
|
|
1993
|
-
if (lastSpace >= 0) {
|
|
1994
|
-
glyphs.length = lastSpace + 1;
|
|
1995
|
-
end = k + lastSpace + 1;
|
|
1996
|
-
}
|
|
1997
|
-
if (prevGlyph) prevGlyph.w -= kx;
|
|
1998
|
-
break;
|
|
1999
|
-
}
|
|
2000
|
-
glyphs.push({ ch, run, x, w });
|
|
2001
|
-
x += w;
|
|
2002
|
-
if (breaksAfter(ch, chars[end + 1]?.ch ?? "")) lastSpace = glyphs.length - 1;
|
|
2003
|
-
}
|
|
2004
|
-
if (glyphs.length === 0 && end === k && k < chars.length) {
|
|
2005
|
-
const { ch, run } = chars[k];
|
|
2006
|
-
glyphs.push({ ch, run, x: start, w: charWidth(ch, runs[run]) });
|
|
2007
|
-
end = k + 1;
|
|
2008
|
-
}
|
|
2009
|
-
let asc = 0, desc = 0, lead = 0;
|
|
2010
|
-
for (const g of glyphs) {
|
|
2011
|
-
const [a, d, l] = metrics(g.ch, runs[g.run]);
|
|
2012
|
-
asc = Math.max(asc, a);
|
|
2013
|
-
desc = Math.max(desc, d);
|
|
2014
|
-
lead = Math.max(lead, l);
|
|
2015
|
-
}
|
|
2016
|
-
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 });
|
|
2017
|
-
baseline = prev ? baseline + Math.round(prev.desc) + Math.round(prev.lead) + (firstLine ? p.spacing : 0) + Math.round(asc) : Math.round(asc);
|
|
2018
|
-
if (baseline + desc > height + 0.5) return;
|
|
2019
|
-
const line2 = { glyphs, asc, desc, lead, baseline, para: p, first: firstLine };
|
|
2020
|
-
line2.glyphs.forEach((g) => {
|
|
2021
|
-
g.r = runs[g.run];
|
|
2022
|
-
});
|
|
2023
|
-
lines.push(line2);
|
|
2024
|
-
prev = line2;
|
|
2025
|
-
firstLine = false;
|
|
2026
|
-
k = end;
|
|
2027
|
-
if (chars.length === 0) break;
|
|
2028
|
-
}
|
|
2029
|
-
});
|
|
2030
|
-
return lines;
|
|
2031
|
-
}
|
|
2032
|
-
function noteMarkup(source, e) {
|
|
2033
|
-
const rect2 = textRect({ x: e.x, y: e.y, width: e.width, height: e.height });
|
|
2034
|
-
if (!(rect2.width > 1 && rect2.height > 1)) return "";
|
|
2035
|
-
const b = new Builder(e.style.fontSize, rect2.width);
|
|
2036
|
-
for (const block of parseBlocks(source)) b.block(block, { indent: 0, marker: null, quote: null, listDepth: 0 });
|
|
2037
|
-
const lines = layoutLines(b.paras, rect2.width, rect2.height, e.style.fontSize);
|
|
2038
|
-
const ink = color(e.style.stroke);
|
|
2039
|
-
let s = "";
|
|
2040
|
-
const marks = [];
|
|
2041
|
-
for (const l of lines) {
|
|
2042
|
-
const p = l.para;
|
|
2043
|
-
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]);
|
|
2044
|
-
if (p.rule !== null) marks.push([rect2.x + p.rule, rect2.y + l.baseline - 3, rect2.x + rect2.width, rect2.y + l.baseline - 3]);
|
|
2045
|
-
if (p.tableRule) {
|
|
2046
|
-
const y = rect2.y + l.baseline + l.desc + 2;
|
|
2047
|
-
marks.push([rect2.x + p.tableRule[0], y, Math.min(rect2.x + rect2.width, rect2.x + p.tableRule[0] + p.tableRule[1]), y]);
|
|
2048
|
-
}
|
|
2049
|
-
let spans = "";
|
|
2050
|
-
let i = 0;
|
|
2051
|
-
const gs = l.glyphs;
|
|
2052
|
-
while (i < gs.length) {
|
|
2053
|
-
const r = gs[i].r;
|
|
2054
|
-
let j = i;
|
|
2055
|
-
let text = "";
|
|
2056
|
-
const font = fontOf(gs[i].ch);
|
|
2057
|
-
while (j < gs.length && sameStyle(gs[j].r, r) && gs[j].ch !== " " && fontOf(gs[j].ch) === font) {
|
|
2058
|
-
text += gs[j].ch;
|
|
2059
|
-
j++;
|
|
2060
|
-
}
|
|
2061
|
-
const shown = text.replaceAll("\n", "").replaceAll(SEP, "");
|
|
2062
|
-
if (shown.trim() !== "") {
|
|
2063
|
-
let a = `font-size="${fmt(r.size)}"`;
|
|
2064
|
-
if (r.mono) a += ` font-family="Menlo, Consolas, monospace"`;
|
|
2065
|
-
if (r.bold) a += ` font-weight="bold"`;
|
|
2066
|
-
if (r.italic) a += ` font-style="italic"`;
|
|
2067
|
-
if (r.underline) a += ` text-decoration="underline"`;
|
|
2068
|
-
if (r.alpha < 1) a += ` fill-opacity="${fmt(r.alpha * e.style.stroke.a)}"`;
|
|
2069
|
-
spans += `<tspan x="${fmt(rect2.x + gs[i].x)}" ${a}>${escapeXML(shown)}</tspan>`;
|
|
2070
|
-
}
|
|
2071
|
-
if (r.strike && j > i) {
|
|
2072
|
-
const y = l.baseline - metrics(gs[i].ch, r)[0] * 0.32;
|
|
2073
|
-
marks.push([rect2.x + gs[i].x, rect2.y + y, rect2.x + gs[j - 1].x + gs[j - 1].w, rect2.y + y]);
|
|
2074
|
-
}
|
|
2075
|
-
if (j === i) j++;
|
|
2076
|
-
i = j;
|
|
2077
|
-
}
|
|
2078
|
-
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>
|
|
2079
|
-
`;
|
|
2080
|
-
}
|
|
2081
|
-
const width = fmt(Math.max(1, e.style.fontSize / 14));
|
|
2082
|
-
for (const [x1, y1, x2, y2] of marks) {
|
|
2083
|
-
s += `<line x1="${fmt(x1)}" y1="${fmt(y1)}" x2="${fmt(x2)}" y2="${fmt(y2)}" stroke="${ink}" stroke-width="${width}"/>
|
|
2084
|
-
`;
|
|
2085
|
-
}
|
|
2086
|
-
return s;
|
|
2087
|
-
}
|
|
2088
|
-
function fontOf(ch) {
|
|
2089
|
-
return fallbackMetrics[ch] ? `fb:${ch}` : "";
|
|
2090
|
-
}
|
|
2091
|
-
function sameStyle(a, b) {
|
|
2092
|
-
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;
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
// src/theme.ts
|
|
2096
|
-
var rgb = (r, g, b) => ({ r, g, b, a: 1 });
|
|
2097
|
-
var themes = [
|
|
2098
|
-
{ id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13) },
|
|
2099
|
-
{ id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13) },
|
|
2100
|
-
{ id: "sepia", name: "Sepia", background: rgb(0.953, 0.914, 0.824), ink: rgb(0.23, 0.18, 0.13) },
|
|
2101
|
-
{ id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984) },
|
|
2102
|
-
{ id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918) },
|
|
2103
|
-
{ id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94) }
|
|
2104
|
-
];
|
|
2105
|
-
function themeNamed(id) {
|
|
2106
|
-
return themes.find((t) => t.id === id) ?? null;
|
|
2107
|
-
}
|
|
2108
|
-
|
|
2109
|
-
// src/timeline.ts
|
|
2110
|
-
function applyEase(ease, u) {
|
|
2111
|
-
switch (ease) {
|
|
2112
|
-
case "linear":
|
|
2113
|
-
return u;
|
|
2114
|
-
case "easeIn":
|
|
2115
|
-
return u * u * u;
|
|
2116
|
-
case "easeOut":
|
|
2117
|
-
return 1 - Math.pow(1 - u, 3);
|
|
2118
|
-
case "easeInOut":
|
|
2119
|
-
return u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
|
|
2120
|
-
case "hold":
|
|
2121
|
-
return 0;
|
|
2122
|
-
case "spring":
|
|
2123
|
-
return spring(u);
|
|
2124
|
-
case "bounce":
|
|
2125
|
-
return bounce(u);
|
|
2126
|
-
case "elastic":
|
|
2127
|
-
return elastic(u);
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
function spring(u) {
|
|
2131
|
-
if (u <= 0) return 0;
|
|
2132
|
-
if (u >= 1) return 1;
|
|
2133
|
-
const s = (u2) => 1 - Math.exp(-6 * u2) * (Math.cos(3 * Math.PI * u2) + 6 / (3 * Math.PI) * Math.sin(3 * Math.PI * u2));
|
|
2134
|
-
return s(u) + u * (1 - s(1));
|
|
2135
|
-
}
|
|
2136
|
-
function bounce(u) {
|
|
2137
|
-
if (u <= 0) return 0;
|
|
2138
|
-
if (u >= 1) return 1;
|
|
2139
|
-
const n = 7.5625, d = 2.75;
|
|
2140
|
-
if (u < 1 / d) return n * u * u;
|
|
2141
|
-
if (u < 2 / d) {
|
|
2142
|
-
const v2 = u - 1.5 / d;
|
|
2143
|
-
return n * v2 * v2 + 0.75;
|
|
2144
|
-
}
|
|
2145
|
-
if (u < 2.5 / d) {
|
|
2146
|
-
const v2 = u - 2.25 / d;
|
|
2147
|
-
return n * v2 * v2 + 0.9375;
|
|
2148
|
-
}
|
|
2149
|
-
const v = u - 2.625 / d;
|
|
2150
|
-
return n * v * v + 0.984375;
|
|
2151
|
-
}
|
|
2152
|
-
function elastic(u) {
|
|
2153
|
-
if (u <= 0) return 0;
|
|
2154
|
-
if (u >= 1) return 1;
|
|
2155
|
-
return Math.pow(2, -10 * u) * Math.sin((u * 10 - 0.75) * (2 * Math.PI / 3)) + 1;
|
|
2156
|
-
}
|
|
2157
|
-
var rem = (a, b) => a % b;
|
|
2158
|
-
function localTime(track, t) {
|
|
2159
|
-
const reps = (track.repeats ?? []).filter(repeatIsValid).slice().sort((a, b) => a.start - b.start);
|
|
2160
|
-
if (reps.length === 0) return t;
|
|
2161
|
-
for (const r of reps) {
|
|
2162
|
-
if (!(t >= r.end)) continue;
|
|
2163
|
-
const length = r.end - r.start;
|
|
2164
|
-
if (r.count === null) return r.start + rem(t - r.start, length);
|
|
2165
|
-
const played = length * r.count;
|
|
2166
|
-
if (t < r.start + played) return r.start + rem(t - r.start, length);
|
|
2167
|
-
t -= played - length;
|
|
2168
|
-
}
|
|
2169
|
-
return t;
|
|
2170
|
-
}
|
|
2171
|
-
function value(track, t, get, base, lerp) {
|
|
2172
|
-
t = localTime(track, t);
|
|
2173
|
-
let before, after;
|
|
2174
|
-
for (const k of track.keys) {
|
|
2175
|
-
if (get(k) === void 0) continue;
|
|
2176
|
-
if (k.t <= t) before = k;
|
|
2177
|
-
else {
|
|
2178
|
-
after = k;
|
|
2179
|
-
break;
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
if (!before && !after) return base;
|
|
2183
|
-
if (before && !after) return get(before);
|
|
2184
|
-
if (!before && after) return get(after);
|
|
2185
|
-
const b = before, a = after;
|
|
2186
|
-
const u = a.t - b.t > 0 ? (t - b.t) / (a.t - b.t) : 1;
|
|
2187
|
-
return lerp(get(b), get(a), applyEase(b.ease ?? "linear", u));
|
|
2188
|
-
}
|
|
2189
|
-
var lerpNum = (a, b, u) => a + (b - a) * u;
|
|
2190
|
-
var mix01 = (a, b, u) => Math.min(Math.max(a + (b - a) * u, 0), 1);
|
|
2191
|
-
var lerpColor = (a, b, u) => ({
|
|
2192
|
-
r: mix01(a.r, b.r, u),
|
|
2193
|
-
g: mix01(a.g, b.g, u),
|
|
2194
|
-
b: mix01(a.b, b.b, u),
|
|
2195
|
-
a: mix01(a.a, b.a, u)
|
|
2196
|
-
});
|
|
2197
|
-
function num2(track, t, p, base) {
|
|
2198
|
-
return value(track, t, (k) => k[p], base, lerpNum);
|
|
2199
|
-
}
|
|
2200
|
-
function isChanging(track, t) {
|
|
2201
|
-
t = localTime(track, t);
|
|
2202
|
-
const changing = (get, eq) => {
|
|
2203
|
-
let b;
|
|
2204
|
-
for (const k of track.keys) if (k.t <= t && get(k) !== void 0) b = k;
|
|
2205
|
-
const a = track.keys.find((k) => k.t > t && get(k) !== void 0);
|
|
2206
|
-
if (!b || !a) return false;
|
|
2207
|
-
return b.ease !== "hold" && !eq(get(b), get(a));
|
|
2208
|
-
};
|
|
2209
|
-
const n = (p) => changing((k) => k[p], (a, b) => a === b);
|
|
2210
|
-
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");
|
|
2211
|
-
}
|
|
2212
|
-
function isVisible(track, t) {
|
|
2213
|
-
t = localTime(track, t);
|
|
2214
|
-
const keyed = track.keys.filter((k) => k.visible !== void 0);
|
|
2215
|
-
if (keyed.length === 0) return true;
|
|
2216
|
-
let last;
|
|
2217
|
-
for (const k of keyed) if (k.t <= t) last = k;
|
|
2218
|
-
return last ? last.visible : !keyed[0].visible;
|
|
2219
|
-
}
|
|
2220
|
-
function applyTrack(track, e, t) {
|
|
2221
|
-
e = cloneElement(e);
|
|
2222
|
-
if (track.keys.some((k) => k.reveal !== void 0)) {
|
|
2223
|
-
const r = num2(track, t, "reveal", e.reveal ?? 1);
|
|
2224
|
-
e.reveal = r >= 1 ? null : Math.max(0, r);
|
|
2225
|
-
}
|
|
2226
|
-
if (track.keys.some((k) => k.opacity !== void 0)) {
|
|
2227
|
-
const o = Math.min(Math.max(num2(track, t, "opacity", e.style.opacity ?? 1), 0), 1);
|
|
2228
|
-
e.style.opacity = o >= 1 ? null : o;
|
|
2229
|
-
}
|
|
2230
|
-
if (e.kind === "connector") return e;
|
|
2231
|
-
e.x = num2(track, t, "x", e.x);
|
|
2232
|
-
e.y = num2(track, t, "y", e.y);
|
|
2233
|
-
e.width = Math.max(0, num2(track, t, "width", e.width));
|
|
2234
|
-
e.height = Math.max(0, num2(track, t, "height", e.height));
|
|
2235
|
-
if (track.keys.some((k) => k.rotation !== void 0)) e.rotation = num2(track, t, "rotation", angle(e));
|
|
2236
|
-
e.style.strokeWidth = Math.max(0, num2(track, t, "strokeWidth", e.style.strokeWidth));
|
|
2237
|
-
e.style.stroke = value(track, t, (k) => k.stroke, e.style.stroke, lerpColor);
|
|
2238
|
-
e.style.fill = value(track, t, (k) => k.fill, e.style.fill, lerpColor);
|
|
2239
|
-
return e;
|
|
2240
|
-
}
|
|
2241
|
-
function followSpan(track, t) {
|
|
2242
|
-
t = localTime(track, t);
|
|
2243
|
-
let f = null;
|
|
2244
|
-
for (const s of track.follow ?? []) if (s.start <= t && (f === null || s.start > f.start)) f = s;
|
|
2245
|
-
if (!f) return null;
|
|
2246
|
-
const span = f;
|
|
2247
|
-
if (track.keys.some((k) => k.t >= span.end && k.t <= t && (k.x !== void 0 || k.y !== void 0))) return null;
|
|
2248
|
-
return f;
|
|
2249
|
-
}
|
|
2250
|
-
function tracing(e, page) {
|
|
2251
|
-
if (e.kind === "connector") return new Polyline(routePoints(route(e, indexOf(page))));
|
|
2252
|
-
const clean = cloneElement(e);
|
|
2253
|
-
clean.style.roughness = 0;
|
|
2254
|
-
const pts = outline(clean).contours()[0] ?? [];
|
|
2255
|
-
return new Polyline(angle(e) === 0 ? pts : pts.map((p) => rotated(e, p)));
|
|
2256
|
-
}
|
|
2257
|
-
function following(track, e, t, page) {
|
|
2258
|
-
const f = followSpan(track, t);
|
|
2259
|
-
if (!f) return e;
|
|
2260
|
-
t = localTime(track, t);
|
|
2261
|
-
const pathElement = page.elements.find((x) => x.id === f.path);
|
|
2262
|
-
if (!pathElement || pathElement.id === e.id) return e;
|
|
2263
|
-
const line2 = tracing(pathElement, page);
|
|
2264
|
-
const span = f.end - f.start;
|
|
2265
|
-
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);
|
|
2266
|
-
if (u > 1) u = rem(u, 1) === 0 ? 1 : rem(u, 1);
|
|
2267
|
-
if (f.reverse === true) u = 1 - u;
|
|
2268
|
-
const p = line2.point(u);
|
|
2269
|
-
const moved = cloneElement(e);
|
|
2270
|
-
moved.x = p.x - e.width / 2;
|
|
2271
|
-
moved.y = p.y - e.height / 2;
|
|
2272
|
-
if (f.orient === true) {
|
|
2273
|
-
const step = f.reverse === true ? -0.01 : 0.01;
|
|
2274
|
-
const a = line2.point(u - step / 2), b = line2.point(u + step / 2);
|
|
2275
|
-
if (a.x !== b.x || a.y !== b.y) moved.rotation = Math.atan2(b.y - a.y, b.x - a.x);
|
|
2276
|
-
}
|
|
2277
|
-
return moved;
|
|
2278
|
-
}
|
|
2279
|
-
function marchOffset(tl, speed, period, t) {
|
|
2280
|
-
if (!(period > 0)) return 0;
|
|
2281
|
-
const d = clampTime(tl.duration);
|
|
2282
|
-
if (d > 0) {
|
|
2283
|
-
const laps = roundHalfAway(speed * d / period);
|
|
2284
|
-
if (laps !== 0) speed = laps * period / d;
|
|
2285
|
-
}
|
|
2286
|
-
const r = rem(speed * t, period);
|
|
2287
|
-
return r < 0 ? -r : r === 0 ? 0 : period - r;
|
|
2288
|
-
}
|
|
2289
|
-
function trackMap(tl) {
|
|
2290
|
-
const m = /* @__PURE__ */ new Map();
|
|
2291
|
-
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
2292
|
-
return m;
|
|
2293
|
-
}
|
|
2294
|
-
function resolved(page, t) {
|
|
2295
|
-
const timeline = page.timeline;
|
|
2296
|
-
if (!timeline) return page;
|
|
2297
|
-
t = clampTime(t);
|
|
2298
|
-
const tracks = trackMap(timeline);
|
|
2299
|
-
const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
|
|
2300
|
-
const hidden = /* @__PURE__ */ new Set();
|
|
2301
|
-
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
2302
|
-
let moving = null;
|
|
2303
|
-
if (timeline.settle === true) {
|
|
2304
|
-
moving = /* @__PURE__ */ new Set();
|
|
2305
|
-
for (const tr of tracks.values()) if (isChanging(tr, t)) moving.add(tr.element);
|
|
2306
|
-
}
|
|
2307
|
-
const boils = (e) => {
|
|
2308
|
-
const override = tracks.get(e.id)?.boil;
|
|
2309
|
-
if (override !== void 0 && override !== null) return override;
|
|
2310
|
-
if (!moving) return true;
|
|
2311
|
-
if (e.connector) return [e.connector.start.elementID, e.connector.end.elementID].some((id) => id !== null && moving.has(id));
|
|
2312
|
-
return moving.has(e.id);
|
|
2313
|
-
};
|
|
2314
|
-
let elements = page.elements.map((e) => {
|
|
2315
|
-
const track = tracks.get(e.id);
|
|
2316
|
-
let out = track ? applyTrack(track, e, t) : e;
|
|
2317
|
-
if (reseed !== null && reseed > 0 && boils(e)) {
|
|
2318
|
-
if (out === e) out = cloneElement(e);
|
|
2319
|
-
out.seed = boiled(e.seed, reseed);
|
|
2320
|
-
}
|
|
2321
|
-
const pattern = track?.march != null ? dashPattern(out.style.dash, out.style.strokeWidth) : null;
|
|
2322
|
-
if (track?.march != null && pattern) {
|
|
2323
|
-
if (out === e) out = cloneElement(e);
|
|
2324
|
-
out.style.dashOffset = marchOffset(timeline, track.march, pattern.reduce((a, b) => a + b, 0), t);
|
|
2325
|
-
}
|
|
2326
|
-
return out;
|
|
2327
|
-
});
|
|
2328
|
-
elements = elements.map((e, i) => hidden.has(e.id) ? page.elements[i] : e);
|
|
2329
|
-
const posed = { ...page, elements, timeline: null };
|
|
2330
|
-
elements = elements.map((e) => {
|
|
2331
|
-
const track = tracks.get(e.id);
|
|
2332
|
-
return track ? following(track, e, t, posed) : e;
|
|
2333
|
-
});
|
|
2334
|
-
const current = { ...page, elements, timeline: null };
|
|
2335
|
-
const index = indexOf(current);
|
|
2336
|
-
const gone = new Set(hidden);
|
|
2337
|
-
elements = elements.map((e) => {
|
|
2338
|
-
const c = e.connector;
|
|
2339
|
-
if (!c || ![c.start.elementID, c.end.elementID].some((id) => id !== null && hidden.has(id))) return e;
|
|
2340
|
-
if (!(tracks.get(e.id)?.keys.some((k) => k.visible !== void 0) ?? false)) {
|
|
2341
|
-
gone.add(e.id);
|
|
2342
|
-
return e;
|
|
2565
|
+
function layoutLines(paras, width, height, fontSize) {
|
|
2566
|
+
const lines = [];
|
|
2567
|
+
let baseline = 0;
|
|
2568
|
+
let prev = null;
|
|
2569
|
+
paras.forEach((p, pi) => {
|
|
2570
|
+
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;
|
|
2571
|
+
const chars = [];
|
|
2572
|
+
runs.forEach((r, ri) => {
|
|
2573
|
+
for (const ch of r.text) chars.push({ ch, run: ri });
|
|
2574
|
+
});
|
|
2575
|
+
let k = 0;
|
|
2576
|
+
let firstLine = true;
|
|
2577
|
+
while (k < chars.length || firstLine && chars.length === 0) {
|
|
2578
|
+
const start = firstLine ? p.firstIndent : p.indent;
|
|
2579
|
+
const maxX2 = width;
|
|
2580
|
+
const glyphs = [];
|
|
2581
|
+
let x = start, lastSpace = -1, end = k;
|
|
2582
|
+
for (; end < chars.length; end++) {
|
|
2583
|
+
const { ch, run } = chars[end];
|
|
2584
|
+
const r = runs[run];
|
|
2585
|
+
if (ch === SEP || ch === "\n") {
|
|
2586
|
+
glyphs.push({ ch, run, x, w: 0 });
|
|
2587
|
+
end++;
|
|
2588
|
+
break;
|
|
2589
|
+
}
|
|
2590
|
+
if (ch === " ") {
|
|
2591
|
+
const stop = p.tabs.find((t) => t.x > x + 0.01);
|
|
2592
|
+
let tx;
|
|
2593
|
+
if (stop) {
|
|
2594
|
+
if (stop.align === "left") tx = stop.x;
|
|
2595
|
+
else {
|
|
2596
|
+
let seg = 0, prevCh = "";
|
|
2597
|
+
for (let q = end + 1; q < chars.length && chars[q].ch !== " " && chars[q].ch !== "\n" && chars[q].ch !== SEP; q++) {
|
|
2598
|
+
const rr = runs[chars[q].run];
|
|
2599
|
+
seg += charWidth(chars[q].ch, rr) + (prevCh ? kern(prevCh, chars[q].ch, rr) : 0);
|
|
2600
|
+
prevCh = chars[q].ch;
|
|
2601
|
+
}
|
|
2602
|
+
tx = Math.max(x, stop.align === "right" ? stop.x - seg : stop.x - seg / 2);
|
|
2603
|
+
}
|
|
2604
|
+
} else tx = (Math.floor(x / TAB_INTERVAL) + 1) * TAB_INTERVAL;
|
|
2605
|
+
glyphs.push({ ch, run, x, w: tx - x });
|
|
2606
|
+
x = tx;
|
|
2607
|
+
continue;
|
|
2608
|
+
}
|
|
2609
|
+
const prevGlyph = glyphs[glyphs.length - 1];
|
|
2610
|
+
const kx = prevGlyph && prevGlyph.run === run && prevGlyph.ch !== " " ? kern(prevGlyph.ch, ch, r) : 0;
|
|
2611
|
+
if (prevGlyph) {
|
|
2612
|
+
prevGlyph.w += kx;
|
|
2613
|
+
x += kx;
|
|
2614
|
+
}
|
|
2615
|
+
const w = charWidth(ch, r);
|
|
2616
|
+
if (ch !== " " && x + w > maxX2 + 0.01 && glyphs.some((g) => g.ch !== " ")) {
|
|
2617
|
+
if (lastSpace >= 0) {
|
|
2618
|
+
glyphs.length = lastSpace + 1;
|
|
2619
|
+
end = k + lastSpace + 1;
|
|
2620
|
+
}
|
|
2621
|
+
if (prevGlyph) prevGlyph.w -= kx;
|
|
2622
|
+
break;
|
|
2623
|
+
}
|
|
2624
|
+
glyphs.push({ ch, run, x, w });
|
|
2625
|
+
x += w;
|
|
2626
|
+
if (breaksAfter(ch, chars[end + 1]?.ch ?? "")) lastSpace = glyphs.length - 1;
|
|
2627
|
+
}
|
|
2628
|
+
if (glyphs.length === 0 && end === k && k < chars.length) {
|
|
2629
|
+
const { ch, run } = chars[k];
|
|
2630
|
+
glyphs.push({ ch, run, x: start, w: charWidth(ch, runs[run]) });
|
|
2631
|
+
end = k + 1;
|
|
2632
|
+
}
|
|
2633
|
+
let asc = 0, desc = 0, lead = 0;
|
|
2634
|
+
for (const g of glyphs) {
|
|
2635
|
+
const [a, d, l] = metrics(g.ch, runs[g.run]);
|
|
2636
|
+
asc = Math.max(asc, a);
|
|
2637
|
+
desc = Math.max(desc, d);
|
|
2638
|
+
lead = Math.max(lead, l);
|
|
2639
|
+
}
|
|
2640
|
+
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 });
|
|
2641
|
+
baseline = prev ? baseline + Math.round(prev.desc) + Math.round(prev.lead) + (firstLine ? p.spacing : 0) + Math.round(asc) : Math.round(asc);
|
|
2642
|
+
if (baseline + desc > height + 0.5) return;
|
|
2643
|
+
const line2 = { glyphs, asc, desc, lead, baseline, para: p, first: firstLine };
|
|
2644
|
+
line2.glyphs.forEach((g) => {
|
|
2645
|
+
g.r = runs[g.run];
|
|
2646
|
+
});
|
|
2647
|
+
lines.push(line2);
|
|
2648
|
+
prev = line2;
|
|
2649
|
+
firstLine = false;
|
|
2650
|
+
k = end;
|
|
2651
|
+
if (chars.length === 0) break;
|
|
2343
2652
|
}
|
|
2344
|
-
const [a, b] = endpoints(e, index);
|
|
2345
|
-
const pinned = cloneElement(e);
|
|
2346
|
-
pinned.connector.start = { elementID: null, port: c.start.port, x: a.x, y: a.y };
|
|
2347
|
-
pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
|
|
2348
|
-
return pinned;
|
|
2349
2653
|
});
|
|
2350
|
-
return
|
|
2351
|
-
}
|
|
2352
|
-
function posterTime(page) {
|
|
2353
|
-
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
2354
|
-
}
|
|
2355
|
-
function still(page) {
|
|
2356
|
-
return page.timeline ? resolved(page, posterTime(page)) : page;
|
|
2357
|
-
}
|
|
2358
|
-
function cameraRect(tl, t) {
|
|
2359
|
-
const keys = tl.camera ?? [];
|
|
2360
|
-
if (keys.length === 0) return null;
|
|
2361
|
-
let before;
|
|
2362
|
-
for (const k of keys) if (k.t <= t) before = k;
|
|
2363
|
-
const after = keys.find((k) => k.t > t);
|
|
2364
|
-
if (!before) return rectOf(keys[0]);
|
|
2365
|
-
if (!after) return rectOf(before);
|
|
2366
|
-
const u = applyEase(before.ease ?? "easeInOut", (t - before.t) / Math.max(after.t - before.t, 1e-9));
|
|
2367
|
-
const mix2 = (p, q) => p + (q - p) * u;
|
|
2368
|
-
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)) };
|
|
2369
|
-
}
|
|
2370
|
-
var rectOf = (k) => ({ x: k.x, y: k.y, width: k.width, height: k.height });
|
|
2371
|
-
function cameraView(tl, t) {
|
|
2372
|
-
const first = tl.camera?.[0];
|
|
2373
|
-
const r = cameraRect(tl, t);
|
|
2374
|
-
if (!first || !r || !(r.width > 0 && r.height > 0)) return null;
|
|
2375
|
-
return fit(r, first.width / first.height);
|
|
2376
|
-
}
|
|
2377
|
-
function fit(r, aspect) {
|
|
2378
|
-
if (!(r.width > 0 && r.height > 0 && aspect > 0)) return r;
|
|
2379
|
-
let w = r.width, h = r.height;
|
|
2380
|
-
if (r.width / r.height < aspect) w = r.height * aspect;
|
|
2381
|
-
else h = r.width / aspect;
|
|
2382
|
-
return { x: r.x + r.width / 2 - w / 2, y: r.y + r.height / 2 - h / 2, width: w, height: h };
|
|
2654
|
+
return lines;
|
|
2383
2655
|
}
|
|
2384
|
-
function
|
|
2385
|
-
|
|
2656
|
+
function noteMarkup(source, e) {
|
|
2657
|
+
const rect2 = textRect({ x: e.x, y: e.y, width: e.width, height: e.height });
|
|
2658
|
+
if (!(rect2.width > 1 && rect2.height > 1)) return "";
|
|
2659
|
+
const b = new Builder(e.style.fontSize, rect2.width);
|
|
2660
|
+
for (const block of parseBlocks(source)) b.block(block, { indent: 0, marker: null, quote: null, listDepth: 0 });
|
|
2661
|
+
const lines = layoutLines(b.paras, rect2.width, rect2.height, e.style.fontSize);
|
|
2662
|
+
const ink = color(e.style.stroke);
|
|
2663
|
+
let s = "";
|
|
2664
|
+
const marks = [];
|
|
2665
|
+
for (const l of lines) {
|
|
2666
|
+
const p = l.para;
|
|
2667
|
+
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]);
|
|
2668
|
+
if (p.rule !== null) marks.push([rect2.x + p.rule, rect2.y + l.baseline - 3, rect2.x + rect2.width, rect2.y + l.baseline - 3]);
|
|
2669
|
+
if (p.tableRule) {
|
|
2670
|
+
const y = rect2.y + l.baseline + l.desc + 2;
|
|
2671
|
+
marks.push([rect2.x + p.tableRule[0], y, Math.min(rect2.x + rect2.width, rect2.x + p.tableRule[0] + p.tableRule[1]), y]);
|
|
2672
|
+
}
|
|
2673
|
+
let spans = "";
|
|
2674
|
+
let i = 0;
|
|
2675
|
+
const gs = l.glyphs;
|
|
2676
|
+
while (i < gs.length) {
|
|
2677
|
+
const r = gs[i].r;
|
|
2678
|
+
let j = i;
|
|
2679
|
+
let text = "";
|
|
2680
|
+
const font = fontOf(gs[i].ch);
|
|
2681
|
+
while (j < gs.length && sameStyle(gs[j].r, r) && gs[j].ch !== " " && fontOf(gs[j].ch) === font) {
|
|
2682
|
+
text += gs[j].ch;
|
|
2683
|
+
j++;
|
|
2684
|
+
}
|
|
2685
|
+
const shown = text.replaceAll("\n", "").replaceAll(SEP, "");
|
|
2686
|
+
if (shown.trim() !== "") {
|
|
2687
|
+
let a = `font-size="${fmt(r.size)}"`;
|
|
2688
|
+
if (r.mono) a += ` font-family="Menlo, Consolas, monospace"`;
|
|
2689
|
+
if (r.bold) a += ` font-weight="bold"`;
|
|
2690
|
+
if (r.italic) a += ` font-style="italic"`;
|
|
2691
|
+
if (r.underline) a += ` text-decoration="underline"`;
|
|
2692
|
+
if (r.alpha < 1) a += ` fill-opacity="${fmt(r.alpha * e.style.stroke.a)}"`;
|
|
2693
|
+
spans += `<tspan x="${fmt(rect2.x + gs[i].x)}" ${a}>${escapeXML(shown)}</tspan>`;
|
|
2694
|
+
}
|
|
2695
|
+
if (r.strike && j > i) {
|
|
2696
|
+
const y = l.baseline - metrics(gs[i].ch, r)[0] * 0.32;
|
|
2697
|
+
marks.push([rect2.x + gs[i].x, rect2.y + y, rect2.x + gs[j - 1].x + gs[j - 1].w, rect2.y + y]);
|
|
2698
|
+
}
|
|
2699
|
+
if (j === i) j++;
|
|
2700
|
+
i = j;
|
|
2701
|
+
}
|
|
2702
|
+
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>
|
|
2703
|
+
`;
|
|
2704
|
+
}
|
|
2705
|
+
const width = fmt(Math.max(1, e.style.fontSize / 14));
|
|
2706
|
+
for (const [x1, y1, x2, y2] of marks) {
|
|
2707
|
+
s += `<line x1="${fmt(x1)}" y1="${fmt(y1)}" x2="${fmt(x2)}" y2="${fmt(y2)}" stroke="${ink}" stroke-width="${width}"/>
|
|
2708
|
+
`;
|
|
2709
|
+
}
|
|
2710
|
+
return s;
|
|
2386
2711
|
}
|
|
2387
|
-
function
|
|
2388
|
-
|
|
2389
|
-
const times = [];
|
|
2390
|
-
for (let i = 0; i < frameCount(tl); i++) times.push(i / fps);
|
|
2391
|
-
const last = times[times.length - 1];
|
|
2392
|
-
if (tl.duration - last > 1e-6) times.push(tl.duration);
|
|
2393
|
-
return times;
|
|
2712
|
+
function fontOf(ch) {
|
|
2713
|
+
return fallbackMetrics[ch] ? `fb:${ch}` : "";
|
|
2394
2714
|
}
|
|
2395
|
-
function
|
|
2396
|
-
|
|
2397
|
-
(tl.markers ?? []).forEach((m, i) => {
|
|
2398
|
-
if (m.t <= t) s = i;
|
|
2399
|
-
});
|
|
2400
|
-
return s;
|
|
2715
|
+
function sameStyle(a, b) {
|
|
2716
|
+
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;
|
|
2401
2717
|
}
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2718
|
+
|
|
2719
|
+
// src/theme.ts
|
|
2720
|
+
var rgb = (r, g, b) => ({ r, g, b, a: 1 });
|
|
2721
|
+
var themes = [
|
|
2722
|
+
{ id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13) },
|
|
2723
|
+
{ id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13) },
|
|
2724
|
+
{ id: "sepia", name: "Sepia", background: rgb(0.953, 0.914, 0.824), ink: rgb(0.23, 0.18, 0.13) },
|
|
2725
|
+
{ id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984) },
|
|
2726
|
+
{ id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918) },
|
|
2727
|
+
{ id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94) }
|
|
2728
|
+
];
|
|
2729
|
+
function themeNamed(id) {
|
|
2730
|
+
return themes.find((t) => t.id === id) ?? null;
|
|
2407
2731
|
}
|
|
2408
2732
|
|
|
2409
2733
|
// src/svg.ts
|
|
2410
2734
|
function bounds(page, padding = 24) {
|
|
2735
|
+
if (hasInstances(page)) return bounds(expandingSymbols(page, 0), padding);
|
|
2411
2736
|
const index = indexOf(page);
|
|
2412
2737
|
const box = (e) => {
|
|
2413
2738
|
if (e.kind === "connector") return connectorBounds(e, index);
|
|
@@ -2449,6 +2774,7 @@ function svgOpen(b, background, paint) {
|
|
|
2449
2774
|
return out;
|
|
2450
2775
|
}
|
|
2451
2776
|
function svgParts(page, background, fixed) {
|
|
2777
|
+
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed);
|
|
2452
2778
|
const theme = themeNamed(page.theme);
|
|
2453
2779
|
const bg = background ? theme?.background ?? background : null;
|
|
2454
2780
|
const ink = theme?.ink ?? black;
|
|
@@ -2516,10 +2842,10 @@ function svgElement(e, index, labelBackground) {
|
|
|
2516
2842
|
}
|
|
2517
2843
|
switch (e.kind) {
|
|
2518
2844
|
case "connector": {
|
|
2519
|
-
const [line2,
|
|
2845
|
+
const [line2, filled2] = connectorParts(e, index);
|
|
2520
2846
|
let s = `<path d="${line2.data()}" fill="none" ${strokeAttrs}/>
|
|
2521
2847
|
`;
|
|
2522
|
-
if (!
|
|
2848
|
+
if (!filled2.isEmpty) s += `<path d="${filled2.data()}" fill="${stroke}" stroke="none"/>
|
|
2523
2849
|
`;
|
|
2524
2850
|
if (text) {
|
|
2525
2851
|
const lr = labelRect(e, index);
|
|
@@ -2629,8 +2955,9 @@ input[type=range] { flex: 1; min-width: 60px; accent-color: currentColor; }
|
|
|
2629
2955
|
output { font-variant-numeric: tabular-nums; min-width: 4.5em; text-align: right; }
|
|
2630
2956
|
.scene { min-width: 0; flex: 0 1 auto; text-align: center; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
2631
2957
|
.error { padding: 16px; font-family: ui-monospace, Menlo, monospace; white-space: pre-wrap; }
|
|
2632
|
-
`;
|
|
2958
|
+
${CLICK_STYLE}`;
|
|
2633
2959
|
var Playback = class {
|
|
2960
|
+
/** `file` is the drawing `page` belongs to, so links to its other pages are clickable. */
|
|
2634
2961
|
constructor(page, into, options = {}) {
|
|
2635
2962
|
__publicField(this, "page");
|
|
2636
2963
|
__publicField(this, "plan");
|
|
@@ -2638,6 +2965,22 @@ var Playback = class {
|
|
|
2638
2965
|
__publicField(this, "sceneNames");
|
|
2639
2966
|
__publicField(this, "background");
|
|
2640
2967
|
__publicField(this, "ink");
|
|
2968
|
+
/** What clicking each clickable element does, by element id. */
|
|
2969
|
+
__publicField(this, "clicks");
|
|
2970
|
+
/** Copies of symbols that play through once when clicked (playback "click"), by id, with their clip's length. */
|
|
2971
|
+
__publicField(this, "replays", /* @__PURE__ */ new Map());
|
|
2972
|
+
/** When each replaying copy was clicked (performance.now() milliseconds). */
|
|
2973
|
+
__publicField(this, "replayStarts", /* @__PURE__ */ new Map());
|
|
2974
|
+
__publicField(this, "hoverIDs");
|
|
2975
|
+
__publicField(this, "labels", /* @__PURE__ */ new Map());
|
|
2976
|
+
/** Each drawn part of a clickable copy, to that copy's id; and each copy's parts in drawing order. */
|
|
2977
|
+
__publicField(this, "owner", /* @__PURE__ */ new Map());
|
|
2978
|
+
__publicField(this, "partsOfOwner", /* @__PURE__ */ new Map());
|
|
2979
|
+
/** The moments of the button copies' over, down and hit states. */
|
|
2980
|
+
__publicField(this, "overTimes", /* @__PURE__ */ new Map());
|
|
2981
|
+
__publicField(this, "downTimes", /* @__PURE__ */ new Map());
|
|
2982
|
+
__publicField(this, "hitTimes", /* @__PURE__ */ new Map());
|
|
2983
|
+
__publicField(this, "last", { t: 0, i: 0 });
|
|
2641
2984
|
__publicField(this, "svg");
|
|
2642
2985
|
__publicField(this, "groups", /* @__PURE__ */ new Map());
|
|
2643
2986
|
__publicField(this, "order", []);
|
|
@@ -2645,6 +2988,30 @@ var Playback = class {
|
|
|
2645
2988
|
__publicField(this, "onFrame", null);
|
|
2646
2989
|
this.page = options.clean ? withRoughness(page, 0) : page;
|
|
2647
2990
|
this.plan = plan(this.page);
|
|
2991
|
+
this.clicks = clickTargets(page, options.file);
|
|
2992
|
+
const table = symbolTable(this.page);
|
|
2993
|
+
for (const e of this.page.elements) {
|
|
2994
|
+
const s = e.kind === "symbol" && e.symbol !== null ? table.get(e.symbol) : void 0;
|
|
2995
|
+
if (!s) continue;
|
|
2996
|
+
if (s.button === true) {
|
|
2997
|
+
for (const [state, times] of [["over", this.overTimes], ["down", this.downTimes], ["hit", this.hitTimes]]) {
|
|
2998
|
+
const at = buttonTime(s, state);
|
|
2999
|
+
if (at !== null) times.set(e.id, at);
|
|
3000
|
+
}
|
|
3001
|
+
} else if (e.playback === "click" && (s.timeline?.duration ?? 0) > 0) {
|
|
3002
|
+
this.replays.set(e.id, s.timeline.duration);
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
const clickable2 = /* @__PURE__ */ new Set([...this.clicks.keys(), ...this.replays.keys()]);
|
|
3006
|
+
this.hoverIDs = new Set(this.page.elements.filter((e) => clickable2.has(e.id) && e.kind !== "symbol" && hasHover(e)).map((e) => e.id));
|
|
3007
|
+
const instances = new Set(this.page.elements.filter((e) => clickable2.has(e.id) && e.kind === "symbol").map((e) => e.id));
|
|
3008
|
+
for (const e of this.page.elements) {
|
|
3009
|
+
if (!clickable2.has(e.id)) continue;
|
|
3010
|
+
this.labels.set(e.id, buttonLabel(e, e.symbol !== null ? table.get(e.symbol)?.name : void 0));
|
|
3011
|
+
if (e.kind !== "symbol") this.owner.set(e.id, e.id);
|
|
3012
|
+
}
|
|
3013
|
+
for (const [part, id] of partsOf(this.page, instances)) this.owner.set(part, id);
|
|
3014
|
+
for (const [part, id] of this.owner) this.partsOfOwner.set(id, [...this.partsOfOwner.get(id) ?? [], part]);
|
|
2648
3015
|
const near = (t) => {
|
|
2649
3016
|
let best = 0;
|
|
2650
3017
|
this.plan.times.forEach((x, i) => {
|
|
@@ -2683,24 +3050,73 @@ var Playback = class {
|
|
|
2683
3050
|
this.frame = i;
|
|
2684
3051
|
this.onFrame?.(i);
|
|
2685
3052
|
}
|
|
3053
|
+
/** Starts copy `id` replaying from `at` (performance.now() milliseconds; earlier to start partway). */
|
|
3054
|
+
replay(id, at) {
|
|
3055
|
+
if (this.replays.has(id)) this.replayStarts.set(id, at);
|
|
3056
|
+
}
|
|
3057
|
+
/** Whether a copy is still playing through after a click. */
|
|
3058
|
+
replaying(now = performance.now()) {
|
|
3059
|
+
for (const [id, start] of this.replayStarts) if (now - start < (this.replays.get(id) ?? 0) * 1e3) return true;
|
|
3060
|
+
return false;
|
|
3061
|
+
}
|
|
3062
|
+
/** Draws the last moment drawn again, for copies replaying while the page stands still. */
|
|
3063
|
+
redraw() {
|
|
3064
|
+
this.drawAt(this.last.t, this.last.i);
|
|
3065
|
+
}
|
|
2686
3066
|
/** Draws the moment `t` (seconds) in the frame-`i` view. */
|
|
2687
3067
|
drawAt(t, i = 0) {
|
|
3068
|
+
this.last = { t, i };
|
|
2688
3069
|
const r = this.plan.rects[Math.min(i, this.plan.rects.length - 1)];
|
|
2689
|
-
const
|
|
3070
|
+
const now = typeof performance === "undefined" ? 0 : performance.now();
|
|
3071
|
+
const times = /* @__PURE__ */ new Map();
|
|
3072
|
+
for (const [id, start] of this.replayStarts) times.set(id, Math.min((now - start) / 1e3, this.replays.get(id) ?? 0));
|
|
3073
|
+
const tc = clampTime(t);
|
|
3074
|
+
const pose = posed(this.page, tc);
|
|
3075
|
+
const frame2 = expandingSymbols(pose, tc, true, times);
|
|
3076
|
+
const { parts } = svgParts(frame2, this.background, r);
|
|
3077
|
+
const owned = (m, parts2) => {
|
|
3078
|
+
for (const p of parts2) if (this.owner.has(p.key)) m.set(p.key, p.markup);
|
|
3079
|
+
return m;
|
|
3080
|
+
};
|
|
3081
|
+
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) ?? "")));
|
|
3082
|
+
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)));
|
|
3083
|
+
const over = state(this.overTimes), down = state(this.downTimes), hit = state(this.hitTimes);
|
|
3084
|
+
const normal = owned(/* @__PURE__ */ new Map(), parts);
|
|
3085
|
+
const merged = [];
|
|
3086
|
+
const done = /* @__PURE__ */ new Set();
|
|
3087
|
+
for (const p of parts) {
|
|
3088
|
+
const o = this.owner.get(p.key);
|
|
3089
|
+
if (o === void 0) {
|
|
3090
|
+
merged.push(p);
|
|
3091
|
+
continue;
|
|
3092
|
+
}
|
|
3093
|
+
if (done.has(o)) continue;
|
|
3094
|
+
done.add(o);
|
|
3095
|
+
merged.push(this.buttonPart(o, normal, hover, over, down, hit, pose));
|
|
3096
|
+
}
|
|
2690
3097
|
this.svg.setAttribute("viewBox", `${fmt(r.x)} ${fmt(r.y)} ${fmt(r.width)} ${fmt(r.height)}`);
|
|
2691
3098
|
const ns = "http://www.w3.org/2000/svg";
|
|
2692
|
-
const keys =
|
|
2693
|
-
for (const p of
|
|
3099
|
+
const keys = merged.map((p) => p.key);
|
|
3100
|
+
for (const p of merged) {
|
|
3101
|
+
const markup = p.markup;
|
|
2694
3102
|
const have = this.groups.get(p.key);
|
|
2695
3103
|
if (have) {
|
|
2696
|
-
if (have.markup !==
|
|
2697
|
-
have.g.innerHTML =
|
|
2698
|
-
have.markup =
|
|
3104
|
+
if (have.markup !== markup) {
|
|
3105
|
+
have.g.innerHTML = markup;
|
|
3106
|
+
have.markup = markup;
|
|
2699
3107
|
}
|
|
3108
|
+
if (p.button !== void 0) have.g.classList.toggle("dottie-b", p.button);
|
|
2700
3109
|
} else {
|
|
2701
3110
|
const g = document.createElementNS(ns, "g");
|
|
2702
|
-
g.innerHTML =
|
|
2703
|
-
|
|
3111
|
+
g.innerHTML = markup;
|
|
3112
|
+
if (p.button !== void 0) {
|
|
3113
|
+
g.setAttribute("class", p.button ? "dottie-a dottie-b" : "dottie-a");
|
|
3114
|
+
g.setAttribute("data-a", p.key);
|
|
3115
|
+
g.setAttribute("tabindex", "0");
|
|
3116
|
+
g.setAttribute("role", "button");
|
|
3117
|
+
g.setAttribute("aria-label", this.labels.get(p.key) ?? "");
|
|
3118
|
+
}
|
|
3119
|
+
this.groups.set(p.key, { g, markup });
|
|
2704
3120
|
}
|
|
2705
3121
|
}
|
|
2706
3122
|
const live = new Set(keys);
|
|
@@ -2709,10 +3125,66 @@ var Playback = class {
|
|
|
2709
3125
|
this.groups.delete(k);
|
|
2710
3126
|
}
|
|
2711
3127
|
if (keys.length !== this.order.length || keys.some((k, j) => k !== this.order[j])) {
|
|
2712
|
-
|
|
3128
|
+
let at = this.svg.firstChild;
|
|
3129
|
+
for (const k of keys) {
|
|
3130
|
+
const g = this.groups.get(k).g;
|
|
3131
|
+
if (g === at) at = at.nextSibling;
|
|
3132
|
+
else this.svg.insertBefore(g, at);
|
|
3133
|
+
}
|
|
3134
|
+
while (at) {
|
|
3135
|
+
const next = at.nextSibling;
|
|
3136
|
+
at.remove();
|
|
3137
|
+
at = next;
|
|
3138
|
+
}
|
|
2713
3139
|
this.order = keys;
|
|
2714
3140
|
}
|
|
2715
3141
|
}
|
|
3142
|
+
/** What a clickable owner draws as: its normal look, and its hover, over and down looks where they differ. A
|
|
3143
|
+
* symbol's copy keeps its drawing out of the pointer's way and takes clicks on a layer above: its hit state drawn
|
|
3144
|
+
* invisibly, or its frame. `button` is whether it has a down look (the stylesheet nudges the others). */
|
|
3145
|
+
buttonPart(o, normal, hover, over, down, hit, pose) {
|
|
3146
|
+
const keys = this.partsOfOwner.get(o) ?? [o];
|
|
3147
|
+
const join = (m2) => {
|
|
3148
|
+
const s = keys.map((k) => m2.get(k) ?? "").join("");
|
|
3149
|
+
return s === "" ? null : s;
|
|
3150
|
+
};
|
|
3151
|
+
const base = join(normal) ?? "";
|
|
3152
|
+
const instance = pose.elements.find((e) => e.id === o && e.kind === "symbol");
|
|
3153
|
+
const overLook = instance ? join(over) : join(hover);
|
|
3154
|
+
const downLook = instance ? join(down) : null;
|
|
3155
|
+
const shownOver = overLook !== null && overLook !== base ? overLook : null;
|
|
3156
|
+
const shownDown = downLook !== null && downLook !== (overLook ?? base) ? downLook : null;
|
|
3157
|
+
if (!instance && shownOver === null) return { key: o, markup: base, button: false };
|
|
3158
|
+
const still2 = instance ? ` pointer-events="none"` : "";
|
|
3159
|
+
let m = `<g class="dottie-n"${still2}>
|
|
3160
|
+
${base}</g>
|
|
3161
|
+
`;
|
|
3162
|
+
if (shownOver !== null) m += `<g class="dottie-h"${still2}>
|
|
3163
|
+
${shownOver}</g>
|
|
3164
|
+
`;
|
|
3165
|
+
if (shownDown !== null) m += `<g class="dottie-d"${still2}>
|
|
3166
|
+
${shownDown}</g>
|
|
3167
|
+
`;
|
|
3168
|
+
if (instance) {
|
|
3169
|
+
const h = join(hit);
|
|
3170
|
+
if (h !== null) m += `<g class="dottie-hit" opacity="0" pointer-events="all">
|
|
3171
|
+
${h}</g>
|
|
3172
|
+
`;
|
|
3173
|
+
else {
|
|
3174
|
+
const c = { x: instance.x + instance.width / 2, y: instance.y + instance.height / 2 }, a = instance.rotation ?? 0;
|
|
3175
|
+
const corners2 = [
|
|
3176
|
+
[instance.x, instance.y],
|
|
3177
|
+
[instance.x + instance.width, instance.y],
|
|
3178
|
+
[instance.x + instance.width, instance.y + instance.height],
|
|
3179
|
+
[instance.x, instance.y + instance.height]
|
|
3180
|
+
].map(([x, y]) => rotate({ x, y }, c, a));
|
|
3181
|
+
const path = new Path().moveTo(corners2[0]).lineTo(corners2[1]).lineTo(corners2[2]).lineTo(corners2[3]).close();
|
|
3182
|
+
m += `<path class="dottie-hit" d="${path.data()}" fill="none" stroke="none" pointer-events="all"/>
|
|
3183
|
+
`;
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
return { key: o, markup: m, button: shownDown !== null };
|
|
3187
|
+
}
|
|
2716
3188
|
/** The scene frame `i` is in, or -1 before the first. */
|
|
2717
3189
|
sceneAt(i) {
|
|
2718
3190
|
let s = -1;
|
|
@@ -2742,6 +3214,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2742
3214
|
__publicField(this, "last", 0);
|
|
2743
3215
|
__publicField(this, "acc", 0);
|
|
2744
3216
|
__publicField(this, "raf", 0);
|
|
3217
|
+
__publicField(this, "replayRaf", 0);
|
|
2745
3218
|
__publicField(this, "loadToken", 0);
|
|
2746
3219
|
__publicField(this, "visible", true);
|
|
2747
3220
|
__publicField(this, "observer", null);
|
|
@@ -2775,6 +3248,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2775
3248
|
};
|
|
2776
3249
|
this.next.onclick = () => this.nextScene();
|
|
2777
3250
|
this.prev.onclick = () => this.prevScene();
|
|
3251
|
+
this.stage.addEventListener("click", (e) => {
|
|
3252
|
+
const b = clickable(e.target);
|
|
3253
|
+
if (b) this.activate(b.getAttribute("data-a") ?? "");
|
|
3254
|
+
});
|
|
2778
3255
|
this.addEventListener("keydown", (e) => this.key(e));
|
|
2779
3256
|
}
|
|
2780
3257
|
connectedCallback() {
|
|
@@ -2836,7 +3313,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2836
3313
|
mount() {
|
|
2837
3314
|
const file = this.file;
|
|
2838
3315
|
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
2839
|
-
const p = new Playback(page, this.stage, { clean: this.hasAttribute("clean") });
|
|
3316
|
+
const p = new Playback(page, this.stage, { clean: this.hasAttribute("clean"), file });
|
|
2840
3317
|
this.playback = p;
|
|
2841
3318
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
2842
3319
|
this.style.setProperty("--dottie-ink", color(p.ink));
|
|
@@ -2911,6 +3388,50 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2911
3388
|
this.setPlaying(false);
|
|
2912
3389
|
p.show(s >= 0 ? p.scenes[s] : 0);
|
|
2913
3390
|
}
|
|
3391
|
+
/** Does what clicking element `id` does: jumps to its scene and plays on to the next (with reduced motion, or in a
|
|
3392
|
+
* still, straight to where that stops), opens its link in a new tab, or shows its page. */
|
|
3393
|
+
activate(id) {
|
|
3394
|
+
const p = this.playback;
|
|
3395
|
+
const click = p?.clicks.get(id);
|
|
3396
|
+
const replay = p?.replays.has(id) ?? false;
|
|
3397
|
+
if (!p || !click && !replay) return;
|
|
3398
|
+
const detail = { element: id, ...click ?? {}, ...replay ? { replay: true } : {} };
|
|
3399
|
+
if (!this.dispatchEvent(new CustomEvent("action", { detail, cancelable: true }))) return;
|
|
3400
|
+
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
3401
|
+
if (replay) {
|
|
3402
|
+
const now = performance.now();
|
|
3403
|
+
p.replay(id, reduced ? now - p.replays.get(id) * 1e3 : now);
|
|
3404
|
+
p.redraw();
|
|
3405
|
+
this.scheduleReplay();
|
|
3406
|
+
}
|
|
3407
|
+
if (!click) return;
|
|
3408
|
+
if ("url" in click) {
|
|
3409
|
+
if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
|
|
3410
|
+
} else if ("page" in click) {
|
|
3411
|
+
this.setAttribute("page", String(click.page + 1));
|
|
3412
|
+
} else if (click.scene >= 0 && click.scene < p.scenes.length) {
|
|
3413
|
+
if (reduced || this.hasAttribute("at") || p.frameCount < 2) {
|
|
3414
|
+
this.setPlaying(false);
|
|
3415
|
+
p.show(click.scene + 1 < p.scenes.length ? p.scenes[click.scene + 1] : p.frameCount - 1);
|
|
3416
|
+
} else {
|
|
3417
|
+
p.show(p.scenes[click.scene]);
|
|
3418
|
+
this.setPlaying(true);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
/** Redraws, while the page itself stands still, as long as a clicked copy is playing through. */
|
|
3423
|
+
scheduleReplay() {
|
|
3424
|
+
if (this.replayRaf) return;
|
|
3425
|
+
const step = () => {
|
|
3426
|
+
this.replayRaf = 0;
|
|
3427
|
+
const p = this.playback;
|
|
3428
|
+
if (!p || !this.isConnected) return;
|
|
3429
|
+
if (!this.playing) p.redraw();
|
|
3430
|
+
if (p.replaying()) this.replayRaf = requestAnimationFrame(step);
|
|
3431
|
+
else if (!this.playing) p.redraw();
|
|
3432
|
+
};
|
|
3433
|
+
this.replayRaf = requestAnimationFrame(step);
|
|
3434
|
+
}
|
|
2914
3435
|
setPlaying(on) {
|
|
2915
3436
|
const p = this.playback;
|
|
2916
3437
|
this.playing = on && !!p && p.frameCount > 1 && p.page.timeline !== null;
|
|
@@ -2952,8 +3473,15 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2952
3473
|
}
|
|
2953
3474
|
key(e) {
|
|
2954
3475
|
const p = this.playback;
|
|
3476
|
+
const target = e.composedPath()[0];
|
|
3477
|
+
const b = clickable(target);
|
|
3478
|
+
if (p && b && (e.key === "Enter" || e.key === " ")) {
|
|
3479
|
+
e.preventDefault();
|
|
3480
|
+
this.activate(b.getAttribute("data-a") ?? "");
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
2955
3483
|
if (!p || p.page.timeline === null || p.frameCount <= 1 || this.hasAttribute("at")) return;
|
|
2956
|
-
if (
|
|
3484
|
+
if (target === this.scrub && e.key !== " ") return;
|
|
2957
3485
|
const scenes = p.scenes.length > 0;
|
|
2958
3486
|
let handled = true;
|
|
2959
3487
|
if (e.key === " ") scenes ? this.playing ? this.setPlaying(false) : this.nextScene() : this.setPlaying(!this.playing);
|
|
@@ -2980,6 +3508,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
2980
3508
|
}
|
|
2981
3509
|
};
|
|
2982
3510
|
__publicField(DottiePlayer, "observedAttributes", ["src", "page", "at", "clean", "no-controls", "label"]);
|
|
3511
|
+
function clickable(target) {
|
|
3512
|
+
const el = target;
|
|
3513
|
+
return el && typeof el.closest === "function" ? el.closest(".dottie-a") : null;
|
|
3514
|
+
}
|
|
2983
3515
|
function nearest(p, t) {
|
|
2984
3516
|
let best = 0;
|
|
2985
3517
|
p.plan.times.forEach((x, i) => {
|
|
@@ -3079,7 +3611,7 @@ function render(source, options = {}) {
|
|
|
3079
3611
|
function isFile(v) {
|
|
3080
3612
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
3081
3613
|
}
|
|
3082
|
-
var VERSION = "0.5.
|
|
3614
|
+
var VERSION = "0.5.2";
|
|
3083
3615
|
|
|
3084
3616
|
// src/player-entry.ts
|
|
3085
3617
|
definePlayer();
|
|
@@ -3095,6 +3627,7 @@ export {
|
|
|
3095
3627
|
Playback,
|
|
3096
3628
|
SUPPORTED_VERSION,
|
|
3097
3629
|
VERSION,
|
|
3630
|
+
allowedURL,
|
|
3098
3631
|
black,
|
|
3099
3632
|
bounds,
|
|
3100
3633
|
cameraView,
|