@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.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 __publicField = (obj, key, value2) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2);
|
|
@@ -546,15 +546,15 @@ var Reader = class {
|
|
|
546
546
|
const keys = (Array.isArray(v.keys) ? v.keys : []).map((k) => this.keyframe(k)).filter((k) => k !== null && k.t >= 0 && k.t <= MAX_DURATION);
|
|
547
547
|
stableSortBy(keys, (k) => k.t);
|
|
548
548
|
const follow = Array.isArray(v.follow) ? v.follow.filter(isObj).flatMap((f) => {
|
|
549
|
-
const path = uuid(f.path),
|
|
550
|
-
if (!path ||
|
|
551
|
-
return [{ path, start, end, ease: this.ease(f.ease), reverse: bool(f.reverse) ?? null, orient: bool(f.orient) ?? null, laps: num(f.laps) ?? null }];
|
|
549
|
+
const path = uuid(f.path), start2 = num(f.start), end = num(f.end);
|
|
550
|
+
if (!path || start2 === void 0 || end === void 0 || start2 < 0 || end > MAX_DURATION) return [];
|
|
551
|
+
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 }];
|
|
552
552
|
}) : null;
|
|
553
553
|
const repeats = Array.isArray(v.repeats) ? v.repeats.filter(isObj).flatMap((r) => {
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
554
|
+
const start2 = num(r.start), end = num(r.end);
|
|
555
|
+
if (start2 === void 0 || end === void 0) return [];
|
|
556
556
|
const count = num(r.count);
|
|
557
|
-
const rep = { start, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
557
|
+
const rep = { start: start2, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
558
558
|
return repeatIsValid(rep) ? [rep] : [];
|
|
559
559
|
}) : null;
|
|
560
560
|
return { element, keys, boil: bool(v.boil) ?? null, follow, repeats, march: clampMarch(num(v.march)) };
|
|
@@ -779,6 +779,21 @@ function pieces(para) {
|
|
|
779
779
|
if (cur || out.length === 0) out.push(cur);
|
|
780
780
|
return out;
|
|
781
781
|
}
|
|
782
|
+
function prefixWidths(chars, fontSize, face, font) {
|
|
783
|
+
const fam = familyMetrics(font);
|
|
784
|
+
const f = fam.faces[face] ?? fam.faces.regular;
|
|
785
|
+
const fallback = f.w["n"];
|
|
786
|
+
const out = [0];
|
|
787
|
+
let w = 0;
|
|
788
|
+
let prev = "";
|
|
789
|
+
for (const ch of chars) {
|
|
790
|
+
w += f.w[ch] ?? fallback;
|
|
791
|
+
if (prev) w += f.k[prev + ch] ?? 0;
|
|
792
|
+
prev = ch;
|
|
793
|
+
out.push(w * fontSize / 1e3);
|
|
794
|
+
}
|
|
795
|
+
return out;
|
|
796
|
+
}
|
|
782
797
|
function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
783
798
|
const width = (t) => measure(t, fontSize, face, font);
|
|
784
799
|
const out = [];
|
|
@@ -794,8 +809,9 @@ function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
|
794
809
|
}
|
|
795
810
|
while (width(line2.trimEnd()) > maxWidth && [...line2.trimEnd()].length > 1) {
|
|
796
811
|
const chars = [...line2];
|
|
812
|
+
const prefix = prefixWidths(chars, fontSize, face, font);
|
|
797
813
|
let n = chars.length - 1;
|
|
798
|
-
while (n > 1 &&
|
|
814
|
+
while (n > 1 && prefix[n] > maxWidth) n--;
|
|
799
815
|
out.push(chars.slice(0, n).join(""));
|
|
800
816
|
line2 = chars.slice(n).join("");
|
|
801
817
|
}
|
|
@@ -950,13 +966,13 @@ var Path = class _Path {
|
|
|
950
966
|
contours() {
|
|
951
967
|
const out = [];
|
|
952
968
|
let current = [];
|
|
953
|
-
let
|
|
969
|
+
let start2 = { x: 0, y: 0 };
|
|
954
970
|
for (const s of this.segments) {
|
|
955
|
-
const last = current.length ? current[current.length - 1] :
|
|
971
|
+
const last = current.length ? current[current.length - 1] : start2;
|
|
956
972
|
switch (s.op) {
|
|
957
973
|
case "M":
|
|
958
974
|
if (current.length > 1) out.push(current);
|
|
959
|
-
|
|
975
|
+
start2 = s.p;
|
|
960
976
|
current = [s.p];
|
|
961
977
|
break;
|
|
962
978
|
case "L":
|
|
@@ -978,9 +994,9 @@ var Path = class _Path {
|
|
|
978
994
|
}
|
|
979
995
|
break;
|
|
980
996
|
case "Z":
|
|
981
|
-
current.push(
|
|
997
|
+
current.push(start2);
|
|
982
998
|
if (current.length > 1) out.push(current);
|
|
983
|
-
current = [
|
|
999
|
+
current = [start2];
|
|
984
1000
|
break;
|
|
985
1001
|
}
|
|
986
1002
|
}
|
|
@@ -1372,9 +1388,17 @@ function midpoint(e, index) {
|
|
|
1372
1388
|
}
|
|
1373
1389
|
return pts[0];
|
|
1374
1390
|
}
|
|
1391
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
1392
|
+
function isIndexOf(page, index) {
|
|
1393
|
+
return indexes.get(page.elements)?.index === index;
|
|
1394
|
+
}
|
|
1375
1395
|
function indexOf(page) {
|
|
1396
|
+
const els = page.elements;
|
|
1397
|
+
const hit = indexes.get(els);
|
|
1398
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.index;
|
|
1376
1399
|
const m = /* @__PURE__ */ new Map();
|
|
1377
|
-
for (const e of
|
|
1400
|
+
for (const e of els) if (!m.has(e.id)) m.set(e.id, e);
|
|
1401
|
+
indexes.set(els, { members: [...els], index: m });
|
|
1378
1402
|
return m;
|
|
1379
1403
|
}
|
|
1380
1404
|
|
|
@@ -1457,43 +1481,112 @@ function packetID(flow, k) {
|
|
|
1457
1481
|
return flow.slice(0, 24) + (n < 0 ? n + 2 ** 48 : n).toString(16).toUpperCase().padStart(12, "0");
|
|
1458
1482
|
}
|
|
1459
1483
|
var routeCost = (cost) => cost === null || !Number.isFinite(cost) ? 1 : Math.min(Math.max(cost, 0), 1e6);
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
const
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
for (const e of frame2.elements) {
|
|
1484
|
+
var graphs = /* @__PURE__ */ new WeakMap();
|
|
1485
|
+
var networks = /* @__PURE__ */ new Map();
|
|
1486
|
+
function graphOf(frame2) {
|
|
1487
|
+
const els = frame2.elements;
|
|
1488
|
+
const hit = graphs.get(els);
|
|
1489
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.graph;
|
|
1490
|
+
const shapes2 = new Set(els.filter((e) => e.kind !== "connector").map((e) => e.id));
|
|
1491
|
+
const lines = [];
|
|
1492
|
+
for (const e of els) {
|
|
1470
1493
|
const c = e.connector;
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1494
|
+
if (c) lines.push([e.id, c.start.elementID, c.end.elementID, String(routeCost(c.cost)), c.startHead !== "none" ? "both" : "one"]);
|
|
1495
|
+
}
|
|
1496
|
+
const key = JSON.stringify([[...shapes2], lines]);
|
|
1497
|
+
let graph = networks.get(key);
|
|
1498
|
+
if (!graph) {
|
|
1499
|
+
const next = /* @__PURE__ */ new Map();
|
|
1500
|
+
const add2 = (a, edge) => {
|
|
1501
|
+
const list = next.get(a);
|
|
1502
|
+
if (list) list.push(edge);
|
|
1503
|
+
else next.set(a, [edge]);
|
|
1504
|
+
};
|
|
1505
|
+
for (const e of els) {
|
|
1506
|
+
const c = e.connector;
|
|
1507
|
+
const a = c?.start.elementID ?? null, b = c?.end.elementID ?? null;
|
|
1508
|
+
if (!c || a === null || b === null || a === b || !shapes2.has(a) || !shapes2.has(b)) continue;
|
|
1509
|
+
const cost = routeCost(c.cost);
|
|
1510
|
+
add2(a, { to: b, via: e.id, back: false, cost });
|
|
1511
|
+
if (c.startHead !== "none") add2(b, { to: a, via: e.id, back: true, cost });
|
|
1512
|
+
}
|
|
1513
|
+
graph = { shapes: shapes2, next, trees: /* @__PURE__ */ new Map() };
|
|
1514
|
+
if (networks.size >= 16) networks.clear();
|
|
1515
|
+
networks.set(key, graph);
|
|
1516
|
+
}
|
|
1517
|
+
graphs.set(els, { members: [...els], graph });
|
|
1518
|
+
return graph;
|
|
1519
|
+
}
|
|
1520
|
+
var Frontier = class {
|
|
1521
|
+
constructor() {
|
|
1522
|
+
__publicField(this, "heap", []);
|
|
1523
|
+
}
|
|
1524
|
+
get size() {
|
|
1525
|
+
return this.heap.length;
|
|
1526
|
+
}
|
|
1527
|
+
less(i, j) {
|
|
1528
|
+
const a = this.heap[i], b = this.heap[j];
|
|
1529
|
+
return a.dist < b.dist || a.dist === b.dist && a.order < b.order;
|
|
1530
|
+
}
|
|
1531
|
+
swap(i, j) {
|
|
1532
|
+
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
|
|
1533
|
+
}
|
|
1534
|
+
push(id, dist, order) {
|
|
1535
|
+
this.heap.push({ id, dist, order });
|
|
1536
|
+
for (let i = this.heap.length - 1; i > 0; ) {
|
|
1537
|
+
const up = i - 1 >> 1;
|
|
1538
|
+
if (!this.less(i, up)) break;
|
|
1539
|
+
this.swap(i, up);
|
|
1540
|
+
i = up;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
pop() {
|
|
1544
|
+
const top = this.heap[0], last = this.heap.pop();
|
|
1545
|
+
if (this.heap.length > 0) {
|
|
1546
|
+
this.heap[0] = last;
|
|
1547
|
+
for (let i = 0; ; ) {
|
|
1548
|
+
const l = 2 * i + 1, r = l + 1;
|
|
1549
|
+
let m = i;
|
|
1550
|
+
if (l < this.heap.length && this.less(l, m)) m = l;
|
|
1551
|
+
if (r < this.heap.length && this.less(r, m)) m = r;
|
|
1552
|
+
if (m === i) break;
|
|
1553
|
+
this.swap(i, m);
|
|
1554
|
+
i = m;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
return top;
|
|
1476
1558
|
}
|
|
1559
|
+
};
|
|
1560
|
+
function flowTree(from, frame2, offline = /* @__PURE__ */ new Set(), until) {
|
|
1561
|
+
const graph = graphOf(frame2);
|
|
1562
|
+
const key = JSON.stringify([from, until ?? null, [...offline].sort()]);
|
|
1563
|
+
if (graph.trees.has(key)) return graph.trees.get(key);
|
|
1564
|
+
const tree = grow(graph, from, offline, until);
|
|
1565
|
+
if (graph.trees.size >= 4096) graph.trees.clear();
|
|
1566
|
+
graph.trees.set(key, tree);
|
|
1567
|
+
return tree;
|
|
1568
|
+
}
|
|
1569
|
+
function grow({ shapes: shapes2, next }, from, offline, until) {
|
|
1570
|
+
if (!shapes2.has(from) || offline.has(from)) return null;
|
|
1477
1571
|
const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
|
|
1478
1572
|
let counter = 1;
|
|
1479
1573
|
const tree = new FlowTree(from);
|
|
1480
|
-
const open =
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
if (da < db || da === db && order.get(open[i]) < order.get(open[best])) best = i;
|
|
1486
|
-
}
|
|
1487
|
-
const n = open.splice(best, 1)[0];
|
|
1574
|
+
const open = new Frontier();
|
|
1575
|
+
open.push(from, 0, 0);
|
|
1576
|
+
while (open.size > 0) {
|
|
1577
|
+
const { id: n, order: o } = open.pop();
|
|
1578
|
+
if (tree.settled.has(n) || order.get(n) !== o) continue;
|
|
1488
1579
|
tree.settled.add(n);
|
|
1580
|
+
if (n === until && n !== from) break;
|
|
1489
1581
|
for (const edge of next.get(n) ?? []) {
|
|
1582
|
+
if (offline.has(edge.via) || offline.has(edge.to)) continue;
|
|
1490
1583
|
if (tree.settled.has(edge.to)) continue;
|
|
1491
1584
|
const d = dist.get(n) + edge.cost;
|
|
1492
1585
|
const old = dist.get(edge.to);
|
|
1493
1586
|
if (old !== void 0 && old <= d) continue;
|
|
1494
|
-
if (old === void 0) open.push(edge.to);
|
|
1495
1587
|
dist.set(edge.to, d);
|
|
1496
|
-
order.set(edge.to, counter
|
|
1588
|
+
order.set(edge.to, counter);
|
|
1589
|
+
open.push(edge.to, d, counter++);
|
|
1497
1590
|
tree.came.set(edge.to, { from: n, via: edge.via, back: edge.back });
|
|
1498
1591
|
}
|
|
1499
1592
|
}
|
|
@@ -1573,8 +1666,16 @@ function delayOf(e) {
|
|
|
1573
1666
|
var queueKey = (shape, flow) => shape + "|" + flow;
|
|
1574
1667
|
function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set(), sinceChange = null, carried = {}) {
|
|
1575
1668
|
const since = Math.max(0, Math.min(sinceChange ?? t, 1e6));
|
|
1576
|
-
const byID =
|
|
1577
|
-
|
|
1669
|
+
const byID = indexOf(frame2);
|
|
1670
|
+
const traced = /* @__PURE__ */ new Map();
|
|
1671
|
+
const traceOf = (c) => {
|
|
1672
|
+
let pts = traced.get(c.id);
|
|
1673
|
+
if (!pts) {
|
|
1674
|
+
pts = tracing(c, frame2, byID).points;
|
|
1675
|
+
traced.set(c.id, pts);
|
|
1676
|
+
}
|
|
1677
|
+
return pts;
|
|
1678
|
+
};
|
|
1578
1679
|
const metrics2 = emptyMetrics();
|
|
1579
1680
|
for (const e of frame2.elements) {
|
|
1580
1681
|
if (e.kind === "connector" && !e.connector) continue;
|
|
@@ -1604,7 +1705,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1604
1705
|
hops.forEach(([id, back], k) => {
|
|
1605
1706
|
const c = byID.get(id);
|
|
1606
1707
|
if (!c) return;
|
|
1607
|
-
const line2 = back ? [...
|
|
1708
|
+
const line2 = back ? [...traceOf(c)].reverse() : traceOf(c);
|
|
1608
1709
|
if (line2.length === 0) return;
|
|
1609
1710
|
const last = points[points.length - 1];
|
|
1610
1711
|
if (last) {
|
|
@@ -1613,9 +1714,9 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1613
1714
|
inside.push([length, length + gap, shape ? delayOf(shape) : 0]);
|
|
1614
1715
|
length += gap;
|
|
1615
1716
|
}
|
|
1616
|
-
const
|
|
1717
|
+
const start2 = length;
|
|
1617
1718
|
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);
|
|
1618
|
-
stretches.push([id,
|
|
1719
|
+
stretches.push([id, start2, length]);
|
|
1619
1720
|
points.push(...line2);
|
|
1620
1721
|
});
|
|
1621
1722
|
return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
|
|
@@ -1650,22 +1751,27 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1650
1751
|
const packets = [];
|
|
1651
1752
|
const waitingBy = /* @__PURE__ */ new Map();
|
|
1652
1753
|
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);
|
|
1754
|
+
const bases = /* @__PURE__ */ new Map();
|
|
1755
|
+
const baseTree = (from) => {
|
|
1756
|
+
if (!bases.has(from)) bases.set(from, flowTree(from, frame2, offline));
|
|
1757
|
+
return bases.get(from);
|
|
1758
|
+
};
|
|
1653
1759
|
for (const [i, flow] of list.entries()) {
|
|
1654
1760
|
if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
|
|
1655
1761
|
const source = byID.get(flow.from);
|
|
1656
|
-
const base2 = source ?
|
|
1762
|
+
const base2 = source ? baseTree(flow.from) : null;
|
|
1657
1763
|
if (!source || !base2) continue;
|
|
1658
1764
|
const volume = amountIn(flow, values);
|
|
1659
1765
|
const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
|
|
1660
1766
|
const placements = [];
|
|
1661
|
-
const
|
|
1767
|
+
const waiting2 = [];
|
|
1662
1768
|
const goals = base2.goals(flow.to, flow.spread === true);
|
|
1663
1769
|
const shares = goals.length > 1 ? goals.map((g) => [[g], volume / goals.length]) : [[flow.to, volume]];
|
|
1664
1770
|
for (const [candidates, share] of shares) {
|
|
1665
1771
|
if (!(share > TINY)) continue;
|
|
1666
1772
|
let left = share;
|
|
1667
1773
|
for (let n = 0; n < 32 && left > TINY; n++) {
|
|
1668
|
-
const tree = flowTree(flow.from, frame2, union2(offline, full()));
|
|
1774
|
+
const tree = flowTree(flow.from, frame2, union2(offline, full()), candidates[0]);
|
|
1669
1775
|
const goal2 = tree?.goals(candidates, false)[0];
|
|
1670
1776
|
if (!tree || goal2 === void 0) break;
|
|
1671
1777
|
const r = route2(tree, goal2);
|
|
@@ -1705,18 +1811,18 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1705
1811
|
placements.push({ hops, shapes: shapes2, goal: at, amount: left, waits: true });
|
|
1706
1812
|
}
|
|
1707
1813
|
}
|
|
1708
|
-
const existing =
|
|
1814
|
+
const existing = waiting2.find(([s]) => s === at);
|
|
1709
1815
|
if (existing) existing[1] += left;
|
|
1710
|
-
else
|
|
1816
|
+
else waiting2.push([at, left]);
|
|
1711
1817
|
}
|
|
1712
|
-
waitingBy.set(flow.id,
|
|
1818
|
+
waitingBy.set(flow.id, waiting2);
|
|
1713
1819
|
for (const p of placements) {
|
|
1714
1820
|
for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1715
1821
|
m.throughput += p.amount;
|
|
1716
1822
|
});
|
|
1717
1823
|
if (!p.waits) fm.placed += p.amount;
|
|
1718
1824
|
}
|
|
1719
|
-
for (const [at, a] of
|
|
1825
|
+
for (const [at, a] of waiting2) {
|
|
1720
1826
|
bump(at, (m) => {
|
|
1721
1827
|
m.backlog += a;
|
|
1722
1828
|
});
|
|
@@ -1761,10 +1867,12 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1761
1867
|
metrics2.flows.set(flow.id, fm);
|
|
1762
1868
|
}
|
|
1763
1869
|
const piles = /* @__PURE__ */ new Map();
|
|
1870
|
+
const carriedKeys = Object.keys(carried).filter((k) => carried[k] > TINY);
|
|
1764
1871
|
for (const flow of list) {
|
|
1765
|
-
const
|
|
1766
|
-
const shapes2 =
|
|
1767
|
-
|
|
1872
|
+
const waiting2 = waitingBy.get(flow.id) ?? [];
|
|
1873
|
+
const shapes2 = waiting2.map(([s]) => s);
|
|
1874
|
+
const suffix = "|" + flow.id;
|
|
1875
|
+
if (carriedKeys.some((k) => k.endsWith(suffix))) for (const e of frame2.elements) {
|
|
1768
1876
|
const c = carried[queueKey(e.id, flow.id)];
|
|
1769
1877
|
if (c !== void 0 && c > TINY && !shapes2.includes(e.id)) shapes2.push(e.id);
|
|
1770
1878
|
}
|
|
@@ -1778,7 +1886,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1778
1886
|
const shape = byID.get(at);
|
|
1779
1887
|
if (!shape) return;
|
|
1780
1888
|
const layer = byID.get(flow.from)?.layerID ?? shape.layerID;
|
|
1781
|
-
const backlog =
|
|
1889
|
+
const backlog = waiting2.find(([s]) => s === at)?.[1] ?? 0;
|
|
1782
1890
|
const before = Math.max(0, carried[queueKey(at, flow.id)] ?? 0);
|
|
1783
1891
|
let depth = before + backlog * since;
|
|
1784
1892
|
if (backlog <= TINY && before > TINY) {
|
|
@@ -1787,7 +1895,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1787
1895
|
let total = 0;
|
|
1788
1896
|
for (let n2 = 0; n2 < 32; n2++) {
|
|
1789
1897
|
const blocked = new Set([...trial].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1790
|
-
const tree = flowTree(at, frame2, union2(offline, blocked));
|
|
1898
|
+
const tree = flowTree(at, frame2, union2(offline, blocked), flow.to[0]);
|
|
1791
1899
|
const goal = tree?.goals(flow.to, false)[0];
|
|
1792
1900
|
if (!tree || goal === void 0) break;
|
|
1793
1901
|
const r = route2(tree, goal);
|
|
@@ -1900,21 +2008,21 @@ function metricLookup(m, frame2) {
|
|
|
1900
2008
|
return id === null ? null : metricValue(m, term.metric, id);
|
|
1901
2009
|
};
|
|
1902
2010
|
}
|
|
1903
|
-
function
|
|
2011
|
+
function finishPlan(elements, metrics2, values, frame2, since = Infinity, carried = {}) {
|
|
1904
2012
|
const lookup = metricLookup(metrics2, frame2);
|
|
1905
|
-
const
|
|
1906
|
-
for (
|
|
2013
|
+
const plan2 = [], readings = {};
|
|
2014
|
+
for (const e of elements) {
|
|
2015
|
+
const applied = [];
|
|
1907
2016
|
if (values !== null && e.overrides) {
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
}
|
|
2017
|
+
e.overrides.forEach((o, k) => {
|
|
2018
|
+
if (mentionsMetric(o.when) && holds(o.when, values, (m) => lookup(m, e.id))) applied.push(k);
|
|
2019
|
+
});
|
|
1912
2020
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
2021
|
+
const g = values === null ? null : e.gauge;
|
|
2022
|
+
if (!g) {
|
|
2023
|
+
plan2.push({ applied, gauge: null });
|
|
1915
2024
|
continue;
|
|
1916
2025
|
}
|
|
1917
|
-
const g = e.gauge;
|
|
1918
2026
|
let shown = metricValue(metrics2, g.metric, g.of);
|
|
1919
2027
|
const then = carried[gaugeKey(e.id)];
|
|
1920
2028
|
if (shown !== null && then !== void 0 && g.metric !== "depth" && g.metric !== "inFlight" && since < GAUGE_EASE) {
|
|
@@ -1922,8 +2030,19 @@ function finishing(elements, metrics2, values, frame2, flows, since = Infinity,
|
|
|
1922
2030
|
shown = then + (shown - then) * smooth;
|
|
1923
2031
|
}
|
|
1924
2032
|
if (shown !== null) readings[gaugeKey(e.id)] = shown;
|
|
1925
|
-
|
|
2033
|
+
plan2.push({ applied, gauge: { value: shown } });
|
|
1926
2034
|
}
|
|
2035
|
+
return { plan: plan2, readings };
|
|
2036
|
+
}
|
|
2037
|
+
function finishing(elements, metrics2, values, frame2, flows, since = Infinity, carried = {}) {
|
|
2038
|
+
const { plan: plan2, readings } = finishPlan(elements, metrics2, values, frame2, since, carried);
|
|
2039
|
+
const out = [];
|
|
2040
|
+
elements.forEach((e, i) => {
|
|
2041
|
+
for (const k of plan2[i].applied) e = applyOverride(looksOnly(e.overrides[k]), e);
|
|
2042
|
+
const g = plan2[i].gauge;
|
|
2043
|
+
if (g && e.gauge) out.push(...gauge(e, e.gauge, g.value, frame2, flows));
|
|
2044
|
+
else out.push(e);
|
|
2045
|
+
});
|
|
1927
2046
|
return { elements: out, readings };
|
|
1928
2047
|
}
|
|
1929
2048
|
var gaugeKey = (id) => "gauge|" + id;
|
|
@@ -2069,7 +2188,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
2069
2188
|
const bow = offset(rng, length / 60, roughness) * bowing * gain;
|
|
2070
2189
|
const dx = b.x - a.x, dy = b.y - a.y;
|
|
2071
2190
|
const nx = -dy / length, ny = dx / length;
|
|
2072
|
-
const
|
|
2191
|
+
const start2 = preserveEnds ? a : (() => {
|
|
2073
2192
|
const x = a.x + j();
|
|
2074
2193
|
return { x, y: a.y + j() };
|
|
2075
2194
|
})();
|
|
@@ -2081,7 +2200,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
2081
2200
|
const c1y = a.y + dy * mid * 0.66 + ny * bow + j() * 0.5;
|
|
2082
2201
|
const c2x = a.x + dx * (0.5 + mid * 0.5) + nx * bow + j() * 0.5;
|
|
2083
2202
|
const c2y = a.y + dy * (0.5 + mid * 0.5) + ny * bow + j() * 0.5;
|
|
2084
|
-
into.moveTo(
|
|
2203
|
+
into.moveTo(start2);
|
|
2085
2204
|
into.curveTo(end, { x: c1x, y: c1y }, { x: c2x, y: c2y });
|
|
2086
2205
|
}
|
|
2087
2206
|
}
|
|
@@ -2342,10 +2461,10 @@ var Cache = class {
|
|
|
2342
2461
|
__publicField(this, "limit");
|
|
2343
2462
|
this.limit = limit;
|
|
2344
2463
|
}
|
|
2345
|
-
get(key,
|
|
2464
|
+
get(key, make2) {
|
|
2346
2465
|
const hit = this.map.get(key);
|
|
2347
2466
|
if (hit !== void 0) return hit;
|
|
2348
|
-
const v =
|
|
2467
|
+
const v = make2();
|
|
2349
2468
|
if (this.map.size >= this.limit) this.map.clear();
|
|
2350
2469
|
this.map.set(key, v);
|
|
2351
2470
|
return v;
|
|
@@ -2569,26 +2688,59 @@ function subCubic(p, t0, t1) {
|
|
|
2569
2688
|
const left = split(p, t1)[0];
|
|
2570
2689
|
return t1 > 0 ? split(left, t0 / t1)[1] : [p[0], p[0], p[0], p[0]];
|
|
2571
2690
|
}
|
|
2691
|
+
function boxOf(pts) {
|
|
2692
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
2693
|
+
for (const p of pts) {
|
|
2694
|
+
x0 = Math.min(x0, p.x);
|
|
2695
|
+
y0 = Math.min(y0, p.y);
|
|
2696
|
+
x1 = Math.max(x1, p.x);
|
|
2697
|
+
y1 = Math.max(y1, p.y);
|
|
2698
|
+
}
|
|
2699
|
+
return { x0, y0, x1, y1 };
|
|
2700
|
+
}
|
|
2701
|
+
function touching(a, b) {
|
|
2702
|
+
const eps = 1e-6;
|
|
2703
|
+
return a.x0 <= b.x1 + eps && b.x0 <= a.x1 + eps && a.y0 <= b.y1 + eps && b.y0 <= a.y1 + eps;
|
|
2704
|
+
}
|
|
2705
|
+
var hopFrames = /* @__PURE__ */ new WeakMap();
|
|
2706
|
+
function linesBeneath(e, page, index) {
|
|
2707
|
+
let f = hopFrames.get(index);
|
|
2708
|
+
if (!f || f.layers !== page.layers || !isIndexOf(page, index)) {
|
|
2709
|
+
const visible = new Set(page.layers.filter((l) => l.visible).map((l) => l.id));
|
|
2710
|
+
const first = /* @__PURE__ */ new Map(), under = [];
|
|
2711
|
+
for (const o of orderedElements(page)) {
|
|
2712
|
+
if (!first.has(o.id)) first.set(o.id, under.length);
|
|
2713
|
+
if (o.kind === "connector" && visible.has(o.layerID)) under.push(o);
|
|
2714
|
+
}
|
|
2715
|
+
f = { layers: page.layers, first, under, lines: [] };
|
|
2716
|
+
if (isIndexOf(page, index)) hopFrames.set(index, f);
|
|
2717
|
+
}
|
|
2718
|
+
const count = f.first.get(e.id) ?? f.under.length;
|
|
2719
|
+
while (f.lines.length < count) {
|
|
2720
|
+
const points = routePoints(route(f.under[f.lines.length], index));
|
|
2721
|
+
f.lines.push({ points, box: boxOf(points) });
|
|
2722
|
+
}
|
|
2723
|
+
return { lines: f.lines, count };
|
|
2724
|
+
}
|
|
2572
2725
|
function hopCrossings(e, r, page, index) {
|
|
2573
2726
|
const segments = hopSamples(r);
|
|
2574
2727
|
if (segments.length === 0) return [];
|
|
2575
|
-
const
|
|
2576
|
-
const beneath = [];
|
|
2577
|
-
for (const o of orderedElements(page)) {
|
|
2578
|
-
if (o.id === e.id) break;
|
|
2579
|
-
if (o.kind !== "connector" || !visible.has(o.layerID)) continue;
|
|
2580
|
-
beneath.push(routePoints(route(o, index)));
|
|
2581
|
-
}
|
|
2728
|
+
const { lines, count } = linesBeneath(e, page, index);
|
|
2582
2729
|
const rad = hopRadius(e.style.strokeWidth);
|
|
2583
2730
|
const out = [];
|
|
2584
2731
|
for (const samples of segments) {
|
|
2585
2732
|
const along = runningLengths(samples);
|
|
2586
2733
|
const length = along[along.length - 1];
|
|
2587
2734
|
const found = [];
|
|
2735
|
+
const near = [];
|
|
2736
|
+
const whole = boxOf(samples);
|
|
2737
|
+
for (let n = 0; n < count; n++) if (touching(whole, lines[n].box)) near.push(lines[n]);
|
|
2588
2738
|
for (let i = 0; i < samples.length - 1; i++) {
|
|
2589
2739
|
const p = samples[i], q = samples[i + 1];
|
|
2590
2740
|
const dx = q.x - p.x, dy = q.y - p.y;
|
|
2591
|
-
|
|
2741
|
+
const box = boxOf([p, q]);
|
|
2742
|
+
for (const { points: line2, box: lineBox } of near) {
|
|
2743
|
+
if (!touching(box, lineBox)) continue;
|
|
2592
2744
|
for (let k = 0; k < line2.length - 1; k++) {
|
|
2593
2745
|
const a = line2[k], b = line2[k + 1];
|
|
2594
2746
|
const ex = b.x - a.x, ey = b.y - a.y;
|
|
@@ -2868,8 +3020,8 @@ function followSpan(track, t) {
|
|
|
2868
3020
|
if (track.keys.some((k) => k.t >= span.end && k.t <= t && (k.x !== void 0 || k.y !== void 0))) return null;
|
|
2869
3021
|
return f;
|
|
2870
3022
|
}
|
|
2871
|
-
function tracing(e, page) {
|
|
2872
|
-
if (e.kind === "connector") return new Polyline(routePoints(route(e,
|
|
3023
|
+
function tracing(e, page, index = indexOf(page)) {
|
|
3024
|
+
if (e.kind === "connector") return new Polyline(routePoints(route(e, index)));
|
|
2873
3025
|
const clean = cloneElement(e);
|
|
2874
3026
|
clean.style.roughness = 0;
|
|
2875
3027
|
const pts = outline(clean).contours()[0] ?? [];
|
|
@@ -2879,9 +3031,10 @@ function following(track, e, t, page) {
|
|
|
2879
3031
|
const f = followSpan(track, t);
|
|
2880
3032
|
if (!f) return e;
|
|
2881
3033
|
t = localTime(track, t);
|
|
2882
|
-
const
|
|
3034
|
+
const index = indexOf(page);
|
|
3035
|
+
const pathElement = index.get(f.path);
|
|
2883
3036
|
if (!pathElement || pathElement.id === e.id) return e;
|
|
2884
|
-
const line2 = tracing(pathElement, page);
|
|
3037
|
+
const line2 = tracing(pathElement, page, index);
|
|
2885
3038
|
const span = f.end - f.start;
|
|
2886
3039
|
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);
|
|
2887
3040
|
if (u > 1) u = rem(u, 1) === 0 ? 1 : rem(u, 1);
|
|
@@ -3136,17 +3289,30 @@ function symbolPage(s) {
|
|
|
3136
3289
|
function hasInstances(page) {
|
|
3137
3290
|
return page.elements.some((e) => e.kind === "symbol");
|
|
3138
3291
|
}
|
|
3139
|
-
|
|
3292
|
+
var Expansions = class {
|
|
3293
|
+
constructor(page, t, posing) {
|
|
3294
|
+
__publicField(this, "page");
|
|
3295
|
+
__publicField(this, "t");
|
|
3296
|
+
__publicField(this, "posing");
|
|
3297
|
+
/** For each instance: its time override, the budget it found and left, and what it made. */
|
|
3298
|
+
__publicField(this, "made", /* @__PURE__ */ new Map());
|
|
3299
|
+
this.page = page;
|
|
3300
|
+
this.t = t;
|
|
3301
|
+
this.posing = posing;
|
|
3302
|
+
}
|
|
3303
|
+
};
|
|
3304
|
+
function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map(), memo) {
|
|
3140
3305
|
if (!hasInstances(page)) return page;
|
|
3141
3306
|
const budget = { left: MAX_ELEMENTS };
|
|
3142
|
-
|
|
3307
|
+
const m = memo && memo.page === page && memo.t === t && memo.posing === posing ? memo : void 0;
|
|
3308
|
+
return { ...page, elements: expand(page.elements, page, t, symbolTable(page), [], posing, budget, times, m) };
|
|
3143
3309
|
}
|
|
3144
3310
|
function symbolTable(page) {
|
|
3145
3311
|
const table = /* @__PURE__ */ new Map();
|
|
3146
3312
|
for (const s of page.symbols ?? []) if (!table.has(s.id)) table.set(s.id, s);
|
|
3147
3313
|
return table;
|
|
3148
3314
|
}
|
|
3149
|
-
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map()) {
|
|
3315
|
+
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map(), memo) {
|
|
3150
3316
|
const instances = new Set(elements.filter((e) => e.kind === "symbol").map((e) => e.id));
|
|
3151
3317
|
if (instances.size === 0) {
|
|
3152
3318
|
budget.left -= elements.length;
|
|
@@ -3171,6 +3337,14 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
3171
3337
|
budget.left -= 1;
|
|
3172
3338
|
continue;
|
|
3173
3339
|
}
|
|
3340
|
+
const override0 = stack.length === 0 ? times.get(e.id) : void 0;
|
|
3341
|
+
const was = memo?.made.get(e);
|
|
3342
|
+
if (was && was.override === override0 && was.after > 0 && budget.left - (was.before - was.after) > 0) {
|
|
3343
|
+
budget.left -= was.before - was.after;
|
|
3344
|
+
for (const x of was.out) out.push(x);
|
|
3345
|
+
continue;
|
|
3346
|
+
}
|
|
3347
|
+
const before = budget.left, from = out.length;
|
|
3174
3348
|
budget.left -= 1;
|
|
3175
3349
|
const id = e.symbol;
|
|
3176
3350
|
const symbol = id === null ? void 0 : table.get(id);
|
|
@@ -3186,6 +3360,7 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
3186
3360
|
layers: [{ id: e.layerID, name: "", visible: true, locked: false }]
|
|
3187
3361
|
};
|
|
3188
3362
|
for (const x of expand(drawn.elements, drawn, u, table, [...stack, id], posing, budget)) out.push(placed(x, e, symbol));
|
|
3363
|
+
if (memo && stack.length === 0 && !was) memo.made.set(e, { override: override0, before, after: budget.left, out: out.slice(from) });
|
|
3189
3364
|
}
|
|
3190
3365
|
return out;
|
|
3191
3366
|
}
|
|
@@ -3453,13 +3628,13 @@ function blocks(lines) {
|
|
|
3453
3628
|
flush();
|
|
3454
3629
|
return out;
|
|
3455
3630
|
}
|
|
3456
|
-
function parseList(lines,
|
|
3457
|
-
const first = reItem.exec(lines[
|
|
3631
|
+
function parseList(lines, start2) {
|
|
3632
|
+
const first = reItem.exec(lines[start2]);
|
|
3458
3633
|
const indent = first[1].length;
|
|
3459
3634
|
const bullet = /^\d/.test(first[2]) ? null : first[2];
|
|
3460
3635
|
const ordered = bullet === null ? parseInt(first[2], 10) : null;
|
|
3461
3636
|
const items = [];
|
|
3462
|
-
let i =
|
|
3637
|
+
let i = start2;
|
|
3463
3638
|
while (i < lines.length) {
|
|
3464
3639
|
const m = reItem.exec(lines[i]);
|
|
3465
3640
|
if (!m || m[1].length !== indent || bullet === null !== /^\d/.test(m[2]) || bullet !== null && m[2] !== bullet) break;
|
|
@@ -3788,10 +3963,10 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3788
3963
|
let k = 0;
|
|
3789
3964
|
let firstLine = true;
|
|
3790
3965
|
while (k < chars.length || firstLine && chars.length === 0) {
|
|
3791
|
-
const
|
|
3966
|
+
const start2 = firstLine ? p.firstIndent : p.indent;
|
|
3792
3967
|
const maxX2 = width;
|
|
3793
3968
|
const glyphs = [];
|
|
3794
|
-
let x =
|
|
3969
|
+
let x = start2, lastSpace = -1, end = k;
|
|
3795
3970
|
for (; end < chars.length; end++) {
|
|
3796
3971
|
const { ch, run } = chars[end];
|
|
3797
3972
|
const r = runs[run];
|
|
@@ -3840,7 +4015,7 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3840
4015
|
}
|
|
3841
4016
|
if (glyphs.length === 0 && end === k && k < chars.length) {
|
|
3842
4017
|
const { ch, run } = chars[k];
|
|
3843
|
-
glyphs.push({ ch, run, x:
|
|
4018
|
+
glyphs.push({ ch, run, x: start2, w: charWidth(ch, runs[run], fam) });
|
|
3844
4019
|
end = k + 1;
|
|
3845
4020
|
}
|
|
3846
4021
|
let asc = 0, desc = 0, lead = 0;
|
|
@@ -3866,13 +4041,38 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3866
4041
|
});
|
|
3867
4042
|
return lines;
|
|
3868
4043
|
}
|
|
4044
|
+
var Memo = class {
|
|
4045
|
+
constructor(limit) {
|
|
4046
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
4047
|
+
__publicField(this, "limit");
|
|
4048
|
+
this.limit = limit;
|
|
4049
|
+
}
|
|
4050
|
+
get(key, make2) {
|
|
4051
|
+
const hit = this.map.get(key);
|
|
4052
|
+
if (hit !== void 0) return hit;
|
|
4053
|
+
const v = make2();
|
|
4054
|
+
if (this.map.size >= this.limit) this.map.clear();
|
|
4055
|
+
this.map.set(key, v);
|
|
4056
|
+
return v;
|
|
4057
|
+
}
|
|
4058
|
+
};
|
|
4059
|
+
var layouts = new Memo(500);
|
|
4060
|
+
var markups = new Memo(500);
|
|
3869
4061
|
function noteMarkup(source, e) {
|
|
4062
|
+
const st = e.style, ink = st.stroke;
|
|
4063
|
+
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]);
|
|
4064
|
+
return markups.get(key, () => computeNoteMarkup(source, e));
|
|
4065
|
+
}
|
|
4066
|
+
function computeNoteMarkup(source, e) {
|
|
3870
4067
|
const rect2 = textRect({ x: e.x, y: e.y, width: e.width, height: e.height });
|
|
3871
4068
|
if (!(rect2.width > 1 && rect2.height > 1)) return "";
|
|
3872
4069
|
const fam = familyMetrics(e.style.font);
|
|
3873
|
-
const
|
|
3874
|
-
|
|
3875
|
-
|
|
4070
|
+
const layoutKey = JSON.stringify([...[rect2.width, rect2.height, e.style.fontSize].map(String), e.style.font ?? "", source]);
|
|
4071
|
+
const lines = layouts.get(layoutKey, () => {
|
|
4072
|
+
const b = new Builder(e.style.fontSize, rect2.width, fam);
|
|
4073
|
+
for (const block of parseBlocks(source)) b.block(block, { indent: 0, marker: null, quote: null, listDepth: 0 });
|
|
4074
|
+
return layoutLines(b.paras, rect2.width, rect2.height, e.style.fontSize, fam);
|
|
4075
|
+
});
|
|
3876
4076
|
const ink = color(e.style.stroke);
|
|
3877
4077
|
let s = "";
|
|
3878
4078
|
const marks = [];
|
|
@@ -3961,6 +4161,26 @@ function bounds(page, padding = 24) {
|
|
|
3961
4161
|
return inset(r, -padding, -padding);
|
|
3962
4162
|
}
|
|
3963
4163
|
function plan(page) {
|
|
4164
|
+
const hit = plans.get(page);
|
|
4165
|
+
if (hit && unchanged(page, hit.fields)) return hit.plan;
|
|
4166
|
+
const made = makePlan(page);
|
|
4167
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4168
|
+
return made;
|
|
4169
|
+
}
|
|
4170
|
+
var plans = /* @__PURE__ */ new WeakMap();
|
|
4171
|
+
function hasPlan(page) {
|
|
4172
|
+
const hit = plans.get(page);
|
|
4173
|
+
return !!hit && unchanged(page, hit.fields);
|
|
4174
|
+
}
|
|
4175
|
+
function rememberPlan(page, made) {
|
|
4176
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4177
|
+
}
|
|
4178
|
+
function unchanged(page, fields) {
|
|
4179
|
+
const a = page, b = fields;
|
|
4180
|
+
const keys = Object.keys(a);
|
|
4181
|
+
return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
|
|
4182
|
+
}
|
|
4183
|
+
function makePlan(page) {
|
|
3964
4184
|
const tl = page.timeline ?? { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
3965
4185
|
const times = frameTimes(tl);
|
|
3966
4186
|
const first = tl.camera?.find((k) => k.width > 1 && k.height > 1);
|
|
@@ -3988,8 +4208,8 @@ function svgOpen(b, background, paint) {
|
|
|
3988
4208
|
}
|
|
3989
4209
|
return out;
|
|
3990
4210
|
}
|
|
3991
|
-
function svgParts(page, background, fixed, links = false) {
|
|
3992
|
-
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links);
|
|
4211
|
+
function svgParts(page, background, fixed, links = false, only) {
|
|
4212
|
+
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links, only);
|
|
3993
4213
|
const theme = themeNamed(page.theme);
|
|
3994
4214
|
const bg = background ? theme?.background ?? background : null;
|
|
3995
4215
|
const ink = theme?.ink ?? black;
|
|
@@ -4000,11 +4220,11 @@ function svgParts(page, background, fixed, links = false) {
|
|
|
4000
4220
|
const index = indexOf(d);
|
|
4001
4221
|
const ordered = orderedElements(d);
|
|
4002
4222
|
const floors = ordered.filter((e) => visible.has(e.layerID) && isFloor(e));
|
|
4003
|
-
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"/>
|
|
4223
|
+
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"/>
|
|
4004
4224
|
`).join("");
|
|
4005
4225
|
if (rockMarkup) parts.push({ key: "rock", markup: rockMarkup });
|
|
4006
4226
|
for (const e of ordered) {
|
|
4007
|
-
if (!visible.has(e.layerID)) continue;
|
|
4227
|
+
if (!visible.has(e.layerID) || only && !only(e.id)) continue;
|
|
4008
4228
|
const alpha = Math.min(Math.max(e.style.opacity ?? 1, 0), 1);
|
|
4009
4229
|
if (alpha <= 0) continue;
|
|
4010
4230
|
let body = svgElement(e, index, bg, d);
|
|
@@ -4152,6 +4372,95 @@ function textElement(text, f, e, centered) {
|
|
|
4152
4372
|
return s + "</text>\n";
|
|
4153
4373
|
}
|
|
4154
4374
|
|
|
4375
|
+
// src/planner.ts
|
|
4376
|
+
var WORKER_WORK = 3e4;
|
|
4377
|
+
function planWork(page) {
|
|
4378
|
+
const tl = page.timeline;
|
|
4379
|
+
if (!tl || tl.camera?.some((k) => k.width > 1 && k.height > 1)) return 0;
|
|
4380
|
+
const elements = page.elements.length + (page.symbols ?? []).reduce((n, s) => n + s.elements.length, 0);
|
|
4381
|
+
return frameTimes(tl).length * elements;
|
|
4382
|
+
}
|
|
4383
|
+
function workerMaker() {
|
|
4384
|
+
if (typeof Worker === "undefined" || typeof __DOTTIE_WORKER__ === "undefined") return null;
|
|
4385
|
+
if (__DOTTIE_WORKER__ === "self" && typeof dottiePlayerScript === "function") {
|
|
4386
|
+
const source = `(${dottiePlayerScript.toString()})();
|
|
4387
|
+
`;
|
|
4388
|
+
return () => {
|
|
4389
|
+
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
4390
|
+
try {
|
|
4391
|
+
return new Worker(url);
|
|
4392
|
+
} finally {
|
|
4393
|
+
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
4394
|
+
}
|
|
4395
|
+
};
|
|
4396
|
+
}
|
|
4397
|
+
if (__DOTTIE_WORKER__ === "sibling") {
|
|
4398
|
+
const here = import.meta.url;
|
|
4399
|
+
if (!here) return null;
|
|
4400
|
+
return () => new Worker(new URL("./dottie-player.min.js", here));
|
|
4401
|
+
}
|
|
4402
|
+
return null;
|
|
4403
|
+
}
|
|
4404
|
+
var make;
|
|
4405
|
+
var worker = null;
|
|
4406
|
+
var failed = false;
|
|
4407
|
+
var nextID = 1;
|
|
4408
|
+
var waiting = /* @__PURE__ */ new Map();
|
|
4409
|
+
function plansInWorker(page) {
|
|
4410
|
+
if (failed || hasPlan(page) || planWork(page) < WORKER_WORK) return false;
|
|
4411
|
+
if (make === void 0) make = workerMaker();
|
|
4412
|
+
return make !== null;
|
|
4413
|
+
}
|
|
4414
|
+
function planInWorker(page) {
|
|
4415
|
+
return new Promise((resolve2) => {
|
|
4416
|
+
const done = (p) => {
|
|
4417
|
+
if (p) rememberPlan(page, p);
|
|
4418
|
+
resolve2();
|
|
4419
|
+
};
|
|
4420
|
+
let w;
|
|
4421
|
+
try {
|
|
4422
|
+
w = worker ?? start();
|
|
4423
|
+
} catch {
|
|
4424
|
+
failed = true;
|
|
4425
|
+
done(null);
|
|
4426
|
+
return;
|
|
4427
|
+
}
|
|
4428
|
+
const id = nextID++;
|
|
4429
|
+
waiting.set(id, { page, done });
|
|
4430
|
+
try {
|
|
4431
|
+
w.postMessage({ dottiePlan: id, page });
|
|
4432
|
+
} catch {
|
|
4433
|
+
waiting.delete(id);
|
|
4434
|
+
done(null);
|
|
4435
|
+
}
|
|
4436
|
+
});
|
|
4437
|
+
}
|
|
4438
|
+
function start() {
|
|
4439
|
+
const w = make();
|
|
4440
|
+
w.onmessage = (ev) => {
|
|
4441
|
+
const { dottiePlan: id, plan: p } = ev.data ?? {};
|
|
4442
|
+
const job = id === void 0 ? void 0 : waiting.get(id);
|
|
4443
|
+
if (!job) return;
|
|
4444
|
+
waiting.delete(id);
|
|
4445
|
+
job.done(p ?? null);
|
|
4446
|
+
if (waiting.size === 0 && worker === w) {
|
|
4447
|
+
w.terminate();
|
|
4448
|
+
worker = null;
|
|
4449
|
+
}
|
|
4450
|
+
};
|
|
4451
|
+
w.onerror = (ev) => {
|
|
4452
|
+
ev.preventDefault?.();
|
|
4453
|
+
failed = true;
|
|
4454
|
+
if (worker === w) worker = null;
|
|
4455
|
+
w.terminate();
|
|
4456
|
+
const jobs = [...waiting.values()];
|
|
4457
|
+
waiting.clear();
|
|
4458
|
+
for (const job of jobs) job.done(null);
|
|
4459
|
+
};
|
|
4460
|
+
worker = w;
|
|
4461
|
+
return w;
|
|
4462
|
+
}
|
|
4463
|
+
|
|
4155
4464
|
// src/player.ts
|
|
4156
4465
|
var WHITE = { r: 1, g: 1, b: 1, a: 1 };
|
|
4157
4466
|
function pageIndex(file, spec) {
|
|
@@ -4221,7 +4530,7 @@ var Playback = class {
|
|
|
4221
4530
|
__publicField(this, "order", []);
|
|
4222
4531
|
__publicField(this, "frame", -1);
|
|
4223
4532
|
__publicField(this, "onFrame", null);
|
|
4224
|
-
this.page = options.clean ?
|
|
4533
|
+
this.page = options.clean ? cleanCopy(page) : page;
|
|
4225
4534
|
addFonts(usedFonts(this.page));
|
|
4226
4535
|
this.plan = plan(this.page);
|
|
4227
4536
|
this.clicks = clickTargets(page, options.file);
|
|
@@ -4298,7 +4607,7 @@ var Playback = class {
|
|
|
4298
4607
|
}
|
|
4299
4608
|
/** Whether a copy is still playing through after a click. */
|
|
4300
4609
|
replaying(now = performance.now()) {
|
|
4301
|
-
for (const [id,
|
|
4610
|
+
for (const [id, start2] of this.replayStarts) if (now - start2 < (this.replays.get(id) ?? 0) * 1e3) return true;
|
|
4302
4611
|
return false;
|
|
4303
4612
|
}
|
|
4304
4613
|
/** Draws the last moment drawn again, for copies replaying while the page stands still. */
|
|
@@ -4311,17 +4620,24 @@ var Playback = class {
|
|
|
4311
4620
|
const r = this.plan.rects[Math.min(i, this.plan.rects.length - 1)];
|
|
4312
4621
|
const now = typeof performance === "undefined" ? 0 : performance.now();
|
|
4313
4622
|
const times = /* @__PURE__ */ new Map();
|
|
4314
|
-
for (const [id,
|
|
4623
|
+
for (const [id, start2] of this.replayStarts) times.set(id, Math.min((now - start2) / 1e3, this.replays.get(id) ?? 0));
|
|
4315
4624
|
const tc = clampTime(t);
|
|
4316
4625
|
const pose = posed(this.page, tc, this.state, this.sinceChange(now), this.carried);
|
|
4317
|
-
const
|
|
4626
|
+
const made = new Expansions(pose, tc, true);
|
|
4627
|
+
const frame2 = expandingSymbols(pose, tc, true, times, made);
|
|
4318
4628
|
const { parts } = svgParts(frame2, this.background, r);
|
|
4319
4629
|
const owned = (m, parts2) => {
|
|
4320
4630
|
for (const p of parts2) if (this.owner.has(p.key)) m.set(p.key, p.markup);
|
|
4321
4631
|
return m;
|
|
4322
4632
|
};
|
|
4323
|
-
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
4324
|
-
|
|
4633
|
+
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
4634
|
+
expandingSymbols(pose, tc, true, new Map([...times, ...st]), made),
|
|
4635
|
+
this.background,
|
|
4636
|
+
r,
|
|
4637
|
+
false,
|
|
4638
|
+
(key) => st.has(this.owner.get(key) ?? "")
|
|
4639
|
+
).parts);
|
|
4640
|
+
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);
|
|
4325
4641
|
const over = state(this.overTimes), down = state(this.downTimes), hit = state(this.hitTimes);
|
|
4326
4642
|
const normal = owned(/* @__PURE__ */ new Map(), parts);
|
|
4327
4643
|
const merged = [];
|
|
@@ -4450,6 +4766,14 @@ ${h}</g>
|
|
|
4450
4766
|
return { frame: next, stop: entered >= 0 && !this.sceneLoops[entered] };
|
|
4451
4767
|
}
|
|
4452
4768
|
};
|
|
4769
|
+
var cleanCopies = /* @__PURE__ */ new WeakMap();
|
|
4770
|
+
function cleanCopy(page) {
|
|
4771
|
+
const hit = cleanCopies.get(page);
|
|
4772
|
+
if (hit && unchanged(page, hit.fields)) return hit.page;
|
|
4773
|
+
const clean = withRoughness(page, 0);
|
|
4774
|
+
cleanCopies.set(page, { fields: { ...page }, page: clean });
|
|
4775
|
+
return clean;
|
|
4776
|
+
}
|
|
4453
4777
|
var ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
4454
4778
|
} : HTMLElement;
|
|
4455
4779
|
var DottiePlayer = class extends ElementBase {
|
|
@@ -4472,6 +4796,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4472
4796
|
__publicField(this, "raf", 0);
|
|
4473
4797
|
__publicField(this, "replayRaf", 0);
|
|
4474
4798
|
__publicField(this, "loadToken", 0);
|
|
4799
|
+
/** Counts mounts, so a plan that comes back from a worker after a newer one is dropped. */
|
|
4800
|
+
__publicField(this, "mountToken", 0);
|
|
4801
|
+
/** The time a still shows; null while the drawing is animated. */
|
|
4802
|
+
__publicField(this, "stillTime", null);
|
|
4475
4803
|
__publicField(this, "visible", true);
|
|
4476
4804
|
__publicField(this, "observer", null);
|
|
4477
4805
|
this.root = this.attachShadow({ mode: "open" });
|
|
@@ -4548,9 +4876,19 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4548
4876
|
text = inline2.textContent ?? "";
|
|
4549
4877
|
}
|
|
4550
4878
|
if (token !== this.loadToken) return;
|
|
4551
|
-
this.
|
|
4879
|
+
this.showFile(parseDottie(text));
|
|
4552
4880
|
} catch (err) {
|
|
4553
4881
|
if (token !== this.loadToken) return;
|
|
4882
|
+
this.setPlaying(false);
|
|
4883
|
+
cancelAnimationFrame(this.raf);
|
|
4884
|
+
cancelAnimationFrame(this.replayRaf);
|
|
4885
|
+
this.raf = 0;
|
|
4886
|
+
this.replayRaf = 0;
|
|
4887
|
+
this.playback = null;
|
|
4888
|
+
this.file = null;
|
|
4889
|
+
this.mountToken++;
|
|
4890
|
+
this.stillTime = null;
|
|
4891
|
+
this.bar.hidden = true;
|
|
4554
4892
|
this.stage.innerHTML = "";
|
|
4555
4893
|
const box = document.createElement("div");
|
|
4556
4894
|
box.className = "error";
|
|
@@ -4561,15 +4899,35 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4561
4899
|
}
|
|
4562
4900
|
/** Shows an already-parsed file. */
|
|
4563
4901
|
setFile(file) {
|
|
4902
|
+
this.loadToken++;
|
|
4903
|
+
this.showFile(file);
|
|
4904
|
+
}
|
|
4905
|
+
showFile(file) {
|
|
4564
4906
|
this.file = file;
|
|
4565
4907
|
for (const w of file.warnings) console.warn(`dottie-player: ${w}`);
|
|
4566
|
-
this.mount();
|
|
4567
|
-
this.dispatchEvent(new CustomEvent("load", { detail: { file } }));
|
|
4908
|
+
this.mount(() => this.dispatchEvent(new CustomEvent("load", { detail: { file } })));
|
|
4568
4909
|
}
|
|
4569
|
-
|
|
4910
|
+
/** Shows the file's page; then `mounted`. A big animation is planned in a worker first (planner.ts), and what was
|
|
4911
|
+
* showing stays until it is ready; a newer mount, file or failed load in the meantime wins. */
|
|
4912
|
+
mount(mounted) {
|
|
4570
4913
|
const file = this.file;
|
|
4571
4914
|
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
4572
|
-
const
|
|
4915
|
+
const clean = this.hasAttribute("clean");
|
|
4916
|
+
const token = ++this.mountToken;
|
|
4917
|
+
const shown = clean ? cleanCopy(page) : page;
|
|
4918
|
+
if (!plansInWorker(shown)) {
|
|
4919
|
+
this.mountPage(file, page, clean);
|
|
4920
|
+
mounted?.();
|
|
4921
|
+
return;
|
|
4922
|
+
}
|
|
4923
|
+
void planInWorker(shown).then(() => {
|
|
4924
|
+
if (token !== this.mountToken || this.file !== file) return;
|
|
4925
|
+
this.mountPage(file, page, clean);
|
|
4926
|
+
mounted?.();
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
mountPage(file, page, clean) {
|
|
4930
|
+
const p = new Playback(page, this.stage, { clean, file });
|
|
4573
4931
|
p.still = this.hasAttribute("at");
|
|
4574
4932
|
this.playback = p;
|
|
4575
4933
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
@@ -4592,10 +4950,14 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4592
4950
|
this.dispatchEvent(new CustomEvent("frame", { detail: { frame: i, time: p.plan.times[i] } }));
|
|
4593
4951
|
};
|
|
4594
4952
|
if (!animated) {
|
|
4595
|
-
|
|
4953
|
+
const i = at !== null ? nearest(p, Number(at) || 0) : p.posterFrame;
|
|
4954
|
+
this.stillTime = at !== null ? Number(at) || 0 : posterTime(page);
|
|
4955
|
+
p.drawAt(this.stillTime, i);
|
|
4956
|
+
p.frame = i;
|
|
4596
4957
|
this.setPlaying(false);
|
|
4597
4958
|
return;
|
|
4598
4959
|
}
|
|
4960
|
+
this.stillTime = null;
|
|
4599
4961
|
p.show(0);
|
|
4600
4962
|
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
4601
4963
|
if (reduced) {
|
|
@@ -4609,8 +4971,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4609
4971
|
get paused() {
|
|
4610
4972
|
return !this.playing;
|
|
4611
4973
|
}
|
|
4974
|
+
/** The moment shown: a still's own time (its `at`, or the poster), else the current frame's. */
|
|
4612
4975
|
get currentTime() {
|
|
4613
4976
|
const p = this.playback;
|
|
4977
|
+
if (p && this.stillTime !== null) return this.stillTime;
|
|
4614
4978
|
return p && p.frame >= 0 ? p.plan.times[p.frame] : 0;
|
|
4615
4979
|
}
|
|
4616
4980
|
set currentTime(t) {
|
|
@@ -4897,7 +5261,7 @@ function render(source, options = {}) {
|
|
|
4897
5261
|
function isFile(v) {
|
|
4898
5262
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
4899
5263
|
}
|
|
4900
|
-
var VERSION = "0.
|
|
5264
|
+
var VERSION = "0.9.0";
|
|
4901
5265
|
export {
|
|
4902
5266
|
BOIL_DRAWINGS,
|
|
4903
5267
|
BUNDLED_FONTS,
|