@phin-tech/dottie 0.8.0 → 0.9.0
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 +8 -8
- package/README.md +7 -0
- package/dist/compile.js +7 -2
- package/dist/dottie-compile.mjs +8 -3
- package/dist/dottie-player.js +492 -112
- package/dist/dottie-player.min.js +56 -55
- package/dist/dottie-sim.min.js +4 -0
- package/dist/dottie.js +476 -112
- package/dist/types/compile.d.ts +1 -1
- package/dist/types/connector.d.ts +4 -0
- package/dist/types/flows.d.ts +5 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/measure.d.ts +18 -3
- package/dist/types/planner.d.ts +12 -0
- package/dist/types/player.d.ts +9 -0
- package/dist/types/sim-entry.d.ts +1 -0
- package/dist/types/svg.d.ts +9 -2
- package/dist/types/symbols.d.ts +19 -3
- package/dist/types/timeline.d.ts +4 -2
- package/package.json +1 -1
package/dist/dottie-player.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! dottie.js 0.
|
|
1
|
+
/*! dottie.js 0.9.0 | 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) => {
|
|
@@ -609,15 +609,15 @@ var Reader = class {
|
|
|
609
609
|
const keys = (Array.isArray(v.keys) ? v.keys : []).map((k) => this.keyframe(k)).filter((k) => k !== null && k.t >= 0 && k.t <= MAX_DURATION);
|
|
610
610
|
stableSortBy(keys, (k) => k.t);
|
|
611
611
|
const follow = Array.isArray(v.follow) ? v.follow.filter(isObj).flatMap((f) => {
|
|
612
|
-
const path = uuid(f.path),
|
|
613
|
-
if (!path ||
|
|
614
|
-
return [{ path, start, end, ease: this.ease(f.ease), reverse: bool(f.reverse) ?? null, orient: bool(f.orient) ?? null, laps: num(f.laps) ?? null }];
|
|
612
|
+
const path = uuid(f.path), start2 = num(f.start), end = num(f.end);
|
|
613
|
+
if (!path || start2 === void 0 || end === void 0 || start2 < 0 || end > MAX_DURATION) return [];
|
|
614
|
+
return [{ path, start: start2, end, ease: this.ease(f.ease), reverse: bool(f.reverse) ?? null, orient: bool(f.orient) ?? null, laps: num(f.laps) ?? null }];
|
|
615
615
|
}) : null;
|
|
616
616
|
const repeats = Array.isArray(v.repeats) ? v.repeats.filter(isObj).flatMap((r) => {
|
|
617
|
-
const
|
|
618
|
-
if (
|
|
617
|
+
const start2 = num(r.start), end = num(r.end);
|
|
618
|
+
if (start2 === void 0 || end === void 0) return [];
|
|
619
619
|
const count = num(r.count);
|
|
620
|
-
const rep = { start, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
620
|
+
const rep = { start: start2, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
621
621
|
return repeatIsValid(rep) ? [rep] : [];
|
|
622
622
|
}) : null;
|
|
623
623
|
return { element, keys, boil: bool(v.boil) ?? null, follow, repeats, march: clampMarch(num(v.march)) };
|
|
@@ -842,6 +842,21 @@ function pieces(para) {
|
|
|
842
842
|
if (cur || out.length === 0) out.push(cur);
|
|
843
843
|
return out;
|
|
844
844
|
}
|
|
845
|
+
function prefixWidths(chars, fontSize, face, font) {
|
|
846
|
+
const fam = familyMetrics(font);
|
|
847
|
+
const f = fam.faces[face] ?? fam.faces.regular;
|
|
848
|
+
const fallback = f.w["n"];
|
|
849
|
+
const out = [0];
|
|
850
|
+
let w = 0;
|
|
851
|
+
let prev = "";
|
|
852
|
+
for (const ch of chars) {
|
|
853
|
+
w += f.w[ch] ?? fallback;
|
|
854
|
+
if (prev) w += f.k[prev + ch] ?? 0;
|
|
855
|
+
prev = ch;
|
|
856
|
+
out.push(w * fontSize / 1e3);
|
|
857
|
+
}
|
|
858
|
+
return out;
|
|
859
|
+
}
|
|
845
860
|
function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
846
861
|
const width = (t) => measure(t, fontSize, face, font);
|
|
847
862
|
const out = [];
|
|
@@ -857,8 +872,9 @@ function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
|
857
872
|
}
|
|
858
873
|
while (width(line2.trimEnd()) > maxWidth && [...line2.trimEnd()].length > 1) {
|
|
859
874
|
const chars = [...line2];
|
|
875
|
+
const prefix = prefixWidths(chars, fontSize, face, font);
|
|
860
876
|
let n = chars.length - 1;
|
|
861
|
-
while (n > 1 &&
|
|
877
|
+
while (n > 1 && prefix[n] > maxWidth) n--;
|
|
862
878
|
out.push(chars.slice(0, n).join(""));
|
|
863
879
|
line2 = chars.slice(n).join("");
|
|
864
880
|
}
|
|
@@ -1013,13 +1029,13 @@ var Path = class _Path {
|
|
|
1013
1029
|
contours() {
|
|
1014
1030
|
const out = [];
|
|
1015
1031
|
let current = [];
|
|
1016
|
-
let
|
|
1032
|
+
let start2 = { x: 0, y: 0 };
|
|
1017
1033
|
for (const s of this.segments) {
|
|
1018
|
-
const last = current.length ? current[current.length - 1] :
|
|
1034
|
+
const last = current.length ? current[current.length - 1] : start2;
|
|
1019
1035
|
switch (s.op) {
|
|
1020
1036
|
case "M":
|
|
1021
1037
|
if (current.length > 1) out.push(current);
|
|
1022
|
-
|
|
1038
|
+
start2 = s.p;
|
|
1023
1039
|
current = [s.p];
|
|
1024
1040
|
break;
|
|
1025
1041
|
case "L":
|
|
@@ -1041,9 +1057,9 @@ var Path = class _Path {
|
|
|
1041
1057
|
}
|
|
1042
1058
|
break;
|
|
1043
1059
|
case "Z":
|
|
1044
|
-
current.push(
|
|
1060
|
+
current.push(start2);
|
|
1045
1061
|
if (current.length > 1) out.push(current);
|
|
1046
|
-
current = [
|
|
1062
|
+
current = [start2];
|
|
1047
1063
|
break;
|
|
1048
1064
|
}
|
|
1049
1065
|
}
|
|
@@ -1435,9 +1451,17 @@ function midpoint(e, index) {
|
|
|
1435
1451
|
}
|
|
1436
1452
|
return pts[0];
|
|
1437
1453
|
}
|
|
1454
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
1455
|
+
function isIndexOf(page, index) {
|
|
1456
|
+
return indexes.get(page.elements)?.index === index;
|
|
1457
|
+
}
|
|
1438
1458
|
function indexOf(page) {
|
|
1459
|
+
const els = page.elements;
|
|
1460
|
+
const hit = indexes.get(els);
|
|
1461
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.index;
|
|
1439
1462
|
const m = /* @__PURE__ */ new Map();
|
|
1440
|
-
for (const e of
|
|
1463
|
+
for (const e of els) if (!m.has(e.id)) m.set(e.id, e);
|
|
1464
|
+
indexes.set(els, { members: [...els], index: m });
|
|
1441
1465
|
return m;
|
|
1442
1466
|
}
|
|
1443
1467
|
|
|
@@ -1520,43 +1544,112 @@ function packetID(flow, k) {
|
|
|
1520
1544
|
return flow.slice(0, 24) + (n < 0 ? n + 2 ** 48 : n).toString(16).toUpperCase().padStart(12, "0");
|
|
1521
1545
|
}
|
|
1522
1546
|
var routeCost = (cost) => cost === null || !Number.isFinite(cost) ? 1 : Math.min(Math.max(cost, 0), 1e6);
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
const
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
for (const e of frame2.elements) {
|
|
1547
|
+
var graphs = /* @__PURE__ */ new WeakMap();
|
|
1548
|
+
var networks = /* @__PURE__ */ new Map();
|
|
1549
|
+
function graphOf(frame2) {
|
|
1550
|
+
const els = frame2.elements;
|
|
1551
|
+
const hit = graphs.get(els);
|
|
1552
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.graph;
|
|
1553
|
+
const shapes2 = new Set(els.filter((e) => e.kind !== "connector").map((e) => e.id));
|
|
1554
|
+
const lines = [];
|
|
1555
|
+
for (const e of els) {
|
|
1533
1556
|
const c = e.connector;
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1557
|
+
if (c) lines.push([e.id, c.start.elementID, c.end.elementID, String(routeCost(c.cost)), c.startHead !== "none" ? "both" : "one"]);
|
|
1558
|
+
}
|
|
1559
|
+
const key = JSON.stringify([[...shapes2], lines]);
|
|
1560
|
+
let graph = networks.get(key);
|
|
1561
|
+
if (!graph) {
|
|
1562
|
+
const next = /* @__PURE__ */ new Map();
|
|
1563
|
+
const add2 = (a, edge) => {
|
|
1564
|
+
const list = next.get(a);
|
|
1565
|
+
if (list) list.push(edge);
|
|
1566
|
+
else next.set(a, [edge]);
|
|
1567
|
+
};
|
|
1568
|
+
for (const e of els) {
|
|
1569
|
+
const c = e.connector;
|
|
1570
|
+
const a = c?.start.elementID ?? null, b = c?.end.elementID ?? null;
|
|
1571
|
+
if (!c || a === null || b === null || a === b || !shapes2.has(a) || !shapes2.has(b)) continue;
|
|
1572
|
+
const cost = routeCost(c.cost);
|
|
1573
|
+
add2(a, { to: b, via: e.id, back: false, cost });
|
|
1574
|
+
if (c.startHead !== "none") add2(b, { to: a, via: e.id, back: true, cost });
|
|
1575
|
+
}
|
|
1576
|
+
graph = { shapes: shapes2, next, trees: /* @__PURE__ */ new Map() };
|
|
1577
|
+
if (networks.size >= 16) networks.clear();
|
|
1578
|
+
networks.set(key, graph);
|
|
1579
|
+
}
|
|
1580
|
+
graphs.set(els, { members: [...els], graph });
|
|
1581
|
+
return graph;
|
|
1582
|
+
}
|
|
1583
|
+
var Frontier = class {
|
|
1584
|
+
constructor() {
|
|
1585
|
+
__publicField(this, "heap", []);
|
|
1586
|
+
}
|
|
1587
|
+
get size() {
|
|
1588
|
+
return this.heap.length;
|
|
1589
|
+
}
|
|
1590
|
+
less(i, j) {
|
|
1591
|
+
const a = this.heap[i], b = this.heap[j];
|
|
1592
|
+
return a.dist < b.dist || a.dist === b.dist && a.order < b.order;
|
|
1593
|
+
}
|
|
1594
|
+
swap(i, j) {
|
|
1595
|
+
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
|
|
1596
|
+
}
|
|
1597
|
+
push(id, dist, order) {
|
|
1598
|
+
this.heap.push({ id, dist, order });
|
|
1599
|
+
for (let i = this.heap.length - 1; i > 0; ) {
|
|
1600
|
+
const up = i - 1 >> 1;
|
|
1601
|
+
if (!this.less(i, up)) break;
|
|
1602
|
+
this.swap(i, up);
|
|
1603
|
+
i = up;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
pop() {
|
|
1607
|
+
const top = this.heap[0], last = this.heap.pop();
|
|
1608
|
+
if (this.heap.length > 0) {
|
|
1609
|
+
this.heap[0] = last;
|
|
1610
|
+
for (let i = 0; ; ) {
|
|
1611
|
+
const l = 2 * i + 1, r = l + 1;
|
|
1612
|
+
let m = i;
|
|
1613
|
+
if (l < this.heap.length && this.less(l, m)) m = l;
|
|
1614
|
+
if (r < this.heap.length && this.less(r, m)) m = r;
|
|
1615
|
+
if (m === i) break;
|
|
1616
|
+
this.swap(i, m);
|
|
1617
|
+
i = m;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
return top;
|
|
1539
1621
|
}
|
|
1622
|
+
};
|
|
1623
|
+
function flowTree(from, frame2, offline = /* @__PURE__ */ new Set(), until) {
|
|
1624
|
+
const graph = graphOf(frame2);
|
|
1625
|
+
const key = JSON.stringify([from, until ?? null, [...offline].sort()]);
|
|
1626
|
+
if (graph.trees.has(key)) return graph.trees.get(key);
|
|
1627
|
+
const tree = grow(graph, from, offline, until);
|
|
1628
|
+
if (graph.trees.size >= 4096) graph.trees.clear();
|
|
1629
|
+
graph.trees.set(key, tree);
|
|
1630
|
+
return tree;
|
|
1631
|
+
}
|
|
1632
|
+
function grow({ shapes: shapes2, next }, from, offline, until) {
|
|
1633
|
+
if (!shapes2.has(from) || offline.has(from)) return null;
|
|
1540
1634
|
const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
|
|
1541
1635
|
let counter = 1;
|
|
1542
1636
|
const tree = new FlowTree(from);
|
|
1543
|
-
const open =
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
if (da < db || da === db && order.get(open[i]) < order.get(open[best])) best = i;
|
|
1549
|
-
}
|
|
1550
|
-
const n = open.splice(best, 1)[0];
|
|
1637
|
+
const open = new Frontier();
|
|
1638
|
+
open.push(from, 0, 0);
|
|
1639
|
+
while (open.size > 0) {
|
|
1640
|
+
const { id: n, order: o } = open.pop();
|
|
1641
|
+
if (tree.settled.has(n) || order.get(n) !== o) continue;
|
|
1551
1642
|
tree.settled.add(n);
|
|
1643
|
+
if (n === until && n !== from) break;
|
|
1552
1644
|
for (const edge of next.get(n) ?? []) {
|
|
1645
|
+
if (offline.has(edge.via) || offline.has(edge.to)) continue;
|
|
1553
1646
|
if (tree.settled.has(edge.to)) continue;
|
|
1554
1647
|
const d = dist.get(n) + edge.cost;
|
|
1555
1648
|
const old = dist.get(edge.to);
|
|
1556
1649
|
if (old !== void 0 && old <= d) continue;
|
|
1557
|
-
if (old === void 0) open.push(edge.to);
|
|
1558
1650
|
dist.set(edge.to, d);
|
|
1559
|
-
order.set(edge.to, counter
|
|
1651
|
+
order.set(edge.to, counter);
|
|
1652
|
+
open.push(edge.to, d, counter++);
|
|
1560
1653
|
tree.came.set(edge.to, { from: n, via: edge.via, back: edge.back });
|
|
1561
1654
|
}
|
|
1562
1655
|
}
|
|
@@ -1636,8 +1729,16 @@ function delayOf(e) {
|
|
|
1636
1729
|
var queueKey = (shape, flow) => shape + "|" + flow;
|
|
1637
1730
|
function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set(), sinceChange = null, carried = {}) {
|
|
1638
1731
|
const since = Math.max(0, Math.min(sinceChange ?? t, 1e6));
|
|
1639
|
-
const byID =
|
|
1640
|
-
|
|
1732
|
+
const byID = indexOf(frame2);
|
|
1733
|
+
const traced = /* @__PURE__ */ new Map();
|
|
1734
|
+
const traceOf = (c) => {
|
|
1735
|
+
let pts = traced.get(c.id);
|
|
1736
|
+
if (!pts) {
|
|
1737
|
+
pts = tracing(c, frame2, byID).points;
|
|
1738
|
+
traced.set(c.id, pts);
|
|
1739
|
+
}
|
|
1740
|
+
return pts;
|
|
1741
|
+
};
|
|
1641
1742
|
const metrics2 = emptyMetrics();
|
|
1642
1743
|
for (const e of frame2.elements) {
|
|
1643
1744
|
if (e.kind === "connector" && !e.connector) continue;
|
|
@@ -1667,7 +1768,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1667
1768
|
hops.forEach(([id, back], k) => {
|
|
1668
1769
|
const c = byID.get(id);
|
|
1669
1770
|
if (!c) return;
|
|
1670
|
-
const line2 = back ? [...
|
|
1771
|
+
const line2 = back ? [...traceOf(c)].reverse() : traceOf(c);
|
|
1671
1772
|
if (line2.length === 0) return;
|
|
1672
1773
|
const last = points[points.length - 1];
|
|
1673
1774
|
if (last) {
|
|
@@ -1676,9 +1777,9 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1676
1777
|
inside.push([length, length + gap, shape ? delayOf(shape) : 0]);
|
|
1677
1778
|
length += gap;
|
|
1678
1779
|
}
|
|
1679
|
-
const
|
|
1780
|
+
const start2 = length;
|
|
1680
1781
|
for (let q = 1; q < line2.length; q++) length += Math.hypot(line2[q].x - line2[q - 1].x, line2[q].y - line2[q - 1].y);
|
|
1681
|
-
stretches.push([id,
|
|
1782
|
+
stretches.push([id, start2, length]);
|
|
1682
1783
|
points.push(...line2);
|
|
1683
1784
|
});
|
|
1684
1785
|
return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
|
|
@@ -1713,22 +1814,27 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1713
1814
|
const packets = [];
|
|
1714
1815
|
const waitingBy = /* @__PURE__ */ new Map();
|
|
1715
1816
|
const everyOf = (flow) => Math.min(Math.max(clamp(flow.every, DEFAULT_EVERY, 0.05, 60) * biggest / Math.max(amountIn(flow, values), TINY), 0.05), 60);
|
|
1817
|
+
const bases = /* @__PURE__ */ new Map();
|
|
1818
|
+
const baseTree = (from) => {
|
|
1819
|
+
if (!bases.has(from)) bases.set(from, flowTree(from, frame2, offline));
|
|
1820
|
+
return bases.get(from);
|
|
1821
|
+
};
|
|
1716
1822
|
for (const [i, flow] of list.entries()) {
|
|
1717
1823
|
if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
|
|
1718
1824
|
const source = byID.get(flow.from);
|
|
1719
|
-
const base2 = source ?
|
|
1825
|
+
const base2 = source ? baseTree(flow.from) : null;
|
|
1720
1826
|
if (!source || !base2) continue;
|
|
1721
1827
|
const volume = amountIn(flow, values);
|
|
1722
1828
|
const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
|
|
1723
1829
|
const placements = [];
|
|
1724
|
-
const
|
|
1830
|
+
const waiting2 = [];
|
|
1725
1831
|
const goals = base2.goals(flow.to, flow.spread === true);
|
|
1726
1832
|
const shares = goals.length > 1 ? goals.map((g) => [[g], volume / goals.length]) : [[flow.to, volume]];
|
|
1727
1833
|
for (const [candidates, share] of shares) {
|
|
1728
1834
|
if (!(share > TINY)) continue;
|
|
1729
1835
|
let left = share;
|
|
1730
1836
|
for (let n = 0; n < 32 && left > TINY; n++) {
|
|
1731
|
-
const tree = flowTree(flow.from, frame2, union2(offline, full()));
|
|
1837
|
+
const tree = flowTree(flow.from, frame2, union2(offline, full()), candidates[0]);
|
|
1732
1838
|
const goal2 = tree?.goals(candidates, false)[0];
|
|
1733
1839
|
if (!tree || goal2 === void 0) break;
|
|
1734
1840
|
const r = route2(tree, goal2);
|
|
@@ -1768,18 +1874,18 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1768
1874
|
placements.push({ hops, shapes: shapes2, goal: at, amount: left, waits: true });
|
|
1769
1875
|
}
|
|
1770
1876
|
}
|
|
1771
|
-
const existing =
|
|
1877
|
+
const existing = waiting2.find(([s]) => s === at);
|
|
1772
1878
|
if (existing) existing[1] += left;
|
|
1773
|
-
else
|
|
1879
|
+
else waiting2.push([at, left]);
|
|
1774
1880
|
}
|
|
1775
|
-
waitingBy.set(flow.id,
|
|
1881
|
+
waitingBy.set(flow.id, waiting2);
|
|
1776
1882
|
for (const p of placements) {
|
|
1777
1883
|
for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1778
1884
|
m.throughput += p.amount;
|
|
1779
1885
|
});
|
|
1780
1886
|
if (!p.waits) fm.placed += p.amount;
|
|
1781
1887
|
}
|
|
1782
|
-
for (const [at, a] of
|
|
1888
|
+
for (const [at, a] of waiting2) {
|
|
1783
1889
|
bump(at, (m) => {
|
|
1784
1890
|
m.backlog += a;
|
|
1785
1891
|
});
|
|
@@ -1824,10 +1930,12 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1824
1930
|
metrics2.flows.set(flow.id, fm);
|
|
1825
1931
|
}
|
|
1826
1932
|
const piles = /* @__PURE__ */ new Map();
|
|
1933
|
+
const carriedKeys = Object.keys(carried).filter((k) => carried[k] > TINY);
|
|
1827
1934
|
for (const flow of list) {
|
|
1828
|
-
const
|
|
1829
|
-
const shapes2 =
|
|
1830
|
-
|
|
1935
|
+
const waiting2 = waitingBy.get(flow.id) ?? [];
|
|
1936
|
+
const shapes2 = waiting2.map(([s]) => s);
|
|
1937
|
+
const suffix = "|" + flow.id;
|
|
1938
|
+
if (carriedKeys.some((k) => k.endsWith(suffix))) for (const e of frame2.elements) {
|
|
1831
1939
|
const c = carried[queueKey(e.id, flow.id)];
|
|
1832
1940
|
if (c !== void 0 && c > TINY && !shapes2.includes(e.id)) shapes2.push(e.id);
|
|
1833
1941
|
}
|
|
@@ -1841,7 +1949,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1841
1949
|
const shape = byID.get(at);
|
|
1842
1950
|
if (!shape) return;
|
|
1843
1951
|
const layer = byID.get(flow.from)?.layerID ?? shape.layerID;
|
|
1844
|
-
const backlog =
|
|
1952
|
+
const backlog = waiting2.find(([s]) => s === at)?.[1] ?? 0;
|
|
1845
1953
|
const before = Math.max(0, carried[queueKey(at, flow.id)] ?? 0);
|
|
1846
1954
|
let depth = before + backlog * since;
|
|
1847
1955
|
if (backlog <= TINY && before > TINY) {
|
|
@@ -1850,7 +1958,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1850
1958
|
let total = 0;
|
|
1851
1959
|
for (let n2 = 0; n2 < 32; n2++) {
|
|
1852
1960
|
const blocked = new Set([...trial].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1853
|
-
const tree = flowTree(at, frame2, union2(offline, blocked));
|
|
1961
|
+
const tree = flowTree(at, frame2, union2(offline, blocked), flow.to[0]);
|
|
1854
1962
|
const goal = tree?.goals(flow.to, false)[0];
|
|
1855
1963
|
if (!tree || goal === void 0) break;
|
|
1856
1964
|
const r = route2(tree, goal);
|
|
@@ -1963,21 +2071,21 @@ function metricLookup(m, frame2) {
|
|
|
1963
2071
|
return id === null ? null : metricValue(m, term.metric, id);
|
|
1964
2072
|
};
|
|
1965
2073
|
}
|
|
1966
|
-
function
|
|
2074
|
+
function finishPlan(elements, metrics2, values, frame2, since = Infinity, carried = {}) {
|
|
1967
2075
|
const lookup = metricLookup(metrics2, frame2);
|
|
1968
|
-
const
|
|
1969
|
-
for (
|
|
2076
|
+
const plan2 = [], readings = {};
|
|
2077
|
+
for (const e of elements) {
|
|
2078
|
+
const applied = [];
|
|
1970
2079
|
if (values !== null && e.overrides) {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
}
|
|
2080
|
+
e.overrides.forEach((o, k) => {
|
|
2081
|
+
if (mentionsMetric(o.when) && holds(o.when, values, (m) => lookup(m, e.id))) applied.push(k);
|
|
2082
|
+
});
|
|
1975
2083
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
2084
|
+
const g = values === null ? null : e.gauge;
|
|
2085
|
+
if (!g) {
|
|
2086
|
+
plan2.push({ applied, gauge: null });
|
|
1978
2087
|
continue;
|
|
1979
2088
|
}
|
|
1980
|
-
const g = e.gauge;
|
|
1981
2089
|
let shown = metricValue(metrics2, g.metric, g.of);
|
|
1982
2090
|
const then = carried[gaugeKey(e.id)];
|
|
1983
2091
|
if (shown !== null && then !== void 0 && g.metric !== "depth" && g.metric !== "inFlight" && since < GAUGE_EASE) {
|
|
@@ -1985,8 +2093,19 @@ function finishing(elements, metrics2, values, frame2, flows, since = Infinity,
|
|
|
1985
2093
|
shown = then + (shown - then) * smooth;
|
|
1986
2094
|
}
|
|
1987
2095
|
if (shown !== null) readings[gaugeKey(e.id)] = shown;
|
|
1988
|
-
|
|
2096
|
+
plan2.push({ applied, gauge: { value: shown } });
|
|
1989
2097
|
}
|
|
2098
|
+
return { plan: plan2, readings };
|
|
2099
|
+
}
|
|
2100
|
+
function finishing(elements, metrics2, values, frame2, flows, since = Infinity, carried = {}) {
|
|
2101
|
+
const { plan: plan2, readings } = finishPlan(elements, metrics2, values, frame2, since, carried);
|
|
2102
|
+
const out = [];
|
|
2103
|
+
elements.forEach((e, i) => {
|
|
2104
|
+
for (const k of plan2[i].applied) e = applyOverride(looksOnly(e.overrides[k]), e);
|
|
2105
|
+
const g = plan2[i].gauge;
|
|
2106
|
+
if (g && e.gauge) out.push(...gauge(e, e.gauge, g.value, frame2, flows));
|
|
2107
|
+
else out.push(e);
|
|
2108
|
+
});
|
|
1990
2109
|
return { elements: out, readings };
|
|
1991
2110
|
}
|
|
1992
2111
|
var gaugeKey = (id) => "gauge|" + id;
|
|
@@ -2132,7 +2251,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
2132
2251
|
const bow = offset(rng, length / 60, roughness) * bowing * gain;
|
|
2133
2252
|
const dx = b.x - a.x, dy = b.y - a.y;
|
|
2134
2253
|
const nx = -dy / length, ny = dx / length;
|
|
2135
|
-
const
|
|
2254
|
+
const start2 = preserveEnds ? a : (() => {
|
|
2136
2255
|
const x = a.x + j();
|
|
2137
2256
|
return { x, y: a.y + j() };
|
|
2138
2257
|
})();
|
|
@@ -2144,7 +2263,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
2144
2263
|
const c1y = a.y + dy * mid * 0.66 + ny * bow + j() * 0.5;
|
|
2145
2264
|
const c2x = a.x + dx * (0.5 + mid * 0.5) + nx * bow + j() * 0.5;
|
|
2146
2265
|
const c2y = a.y + dy * (0.5 + mid * 0.5) + ny * bow + j() * 0.5;
|
|
2147
|
-
into.moveTo(
|
|
2266
|
+
into.moveTo(start2);
|
|
2148
2267
|
into.curveTo(end, { x: c1x, y: c1y }, { x: c2x, y: c2y });
|
|
2149
2268
|
}
|
|
2150
2269
|
}
|
|
@@ -2405,10 +2524,10 @@ var Cache = class {
|
|
|
2405
2524
|
__publicField(this, "limit");
|
|
2406
2525
|
this.limit = limit;
|
|
2407
2526
|
}
|
|
2408
|
-
get(key,
|
|
2527
|
+
get(key, make2) {
|
|
2409
2528
|
const hit = this.map.get(key);
|
|
2410
2529
|
if (hit !== void 0) return hit;
|
|
2411
|
-
const v =
|
|
2530
|
+
const v = make2();
|
|
2412
2531
|
if (this.map.size >= this.limit) this.map.clear();
|
|
2413
2532
|
this.map.set(key, v);
|
|
2414
2533
|
return v;
|
|
@@ -2632,26 +2751,59 @@ function subCubic(p, t0, t1) {
|
|
|
2632
2751
|
const left = split(p, t1)[0];
|
|
2633
2752
|
return t1 > 0 ? split(left, t0 / t1)[1] : [p[0], p[0], p[0], p[0]];
|
|
2634
2753
|
}
|
|
2754
|
+
function boxOf(pts) {
|
|
2755
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
2756
|
+
for (const p of pts) {
|
|
2757
|
+
x0 = Math.min(x0, p.x);
|
|
2758
|
+
y0 = Math.min(y0, p.y);
|
|
2759
|
+
x1 = Math.max(x1, p.x);
|
|
2760
|
+
y1 = Math.max(y1, p.y);
|
|
2761
|
+
}
|
|
2762
|
+
return { x0, y0, x1, y1 };
|
|
2763
|
+
}
|
|
2764
|
+
function touching(a, b) {
|
|
2765
|
+
const eps = 1e-6;
|
|
2766
|
+
return a.x0 <= b.x1 + eps && b.x0 <= a.x1 + eps && a.y0 <= b.y1 + eps && b.y0 <= a.y1 + eps;
|
|
2767
|
+
}
|
|
2768
|
+
var hopFrames = /* @__PURE__ */ new WeakMap();
|
|
2769
|
+
function linesBeneath(e, page, index) {
|
|
2770
|
+
let f = hopFrames.get(index);
|
|
2771
|
+
if (!f || f.layers !== page.layers || !isIndexOf(page, index)) {
|
|
2772
|
+
const visible = new Set(page.layers.filter((l) => l.visible).map((l) => l.id));
|
|
2773
|
+
const first = /* @__PURE__ */ new Map(), under = [];
|
|
2774
|
+
for (const o of orderedElements(page)) {
|
|
2775
|
+
if (!first.has(o.id)) first.set(o.id, under.length);
|
|
2776
|
+
if (o.kind === "connector" && visible.has(o.layerID)) under.push(o);
|
|
2777
|
+
}
|
|
2778
|
+
f = { layers: page.layers, first, under, lines: [] };
|
|
2779
|
+
if (isIndexOf(page, index)) hopFrames.set(index, f);
|
|
2780
|
+
}
|
|
2781
|
+
const count = f.first.get(e.id) ?? f.under.length;
|
|
2782
|
+
while (f.lines.length < count) {
|
|
2783
|
+
const points = routePoints(route(f.under[f.lines.length], index));
|
|
2784
|
+
f.lines.push({ points, box: boxOf(points) });
|
|
2785
|
+
}
|
|
2786
|
+
return { lines: f.lines, count };
|
|
2787
|
+
}
|
|
2635
2788
|
function hopCrossings(e, r, page, index) {
|
|
2636
2789
|
const segments = hopSamples(r);
|
|
2637
2790
|
if (segments.length === 0) return [];
|
|
2638
|
-
const
|
|
2639
|
-
const beneath = [];
|
|
2640
|
-
for (const o of orderedElements(page)) {
|
|
2641
|
-
if (o.id === e.id) break;
|
|
2642
|
-
if (o.kind !== "connector" || !visible.has(o.layerID)) continue;
|
|
2643
|
-
beneath.push(routePoints(route(o, index)));
|
|
2644
|
-
}
|
|
2791
|
+
const { lines, count } = linesBeneath(e, page, index);
|
|
2645
2792
|
const rad = hopRadius(e.style.strokeWidth);
|
|
2646
2793
|
const out = [];
|
|
2647
2794
|
for (const samples of segments) {
|
|
2648
2795
|
const along = runningLengths(samples);
|
|
2649
2796
|
const length = along[along.length - 1];
|
|
2650
2797
|
const found = [];
|
|
2798
|
+
const near = [];
|
|
2799
|
+
const whole = boxOf(samples);
|
|
2800
|
+
for (let n = 0; n < count; n++) if (touching(whole, lines[n].box)) near.push(lines[n]);
|
|
2651
2801
|
for (let i = 0; i < samples.length - 1; i++) {
|
|
2652
2802
|
const p = samples[i], q = samples[i + 1];
|
|
2653
2803
|
const dx = q.x - p.x, dy = q.y - p.y;
|
|
2654
|
-
|
|
2804
|
+
const box = boxOf([p, q]);
|
|
2805
|
+
for (const { points: line2, box: lineBox } of near) {
|
|
2806
|
+
if (!touching(box, lineBox)) continue;
|
|
2655
2807
|
for (let k = 0; k < line2.length - 1; k++) {
|
|
2656
2808
|
const a = line2[k], b = line2[k + 1];
|
|
2657
2809
|
const ex = b.x - a.x, ey = b.y - a.y;
|
|
@@ -2931,8 +3083,8 @@ function followSpan(track, t) {
|
|
|
2931
3083
|
if (track.keys.some((k) => k.t >= span.end && k.t <= t && (k.x !== void 0 || k.y !== void 0))) return null;
|
|
2932
3084
|
return f;
|
|
2933
3085
|
}
|
|
2934
|
-
function tracing(e, page) {
|
|
2935
|
-
if (e.kind === "connector") return new Polyline(routePoints(route(e,
|
|
3086
|
+
function tracing(e, page, index = indexOf(page)) {
|
|
3087
|
+
if (e.kind === "connector") return new Polyline(routePoints(route(e, index)));
|
|
2936
3088
|
const clean = cloneElement(e);
|
|
2937
3089
|
clean.style.roughness = 0;
|
|
2938
3090
|
const pts = outline(clean).contours()[0] ?? [];
|
|
@@ -2942,9 +3094,10 @@ function following(track, e, t, page) {
|
|
|
2942
3094
|
const f = followSpan(track, t);
|
|
2943
3095
|
if (!f) return e;
|
|
2944
3096
|
t = localTime(track, t);
|
|
2945
|
-
const
|
|
3097
|
+
const index = indexOf(page);
|
|
3098
|
+
const pathElement = index.get(f.path);
|
|
2946
3099
|
if (!pathElement || pathElement.id === e.id) return e;
|
|
2947
|
-
const line2 = tracing(pathElement, page);
|
|
3100
|
+
const line2 = tracing(pathElement, page, index);
|
|
2948
3101
|
const span = f.end - f.start;
|
|
2949
3102
|
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);
|
|
2950
3103
|
if (u > 1) u = rem(u, 1) === 0 ? 1 : rem(u, 1);
|
|
@@ -3199,17 +3352,30 @@ function symbolPage(s) {
|
|
|
3199
3352
|
function hasInstances(page) {
|
|
3200
3353
|
return page.elements.some((e) => e.kind === "symbol");
|
|
3201
3354
|
}
|
|
3202
|
-
|
|
3355
|
+
var Expansions = class {
|
|
3356
|
+
constructor(page, t, posing) {
|
|
3357
|
+
__publicField(this, "page");
|
|
3358
|
+
__publicField(this, "t");
|
|
3359
|
+
__publicField(this, "posing");
|
|
3360
|
+
/** For each instance: its time override, the budget it found and left, and what it made. */
|
|
3361
|
+
__publicField(this, "made", /* @__PURE__ */ new Map());
|
|
3362
|
+
this.page = page;
|
|
3363
|
+
this.t = t;
|
|
3364
|
+
this.posing = posing;
|
|
3365
|
+
}
|
|
3366
|
+
};
|
|
3367
|
+
function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map(), memo) {
|
|
3203
3368
|
if (!hasInstances(page)) return page;
|
|
3204
3369
|
const budget = { left: MAX_ELEMENTS };
|
|
3205
|
-
|
|
3370
|
+
const m = memo && memo.page === page && memo.t === t && memo.posing === posing ? memo : void 0;
|
|
3371
|
+
return { ...page, elements: expand(page.elements, page, t, symbolTable(page), [], posing, budget, times, m) };
|
|
3206
3372
|
}
|
|
3207
3373
|
function symbolTable(page) {
|
|
3208
3374
|
const table = /* @__PURE__ */ new Map();
|
|
3209
3375
|
for (const s of page.symbols ?? []) if (!table.has(s.id)) table.set(s.id, s);
|
|
3210
3376
|
return table;
|
|
3211
3377
|
}
|
|
3212
|
-
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map()) {
|
|
3378
|
+
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map(), memo) {
|
|
3213
3379
|
const instances = new Set(elements.filter((e) => e.kind === "symbol").map((e) => e.id));
|
|
3214
3380
|
if (instances.size === 0) {
|
|
3215
3381
|
budget.left -= elements.length;
|
|
@@ -3234,6 +3400,14 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
3234
3400
|
budget.left -= 1;
|
|
3235
3401
|
continue;
|
|
3236
3402
|
}
|
|
3403
|
+
const override0 = stack.length === 0 ? times.get(e.id) : void 0;
|
|
3404
|
+
const was = memo?.made.get(e);
|
|
3405
|
+
if (was && was.override === override0 && was.after > 0 && budget.left - (was.before - was.after) > 0) {
|
|
3406
|
+
budget.left -= was.before - was.after;
|
|
3407
|
+
for (const x of was.out) out.push(x);
|
|
3408
|
+
continue;
|
|
3409
|
+
}
|
|
3410
|
+
const before = budget.left, from = out.length;
|
|
3237
3411
|
budget.left -= 1;
|
|
3238
3412
|
const id = e.symbol;
|
|
3239
3413
|
const symbol = id === null ? void 0 : table.get(id);
|
|
@@ -3249,6 +3423,7 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
3249
3423
|
layers: [{ id: e.layerID, name: "", visible: true, locked: false }]
|
|
3250
3424
|
};
|
|
3251
3425
|
for (const x of expand(drawn.elements, drawn, u, table, [...stack, id], posing, budget)) out.push(placed(x, e, symbol));
|
|
3426
|
+
if (memo && stack.length === 0 && !was) memo.made.set(e, { override: override0, before, after: budget.left, out: out.slice(from) });
|
|
3252
3427
|
}
|
|
3253
3428
|
return out;
|
|
3254
3429
|
}
|
|
@@ -3516,13 +3691,13 @@ function blocks(lines) {
|
|
|
3516
3691
|
flush();
|
|
3517
3692
|
return out;
|
|
3518
3693
|
}
|
|
3519
|
-
function parseList(lines,
|
|
3520
|
-
const first = reItem.exec(lines[
|
|
3694
|
+
function parseList(lines, start2) {
|
|
3695
|
+
const first = reItem.exec(lines[start2]);
|
|
3521
3696
|
const indent = first[1].length;
|
|
3522
3697
|
const bullet = /^\d/.test(first[2]) ? null : first[2];
|
|
3523
3698
|
const ordered = bullet === null ? parseInt(first[2], 10) : null;
|
|
3524
3699
|
const items = [];
|
|
3525
|
-
let i =
|
|
3700
|
+
let i = start2;
|
|
3526
3701
|
while (i < lines.length) {
|
|
3527
3702
|
const m = reItem.exec(lines[i]);
|
|
3528
3703
|
if (!m || m[1].length !== indent || bullet === null !== /^\d/.test(m[2]) || bullet !== null && m[2] !== bullet) break;
|
|
@@ -3851,10 +4026,10 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3851
4026
|
let k = 0;
|
|
3852
4027
|
let firstLine = true;
|
|
3853
4028
|
while (k < chars.length || firstLine && chars.length === 0) {
|
|
3854
|
-
const
|
|
4029
|
+
const start2 = firstLine ? p.firstIndent : p.indent;
|
|
3855
4030
|
const maxX2 = width;
|
|
3856
4031
|
const glyphs = [];
|
|
3857
|
-
let x =
|
|
4032
|
+
let x = start2, lastSpace = -1, end = k;
|
|
3858
4033
|
for (; end < chars.length; end++) {
|
|
3859
4034
|
const { ch, run } = chars[end];
|
|
3860
4035
|
const r = runs[run];
|
|
@@ -3903,7 +4078,7 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3903
4078
|
}
|
|
3904
4079
|
if (glyphs.length === 0 && end === k && k < chars.length) {
|
|
3905
4080
|
const { ch, run } = chars[k];
|
|
3906
|
-
glyphs.push({ ch, run, x:
|
|
4081
|
+
glyphs.push({ ch, run, x: start2, w: charWidth(ch, runs[run], fam) });
|
|
3907
4082
|
end = k + 1;
|
|
3908
4083
|
}
|
|
3909
4084
|
let asc = 0, desc = 0, lead = 0;
|
|
@@ -3929,13 +4104,38 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3929
4104
|
});
|
|
3930
4105
|
return lines;
|
|
3931
4106
|
}
|
|
4107
|
+
var Memo = class {
|
|
4108
|
+
constructor(limit) {
|
|
4109
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
4110
|
+
__publicField(this, "limit");
|
|
4111
|
+
this.limit = limit;
|
|
4112
|
+
}
|
|
4113
|
+
get(key, make2) {
|
|
4114
|
+
const hit = this.map.get(key);
|
|
4115
|
+
if (hit !== void 0) return hit;
|
|
4116
|
+
const v = make2();
|
|
4117
|
+
if (this.map.size >= this.limit) this.map.clear();
|
|
4118
|
+
this.map.set(key, v);
|
|
4119
|
+
return v;
|
|
4120
|
+
}
|
|
4121
|
+
};
|
|
4122
|
+
var layouts = new Memo(500);
|
|
4123
|
+
var markups = new Memo(500);
|
|
3932
4124
|
function noteMarkup(source, e) {
|
|
4125
|
+
const st = e.style, ink = st.stroke;
|
|
4126
|
+
const key = JSON.stringify([...[e.x, e.y, e.width, e.height, st.fontSize, ink.r, ink.g, ink.b, ink.a].map(String), st.font ?? "", source]);
|
|
4127
|
+
return markups.get(key, () => computeNoteMarkup(source, e));
|
|
4128
|
+
}
|
|
4129
|
+
function computeNoteMarkup(source, e) {
|
|
3933
4130
|
const rect2 = textRect({ x: e.x, y: e.y, width: e.width, height: e.height });
|
|
3934
4131
|
if (!(rect2.width > 1 && rect2.height > 1)) return "";
|
|
3935
4132
|
const fam = familyMetrics(e.style.font);
|
|
3936
|
-
const
|
|
3937
|
-
|
|
3938
|
-
|
|
4133
|
+
const layoutKey = JSON.stringify([...[rect2.width, rect2.height, e.style.fontSize].map(String), e.style.font ?? "", source]);
|
|
4134
|
+
const lines = layouts.get(layoutKey, () => {
|
|
4135
|
+
const b = new Builder(e.style.fontSize, rect2.width, fam);
|
|
4136
|
+
for (const block of parseBlocks(source)) b.block(block, { indent: 0, marker: null, quote: null, listDepth: 0 });
|
|
4137
|
+
return layoutLines(b.paras, rect2.width, rect2.height, e.style.fontSize, fam);
|
|
4138
|
+
});
|
|
3939
4139
|
const ink = color(e.style.stroke);
|
|
3940
4140
|
let s = "";
|
|
3941
4141
|
const marks = [];
|
|
@@ -4024,6 +4224,26 @@ function bounds(page, padding = 24) {
|
|
|
4024
4224
|
return inset(r, -padding, -padding);
|
|
4025
4225
|
}
|
|
4026
4226
|
function plan(page) {
|
|
4227
|
+
const hit = plans.get(page);
|
|
4228
|
+
if (hit && unchanged(page, hit.fields)) return hit.plan;
|
|
4229
|
+
const made = makePlan(page);
|
|
4230
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4231
|
+
return made;
|
|
4232
|
+
}
|
|
4233
|
+
var plans = /* @__PURE__ */ new WeakMap();
|
|
4234
|
+
function hasPlan(page) {
|
|
4235
|
+
const hit = plans.get(page);
|
|
4236
|
+
return !!hit && unchanged(page, hit.fields);
|
|
4237
|
+
}
|
|
4238
|
+
function rememberPlan(page, made) {
|
|
4239
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4240
|
+
}
|
|
4241
|
+
function unchanged(page, fields) {
|
|
4242
|
+
const a = page, b = fields;
|
|
4243
|
+
const keys = Object.keys(a);
|
|
4244
|
+
return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
|
|
4245
|
+
}
|
|
4246
|
+
function makePlan(page) {
|
|
4027
4247
|
const tl = page.timeline ?? { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
4028
4248
|
const times = frameTimes(tl);
|
|
4029
4249
|
const first = tl.camera?.find((k) => k.width > 1 && k.height > 1);
|
|
@@ -4051,8 +4271,8 @@ function svgOpen(b, background, paint) {
|
|
|
4051
4271
|
}
|
|
4052
4272
|
return out;
|
|
4053
4273
|
}
|
|
4054
|
-
function svgParts(page, background, fixed, links = false) {
|
|
4055
|
-
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links);
|
|
4274
|
+
function svgParts(page, background, fixed, links = false, only) {
|
|
4275
|
+
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links, only);
|
|
4056
4276
|
const theme = themeNamed(page.theme);
|
|
4057
4277
|
const bg = background ? theme?.background ?? background : null;
|
|
4058
4278
|
const ink = theme?.ink ?? black;
|
|
@@ -4063,11 +4283,11 @@ function svgParts(page, background, fixed, links = false) {
|
|
|
4063
4283
|
const index = indexOf(d);
|
|
4064
4284
|
const ordered = orderedElements(d);
|
|
4065
4285
|
const floors = ordered.filter((e) => visible.has(e.layerID) && isFloor(e));
|
|
4066
|
-
const rockMarkup = rock(floors).map((r) => `<path d="${r.path.data()}" fill="none" stroke="${color(r.ink)}"${opacity(r.ink, "stroke-opacity")} stroke-width="${fmt(r.width)}" stroke-linecap="round"/>
|
|
4286
|
+
const rockMarkup = only && !only("rock") ? "" : rock(floors).map((r) => `<path d="${r.path.data()}" fill="none" stroke="${color(r.ink)}"${opacity(r.ink, "stroke-opacity")} stroke-width="${fmt(r.width)}" stroke-linecap="round"/>
|
|
4067
4287
|
`).join("");
|
|
4068
4288
|
if (rockMarkup) parts.push({ key: "rock", markup: rockMarkup });
|
|
4069
4289
|
for (const e of ordered) {
|
|
4070
|
-
if (!visible.has(e.layerID)) continue;
|
|
4290
|
+
if (!visible.has(e.layerID) || only && !only(e.id)) continue;
|
|
4071
4291
|
const alpha = Math.min(Math.max(e.style.opacity ?? 1, 0), 1);
|
|
4072
4292
|
if (alpha <= 0) continue;
|
|
4073
4293
|
let body = svgElement(e, index, bg, d);
|
|
@@ -4215,6 +4435,110 @@ function textElement(text, f, e, centered) {
|
|
|
4215
4435
|
return s + "</text>\n";
|
|
4216
4436
|
}
|
|
4217
4437
|
|
|
4438
|
+
// src/planner.ts
|
|
4439
|
+
var WORKER_WORK = 3e4;
|
|
4440
|
+
function planWork(page) {
|
|
4441
|
+
const tl = page.timeline;
|
|
4442
|
+
if (!tl || tl.camera?.some((k) => k.width > 1 && k.height > 1)) return 0;
|
|
4443
|
+
const elements = page.elements.length + (page.symbols ?? []).reduce((n, s) => n + s.elements.length, 0);
|
|
4444
|
+
return frameTimes(tl).length * elements;
|
|
4445
|
+
}
|
|
4446
|
+
function workerMaker() {
|
|
4447
|
+
if (typeof Worker === "undefined" || false) return null;
|
|
4448
|
+
if (false) {
|
|
4449
|
+
const source = `(${dottiePlayerScript.toString()})();
|
|
4450
|
+
`;
|
|
4451
|
+
return () => {
|
|
4452
|
+
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
4453
|
+
try {
|
|
4454
|
+
return new Worker(url);
|
|
4455
|
+
} finally {
|
|
4456
|
+
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
4457
|
+
}
|
|
4458
|
+
};
|
|
4459
|
+
}
|
|
4460
|
+
if (true) {
|
|
4461
|
+
const here = import.meta.url;
|
|
4462
|
+
if (!here) return null;
|
|
4463
|
+
return () => new Worker(new URL("./dottie-player.min.js", here));
|
|
4464
|
+
}
|
|
4465
|
+
return null;
|
|
4466
|
+
}
|
|
4467
|
+
var make;
|
|
4468
|
+
var worker = null;
|
|
4469
|
+
var failed = false;
|
|
4470
|
+
var nextID = 1;
|
|
4471
|
+
var waiting = /* @__PURE__ */ new Map();
|
|
4472
|
+
function plansInWorker(page) {
|
|
4473
|
+
if (failed || hasPlan(page) || planWork(page) < WORKER_WORK) return false;
|
|
4474
|
+
if (make === void 0) make = workerMaker();
|
|
4475
|
+
return make !== null;
|
|
4476
|
+
}
|
|
4477
|
+
function planInWorker(page) {
|
|
4478
|
+
return new Promise((resolve2) => {
|
|
4479
|
+
const done = (p) => {
|
|
4480
|
+
if (p) rememberPlan(page, p);
|
|
4481
|
+
resolve2();
|
|
4482
|
+
};
|
|
4483
|
+
let w;
|
|
4484
|
+
try {
|
|
4485
|
+
w = worker ?? start();
|
|
4486
|
+
} catch {
|
|
4487
|
+
failed = true;
|
|
4488
|
+
done(null);
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
const id = nextID++;
|
|
4492
|
+
waiting.set(id, { page, done });
|
|
4493
|
+
try {
|
|
4494
|
+
w.postMessage({ dottiePlan: id, page });
|
|
4495
|
+
} catch {
|
|
4496
|
+
waiting.delete(id);
|
|
4497
|
+
done(null);
|
|
4498
|
+
}
|
|
4499
|
+
});
|
|
4500
|
+
}
|
|
4501
|
+
function start() {
|
|
4502
|
+
const w = make();
|
|
4503
|
+
w.onmessage = (ev) => {
|
|
4504
|
+
const { dottiePlan: id, plan: p } = ev.data ?? {};
|
|
4505
|
+
const job = id === void 0 ? void 0 : waiting.get(id);
|
|
4506
|
+
if (!job) return;
|
|
4507
|
+
waiting.delete(id);
|
|
4508
|
+
job.done(p ?? null);
|
|
4509
|
+
if (waiting.size === 0 && worker === w) {
|
|
4510
|
+
w.terminate();
|
|
4511
|
+
worker = null;
|
|
4512
|
+
}
|
|
4513
|
+
};
|
|
4514
|
+
w.onerror = (ev) => {
|
|
4515
|
+
ev.preventDefault?.();
|
|
4516
|
+
failed = true;
|
|
4517
|
+
if (worker === w) worker = null;
|
|
4518
|
+
w.terminate();
|
|
4519
|
+
const jobs = [...waiting.values()];
|
|
4520
|
+
waiting.clear();
|
|
4521
|
+
for (const job of jobs) job.done(null);
|
|
4522
|
+
};
|
|
4523
|
+
worker = w;
|
|
4524
|
+
return w;
|
|
4525
|
+
}
|
|
4526
|
+
function servePlans() {
|
|
4527
|
+
const g = globalThis;
|
|
4528
|
+
if (typeof g.WorkerGlobalScope === "undefined" || !(g instanceof g.WorkerGlobalScope)) return;
|
|
4529
|
+
g.addEventListener("message", (ev) => {
|
|
4530
|
+
const id = ev.data?.dottiePlan;
|
|
4531
|
+
if (typeof id !== "number") return;
|
|
4532
|
+
let p = null;
|
|
4533
|
+
try {
|
|
4534
|
+
p = plan(ev.data.page);
|
|
4535
|
+
} catch {
|
|
4536
|
+
p = null;
|
|
4537
|
+
}
|
|
4538
|
+
g.postMessage({ dottiePlan: id, plan: p });
|
|
4539
|
+
});
|
|
4540
|
+
}
|
|
4541
|
+
|
|
4218
4542
|
// src/player.ts
|
|
4219
4543
|
var WHITE = { r: 1, g: 1, b: 1, a: 1 };
|
|
4220
4544
|
function pageIndex(file, spec) {
|
|
@@ -4284,7 +4608,7 @@ var Playback = class {
|
|
|
4284
4608
|
__publicField(this, "order", []);
|
|
4285
4609
|
__publicField(this, "frame", -1);
|
|
4286
4610
|
__publicField(this, "onFrame", null);
|
|
4287
|
-
this.page = options.clean ?
|
|
4611
|
+
this.page = options.clean ? cleanCopy(page) : page;
|
|
4288
4612
|
addFonts(usedFonts(this.page));
|
|
4289
4613
|
this.plan = plan(this.page);
|
|
4290
4614
|
this.clicks = clickTargets(page, options.file);
|
|
@@ -4361,7 +4685,7 @@ var Playback = class {
|
|
|
4361
4685
|
}
|
|
4362
4686
|
/** Whether a copy is still playing through after a click. */
|
|
4363
4687
|
replaying(now = performance.now()) {
|
|
4364
|
-
for (const [id,
|
|
4688
|
+
for (const [id, start2] of this.replayStarts) if (now - start2 < (this.replays.get(id) ?? 0) * 1e3) return true;
|
|
4365
4689
|
return false;
|
|
4366
4690
|
}
|
|
4367
4691
|
/** Draws the last moment drawn again, for copies replaying while the page stands still. */
|
|
@@ -4374,17 +4698,24 @@ var Playback = class {
|
|
|
4374
4698
|
const r = this.plan.rects[Math.min(i, this.plan.rects.length - 1)];
|
|
4375
4699
|
const now = typeof performance === "undefined" ? 0 : performance.now();
|
|
4376
4700
|
const times = /* @__PURE__ */ new Map();
|
|
4377
|
-
for (const [id,
|
|
4701
|
+
for (const [id, start2] of this.replayStarts) times.set(id, Math.min((now - start2) / 1e3, this.replays.get(id) ?? 0));
|
|
4378
4702
|
const tc = clampTime(t);
|
|
4379
4703
|
const pose = posed(this.page, tc, this.state, this.sinceChange(now), this.carried);
|
|
4380
|
-
const
|
|
4704
|
+
const made = new Expansions(pose, tc, true);
|
|
4705
|
+
const frame2 = expandingSymbols(pose, tc, true, times, made);
|
|
4381
4706
|
const { parts } = svgParts(frame2, this.background, r);
|
|
4382
4707
|
const owned = (m, parts2) => {
|
|
4383
4708
|
for (const p of parts2) if (this.owner.has(p.key)) m.set(p.key, p.markup);
|
|
4384
4709
|
return m;
|
|
4385
4710
|
};
|
|
4386
|
-
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
4387
|
-
|
|
4711
|
+
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
4712
|
+
expandingSymbols(pose, tc, true, new Map([...times, ...st]), made),
|
|
4713
|
+
this.background,
|
|
4714
|
+
r,
|
|
4715
|
+
false,
|
|
4716
|
+
(key) => st.has(this.owner.get(key) ?? "")
|
|
4717
|
+
).parts);
|
|
4718
|
+
const hover = this.hoverIDs.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(hovered(frame2, this.hoverIDs), this.background, r, false, (key) => this.hoverIDs.has(key)).parts);
|
|
4388
4719
|
const over = state(this.overTimes), down = state(this.downTimes), hit = state(this.hitTimes);
|
|
4389
4720
|
const normal = owned(/* @__PURE__ */ new Map(), parts);
|
|
4390
4721
|
const merged = [];
|
|
@@ -4513,6 +4844,14 @@ ${h}</g>
|
|
|
4513
4844
|
return { frame: next, stop: entered >= 0 && !this.sceneLoops[entered] };
|
|
4514
4845
|
}
|
|
4515
4846
|
};
|
|
4847
|
+
var cleanCopies = /* @__PURE__ */ new WeakMap();
|
|
4848
|
+
function cleanCopy(page) {
|
|
4849
|
+
const hit = cleanCopies.get(page);
|
|
4850
|
+
if (hit && unchanged(page, hit.fields)) return hit.page;
|
|
4851
|
+
const clean = withRoughness(page, 0);
|
|
4852
|
+
cleanCopies.set(page, { fields: { ...page }, page: clean });
|
|
4853
|
+
return clean;
|
|
4854
|
+
}
|
|
4516
4855
|
var ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
4517
4856
|
} : HTMLElement;
|
|
4518
4857
|
var DottiePlayer = class extends ElementBase {
|
|
@@ -4535,6 +4874,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4535
4874
|
__publicField(this, "raf", 0);
|
|
4536
4875
|
__publicField(this, "replayRaf", 0);
|
|
4537
4876
|
__publicField(this, "loadToken", 0);
|
|
4877
|
+
/** Counts mounts, so a plan that comes back from a worker after a newer one is dropped. */
|
|
4878
|
+
__publicField(this, "mountToken", 0);
|
|
4879
|
+
/** The time a still shows; null while the drawing is animated. */
|
|
4880
|
+
__publicField(this, "stillTime", null);
|
|
4538
4881
|
__publicField(this, "visible", true);
|
|
4539
4882
|
__publicField(this, "observer", null);
|
|
4540
4883
|
this.root = this.attachShadow({ mode: "open" });
|
|
@@ -4611,9 +4954,19 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4611
4954
|
text = inline2.textContent ?? "";
|
|
4612
4955
|
}
|
|
4613
4956
|
if (token !== this.loadToken) return;
|
|
4614
|
-
this.
|
|
4957
|
+
this.showFile(parseDottie(text));
|
|
4615
4958
|
} catch (err) {
|
|
4616
4959
|
if (token !== this.loadToken) return;
|
|
4960
|
+
this.setPlaying(false);
|
|
4961
|
+
cancelAnimationFrame(this.raf);
|
|
4962
|
+
cancelAnimationFrame(this.replayRaf);
|
|
4963
|
+
this.raf = 0;
|
|
4964
|
+
this.replayRaf = 0;
|
|
4965
|
+
this.playback = null;
|
|
4966
|
+
this.file = null;
|
|
4967
|
+
this.mountToken++;
|
|
4968
|
+
this.stillTime = null;
|
|
4969
|
+
this.bar.hidden = true;
|
|
4617
4970
|
this.stage.innerHTML = "";
|
|
4618
4971
|
const box = document.createElement("div");
|
|
4619
4972
|
box.className = "error";
|
|
@@ -4624,15 +4977,35 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4624
4977
|
}
|
|
4625
4978
|
/** Shows an already-parsed file. */
|
|
4626
4979
|
setFile(file) {
|
|
4980
|
+
this.loadToken++;
|
|
4981
|
+
this.showFile(file);
|
|
4982
|
+
}
|
|
4983
|
+
showFile(file) {
|
|
4627
4984
|
this.file = file;
|
|
4628
4985
|
for (const w of file.warnings) console.warn(`dottie-player: ${w}`);
|
|
4629
|
-
this.mount();
|
|
4630
|
-
this.dispatchEvent(new CustomEvent("load", { detail: { file } }));
|
|
4986
|
+
this.mount(() => this.dispatchEvent(new CustomEvent("load", { detail: { file } })));
|
|
4631
4987
|
}
|
|
4632
|
-
|
|
4988
|
+
/** Shows the file's page; then `mounted`. A big animation is planned in a worker first (planner.ts), and what was
|
|
4989
|
+
* showing stays until it is ready; a newer mount, file or failed load in the meantime wins. */
|
|
4990
|
+
mount(mounted) {
|
|
4633
4991
|
const file = this.file;
|
|
4634
4992
|
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
4635
|
-
const
|
|
4993
|
+
const clean = this.hasAttribute("clean");
|
|
4994
|
+
const token = ++this.mountToken;
|
|
4995
|
+
const shown = clean ? cleanCopy(page) : page;
|
|
4996
|
+
if (!plansInWorker(shown)) {
|
|
4997
|
+
this.mountPage(file, page, clean);
|
|
4998
|
+
mounted?.();
|
|
4999
|
+
return;
|
|
5000
|
+
}
|
|
5001
|
+
void planInWorker(shown).then(() => {
|
|
5002
|
+
if (token !== this.mountToken || this.file !== file) return;
|
|
5003
|
+
this.mountPage(file, page, clean);
|
|
5004
|
+
mounted?.();
|
|
5005
|
+
});
|
|
5006
|
+
}
|
|
5007
|
+
mountPage(file, page, clean) {
|
|
5008
|
+
const p = new Playback(page, this.stage, { clean, file });
|
|
4636
5009
|
p.still = this.hasAttribute("at");
|
|
4637
5010
|
this.playback = p;
|
|
4638
5011
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
@@ -4655,10 +5028,14 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4655
5028
|
this.dispatchEvent(new CustomEvent("frame", { detail: { frame: i, time: p.plan.times[i] } }));
|
|
4656
5029
|
};
|
|
4657
5030
|
if (!animated) {
|
|
4658
|
-
|
|
5031
|
+
const i = at !== null ? nearest(p, Number(at) || 0) : p.posterFrame;
|
|
5032
|
+
this.stillTime = at !== null ? Number(at) || 0 : posterTime(page);
|
|
5033
|
+
p.drawAt(this.stillTime, i);
|
|
5034
|
+
p.frame = i;
|
|
4659
5035
|
this.setPlaying(false);
|
|
4660
5036
|
return;
|
|
4661
5037
|
}
|
|
5038
|
+
this.stillTime = null;
|
|
4662
5039
|
p.show(0);
|
|
4663
5040
|
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
4664
5041
|
if (reduced) {
|
|
@@ -4672,8 +5049,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4672
5049
|
get paused() {
|
|
4673
5050
|
return !this.playing;
|
|
4674
5051
|
}
|
|
5052
|
+
/** The moment shown: a still's own time (its `at`, or the poster), else the current frame's. */
|
|
4675
5053
|
get currentTime() {
|
|
4676
5054
|
const p = this.playback;
|
|
5055
|
+
if (p && this.stillTime !== null) return this.stillTime;
|
|
4677
5056
|
return p && p.frame >= 0 ? p.plan.times[p.frame] : 0;
|
|
4678
5057
|
}
|
|
4679
5058
|
set currentTime(t) {
|
|
@@ -4960,9 +5339,10 @@ function render(source, options = {}) {
|
|
|
4960
5339
|
function isFile(v) {
|
|
4961
5340
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
4962
5341
|
}
|
|
4963
|
-
var VERSION = "0.
|
|
5342
|
+
var VERSION = "0.9.0";
|
|
4964
5343
|
|
|
4965
5344
|
// src/player-entry.ts
|
|
5345
|
+
servePlans();
|
|
4966
5346
|
definePlayer();
|
|
4967
5347
|
globalThis.Dottie ?? (globalThis.Dottie = index_exports);
|
|
4968
5348
|
export {
|