@pixerate/editor 0.23.0 → 0.25.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/README.md +167 -2
- package/THIRD_PARTY_NOTICES.md +80 -0
- package/dist/agent/index.cjs +10 -8
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +4 -3
- package/dist/agent/index.d.ts +4 -3
- package/dist/agent/index.js +4 -2
- package/dist/agent/index.js.map +1 -1
- package/dist/canvas/index.cjs +4437 -411
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +934 -4
- package/dist/canvas/index.d.ts +934 -4
- package/dist/canvas/index.js +4309 -283
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-4BVP64JJ.cjs +1406 -0
- package/dist/chunk-4BVP64JJ.cjs.map +1 -0
- package/dist/chunk-664LQYFJ.js +115 -0
- package/dist/chunk-664LQYFJ.js.map +1 -0
- package/dist/chunk-7WISYZFO.cjs +740 -0
- package/dist/chunk-7WISYZFO.cjs.map +1 -0
- package/dist/chunk-AL6JMGWW.cjs +783 -0
- package/dist/chunk-AL6JMGWW.cjs.map +1 -0
- package/dist/chunk-BVP3TSLH.cjs +69 -0
- package/dist/chunk-BVP3TSLH.cjs.map +1 -0
- package/dist/{chunk-RIKBJLS2.cjs → chunk-C7P22VIP.cjs} +2001 -279
- package/dist/chunk-C7P22VIP.cjs.map +1 -0
- package/dist/chunk-D6FH2YAX.cjs +131 -0
- package/dist/chunk-D6FH2YAX.cjs.map +1 -0
- package/dist/chunk-ECCCYKGL.js +740 -0
- package/dist/chunk-ECCCYKGL.js.map +1 -0
- package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
- package/dist/chunk-FD4LPFAE.cjs.map +1 -0
- package/dist/{chunk-QBJ22YZR.js → chunk-FH6F67DN.js} +1999 -277
- package/dist/chunk-FH6F67DN.js.map +1 -0
- package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
- package/dist/chunk-FPENXAEJ.js.map +1 -0
- package/dist/chunk-HLIMTC6N.js +1 -0
- package/dist/chunk-HLIMTC6N.js.map +1 -0
- package/dist/chunk-HRMEDT2L.js +1406 -0
- package/dist/chunk-HRMEDT2L.js.map +1 -0
- package/dist/{chunk-QGHGJ56O.js → chunk-IXHSYOVY.js} +2518 -122
- package/dist/chunk-IXHSYOVY.js.map +1 -0
- package/dist/chunk-MKA3AHLV.cjs +256 -0
- package/dist/chunk-MKA3AHLV.cjs.map +1 -0
- package/dist/chunk-NAXVVS5I.js +783 -0
- package/dist/chunk-NAXVVS5I.js.map +1 -0
- package/dist/chunk-O5RYH2PG.js +69 -0
- package/dist/chunk-O5RYH2PG.js.map +1 -0
- package/dist/chunk-OXSV64JX.js +689 -0
- package/dist/chunk-OXSV64JX.js.map +1 -0
- package/dist/chunk-P3RQ5UJE.cjs +689 -0
- package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
- package/dist/chunk-P6ODPZ4W.js +256 -0
- package/dist/chunk-P6ODPZ4W.js.map +1 -0
- package/dist/chunk-PL4JB3BF.cjs +1 -0
- package/dist/chunk-PL4JB3BF.cjs.map +1 -0
- package/dist/{chunk-GVSMRPHB.cjs → chunk-PO6A6QQH.cjs} +2607 -211
- package/dist/chunk-PO6A6QQH.cjs.map +1 -0
- package/dist/{chunk-JJDLPEWL.js → chunk-QGYDWPRM.js} +2 -2
- package/dist/chunk-RR7F5GUM.js +5726 -0
- package/dist/chunk-RR7F5GUM.js.map +1 -0
- package/dist/chunk-TNNJXF46.cjs +115 -0
- package/dist/chunk-TNNJXF46.cjs.map +1 -0
- package/dist/chunk-U5HZSFCS.js +131 -0
- package/dist/chunk-U5HZSFCS.js.map +1 -0
- package/dist/{chunk-MJZBNWG7.js → chunk-UAKDG62Z.js} +309 -581
- package/dist/chunk-UAKDG62Z.js.map +1 -0
- package/dist/{chunk-CL37JGFW.cjs → chunk-V3CHHWOE.cjs} +4 -4
- package/dist/{chunk-CL37JGFW.cjs.map → chunk-V3CHHWOE.cjs.map} +1 -1
- package/dist/chunk-WSQKHXW7.cjs +5726 -0
- package/dist/chunk-WSQKHXW7.cjs.map +1 -0
- package/dist/{chunk-AA4ZEVCC.cjs → chunk-X4N4WUKB.cjs} +336 -608
- package/dist/chunk-X4N4WUKB.cjs.map +1 -0
- package/dist/commands/index.cjs +74 -1
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.d.cts +38 -353
- package/dist/commands/index.d.ts +38 -353
- package/dist/commands/index.js +73 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/comment-store-BEMgVlAA.d.cts +135 -0
- package/dist/comment-store-BEMgVlAA.d.ts +135 -0
- package/dist/compare/index.cjs +15 -0
- package/dist/compare/index.cjs.map +1 -0
- package/dist/compare/index.d.cts +58 -0
- package/dist/compare/index.d.ts +58 -0
- package/dist/compare/index.js +15 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/controller-BIJiNoVs.d.cts +1842 -0
- package/dist/controller-BIJiNoVs.d.ts +1842 -0
- package/dist/documents-CHT1U5Vs.d.ts +86 -0
- package/dist/documents-Gp7wb0Me.d.cts +86 -0
- package/dist/extensions/index.cjs +100 -5
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +16 -4
- package/dist/extensions/index.d.ts +16 -4
- package/dist/extensions/index.js +100 -5
- package/dist/generation/index.cjs +2317 -0
- package/dist/generation/index.cjs.map +1 -0
- package/dist/generation/index.d.cts +1015 -0
- package/dist/generation/index.d.ts +1015 -0
- package/dist/generation/index.js +2317 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/grammar/index.cjs +48 -3
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.d.cts +322 -100
- package/dist/grammar/index.d.ts +322 -100
- package/dist/grammar/index.js +54 -9
- package/dist/image-editor/index.cjs +4392 -481
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +1249 -97
- package/dist/image-editor/index.d.ts +1249 -97
- package/dist/image-editor/index.js +4281 -370
- package/dist/image-editor/index.js.map +1 -1
- package/dist/index-7a6bHTLy.d.ts +1754 -0
- package/dist/index-Csd5qI8u.d.cts +1754 -0
- package/dist/index.cjs +1682 -99
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -21
- package/dist/index.d.ts +118 -21
- package/dist/index.js +1630 -47
- package/dist/index.js.map +1 -1
- package/dist/mention-text-CDnCFd3c.d.cts +53 -0
- package/dist/mention-text-CDnCFd3c.d.ts +53 -0
- package/dist/migrate-iTlm3a8h.d.cts +112 -0
- package/dist/migrate-iTlm3a8h.d.ts +112 -0
- package/dist/model-C1zApQM8.d.cts +219 -0
- package/dist/model-C1zApQM8.d.ts +219 -0
- package/dist/persistence/index.cjs +676 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +230 -0
- package/dist/persistence/index.d.ts +230 -0
- package/dist/persistence/index.js +676 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/{registry-D5-R90p0.d.cts → registry-BDe-vlv3.d.ts} +2 -40
- package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.cts} +12 -1
- package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.ts} +12 -1
- package/dist/registry-D32LpIc4.d.cts +354 -0
- package/dist/{registry-D5-R90p0.d.ts → registry-DnzpgY27.d.cts} +2 -40
- package/dist/registry-evKTP4ls.d.ts +354 -0
- package/dist/schema-DYDGdYL9.d.cts +41 -0
- package/dist/schema-DYDGdYL9.d.ts +41 -0
- package/dist/serializers/index.cjs +12 -3
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +40 -12
- package/dist/serializers/index.d.ts +40 -12
- package/dist/serializers/index.js +12 -3
- package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
- package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
- package/dist/spreadsheet/index.cjs +161 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +611 -20
- package/dist/spreadsheet/index.d.ts +611 -20
- package/dist/spreadsheet/index.js +168 -10
- package/dist/storage-BpFfspHm.d.cts +120 -0
- package/dist/storage-BpFfspHm.d.ts +120 -0
- package/dist/tokens/index.cjs +115 -0
- package/dist/tokens/index.cjs.map +1 -0
- package/dist/tokens/index.d.cts +212 -0
- package/dist/tokens/index.d.ts +212 -0
- package/dist/tokens/index.js +115 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
- package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
- package/dist/{track-changes-BSF58RK0.d.cts → track-changes-D_VAPELi.d.cts} +110 -144
- package/dist/{track-changes-BSF58RK0.d.ts → track-changes-Db_fGEvx.d.ts} +110 -144
- package/package.json +21 -1
- package/dist/chunk-4SUNJZWB.cjs.map +0 -1
- package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
- package/dist/chunk-AXV2ZW34.cjs +0 -118
- package/dist/chunk-AXV2ZW34.cjs.map +0 -1
- package/dist/chunk-B2ZRLN4S.js +0 -118
- package/dist/chunk-B2ZRLN4S.js.map +0 -1
- package/dist/chunk-GVSMRPHB.cjs.map +0 -1
- package/dist/chunk-K3R5NG2D.js +0 -3100
- package/dist/chunk-K3R5NG2D.js.map +0 -1
- package/dist/chunk-MJZBNWG7.js.map +0 -1
- package/dist/chunk-OYXSKQFR.js.map +0 -1
- package/dist/chunk-QBJ22YZR.js.map +0 -1
- package/dist/chunk-QGHGJ56O.js.map +0 -1
- package/dist/chunk-RIKBJLS2.cjs.map +0 -1
- package/dist/chunk-XDMXXC4Z.cjs +0 -3100
- package/dist/chunk-XDMXXC4Z.cjs.map +0 -1
- package/dist/controller-pePCJLT1.d.cts +0 -959
- package/dist/controller-pePCJLT1.d.ts +0 -959
- package/dist/index-CCr_z9kF.d.cts +0 -899
- package/dist/index-Ck6ajwXh.d.ts +0 -899
- /package/dist/{chunk-JJDLPEWL.js.map → chunk-QGYDWPRM.js.map} +0 -0
package/dist/canvas/index.js
CHANGED
|
@@ -2,6 +2,13 @@ import {
|
|
|
2
2
|
DEFAULT_ARRANGE_SHORTCUTS,
|
|
3
3
|
matchArrangeShortcut
|
|
4
4
|
} from "../chunk-7DWMJCAT.js";
|
|
5
|
+
import {
|
|
6
|
+
TokenError,
|
|
7
|
+
getTokenCollection,
|
|
8
|
+
getTokenVariable,
|
|
9
|
+
isTokenInScope,
|
|
10
|
+
resolveToken
|
|
11
|
+
} from "../chunk-HRMEDT2L.js";
|
|
5
12
|
import {
|
|
6
13
|
DEFAULT_NODE_HEIGHT,
|
|
7
14
|
DEFAULT_NODE_WIDTH,
|
|
@@ -129,11 +136,11 @@ function createFanOutTrajectory(options = {}) {
|
|
|
129
136
|
const oneMinusT = 1 - t;
|
|
130
137
|
const x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * midX + t * t * target.x;
|
|
131
138
|
const y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * midY + t * t * target.y;
|
|
132
|
-
const
|
|
139
|
+
const scale2 = startScale + (endScale - startScale) * progress;
|
|
133
140
|
const opacity = startOpacity + (endOpacity - startOpacity) * progress;
|
|
134
141
|
return {
|
|
135
142
|
position: { x, y },
|
|
136
|
-
scale,
|
|
143
|
+
scale: scale2,
|
|
137
144
|
opacity
|
|
138
145
|
};
|
|
139
146
|
};
|
|
@@ -1160,7 +1167,7 @@ function spacingCandidate(rects, moving, horizontal, tolerance) {
|
|
|
1160
1167
|
return best2;
|
|
1161
1168
|
};
|
|
1162
1169
|
const crossMid = (a, b) => (Math.max(crossStart(a), crossStart(b)) + Math.min(crossEnd(a), crossEnd(b))) / 2;
|
|
1163
|
-
const segment = (from, to,
|
|
1170
|
+
const segment = (from, to, cross2) => horizontal ? { orientation: "horizontal", kind: "spacing", x1: from, y1: cross2, x2: to, y2: cross2 } : { orientation: "vertical", kind: "spacing", x1: cross2, y1: from, x2: cross2, y2: to };
|
|
1164
1171
|
const candidates = [];
|
|
1165
1172
|
if (before && after) {
|
|
1166
1173
|
const b = before;
|
|
@@ -1577,10 +1584,10 @@ var CanvasScenesStore = class {
|
|
|
1577
1584
|
return this.update(id, { name });
|
|
1578
1585
|
}
|
|
1579
1586
|
/** Moves a scene to `toIndex` (clamped). */
|
|
1580
|
-
move(id,
|
|
1587
|
+
move(id, toIndex2) {
|
|
1581
1588
|
const from = this.scenes.findIndex((s) => s.id === id);
|
|
1582
1589
|
if (from === -1) return;
|
|
1583
|
-
const to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(
|
|
1590
|
+
const to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(toIndex2)));
|
|
1584
1591
|
if (from === to) return;
|
|
1585
1592
|
const scenes = [...this.scenes];
|
|
1586
1593
|
const [scene] = scenes.splice(from, 1);
|
|
@@ -1717,7 +1724,7 @@ function multiPolygonBounds(input) {
|
|
|
1717
1724
|
return minX === Infinity ? null : { minX, minY, maxX, maxY };
|
|
1718
1725
|
}
|
|
1719
1726
|
function pointInRings(point, rings, fillRule = "nonzero") {
|
|
1720
|
-
const [
|
|
1727
|
+
const [px2, py] = point;
|
|
1721
1728
|
let winding = 0;
|
|
1722
1729
|
for (const ring of rings) {
|
|
1723
1730
|
const n = ring.length;
|
|
@@ -1725,9 +1732,9 @@ function pointInRings(point, rings, fillRule = "nonzero") {
|
|
|
1725
1732
|
const [ax, ay] = ring[i];
|
|
1726
1733
|
const [bx, by] = ring[(i + 1) % n];
|
|
1727
1734
|
if (ay <= py && by > py) {
|
|
1728
|
-
if (ax + (py - ay) / (by - ay) * (bx - ax) >
|
|
1735
|
+
if (ax + (py - ay) / (by - ay) * (bx - ax) > px2) winding++;
|
|
1729
1736
|
} else if (by <= py && ay > py) {
|
|
1730
|
-
if (ax + (py - ay) / (by - ay) * (bx - ax) >
|
|
1737
|
+
if (ax + (py - ay) / (by - ay) * (bx - ax) > px2) winding--;
|
|
1731
1738
|
}
|
|
1732
1739
|
}
|
|
1733
1740
|
}
|
|
@@ -1852,9 +1859,9 @@ function splitPass(segments, pool, eps) {
|
|
|
1852
1859
|
]) {
|
|
1853
1860
|
for (const p of [other.a, other.b]) {
|
|
1854
1861
|
if (p.id === seg.a.id || p.id === seg.b.id) continue;
|
|
1855
|
-
const { t: t2, dist:
|
|
1862
|
+
const { t: t2, dist: dist3 } = project(seg.a, seg.b, p);
|
|
1856
1863
|
const len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);
|
|
1857
|
-
if (
|
|
1864
|
+
if (dist3 <= eps && t2 * len > eps && (1 - t2) * len > eps) {
|
|
1858
1865
|
addSplit(split, t2, p);
|
|
1859
1866
|
touched = true;
|
|
1860
1867
|
}
|
|
@@ -1988,14 +1995,14 @@ function buildOverlay(inputs, options) {
|
|
|
1988
1995
|
function insideFlags(windings, fillRules) {
|
|
1989
1996
|
return windings.map((w, k) => fillRules[k] === "evenodd" ? (w & 1) === 1 : w !== 0);
|
|
1990
1997
|
}
|
|
1991
|
-
function traceRings(
|
|
1998
|
+
function traceRings(directed2) {
|
|
1992
1999
|
const outgoing = /* @__PURE__ */ new Map();
|
|
1993
|
-
for (const e of
|
|
2000
|
+
for (const e of directed2) {
|
|
1994
2001
|
const list = outgoing.get(e.from.id);
|
|
1995
2002
|
if (list) list.push(e);
|
|
1996
2003
|
else outgoing.set(e.from.id, [e]);
|
|
1997
2004
|
}
|
|
1998
|
-
const
|
|
2005
|
+
const TWO_PI2 = Math.PI * 2;
|
|
1999
2006
|
const next = (incoming) => {
|
|
2000
2007
|
const candidates = outgoing.get(incoming.to.id);
|
|
2001
2008
|
if (!candidates) return void 0;
|
|
@@ -2004,8 +2011,8 @@ function traceRings(directed) {
|
|
|
2004
2011
|
let bestTurn = Infinity;
|
|
2005
2012
|
for (const c of candidates) {
|
|
2006
2013
|
let turn = back - c.angle;
|
|
2007
|
-
turn -= Math.floor(turn /
|
|
2008
|
-
if (turn <= 1e-15) turn =
|
|
2014
|
+
turn -= Math.floor(turn / TWO_PI2) * TWO_PI2;
|
|
2015
|
+
if (turn <= 1e-15) turn = TWO_PI2;
|
|
2009
2016
|
if (turn < bestTurn) {
|
|
2010
2017
|
bestTurn = turn;
|
|
2011
2018
|
best = c;
|
|
@@ -2015,11 +2022,11 @@ function traceRings(directed) {
|
|
|
2015
2022
|
};
|
|
2016
2023
|
const used = /* @__PURE__ */ new Set();
|
|
2017
2024
|
const rings = [];
|
|
2018
|
-
for (const start of
|
|
2025
|
+
for (const start of directed2) {
|
|
2019
2026
|
if (used.has(start)) continue;
|
|
2020
2027
|
const ring = [];
|
|
2021
2028
|
let edge = start;
|
|
2022
|
-
let guard =
|
|
2029
|
+
let guard = directed2.length + 1;
|
|
2023
2030
|
while (edge && !used.has(edge) && guard-- > 0) {
|
|
2024
2031
|
used.add(edge);
|
|
2025
2032
|
ring.push(edge.from);
|
|
@@ -2043,9 +2050,9 @@ function removeCollinear(ring, eps) {
|
|
|
2043
2050
|
const dx = nxt[0] - prev[0];
|
|
2044
2051
|
const dy = nxt[1] - prev[1];
|
|
2045
2052
|
const len = Math.hypot(dx, dy);
|
|
2046
|
-
const
|
|
2047
|
-
const
|
|
2048
|
-
if (len > 0 && Math.abs(
|
|
2053
|
+
const cross2 = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;
|
|
2054
|
+
const dot3 = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;
|
|
2055
|
+
if (len > 0 && Math.abs(cross2) / len <= eps && dot3 > 0 && dot3 < len * len) {
|
|
2049
2056
|
changed = true;
|
|
2050
2057
|
continue;
|
|
2051
2058
|
}
|
|
@@ -2087,15 +2094,15 @@ function assemble(rings, eps) {
|
|
|
2087
2094
|
return polygons;
|
|
2088
2095
|
}
|
|
2089
2096
|
function extract(overlay, isInside) {
|
|
2090
|
-
const
|
|
2097
|
+
const directed2 = [];
|
|
2091
2098
|
overlay.edges.forEach((edge, i) => {
|
|
2092
2099
|
const l = isInside(insideFlags(overlay.left[i], overlay.fillRules));
|
|
2093
2100
|
const r = isInside(insideFlags(overlay.right[i], overlay.fillRules));
|
|
2094
2101
|
if (l === r) return;
|
|
2095
2102
|
const [from, to] = l ? [edge.a, edge.b] : [edge.b, edge.a];
|
|
2096
|
-
|
|
2103
|
+
directed2.push({ from, to, angle: Math.atan2(to.y - from.y, to.x - from.x) });
|
|
2097
2104
|
});
|
|
2098
|
-
return assemble(traceRings(
|
|
2105
|
+
return assemble(traceRings(directed2), overlay.eps);
|
|
2099
2106
|
}
|
|
2100
2107
|
function predicate(operation) {
|
|
2101
2108
|
switch (operation) {
|
|
@@ -2978,9 +2985,9 @@ function rdp(points, tolerance) {
|
|
|
2978
2985
|
let maxDist = -1;
|
|
2979
2986
|
let index = -1;
|
|
2980
2987
|
for (let i = s + 1; i < e; i++) {
|
|
2981
|
-
const
|
|
2982
|
-
if (
|
|
2983
|
-
maxDist =
|
|
2988
|
+
const dist3 = segmentDistance(points[i], points[s], points[e]);
|
|
2989
|
+
if (dist3 > maxDist) {
|
|
2990
|
+
maxDist = dist3;
|
|
2984
2991
|
index = i;
|
|
2985
2992
|
}
|
|
2986
2993
|
}
|
|
@@ -2996,9 +3003,9 @@ function simplifyRing(ring, tolerance) {
|
|
|
2996
3003
|
let far = 0;
|
|
2997
3004
|
let farDist = -1;
|
|
2998
3005
|
for (let i = 1; i < ring.length; i++) {
|
|
2999
|
-
const
|
|
3000
|
-
if (
|
|
3001
|
-
farDist =
|
|
3006
|
+
const dist3 = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);
|
|
3007
|
+
if (dist3 > farDist) {
|
|
3008
|
+
farDist = dist3;
|
|
3002
3009
|
far = i;
|
|
3003
3010
|
}
|
|
3004
3011
|
}
|
|
@@ -3036,253 +3043,2539 @@ function polygonToSvgPath(input, options = {}) {
|
|
|
3036
3043
|
return fit ? fitted : ringsToPath(rings, f);
|
|
3037
3044
|
}
|
|
3038
3045
|
|
|
3039
|
-
// src/canvas/
|
|
3040
|
-
var
|
|
3041
|
-
var
|
|
3042
|
-
var
|
|
3043
|
-
var
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
return
|
|
3051
|
-
}
|
|
3052
|
-
function
|
|
3053
|
-
const
|
|
3054
|
-
|
|
3046
|
+
// src/canvas/pathops/bezier.ts
|
|
3047
|
+
var sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
3048
|
+
var add2 = (a, b) => [a[0] + b[0], a[1] + b[1]];
|
|
3049
|
+
var scale = (a, s) => [a[0] * s, a[1] * s];
|
|
3050
|
+
var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
3051
|
+
var cross = (a, b) => a[0] * b[1] - a[1] * b[0];
|
|
3052
|
+
var dist2 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
3053
|
+
var lerp = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
3054
|
+
var samePoint = (a, b) => a[0] === b[0] && a[1] === b[1];
|
|
3055
|
+
function unit2(v) {
|
|
3056
|
+
const l = Math.hypot(v[0], v[1]);
|
|
3057
|
+
return l > 0 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
|
|
3058
|
+
}
|
|
3059
|
+
function segmentPoint(seg, t) {
|
|
3060
|
+
const n = seg.length;
|
|
3061
|
+
if (n === 2) return lerp(seg[0], seg[1], t);
|
|
3062
|
+
const mt = 1 - t;
|
|
3063
|
+
if (n === 3) {
|
|
3064
|
+
const a2 = mt * mt;
|
|
3065
|
+
const b2 = 2 * mt * t;
|
|
3066
|
+
const c2 = t * t;
|
|
3067
|
+
return [a2 * seg[0][0] + b2 * seg[1][0] + c2 * seg[2][0], a2 * seg[0][1] + b2 * seg[1][1] + c2 * seg[2][1]];
|
|
3068
|
+
}
|
|
3069
|
+
const a = mt * mt * mt;
|
|
3070
|
+
const b = 3 * mt * mt * t;
|
|
3071
|
+
const c = 3 * mt * t * t;
|
|
3072
|
+
const d = t * t * t;
|
|
3073
|
+
return [
|
|
3074
|
+
a * seg[0][0] + b * seg[1][0] + c * seg[2][0] + d * seg[3][0],
|
|
3075
|
+
a * seg[0][1] + b * seg[1][1] + c * seg[2][1] + d * seg[3][1]
|
|
3076
|
+
];
|
|
3055
3077
|
}
|
|
3056
|
-
function
|
|
3057
|
-
const
|
|
3058
|
-
if (
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3078
|
+
function segmentDerivative(seg, t) {
|
|
3079
|
+
const n = seg.length;
|
|
3080
|
+
if (n === 2) return sub2(seg[1], seg[0]);
|
|
3081
|
+
if (n === 3) {
|
|
3082
|
+
const a2 = sub2(seg[1], seg[0]);
|
|
3083
|
+
const b2 = sub2(seg[2], seg[1]);
|
|
3084
|
+
return [2 * ((1 - t) * a2[0] + t * b2[0]), 2 * ((1 - t) * a2[1] + t * b2[1])];
|
|
3085
|
+
}
|
|
3086
|
+
const a = sub2(seg[1], seg[0]);
|
|
3087
|
+
const b = sub2(seg[2], seg[1]);
|
|
3088
|
+
const c = sub2(seg[3], seg[2]);
|
|
3089
|
+
const mt = 1 - t;
|
|
3090
|
+
return [
|
|
3091
|
+
3 * (mt * mt * a[0] + 2 * mt * t * b[0] + t * t * c[0]),
|
|
3092
|
+
3 * (mt * mt * a[1] + 2 * mt * t * b[1] + t * t * c[1])
|
|
3093
|
+
];
|
|
3066
3094
|
}
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
if (
|
|
3070
|
-
|
|
3071
|
-
|
|
3095
|
+
function segmentSecondDerivative(seg, t) {
|
|
3096
|
+
const n = seg.length;
|
|
3097
|
+
if (n === 2) return [0, 0];
|
|
3098
|
+
if (n === 3) return [2 * (seg[2][0] - 2 * seg[1][0] + seg[0][0]), 2 * (seg[2][1] - 2 * seg[1][1] + seg[0][1])];
|
|
3099
|
+
const a = [seg[2][0] - 2 * seg[1][0] + seg[0][0], seg[2][1] - 2 * seg[1][1] + seg[0][1]];
|
|
3100
|
+
const b = [seg[3][0] - 2 * seg[2][0] + seg[1][0], seg[3][1] - 2 * seg[2][1] + seg[1][1]];
|
|
3101
|
+
return [6 * ((1 - t) * a[0] + t * b[0]), 6 * ((1 - t) * a[1] + t * b[1])];
|
|
3102
|
+
}
|
|
3103
|
+
function segmentTangent(seg, t) {
|
|
3104
|
+
const d = unit2(segmentDerivative(seg, t));
|
|
3105
|
+
if (d) return d;
|
|
3106
|
+
const h = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
|
|
3107
|
+
const e = unit2(segmentDerivative(seg, h));
|
|
3108
|
+
if (e) return e;
|
|
3109
|
+
return unit2(sub2(seg[seg.length - 1], seg[0])) ?? [1, 0];
|
|
3110
|
+
}
|
|
3111
|
+
function segmentCurvature(seg, t) {
|
|
3112
|
+
if (seg.length === 2) return 0;
|
|
3113
|
+
let d = segmentDerivative(seg, t);
|
|
3114
|
+
let tt = t;
|
|
3115
|
+
if (Math.hypot(d[0], d[1]) < 1e-12) {
|
|
3116
|
+
tt = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
|
|
3117
|
+
d = segmentDerivative(seg, tt);
|
|
3118
|
+
}
|
|
3119
|
+
const dd = segmentSecondDerivative(seg, tt);
|
|
3120
|
+
const len = Math.hypot(d[0], d[1]);
|
|
3121
|
+
if (!(len > 0)) return 0;
|
|
3122
|
+
return cross(d, dd) / (len * len * len);
|
|
3123
|
+
}
|
|
3124
|
+
function splitSegment(seg, t) {
|
|
3125
|
+
const n = seg.length;
|
|
3126
|
+
if (n === 2) {
|
|
3127
|
+
const m2 = lerp(seg[0], seg[1], t);
|
|
3128
|
+
return [
|
|
3129
|
+
[seg[0], m2],
|
|
3130
|
+
[m2, seg[1]]
|
|
3131
|
+
];
|
|
3132
|
+
}
|
|
3133
|
+
if (n === 3) {
|
|
3134
|
+
const a2 = lerp(seg[0], seg[1], t);
|
|
3135
|
+
const b2 = lerp(seg[1], seg[2], t);
|
|
3136
|
+
const m2 = lerp(a2, b2, t);
|
|
3137
|
+
return [
|
|
3138
|
+
[seg[0], a2, m2],
|
|
3139
|
+
[m2, b2, seg[2]]
|
|
3140
|
+
];
|
|
3141
|
+
}
|
|
3142
|
+
const a = lerp(seg[0], seg[1], t);
|
|
3143
|
+
const b = lerp(seg[1], seg[2], t);
|
|
3144
|
+
const c = lerp(seg[2], seg[3], t);
|
|
3145
|
+
const ab = lerp(a, b, t);
|
|
3146
|
+
const bc = lerp(b, c, t);
|
|
3147
|
+
const m = lerp(ab, bc, t);
|
|
3148
|
+
return [
|
|
3149
|
+
[seg[0], a, ab, m],
|
|
3150
|
+
[m, bc, c, seg[3]]
|
|
3151
|
+
];
|
|
3072
3152
|
}
|
|
3073
|
-
function
|
|
3074
|
-
|
|
3153
|
+
function subSegment(seg, t0, t1) {
|
|
3154
|
+
if (t1 < t0) return subSegment(seg, t1, t0).reverse();
|
|
3155
|
+
if (t0 <= 0 && t1 >= 1) return [...seg];
|
|
3156
|
+
let s = [...seg];
|
|
3157
|
+
if (t1 < 1) s = splitSegment(s, t1)[0];
|
|
3158
|
+
if (t0 > 0) s = splitSegment(s, t1 > 0 ? t0 / t1 : 0)[1];
|
|
3159
|
+
return s;
|
|
3160
|
+
}
|
|
3161
|
+
function isStraightSegment(seg, tol) {
|
|
3162
|
+
if (seg.length === 2) return true;
|
|
3163
|
+
const a = seg[0];
|
|
3164
|
+
const b = seg[seg.length - 1];
|
|
3165
|
+
const chord = sub2(b, a);
|
|
3166
|
+
const len = Math.hypot(chord[0], chord[1]);
|
|
3167
|
+
for (let i = 1; i < seg.length - 1; i++) {
|
|
3168
|
+
const v = sub2(seg[i], a);
|
|
3169
|
+
if (len === 0) {
|
|
3170
|
+
if (Math.hypot(v[0], v[1]) > tol) return false;
|
|
3171
|
+
continue;
|
|
3172
|
+
}
|
|
3173
|
+
if (Math.abs(cross(chord, v)) / len > tol) return false;
|
|
3174
|
+
const along = dot2(chord, v) / len;
|
|
3175
|
+
if (along < -tol || along > len + tol) return false;
|
|
3176
|
+
}
|
|
3177
|
+
return true;
|
|
3075
3178
|
}
|
|
3076
|
-
function
|
|
3077
|
-
const
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3179
|
+
function quadraticRoots(a, b, c, out) {
|
|
3180
|
+
const push = (t) => {
|
|
3181
|
+
if (t > 1e-12 && t < 1 - 1e-12) out.push(t);
|
|
3182
|
+
};
|
|
3183
|
+
const scaleAbs = Math.max(Math.abs(a), Math.abs(b), Math.abs(c));
|
|
3184
|
+
if (scaleAbs === 0) return;
|
|
3185
|
+
if (Math.abs(a) <= 1e-12 * scaleAbs) {
|
|
3186
|
+
if (b !== 0) push(-c / b);
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
const disc = b * b - 4 * a * c;
|
|
3190
|
+
if (disc < 0) return;
|
|
3191
|
+
const sq = Math.sqrt(disc);
|
|
3192
|
+
const q = -0.5 * (b + (b >= 0 ? sq : -sq));
|
|
3193
|
+
push(q / a);
|
|
3194
|
+
if (q !== 0) push(c / q);
|
|
3082
3195
|
}
|
|
3083
|
-
function
|
|
3084
|
-
const
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
const operands = ordered.map((node) => shapeNodeToPolygon(node, lookup, options));
|
|
3091
|
-
const parentIds = new Set(ordered.map((n) => n.parentId ?? ""));
|
|
3092
|
-
const sharedParent = parentIds.size === 1 ? ordered[0].parentId : void 0;
|
|
3093
|
-
const parentOrigin = sharedParent && lookup.get(sharedParent) ? getAbsolutePosition(lookup.get(sharedParent), lookup) : { x: 0, y: 0 };
|
|
3094
|
-
const pickSource = () => {
|
|
3095
|
-
const style = options.styleFrom ?? "bottom";
|
|
3096
|
-
if (style === "top") return ordered[ordered.length - 1];
|
|
3097
|
-
if (style === "first") {
|
|
3098
|
-
const first = idList.find((id) => shapeIds.has(id));
|
|
3099
|
-
return first && lookup.get(first) || ordered[0];
|
|
3196
|
+
function segmentExtrema(seg, axis) {
|
|
3197
|
+
const out = [];
|
|
3198
|
+
if (seg.length === 3) {
|
|
3199
|
+
const d = seg[0][axis] - 2 * seg[1][axis] + seg[2][axis];
|
|
3200
|
+
if (d !== 0) {
|
|
3201
|
+
const t = (seg[0][axis] - seg[1][axis]) / d;
|
|
3202
|
+
if (t > 1e-12 && t < 1 - 1e-12) out.push(t);
|
|
3100
3203
|
}
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
const
|
|
3107
|
-
|
|
3108
|
-
const
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
const
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3204
|
+
} else if (seg.length === 4) {
|
|
3205
|
+
const p0 = seg[0][axis];
|
|
3206
|
+
const p1 = seg[1][axis];
|
|
3207
|
+
const p2 = seg[2][axis];
|
|
3208
|
+
const p3 = seg[3][axis];
|
|
3209
|
+
const a = -p0 + 3 * p1 - 3 * p2 + p3;
|
|
3210
|
+
const b = 2 * (p0 - 2 * p1 + p2);
|
|
3211
|
+
const c = p1 - p0;
|
|
3212
|
+
quadraticRoots(a, b, c, out);
|
|
3213
|
+
}
|
|
3214
|
+
return out.sort((x, y) => x - y);
|
|
3215
|
+
}
|
|
3216
|
+
function segmentBounds(seg) {
|
|
3217
|
+
const a = seg[0];
|
|
3218
|
+
const b = seg[seg.length - 1];
|
|
3219
|
+
let minX = Math.min(a[0], b[0]);
|
|
3220
|
+
let maxX = Math.max(a[0], b[0]);
|
|
3221
|
+
let minY = Math.min(a[1], b[1]);
|
|
3222
|
+
let maxY = Math.max(a[1], b[1]);
|
|
3223
|
+
for (const axis of [0, 1]) {
|
|
3224
|
+
for (const t of segmentExtrema(seg, axis)) {
|
|
3225
|
+
const p = segmentPoint(seg, t);
|
|
3226
|
+
if (axis === 0) {
|
|
3227
|
+
minX = Math.min(minX, p[0]);
|
|
3228
|
+
maxX = Math.max(maxX, p[0]);
|
|
3229
|
+
} else {
|
|
3230
|
+
minY = Math.min(minY, p[1]);
|
|
3231
|
+
maxY = Math.max(maxY, p[1]);
|
|
3232
|
+
}
|
|
3129
3233
|
}
|
|
3130
|
-
const {
|
|
3131
|
-
[LOCKED_DATA_KEY]: _locked,
|
|
3132
|
-
[LOCKED_PREVIOUS_FLAGS_KEY]: _previous,
|
|
3133
|
-
...data
|
|
3134
|
-
} = source.data ?? {};
|
|
3135
|
-
const {
|
|
3136
|
-
measured: _measured,
|
|
3137
|
-
selected: _selected,
|
|
3138
|
-
dragging: _dragging,
|
|
3139
|
-
positionAbsolute: _positionAbsolute,
|
|
3140
|
-
internals: _internals,
|
|
3141
|
-
parentId: _parentId,
|
|
3142
|
-
...rest
|
|
3143
|
-
} = source;
|
|
3144
|
-
const node = {
|
|
3145
|
-
...rest,
|
|
3146
|
-
id: createId(index),
|
|
3147
|
-
position: { x: bounds.minX - parentOrigin.x, y: bounds.minY - parentOrigin.y },
|
|
3148
|
-
width,
|
|
3149
|
-
height,
|
|
3150
|
-
data: { ...data, [SHAPE_DATA_KEY]: shape }
|
|
3151
|
-
};
|
|
3152
|
-
if (sharedParent) node.parentId = sharedParent;
|
|
3153
|
-
if (options.nodeType !== void 0) node.type = options.nodeType;
|
|
3154
|
-
if (source.style) node.style = withStyleSize(source.style, { width, height });
|
|
3155
|
-
if (options.select !== false) node.selected = true;
|
|
3156
|
-
return node;
|
|
3157
|
-
};
|
|
3158
|
-
const created = [];
|
|
3159
|
-
if (operation === "divide") {
|
|
3160
|
-
const pieces = dividePolygons(operands, { epsilon: options.epsilon });
|
|
3161
|
-
pieces.forEach((piece) => {
|
|
3162
|
-
const source = ordered[piece.operands[piece.operands.length - 1]];
|
|
3163
|
-
const node = build([piece.polygon], source, created.length);
|
|
3164
|
-
if (node) created.push(node);
|
|
3165
|
-
});
|
|
3166
|
-
} else {
|
|
3167
|
-
const result = polygonBoolean(BOOLEAN_FOR[operation], operands, { epsilon: options.epsilon });
|
|
3168
|
-
const node = build(result, pickSource(), 0);
|
|
3169
|
-
if (node) created.push(node);
|
|
3170
3234
|
}
|
|
3171
|
-
|
|
3172
|
-
return { ok: true, operation, removeIds: ordered.map((n) => n.id), nodes: created };
|
|
3235
|
+
return { minX, minY, maxX, maxY };
|
|
3173
3236
|
}
|
|
3174
|
-
function
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
function applyShapeCombination(nodes, change, options = {}) {
|
|
3178
|
-
const remove = new Set(change.removeIds);
|
|
3179
|
-
const insertAt = nodes.findIndex((n) => remove.has(n.id));
|
|
3180
|
-
const deselect = options.deselect !== false && change.nodes.some((n) => n.selected);
|
|
3237
|
+
function monotoneParams(seg) {
|
|
3238
|
+
if (seg.length === 2) return [];
|
|
3239
|
+
const ts = [...segmentExtrema(seg, 0), ...segmentExtrema(seg, 1)].sort((a, b) => a - b);
|
|
3181
3240
|
const out = [];
|
|
3182
|
-
|
|
3183
|
-
if (index === insertAt) out.push(...change.nodes);
|
|
3184
|
-
if (remove.has(node.id)) return;
|
|
3185
|
-
out.push(deselect && node.selected ? { ...node, selected: false } : node);
|
|
3186
|
-
});
|
|
3187
|
-
if (insertAt === -1) out.push(...change.nodes);
|
|
3188
|
-
return out;
|
|
3189
|
-
}
|
|
3190
|
-
function revertShapeCombination(nodes, change, previous) {
|
|
3191
|
-
const created = new Set(change.nodes.map((n) => n.id));
|
|
3192
|
-
const removed = new Set(change.removeIds);
|
|
3193
|
-
const out = nodes.filter((n) => !created.has(n.id));
|
|
3194
|
-
previous.forEach((node, index) => {
|
|
3195
|
-
if (!removed.has(node.id) || out.some((n) => n.id === node.id)) return;
|
|
3196
|
-
out.splice(Math.min(index, out.length), 0, node);
|
|
3197
|
-
});
|
|
3241
|
+
for (const t of ts) if (out.length === 0 || t - out[out.length - 1] > 1e-9) out.push(t);
|
|
3198
3242
|
return out;
|
|
3199
3243
|
}
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3244
|
+
function segmentAreaTerm(seg) {
|
|
3245
|
+
if (seg.length === 2) return (seg[0][0] * seg[1][1] - seg[1][0] * seg[0][1]) / 2;
|
|
3246
|
+
const nodes = [-Math.sqrt(3 / 5), 0, Math.sqrt(3 / 5)];
|
|
3247
|
+
const weights = [5 / 9, 8 / 9, 5 / 9];
|
|
3248
|
+
let sum = 0;
|
|
3249
|
+
for (let i = 0; i < 3; i++) {
|
|
3250
|
+
const t = (nodes[i] + 1) / 2;
|
|
3251
|
+
const p = segmentPoint(seg, t);
|
|
3252
|
+
const d = segmentDerivative(seg, t);
|
|
3253
|
+
sum += weights[i] * (p[0] * d[1] - p[1] * d[0]);
|
|
3254
|
+
}
|
|
3255
|
+
return sum / 4;
|
|
3256
|
+
}
|
|
3257
|
+
var GL_X = [
|
|
3258
|
+
-0.9602898564975363,
|
|
3259
|
+
-0.7966664774136267,
|
|
3260
|
+
-0.525532409916329,
|
|
3261
|
+
-0.1834346424956498,
|
|
3262
|
+
0.1834346424956498,
|
|
3263
|
+
0.525532409916329,
|
|
3264
|
+
0.7966664774136267,
|
|
3265
|
+
0.9602898564975363
|
|
3211
3266
|
];
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
const
|
|
3224
|
-
const
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3267
|
+
var GL_W = [
|
|
3268
|
+
0.1012285362903763,
|
|
3269
|
+
0.2223810344533745,
|
|
3270
|
+
0.3137066458778873,
|
|
3271
|
+
0.362683783378362,
|
|
3272
|
+
0.362683783378362,
|
|
3273
|
+
0.3137066458778873,
|
|
3274
|
+
0.2223810344533745,
|
|
3275
|
+
0.1012285362903763
|
|
3276
|
+
];
|
|
3277
|
+
function glLength(seg, t0, t1) {
|
|
3278
|
+
const half = (t1 - t0) / 2;
|
|
3279
|
+
const mid = (t0 + t1) / 2;
|
|
3280
|
+
let sum = 0;
|
|
3281
|
+
for (let i = 0; i < GL_X.length; i++) {
|
|
3282
|
+
const d = segmentDerivative(seg, mid + half * GL_X[i]);
|
|
3283
|
+
sum += GL_W[i] * Math.hypot(d[0], d[1]);
|
|
3284
|
+
}
|
|
3285
|
+
return sum * half;
|
|
3286
|
+
}
|
|
3287
|
+
function adaptiveLength(seg, t0, t1, whole, tol, depth) {
|
|
3288
|
+
const m = (t0 + t1) / 2;
|
|
3289
|
+
const left = glLength(seg, t0, m);
|
|
3290
|
+
const right = glLength(seg, m, t1);
|
|
3291
|
+
if (depth >= 18 || Math.abs(left + right - whole) <= tol) return left + right;
|
|
3292
|
+
return adaptiveLength(seg, t0, m, left, tol / 2, depth + 1) + adaptiveLength(seg, m, t1, right, tol / 2, depth + 1);
|
|
3293
|
+
}
|
|
3294
|
+
function segmentLength(seg, t0 = 0, t1 = 1, tolerance = 1e-9) {
|
|
3295
|
+
if (t1 < t0) return segmentLength(seg, t1, t0, tolerance);
|
|
3296
|
+
if (seg.length === 2) return dist2(seg[0], seg[1]) * (t1 - t0);
|
|
3297
|
+
const whole = glLength(seg, t0, t1);
|
|
3298
|
+
return adaptiveLength(seg, t0, t1, whole, Math.max(tolerance, whole * 1e-12), 0);
|
|
3299
|
+
}
|
|
3300
|
+
function segmentParamAtLength(seg, length, total = segmentLength(seg)) {
|
|
3301
|
+
if (!(total > 0) || length <= 0) return 0;
|
|
3302
|
+
if (length >= total) return 1;
|
|
3303
|
+
if (seg.length === 2) return length / total;
|
|
3230
3304
|
let lo = 0;
|
|
3231
|
-
let hi =
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3305
|
+
let hi = 1;
|
|
3306
|
+
let t = length / total;
|
|
3307
|
+
for (let i = 0; i < 40; i++) {
|
|
3308
|
+
const err = segmentLength(seg, 0, t) - length;
|
|
3309
|
+
if (Math.abs(err) <= 1e-10 * Math.max(1, total)) break;
|
|
3310
|
+
if (err > 0) hi = t;
|
|
3311
|
+
else lo = t;
|
|
3312
|
+
const d = segmentDerivative(seg, t);
|
|
3313
|
+
const speed = Math.hypot(d[0], d[1]);
|
|
3314
|
+
let next = speed > 0 ? t - err / speed : (lo + hi) / 2;
|
|
3315
|
+
if (!(next > lo && next < hi)) next = (lo + hi) / 2;
|
|
3316
|
+
t = next;
|
|
3317
|
+
}
|
|
3318
|
+
return t;
|
|
3319
|
+
}
|
|
3320
|
+
function segmentNearest(seg, p) {
|
|
3321
|
+
if (seg.length === 2) {
|
|
3322
|
+
const d = sub2(seg[1], seg[0]);
|
|
3323
|
+
const len2 = dot2(d, d);
|
|
3324
|
+
const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, dot2(sub2(p, seg[0]), d) / len2));
|
|
3325
|
+
return { t, distance: dist2(p, segmentPoint(seg, t)) };
|
|
3326
|
+
}
|
|
3327
|
+
const samples = 16;
|
|
3328
|
+
let best = { t: 0, distance: dist2(p, seg[0]) };
|
|
3329
|
+
const candidates = [];
|
|
3330
|
+
let prev = Infinity;
|
|
3331
|
+
let prevT = 0;
|
|
3332
|
+
let descending = false;
|
|
3333
|
+
for (let i = 0; i <= samples; i++) {
|
|
3334
|
+
const t = i / samples;
|
|
3335
|
+
const d = dist2(p, segmentPoint(seg, t));
|
|
3336
|
+
if (d < best.distance) best = { t, distance: d };
|
|
3337
|
+
if (i > 0 && d > prev && descending) candidates.push(prevT);
|
|
3338
|
+
descending = d < prev;
|
|
3339
|
+
prev = d;
|
|
3340
|
+
prevT = t;
|
|
3341
|
+
}
|
|
3342
|
+
candidates.push(best.t, 1);
|
|
3343
|
+
for (const start of candidates) {
|
|
3344
|
+
let t = start;
|
|
3345
|
+
for (let k = 0; k < 12; k++) {
|
|
3346
|
+
const q = segmentPoint(seg, t);
|
|
3347
|
+
const d1 = segmentDerivative(seg, t);
|
|
3348
|
+
const d2 = segmentSecondDerivative(seg, t);
|
|
3349
|
+
const diff = sub2(q, p);
|
|
3350
|
+
const f = dot2(diff, d1);
|
|
3351
|
+
const fp = dot2(d1, d1) + dot2(diff, d2);
|
|
3352
|
+
if (!(Math.abs(fp) > 1e-300)) break;
|
|
3353
|
+
const next = Math.max(0, Math.min(1, t - f / fp));
|
|
3354
|
+
if (Math.abs(next - t) < 1e-14) {
|
|
3355
|
+
t = next;
|
|
3356
|
+
break;
|
|
3357
|
+
}
|
|
3358
|
+
t = next;
|
|
3359
|
+
}
|
|
3360
|
+
const d = dist2(p, segmentPoint(seg, t));
|
|
3361
|
+
if (d < best.distance) best = { t, distance: d };
|
|
3236
3362
|
}
|
|
3363
|
+
return best;
|
|
3364
|
+
}
|
|
3365
|
+
function arcToCubics(from, cmd) {
|
|
3366
|
+
const [x0, y0] = from;
|
|
3367
|
+
const { x, y } = cmd;
|
|
3368
|
+
if (x0 === x && y0 === y) return [];
|
|
3369
|
+
let rx = Math.abs(cmd.rx);
|
|
3370
|
+
let ry = Math.abs(cmd.ry);
|
|
3371
|
+
if (rx === 0 || ry === 0) return [[from, [x, y]]];
|
|
3372
|
+
const phi = cmd.rotation * Math.PI / 180;
|
|
3373
|
+
const cos = Math.cos(phi);
|
|
3374
|
+
const sin = Math.sin(phi);
|
|
3375
|
+
const dx = (x0 - x) / 2;
|
|
3376
|
+
const dy = (y0 - y) / 2;
|
|
3377
|
+
const x1p = cos * dx + sin * dy;
|
|
3378
|
+
const y1p = -sin * dx + cos * dy;
|
|
3379
|
+
const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
|
|
3380
|
+
if (lambda > 1) {
|
|
3381
|
+
const s = Math.sqrt(lambda);
|
|
3382
|
+
rx *= s;
|
|
3383
|
+
ry *= s;
|
|
3384
|
+
}
|
|
3385
|
+
const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
|
|
3386
|
+
const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
|
|
3387
|
+
let coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));
|
|
3388
|
+
if (cmd.largeArc === cmd.sweep) coef = -coef;
|
|
3389
|
+
const cxp = coef * rx * y1p / ry;
|
|
3390
|
+
const cyp = -coef * ry * x1p / rx;
|
|
3391
|
+
const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
|
|
3392
|
+
const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
|
|
3393
|
+
const angle = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
3394
|
+
const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
|
|
3395
|
+
let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
|
|
3396
|
+
if (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;
|
|
3397
|
+
else if (cmd.sweep && delta < 0) delta += 2 * Math.PI;
|
|
3398
|
+
const n = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9));
|
|
3399
|
+
const step = delta / n;
|
|
3400
|
+
const k = 4 / 3 * Math.tan(step / 4);
|
|
3401
|
+
const map = (ux, uy) => {
|
|
3402
|
+
const ex = rx * ux;
|
|
3403
|
+
const ey = ry * uy;
|
|
3404
|
+
return [cos * ex - sin * ey + cx, sin * ex + cos * ey + cy];
|
|
3405
|
+
};
|
|
3237
3406
|
const out = [];
|
|
3238
|
-
|
|
3407
|
+
let prev = from;
|
|
3408
|
+
for (let i = 0; i < n; i++) {
|
|
3409
|
+
const a0 = theta1 + step * i;
|
|
3410
|
+
const a1 = a0 + step;
|
|
3411
|
+
const c0 = Math.cos(a0);
|
|
3412
|
+
const s0 = Math.sin(a0);
|
|
3413
|
+
const c1 = Math.cos(a1);
|
|
3414
|
+
const s1 = Math.sin(a1);
|
|
3415
|
+
const end = i === n - 1 ? [x, y] : map(c1, s1);
|
|
3416
|
+
out.push([prev, map(c0 - k * s0, s0 + k * c0), map(c1 + k * s1, s1 - k * c1), end]);
|
|
3417
|
+
prev = end;
|
|
3418
|
+
}
|
|
3239
3419
|
return out;
|
|
3240
3420
|
}
|
|
3241
|
-
function
|
|
3242
|
-
|
|
3421
|
+
function isBezierCommandList(input) {
|
|
3422
|
+
return Array.isArray(input) && !input.some((c) => c && c.type === "A");
|
|
3423
|
+
}
|
|
3424
|
+
function toBezierPath(input) {
|
|
3425
|
+
const commands = typeof input === "string" ? parseSvgPath(input) : isBezierCommandList(input) ? input : input;
|
|
3243
3426
|
const out = [];
|
|
3244
|
-
|
|
3427
|
+
let current = [0, 0];
|
|
3428
|
+
let start = [0, 0];
|
|
3429
|
+
let open = false;
|
|
3430
|
+
const ensureMove = () => {
|
|
3431
|
+
if (!open) {
|
|
3432
|
+
out.push({ type: "M", x: start[0], y: start[1] });
|
|
3433
|
+
current = start;
|
|
3434
|
+
open = true;
|
|
3435
|
+
}
|
|
3436
|
+
};
|
|
3437
|
+
for (const cmd of commands) {
|
|
3438
|
+
switch (cmd.type) {
|
|
3439
|
+
case "M":
|
|
3440
|
+
start = [cmd.x, cmd.y];
|
|
3441
|
+
current = start;
|
|
3442
|
+
out.push({ type: "M", x: cmd.x, y: cmd.y });
|
|
3443
|
+
open = true;
|
|
3444
|
+
break;
|
|
3445
|
+
case "L":
|
|
3446
|
+
ensureMove();
|
|
3447
|
+
out.push({ type: "L", x: cmd.x, y: cmd.y });
|
|
3448
|
+
current = [cmd.x, cmd.y];
|
|
3449
|
+
break;
|
|
3450
|
+
case "Q":
|
|
3451
|
+
ensureMove();
|
|
3452
|
+
out.push({ type: "Q", x1: cmd.x1, y1: cmd.y1, x: cmd.x, y: cmd.y });
|
|
3453
|
+
current = [cmd.x, cmd.y];
|
|
3454
|
+
break;
|
|
3455
|
+
case "C":
|
|
3456
|
+
ensureMove();
|
|
3457
|
+
out.push({ type: "C", x1: cmd.x1, y1: cmd.y1, x2: cmd.x2, y2: cmd.y2, x: cmd.x, y: cmd.y });
|
|
3458
|
+
current = [cmd.x, cmd.y];
|
|
3459
|
+
break;
|
|
3460
|
+
case "A": {
|
|
3461
|
+
ensureMove();
|
|
3462
|
+
for (const c of arcToCubics(current, cmd)) {
|
|
3463
|
+
if (c.length === 2) out.push({ type: "L", x: c[1][0], y: c[1][1] });
|
|
3464
|
+
else out.push({ type: "C", x1: c[1][0], y1: c[1][1], x2: c[2][0], y2: c[2][1], x: c[3][0], y: c[3][1] });
|
|
3465
|
+
}
|
|
3466
|
+
current = [cmd.x, cmd.y];
|
|
3467
|
+
break;
|
|
3468
|
+
}
|
|
3469
|
+
case "Z":
|
|
3470
|
+
if (open) out.push({ type: "Z" });
|
|
3471
|
+
open = false;
|
|
3472
|
+
current = start;
|
|
3473
|
+
break;
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3245
3476
|
return out;
|
|
3246
3477
|
}
|
|
3247
|
-
function
|
|
3248
|
-
const
|
|
3249
|
-
const
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
const
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) return unchanged;
|
|
3258
|
-
const x0 = rect.x;
|
|
3259
|
-
const x1 = rect.x + rect.width;
|
|
3260
|
-
const y0 = rect.y;
|
|
3261
|
-
const y1 = rect.y + rect.height;
|
|
3262
|
-
const bestHit = (horizontal) => {
|
|
3263
|
-
const dir = horizontal ? axes.x : axes.y;
|
|
3264
|
-
if (dir === 0) return null;
|
|
3265
|
-
const moving = horizontal ? dir < 0 ? x0 : x1 : dir < 0 ? y0 : y1;
|
|
3266
|
-
const fixed = horizontal ? dir < 0 ? x1 : x0 : dir < 0 ? y1 : y0;
|
|
3267
|
-
const min = (horizontal ? options.minWidth : options.minHeight) ?? 0;
|
|
3268
|
-
const valid = (t) => {
|
|
3269
|
-
const size = dir < 0 ? fixed - t : t - fixed;
|
|
3270
|
-
return Number.isFinite(size) && size > 1e-9 && size >= min;
|
|
3271
|
-
};
|
|
3272
|
-
let best = null;
|
|
3273
|
-
const consider = (values, kind) => {
|
|
3274
|
-
for (const t of values) {
|
|
3275
|
-
if (!valid(t)) continue;
|
|
3276
|
-
if (!best || Math.abs(t - moving) < Math.abs(best.value - moving)) best = { value: t, kind };
|
|
3277
|
-
}
|
|
3278
|
-
};
|
|
3279
|
-
if (useEdges) consider(within(horizontal ? index.edgeX : index.edgeY, moving, tolerance), "edge");
|
|
3280
|
-
if (useCentres) consider(within(horizontal ? index.centreX : index.centreY, moving, tolerance), "centre");
|
|
3281
|
-
if (!best && grid > 0) {
|
|
3282
|
-
const origin = horizontal ? options.gridOrigin?.x ?? 0 : options.gridOrigin?.y ?? 0;
|
|
3283
|
-
consider([snapToGrid(moving, grid, origin)], "grid");
|
|
3478
|
+
function pathToContours(input) {
|
|
3479
|
+
const path = toBezierPath(input);
|
|
3480
|
+
const contours = [];
|
|
3481
|
+
let current = null;
|
|
3482
|
+
let pen = [0, 0];
|
|
3483
|
+
let start = [0, 0];
|
|
3484
|
+
const push = (seg) => {
|
|
3485
|
+
if (!current) {
|
|
3486
|
+
current = { segments: [], closed: false };
|
|
3487
|
+
contours.push(current);
|
|
3284
3488
|
}
|
|
3285
|
-
return
|
|
3489
|
+
if (seg.every((p) => samePoint(p, seg[0]))) return;
|
|
3490
|
+
current.segments.push(seg);
|
|
3491
|
+
};
|
|
3492
|
+
for (const cmd of path) {
|
|
3493
|
+
switch (cmd.type) {
|
|
3494
|
+
case "M":
|
|
3495
|
+
start = [cmd.x, cmd.y];
|
|
3496
|
+
pen = start;
|
|
3497
|
+
current = { segments: [], closed: false };
|
|
3498
|
+
contours.push(current);
|
|
3499
|
+
break;
|
|
3500
|
+
case "L":
|
|
3501
|
+
push([pen, [cmd.x, cmd.y]]);
|
|
3502
|
+
pen = [cmd.x, cmd.y];
|
|
3503
|
+
break;
|
|
3504
|
+
case "Q":
|
|
3505
|
+
push([pen, [cmd.x1, cmd.y1], [cmd.x, cmd.y]]);
|
|
3506
|
+
pen = [cmd.x, cmd.y];
|
|
3507
|
+
break;
|
|
3508
|
+
case "C":
|
|
3509
|
+
push([pen, [cmd.x1, cmd.y1], [cmd.x2, cmd.y2], [cmd.x, cmd.y]]);
|
|
3510
|
+
pen = [cmd.x, cmd.y];
|
|
3511
|
+
break;
|
|
3512
|
+
case "Z":
|
|
3513
|
+
if (current) current.closed = true;
|
|
3514
|
+
current = null;
|
|
3515
|
+
pen = start;
|
|
3516
|
+
break;
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
return contours;
|
|
3520
|
+
}
|
|
3521
|
+
function contoursToPath(contours) {
|
|
3522
|
+
const out = [];
|
|
3523
|
+
for (const contour of contours) {
|
|
3524
|
+
const segs = contour.segments;
|
|
3525
|
+
if (segs.length === 0) continue;
|
|
3526
|
+
out.push({ type: "M", x: segs[0][0][0], y: segs[0][0][1] });
|
|
3527
|
+
let count = segs.length;
|
|
3528
|
+
if (contour.closed && count > 1) {
|
|
3529
|
+
const last = segs[count - 1];
|
|
3530
|
+
if (last.length === 2 && samePoint(last[1], segs[0][0])) count--;
|
|
3531
|
+
}
|
|
3532
|
+
for (let i = 0; i < count; i++) {
|
|
3533
|
+
const s = segs[i];
|
|
3534
|
+
const end = s[s.length - 1];
|
|
3535
|
+
if (s.length === 2) out.push({ type: "L", x: end[0], y: end[1] });
|
|
3536
|
+
else if (s.length === 3) out.push({ type: "Q", x1: s[1][0], y1: s[1][1], x: end[0], y: end[1] });
|
|
3537
|
+
else out.push({ type: "C", x1: s[1][0], y1: s[1][1], x2: s[2][0], y2: s[2][1], x: end[0], y: end[1] });
|
|
3538
|
+
}
|
|
3539
|
+
if (contour.closed) out.push({ type: "Z" });
|
|
3540
|
+
}
|
|
3541
|
+
return out;
|
|
3542
|
+
}
|
|
3543
|
+
function bezierPathToSvg(input, options = {}) {
|
|
3544
|
+
const path = toBezierPath(input);
|
|
3545
|
+
const precision = options.precision ?? 3;
|
|
3546
|
+
const dx = options.offset?.x ?? 0;
|
|
3547
|
+
const dy = options.offset?.y ?? 0;
|
|
3548
|
+
const fx = (n) => formatNumber2(n + dx, precision);
|
|
3549
|
+
const fy = (n) => formatNumber2(n + dy, precision);
|
|
3550
|
+
let d = "";
|
|
3551
|
+
for (const c of path) {
|
|
3552
|
+
switch (c.type) {
|
|
3553
|
+
case "M":
|
|
3554
|
+
d += `M${fx(c.x)} ${fy(c.y)}`;
|
|
3555
|
+
break;
|
|
3556
|
+
case "L":
|
|
3557
|
+
d += `L${fx(c.x)} ${fy(c.y)}`;
|
|
3558
|
+
break;
|
|
3559
|
+
case "Q":
|
|
3560
|
+
d += `Q${fx(c.x1)} ${fy(c.y1)} ${fx(c.x)} ${fy(c.y)}`;
|
|
3561
|
+
break;
|
|
3562
|
+
case "C":
|
|
3563
|
+
d += `C${fx(c.x1)} ${fy(c.y1)} ${fx(c.x2)} ${fy(c.y2)} ${fx(c.x)} ${fy(c.y)}`;
|
|
3564
|
+
break;
|
|
3565
|
+
case "Z":
|
|
3566
|
+
d += "Z";
|
|
3567
|
+
break;
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
return d;
|
|
3571
|
+
}
|
|
3572
|
+
function transformBezierPath(input, m) {
|
|
3573
|
+
const [a, b, c, d, e, f] = m;
|
|
3574
|
+
const X = (x, y) => a * x + c * y + e;
|
|
3575
|
+
const Y = (x, y) => b * x + d * y + f;
|
|
3576
|
+
return toBezierPath(input).map((cmd) => {
|
|
3577
|
+
switch (cmd.type) {
|
|
3578
|
+
case "M":
|
|
3579
|
+
case "L":
|
|
3580
|
+
return { type: cmd.type, x: X(cmd.x, cmd.y), y: Y(cmd.x, cmd.y) };
|
|
3581
|
+
case "Q":
|
|
3582
|
+
return { type: "Q", x1: X(cmd.x1, cmd.y1), y1: Y(cmd.x1, cmd.y1), x: X(cmd.x, cmd.y), y: Y(cmd.x, cmd.y) };
|
|
3583
|
+
case "C":
|
|
3584
|
+
return {
|
|
3585
|
+
type: "C",
|
|
3586
|
+
x1: X(cmd.x1, cmd.y1),
|
|
3587
|
+
y1: Y(cmd.x1, cmd.y1),
|
|
3588
|
+
x2: X(cmd.x2, cmd.y2),
|
|
3589
|
+
y2: Y(cmd.x2, cmd.y2),
|
|
3590
|
+
x: X(cmd.x, cmd.y),
|
|
3591
|
+
y: Y(cmd.x, cmd.y)
|
|
3592
|
+
};
|
|
3593
|
+
case "Z":
|
|
3594
|
+
return cmd;
|
|
3595
|
+
}
|
|
3596
|
+
});
|
|
3597
|
+
}
|
|
3598
|
+
function countPathSegments(input) {
|
|
3599
|
+
const out = { lines: 0, quads: 0, cubics: 0 };
|
|
3600
|
+
for (const contour of pathToContours(input)) {
|
|
3601
|
+
const segs = closeContour(contour);
|
|
3602
|
+
for (const s of segs) {
|
|
3603
|
+
if (s.length === 2) out.lines++;
|
|
3604
|
+
else if (s.length === 3) out.quads++;
|
|
3605
|
+
else out.cubics++;
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
return out;
|
|
3609
|
+
}
|
|
3610
|
+
function closeContour(contour, force = false) {
|
|
3611
|
+
const segs = contour.segments;
|
|
3612
|
+
if (segs.length === 0) return [];
|
|
3613
|
+
if (!contour.closed && !force) return segs;
|
|
3614
|
+
const first = segs[0][0];
|
|
3615
|
+
const lastSeg = segs[segs.length - 1];
|
|
3616
|
+
const last = lastSeg[lastSeg.length - 1];
|
|
3617
|
+
return samePoint(first, last) ? segs : [...segs, [last, first]];
|
|
3618
|
+
}
|
|
3619
|
+
function bezierPathBounds(input) {
|
|
3620
|
+
let minX = Infinity;
|
|
3621
|
+
let minY = Infinity;
|
|
3622
|
+
let maxX = -Infinity;
|
|
3623
|
+
let maxY = -Infinity;
|
|
3624
|
+
const path = toBezierPath(input);
|
|
3625
|
+
let pen = null;
|
|
3626
|
+
for (const contour of pathToContours(path)) {
|
|
3627
|
+
for (const s of contour.segments) {
|
|
3628
|
+
const b = segmentBounds(s);
|
|
3629
|
+
minX = Math.min(minX, b.minX);
|
|
3630
|
+
minY = Math.min(minY, b.minY);
|
|
3631
|
+
maxX = Math.max(maxX, b.maxX);
|
|
3632
|
+
maxY = Math.max(maxY, b.maxY);
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
for (const c of path) {
|
|
3636
|
+
if (c.type === "M") pen = [c.x, c.y];
|
|
3637
|
+
if (pen) {
|
|
3638
|
+
minX = Math.min(minX, pen[0]);
|
|
3639
|
+
minY = Math.min(minY, pen[1]);
|
|
3640
|
+
maxX = Math.max(maxX, pen[0]);
|
|
3641
|
+
maxY = Math.max(maxY, pen[1]);
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
return minX === Infinity ? null : { minX, minY, maxX, maxY };
|
|
3645
|
+
}
|
|
3646
|
+
function bezierPathSignedArea(input) {
|
|
3647
|
+
let sum = 0;
|
|
3648
|
+
for (const contour of pathToContours(input)) sum += contourSignedArea(closeContour(contour, true));
|
|
3649
|
+
return sum;
|
|
3650
|
+
}
|
|
3651
|
+
function contourSignedArea(segs) {
|
|
3652
|
+
let sum = 0;
|
|
3653
|
+
for (const s of segs) sum += segmentAreaTerm(s);
|
|
3654
|
+
return sum;
|
|
3655
|
+
}
|
|
3656
|
+
function bezierPathLength(input) {
|
|
3657
|
+
let total = 0;
|
|
3658
|
+
for (const contour of pathToContours(input)) for (const s of closeContour(contour)) total += segmentLength(s);
|
|
3659
|
+
return total;
|
|
3660
|
+
}
|
|
3661
|
+
function bezierPathPointAtLength(input, length) {
|
|
3662
|
+
const contours = pathToContours(input);
|
|
3663
|
+
let remaining = Math.max(0, length);
|
|
3664
|
+
let last = null;
|
|
3665
|
+
for (let ci = 0; ci < contours.length; ci++) {
|
|
3666
|
+
const segs = closeContour(contours[ci]);
|
|
3667
|
+
for (let si = 0; si < segs.length; si++) {
|
|
3668
|
+
const s = segs[si];
|
|
3669
|
+
const len = segmentLength(s);
|
|
3670
|
+
if (remaining <= len) {
|
|
3671
|
+
const t = segmentParamAtLength(s, remaining, len);
|
|
3672
|
+
const tangent2 = segmentTangent(s, t);
|
|
3673
|
+
return { point: segmentPoint(s, t), tangent: tangent2, angle: Math.atan2(tangent2[1], tangent2[0]), contour: ci, segment: si, t };
|
|
3674
|
+
}
|
|
3675
|
+
remaining -= len;
|
|
3676
|
+
const tangent = segmentTangent(s, 1);
|
|
3677
|
+
last = { point: s[s.length - 1], tangent, angle: Math.atan2(tangent[1], tangent[0]), contour: ci, segment: si, t: 1 };
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
return last;
|
|
3681
|
+
}
|
|
3682
|
+
function trimSegments(segs, from, to, lengths) {
|
|
3683
|
+
const lens = lengths ?? segs.map((s) => segmentLength(s));
|
|
3684
|
+
const out = [];
|
|
3685
|
+
let at = 0;
|
|
3686
|
+
for (let i = 0; i < segs.length; i++) {
|
|
3687
|
+
const len = lens[i];
|
|
3688
|
+
const s0 = at;
|
|
3689
|
+
const s1 = at + len;
|
|
3690
|
+
at = s1;
|
|
3691
|
+
if (s1 <= from || s0 >= to || len <= 0) continue;
|
|
3692
|
+
const t0 = from > s0 ? segmentParamAtLength(segs[i], from - s0, len) : 0;
|
|
3693
|
+
const t1 = to < s1 ? segmentParamAtLength(segs[i], to - s0, len) : 1;
|
|
3694
|
+
if (t1 > t0) out.push(subSegment(segs[i], t0, t1));
|
|
3695
|
+
}
|
|
3696
|
+
return out;
|
|
3697
|
+
}
|
|
3698
|
+
function solveMonotone(seg, axis, value) {
|
|
3699
|
+
const a = seg[0][axis];
|
|
3700
|
+
const b = seg[seg.length - 1][axis];
|
|
3701
|
+
if (seg.length === 2) return b === a ? 0 : (value - a) / (b - a);
|
|
3702
|
+
const increasing = b > a;
|
|
3703
|
+
let lo = 0;
|
|
3704
|
+
let hi = 1;
|
|
3705
|
+
let t = b === a ? 0.5 : Math.max(0, Math.min(1, (value - a) / (b - a)));
|
|
3706
|
+
for (let i = 0; i < 60; i++) {
|
|
3707
|
+
const v = segmentPoint(seg, t)[axis] - value;
|
|
3708
|
+
if (v === 0) return t;
|
|
3709
|
+
if (v > 0 === increasing) hi = t;
|
|
3710
|
+
else lo = t;
|
|
3711
|
+
const d = segmentDerivative(seg, t)[axis];
|
|
3712
|
+
let next = d !== 0 ? t - v / d : (lo + hi) / 2;
|
|
3713
|
+
if (!(next > lo && next < hi)) next = (lo + hi) / 2;
|
|
3714
|
+
if (Math.abs(next - t) < 1e-15) return next;
|
|
3715
|
+
t = next;
|
|
3716
|
+
}
|
|
3717
|
+
return t;
|
|
3718
|
+
}
|
|
3719
|
+
function yMonotonePieces(segs) {
|
|
3720
|
+
const out = [];
|
|
3721
|
+
for (const s of segs) {
|
|
3722
|
+
if (s.length === 2) {
|
|
3723
|
+
out.push(s);
|
|
3724
|
+
continue;
|
|
3725
|
+
}
|
|
3726
|
+
const ts = segmentExtrema(s, 1);
|
|
3727
|
+
let prev = 0;
|
|
3728
|
+
for (const t of [...ts, 1]) {
|
|
3729
|
+
out.push(subSegment(s, prev, t));
|
|
3730
|
+
prev = t;
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
return out;
|
|
3734
|
+
}
|
|
3735
|
+
function windingNumber(chains, point) {
|
|
3736
|
+
const [px2, py] = point;
|
|
3737
|
+
let winding = 0;
|
|
3738
|
+
for (const chain of chains) {
|
|
3739
|
+
for (const s of yMonotonePieces(chain)) {
|
|
3740
|
+
const ay = s[0][1];
|
|
3741
|
+
const by = s[s.length - 1][1];
|
|
3742
|
+
let dir = 0;
|
|
3743
|
+
if (ay <= py && by > py) dir = 1;
|
|
3744
|
+
else if (by <= py && ay > py) dir = -1;
|
|
3745
|
+
else continue;
|
|
3746
|
+
let minX = Infinity;
|
|
3747
|
+
let maxX = -Infinity;
|
|
3748
|
+
for (const p of s) {
|
|
3749
|
+
if (p[0] < minX) minX = p[0];
|
|
3750
|
+
if (p[0] > maxX) maxX = p[0];
|
|
3751
|
+
}
|
|
3752
|
+
if (minX > px2) {
|
|
3753
|
+
winding += dir;
|
|
3754
|
+
continue;
|
|
3755
|
+
}
|
|
3756
|
+
if (maxX <= px2) continue;
|
|
3757
|
+
const t = solveMonotone(s, 1, py);
|
|
3758
|
+
if (segmentPoint(s, t)[0] > px2) winding += dir;
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
return winding;
|
|
3762
|
+
}
|
|
3763
|
+
function pointInBezierPath(input, point, fillRule = "nonzero") {
|
|
3764
|
+
const chains = pathToContours(input).map((c) => closeContour(c, true));
|
|
3765
|
+
const w = windingNumber(chains, point);
|
|
3766
|
+
return fillRule === "evenodd" ? (w & 1) === 1 : w !== 0;
|
|
3767
|
+
}
|
|
3768
|
+
function distanceToBezierPath(input, point) {
|
|
3769
|
+
const contours = Array.isArray(input) && input.length > 0 && input[0].segments ? input : pathToContours(input);
|
|
3770
|
+
let best = Infinity;
|
|
3771
|
+
for (const contour of contours) {
|
|
3772
|
+
for (const s of closeContour(contour)) {
|
|
3773
|
+
let minX = Infinity;
|
|
3774
|
+
let minY = Infinity;
|
|
3775
|
+
let maxX = -Infinity;
|
|
3776
|
+
let maxY = -Infinity;
|
|
3777
|
+
for (const p of s) {
|
|
3778
|
+
minX = Math.min(minX, p[0]);
|
|
3779
|
+
minY = Math.min(minY, p[1]);
|
|
3780
|
+
maxX = Math.max(maxX, p[0]);
|
|
3781
|
+
maxY = Math.max(maxY, p[1]);
|
|
3782
|
+
}
|
|
3783
|
+
const dx = Math.max(minX - point[0], 0, point[0] - maxX);
|
|
3784
|
+
const dy = Math.max(minY - point[1], 0, point[1] - maxY);
|
|
3785
|
+
if (Math.hypot(dx, dy) >= best) continue;
|
|
3786
|
+
best = Math.min(best, segmentNearest(s, point).distance);
|
|
3787
|
+
}
|
|
3788
|
+
if (contour.segments.length === 0) continue;
|
|
3789
|
+
}
|
|
3790
|
+
return best;
|
|
3791
|
+
}
|
|
3792
|
+
function flattenChain(segs, tolerance) {
|
|
3793
|
+
const out = [];
|
|
3794
|
+
for (const s of segs) {
|
|
3795
|
+
if (out.length === 0) out.push(s[0]);
|
|
3796
|
+
if (s.length === 2) {
|
|
3797
|
+
out.push(s[1]);
|
|
3798
|
+
continue;
|
|
3799
|
+
}
|
|
3800
|
+
let dd = 0;
|
|
3801
|
+
for (let i = 0; i + 2 < s.length; i++) {
|
|
3802
|
+
dd = Math.max(dd, Math.hypot(s[i][0] - 2 * s[i + 1][0] + s[i + 2][0], s[i][1] - 2 * s[i + 1][1] + s[i + 2][1]));
|
|
3803
|
+
}
|
|
3804
|
+
const deg = s.length - 1;
|
|
3805
|
+
const n = Math.max(1, Math.min(256, Math.ceil(Math.sqrt(deg * (deg - 1) * dd / (8 * tolerance)))));
|
|
3806
|
+
for (let k = 1; k <= n; k++) out.push(segmentPoint(s, k / n));
|
|
3807
|
+
}
|
|
3808
|
+
if (out.length > 1 && samePoint(out[0], out[out.length - 1])) out.pop();
|
|
3809
|
+
return out;
|
|
3810
|
+
}
|
|
3811
|
+
|
|
3812
|
+
// src/canvas/pathops/boolean.ts
|
|
3813
|
+
var PathOpsError = class extends Error {
|
|
3814
|
+
constructor(message) {
|
|
3815
|
+
super(message);
|
|
3816
|
+
this.name = "PathOpsError";
|
|
3817
|
+
}
|
|
3818
|
+
};
|
|
3819
|
+
function isOperandObject(input) {
|
|
3820
|
+
return !!input && typeof input === "object" && !Array.isArray(input) && "path" in input;
|
|
3821
|
+
}
|
|
3822
|
+
function normalizeOperand(input) {
|
|
3823
|
+
const path = isOperandObject(input) ? input.path : input;
|
|
3824
|
+
const fillRule = isOperandObject(input) ? input.fillRule ?? "nonzero" : "nonzero";
|
|
3825
|
+
const chains = [];
|
|
3826
|
+
for (const contour of pathToContours(path ?? "")) {
|
|
3827
|
+
const chain = closeContour(contour, true);
|
|
3828
|
+
if (chain.length === 0) continue;
|
|
3829
|
+
for (const seg of chain) {
|
|
3830
|
+
for (const p of seg) {
|
|
3831
|
+
if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) throw new PathOpsError(NON_FINITE);
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
chains.push(chain);
|
|
3835
|
+
}
|
|
3836
|
+
return { chains, fillRule };
|
|
3837
|
+
}
|
|
3838
|
+
function scaleOf(operands) {
|
|
3839
|
+
let max = 0;
|
|
3840
|
+
for (const op of operands) {
|
|
3841
|
+
for (const chain of op.chains) {
|
|
3842
|
+
for (const seg of chain) {
|
|
3843
|
+
for (const [x, y] of seg) max = Math.max(max, Math.abs(x), Math.abs(y));
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
return Math.max(1, max);
|
|
3848
|
+
}
|
|
3849
|
+
var VertexPool = class {
|
|
3850
|
+
constructor(eps) {
|
|
3851
|
+
this.eps = eps;
|
|
3852
|
+
}
|
|
3853
|
+
eps;
|
|
3854
|
+
vertices = [];
|
|
3855
|
+
cells = /* @__PURE__ */ new Map();
|
|
3856
|
+
get(x, y) {
|
|
3857
|
+
const cx = Math.floor(x / this.eps);
|
|
3858
|
+
const cy = Math.floor(y / this.eps);
|
|
3859
|
+
let best = null;
|
|
3860
|
+
let bestDist = Infinity;
|
|
3861
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
3862
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
3863
|
+
const ids = this.cells.get(`${cx + dx},${cy + dy}`);
|
|
3864
|
+
if (!ids) continue;
|
|
3865
|
+
for (const id of ids) {
|
|
3866
|
+
const v = this.vertices[id];
|
|
3867
|
+
const d = Math.hypot(v.x - x, v.y - y);
|
|
3868
|
+
if (d <= this.eps && d < bestDist) {
|
|
3869
|
+
best = v;
|
|
3870
|
+
bestDist = d;
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
if (best) return best;
|
|
3876
|
+
const vertex = { id: this.vertices.length, x, y, p: [x, y] };
|
|
3877
|
+
this.vertices.push(vertex);
|
|
3878
|
+
const key = `${cx},${cy}`;
|
|
3879
|
+
const bucket = this.cells.get(key);
|
|
3880
|
+
if (bucket) bucket.push(vertex.id);
|
|
3881
|
+
else this.cells.set(key, [vertex.id]);
|
|
3882
|
+
return vertex;
|
|
3883
|
+
}
|
|
3884
|
+
};
|
|
3885
|
+
function hull(pts) {
|
|
3886
|
+
let minX = Infinity;
|
|
3887
|
+
let maxX = -Infinity;
|
|
3888
|
+
let minY = Infinity;
|
|
3889
|
+
let maxY = -Infinity;
|
|
3890
|
+
for (const [x, y] of pts) {
|
|
3891
|
+
if (x < minX) minX = x;
|
|
3892
|
+
if (x > maxX) maxX = x;
|
|
3893
|
+
if (y < minY) minY = y;
|
|
3894
|
+
if (y > maxY) maxY = y;
|
|
3895
|
+
}
|
|
3896
|
+
return { minX, maxX, minY, maxY };
|
|
3897
|
+
}
|
|
3898
|
+
function snapEnds(pts, a, b) {
|
|
3899
|
+
const n = pts.length;
|
|
3900
|
+
if (n === 2) return [a, b];
|
|
3901
|
+
const d0 = sub2(a, pts[0]);
|
|
3902
|
+
const d1 = sub2(b, pts[n - 1]);
|
|
3903
|
+
if (n === 3) {
|
|
3904
|
+
return [a, [pts[1][0] + (d0[0] + d1[0]) / 2, pts[1][1] + (d0[1] + d1[1]) / 2], b];
|
|
3905
|
+
}
|
|
3906
|
+
return [a, [pts[1][0] + d0[0], pts[1][1] + d0[1]], [pts[2][0] + d1[0], pts[2][1] + d1[1]], b];
|
|
3907
|
+
}
|
|
3908
|
+
function makePiece(pts, a, b, operand, src, s0, s1) {
|
|
3909
|
+
const snapped = snapEnds(pts, a.p, b.p);
|
|
3910
|
+
return { pts: snapped, a, b, operand, src, s0, s1, dirty: true, ...hull(snapped) };
|
|
3911
|
+
}
|
|
3912
|
+
var NON_FINITE = "non-finite coordinates";
|
|
3913
|
+
function flatness(pts) {
|
|
3914
|
+
if (pts.length === 2) return 0;
|
|
3915
|
+
const a = pts[0];
|
|
3916
|
+
const b = pts[pts.length - 1];
|
|
3917
|
+
const chord = sub2(b, a);
|
|
3918
|
+
const len = Math.hypot(chord[0], chord[1]);
|
|
3919
|
+
let worst = 0;
|
|
3920
|
+
for (let i = 1; i < pts.length - 1; i++) {
|
|
3921
|
+
const v = sub2(pts[i], a);
|
|
3922
|
+
const d = len > 0 ? Math.abs(cross(chord, v)) / len : Math.hypot(v[0], v[1]);
|
|
3923
|
+
const along = len > 0 ? dot2(chord, v) / len : 0;
|
|
3924
|
+
const over = Math.max(0, -along, along - len);
|
|
3925
|
+
worst = Math.max(worst, d, over);
|
|
3926
|
+
}
|
|
3927
|
+
return worst;
|
|
3928
|
+
}
|
|
3929
|
+
function split2(pts) {
|
|
3930
|
+
const n = pts.length;
|
|
3931
|
+
const mid = (p, q) => [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
|
|
3932
|
+
if (n === 2) {
|
|
3933
|
+
const m2 = mid(pts[0], pts[1]);
|
|
3934
|
+
return [
|
|
3935
|
+
[pts[0], m2],
|
|
3936
|
+
[m2, pts[1]]
|
|
3937
|
+
];
|
|
3938
|
+
}
|
|
3939
|
+
if (n === 3) {
|
|
3940
|
+
const a2 = mid(pts[0], pts[1]);
|
|
3941
|
+
const b2 = mid(pts[1], pts[2]);
|
|
3942
|
+
const m2 = mid(a2, b2);
|
|
3943
|
+
return [
|
|
3944
|
+
[pts[0], a2, m2],
|
|
3945
|
+
[m2, b2, pts[2]]
|
|
3946
|
+
];
|
|
3947
|
+
}
|
|
3948
|
+
const a = mid(pts[0], pts[1]);
|
|
3949
|
+
const b = mid(pts[1], pts[2]);
|
|
3950
|
+
const c = mid(pts[2], pts[3]);
|
|
3951
|
+
const ab = mid(a, b);
|
|
3952
|
+
const bc = mid(b, c);
|
|
3953
|
+
const m = mid(ab, bc);
|
|
3954
|
+
return [
|
|
3955
|
+
[pts[0], a, ab, m],
|
|
3956
|
+
[m, bc, c, pts[3]]
|
|
3957
|
+
];
|
|
3958
|
+
}
|
|
3959
|
+
function closestSegmentPoints(a0, a1, b0, b1) {
|
|
3960
|
+
const d1 = sub2(a1, a0);
|
|
3961
|
+
const d2 = sub2(b1, b0);
|
|
3962
|
+
const r = sub2(a0, b0);
|
|
3963
|
+
const a = dot2(d1, d1);
|
|
3964
|
+
const e = dot2(d2, d2);
|
|
3965
|
+
const f = dot2(d2, r);
|
|
3966
|
+
let s = 0;
|
|
3967
|
+
let u = 0;
|
|
3968
|
+
if (a <= 1e-300 && e <= 1e-300) return { s: 0, u: 0, d: dist2(a0, b0) };
|
|
3969
|
+
if (a <= 1e-300) {
|
|
3970
|
+
u = Math.max(0, Math.min(1, f / e));
|
|
3971
|
+
} else {
|
|
3972
|
+
const c = dot2(d1, r);
|
|
3973
|
+
if (e <= 1e-300) {
|
|
3974
|
+
s = Math.max(0, Math.min(1, -c / a));
|
|
3975
|
+
} else {
|
|
3976
|
+
const b = dot2(d1, d2);
|
|
3977
|
+
const denom = a * e - b * b;
|
|
3978
|
+
s = denom > 0 ? Math.max(0, Math.min(1, (b * f - c * e) / denom)) : 0;
|
|
3979
|
+
u = (b * s + f) / e;
|
|
3980
|
+
if (u < 0) {
|
|
3981
|
+
u = 0;
|
|
3982
|
+
s = Math.max(0, Math.min(1, -c / a));
|
|
3983
|
+
} else if (u > 1) {
|
|
3984
|
+
u = 1;
|
|
3985
|
+
s = Math.max(0, Math.min(1, (b - c) / a));
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
const pa = [a0[0] + d1[0] * s, a0[1] + d1[1] * s];
|
|
3990
|
+
const pb = [b0[0] + d2[0] * u, b0[1] + d2[1] * u];
|
|
3991
|
+
return { s, u, d: dist2(pa, pb) };
|
|
3992
|
+
}
|
|
3993
|
+
function polish(P, Q, s, u) {
|
|
3994
|
+
let best = { s, u, d: dist2(segmentPoint(P, s), segmentPoint(Q, u)) };
|
|
3995
|
+
for (let i = 0; i < 10 && best.d > 0; i++) {
|
|
3996
|
+
const f = sub2(segmentPoint(P, s), segmentPoint(Q, u));
|
|
3997
|
+
const dp = segmentDerivative(P, s);
|
|
3998
|
+
const dq = segmentDerivative(Q, u);
|
|
3999
|
+
const det = cross(dp, [-dq[0], -dq[1]]);
|
|
4000
|
+
if (!(Math.abs(det) > 1e-300)) break;
|
|
4001
|
+
const ds = cross([-f[0], -f[1]], [-dq[0], -dq[1]]) / det;
|
|
4002
|
+
const du = cross(dp, [-f[0], -f[1]]) / det;
|
|
4003
|
+
s = Math.max(0, Math.min(1, s + ds));
|
|
4004
|
+
u = Math.max(0, Math.min(1, u + du));
|
|
4005
|
+
const d = dist2(segmentPoint(P, s), segmentPoint(Q, u));
|
|
4006
|
+
if (d < best.d) best = { s, u, d };
|
|
4007
|
+
else break;
|
|
4008
|
+
}
|
|
4009
|
+
return best;
|
|
4010
|
+
}
|
|
4011
|
+
function intersectSubdivided(P, p0, p1, Q, q0, q1, fullP, fullQ, ctx, out, depth) {
|
|
4012
|
+
const hp = hull(P);
|
|
4013
|
+
const hq = hull(Q);
|
|
4014
|
+
const e = ctx.eps;
|
|
4015
|
+
if (hp.minX > hq.maxX + e || hq.minX > hp.maxX + e || hp.minY > hq.maxY + e || hq.minY > hp.maxY + e) return;
|
|
4016
|
+
const fp = flatness(P);
|
|
4017
|
+
const fq = flatness(Q);
|
|
4018
|
+
const flatTol = e * 0.5;
|
|
4019
|
+
if (fp <= flatTol && fq <= flatTol || depth >= 52) {
|
|
4020
|
+
if (++ctx.leaves > ctx.budget) throw new PathOpsError("curve intersection did not converge");
|
|
4021
|
+
const a0 = P[0];
|
|
4022
|
+
const a1 = P[P.length - 1];
|
|
4023
|
+
const b0 = Q[0];
|
|
4024
|
+
const b1 = Q[Q.length - 1];
|
|
4025
|
+
const d1 = sub2(a1, a0);
|
|
4026
|
+
const d2 = sub2(b1, b0);
|
|
4027
|
+
const denom = cross(d1, d2);
|
|
4028
|
+
let s = 0;
|
|
4029
|
+
let u = 0;
|
|
4030
|
+
let hit = false;
|
|
4031
|
+
const l1 = Math.hypot(d1[0], d1[1]);
|
|
4032
|
+
const l2 = Math.hypot(d2[0], d2[1]);
|
|
4033
|
+
if (Math.abs(denom) > 1e-12 * l1 * l2) {
|
|
4034
|
+
const r = sub2(b0, a0);
|
|
4035
|
+
s = cross(r, d2) / denom;
|
|
4036
|
+
u = cross(r, d1) / denom;
|
|
4037
|
+
const ts = l1 > 0 ? e / l1 : 1;
|
|
4038
|
+
const tu = l2 > 0 ? e / l2 : 1;
|
|
4039
|
+
hit = s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu;
|
|
4040
|
+
}
|
|
4041
|
+
if (!hit) {
|
|
4042
|
+
const c = closestSegmentPoints(a0, a1, b0, b1);
|
|
4043
|
+
if (c.d > e) return;
|
|
4044
|
+
s = c.s;
|
|
4045
|
+
u = c.u;
|
|
4046
|
+
}
|
|
4047
|
+
s = Math.max(0, Math.min(1, s));
|
|
4048
|
+
u = Math.max(0, Math.min(1, u));
|
|
4049
|
+
const best = polish(fullP, fullQ, p0 + (p1 - p0) * s, q0 + (q1 - q0) * u);
|
|
4050
|
+
if (best.d > e * 2) return;
|
|
4051
|
+
const a = segmentPoint(fullP, best.s);
|
|
4052
|
+
const b = segmentPoint(fullQ, best.u);
|
|
4053
|
+
out.push({ tp: best.s, tq: best.u, point: [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2] });
|
|
4054
|
+
return;
|
|
4055
|
+
}
|
|
4056
|
+
const splitP = fp > flatTol;
|
|
4057
|
+
const splitQ = fq > flatTol;
|
|
4058
|
+
const pm = (p0 + p1) / 2;
|
|
4059
|
+
const qm = (q0 + q1) / 2;
|
|
4060
|
+
if (splitP && splitQ) {
|
|
4061
|
+
const [pa, pb] = split2(P);
|
|
4062
|
+
const [qa, qb] = split2(Q);
|
|
4063
|
+
intersectSubdivided(pa, p0, pm, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
|
|
4064
|
+
intersectSubdivided(pa, p0, pm, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4065
|
+
intersectSubdivided(pb, pm, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
|
|
4066
|
+
intersectSubdivided(pb, pm, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4067
|
+
} else if (splitP) {
|
|
4068
|
+
const [pa, pb] = split2(P);
|
|
4069
|
+
intersectSubdivided(pa, p0, pm, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4070
|
+
intersectSubdivided(pb, pm, p1, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4071
|
+
} else {
|
|
4072
|
+
const [qa, qb] = split2(Q);
|
|
4073
|
+
intersectSubdivided(P, p0, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
|
|
4074
|
+
intersectSubdivided(P, p0, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
function nearestOn(piece, p, eps) {
|
|
4078
|
+
if (p[0] < piece.minX - eps || p[0] > piece.maxX + eps || p[1] < piece.minY - eps || p[1] > piece.maxY + eps) return null;
|
|
4079
|
+
return segmentNearest(piece.pts, p);
|
|
4080
|
+
}
|
|
4081
|
+
function staysClose(P, Q, e1, e2, tol) {
|
|
4082
|
+
for (const f of [0.25, 0.5, 0.75]) {
|
|
4083
|
+
const tp = e1.tp + (e2.tp - e1.tp) * f;
|
|
4084
|
+
const q = segmentNearest(Q.pts, segmentPoint(P.pts, tp));
|
|
4085
|
+
if (q.distance > tol) return false;
|
|
4086
|
+
}
|
|
4087
|
+
return true;
|
|
4088
|
+
}
|
|
4089
|
+
function pairEvents(P, Q, ctx) {
|
|
4090
|
+
const eps = ctx.eps;
|
|
4091
|
+
const anchors = [];
|
|
4092
|
+
for (const [va, tp] of [
|
|
4093
|
+
[P.a, 0],
|
|
4094
|
+
[P.b, 1]
|
|
4095
|
+
]) {
|
|
4096
|
+
if (va === Q.a) anchors.push({ tp, tq: 0, point: va.p, anchor: true, shared: true });
|
|
4097
|
+
else if (va === Q.b) anchors.push({ tp, tq: 1, point: va.p, anchor: true, shared: true });
|
|
4098
|
+
}
|
|
4099
|
+
for (const [v, tq] of [
|
|
4100
|
+
[Q.a, 0],
|
|
4101
|
+
[Q.b, 1]
|
|
4102
|
+
]) {
|
|
4103
|
+
if (v === P.a || v === P.b) continue;
|
|
4104
|
+
const near = nearestOn(P, v.p, eps);
|
|
4105
|
+
if (near && near.distance <= eps) anchors.push({ tp: near.t, tq, point: v.p, anchor: true });
|
|
4106
|
+
}
|
|
4107
|
+
for (const [v, tp] of [
|
|
4108
|
+
[P.a, 0],
|
|
4109
|
+
[P.b, 1]
|
|
4110
|
+
]) {
|
|
4111
|
+
if (v === Q.a || v === Q.b) continue;
|
|
4112
|
+
const near = nearestOn(Q, v.p, eps);
|
|
4113
|
+
if (near && near.distance <= eps) anchors.push({ tp, tq: near.t, point: v.p, anchor: true });
|
|
4114
|
+
}
|
|
4115
|
+
if (anchors.length >= 2) {
|
|
4116
|
+
const sorted = [...anchors].sort((x, y) => x.tp - y.tp);
|
|
4117
|
+
const first = sorted[0];
|
|
4118
|
+
const last = sorted[sorted.length - 1];
|
|
4119
|
+
if (dist2(first.point, last.point) > eps && staysClose(P, Q, first, last, eps * 4)) {
|
|
4120
|
+
return anchors.filter((a) => !a.shared);
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
const found = [];
|
|
4124
|
+
if (P.pts.length === 2 && Q.pts.length === 2) {
|
|
4125
|
+
const a0 = P.pts[0];
|
|
4126
|
+
const d1 = sub2(P.pts[1], a0);
|
|
4127
|
+
const b0 = Q.pts[0];
|
|
4128
|
+
const d2 = sub2(Q.pts[1], b0);
|
|
4129
|
+
const denom = cross(d1, d2);
|
|
4130
|
+
const l1 = Math.hypot(d1[0], d1[1]);
|
|
4131
|
+
const l2 = Math.hypot(d2[0], d2[1]);
|
|
4132
|
+
if (Math.abs(denom) > 1e-12 * l1 * l2) {
|
|
4133
|
+
const r = sub2(b0, a0);
|
|
4134
|
+
const s = cross(r, d2) / denom;
|
|
4135
|
+
const u = cross(r, d1) / denom;
|
|
4136
|
+
const ts = eps / l1;
|
|
4137
|
+
const tu = eps / l2;
|
|
4138
|
+
if (s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu) {
|
|
4139
|
+
const sc = Math.max(0, Math.min(1, s));
|
|
4140
|
+
const uc = Math.max(0, Math.min(1, u));
|
|
4141
|
+
found.push({ tp: sc, tq: uc, point: [a0[0] + d1[0] * sc, a0[1] + d1[1] * sc] });
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
} else {
|
|
4145
|
+
ctx.leaves = 0;
|
|
4146
|
+
intersectSubdivided(P.pts, 0, 1, Q.pts, 0, 1, P.pts, Q.pts, ctx, found, 0);
|
|
4147
|
+
}
|
|
4148
|
+
if (found.length === 0) return anchors.filter((a) => !a.shared);
|
|
4149
|
+
const all = [...anchors, ...found].sort((x, y) => x.tp - y.tp || x.tq - y.tq);
|
|
4150
|
+
const clusters = [];
|
|
4151
|
+
for (const ev of all) {
|
|
4152
|
+
const cluster = clusters[clusters.length - 1];
|
|
4153
|
+
const prev = cluster?.[cluster.length - 1];
|
|
4154
|
+
if (prev && (dist2(prev.point, ev.point) <= eps * 2 || staysClose(P, Q, prev, ev, eps * 2))) cluster.push(ev);
|
|
4155
|
+
else clusters.push([ev]);
|
|
4156
|
+
}
|
|
4157
|
+
const out = [];
|
|
4158
|
+
for (const cluster of clusters) {
|
|
4159
|
+
const shared = cluster.find((e) => e.shared);
|
|
4160
|
+
if (shared) continue;
|
|
4161
|
+
const anchor = cluster.find((e) => e.anchor);
|
|
4162
|
+
out.push(anchor ?? cluster[Math.floor(cluster.length / 2)]);
|
|
4163
|
+
}
|
|
4164
|
+
return out;
|
|
4165
|
+
}
|
|
4166
|
+
function addSplit2(map, piece, t, v) {
|
|
4167
|
+
if (v === piece.a || v === piece.b) return;
|
|
4168
|
+
const list = map.get(piece);
|
|
4169
|
+
if (list) list.push({ t, v });
|
|
4170
|
+
else map.set(piece, [{ t, v }]);
|
|
4171
|
+
}
|
|
4172
|
+
function eventVertex(P, Q, ev, pool, eps) {
|
|
4173
|
+
for (const v of [P.a, P.b, Q.a, Q.b]) if (dist2(v.p, ev.point) <= eps) return v;
|
|
4174
|
+
return pool.get(ev.point[0], ev.point[1]);
|
|
4175
|
+
}
|
|
4176
|
+
function splitPass2(pieces, pool, ctx) {
|
|
4177
|
+
const eps = ctx.eps;
|
|
4178
|
+
const order = pieces.map((_, i) => i).sort((i, j) => pieces[i].minX - pieces[j].minX);
|
|
4179
|
+
const splits = /* @__PURE__ */ new Map();
|
|
4180
|
+
for (let oi = 0; oi < order.length; oi++) {
|
|
4181
|
+
const P = pieces[order[oi]];
|
|
4182
|
+
for (let oj = oi + 1; oj < order.length; oj++) {
|
|
4183
|
+
const Q = pieces[order[oj]];
|
|
4184
|
+
if (Q.minX > P.maxX + eps) break;
|
|
4185
|
+
if (!P.dirty && !Q.dirty) continue;
|
|
4186
|
+
if (Q.minY > P.maxY + eps || Q.maxY < P.minY - eps) continue;
|
|
4187
|
+
if (P.a === Q.a && P.b === Q.b && P.pts.length === 2 && Q.pts.length === 2) continue;
|
|
4188
|
+
if (P.a === Q.b && P.b === Q.a && P.pts.length === 2 && Q.pts.length === 2) continue;
|
|
4189
|
+
for (const ev of pairEvents(P, Q, ctx)) {
|
|
4190
|
+
const v = eventVertex(P, Q, ev, pool, eps);
|
|
4191
|
+
addSplit2(splits, P, ev.tp, v);
|
|
4192
|
+
addSplit2(splits, Q, ev.tq, v);
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
for (const p of pieces) p.dirty = false;
|
|
4197
|
+
if (splits.size === 0) return { pieces, changed: false };
|
|
4198
|
+
const out = [];
|
|
4199
|
+
for (const piece of pieces) {
|
|
4200
|
+
const list = splits.get(piece);
|
|
4201
|
+
if (!list) {
|
|
4202
|
+
out.push(piece);
|
|
4203
|
+
continue;
|
|
4204
|
+
}
|
|
4205
|
+
list.sort((x, y) => x.t - y.t);
|
|
4206
|
+
let prevT = 0;
|
|
4207
|
+
let prevV = piece.a;
|
|
4208
|
+
const range = piece.s1 - piece.s0;
|
|
4209
|
+
const emit = (t, v) => {
|
|
4210
|
+
if (v === prevV || t <= prevT) return;
|
|
4211
|
+
const pts = subSegment(piece.pts, prevT, t);
|
|
4212
|
+
out.push(makePiece(pts, prevV, v, piece.operand, piece.src, piece.s0 + range * prevT, piece.s0 + range * t));
|
|
4213
|
+
prevT = t;
|
|
4214
|
+
prevV = v;
|
|
4215
|
+
};
|
|
4216
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4217
|
+
for (const { t, v } of list) {
|
|
4218
|
+
if (seen.has(v) || t <= 0 || t >= 1) continue;
|
|
4219
|
+
seen.add(v);
|
|
4220
|
+
emit(t, v);
|
|
4221
|
+
}
|
|
4222
|
+
if (prevV !== piece.b) {
|
|
4223
|
+
const pts = subSegment(piece.pts, prevT, 1);
|
|
4224
|
+
out.push(makePiece(pts, prevV, piece.b, piece.operand, piece.src, piece.s0 + range * prevT, piece.s1));
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
return { pieces: out, changed: true };
|
|
4228
|
+
}
|
|
4229
|
+
function buildArrangement(inputs, options) {
|
|
4230
|
+
const operands = inputs.map(normalizeOperand);
|
|
4231
|
+
const scale2 = scaleOf(operands);
|
|
4232
|
+
const eps = options.epsilon && options.epsilon > 0 ? options.epsilon : 1e-9 * scale2;
|
|
4233
|
+
const pool = new VertexPool(eps);
|
|
4234
|
+
const sources = [];
|
|
4235
|
+
let pieces = [];
|
|
4236
|
+
operands.forEach((op, operand) => {
|
|
4237
|
+
for (const chain of op.chains) {
|
|
4238
|
+
for (const raw of chain) {
|
|
4239
|
+
const straight = raw.length > 2 && isStraightSegment(raw, eps * 0.01);
|
|
4240
|
+
const seg = straight ? [raw[0], raw[raw.length - 1]] : raw;
|
|
4241
|
+
const src = sources.length;
|
|
4242
|
+
sources.push({ seg, line: seg.length === 2 });
|
|
4243
|
+
const ts = [0, ...monotoneParams(seg), 1];
|
|
4244
|
+
for (let i = 0; i + 1 < ts.length; i++) {
|
|
4245
|
+
const pts = subSegment(seg, ts[i], ts[i + 1]);
|
|
4246
|
+
const a = pool.get(pts[0][0], pts[0][1]);
|
|
4247
|
+
const b = pool.get(pts[pts.length - 1][0], pts[pts.length - 1][1]);
|
|
4248
|
+
if (a === b) continue;
|
|
4249
|
+
pieces.push(makePiece(pts, a, b, operand, src, ts[i], ts[i + 1]));
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
});
|
|
4254
|
+
const ctx = { eps, leaves: 0, budget: options.subdivisionBudget ?? 2e4 };
|
|
4255
|
+
let settled = false;
|
|
4256
|
+
for (let pass = 0; pass < 6; pass++) {
|
|
4257
|
+
const result = splitPass2(pieces, pool, ctx);
|
|
4258
|
+
pieces = result.pieces;
|
|
4259
|
+
if (!result.changed) {
|
|
4260
|
+
settled = true;
|
|
4261
|
+
break;
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
if (!settled) throw new PathOpsError("intersections did not settle");
|
|
4265
|
+
const count = operands.length;
|
|
4266
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4267
|
+
const edges = [];
|
|
4268
|
+
for (const piece of pieces) {
|
|
4269
|
+
const forward = piece.a.id < piece.b.id;
|
|
4270
|
+
const a = forward ? piece.a : piece.b;
|
|
4271
|
+
const b = forward ? piece.b : piece.a;
|
|
4272
|
+
const pts = forward ? piece.pts : [...piece.pts].reverse();
|
|
4273
|
+
const key = `${a.id},${b.id}`;
|
|
4274
|
+
const group = groups.get(key) ?? [];
|
|
4275
|
+
if (!groups.has(key)) groups.set(key, group);
|
|
4276
|
+
const mid = segmentPoint(pts, 0.5);
|
|
4277
|
+
let edge = group.find((e) => {
|
|
4278
|
+
if (e.pts.length === 2 && pts.length === 2) return true;
|
|
4279
|
+
return segmentNearest(e.pts, mid).distance <= eps * 4 && segmentNearest(pts, segmentPoint(e.pts, 0.5)).distance <= eps * 4;
|
|
4280
|
+
});
|
|
4281
|
+
if (!edge) {
|
|
4282
|
+
edge = {
|
|
4283
|
+
a,
|
|
4284
|
+
b,
|
|
4285
|
+
pts,
|
|
4286
|
+
mult: new Array(count).fill(0),
|
|
4287
|
+
src: piece.src,
|
|
4288
|
+
sa: forward ? piece.s0 : piece.s1,
|
|
4289
|
+
sb: forward ? piece.s1 : piece.s0,
|
|
4290
|
+
...hull(pts)
|
|
4291
|
+
};
|
|
4292
|
+
group.push(edge);
|
|
4293
|
+
edges.push(edge);
|
|
4294
|
+
}
|
|
4295
|
+
edge.mult[piece.operand] += forward ? 1 : -1;
|
|
4296
|
+
}
|
|
4297
|
+
const live = edges.filter((e) => e.mult.some((m) => m !== 0));
|
|
4298
|
+
const byY = new BandIndex(live, (e) => e.minY, (e) => e.maxY);
|
|
4299
|
+
const byX = new BandIndex(live, (e) => e.minX, (e) => e.maxX);
|
|
4300
|
+
const right = [];
|
|
4301
|
+
const left = [];
|
|
4302
|
+
for (let i = 0; i < live.length; i++) {
|
|
4303
|
+
const edge = live[i];
|
|
4304
|
+
const mid = segmentPoint(edge.pts, 0.5);
|
|
4305
|
+
const tan = segmentTangent(edge.pts, 0.5);
|
|
4306
|
+
const horizontal = Math.abs(tan[1]) < Math.abs(tan[0]);
|
|
4307
|
+
const winding = new Array(count).fill(0);
|
|
4308
|
+
const [mx, my] = mid;
|
|
4309
|
+
for (const j of horizontal ? byX.at(mx) : byY.at(my)) {
|
|
4310
|
+
if (j === i) continue;
|
|
4311
|
+
const o = live[j];
|
|
4312
|
+
let dir = 0;
|
|
4313
|
+
if (!horizontal) {
|
|
4314
|
+
if (o.maxX <= mx) continue;
|
|
4315
|
+
const ay = o.a.y;
|
|
4316
|
+
const by = o.b.y;
|
|
4317
|
+
if (ay <= my && by > my) dir = 1;
|
|
4318
|
+
else if (by <= my && ay > my) dir = -1;
|
|
4319
|
+
else continue;
|
|
4320
|
+
if (o.minX <= mx) {
|
|
4321
|
+
const t = solveMonotone(o.pts, 1, my);
|
|
4322
|
+
if (!(segmentPoint(o.pts, t)[0] > mx)) continue;
|
|
4323
|
+
}
|
|
4324
|
+
} else {
|
|
4325
|
+
if (o.maxY <= my) continue;
|
|
4326
|
+
const ax = o.a.x;
|
|
4327
|
+
const bx = o.b.x;
|
|
4328
|
+
if (bx < mx && mx <= ax) dir = 1;
|
|
4329
|
+
else if (ax < mx && mx <= bx) dir = -1;
|
|
4330
|
+
else continue;
|
|
4331
|
+
if (o.minY <= my) {
|
|
4332
|
+
const t = solveMonotone(o.pts, 0, mx);
|
|
4333
|
+
if (!(segmentPoint(o.pts, t)[1] > my)) continue;
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
for (let k = 0; k < count; k++) if (o.mult[k] !== 0) winding[k] += dir * o.mult[k];
|
|
4337
|
+
}
|
|
4338
|
+
const sideIsRight = horizontal ? edge.b.x < edge.a.x : edge.b.y > edge.a.y;
|
|
4339
|
+
const r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);
|
|
4340
|
+
right.push(r);
|
|
4341
|
+
left.push(r.map((w, k) => w + edge.mult[k]));
|
|
4342
|
+
}
|
|
4343
|
+
return { edges: live, left, right, fillRules: operands.map((o) => o.fillRule), sources, eps, scale: scale2 };
|
|
4344
|
+
}
|
|
4345
|
+
var BandIndex = class {
|
|
4346
|
+
bands;
|
|
4347
|
+
min;
|
|
4348
|
+
size;
|
|
4349
|
+
constructor(edges, lo, hi) {
|
|
4350
|
+
let min = Infinity;
|
|
4351
|
+
let max = -Infinity;
|
|
4352
|
+
for (const e of edges) {
|
|
4353
|
+
min = Math.min(min, lo(e));
|
|
4354
|
+
max = Math.max(max, hi(e));
|
|
4355
|
+
}
|
|
4356
|
+
const count = Math.max(1, Math.min(4096, Math.ceil(Math.sqrt(edges.length) * 2)));
|
|
4357
|
+
this.min = min;
|
|
4358
|
+
this.size = max > min ? (max - min) / count : 1;
|
|
4359
|
+
this.bands = Array.from({ length: count }, () => []);
|
|
4360
|
+
edges.forEach((e, i) => {
|
|
4361
|
+
const from = this.index(lo(e));
|
|
4362
|
+
const to = this.index(hi(e));
|
|
4363
|
+
for (let b = from; b <= to; b++) this.bands[b].push(i);
|
|
4364
|
+
});
|
|
4365
|
+
}
|
|
4366
|
+
index(v) {
|
|
4367
|
+
const k = Math.floor((v - this.min) / this.size);
|
|
4368
|
+
return Math.max(0, Math.min(this.bands.length - 1, Number.isFinite(k) ? k : 0));
|
|
4369
|
+
}
|
|
4370
|
+
at(v) {
|
|
4371
|
+
return this.bands[this.index(v)] ?? [];
|
|
4372
|
+
}
|
|
4373
|
+
};
|
|
4374
|
+
function insideFlags2(windings, fillRules) {
|
|
4375
|
+
return windings.map((w, k) => fillRules[k] === "evenodd" ? (w & 1) === 1 : w !== 0);
|
|
4376
|
+
}
|
|
4377
|
+
var TWO_PI = Math.PI * 2;
|
|
4378
|
+
var TIE = 1e-3;
|
|
4379
|
+
function directed(edge, forward) {
|
|
4380
|
+
const pts = forward ? edge.pts : [...edge.pts].reverse();
|
|
4381
|
+
const out = segmentTangent(pts, 0);
|
|
4382
|
+
const end = segmentTangent(pts, 1);
|
|
4383
|
+
return {
|
|
4384
|
+
from: forward ? edge.a : edge.b,
|
|
4385
|
+
to: forward ? edge.b : edge.a,
|
|
4386
|
+
pts,
|
|
4387
|
+
src: edge.src,
|
|
4388
|
+
sFrom: forward ? edge.sa : edge.sb,
|
|
4389
|
+
sTo: forward ? edge.sb : edge.sa,
|
|
4390
|
+
outAngle: Math.atan2(out[1], out[0]),
|
|
4391
|
+
backAngle: Math.atan2(-end[1], -end[0]),
|
|
4392
|
+
length: dist2(pts[0], pts[pts.length - 1]) + (pts.length > 2 ? flatness(pts) : 0)
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
4395
|
+
function chordAngle(pts, t) {
|
|
4396
|
+
const p = segmentPoint(pts, t);
|
|
4397
|
+
return Math.atan2(p[1] - pts[0][1], p[0] - pts[0][0]);
|
|
4398
|
+
}
|
|
4399
|
+
function mod2pi(a) {
|
|
4400
|
+
const r = a - Math.floor(a / TWO_PI) * TWO_PI;
|
|
4401
|
+
return r >= TWO_PI ? 0 : r;
|
|
4402
|
+
}
|
|
4403
|
+
function traceRings2(edges, scale2) {
|
|
4404
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
4405
|
+
for (const e of edges) {
|
|
4406
|
+
const list = outgoing.get(e.from.id);
|
|
4407
|
+
if (list) list.push(e);
|
|
4408
|
+
else outgoing.set(e.from.id, [e]);
|
|
4409
|
+
}
|
|
4410
|
+
const next = (incoming) => {
|
|
4411
|
+
const candidates = outgoing.get(incoming.to.id)?.filter((c) => !used.has(c) || c === start);
|
|
4412
|
+
if (!candidates || candidates.length === 0) return void 0;
|
|
4413
|
+
if (candidates.length === 1) return candidates[0];
|
|
4414
|
+
const turns = candidates.map((c) => mod2pi(incoming.backAngle - c.outAngle));
|
|
4415
|
+
const tied = (x) => x < TIE || x > TWO_PI - TIE;
|
|
4416
|
+
let ambiguous = turns.some(tied);
|
|
4417
|
+
if (!ambiguous) {
|
|
4418
|
+
const sorted = [...turns].sort((a, b) => a - b);
|
|
4419
|
+
for (let i = 1; i < sorted.length; i++) if (sorted[i] - sorted[i - 1] < TIE) ambiguous = true;
|
|
4420
|
+
}
|
|
4421
|
+
if (ambiguous) {
|
|
4422
|
+
const back = [...incoming.pts].reverse();
|
|
4423
|
+
let delta = Math.min(incoming.length, ...candidates.map((c) => c.length));
|
|
4424
|
+
delta = Math.min(delta * 0.25, scale2 * 0.02);
|
|
4425
|
+
const frac = (e) => Math.min(1, Math.max(1e-6, delta / Math.max(e.length, 1e-300)));
|
|
4426
|
+
const backChord = chordAngle(back, frac(incoming));
|
|
4427
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
4428
|
+
turns[i] = mod2pi(backChord - chordAngle(candidates[i].pts, frac(candidates[i])));
|
|
4429
|
+
if (turns[i] < 1e-12) turns[i] = TWO_PI;
|
|
4430
|
+
}
|
|
4431
|
+
} else {
|
|
4432
|
+
for (let i = 0; i < turns.length; i++) if (turns[i] < 1e-15) turns[i] = TWO_PI;
|
|
4433
|
+
}
|
|
4434
|
+
let best = 0;
|
|
4435
|
+
for (let i = 1; i < candidates.length; i++) if (turns[i] < turns[best]) best = i;
|
|
4436
|
+
return candidates[best];
|
|
4437
|
+
};
|
|
4438
|
+
const used = /* @__PURE__ */ new Set();
|
|
4439
|
+
const rings = [];
|
|
4440
|
+
let start = null;
|
|
4441
|
+
for (const first of edges) {
|
|
4442
|
+
if (used.has(first)) continue;
|
|
4443
|
+
start = first;
|
|
4444
|
+
const ring = [];
|
|
4445
|
+
let edge = first;
|
|
4446
|
+
let guard = edges.length + 1;
|
|
4447
|
+
let closed = false;
|
|
4448
|
+
while (edge && guard-- > 0) {
|
|
4449
|
+
used.add(edge);
|
|
4450
|
+
ring.push(edge);
|
|
4451
|
+
edge = next(edge);
|
|
4452
|
+
if (edge === first) {
|
|
4453
|
+
closed = true;
|
|
4454
|
+
break;
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
if (!closed) throw new PathOpsError("result outline does not close");
|
|
4458
|
+
rings.push(ring);
|
|
4459
|
+
}
|
|
4460
|
+
return rings;
|
|
4461
|
+
}
|
|
4462
|
+
function mergeable(a, b, sources, eps) {
|
|
4463
|
+
if (a.src === b.src && Math.abs(a.sTo - b.sFrom) < 1e-9 && a.sTo - a.sFrom > 0 === b.sTo - b.sFrom > 0) return true;
|
|
4464
|
+
if (a.pts.length === 2 && b.pts.length === 2) {
|
|
4465
|
+
const p = a.pts[0];
|
|
4466
|
+
const q = b.pts[1];
|
|
4467
|
+
const m = a.pts[1];
|
|
4468
|
+
const chord = sub2(q, p);
|
|
4469
|
+
const len = Math.hypot(chord[0], chord[1]);
|
|
4470
|
+
if (len === 0) return false;
|
|
4471
|
+
const off = Math.abs(cross(chord, sub2(m, p))) / len;
|
|
4472
|
+
const along = dot2(chord, sub2(m, p));
|
|
4473
|
+
return off <= eps && along > 0 && along < len * len;
|
|
4474
|
+
}
|
|
4475
|
+
void sources;
|
|
4476
|
+
return false;
|
|
4477
|
+
}
|
|
4478
|
+
function rebuildRing(ring, sources, eps) {
|
|
4479
|
+
const n = ring.length;
|
|
4480
|
+
let startIndex = 0;
|
|
4481
|
+
for (let k = 0; k < n; k++) {
|
|
4482
|
+
if (!mergeable(ring[(k + n - 1) % n], ring[k], sources, eps)) {
|
|
4483
|
+
startIndex = k;
|
|
4484
|
+
break;
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
const ordered = [...ring.slice(startIndex), ...ring.slice(0, startIndex)];
|
|
4488
|
+
const out = [];
|
|
4489
|
+
let i = 0;
|
|
4490
|
+
while (i < ordered.length) {
|
|
4491
|
+
const first = ordered[i];
|
|
4492
|
+
let j = i + 1;
|
|
4493
|
+
while (j < ordered.length && mergeable(ordered[j - 1], ordered[j], sources, eps)) j++;
|
|
4494
|
+
const last = ordered[j - 1];
|
|
4495
|
+
if (j - i === 1) {
|
|
4496
|
+
out.push(first.pts);
|
|
4497
|
+
} else if (first.pts.length === 2 && ordered.slice(i, j).every((e) => e.pts.length === 2)) {
|
|
4498
|
+
out.push([first.from.p, last.to.p]);
|
|
4499
|
+
} else {
|
|
4500
|
+
const source = sources[first.src];
|
|
4501
|
+
out.push(snapEnds(subSegment(source.seg, first.sFrom, last.sTo), first.from.p, last.to.p));
|
|
4502
|
+
}
|
|
4503
|
+
i = j;
|
|
4504
|
+
}
|
|
4505
|
+
return out;
|
|
4506
|
+
}
|
|
4507
|
+
function extractRings(arr, isInside) {
|
|
4508
|
+
const kept = [];
|
|
4509
|
+
arr.edges.forEach((edge, i) => {
|
|
4510
|
+
const l = isInside(insideFlags2(arr.left[i], arr.fillRules));
|
|
4511
|
+
const r = isInside(insideFlags2(arr.right[i], arr.fillRules));
|
|
4512
|
+
if (l === r) return;
|
|
4513
|
+
kept.push(directed(edge, l));
|
|
4514
|
+
});
|
|
4515
|
+
const rings = [];
|
|
4516
|
+
const sliver = Math.max(arr.eps * 10, 1e-7 * arr.scale);
|
|
4517
|
+
for (const ring of traceRings2(kept, arr.scale)) {
|
|
4518
|
+
const segs = rebuildRing(ring, arr.sources, arr.eps);
|
|
4519
|
+
const area = contourSignedArea(segs);
|
|
4520
|
+
let perimeter = 0;
|
|
4521
|
+
for (const s of segs) perimeter += s.length === 2 ? dist2(s[0], s[1]) : segmentLength(s, 0, 1, 1e-6);
|
|
4522
|
+
if (!(perimeter > 0) || 2 * Math.abs(area) / perimeter < sliver) continue;
|
|
4523
|
+
rings.push({ segs, area });
|
|
4524
|
+
}
|
|
4525
|
+
return rings;
|
|
4526
|
+
}
|
|
4527
|
+
function ringSample(ring) {
|
|
4528
|
+
let best = ring.segs[0];
|
|
4529
|
+
let bestLen = -1;
|
|
4530
|
+
for (const s of ring.segs) {
|
|
4531
|
+
const l = dist2(s[0], s[s.length - 1]);
|
|
4532
|
+
if (l > bestLen) {
|
|
4533
|
+
best = s;
|
|
4534
|
+
bestLen = l;
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
return segmentPoint(best, 0.5);
|
|
4538
|
+
}
|
|
4539
|
+
function groupRings(rings) {
|
|
4540
|
+
const outers = rings.filter((r) => r.area > 0).sort((a, b) => b.area - a.area);
|
|
4541
|
+
const holes = rings.filter((r) => r.area < 0);
|
|
4542
|
+
const groups = outers.map((o) => [o]);
|
|
4543
|
+
for (const hole of holes) {
|
|
4544
|
+
const sample = ringSample(hole);
|
|
4545
|
+
for (let i = outers.length - 1; i >= 0; i--) {
|
|
4546
|
+
if (outers[i].area >= -hole.area && windingNumber([outers[i].segs], sample) !== 0) {
|
|
4547
|
+
groups[i].push(hole);
|
|
4548
|
+
break;
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
return groups;
|
|
4553
|
+
}
|
|
4554
|
+
function ringsToPath2(rings) {
|
|
4555
|
+
return contoursToPath(rings.map((r) => ({ segments: r.segs, closed: true })));
|
|
4556
|
+
}
|
|
4557
|
+
function predicate2(operation) {
|
|
4558
|
+
switch (operation) {
|
|
4559
|
+
case "union":
|
|
4560
|
+
return (f) => f.some(Boolean);
|
|
4561
|
+
case "intersection":
|
|
4562
|
+
return (f) => f.length > 0 && f.every(Boolean);
|
|
4563
|
+
case "difference":
|
|
4564
|
+
return (f) => f[0] === true && !f.slice(1).some(Boolean);
|
|
4565
|
+
case "xor":
|
|
4566
|
+
return (f) => f.filter(Boolean).length % 2 === 1;
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
function flattenOperand(input, tolerance) {
|
|
4570
|
+
const path = isOperandObject(input) ? input.path : input;
|
|
4571
|
+
const fillRule = isOperandObject(input) ? input.fillRule ?? "nonzero" : "nonzero";
|
|
4572
|
+
const rings = pathToContours(path ?? "").map((c) => flattenChain(closeContour(c, true), tolerance)).filter((r) => r.length >= 3 && r.every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1])));
|
|
4573
|
+
return { rings, fillRule };
|
|
4574
|
+
}
|
|
4575
|
+
function multiToPath(multi, tolerance) {
|
|
4576
|
+
if (multi.length === 0) return [];
|
|
4577
|
+
return toBezierPath(polygonToSvgPath(multi, { curves: "fit", curveTolerance: tolerance, precision: 9 }));
|
|
4578
|
+
}
|
|
4579
|
+
function reasonOf(error) {
|
|
4580
|
+
return error instanceof Error ? error.message : String(error);
|
|
4581
|
+
}
|
|
4582
|
+
function pathBoolean(operation, operands, options = {}) {
|
|
4583
|
+
if (operands.length === 0) return { path: [], engine: "curve" };
|
|
4584
|
+
try {
|
|
4585
|
+
const arr = buildArrangement(operands, options);
|
|
4586
|
+
return { path: ringsToPath2(extractRings(arr, predicate2(operation))), engine: "curve" };
|
|
4587
|
+
} catch (error) {
|
|
4588
|
+
const reason = reasonOf(error);
|
|
4589
|
+
if (reason === NON_FINITE) return { path: [], engine: "curve", error: reason };
|
|
4590
|
+
if (options.fallback === false) throw error;
|
|
4591
|
+
options.onFallback?.(reason);
|
|
4592
|
+
const tolerance = options.fallbackTolerance ?? 0.05;
|
|
4593
|
+
const multi = polygonBoolean(
|
|
4594
|
+
operation,
|
|
4595
|
+
operands.map((o) => flattenOperand(o, tolerance)),
|
|
4596
|
+
{ epsilon: options.epsilon }
|
|
4597
|
+
);
|
|
4598
|
+
return { path: multiToPath(multi, tolerance), engine: "polygon", fallbackReason: reason };
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
var unitePaths = (a, b, options) => pathBoolean("union", [a, b], options).path;
|
|
4602
|
+
var intersectPaths = (a, b, options) => pathBoolean("intersection", [a, b], options).path;
|
|
4603
|
+
var subtractPaths = (a, b, options) => pathBoolean("difference", [a, b], options).path;
|
|
4604
|
+
var excludePaths = (a, b, options) => pathBoolean("xor", [a, b], options).path;
|
|
4605
|
+
function dividePaths(operands, options = {}) {
|
|
4606
|
+
if (operands.length === 0) return { pieces: [], engine: "curve" };
|
|
4607
|
+
try {
|
|
4608
|
+
const arr = buildArrangement(operands, options);
|
|
4609
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
4610
|
+
const note = (windings) => {
|
|
4611
|
+
const flags = insideFlags2(windings, arr.fillRules);
|
|
4612
|
+
if (!flags.some(Boolean)) return;
|
|
4613
|
+
const key = flags.map((f) => f ? "1" : "0").join("");
|
|
4614
|
+
if (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => f ? [k] : []));
|
|
4615
|
+
};
|
|
4616
|
+
arr.edges.forEach((_, i) => {
|
|
4617
|
+
note(arr.left[i]);
|
|
4618
|
+
note(arr.right[i]);
|
|
4619
|
+
});
|
|
4620
|
+
const pieces = [];
|
|
4621
|
+
for (const key of [...signatures.keys()].sort()) {
|
|
4622
|
+
const rings = extractRings(arr, (flags) => flags.map((f) => f ? "1" : "0").join("") === key);
|
|
4623
|
+
const groups = groupRings(rings).sort((a, b) => b[0].area - a[0].area);
|
|
4624
|
+
for (const group of groups) pieces.push({ path: ringsToPath2(group), operands: signatures.get(key) });
|
|
4625
|
+
}
|
|
4626
|
+
return { pieces, engine: "curve" };
|
|
4627
|
+
} catch (error) {
|
|
4628
|
+
const reason = reasonOf(error);
|
|
4629
|
+
if (reason === NON_FINITE) return { pieces: [], engine: "curve", error: reason };
|
|
4630
|
+
if (options.fallback === false) throw error;
|
|
4631
|
+
options.onFallback?.(reason);
|
|
4632
|
+
const tolerance = options.fallbackTolerance ?? 0.05;
|
|
4633
|
+
const pieces = dividePolygons(
|
|
4634
|
+
operands.map((o) => flattenOperand(o, tolerance)),
|
|
4635
|
+
{ epsilon: options.epsilon }
|
|
4636
|
+
).map((p) => ({ path: multiToPath([p.polygon], tolerance), operands: p.operands }));
|
|
4637
|
+
return { pieces, engine: "polygon", fallbackReason: reason };
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
function normalizeBezierPath(path, fillRule = "nonzero", options = {}) {
|
|
4641
|
+
return pathBoolean("union", [{ path, fillRule }], options).path;
|
|
4642
|
+
}
|
|
4643
|
+
function bezierPathArea(path, fillRule = "nonzero", options = {}) {
|
|
4644
|
+
const normalized = normalizeBezierPath(path, fillRule, options);
|
|
4645
|
+
let sum = 0;
|
|
4646
|
+
for (const contour of pathToContours(normalized)) sum += contourSignedArea(closeContour(contour, true));
|
|
4647
|
+
return Math.abs(sum);
|
|
4648
|
+
}
|
|
4649
|
+
function pathBooleanRings(operation, operands, options = {}) {
|
|
4650
|
+
const arr = buildArrangement(operands, options);
|
|
4651
|
+
const rings = extractRings(arr, typeof operation === "function" ? operation : predicate2(operation));
|
|
4652
|
+
const order = rings.map((_, i) => i).sort((a, b) => Math.abs(rings[a].area) - Math.abs(rings[b].area));
|
|
4653
|
+
return rings.map((ring, i) => {
|
|
4654
|
+
const sample = ringSample(ring);
|
|
4655
|
+
let parent = -1;
|
|
4656
|
+
for (const j of order) {
|
|
4657
|
+
if (j === i || Math.abs(rings[j].area) <= Math.abs(ring.area)) continue;
|
|
4658
|
+
if (windingNumber([rings[j].segs], sample) !== 0) {
|
|
4659
|
+
parent = j;
|
|
4660
|
+
break;
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
return { segments: ring.segs, area: ring.area, parent };
|
|
4664
|
+
});
|
|
4665
|
+
}
|
|
4666
|
+
function nestedRingsToPath(rings, keep = () => true) {
|
|
4667
|
+
const contours = [];
|
|
4668
|
+
rings.forEach((r, i) => {
|
|
4669
|
+
if (keep(i)) contours.push({ segments: r.segments, closed: true });
|
|
4670
|
+
});
|
|
4671
|
+
return contoursToPath(contours);
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
// src/canvas/pathops/stroke.ts
|
|
4675
|
+
var DEFAULT_TOLERANCE = 0.01;
|
|
4676
|
+
var normal = (t) => [-t[1], t[0]];
|
|
4677
|
+
function raise(seg) {
|
|
4678
|
+
if (seg.length !== 3) return [...seg];
|
|
4679
|
+
const [p0, p1, p2] = seg;
|
|
4680
|
+
return [p0, [p0[0] + 2 / 3 * (p1[0] - p0[0]), p0[1] + 2 / 3 * (p1[1] - p0[1])], [p2[0] + 2 / 3 * (p1[0] - p2[0]), p2[1] + 2 / 3 * (p1[1] - p2[1])], p2];
|
|
4681
|
+
}
|
|
4682
|
+
function isDegenerate(seg, tol) {
|
|
4683
|
+
return seg.every((p) => dist2(p, seg[0]) <= tol);
|
|
4684
|
+
}
|
|
4685
|
+
function offsetSegment(seg, d, tol, out, depth = 0) {
|
|
4686
|
+
if (seg.length === 2) {
|
|
4687
|
+
const n = normal(segmentTangent(seg, 0));
|
|
4688
|
+
out.push([add2(seg[0], scale(n, d)), add2(seg[1], scale(n, d))]);
|
|
4689
|
+
return;
|
|
4690
|
+
}
|
|
4691
|
+
const c = raise(seg);
|
|
4692
|
+
const t0 = segmentTangent(c, 0);
|
|
4693
|
+
const t1 = segmentTangent(c, 1);
|
|
4694
|
+
const q0 = add2(c[0], scale(normal(t0), d));
|
|
4695
|
+
const q3 = add2(c[3], scale(normal(t1), d));
|
|
4696
|
+
const k0 = segmentCurvature(c, 0);
|
|
4697
|
+
const k1 = segmentCurvature(c, 1);
|
|
4698
|
+
const f0 = Number.isFinite(k0) ? 1 - d * k0 : 1;
|
|
4699
|
+
const f1 = Number.isFinite(k1) ? 1 - d * k1 : 1;
|
|
4700
|
+
const q1 = add2(q0, scale(sub2(c[1], c[0]), f0));
|
|
4701
|
+
const q2 = add2(q3, scale(sub2(c[2], c[3]), f1));
|
|
4702
|
+
const approx = [q0, q1, q2, q3];
|
|
4703
|
+
if (depth < 10) {
|
|
4704
|
+
let err = 0;
|
|
4705
|
+
for (const t of [0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875]) {
|
|
4706
|
+
const target = add2(segmentPoint(c, t), scale(normal(segmentTangent(c, t)), d));
|
|
4707
|
+
err = Math.max(err, segmentNearest(approx, target).distance);
|
|
4708
|
+
if (err > tol) break;
|
|
4709
|
+
}
|
|
4710
|
+
if (err <= tol) {
|
|
4711
|
+
const m = segmentPoint(approx, 0.5);
|
|
4712
|
+
const back = segmentNearest(c, m).distance;
|
|
4713
|
+
err = Math.max(err, Math.abs(back - Math.abs(d)));
|
|
4714
|
+
}
|
|
4715
|
+
if (err > tol) {
|
|
4716
|
+
const [a, b] = splitSegment(c, 0.5);
|
|
4717
|
+
offsetSegment(a, d, tol, out, depth + 1);
|
|
4718
|
+
offsetSegment(b, d, tol, out, depth + 1);
|
|
4719
|
+
return;
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
out.push(approx);
|
|
4723
|
+
}
|
|
4724
|
+
function pushArc(out, center, radius, start, sweep, from, to) {
|
|
4725
|
+
const n = Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2) - 1e-9));
|
|
4726
|
+
const step = sweep / n;
|
|
4727
|
+
const k = 4 / 3 * Math.tan(step / 4);
|
|
4728
|
+
let prev = from;
|
|
4729
|
+
for (let i = 0; i < n; i++) {
|
|
4730
|
+
const a0 = start + step * i;
|
|
4731
|
+
const a1 = a0 + step;
|
|
4732
|
+
const c0 = Math.cos(a0);
|
|
4733
|
+
const s0 = Math.sin(a0);
|
|
4734
|
+
const c1 = Math.cos(a1);
|
|
4735
|
+
const s1 = Math.sin(a1);
|
|
4736
|
+
const end = i === n - 1 ? to : [center[0] + radius * c1, center[1] + radius * s1];
|
|
4737
|
+
out.push([
|
|
4738
|
+
prev,
|
|
4739
|
+
[center[0] + radius * (c0 - k * s0), center[1] + radius * (s0 + k * c0)],
|
|
4740
|
+
[center[0] + radius * (c1 + k * s1), center[1] + radius * (s1 - k * c1)],
|
|
4741
|
+
end
|
|
4742
|
+
]);
|
|
4743
|
+
prev = end;
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
function lineTo(out, from, to) {
|
|
4747
|
+
if (!samePoint(from, to)) out.push([from, to]);
|
|
4748
|
+
}
|
|
4749
|
+
function addJoin(out, p, tin, tout, d, style) {
|
|
4750
|
+
const from = add2(p, scale(normal(tin), d));
|
|
4751
|
+
const to = add2(p, scale(normal(tout), d));
|
|
4752
|
+
const turn = cross(tin, tout);
|
|
4753
|
+
const cos = dot2(tin, tout);
|
|
4754
|
+
if (Math.abs(turn) < 1e-9 && cos > 0) {
|
|
4755
|
+
lineTo(out, from, to);
|
|
4756
|
+
return;
|
|
4757
|
+
}
|
|
4758
|
+
const reversal = Math.abs(turn) < 1e-9 && cos < 0;
|
|
4759
|
+
const inner = !reversal && turn > 0 === d > 0;
|
|
4760
|
+
if (inner) {
|
|
4761
|
+
lineTo(out, from, p);
|
|
4762
|
+
lineTo(out, p, to);
|
|
4763
|
+
return;
|
|
4764
|
+
}
|
|
4765
|
+
const theta = reversal ? d > 0 ? -Math.PI : Math.PI : Math.atan2(turn, cos);
|
|
4766
|
+
if (style.join === "round") {
|
|
4767
|
+
const v = sub2(from, p);
|
|
4768
|
+
pushArc(out, p, Math.abs(d), Math.atan2(v[1], v[0]), theta, from, to);
|
|
4769
|
+
return;
|
|
4770
|
+
}
|
|
4771
|
+
if (style.join === "miter" && !reversal) {
|
|
4772
|
+
const phi = Math.PI - Math.abs(theta);
|
|
4773
|
+
const ratio = 1 / Math.max(Math.sin(phi / 2), 1e-12);
|
|
4774
|
+
if (ratio <= style.miterLimit) {
|
|
4775
|
+
const nin = normal(tin);
|
|
4776
|
+
const nout = normal(tout);
|
|
4777
|
+
const s = d / (1 + dot2(nin, nout));
|
|
4778
|
+
const miter = add2(p, scale(add2(nin, nout), s));
|
|
4779
|
+
lineTo(out, from, miter);
|
|
4780
|
+
lineTo(out, miter, to);
|
|
4781
|
+
return;
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
lineTo(out, from, to);
|
|
4785
|
+
}
|
|
4786
|
+
function offsetChain(segs, d, closed, style, tol) {
|
|
4787
|
+
const out = [];
|
|
4788
|
+
const pen = () => out.length > 0 ? out[out.length - 1][out[out.length - 1].length - 1] : null;
|
|
4789
|
+
for (let i = 0; i < segs.length; i++) {
|
|
4790
|
+
if (i > 0) addJoin(out, segs[i][0], segmentTangent(segs[i - 1], 1), segmentTangent(segs[i], 0), d, style);
|
|
4791
|
+
const pieces = [];
|
|
4792
|
+
offsetSegment(segs[i], d, tol, pieces);
|
|
4793
|
+
for (const piece of pieces) {
|
|
4794
|
+
const at = pen();
|
|
4795
|
+
if (at && !samePoint(at, piece[0])) lineTo(out, at, piece[0]);
|
|
4796
|
+
out.push(piece);
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
if (closed && segs.length > 0) {
|
|
4800
|
+
const last = segs[segs.length - 1];
|
|
4801
|
+
addJoin(out, segs[0][0], segmentTangent(last, 1), segmentTangent(segs[0], 0), d, style);
|
|
4802
|
+
const at = pen();
|
|
4803
|
+
if (at && out.length > 0 && !samePoint(at, out[0][0])) lineTo(out, at, out[0][0]);
|
|
4804
|
+
}
|
|
4805
|
+
return out;
|
|
4806
|
+
}
|
|
4807
|
+
function reverseChain(chain) {
|
|
4808
|
+
return chain.map((s) => [...s].reverse()).reverse();
|
|
4809
|
+
}
|
|
4810
|
+
function addCap(out, p, t, h, cap) {
|
|
4811
|
+
const n = normal(t);
|
|
4812
|
+
const from = add2(p, scale(n, h));
|
|
4813
|
+
const to = add2(p, scale(n, -h));
|
|
4814
|
+
if (cap === "round") {
|
|
4815
|
+
pushArc(out, p, h, Math.atan2(n[1], n[0]), -Math.PI, from, to);
|
|
4816
|
+
} else if (cap === "square") {
|
|
4817
|
+
const a = add2(from, scale(t, h));
|
|
4818
|
+
const b = add2(to, scale(t, h));
|
|
4819
|
+
lineTo(out, from, a);
|
|
4820
|
+
lineTo(out, a, b);
|
|
4821
|
+
lineTo(out, b, to);
|
|
4822
|
+
} else {
|
|
4823
|
+
lineTo(out, from, to);
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
function dotRings(p, t, h, cap) {
|
|
4827
|
+
if (cap === "butt") return [];
|
|
4828
|
+
const ring = [];
|
|
4829
|
+
addCap(ring, p, t, h, cap);
|
|
4830
|
+
addCap(ring, p, [-t[0], -t[1]], h, cap);
|
|
4831
|
+
return [ring];
|
|
4832
|
+
}
|
|
4833
|
+
function strokeContour(contour, h, style, tol) {
|
|
4834
|
+
const tiny = Math.max(1e-12, tol * 1e-6);
|
|
4835
|
+
const all = contour.closed ? closeContour(contour) : contour.segments;
|
|
4836
|
+
const segs = all.filter((s) => !isDegenerate(s, tiny));
|
|
4837
|
+
if (segs.length === 0) {
|
|
4838
|
+
const p = contour.segments[0]?.[0];
|
|
4839
|
+
return p ? dotRings(p, [1, 0], h, style.cap) : [];
|
|
4840
|
+
}
|
|
4841
|
+
if (contour.closed) {
|
|
4842
|
+
const left2 = offsetChain(segs, h, true, style, tol);
|
|
4843
|
+
const right2 = offsetChain(segs, -h, true, style, tol);
|
|
4844
|
+
return [left2, reverseChain(right2)];
|
|
4845
|
+
}
|
|
4846
|
+
const left = offsetChain(segs, h, false, style, tol);
|
|
4847
|
+
const right = reverseChain(offsetChain(segs, -h, false, style, tol));
|
|
4848
|
+
const ring = [...left];
|
|
4849
|
+
const last = segs[segs.length - 1];
|
|
4850
|
+
addCap(ring, last[last.length - 1], segmentTangent(last, 1), h, style.cap);
|
|
4851
|
+
const joinTo = (p) => {
|
|
4852
|
+
const at = ring[ring.length - 1][ring[ring.length - 1].length - 1];
|
|
4853
|
+
lineTo(ring, at, p);
|
|
4854
|
+
};
|
|
4855
|
+
if (right.length > 0) joinTo(right[0][0]);
|
|
4856
|
+
ring.push(...right);
|
|
4857
|
+
const first = segs[0];
|
|
4858
|
+
const t0 = segmentTangent(first, 0);
|
|
4859
|
+
addCap(ring, first[0], [-t0[0], -t0[1]], h, style.cap);
|
|
4860
|
+
if (ring.length > 0) joinTo(ring[0][0]);
|
|
4861
|
+
return [ring];
|
|
4862
|
+
}
|
|
4863
|
+
function ringsToPath3(rings) {
|
|
4864
|
+
return contoursToPath(rings.filter((r) => r.length > 0).map((segments) => ({ segments, closed: true })));
|
|
4865
|
+
}
|
|
4866
|
+
var MAX_DASHES = 2e5;
|
|
4867
|
+
var MIN_ENTRY = 1e-6;
|
|
4868
|
+
function dashContours(contours, pattern, offset = 0) {
|
|
4869
|
+
if (pattern.length === 0 || pattern.some((v) => !Number.isFinite(v) || v < 0) || !Number.isFinite(offset)) return null;
|
|
4870
|
+
if (!pattern.some((v) => v > MIN_ENTRY)) return null;
|
|
4871
|
+
const pat = pattern.length % 2 === 1 ? [...pattern, ...pattern] : [...pattern];
|
|
4872
|
+
const period = pat.reduce((s, v) => s + v, 0);
|
|
4873
|
+
const chains = contours.map((c) => {
|
|
4874
|
+
const segs = closeContour(c);
|
|
4875
|
+
const lengths = segs.map((s) => segmentLength(s));
|
|
4876
|
+
return { segs, lengths, total: lengths.reduce((s, v) => s + v, 0), closed: c.closed };
|
|
4877
|
+
});
|
|
4878
|
+
const totalLength = chains.reduce((s, c) => s + c.total, 0);
|
|
4879
|
+
if (totalLength / period * pat.length > MAX_DASHES) return null;
|
|
4880
|
+
let off = (offset % period + period) % period;
|
|
4881
|
+
let ix0 = 0;
|
|
4882
|
+
while (off > pat[ix0]) {
|
|
4883
|
+
off -= pat[ix0];
|
|
4884
|
+
ix0 = (ix0 + 1) % pat.length;
|
|
4885
|
+
}
|
|
4886
|
+
const out = { contours: [], dots: [] };
|
|
4887
|
+
for (const chain of chains) {
|
|
4888
|
+
if (chain.segs.length === 0 || !(chain.total > 0)) continue;
|
|
4889
|
+
let ix = ix0;
|
|
4890
|
+
let rem = pat[ix0] - off;
|
|
4891
|
+
let pos = 0;
|
|
4892
|
+
const dashes = [];
|
|
4893
|
+
let guard = 0;
|
|
4894
|
+
while (pos < chain.total && guard++ < MAX_DASHES * 2) {
|
|
4895
|
+
const step = Math.min(rem, chain.total - pos);
|
|
4896
|
+
const on = ix % 2 === 0;
|
|
4897
|
+
if (on) dashes.push({ from: pos, to: pos + step });
|
|
4898
|
+
pos += step;
|
|
4899
|
+
rem -= step;
|
|
4900
|
+
if (rem <= 1e-12) {
|
|
4901
|
+
ix = (ix + 1) % pat.length;
|
|
4902
|
+
rem = pat[ix];
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
if (chain.closed && dashes.length > 1) {
|
|
4906
|
+
const first = dashes[0];
|
|
4907
|
+
const last = dashes[dashes.length - 1];
|
|
4908
|
+
if (first.from <= 1e-9 && last.to >= chain.total - 1e-9 && first.to > first.from && last.to > last.from) {
|
|
4909
|
+
dashes.pop();
|
|
4910
|
+
const tail = trimSegments(chain.segs, last.from, chain.total, chain.lengths);
|
|
4911
|
+
const head = trimSegments(chain.segs, 0, first.to, chain.lengths);
|
|
4912
|
+
dashes.shift();
|
|
4913
|
+
out.contours.push({ segments: [...tail, ...head], closed: false });
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
for (const dash of dashes) {
|
|
4917
|
+
if (dash.to - dash.from <= 1e-12) {
|
|
4918
|
+
const at = locate(chain.segs, chain.lengths, dash.from);
|
|
4919
|
+
out.dots.push(at);
|
|
4920
|
+
continue;
|
|
4921
|
+
}
|
|
4922
|
+
const segments = trimSegments(chain.segs, dash.from, dash.to, chain.lengths);
|
|
4923
|
+
if (segments.length > 0) out.contours.push({ segments, closed: false });
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
return out;
|
|
4927
|
+
}
|
|
4928
|
+
function locate(segs, lengths, at) {
|
|
4929
|
+
let acc = 0;
|
|
4930
|
+
for (let i = 0; i < segs.length; i++) {
|
|
4931
|
+
if (at <= acc + lengths[i] || i === segs.length - 1) {
|
|
4932
|
+
const piece = trimSegments([segs[i]], 0, Math.max(0, at - acc), [lengths[i]]);
|
|
4933
|
+
const t = piece.length > 0 ? 1 : 0;
|
|
4934
|
+
const point = piece.length > 0 ? piece[piece.length - 1][piece[piece.length - 1].length - 1] : segs[i][0];
|
|
4935
|
+
const tangent = segmentTangent(piece.length > 0 ? piece[piece.length - 1] : segs[i], t);
|
|
4936
|
+
return { point, tangent };
|
|
4937
|
+
}
|
|
4938
|
+
acc += lengths[i];
|
|
4939
|
+
}
|
|
4940
|
+
return { point: segs[0][0], tangent: segmentTangent(segs[0], 0) };
|
|
4941
|
+
}
|
|
4942
|
+
function deepPoint(rings) {
|
|
4943
|
+
let minX = Infinity;
|
|
4944
|
+
let minY = Infinity;
|
|
4945
|
+
let maxX = -Infinity;
|
|
4946
|
+
let maxY = -Infinity;
|
|
4947
|
+
for (const p of rings[0].flat()) {
|
|
4948
|
+
minX = Math.min(minX, p[0]);
|
|
4949
|
+
maxX = Math.max(maxX, p[0]);
|
|
4950
|
+
minY = Math.min(minY, p[1]);
|
|
4951
|
+
maxY = Math.max(maxY, p[1]);
|
|
4952
|
+
}
|
|
4953
|
+
const w = maxX - minX;
|
|
4954
|
+
const h = maxY - minY;
|
|
4955
|
+
if (!(w > 0 && h > 0)) return null;
|
|
4956
|
+
const tolerance = Math.max(1e-6, 2e-3 * Math.min(w, h));
|
|
4957
|
+
const polys = rings.map((r) => flattenChain(r, tolerance));
|
|
4958
|
+
let best = null;
|
|
4959
|
+
for (const f of [0.5, 0.3, 0.7, 0.15, 0.85]) {
|
|
4960
|
+
const y = minY + h * f;
|
|
4961
|
+
const xs = [];
|
|
4962
|
+
for (const poly of polys) {
|
|
4963
|
+
for (let i = 0; i < poly.length; i++) {
|
|
4964
|
+
const a = poly[i];
|
|
4965
|
+
const b = poly[(i + 1) % poly.length];
|
|
4966
|
+
if (a[1] <= y !== b[1] <= y) xs.push(a[0] + (y - a[1]) / (b[1] - a[1]) * (b[0] - a[0]));
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
xs.sort((p, q) => p - q);
|
|
4970
|
+
for (let i = 0; i + 1 < xs.length; i += 2) {
|
|
4971
|
+
const span = xs[i + 1] - xs[i];
|
|
4972
|
+
if (!best || span > best.span) best = { span, p: [(xs[i] + xs[i + 1]) / 2, y] };
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4975
|
+
return best?.p ?? null;
|
|
4976
|
+
}
|
|
4977
|
+
function dropSpurious(rings, spurious) {
|
|
4978
|
+
const children = rings.map(() => []);
|
|
4979
|
+
rings.forEach((r, i) => {
|
|
4980
|
+
if (r.parent >= 0) children[r.parent].push(i);
|
|
4981
|
+
});
|
|
4982
|
+
const marked = rings.map((r, i) => spurious([r.segments, ...children[i].map((c) => rings[c].segments)], r));
|
|
4983
|
+
const hasMarkedAncestor = (i) => {
|
|
4984
|
+
for (let k = i; k >= 0; k = rings[k].parent) if (marked[k]) return true;
|
|
4985
|
+
return false;
|
|
4986
|
+
};
|
|
4987
|
+
return nestedRingsToPath(rings, (i) => !hasMarkedAncestor(i));
|
|
4988
|
+
}
|
|
4989
|
+
function mergeSmoothRuns(path, tolerance) {
|
|
4990
|
+
const contours = pathToContours(path).map((contour) => {
|
|
4991
|
+
const segs = closeContour(contour, true).map((s) => s.length === 3 ? raise(s) : [...s]);
|
|
4992
|
+
const n = segs.length;
|
|
4993
|
+
if (n < 2) return { segments: segs, closed: true };
|
|
4994
|
+
const smooth = (a, b) => {
|
|
4995
|
+
if (a.length !== 4 || b.length !== 4) return false;
|
|
4996
|
+
const ta = segmentTangent(a, 1);
|
|
4997
|
+
const tb = segmentTangent(b, 0);
|
|
4998
|
+
return dot2(ta, tb) > 0.9998 && Math.abs(cross(ta, tb)) < 0.02;
|
|
4999
|
+
};
|
|
5000
|
+
let start = 0;
|
|
5001
|
+
for (let k = 0; k < n; k++) {
|
|
5002
|
+
if (!smooth(segs[(k + n - 1) % n], segs[k])) {
|
|
5003
|
+
start = k;
|
|
5004
|
+
break;
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
const ordered = [...segs.slice(start), ...segs.slice(0, start)];
|
|
5008
|
+
const out = [];
|
|
5009
|
+
let i = 0;
|
|
5010
|
+
while (i < ordered.length) {
|
|
5011
|
+
let current = ordered[i];
|
|
5012
|
+
let j = i + 1;
|
|
5013
|
+
while (j < ordered.length && smooth(ordered[j - 1], ordered[j])) {
|
|
5014
|
+
const run = ordered.slice(i, j + 1);
|
|
5015
|
+
const pts = [];
|
|
5016
|
+
run.forEach((s, k) => {
|
|
5017
|
+
for (let q = k === 0 ? 0 : 1; q <= 12; q++) pts.push(segmentPoint(s, q / 12));
|
|
5018
|
+
});
|
|
5019
|
+
const fit = fitCubicRun(pts, tolerance, segmentTangent(run[0], 0), segmentTangent(run[run.length - 1], 1));
|
|
5020
|
+
if (fit.length !== 1) break;
|
|
5021
|
+
const c = fit[0];
|
|
5022
|
+
current = [run[0][0], c.p1, c.p2, run[run.length - 1][3]];
|
|
5023
|
+
j++;
|
|
5024
|
+
}
|
|
5025
|
+
out.push(current);
|
|
5026
|
+
i = j;
|
|
5027
|
+
}
|
|
5028
|
+
return { segments: out, closed: true };
|
|
5029
|
+
});
|
|
5030
|
+
return contoursToPath(contours);
|
|
5031
|
+
}
|
|
5032
|
+
function joinStyle(join, miterLimit) {
|
|
5033
|
+
return {
|
|
5034
|
+
join: join ?? "miter",
|
|
5035
|
+
miterLimit: miterLimit !== void 0 && Number.isFinite(miterLimit) ? Math.max(1, miterLimit) : 4
|
|
5036
|
+
};
|
|
5037
|
+
}
|
|
5038
|
+
function strokeOutlineRings(input, options) {
|
|
5039
|
+
const h = options.width / 2;
|
|
5040
|
+
if (!(h > 0) || !Number.isFinite(h)) return [];
|
|
5041
|
+
const tol = options.tolerance && options.tolerance > 0 ? options.tolerance : DEFAULT_TOLERANCE;
|
|
5042
|
+
const style = { ...joinStyle(options.join, options.miterLimit), cap: options.cap ?? "butt" };
|
|
5043
|
+
let contours = pathToContours(input);
|
|
5044
|
+
const rings = [];
|
|
5045
|
+
if (options.dash && options.dash.length > 0) {
|
|
5046
|
+
const dashed = dashContours(contours, options.dash, options.dashOffset ?? 0);
|
|
5047
|
+
if (dashed) {
|
|
5048
|
+
contours = dashed.contours;
|
|
5049
|
+
for (const dot3 of dashed.dots) rings.push(...dotRings(dot3.point, dot3.tangent, h, style.cap));
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
for (const contour of contours) rings.push(...strokeContour(contour, h, style, tol));
|
|
5053
|
+
return ringsToPath3(rings);
|
|
5054
|
+
}
|
|
5055
|
+
function outlineStroke(input, options) {
|
|
5056
|
+
const h = options.width / 2;
|
|
5057
|
+
if (!(h > 0) || !Number.isFinite(h)) return [];
|
|
5058
|
+
let source;
|
|
5059
|
+
try {
|
|
5060
|
+
source = contoursToPath(pathToContours(input));
|
|
5061
|
+
} catch {
|
|
5062
|
+
return [];
|
|
5063
|
+
}
|
|
5064
|
+
if (source.some((c) => c.type !== "Z" && Object.values(c).some((v) => typeof v === "number" && !Number.isFinite(v)))) return [];
|
|
5065
|
+
const raw = strokeOutlineRings(source, options);
|
|
5066
|
+
if (raw.length === 0) return [];
|
|
5067
|
+
const tol = options.tolerance && options.tolerance > 0 ? options.tolerance : DEFAULT_TOLERANCE;
|
|
5068
|
+
const sourceContours = pathToContours(source);
|
|
5069
|
+
const operand = { path: raw, fillRule: "nonzero" };
|
|
5070
|
+
try {
|
|
5071
|
+
const rings = pathBooleanRings("union", [operand], options);
|
|
5072
|
+
const cleaned = dropSpurious(rings, (face, ring) => {
|
|
5073
|
+
if (ring.area >= 0) return false;
|
|
5074
|
+
const p = deepPoint(face);
|
|
5075
|
+
return !!p && distanceToBezierPath(sourceContours, p) < h * (1 - 1e-4) - 1e-6;
|
|
5076
|
+
});
|
|
5077
|
+
return mergeSmoothRuns(cleaned, tol);
|
|
5078
|
+
} catch {
|
|
5079
|
+
return pathBoolean("union", [operand], options).path;
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
function offsetPath(input, distance, options = {}) {
|
|
5083
|
+
if (!Number.isFinite(distance)) return [];
|
|
5084
|
+
const contours = pathToContours(input);
|
|
5085
|
+
if (contours.some((c) => c.segments.some((s) => s.some((p) => !Number.isFinite(p[0]) || !Number.isFinite(p[1]))))) return [];
|
|
5086
|
+
const closed = contours.filter((c) => c.closed && c.segments.length > 0);
|
|
5087
|
+
const open = contours.filter((c) => !c.closed && c.segments.length > 0);
|
|
5088
|
+
const fillRule = options.fillRule ?? "nonzero";
|
|
5089
|
+
const fill = { path: contoursToPath(closed), fillRule };
|
|
5090
|
+
const d = Math.abs(distance);
|
|
5091
|
+
const tol = options.tolerance && options.tolerance > 0 ? options.tolerance : DEFAULT_TOLERANCE;
|
|
5092
|
+
if (d === 0) return pathBoolean("union", [fill], options).path;
|
|
5093
|
+
const strokeOptions = { width: 2 * d, join: options.join ?? "miter", miterLimit: options.miterLimit, cap: "butt", tolerance: tol };
|
|
5094
|
+
const ring = { path: strokeOutlineRings(contoursToPath(closed), strokeOptions), fillRule: "nonzero" };
|
|
5095
|
+
const openRing = {
|
|
5096
|
+
path: distance > 0 ? strokeOutlineRings(contoursToPath(open), strokeOptions) : [],
|
|
5097
|
+
fillRule: "nonzero"
|
|
5098
|
+
};
|
|
5099
|
+
const grow = distance > 0;
|
|
5100
|
+
const operands = [fill, ring, openRing];
|
|
5101
|
+
try {
|
|
5102
|
+
const rings = pathBooleanRings(grow ? (m) => m[0] || m[1] || m[2] : (m) => m[0] && !m[1], operands, options);
|
|
5103
|
+
const sourceContours = contours;
|
|
5104
|
+
const cleaned = dropSpurious(rings, (face, r) => {
|
|
5105
|
+
if (grow ? r.area >= 0 : r.area <= 0) return false;
|
|
5106
|
+
const p = deepPoint(face);
|
|
5107
|
+
return !!p && distanceToBezierPath(sourceContours, p) < d * (1 - 1e-4) - 1e-6;
|
|
5108
|
+
});
|
|
5109
|
+
return mergeSmoothRuns(cleaned, tol);
|
|
5110
|
+
} catch {
|
|
5111
|
+
return grow ? pathBoolean("union", operands, options).path : pathBoolean("difference", [fill, ring], options).path;
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
|
|
5115
|
+
// src/canvas/shapeNodes.ts
|
|
5116
|
+
var SHAPE_DATA_KEY = "shape";
|
|
5117
|
+
var SHAPE_NODE_TYPE = "shape";
|
|
5118
|
+
var SHAPE_OPERATIONS = ["unite", "intersect", "subtract", "exclude", "divide"];
|
|
5119
|
+
var BOOLEAN_FOR = {
|
|
5120
|
+
unite: "union",
|
|
5121
|
+
intersect: "intersection",
|
|
5122
|
+
subtract: "difference",
|
|
5123
|
+
exclude: "xor"
|
|
5124
|
+
};
|
|
5125
|
+
function isShapeNode(node) {
|
|
5126
|
+
return !!node && isShapeDescription(node.data?.[SHAPE_DATA_KEY]);
|
|
5127
|
+
}
|
|
5128
|
+
function getNodeShape(node) {
|
|
5129
|
+
const shape = node?.data?.[SHAPE_DATA_KEY];
|
|
5130
|
+
return isShapeDescription(shape) ? shape : null;
|
|
5131
|
+
}
|
|
5132
|
+
function shapeNodeToPolygon(node, lookup, options = {}) {
|
|
5133
|
+
const shape = getNodeShape(node);
|
|
5134
|
+
if (!shape) return null;
|
|
5135
|
+
const size = getNodeSize(node, options);
|
|
5136
|
+
const origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;
|
|
5137
|
+
const { rings, fillRule } = shapeToPolygon(shape, size, { tolerance: options.tolerance });
|
|
5138
|
+
return {
|
|
5139
|
+
rings: rings.map((ring) => ring.map(([x, y]) => [x + origin.x, y + origin.y])),
|
|
5140
|
+
fillRule
|
|
5141
|
+
};
|
|
5142
|
+
}
|
|
5143
|
+
function shapeToBezierPath(shape, size) {
|
|
5144
|
+
const { width: w, height: h } = size;
|
|
5145
|
+
if (!(w > 0) || !(h > 0)) return { path: [], fillRule: "nonzero" };
|
|
5146
|
+
const svg = getShapeSvg({ ...shape, rotation: 0 }, size);
|
|
5147
|
+
let path = toBezierPath(svg.d);
|
|
5148
|
+
if (shape.kind === "path" && shape.viewBox) {
|
|
5149
|
+
const vb = shape.viewBox;
|
|
5150
|
+
if (vb.length === 4 && vb.every(Number.isFinite) && vb[2] > 0 && vb[3] > 0) {
|
|
5151
|
+
const sx = w / vb[2];
|
|
5152
|
+
const sy = h / vb[3];
|
|
5153
|
+
path = transformBezierPath(path, [sx, 0, 0, sy, -vb[0] * sx, -vb[1] * sy]);
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
const deg = shape.rotation ?? 0;
|
|
5157
|
+
if (deg && Number.isFinite(deg)) {
|
|
5158
|
+
const rad = deg * Math.PI / 180;
|
|
5159
|
+
const cos = Math.cos(rad);
|
|
5160
|
+
const sin = Math.sin(rad);
|
|
5161
|
+
const cx = w / 2;
|
|
5162
|
+
const cy = h / 2;
|
|
5163
|
+
const m = [cos, sin, -sin, cos, cx - cx * cos + cy * sin, cy - cx * sin - cy * cos];
|
|
5164
|
+
path = transformBezierPath(path, m);
|
|
5165
|
+
}
|
|
5166
|
+
return { path, fillRule: svg.fillRule };
|
|
5167
|
+
}
|
|
5168
|
+
function shapeNodeToBezierPath(node, lookup, options = {}) {
|
|
5169
|
+
const shape = getNodeShape(node);
|
|
5170
|
+
if (!shape) return null;
|
|
5171
|
+
const size = getNodeSize(node, options);
|
|
5172
|
+
const origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;
|
|
5173
|
+
const { path, fillRule } = shapeToBezierPath(shape, size);
|
|
5174
|
+
return { path: transformBezierPath(path, [1, 0, 0, 1, origin.x, origin.y]), fillRule };
|
|
5175
|
+
}
|
|
5176
|
+
var idCounter = 0;
|
|
5177
|
+
function defaultId2(index) {
|
|
5178
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
5179
|
+
idCounter += 1;
|
|
5180
|
+
return `shape-${Date.now().toString(36)}-${idCounter}-${index}`;
|
|
5181
|
+
}
|
|
5182
|
+
function stackOrder(nodes, ids) {
|
|
5183
|
+
return nodes.map((node, index) => ({ node, index, z: typeof node.zIndex === "number" ? node.zIndex : 0 })).filter((entry) => ids.has(entry.node.id)).sort((a, b) => a.z - b.z || a.index - b.index).map((entry) => entry.node);
|
|
5184
|
+
}
|
|
5185
|
+
function canCombineShapeNodes(nodes, ids) {
|
|
5186
|
+
const idSet = new Set(ids);
|
|
5187
|
+
const shapes = nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));
|
|
5188
|
+
if (shapes.some((n) => isLocked(n))) return { ok: false, reason: "locked" };
|
|
5189
|
+
if (shapes.length < 2) return { ok: false, reason: "too-few" };
|
|
5190
|
+
return { ok: true, shapeIds: shapes.map((n) => n.id) };
|
|
5191
|
+
}
|
|
5192
|
+
function combineShapeNodes(nodes, ids, operation, options = {}) {
|
|
5193
|
+
const idList = [...ids];
|
|
5194
|
+
const check = canCombineShapeNodes(nodes, idList);
|
|
5195
|
+
if (!check.ok) return { ok: false, operation, reason: check.reason };
|
|
5196
|
+
const lookup = createNodeLookup(nodes);
|
|
5197
|
+
const shapeIds = new Set(check.shapeIds);
|
|
5198
|
+
const ordered = stackOrder(nodes, shapeIds);
|
|
5199
|
+
const parentIds = new Set(ordered.map((n) => n.parentId ?? ""));
|
|
5200
|
+
const sharedParent = parentIds.size === 1 ? ordered[0].parentId : void 0;
|
|
5201
|
+
const parentOrigin = sharedParent && lookup.get(sharedParent) ? getAbsolutePosition(lookup.get(sharedParent), lookup) : { x: 0, y: 0 };
|
|
5202
|
+
const pickSource = () => {
|
|
5203
|
+
const style = options.styleFrom ?? "bottom";
|
|
5204
|
+
if (style === "top") return ordered[ordered.length - 1];
|
|
5205
|
+
if (style === "first") {
|
|
5206
|
+
const first = idList.find((id) => shapeIds.has(id));
|
|
5207
|
+
return first && lookup.get(first) || ordered[0];
|
|
5208
|
+
}
|
|
5209
|
+
if (typeof style === "object") return shapeIds.has(style.id) && lookup.get(style.id) || ordered[0];
|
|
5210
|
+
return ordered[0];
|
|
5211
|
+
};
|
|
5212
|
+
const createId = options.createId ?? defaultId2;
|
|
5213
|
+
const buildOptions = {
|
|
5214
|
+
precision: options.precision,
|
|
5215
|
+
nodeType: options.nodeType,
|
|
5216
|
+
select: options.select,
|
|
5217
|
+
parentOrigin,
|
|
5218
|
+
sharedParent
|
|
5219
|
+
};
|
|
5220
|
+
const engine = options.engine ?? (options.curves === "lines" ? "polygon" : "curve");
|
|
5221
|
+
const created = [];
|
|
5222
|
+
let report = { engine: "curve" };
|
|
5223
|
+
if (engine === "curve") {
|
|
5224
|
+
const operands = ordered.map((node) => shapeNodeToBezierPath(node, lookup, options));
|
|
5225
|
+
const pathOptions = { epsilon: options.epsilon, onFallback: options.onFallback, fallbackTolerance: options.tolerance };
|
|
5226
|
+
if (operation === "divide") {
|
|
5227
|
+
const result = dividePaths(operands, pathOptions);
|
|
5228
|
+
report = result;
|
|
5229
|
+
for (const piece of result.pieces) {
|
|
5230
|
+
const source = ordered[piece.operands[piece.operands.length - 1]];
|
|
5231
|
+
const node = buildPathNode(piece.path, source, createId(created.length), buildOptions);
|
|
5232
|
+
if (node) created.push(node);
|
|
5233
|
+
}
|
|
5234
|
+
} else {
|
|
5235
|
+
const result = pathBoolean(BOOLEAN_FOR[operation], operands, pathOptions);
|
|
5236
|
+
report = result;
|
|
5237
|
+
const node = buildPathNode(result.path, pickSource(), createId(0), buildOptions);
|
|
5238
|
+
if (node) created.push(node);
|
|
5239
|
+
}
|
|
5240
|
+
} else {
|
|
5241
|
+
report = { engine: "polygon" };
|
|
5242
|
+
const operands = ordered.map((node) => shapeNodeToPolygon(node, lookup, options));
|
|
5243
|
+
const write = (polygon) => toBezierPath(
|
|
5244
|
+
polygonToSvgPath(polygon, {
|
|
5245
|
+
precision: 9,
|
|
5246
|
+
simplify: options.simplify,
|
|
5247
|
+
curves: options.curves ?? "fit",
|
|
5248
|
+
curveTolerance: options.curveTolerance ?? options.tolerance ?? 0.25,
|
|
5249
|
+
cornerAngleDeg: options.cornerAngleDeg
|
|
5250
|
+
})
|
|
5251
|
+
);
|
|
5252
|
+
if (operation === "divide") {
|
|
5253
|
+
for (const piece of dividePolygons(operands, { epsilon: options.epsilon })) {
|
|
5254
|
+
const source = ordered[piece.operands[piece.operands.length - 1]];
|
|
5255
|
+
const node = buildPathNode(write([piece.polygon]), source, createId(created.length), buildOptions);
|
|
5256
|
+
if (node) created.push(node);
|
|
5257
|
+
}
|
|
5258
|
+
} else {
|
|
5259
|
+
const result = polygonBoolean(BOOLEAN_FOR[operation], operands, { epsilon: options.epsilon });
|
|
5260
|
+
const node = buildPathNode(write(result), pickSource(), createId(0), buildOptions);
|
|
5261
|
+
if (node) created.push(node);
|
|
5262
|
+
}
|
|
5263
|
+
}
|
|
5264
|
+
if (created.length === 0) return { ok: false, operation, reason: "empty" };
|
|
5265
|
+
const done = {
|
|
5266
|
+
ok: true,
|
|
5267
|
+
operation,
|
|
5268
|
+
removeIds: ordered.map((n) => n.id),
|
|
5269
|
+
nodes: created,
|
|
5270
|
+
engine: report.engine
|
|
5271
|
+
};
|
|
5272
|
+
if (report.fallbackReason) done.fallbackReason = report.fallbackReason;
|
|
5273
|
+
return done;
|
|
5274
|
+
}
|
|
5275
|
+
function buildPathNode(path, source, id, options) {
|
|
5276
|
+
const bounds = bezierPathBounds(path);
|
|
5277
|
+
if (!bounds) return null;
|
|
5278
|
+
const width = bounds.maxX - bounds.minX;
|
|
5279
|
+
const height = bounds.maxY - bounds.minY;
|
|
5280
|
+
if (!(width > 0) || !(height > 0)) return null;
|
|
5281
|
+
const d = bezierPathToSvg(path, { precision: options.precision ?? 3, offset: { x: -bounds.minX, y: -bounds.minY } });
|
|
5282
|
+
if (!d) return null;
|
|
5283
|
+
const sourceShape = getNodeShape(source);
|
|
5284
|
+
const shape = {
|
|
5285
|
+
kind: "path",
|
|
5286
|
+
d,
|
|
5287
|
+
viewBox: [0, 0, roundSize(width), roundSize(height)],
|
|
5288
|
+
fillRule: "nonzero"
|
|
5289
|
+
};
|
|
5290
|
+
for (const key of ["fill", "stroke", "strokeWidth", "opacity"]) {
|
|
5291
|
+
if (sourceShape[key] !== void 0) shape[key] = sourceShape[key];
|
|
5292
|
+
}
|
|
5293
|
+
for (const [key, value] of Object.entries(options.paint ?? {})) {
|
|
5294
|
+
if (value === null) delete shape[key];
|
|
5295
|
+
else if (value !== void 0) shape[key] = value;
|
|
5296
|
+
}
|
|
5297
|
+
const {
|
|
5298
|
+
[LOCKED_DATA_KEY]: _locked,
|
|
5299
|
+
[LOCKED_PREVIOUS_FLAGS_KEY]: _previous,
|
|
5300
|
+
...data
|
|
5301
|
+
} = source.data ?? {};
|
|
5302
|
+
const {
|
|
5303
|
+
measured: _measured,
|
|
5304
|
+
selected: _selected,
|
|
5305
|
+
dragging: _dragging,
|
|
5306
|
+
positionAbsolute: _positionAbsolute,
|
|
5307
|
+
internals: _internals,
|
|
5308
|
+
parentId: _parentId,
|
|
5309
|
+
...rest
|
|
5310
|
+
} = source;
|
|
5311
|
+
const node = {
|
|
5312
|
+
...rest,
|
|
5313
|
+
id,
|
|
5314
|
+
position: { x: bounds.minX - options.parentOrigin.x, y: bounds.minY - options.parentOrigin.y },
|
|
5315
|
+
width,
|
|
5316
|
+
height,
|
|
5317
|
+
data: { ...data, [SHAPE_DATA_KEY]: shape }
|
|
5318
|
+
};
|
|
5319
|
+
if (options.sharedParent) node.parentId = options.sharedParent;
|
|
5320
|
+
if (options.nodeType !== void 0) node.type = options.nodeType;
|
|
5321
|
+
if (source.style) node.style = withStyleSize(source.style, { width, height });
|
|
5322
|
+
if (options.select !== false) node.selected = true;
|
|
5323
|
+
return node;
|
|
5324
|
+
}
|
|
5325
|
+
function roundSize(value) {
|
|
5326
|
+
return Math.round(value * 1e3) / 1e3;
|
|
5327
|
+
}
|
|
5328
|
+
function applyShapeCombination(nodes, change, options = {}) {
|
|
5329
|
+
const remove = new Set(change.removeIds);
|
|
5330
|
+
const insertAt = nodes.findIndex((n) => remove.has(n.id));
|
|
5331
|
+
const deselect = options.deselect !== false && change.nodes.some((n) => n.selected);
|
|
5332
|
+
const out = [];
|
|
5333
|
+
nodes.forEach((node, index) => {
|
|
5334
|
+
if (index === insertAt) out.push(...change.nodes);
|
|
5335
|
+
if (remove.has(node.id)) return;
|
|
5336
|
+
out.push(deselect && node.selected ? { ...node, selected: false } : node);
|
|
5337
|
+
});
|
|
5338
|
+
if (insertAt === -1) out.push(...change.nodes);
|
|
5339
|
+
return out;
|
|
5340
|
+
}
|
|
5341
|
+
function revertShapeCombination(nodes, change, previous) {
|
|
5342
|
+
const created = new Set(change.nodes.map((n) => n.id));
|
|
5343
|
+
const removed = new Set(change.removeIds);
|
|
5344
|
+
const out = nodes.filter((n) => !created.has(n.id));
|
|
5345
|
+
previous.forEach((node, index) => {
|
|
5346
|
+
if (!removed.has(node.id) || out.some((n) => n.id === node.id)) return;
|
|
5347
|
+
out.splice(Math.min(index, out.length), 0, node);
|
|
5348
|
+
});
|
|
5349
|
+
return out;
|
|
5350
|
+
}
|
|
5351
|
+
var INVISIBLE_PAINT = /* @__PURE__ */ new Set(["none", "transparent"]);
|
|
5352
|
+
function hasVisibleStroke(shape) {
|
|
5353
|
+
if (!shape || typeof shape.stroke !== "string" || INVISIBLE_PAINT.has(shape.stroke.trim().toLowerCase())) return false;
|
|
5354
|
+
const width = shape.strokeWidth ?? 1;
|
|
5355
|
+
return Number.isFinite(width) && width > 0;
|
|
5356
|
+
}
|
|
5357
|
+
function hasVisibleFill(shape) {
|
|
5358
|
+
return shape.fill === void 0 || !INVISIBLE_PAINT.has(String(shape.fill).trim().toLowerCase());
|
|
5359
|
+
}
|
|
5360
|
+
function shapeTargets(nodes, ids) {
|
|
5361
|
+
const idSet = new Set(ids);
|
|
5362
|
+
return nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));
|
|
5363
|
+
}
|
|
5364
|
+
function canOffsetShapeNodes(nodes, ids) {
|
|
5365
|
+
const shapes = shapeTargets(nodes, ids);
|
|
5366
|
+
if (shapes.some((n) => isLocked(n))) return { ok: false, reason: "locked" };
|
|
5367
|
+
if (shapes.length === 0) return { ok: false, reason: "no-shapes" };
|
|
5368
|
+
return { ok: true, shapeIds: shapes.map((n) => n.id) };
|
|
5369
|
+
}
|
|
5370
|
+
function canOutlineShapeNodeStrokes(nodes, ids) {
|
|
5371
|
+
const shapes = shapeTargets(nodes, ids);
|
|
5372
|
+
if (shapes.some((n) => isLocked(n))) return { ok: false, reason: "locked" };
|
|
5373
|
+
if (shapes.length === 0) return { ok: false, reason: "no-shapes" };
|
|
5374
|
+
const stroked = shapes.filter((n) => hasVisibleStroke(getNodeShape(n)));
|
|
5375
|
+
if (stroked.length === 0) return { ok: false, reason: "no-stroke" };
|
|
5376
|
+
return { ok: true, shapeIds: stroked.map((n) => n.id) };
|
|
5377
|
+
}
|
|
5378
|
+
function editPlacement(node, lookup) {
|
|
5379
|
+
const parent = node.parentId ? lookup.get(node.parentId) : void 0;
|
|
5380
|
+
return {
|
|
5381
|
+
parentOrigin: parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 },
|
|
5382
|
+
sharedParent: parent ? node.parentId : void 0
|
|
5383
|
+
};
|
|
5384
|
+
}
|
|
5385
|
+
function finishEdit(operation, replacements) {
|
|
5386
|
+
if (replacements.length === 0) return { ok: false, operation, reason: "empty" };
|
|
5387
|
+
return {
|
|
5388
|
+
ok: true,
|
|
5389
|
+
operation,
|
|
5390
|
+
removeIds: replacements.map((r) => r.id),
|
|
5391
|
+
nodes: replacements.flatMap((r) => r.nodes),
|
|
5392
|
+
replacements
|
|
5393
|
+
};
|
|
5394
|
+
}
|
|
5395
|
+
function offsetShapeNodes(nodes, ids, distance, options = {}) {
|
|
5396
|
+
const operation = "offset";
|
|
5397
|
+
if (!Number.isFinite(distance)) return { ok: false, operation, reason: "invalid" };
|
|
5398
|
+
const check = canOffsetShapeNodes(nodes, ids);
|
|
5399
|
+
if (!check.ok) return { ok: false, operation, reason: check.reason };
|
|
5400
|
+
const lookup = createNodeLookup(nodes);
|
|
5401
|
+
const createId = options.createId ?? defaultId2;
|
|
5402
|
+
let index = 0;
|
|
5403
|
+
const replacements = [];
|
|
5404
|
+
for (const id of check.shapeIds) {
|
|
5405
|
+
const node = lookup.get(id);
|
|
5406
|
+
const outline = shapeNodeToBezierPath(node, lookup, options);
|
|
5407
|
+
const path = offsetPath(outline.path, distance, {
|
|
5408
|
+
join: options.join,
|
|
5409
|
+
miterLimit: options.miterLimit,
|
|
5410
|
+
fillRule: outline.fillRule,
|
|
5411
|
+
tolerance: options.tolerance,
|
|
5412
|
+
epsilon: options.epsilon,
|
|
5413
|
+
onFallback: options.onFallback
|
|
5414
|
+
});
|
|
5415
|
+
const created = buildPathNode(path, node, createId(index), {
|
|
5416
|
+
...editPlacement(node, lookup),
|
|
5417
|
+
precision: options.precision,
|
|
5418
|
+
nodeType: options.nodeType,
|
|
5419
|
+
select: options.select
|
|
5420
|
+
});
|
|
5421
|
+
if (!created) continue;
|
|
5422
|
+
index++;
|
|
5423
|
+
const original = { ...node, selected: false };
|
|
5424
|
+
replacements.push({ id, nodes: options.keepOriginal ? [created, original] : [created] });
|
|
5425
|
+
}
|
|
5426
|
+
return finishEdit(operation, replacements);
|
|
5427
|
+
}
|
|
5428
|
+
function outlineShapeNodeStrokes(nodes, ids, options = {}) {
|
|
5429
|
+
const operation = "outlineStroke";
|
|
5430
|
+
if (options.width !== void 0 && !(Number.isFinite(options.width) && options.width > 0)) {
|
|
5431
|
+
return { ok: false, operation, reason: "invalid" };
|
|
5432
|
+
}
|
|
5433
|
+
const check = canOutlineShapeNodeStrokes(nodes, ids);
|
|
5434
|
+
if (!check.ok) return { ok: false, operation, reason: check.reason };
|
|
5435
|
+
const lookup = createNodeLookup(nodes);
|
|
5436
|
+
const createId = options.createId ?? defaultId2;
|
|
5437
|
+
let index = 0;
|
|
5438
|
+
const replacements = [];
|
|
5439
|
+
for (const id of check.shapeIds) {
|
|
5440
|
+
const node = lookup.get(id);
|
|
5441
|
+
const shape = getNodeShape(node);
|
|
5442
|
+
const outline = shapeNodeToBezierPath(node, lookup, options);
|
|
5443
|
+
const path = outlineStroke(outline.path, {
|
|
5444
|
+
width: options.width ?? shape.strokeWidth ?? 1,
|
|
5445
|
+
join: options.join,
|
|
5446
|
+
cap: options.cap,
|
|
5447
|
+
miterLimit: options.miterLimit,
|
|
5448
|
+
dash: options.dash,
|
|
5449
|
+
dashOffset: options.dashOffset,
|
|
5450
|
+
tolerance: options.tolerance,
|
|
5451
|
+
epsilon: options.epsilon,
|
|
5452
|
+
onFallback: options.onFallback
|
|
5453
|
+
});
|
|
5454
|
+
const created = buildPathNode(path, node, createId(index), {
|
|
5455
|
+
...editPlacement(node, lookup),
|
|
5456
|
+
precision: options.precision,
|
|
5457
|
+
nodeType: options.nodeType,
|
|
5458
|
+
select: options.select,
|
|
5459
|
+
paint: { fill: shape.stroke, stroke: null, strokeWidth: null }
|
|
5460
|
+
});
|
|
5461
|
+
if (!created) continue;
|
|
5462
|
+
index++;
|
|
5463
|
+
const replacement = [];
|
|
5464
|
+
if (options.keepFill !== false && hasVisibleFill(shape)) {
|
|
5465
|
+
const { stroke: _stroke, strokeWidth: _width, ...rest } = shape;
|
|
5466
|
+
replacement.push({
|
|
5467
|
+
...node,
|
|
5468
|
+
selected: options.select === false ? node.selected : false,
|
|
5469
|
+
data: { ...node.data, [SHAPE_DATA_KEY]: rest }
|
|
5470
|
+
});
|
|
5471
|
+
}
|
|
5472
|
+
replacement.push(created);
|
|
5473
|
+
replacements.push({ id, nodes: replacement });
|
|
5474
|
+
}
|
|
5475
|
+
return finishEdit(operation, replacements);
|
|
5476
|
+
}
|
|
5477
|
+
function applyShapeReplacement(nodes, change, options = {}) {
|
|
5478
|
+
const byId = new Map(change.replacements.map((r) => [r.id, r.nodes]));
|
|
5479
|
+
const created = new Set(change.replacements.flatMap((r) => r.nodes.map((n) => n.id)));
|
|
5480
|
+
const deselect = options.deselect !== false && change.replacements.some((r) => r.nodes.some((n) => n.selected));
|
|
5481
|
+
const out = [];
|
|
5482
|
+
for (const node of nodes) {
|
|
5483
|
+
const replacement = byId.get(node.id);
|
|
5484
|
+
if (replacement) {
|
|
5485
|
+
out.push(...replacement);
|
|
5486
|
+
continue;
|
|
5487
|
+
}
|
|
5488
|
+
if (created.has(node.id)) continue;
|
|
5489
|
+
out.push(deselect && node.selected ? { ...node, selected: false } : node);
|
|
5490
|
+
}
|
|
5491
|
+
return out;
|
|
5492
|
+
}
|
|
5493
|
+
|
|
5494
|
+
// src/canvas/resizeSnap.ts
|
|
5495
|
+
var RESIZE_HANDLES = [
|
|
5496
|
+
"top-left",
|
|
5497
|
+
"top",
|
|
5498
|
+
"top-right",
|
|
5499
|
+
"right",
|
|
5500
|
+
"bottom-right",
|
|
5501
|
+
"bottom",
|
|
5502
|
+
"bottom-left",
|
|
5503
|
+
"left"
|
|
5504
|
+
];
|
|
5505
|
+
function resizeHandleAxes(handle) {
|
|
5506
|
+
const x = handle.includes("left") ? -1 : handle.includes("right") ? 1 : 0;
|
|
5507
|
+
const y = handle.includes("top") ? -1 : handle.includes("bottom") ? 1 : 0;
|
|
5508
|
+
return { x, y };
|
|
5509
|
+
}
|
|
5510
|
+
function inferResizeHandle(start, next, eps = 1e-6) {
|
|
5511
|
+
const moved = (a, b) => Math.abs(a - b) > eps;
|
|
5512
|
+
const left = moved(start.x, next.x);
|
|
5513
|
+
const right = moved(start.x + start.width, next.x + next.width);
|
|
5514
|
+
const top = moved(start.y, next.y);
|
|
5515
|
+
const bottom = moved(start.y + start.height, next.y + next.height);
|
|
5516
|
+
const h = left ? "left" : right ? "right" : "";
|
|
5517
|
+
const v = top ? "top" : bottom ? "bottom" : "";
|
|
5518
|
+
if (h && v) return `${v}-${h}`;
|
|
5519
|
+
return h || v || null;
|
|
5520
|
+
}
|
|
5521
|
+
function within(axis, v, tolerance) {
|
|
5522
|
+
const { values } = axis;
|
|
5523
|
+
let lo = 0;
|
|
5524
|
+
let hi = values.length;
|
|
5525
|
+
while (lo < hi) {
|
|
5526
|
+
const mid = lo + hi >>> 1;
|
|
5527
|
+
if (values[mid] < v - tolerance) lo = mid + 1;
|
|
5528
|
+
else hi = mid;
|
|
5529
|
+
}
|
|
5530
|
+
const out = [];
|
|
5531
|
+
for (let i = lo; i < values.length && values[i] <= v + tolerance; i++) out.push(values[i]);
|
|
5532
|
+
return out;
|
|
5533
|
+
}
|
|
5534
|
+
function matching(axis, v, eps) {
|
|
5535
|
+
const { values, rects } = axis;
|
|
5536
|
+
const out = [];
|
|
5537
|
+
for (let i = 0; i < values.length; i++) if (Math.abs(values[i] - v) <= eps) out.push(rects[i]);
|
|
5538
|
+
return out;
|
|
5539
|
+
}
|
|
5540
|
+
function snapResize(rect, handle, targets, options = {}) {
|
|
5541
|
+
const index = Array.isArray(targets) ? createGuideIndex(targets) : targets;
|
|
5542
|
+
const zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;
|
|
5543
|
+
const tolerance = (options.threshold ?? 6) / zoom;
|
|
5544
|
+
const eps = 0.5 / zoom;
|
|
5545
|
+
const useEdges = options.edges !== false;
|
|
5546
|
+
const useCentres = options.centres !== false;
|
|
5547
|
+
const grid = options.gridSize ?? 0;
|
|
5548
|
+
const axes = resizeHandleAxes(handle);
|
|
5549
|
+
const unchanged = { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false };
|
|
5550
|
+
if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) return unchanged;
|
|
5551
|
+
const x0 = rect.x;
|
|
5552
|
+
const x1 = rect.x + rect.width;
|
|
5553
|
+
const y0 = rect.y;
|
|
5554
|
+
const y1 = rect.y + rect.height;
|
|
5555
|
+
const bestHit = (horizontal) => {
|
|
5556
|
+
const dir = horizontal ? axes.x : axes.y;
|
|
5557
|
+
if (dir === 0) return null;
|
|
5558
|
+
const moving = horizontal ? dir < 0 ? x0 : x1 : dir < 0 ? y0 : y1;
|
|
5559
|
+
const fixed = horizontal ? dir < 0 ? x1 : x0 : dir < 0 ? y1 : y0;
|
|
5560
|
+
const min = (horizontal ? options.minWidth : options.minHeight) ?? 0;
|
|
5561
|
+
const valid = (t) => {
|
|
5562
|
+
const size = dir < 0 ? fixed - t : t - fixed;
|
|
5563
|
+
return Number.isFinite(size) && size > 1e-9 && size >= min;
|
|
5564
|
+
};
|
|
5565
|
+
let best = null;
|
|
5566
|
+
const consider = (values, kind) => {
|
|
5567
|
+
for (const t of values) {
|
|
5568
|
+
if (!valid(t)) continue;
|
|
5569
|
+
if (!best || Math.abs(t - moving) < Math.abs(best.value - moving)) best = { value: t, kind };
|
|
5570
|
+
}
|
|
5571
|
+
};
|
|
5572
|
+
if (useEdges) consider(within(horizontal ? index.edgeX : index.edgeY, moving, tolerance), "edge");
|
|
5573
|
+
if (useCentres) consider(within(horizontal ? index.centreX : index.centreY, moving, tolerance), "centre");
|
|
5574
|
+
if (!best && grid > 0) {
|
|
5575
|
+
const origin = horizontal ? options.gridOrigin?.x ?? 0 : options.gridOrigin?.y ?? 0;
|
|
5576
|
+
consider([snapToGrid(moving, grid, origin)], "grid");
|
|
5577
|
+
}
|
|
5578
|
+
return best;
|
|
3286
5579
|
};
|
|
3287
5580
|
let nx0 = x0;
|
|
3288
5581
|
let nx1 = x1;
|
|
@@ -3301,8 +5594,8 @@ function snapResize(rect, handle, targets, options = {}) {
|
|
|
3301
5594
|
const ratio = rect.height > 0 ? rect.width / rect.height : 0;
|
|
3302
5595
|
if (options.keepAspectRatio && ratio > 0 && (hitX || hitY)) {
|
|
3303
5596
|
if (axes.x !== 0 && axes.y !== 0) {
|
|
3304
|
-
const
|
|
3305
|
-
const useX =
|
|
5597
|
+
const dist3 = (h, horizontal) => h ? Math.abs(h.value - (horizontal ? axes.x < 0 ? x0 : x1 : axes.y < 0 ? y0 : y1)) : Infinity;
|
|
5598
|
+
const useX = dist3(hitX, true) <= dist3(hitY, false);
|
|
3306
5599
|
if (useX && hitX) {
|
|
3307
5600
|
setEdge(true, hitX.value);
|
|
3308
5601
|
const height = (nx1 - nx0) / ratio;
|
|
@@ -3359,33 +5652,915 @@ function snapResize(rect, handle, targets, options = {}) {
|
|
|
3359
5652
|
changed
|
|
3360
5653
|
};
|
|
3361
5654
|
}
|
|
3362
|
-
function snapNodeResize(nodes, id, rect, handle, options = {}) {
|
|
3363
|
-
const lookup = createNodeLookup(nodes);
|
|
3364
|
-
const node = lookup.get(id);
|
|
3365
|
-
if (!node || isLocked(node)) {
|
|
3366
|
-
return { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false, updates: [] };
|
|
5655
|
+
function snapNodeResize(nodes, id, rect, handle, options = {}) {
|
|
5656
|
+
const lookup = createNodeLookup(nodes);
|
|
5657
|
+
const node = lookup.get(id);
|
|
5658
|
+
if (!node || isLocked(node)) {
|
|
5659
|
+
return { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false, updates: [] };
|
|
5660
|
+
}
|
|
5661
|
+
const index = options.index ?? createNodeGuideIndex(nodes, [id], options);
|
|
5662
|
+
const result = snapResize(rect, handle, index, options);
|
|
5663
|
+
if (!result.changed) return { ...result, updates: [] };
|
|
5664
|
+
const parent = node.parentId ? lookup.get(node.parentId) : void 0;
|
|
5665
|
+
const origin = parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 };
|
|
5666
|
+
return {
|
|
5667
|
+
...result,
|
|
5668
|
+
updates: [
|
|
5669
|
+
{
|
|
5670
|
+
id,
|
|
5671
|
+
position: { x: result.rect.x - origin.x, y: result.rect.y - origin.y },
|
|
5672
|
+
width: result.rect.width,
|
|
5673
|
+
height: result.rect.height
|
|
5674
|
+
}
|
|
5675
|
+
]
|
|
5676
|
+
};
|
|
5677
|
+
}
|
|
5678
|
+
function getResizingNodeRect(node, lookup, options = {}) {
|
|
5679
|
+
const { x, y } = node.parentId ? getAbsolutePosition(node, lookup) : node.position;
|
|
5680
|
+
const size = getNodeSize(node, options);
|
|
5681
|
+
return { x, y, width: node.width ?? size.width, height: node.height ?? size.height };
|
|
5682
|
+
}
|
|
5683
|
+
|
|
5684
|
+
// src/canvas/autoLayout.ts
|
|
5685
|
+
var AUTO_LAYOUT_DATA_KEY = "layout";
|
|
5686
|
+
var CHILD_LAYOUT_DATA_KEY = "childLayout";
|
|
5687
|
+
var MODES = /* @__PURE__ */ new Set(["horizontal", "vertical", "grid"]);
|
|
5688
|
+
var EPS = 1e-6;
|
|
5689
|
+
var MAX_TRACKS = 1e3;
|
|
5690
|
+
var MAX_GRID_CELLS = 1e6;
|
|
5691
|
+
function clampLayoutSize(value, min, max) {
|
|
5692
|
+
let v = Number.isFinite(value) ? Math.max(value, 0) : 0;
|
|
5693
|
+
if (typeof max === "number" && Number.isFinite(max)) v = Math.min(v, max);
|
|
5694
|
+
if (typeof min === "number" && Number.isFinite(min)) v = Math.max(v, min);
|
|
5695
|
+
return v;
|
|
5696
|
+
}
|
|
5697
|
+
var finite = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
5698
|
+
function getAutoLayout(node) {
|
|
5699
|
+
const layout2 = node?.data?.[AUTO_LAYOUT_DATA_KEY];
|
|
5700
|
+
return layout2 && typeof layout2 === "object" && MODES.has(layout2.mode) ? layout2 : void 0;
|
|
5701
|
+
}
|
|
5702
|
+
function isAutoLayoutFrame(node) {
|
|
5703
|
+
return getAutoLayout(node) !== void 0;
|
|
5704
|
+
}
|
|
5705
|
+
function getChildLayout(node) {
|
|
5706
|
+
const child = node?.data?.[CHILD_LAYOUT_DATA_KEY];
|
|
5707
|
+
return child && typeof child === "object" ? child : {};
|
|
5708
|
+
}
|
|
5709
|
+
function getLayoutSizing(node, axis) {
|
|
5710
|
+
const value = getChildLayout(node).sizing?.[axis] ?? getAutoLayout(node)?.sizing?.[axis];
|
|
5711
|
+
return value === "hug" || value === "fill" ? value : "fixed";
|
|
5712
|
+
}
|
|
5713
|
+
function limitsOf(node) {
|
|
5714
|
+
const child = getChildLayout(node);
|
|
5715
|
+
const layout2 = getAutoLayout(node);
|
|
5716
|
+
const min = child.min ?? layout2?.min ?? {};
|
|
5717
|
+
const max = child.max ?? layout2?.max ?? {};
|
|
5718
|
+
return { minW: min.width, maxW: max.width, minH: min.height, maxH: max.height };
|
|
5719
|
+
}
|
|
5720
|
+
function resolvePadding(padding) {
|
|
5721
|
+
let sides;
|
|
5722
|
+
if (typeof padding === "number") sides = [padding, padding, padding, padding];
|
|
5723
|
+
else if (Array.isArray(padding)) {
|
|
5724
|
+
sides = padding.length === 2 ? [padding[0], padding[1], padding[0], padding[1]] : [padding[0], padding[1], padding[2], padding[3]];
|
|
5725
|
+
} else if (padding && typeof padding === "object") sides = [padding.top, padding.right, padding.bottom, padding.left];
|
|
5726
|
+
else sides = [0, 0, 0, 0];
|
|
5727
|
+
return sides.map((p) => Math.max(finite(p), 0));
|
|
5728
|
+
}
|
|
5729
|
+
var LayoutState = class {
|
|
5730
|
+
boxes = /* @__PURE__ */ new Map();
|
|
5731
|
+
measure;
|
|
5732
|
+
visiting = /* @__PURE__ */ new Set();
|
|
5733
|
+
original = /* @__PURE__ */ new Map();
|
|
5734
|
+
constructor(nodes, options = {}) {
|
|
5735
|
+
this.measure = options.measure ?? {};
|
|
5736
|
+
for (const node of nodes) {
|
|
5737
|
+
const size = this.measure.size?.(node) ?? getNodeSize(node, options.rectOptions);
|
|
5738
|
+
const box = {
|
|
5739
|
+
id: node.id,
|
|
5740
|
+
node,
|
|
5741
|
+
parent: node.parentId,
|
|
5742
|
+
x: finite(node.position?.x),
|
|
5743
|
+
y: finite(node.position?.y),
|
|
5744
|
+
w: finite(size.width),
|
|
5745
|
+
h: finite(size.height),
|
|
5746
|
+
kids: []
|
|
5747
|
+
};
|
|
5748
|
+
this.boxes.set(node.id, box);
|
|
5749
|
+
this.original.set(node.id, { x: box.x, y: box.y, w: box.w, h: box.h });
|
|
5750
|
+
}
|
|
5751
|
+
for (const node of nodes) {
|
|
5752
|
+
if (node.parentId && node.parentId !== node.id) this.boxes.get(node.parentId)?.kids.push(node.id);
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
updates() {
|
|
5756
|
+
const out = [];
|
|
5757
|
+
for (const [id, box] of this.boxes) {
|
|
5758
|
+
const o = this.original.get(id);
|
|
5759
|
+
const update = { id };
|
|
5760
|
+
if (Math.abs(box.x - o.x) > EPS || Math.abs(box.y - o.y) > EPS) update.position = { x: box.x, y: box.y };
|
|
5761
|
+
if (Math.abs(box.w - o.w) > EPS) update.width = box.w;
|
|
5762
|
+
if (Math.abs(box.h - o.h) > EPS) update.height = box.h;
|
|
5763
|
+
if (update.position || update.width !== void 0 || update.height !== void 0) out.push(update);
|
|
5764
|
+
}
|
|
5765
|
+
return out;
|
|
5766
|
+
}
|
|
5767
|
+
/** Runs `fn` for `id` unless it is already on the stack (guards `parentId` cycles). */
|
|
5768
|
+
guard(id, fn) {
|
|
5769
|
+
if (this.visiting.has(id)) return;
|
|
5770
|
+
this.visiting.add(id);
|
|
5771
|
+
try {
|
|
5772
|
+
fn();
|
|
5773
|
+
} finally {
|
|
5774
|
+
this.visiting.delete(id);
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
ancestors(id) {
|
|
5778
|
+
const out = [];
|
|
5779
|
+
const seen = /* @__PURE__ */ new Set([id]);
|
|
5780
|
+
let parent = this.boxes.get(id)?.parent;
|
|
5781
|
+
while (parent && !seen.has(parent) && this.boxes.has(parent)) {
|
|
5782
|
+
out.push(parent);
|
|
5783
|
+
seen.add(parent);
|
|
5784
|
+
parent = this.boxes.get(parent).parent;
|
|
5785
|
+
}
|
|
5786
|
+
return out;
|
|
5787
|
+
}
|
|
5788
|
+
};
|
|
5789
|
+
function setSize(s, id, w, h) {
|
|
5790
|
+
const b = s.boxes.get(id);
|
|
5791
|
+
if (!b) return;
|
|
5792
|
+
b.w = Number.isFinite(w) ? Math.max(w, 0) : b.w;
|
|
5793
|
+
b.h = Number.isFinite(h) ? Math.max(h, 0) : b.h;
|
|
5794
|
+
}
|
|
5795
|
+
function place(s, id, x, y) {
|
|
5796
|
+
const b = s.boxes.get(id);
|
|
5797
|
+
if (!b) return;
|
|
5798
|
+
b.x = x;
|
|
5799
|
+
b.y = y;
|
|
5800
|
+
}
|
|
5801
|
+
var isText = (s, b) => !!s.measure.textSize && s.measure.textSize(b.node) !== void 0;
|
|
5802
|
+
function flowChildren(s, frame) {
|
|
5803
|
+
const f = s.boxes.get(frame);
|
|
5804
|
+
if (!f) return [];
|
|
5805
|
+
return f.kids.filter((k) => {
|
|
5806
|
+
const n = s.boxes.get(k).node;
|
|
5807
|
+
return n.hidden !== true && !getChildLayout(n).absolute;
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5810
|
+
function fitHugText(s, id) {
|
|
5811
|
+
const b = s.boxes.get(id);
|
|
5812
|
+
if (!b || !isText(s, b)) return;
|
|
5813
|
+
const natural = s.measure.textSize(b.node);
|
|
5814
|
+
const { minW, maxW } = limitsOf(b.node);
|
|
5815
|
+
if (getLayoutSizing(b.node, "width") === "hug") {
|
|
5816
|
+
const w = clampLayoutSize(natural.width, minW, maxW);
|
|
5817
|
+
const limited = Math.abs(w - natural.width) > EPS;
|
|
5818
|
+
const h = limited ? s.measure.textHeight?.(b.node, w) ?? natural.height : natural.height;
|
|
5819
|
+
setSize(s, id, w, h);
|
|
5820
|
+
} else if (getLayoutSizing(b.node, "height") === "hug") {
|
|
5821
|
+
setSize(s, id, b.w, s.measure.textHeight?.(b.node, b.w) ?? b.h);
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
function enforceLimits(s, id) {
|
|
5825
|
+
const b = s.boxes.get(id);
|
|
5826
|
+
if (!b) return;
|
|
5827
|
+
const { minW, maxW, minH, maxH } = limitsOf(b.node);
|
|
5828
|
+
if (minW === void 0 && maxW === void 0 && minH === void 0 && maxH === void 0) return;
|
|
5829
|
+
const nw = clampLayoutSize(b.w, minW, maxW);
|
|
5830
|
+
const nh = clampLayoutSize(b.h, minH, maxH);
|
|
5831
|
+
if (Math.abs(nw - b.w) < 1e-9 && Math.abs(nh - b.h) < 1e-9) return;
|
|
5832
|
+
if (isText(s, b)) setSize(s, id, nw, nh);
|
|
5833
|
+
else resizeChild(s, id, nw, nh);
|
|
5834
|
+
}
|
|
5835
|
+
function itemBaseline(s, id, depth) {
|
|
5836
|
+
const b = s.boxes.get(id);
|
|
5837
|
+
if (!b) return void 0;
|
|
5838
|
+
if (isText(s, b)) {
|
|
5839
|
+
const v = s.measure.textBaseline?.(b.node);
|
|
5840
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
5841
|
+
}
|
|
5842
|
+
const layout2 = getAutoLayout(b.node);
|
|
5843
|
+
if (!layout2 || layout2.mode === "grid" || depth >= 32) return void 0;
|
|
5844
|
+
for (const k of flowChildren(s, id)) {
|
|
5845
|
+
const base = itemBaseline(s, k, depth + 1);
|
|
5846
|
+
if (base !== void 0) return s.boxes.get(k).y + base;
|
|
5847
|
+
}
|
|
5848
|
+
return void 0;
|
|
5849
|
+
}
|
|
5850
|
+
function resizeChild(s, id, w, h) {
|
|
5851
|
+
const b = s.boxes.get(id);
|
|
5852
|
+
if (!b) return;
|
|
5853
|
+
if (isText(s, b) && getLayoutSizing(b.node, "height") === "hug") {
|
|
5854
|
+
setSize(s, id, w, s.measure.textHeight?.(b.node, w) ?? h);
|
|
5855
|
+
return;
|
|
5856
|
+
}
|
|
5857
|
+
const ow = b.w;
|
|
5858
|
+
const oh = b.h;
|
|
5859
|
+
setSize(s, id, w, h);
|
|
5860
|
+
if (b.kids.length > 0) constrain(s, id, ow, oh);
|
|
5861
|
+
if (getAutoLayout(b.node)) layout(s, id);
|
|
5862
|
+
}
|
|
5863
|
+
function mainOffsets(align, inner, total, n, gap) {
|
|
5864
|
+
const gaps = gap * Math.max(n - 1, 0);
|
|
5865
|
+
const free = inner - total;
|
|
5866
|
+
const k = Math.max(n, 1);
|
|
5867
|
+
switch (align) {
|
|
5868
|
+
case "center":
|
|
5869
|
+
return [(free - gaps) / 2, gap];
|
|
5870
|
+
case "end":
|
|
5871
|
+
return [free - gaps, gap];
|
|
5872
|
+
case "space-between":
|
|
5873
|
+
return [0, n > 1 ? Math.max(free / (n - 1), 0) : 0];
|
|
5874
|
+
case "space-around": {
|
|
5875
|
+
const g = Math.max(free / k, 0);
|
|
5876
|
+
return [g / 2, g];
|
|
5877
|
+
}
|
|
5878
|
+
case "space-evenly": {
|
|
5879
|
+
const g = Math.max(free / (k + 1), 0);
|
|
5880
|
+
return [g, g];
|
|
5881
|
+
}
|
|
5882
|
+
default:
|
|
5883
|
+
return [0, gap];
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
function distribute(items, open, free) {
|
|
5887
|
+
free = Math.max(free, 0);
|
|
5888
|
+
while (open.length > 0) {
|
|
5889
|
+
const share = free / open.length;
|
|
5890
|
+
const sized = open.map((i) => [i, clampLayoutSize(share, items[i].minMain, items[i].maxMain)]);
|
|
5891
|
+
const violation = sized.reduce((sum, [, v]) => sum + (v - share), 0);
|
|
5892
|
+
const frozen = (v) => Math.abs(violation) < 1e-9 || violation > 0 && v > share + 1e-9 || violation < 0 && v < share - 1e-9;
|
|
5893
|
+
for (const [i, v] of sized) {
|
|
5894
|
+
if (frozen(v)) {
|
|
5895
|
+
items[i].main = v;
|
|
5896
|
+
free -= v;
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
free = Math.max(free, 0);
|
|
5900
|
+
open = sized.filter(([, v]) => !frozen(v)).map(([i]) => i);
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
function layout(s, frame) {
|
|
5904
|
+
s.guard(frame, () => layoutFrameInner(s, frame));
|
|
5905
|
+
}
|
|
5906
|
+
function layoutFrameInner(s, frame) {
|
|
5907
|
+
const f = s.boxes.get(frame);
|
|
5908
|
+
const al = f && getAutoLayout(f.node);
|
|
5909
|
+
if (!f || !al) return;
|
|
5910
|
+
const horizontal = al.mode !== "vertical";
|
|
5911
|
+
const hugW = getLayoutSizing(f.node, "width") === "hug";
|
|
5912
|
+
const hugH = getLayoutSizing(f.node, "height") === "hug";
|
|
5913
|
+
const ow = f.w;
|
|
5914
|
+
const oh = f.h;
|
|
5915
|
+
const fl = limitsOf(f.node);
|
|
5916
|
+
const [pt, pr, pb, pl] = resolvePadding(al.padding);
|
|
5917
|
+
const fw = Math.max(clampLayoutSize(ow, fl.minW, fl.maxW), pl + pr);
|
|
5918
|
+
const fh = Math.max(clampLayoutSize(oh, fl.minH, fl.maxH), pt + pb);
|
|
5919
|
+
const kids = flowChildren(s, frame);
|
|
5920
|
+
for (const c of kids) {
|
|
5921
|
+
fitHugText(s, c);
|
|
5922
|
+
enforceLimits(s, c);
|
|
5923
|
+
}
|
|
5924
|
+
if (al.mode === "grid") {
|
|
5925
|
+
layoutGrid(s, frame, al);
|
|
5926
|
+
return;
|
|
5927
|
+
}
|
|
5928
|
+
const innerMainFixed = horizontal ? fw - pl - pr : fh - pt - pb;
|
|
5929
|
+
const innerCrossFixed = horizontal ? fh - pt - pb : fw - pl - pr;
|
|
5930
|
+
const hugMain = horizontal ? hugW : hugH;
|
|
5931
|
+
const hugCross = horizontal ? hugH : hugW;
|
|
5932
|
+
const alignCross = al.alignCross ?? "start";
|
|
5933
|
+
const baseline = horizontal && alignCross === "baseline";
|
|
5934
|
+
const stretch = alignCross === "stretch";
|
|
5935
|
+
const items = [];
|
|
5936
|
+
for (const c of kids) {
|
|
5937
|
+
const b = s.boxes.get(c);
|
|
5938
|
+
const lim = limitsOf(b.node);
|
|
5939
|
+
const sm = getLayoutSizing(b.node, horizontal ? "width" : "height");
|
|
5940
|
+
const sc = getLayoutSizing(b.node, horizontal ? "height" : "width");
|
|
5941
|
+
items.push({
|
|
5942
|
+
id: c,
|
|
5943
|
+
main: horizontal ? b.w : b.h,
|
|
5944
|
+
cross: horizontal ? b.h : b.w,
|
|
5945
|
+
// Fill needs room to fill: on an axis the frame hugs it reads as fixed.
|
|
5946
|
+
fillMain: sm === "fill" && !hugMain,
|
|
5947
|
+
fillCross: (sc === "fill" || stretch && sc !== "hug") && !hugCross,
|
|
5948
|
+
minMain: horizontal ? lim.minW : lim.minH,
|
|
5949
|
+
maxMain: horizontal ? lim.maxW : lim.maxH,
|
|
5950
|
+
minCross: horizontal ? lim.minH : lim.minW,
|
|
5951
|
+
maxCross: horizontal ? lim.maxH : lim.maxW,
|
|
5952
|
+
baseOff: 0
|
|
5953
|
+
});
|
|
5954
|
+
}
|
|
5955
|
+
const gap = finite(al.gap);
|
|
5956
|
+
const alignMain = al.alignMain ?? "start";
|
|
5957
|
+
const autoGap = alignMain === "space-between" || alignMain === "space-around" || alignMain === "space-evenly";
|
|
5958
|
+
const wraps = !!al.wrap && !hugMain;
|
|
5959
|
+
const sizeOf = (id) => {
|
|
5960
|
+
const b = s.boxes.get(id);
|
|
5961
|
+
return horizontal ? [b.w, b.h] : [b.h, b.w];
|
|
5962
|
+
};
|
|
5963
|
+
const resize = (it, main, cross2) => resizeChild(s, it.id, horizontal ? main : cross2, horizontal ? cross2 : main);
|
|
5964
|
+
if (!wraps) {
|
|
5965
|
+
for (const it of items) {
|
|
5966
|
+
if (!it.fillCross || it.fillMain) continue;
|
|
5967
|
+
resize(it, it.main, clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross));
|
|
5968
|
+
it.main = sizeOf(it.id)[0];
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
const lines = [];
|
|
5972
|
+
if (wraps) {
|
|
5973
|
+
let cur = [];
|
|
5974
|
+
let used = 0;
|
|
5975
|
+
items.forEach((it, i) => {
|
|
5976
|
+
const sz = it.fillMain ? it.minMain ?? 0 : it.main;
|
|
5977
|
+
const add3 = cur.length === 0 ? sz : Math.max(gap, 0) + sz;
|
|
5978
|
+
if (cur.length > 0 && used + add3 > innerMainFixed + 1e-6) {
|
|
5979
|
+
lines.push(cur);
|
|
5980
|
+
cur = [];
|
|
5981
|
+
used = sz;
|
|
5982
|
+
} else {
|
|
5983
|
+
used += add3;
|
|
5984
|
+
}
|
|
5985
|
+
cur.push(i);
|
|
5986
|
+
});
|
|
5987
|
+
if (cur.length > 0) lines.push(cur);
|
|
5988
|
+
} else {
|
|
5989
|
+
lines.push(items.map((_, i) => i));
|
|
5990
|
+
}
|
|
5991
|
+
for (const line2 of lines) {
|
|
5992
|
+
const fixed = line2.filter((i) => !items[i].fillMain).reduce((sum, i) => sum + items[i].main, 0);
|
|
5993
|
+
const gaps = gap * Math.max(line2.length - 1, 0);
|
|
5994
|
+
const fills = line2.filter((i) => items[i].fillMain);
|
|
5995
|
+
if (fills.length > 0) distribute(items, fills, innerMainFixed - fixed - (autoGap ? 0 : gaps));
|
|
5996
|
+
}
|
|
5997
|
+
for (const it of items) {
|
|
5998
|
+
if (!it.fillMain) continue;
|
|
5999
|
+
const cross2 = it.fillCross && !wraps ? clampLayoutSize(innerCrossFixed, it.minCross, it.maxCross) : it.cross;
|
|
6000
|
+
resize(it, it.main, cross2);
|
|
6001
|
+
it.cross = sizeOf(it.id)[1];
|
|
6002
|
+
}
|
|
6003
|
+
if (baseline) {
|
|
6004
|
+
for (const it of items) if (!it.fillCross) it.baseline = itemBaseline(s, it.id, 0);
|
|
6005
|
+
}
|
|
6006
|
+
const extents = [];
|
|
6007
|
+
for (const line2 of lines) {
|
|
6008
|
+
const gaps = gap * Math.max(line2.length - 1, 0);
|
|
6009
|
+
const lineBase = line2.reduce((m, i) => Math.max(m, items[i].baseline ?? 0), 0);
|
|
6010
|
+
for (const i of line2) items[i].baseOff = items[i].baseline !== void 0 ? lineBase - items[i].baseline : 0;
|
|
6011
|
+
const inLine = line2.map((i) => items[i]);
|
|
6012
|
+
const mainUsed = inLine.reduce((sum, it) => sum + it.main, 0) + gaps;
|
|
6013
|
+
const allFill = inLine.every((it) => it.fillCross);
|
|
6014
|
+
const cross2 = inLine.filter((it) => allFill || !it.fillCross).reduce((m, it) => Math.max(m, it.baseOff + it.cross), 0);
|
|
6015
|
+
extents.push([mainUsed, cross2]);
|
|
6016
|
+
}
|
|
6017
|
+
const contentMain = extents.reduce((m, l) => Math.max(m, l[0]), 0);
|
|
6018
|
+
let crossGap = al.rowGap !== void 0 ? finite(al.rowGap) : gap;
|
|
6019
|
+
const contentCross = extents.reduce((sum, l) => sum + l[1], 0) + crossGap * Math.max(lines.length - 1, 0);
|
|
6020
|
+
let nw = fw;
|
|
6021
|
+
let nh = fh;
|
|
6022
|
+
if (hugMain) {
|
|
6023
|
+
const v = contentMain + (horizontal ? pl + pr : pt + pb);
|
|
6024
|
+
if (horizontal) nw = v;
|
|
6025
|
+
else nh = v;
|
|
6026
|
+
}
|
|
6027
|
+
if (hugCross) {
|
|
6028
|
+
const v = contentCross + (horizontal ? pt + pb : pl + pr);
|
|
6029
|
+
if (horizontal) nh = v;
|
|
6030
|
+
else nw = v;
|
|
6031
|
+
}
|
|
6032
|
+
nw = Math.max(clampLayoutSize(nw, fl.minW, fl.maxW), pl + pr);
|
|
6033
|
+
nh = Math.max(clampLayoutSize(nh, fl.minH, fl.maxH), pt + pb);
|
|
6034
|
+
setSize(s, frame, nw, nh);
|
|
6035
|
+
constrain(s, frame, ow, oh);
|
|
6036
|
+
const innerMain = horizontal ? nw - pl - pr : nh - pt - pb;
|
|
6037
|
+
const innerCross = horizontal ? nh - pt - pb : nw - pl - pr;
|
|
6038
|
+
let crossPos = horizontal ? pt : pl;
|
|
6039
|
+
const single = lines.length === 1;
|
|
6040
|
+
if (lines.length > 1) {
|
|
6041
|
+
const linesCross = extents.reduce((sum, l) => sum + l[1], 0);
|
|
6042
|
+
const between = lines.length - 1;
|
|
6043
|
+
const spread = (innerCross - linesCross) / between;
|
|
6044
|
+
const spaceBetween = al.alignContent === "space-between";
|
|
6045
|
+
if (spaceBetween && spread > 0) {
|
|
6046
|
+
crossGap = spread;
|
|
6047
|
+
} else {
|
|
6048
|
+
if (spaceBetween) crossGap = 0;
|
|
6049
|
+
const free = innerCross - linesCross - crossGap * between;
|
|
6050
|
+
if (free > 0 && items.every((it) => it.fillCross)) {
|
|
6051
|
+
const extra = free / lines.length;
|
|
6052
|
+
for (const l of extents) l[1] += extra;
|
|
6053
|
+
} else if (Number.isFinite(free)) {
|
|
6054
|
+
crossPos += alignCross === "center" ? free / 2 : alignCross === "end" ? free : 0;
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
6058
|
+
lines.forEach((line2, li) => {
|
|
6059
|
+
const [used, ownCross] = extents[li];
|
|
6060
|
+
const lineCross = single ? innerCross : ownCross;
|
|
6061
|
+
const total = used - gap * Math.max(line2.length - 1, 0);
|
|
6062
|
+
let [pos, step] = mainOffsets(alignMain, innerMain, total, line2.length, gap);
|
|
6063
|
+
pos += horizontal ? pl : pt;
|
|
6064
|
+
for (const i of line2) {
|
|
6065
|
+
const it = items[i];
|
|
6066
|
+
const crossSize = it.fillCross ? clampLayoutSize(lineCross, it.minCross, it.maxCross) : it.cross;
|
|
6067
|
+
let off = 0;
|
|
6068
|
+
if (alignCross === "baseline") off = it.baseOff;
|
|
6069
|
+
else if (alignCross === "center") off = (lineCross - crossSize) / 2;
|
|
6070
|
+
else if (alignCross === "end") off = lineCross - crossSize;
|
|
6071
|
+
if (it.fillCross && wraps) resize(it, it.main, crossSize);
|
|
6072
|
+
const x = horizontal ? pos : crossPos + off;
|
|
6073
|
+
const y = horizontal ? crossPos + off : pos;
|
|
6074
|
+
place(s, it.id, finite(x), finite(y));
|
|
6075
|
+
pos += it.main + step;
|
|
6076
|
+
}
|
|
6077
|
+
crossPos += lineCross + crossGap;
|
|
6078
|
+
});
|
|
6079
|
+
}
|
|
6080
|
+
function parseTrack(track) {
|
|
6081
|
+
if (typeof track === "number") return { sizing: "fixed", value: track };
|
|
6082
|
+
if (track === "hug") return { sizing: "hug", value: 0 };
|
|
6083
|
+
if (typeof track === "string") {
|
|
6084
|
+
const match = /^\s*(-?\d*\.?\d+)\s*fr\s*$/i.exec(track);
|
|
6085
|
+
if (match) return { sizing: "fill", value: Number(match[1]) };
|
|
6086
|
+
}
|
|
6087
|
+
if (track && typeof track === "object" && "fr" in track) return { sizing: "fill", value: track.fr };
|
|
6088
|
+
return { sizing: "fill", value: 1 };
|
|
6089
|
+
}
|
|
6090
|
+
function trackList(spec, fallback) {
|
|
6091
|
+
if (Array.isArray(spec)) return { count: Math.min(Math.max(spec.length, 1), MAX_TRACKS), tracks: spec.map(parseTrack) };
|
|
6092
|
+
const n = typeof spec === "number" && Number.isFinite(spec) ? Math.floor(spec) : fallback;
|
|
6093
|
+
return { count: Math.min(Math.max(n, 1), MAX_TRACKS), tracks: [] };
|
|
6094
|
+
}
|
|
6095
|
+
function trackSpec(tracks, i) {
|
|
6096
|
+
const t = tracks[i] ?? { sizing: "fill", value: 1 };
|
|
6097
|
+
const value = Number.isFinite(t.value) ? Math.min(Math.max(t.value, 0), 1e6) : 0;
|
|
6098
|
+
return { sizing: t.sizing, value };
|
|
6099
|
+
}
|
|
6100
|
+
function cellRect(g, c) {
|
|
6101
|
+
const x0 = g.columns[c.column]?.[0] ?? 0;
|
|
6102
|
+
const x1 = g.columns[c.column + c.columnSpan - 1]?.[1] ?? x0;
|
|
6103
|
+
const y0 = g.rows[c.row]?.[0] ?? 0;
|
|
6104
|
+
const y1 = g.rows[c.row + c.rowSpan - 1]?.[1] ?? y0;
|
|
6105
|
+
return { x: x0, y: y0, width: Math.max(x1, x0) - x0, height: Math.max(y1, y0) - y0 };
|
|
6106
|
+
}
|
|
6107
|
+
var toIndex = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.floor(v) : void 0;
|
|
6108
|
+
function gridPlace(s, al, kids) {
|
|
6109
|
+
const cols = trackList(al.grid?.columns, 2).count;
|
|
6110
|
+
const minRows = trackList(al.grid?.rows, 1).count;
|
|
6111
|
+
const maxRows = Math.min(Math.max(Math.floor(MAX_GRID_CELLS / cols), 1), MAX_TRACKS);
|
|
6112
|
+
const occ = [];
|
|
6113
|
+
const free = (c, r, cs, rs) => {
|
|
6114
|
+
for (let ri = r; ri < r + rs; ri++) {
|
|
6115
|
+
const row = occ[ri];
|
|
6116
|
+
if (!row) continue;
|
|
6117
|
+
for (let ci = c; ci < c + cs; ci++) if (row[ci]) return false;
|
|
6118
|
+
}
|
|
6119
|
+
return true;
|
|
6120
|
+
};
|
|
6121
|
+
const mark = (c, r, cs, rs) => {
|
|
6122
|
+
while (occ.length < r + rs) occ.push(new Array(cols).fill(false));
|
|
6123
|
+
for (let ri = r; ri < r + rs; ri++) for (let ci = c; ci < c + cs; ci++) occ[ri][ci] = true;
|
|
6124
|
+
};
|
|
6125
|
+
const spans = (id) => {
|
|
6126
|
+
const area = getChildLayout(s.boxes.get(id).node).gridArea ?? {};
|
|
6127
|
+
const cs = Math.min(Math.max(toIndex(area.columnSpan) ?? 1, 1), cols);
|
|
6128
|
+
const rs = Math.min(Math.max(toIndex(area.rowSpan) ?? 1, 1), maxRows);
|
|
6129
|
+
const col = toIndex(area.column);
|
|
6130
|
+
const row = toIndex(area.row);
|
|
6131
|
+
return {
|
|
6132
|
+
col: col === void 0 ? void 0 : Math.min(col, cols - cs),
|
|
6133
|
+
row: row === void 0 ? void 0 : Math.min(row, maxRows - rs),
|
|
6134
|
+
cs,
|
|
6135
|
+
rs
|
|
6136
|
+
};
|
|
6137
|
+
};
|
|
6138
|
+
const placed = new Array(kids.length).fill(void 0);
|
|
6139
|
+
kids.forEach((id, i) => {
|
|
6140
|
+
const { col, row, cs, rs } = spans(id);
|
|
6141
|
+
if (col !== void 0 && row !== void 0) {
|
|
6142
|
+
mark(col, row, cs, rs);
|
|
6143
|
+
placed[i] = { id, column: col, row, columnSpan: cs, rowSpan: rs };
|
|
6144
|
+
}
|
|
6145
|
+
});
|
|
6146
|
+
let cursor = [0, 0];
|
|
6147
|
+
kids.forEach((id, i) => {
|
|
6148
|
+
if (placed[i]) return;
|
|
6149
|
+
const { col, row, cs, rs } = spans(id);
|
|
6150
|
+
const lastRow = maxRows - rs;
|
|
6151
|
+
let c = 0;
|
|
6152
|
+
let r = 0;
|
|
6153
|
+
if (col !== void 0) {
|
|
6154
|
+
r = lastRow;
|
|
6155
|
+
for (let ri = 0; ri <= lastRow; ri++) if (free(col, ri, cs, rs)) {
|
|
6156
|
+
r = ri;
|
|
6157
|
+
break;
|
|
6158
|
+
}
|
|
6159
|
+
c = col;
|
|
6160
|
+
} else if (row !== void 0) {
|
|
6161
|
+
r = row;
|
|
6162
|
+
for (let ci = 0; ci <= cols - cs; ci++) if (free(ci, row, cs, rs)) {
|
|
6163
|
+
c = ci;
|
|
6164
|
+
break;
|
|
6165
|
+
}
|
|
6166
|
+
} else {
|
|
6167
|
+
let [rr, cc] = cursor;
|
|
6168
|
+
let found;
|
|
6169
|
+
while (rr <= lastRow) {
|
|
6170
|
+
if (cc + cs <= cols && free(cc, rr, cs, rs)) {
|
|
6171
|
+
found = [cc, rr];
|
|
6172
|
+
break;
|
|
6173
|
+
}
|
|
6174
|
+
cc += 1;
|
|
6175
|
+
if (cc + cs > cols) {
|
|
6176
|
+
cc = 0;
|
|
6177
|
+
rr += 1;
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
[c, r] = found ?? [0, lastRow];
|
|
6181
|
+
cursor = [r, c + cs];
|
|
6182
|
+
}
|
|
6183
|
+
mark(c, r, cs, rs);
|
|
6184
|
+
placed[i] = { id, column: c, row: r, columnSpan: cs, rowSpan: rs };
|
|
6185
|
+
});
|
|
6186
|
+
const cells = placed.filter((c) => !!c);
|
|
6187
|
+
const rows = Math.max(cells.reduce((m, c) => Math.max(m, c.row + c.rowSpan), 0), minRows);
|
|
6188
|
+
return { cells, rows: Math.min(rows, MAX_TRACKS) };
|
|
6189
|
+
}
|
|
6190
|
+
function resolveTracks(tracks, content, avail) {
|
|
6191
|
+
const out = content.map((c, i) => {
|
|
6192
|
+
const t = trackSpec(tracks, i);
|
|
6193
|
+
return t.sizing === "fixed" ? t.value : c;
|
|
6194
|
+
});
|
|
6195
|
+
if (avail === void 0) return out;
|
|
6196
|
+
const fr = (i) => trackSpec(tracks, i).value;
|
|
6197
|
+
let flex = out.map((_, i) => i).filter((i) => trackSpec(tracks, i).sizing === "fill");
|
|
6198
|
+
let used = out.reduce((sum, v, i) => trackSpec(tracks, i).sizing === "fill" ? sum : sum + v, 0);
|
|
6199
|
+
while (flex.length > 0) {
|
|
6200
|
+
const total = flex.reduce((sum, i) => sum + fr(i), 0);
|
|
6201
|
+
const unit3 = total > 1e-12 ? Math.max(avail - used, 0) / total : 0;
|
|
6202
|
+
const open = flex.filter((i) => unit3 * fr(i) >= out[i]);
|
|
6203
|
+
const frozen = flex.filter((i) => unit3 * fr(i) < out[i]);
|
|
6204
|
+
if (frozen.length === 0) {
|
|
6205
|
+
for (const i of open) out[i] = unit3 * fr(i);
|
|
6206
|
+
break;
|
|
6207
|
+
}
|
|
6208
|
+
used += frozen.reduce((sum, i) => sum + out[i], 0);
|
|
6209
|
+
flex = open;
|
|
6210
|
+
}
|
|
6211
|
+
return out;
|
|
6212
|
+
}
|
|
6213
|
+
function trackContent(cells, sizes, tracks, n, gap, columns, hug) {
|
|
6214
|
+
const byContent = (i) => trackSpec(tracks, i).sizing !== "fixed";
|
|
6215
|
+
const content = Array.from({ length: n }, (_, i) => {
|
|
6216
|
+
const t = trackSpec(tracks, i);
|
|
6217
|
+
return t.sizing === "fixed" ? t.value : 0;
|
|
6218
|
+
});
|
|
6219
|
+
const span = (c) => columns ? [c.column, c.columnSpan] : [c.row, c.rowSpan];
|
|
6220
|
+
cells.forEach((c, k) => {
|
|
6221
|
+
const [start, len] = span(c);
|
|
6222
|
+
if (len === 1 && byContent(start) && start < n) content[start] = Math.max(content[start], sizes[k]);
|
|
6223
|
+
});
|
|
6224
|
+
cells.forEach((c, k) => {
|
|
6225
|
+
const [start, len] = span(c);
|
|
6226
|
+
if (len < 2) return;
|
|
6227
|
+
const range = Array.from({ length: len }, (_, j) => start + j).filter((i) => i < n);
|
|
6228
|
+
if (!hug && range.some((i) => trackSpec(tracks, i).sizing === "fill")) return;
|
|
6229
|
+
const have = range.reduce((sum, i) => sum + content[i], 0) + gap * (len - 1);
|
|
6230
|
+
const grow = range.filter(byContent);
|
|
6231
|
+
if (sizes[k] > have && grow.length > 0) {
|
|
6232
|
+
const extra = (sizes[k] - have) / grow.length;
|
|
6233
|
+
for (const i of grow) content[i] += extra;
|
|
6234
|
+
}
|
|
6235
|
+
});
|
|
6236
|
+
return content;
|
|
6237
|
+
}
|
|
6238
|
+
function trackRanges(start, sizes, gap) {
|
|
6239
|
+
let x = start;
|
|
6240
|
+
return sizes.map((size) => {
|
|
6241
|
+
const r = [x, x + size];
|
|
6242
|
+
x += size + gap;
|
|
6243
|
+
return r;
|
|
6244
|
+
});
|
|
6245
|
+
}
|
|
6246
|
+
function gridGeometryOf(s, frame) {
|
|
6247
|
+
const f = s.boxes.get(frame);
|
|
6248
|
+
const al = f && getAutoLayout(f.node);
|
|
6249
|
+
if (!f || !al || al.mode !== "grid") return void 0;
|
|
6250
|
+
const [pt, pr, pb, pl] = resolvePadding(al.padding);
|
|
6251
|
+
const kids = flowChildren(s, frame);
|
|
6252
|
+
const { cells, rows } = gridPlace(s, al, kids);
|
|
6253
|
+
const colSpec = trackList(al.grid?.columns, 2);
|
|
6254
|
+
const rowSpec = trackList(al.grid?.rows, 1);
|
|
6255
|
+
const cols = colSpec.count;
|
|
6256
|
+
const gx = Math.max(finite(al.gap), 0);
|
|
6257
|
+
const gy = Math.max(al.rowGap !== void 0 ? finite(al.rowGap) : gx, 0);
|
|
6258
|
+
const hugW = getLayoutSizing(f.node, "width") === "hug";
|
|
6259
|
+
const hugH = getLayoutSizing(f.node, "height") === "hug";
|
|
6260
|
+
const ws = [];
|
|
6261
|
+
const hs = [];
|
|
6262
|
+
for (const c of cells) {
|
|
6263
|
+
const b = s.boxes.get(c.id);
|
|
6264
|
+
const lim2 = limitsOf(b.node);
|
|
6265
|
+
ws.push(getLayoutSizing(b.node, "width") === "fill" && !hugW ? lim2.minW ?? 0 : b.w);
|
|
6266
|
+
hs.push(getLayoutSizing(b.node, "height") === "fill" && !hugH ? lim2.minH ?? 0 : b.h);
|
|
6267
|
+
}
|
|
6268
|
+
const colContent = trackContent(cells, ws, colSpec.tracks, cols, gx, true, hugW);
|
|
6269
|
+
const rowContent = trackContent(cells, hs, rowSpec.tracks, rows, gy, false, hugH);
|
|
6270
|
+
const gapsW = gx * Math.max(cols - 1, 0);
|
|
6271
|
+
const gapsH = gy * Math.max(rows - 1, 0);
|
|
6272
|
+
const colW = resolveTracks(colSpec.tracks, colContent, hugW ? void 0 : finite(f.w) - pl - pr - gapsW);
|
|
6273
|
+
const rowH = resolveTracks(rowSpec.tracks, rowContent, hugH ? void 0 : finite(f.h) - pt - pb - gapsH);
|
|
6274
|
+
const lim = limitsOf(f.node);
|
|
6275
|
+
const width = hugW ? colW.reduce((a, b) => a + b, 0) + gapsW + pl + pr : finite(f.w);
|
|
6276
|
+
const height = hugH ? rowH.reduce((a, b) => a + b, 0) + gapsH + pt + pb : finite(f.h);
|
|
6277
|
+
return {
|
|
6278
|
+
columns: trackRanges(pl, colW, gx),
|
|
6279
|
+
rows: trackRanges(pt, rowH, gy),
|
|
6280
|
+
cells,
|
|
6281
|
+
width: Math.max(clampLayoutSize(width, lim.minW, lim.maxW), pl + pr),
|
|
6282
|
+
height: Math.max(clampLayoutSize(height, lim.minH, lim.maxH), pt + pb)
|
|
6283
|
+
};
|
|
6284
|
+
}
|
|
6285
|
+
function layoutGrid(s, frame, al) {
|
|
6286
|
+
const f = s.boxes.get(frame);
|
|
6287
|
+
const fw = f.w;
|
|
6288
|
+
const fh = f.h;
|
|
6289
|
+
const hugW = getLayoutSizing(f.node, "width") === "hug";
|
|
6290
|
+
const hugH = getLayoutSizing(f.node, "height") === "hug";
|
|
6291
|
+
const lim = limitsOf(f.node);
|
|
6292
|
+
const [pt, pr, pb, pl] = resolvePadding(al.padding);
|
|
6293
|
+
setSize(
|
|
6294
|
+
s,
|
|
6295
|
+
frame,
|
|
6296
|
+
hugW ? fw : Math.max(clampLayoutSize(fw, lim.minW, lim.maxW), pl + pr),
|
|
6297
|
+
hugH ? fh : Math.max(clampLayoutSize(fh, lim.minH, lim.maxH), pt + pb)
|
|
6298
|
+
);
|
|
6299
|
+
let g = gridGeometryOf(s, frame);
|
|
6300
|
+
if (!g) return;
|
|
6301
|
+
let widened = false;
|
|
6302
|
+
for (const c of g.cells) {
|
|
6303
|
+
const b = s.boxes.get(c.id);
|
|
6304
|
+
if (getLayoutSizing(b.node, "width") === "fill" && !hugW) {
|
|
6305
|
+
const l = limitsOf(b.node);
|
|
6306
|
+
resizeChild(s, c.id, clampLayoutSize(cellRect(g, c).width, l.minW, l.maxW), b.h);
|
|
6307
|
+
widened = true;
|
|
6308
|
+
}
|
|
3367
6309
|
}
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
const
|
|
3372
|
-
const
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
6310
|
+
if (widened) g = gridGeometryOf(s, frame) ?? g;
|
|
6311
|
+
setSize(s, frame, g.width, g.height);
|
|
6312
|
+
constrain(s, frame, fw, fh);
|
|
6313
|
+
const frameX = al.alignMain === "center" ? "center" : al.alignMain === "end" ? "end" : "start";
|
|
6314
|
+
const frameY = al.alignCross === "center" ? "center" : al.alignCross === "end" ? "end" : "start";
|
|
6315
|
+
const offset = (a, free) => a === "center" ? free / 2 : a === "end" ? free : 0;
|
|
6316
|
+
for (const c of g.cells) {
|
|
6317
|
+
const cell = cellRect(g, c);
|
|
6318
|
+
const b = s.boxes.get(c.id);
|
|
6319
|
+
const l = limitsOf(b.node);
|
|
6320
|
+
const area = getChildLayout(b.node).gridArea ?? {};
|
|
6321
|
+
const fillW = getLayoutSizing(b.node, "width") === "fill" && !hugW;
|
|
6322
|
+
const fillH = getLayoutSizing(b.node, "height") === "fill" && !hugH;
|
|
6323
|
+
if (fillW || fillH) {
|
|
6324
|
+
resizeChild(
|
|
6325
|
+
s,
|
|
6326
|
+
c.id,
|
|
6327
|
+
fillW ? clampLayoutSize(cell.width, l.minW, l.maxW) : b.w,
|
|
6328
|
+
fillH ? clampLayoutSize(cell.height, l.minH, l.maxH) : b.h
|
|
6329
|
+
);
|
|
6330
|
+
}
|
|
6331
|
+
const ox = offset(area.alignX ?? frameX, cell.width - b.w);
|
|
6332
|
+
const oy = offset(area.alignY ?? frameY, cell.height - b.h);
|
|
6333
|
+
place(s, c.id, finite(cell.x + ox), finite(cell.y + oy));
|
|
6334
|
+
}
|
|
6335
|
+
}
|
|
6336
|
+
function constrain(s, id, ow, oh) {
|
|
6337
|
+
const f = s.boxes.get(id);
|
|
6338
|
+
if (!f) return;
|
|
6339
|
+
const nw = f.w;
|
|
6340
|
+
const nh = f.h;
|
|
6341
|
+
if (Math.abs(nw - ow) < 1e-9 && Math.abs(nh - oh) < 1e-9) return;
|
|
6342
|
+
const absoluteOnly = !!getAutoLayout(f.node);
|
|
6343
|
+
const sx = ow > 1e-9 ? nw / ow : 1;
|
|
6344
|
+
const sy = oh > 1e-9 ? nh / oh : 1;
|
|
6345
|
+
s.guard(`constrain:${id}`, () => {
|
|
6346
|
+
for (const k of f.kids) {
|
|
6347
|
+
const c = s.boxes.get(k);
|
|
6348
|
+
const child = getChildLayout(c.node);
|
|
6349
|
+
if (absoluteOnly && !child.absolute) continue;
|
|
6350
|
+
let x0 = c.x;
|
|
6351
|
+
let x1 = c.x + c.w;
|
|
6352
|
+
let y0 = c.y;
|
|
6353
|
+
let y1 = c.y + c.h;
|
|
6354
|
+
const dx = nw - ow;
|
|
6355
|
+
const dy = nh - oh;
|
|
6356
|
+
switch (child.constraints?.horizontal ?? "left") {
|
|
6357
|
+
case "right":
|
|
6358
|
+
x0 += dx;
|
|
6359
|
+
x1 += dx;
|
|
6360
|
+
break;
|
|
6361
|
+
case "leftRight":
|
|
6362
|
+
x1 += dx;
|
|
6363
|
+
break;
|
|
6364
|
+
case "center":
|
|
6365
|
+
x0 += dx / 2;
|
|
6366
|
+
x1 += dx / 2;
|
|
6367
|
+
break;
|
|
6368
|
+
case "scale":
|
|
6369
|
+
x0 *= sx;
|
|
6370
|
+
x1 *= sx;
|
|
6371
|
+
break;
|
|
3381
6372
|
}
|
|
3382
|
-
|
|
6373
|
+
switch (child.constraints?.vertical ?? "top") {
|
|
6374
|
+
case "bottom":
|
|
6375
|
+
y0 += dy;
|
|
6376
|
+
y1 += dy;
|
|
6377
|
+
break;
|
|
6378
|
+
case "topBottom":
|
|
6379
|
+
y1 += dy;
|
|
6380
|
+
break;
|
|
6381
|
+
case "center":
|
|
6382
|
+
y0 += dy / 2;
|
|
6383
|
+
y1 += dy / 2;
|
|
6384
|
+
break;
|
|
6385
|
+
case "scale":
|
|
6386
|
+
y0 *= sy;
|
|
6387
|
+
y1 *= sy;
|
|
6388
|
+
break;
|
|
6389
|
+
}
|
|
6390
|
+
const cw = c.w;
|
|
6391
|
+
const ch = c.h;
|
|
6392
|
+
const resized = Math.abs(x1 - x0 - cw) > 1e-9 || Math.abs(y1 - y0 - ch) > 1e-9;
|
|
6393
|
+
place(s, k, finite(x0), finite(y0));
|
|
6394
|
+
if (resized) {
|
|
6395
|
+
setSize(s, k, x1 - x0, y1 - y0);
|
|
6396
|
+
if (c.kids.length > 0) constrain(s, k, cw, ch);
|
|
6397
|
+
if (getAutoLayout(c.node)) layout(s, k);
|
|
6398
|
+
}
|
|
6399
|
+
}
|
|
6400
|
+
});
|
|
6401
|
+
}
|
|
6402
|
+
function layoutFrame(nodes, frameId, options = {}) {
|
|
6403
|
+
const s = new LayoutState(nodes, options);
|
|
6404
|
+
layout(s, frameId);
|
|
6405
|
+
return s.updates();
|
|
6406
|
+
}
|
|
6407
|
+
function postOrder(s, nodes) {
|
|
6408
|
+
const out = [];
|
|
6409
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6410
|
+
const visit = (id) => {
|
|
6411
|
+
if (seen.has(id)) return;
|
|
6412
|
+
seen.add(id);
|
|
6413
|
+
for (const k of s.boxes.get(id)?.kids ?? []) visit(k);
|
|
6414
|
+
out.push(id);
|
|
3383
6415
|
};
|
|
6416
|
+
const roots = nodes.filter((n) => !n.parentId || !s.boxes.has(n.parentId));
|
|
6417
|
+
for (const r of roots) visit(r.id);
|
|
6418
|
+
for (const n of nodes) visit(n.id);
|
|
6419
|
+
return out;
|
|
3384
6420
|
}
|
|
3385
|
-
function
|
|
3386
|
-
const
|
|
3387
|
-
const
|
|
3388
|
-
return
|
|
6421
|
+
function layoutAll(nodes, options = {}) {
|
|
6422
|
+
const s = new LayoutState(nodes, options);
|
|
6423
|
+
for (const id of postOrder(s, nodes)) if (getAutoLayout(s.boxes.get(id).node)) layout(s, id);
|
|
6424
|
+
return s.updates();
|
|
6425
|
+
}
|
|
6426
|
+
function layoutUp(nodes, changedId, options = {}) {
|
|
6427
|
+
return layoutUpMany(nodes, [changedId], options);
|
|
6428
|
+
}
|
|
6429
|
+
function layoutUpMany(nodes, changedIds, options = {}) {
|
|
6430
|
+
const s = new LayoutState(nodes, options);
|
|
6431
|
+
const chains = [...new Set(changedIds)].filter((id) => s.boxes.has(id)).map((id) => [id, ...s.ancestors(id)]);
|
|
6432
|
+
const depth = /* @__PURE__ */ new Map();
|
|
6433
|
+
for (const chain of chains) chain.forEach((id, i) => depth.set(id, Math.max(depth.get(id) ?? 0, chain.length - 1 - i)));
|
|
6434
|
+
const order = [...depth.keys()].sort((a, b) => depth.get(b) - depth.get(a));
|
|
6435
|
+
for (const id of order) if (getAutoLayout(s.boxes.get(id).node)) layout(s, id);
|
|
6436
|
+
return s.updates();
|
|
6437
|
+
}
|
|
6438
|
+
function applyConstraints(nodes, parentId, previousSize, options = {}) {
|
|
6439
|
+
const s = new LayoutState(nodes, options);
|
|
6440
|
+
constrain(s, parentId, previousSize.width, previousSize.height);
|
|
6441
|
+
return s.updates();
|
|
6442
|
+
}
|
|
6443
|
+
function getAutoLayoutGrid(nodes, frameId, options = {}) {
|
|
6444
|
+
return gridGeometryOf(new LayoutState(nodes, options), frameId);
|
|
6445
|
+
}
|
|
6446
|
+
function getFlowChildren(nodes, frameId) {
|
|
6447
|
+
return nodes.filter((n) => n.parentId === frameId && n.id !== frameId && n.hidden !== true && !getChildLayout(n).absolute).map((n) => n.id);
|
|
6448
|
+
}
|
|
6449
|
+
function getLayoutParent(nodes, id) {
|
|
6450
|
+
const lookup = createNodeLookup(nodes);
|
|
6451
|
+
const node = lookup.get(id);
|
|
6452
|
+
if (!node?.parentId || getChildLayout(node).absolute) return void 0;
|
|
6453
|
+
const parent = lookup.get(node.parentId);
|
|
6454
|
+
return isAutoLayoutFrame(parent) ? parent : void 0;
|
|
6455
|
+
}
|
|
6456
|
+
function makeSizingCoherent(nodes, id, options = {}) {
|
|
6457
|
+
const lookup = createNodeLookup(nodes);
|
|
6458
|
+
const targets = /* @__PURE__ */ new Set([id, ...nodes.filter((n) => n.parentId === id).map((n) => n.id)]);
|
|
6459
|
+
if (!lookup.has(id)) return nodes;
|
|
6460
|
+
let changed = false;
|
|
6461
|
+
const next = nodes.map((node) => {
|
|
6462
|
+
if (!targets.has(node.id)) return node;
|
|
6463
|
+
const parent = node.parentId ? lookup.get(node.parentId) : void 0;
|
|
6464
|
+
const flowParent = parent && isAutoLayoutFrame(parent) && !getChildLayout(node).absolute ? parent : void 0;
|
|
6465
|
+
const canHug = isAutoLayoutFrame(node) || options.measure?.textSize?.(node) !== void 0;
|
|
6466
|
+
const coherent = (axis) => {
|
|
6467
|
+
const sizing = getLayoutSizing(node, axis);
|
|
6468
|
+
if (sizing === "fill" && (!flowParent || getLayoutSizing(flowParent, axis) === "hug")) return "fixed";
|
|
6469
|
+
if (sizing === "hug" && !canHug) return "fixed";
|
|
6470
|
+
return sizing;
|
|
6471
|
+
};
|
|
6472
|
+
const width = coherent("width");
|
|
6473
|
+
const height = coherent("height");
|
|
6474
|
+
if (width === getLayoutSizing(node, "width") && height === getLayoutSizing(node, "height")) return node;
|
|
6475
|
+
changed = true;
|
|
6476
|
+
const child = getChildLayout(node);
|
|
6477
|
+
return {
|
|
6478
|
+
...node,
|
|
6479
|
+
data: { ...node.data, [CHILD_LAYOUT_DATA_KEY]: { ...child, sizing: { ...child.sizing, width, height } } }
|
|
6480
|
+
};
|
|
6481
|
+
});
|
|
6482
|
+
return changed ? next : nodes;
|
|
6483
|
+
}
|
|
6484
|
+
function getAutoLayoutInsertTarget(nodes, frameId, point, options = {}) {
|
|
6485
|
+
const lookup = createNodeLookup(nodes);
|
|
6486
|
+
const frame = lookup.get(frameId);
|
|
6487
|
+
const al = getAutoLayout(frame);
|
|
6488
|
+
if (!frame || !al) return void 0;
|
|
6489
|
+
const s = new LayoutState(nodes, options);
|
|
6490
|
+
const origin = getAbsolutePosition(frame, lookup);
|
|
6491
|
+
const px2 = point.x - origin.x;
|
|
6492
|
+
const py = point.y - origin.y;
|
|
6493
|
+
const siblings = flowChildren(s, frameId).filter((id) => id !== options.excludeId).map((id) => s.boxes.get(id));
|
|
6494
|
+
const horizontal = al.mode !== "vertical";
|
|
6495
|
+
const main = (b) => horizontal ? b.x : b.y;
|
|
6496
|
+
const mainSize = (b) => horizontal ? b.w : b.h;
|
|
6497
|
+
const cross2 = (b) => horizontal ? b.y : b.x;
|
|
6498
|
+
const crossSize = (b) => horizontal ? b.h : b.w;
|
|
6499
|
+
const p = { x: px2, y: py };
|
|
6500
|
+
const multiLine = al.mode === "grid" || !!al.wrap;
|
|
6501
|
+
let index = 0;
|
|
6502
|
+
for (const b of siblings) {
|
|
6503
|
+
const before = multiLine ? cross2(b) + crossSize(b) <= cross2(p) || cross2(b) <= cross2(p) && main(b) + mainSize(b) / 2 < main(p) : main(b) + mainSize(b) / 2 < main(p);
|
|
6504
|
+
if (before) index += 1;
|
|
6505
|
+
}
|
|
6506
|
+
const [pt, pr, pb, pl] = resolvePadding(al.padding);
|
|
6507
|
+
const prev = siblings[index - 1];
|
|
6508
|
+
const next = siblings[index];
|
|
6509
|
+
const f = s.boxes.get(frameId);
|
|
6510
|
+
let guide;
|
|
6511
|
+
if (horizontal) {
|
|
6512
|
+
const x = prev && next && Math.abs(cross2(prev) - cross2(next)) < 1e-6 ? (prev.x + prev.w + next.x) / 2 : prev ? prev.x + prev.w : next ? next.x : pl;
|
|
6513
|
+
const ref = next ?? prev;
|
|
6514
|
+
const y1 = ref ? ref.y : pt;
|
|
6515
|
+
const y2 = ref ? ref.y + ref.h : f.h - pb;
|
|
6516
|
+
guide = { orientation: "vertical", kind: "edge", x1: origin.x + x, y1: origin.y + y1, x2: origin.x + x, y2: origin.y + y2 };
|
|
6517
|
+
} else {
|
|
6518
|
+
const y = prev && next && Math.abs(cross2(prev) - cross2(next)) < 1e-6 ? (prev.y + prev.h + next.y) / 2 : prev ? prev.y + prev.h : next ? next.y : pt;
|
|
6519
|
+
const ref = next ?? prev;
|
|
6520
|
+
const x1 = ref ? ref.x : pl;
|
|
6521
|
+
const x2 = ref ? ref.x + ref.w : f.w - pr;
|
|
6522
|
+
guide = { orientation: "horizontal", kind: "edge", x1: origin.x + x1, y1: origin.y + y, x2: origin.x + x2, y2: origin.y + y };
|
|
6523
|
+
}
|
|
6524
|
+
return { frameId, index, guide };
|
|
6525
|
+
}
|
|
6526
|
+
function moveLayoutChild(nodes, childId, index) {
|
|
6527
|
+
const child = nodes.find((n) => n.id === childId);
|
|
6528
|
+
if (!child?.parentId) return nodes;
|
|
6529
|
+
const siblings = nodes.filter((n) => n.parentId === child.parentId && n.id !== child.parentId);
|
|
6530
|
+
const flow = siblings.filter((n) => n.hidden !== true && !getChildLayout(n).absolute && n.id !== childId);
|
|
6531
|
+
const clamped = Math.min(Math.max(Math.floor(Number.isFinite(index) ? index : 0), 0), flow.length);
|
|
6532
|
+
const order = siblings.filter((n) => n.id !== childId);
|
|
6533
|
+
const anchor = flow[clamped];
|
|
6534
|
+
const at = anchor ? order.indexOf(anchor) : flow.length > 0 ? order.indexOf(flow[flow.length - 1]) + 1 : order.length;
|
|
6535
|
+
order.splice(at, 0, child);
|
|
6536
|
+
if (order.every((n, i) => n === siblings[i])) return nodes;
|
|
6537
|
+
let k = 0;
|
|
6538
|
+
const parentId = child.parentId;
|
|
6539
|
+
const placed = nodes.map((n) => n.parentId === parentId && n.id !== parentId ? order[k++] : n);
|
|
6540
|
+
return parentsFirst(placed);
|
|
6541
|
+
}
|
|
6542
|
+
function parentsFirst(nodes) {
|
|
6543
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
6544
|
+
const pending = /* @__PURE__ */ new Map();
|
|
6545
|
+
const done = /* @__PURE__ */ new Set();
|
|
6546
|
+
const out = [];
|
|
6547
|
+
const emit = (n) => {
|
|
6548
|
+
out.push(n);
|
|
6549
|
+
done.add(n.id);
|
|
6550
|
+
const waiting = pending.get(n.id);
|
|
6551
|
+
if (waiting) {
|
|
6552
|
+
pending.delete(n.id);
|
|
6553
|
+
for (const w of waiting) emit(w);
|
|
6554
|
+
}
|
|
6555
|
+
};
|
|
6556
|
+
for (const n of nodes) {
|
|
6557
|
+
if (n.parentId && ids.has(n.parentId) && !done.has(n.parentId) && n.parentId !== n.id) {
|
|
6558
|
+
if (!pending.has(n.parentId)) pending.set(n.parentId, []);
|
|
6559
|
+
pending.get(n.parentId).push(n);
|
|
6560
|
+
} else emit(n);
|
|
6561
|
+
}
|
|
6562
|
+
for (const waiting of pending.values()) for (const n of waiting) if (!done.has(n.id)) emit(n);
|
|
6563
|
+
return out;
|
|
3389
6564
|
}
|
|
3390
6565
|
|
|
3391
6566
|
// src/canvas/arrangeController.ts
|
|
@@ -3406,6 +6581,16 @@ function createCanvasArrangeController(options) {
|
|
|
3406
6581
|
};
|
|
3407
6582
|
function apply(name, updates) {
|
|
3408
6583
|
if (updates.length === 0) return updates;
|
|
6584
|
+
if (options.autoLayout) {
|
|
6585
|
+
const layoutOptions = options.autoLayout === true ? { rectOptions: options.rectOptions } : options.autoLayout;
|
|
6586
|
+
const moved = applyNodeUpdates(options.getNodes(), updates);
|
|
6587
|
+
const layout2 = layoutUpMany(moved, updates.map((u) => u.id), layoutOptions);
|
|
6588
|
+
if (layout2.length > 0) {
|
|
6589
|
+
const byId = /* @__PURE__ */ new Map();
|
|
6590
|
+
for (const u of [...updates, ...layout2]) byId.set(u.id, { ...byId.get(u.id), ...u });
|
|
6591
|
+
updates = [...byId.values()];
|
|
6592
|
+
}
|
|
6593
|
+
}
|
|
3409
6594
|
const inverse = invertNodeUpdates(options.getNodes(), updates);
|
|
3410
6595
|
const run = (u) => options.setNodes(applyNodeUpdates(options.getNodes(), u));
|
|
3411
6596
|
if (options.history) {
|
|
@@ -3489,6 +6674,41 @@ function createCanvasArrangeController(options) {
|
|
|
3489
6674
|
divide: (ids, o) => controller.combineShapes("divide", ids, o),
|
|
3490
6675
|
/** Whether the shape operations can run on `ids` (default: the selection). */
|
|
3491
6676
|
canCombineShapes: (ids) => canCombineShapeNodes(options.getNodes(), selectedIds(ids)).ok,
|
|
6677
|
+
/**
|
|
6678
|
+
* Offset Path: grows (`distance > 0`) or shrinks (`distance < 0`) every shape among
|
|
6679
|
+
* `ids` (default: the selection) by `distance` flow units, keeping curves, as one
|
|
6680
|
+
* undoable step. Refuses without changing anything when a shape is locked, no shape is
|
|
6681
|
+
* selected or every result is empty.
|
|
6682
|
+
*/
|
|
6683
|
+
offsetShapes(distance, ids, offsetOptions = {}) {
|
|
6684
|
+
const before = options.getNodes();
|
|
6685
|
+
const result = offsetShapeNodes(before, selectedIds(ids), distance, { ...rect(), ...offsetOptions });
|
|
6686
|
+
if (!result.ok) return result;
|
|
6687
|
+
const run = () => options.setNodes(applyShapeReplacement(options.getNodes(), result));
|
|
6688
|
+
const undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));
|
|
6689
|
+
if (options.history) options.history.execute({ name: "Offset Path", execute: run, undo });
|
|
6690
|
+
else run();
|
|
6691
|
+
return result;
|
|
6692
|
+
},
|
|
6693
|
+
/** Whether `offsetShapes` can run on `ids` (default: the selection). */
|
|
6694
|
+
canOffsetShapes: (ids) => canOffsetShapeNodes(options.getNodes(), selectedIds(ids)).ok,
|
|
6695
|
+
/**
|
|
6696
|
+
* Outline Stroke: converts the strokes of the shapes among `ids` (default: the
|
|
6697
|
+
* selection) to filled path shapes, as one undoable step. Refuses when a shape is
|
|
6698
|
+
* locked or no selected shape has a visible stroke.
|
|
6699
|
+
*/
|
|
6700
|
+
outlineStroke(ids, outlineOptions = {}) {
|
|
6701
|
+
const before = options.getNodes();
|
|
6702
|
+
const result = outlineShapeNodeStrokes(before, selectedIds(ids), { ...rect(), ...outlineOptions });
|
|
6703
|
+
if (!result.ok) return result;
|
|
6704
|
+
const run = () => options.setNodes(applyShapeReplacement(options.getNodes(), result));
|
|
6705
|
+
const undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));
|
|
6706
|
+
if (options.history) options.history.execute({ name: "Outline Stroke", execute: run, undo });
|
|
6707
|
+
else run();
|
|
6708
|
+
return result;
|
|
6709
|
+
},
|
|
6710
|
+
/** Whether `outlineStroke` can run on `ids` (default: the selection). */
|
|
6711
|
+
canOutlineStroke: (ids) => canOutlineShapeNodeStrokes(options.getNodes(), selectedIds(ids)).ok,
|
|
3492
6712
|
/**
|
|
3493
6713
|
* Runs a shortcut action. Scene actions are not handled here (see the scenes store);
|
|
3494
6714
|
* returns `false` for them and for unknown actions.
|
|
@@ -3784,7 +7004,7 @@ function createCanvasArrangeController(options) {
|
|
|
3784
7004
|
}
|
|
3785
7005
|
|
|
3786
7006
|
// src/canvas/measure.ts
|
|
3787
|
-
var
|
|
7007
|
+
var EPS2 = 1e-6;
|
|
3788
7008
|
function measureLabel(distance) {
|
|
3789
7009
|
const v = Math.round(Math.abs(distance) * 100) / 100;
|
|
3790
7010
|
return Object.is(v, -0) ? "0" : String(v);
|
|
@@ -3796,13 +7016,13 @@ function line(kind, x1, y1, x2, y2) {
|
|
|
3796
7016
|
}
|
|
3797
7017
|
function measured(out, x1, y1, x2, y2) {
|
|
3798
7018
|
const d = Math.hypot(x2 - x1, y2 - y1);
|
|
3799
|
-
if (d >
|
|
7019
|
+
if (d > EPS2 && Number.isFinite(d)) out.push({ ...line("measure", x1, y1, x2, y2), label: measureLabel(d) });
|
|
3800
7020
|
}
|
|
3801
7021
|
function extension(out, x1, y1, x2, y2) {
|
|
3802
|
-
if (Math.hypot(x2 - x1, y2 - y1) >
|
|
7022
|
+
if (Math.hypot(x2 - x1, y2 - y1) > EPS2) out.push(line("extension", x1, y1, x2, y2));
|
|
3803
7023
|
}
|
|
3804
7024
|
function contains(outer, inner) {
|
|
3805
|
-
return outer.x0 <= inner.x0 +
|
|
7025
|
+
return outer.x0 <= inner.x0 + EPS2 && outer.y0 <= inner.y0 + EPS2 && outer.x1 >= inner.x1 - EPS2 && outer.y1 >= inner.y1 - EPS2;
|
|
3806
7026
|
}
|
|
3807
7027
|
function overlapMid(a0, a1, b0, b1) {
|
|
3808
7028
|
const lo = Math.max(a0, b0);
|
|
@@ -3899,7 +7119,727 @@ function isMeasureModifierHeld(event) {
|
|
|
3899
7119
|
if (event.type === "keyup" && event.key === "Alt") return false;
|
|
3900
7120
|
return event.altKey === true || event.type === "keydown" && event.key === "Alt";
|
|
3901
7121
|
}
|
|
7122
|
+
|
|
7123
|
+
// src/canvas/autoLayoutController.ts
|
|
7124
|
+
function mergeNodeUpdates(...lists) {
|
|
7125
|
+
const byId = /* @__PURE__ */ new Map();
|
|
7126
|
+
for (const list of lists) for (const u of list) byId.set(u.id, { ...byId.get(u.id), ...u });
|
|
7127
|
+
return [...byId.values()];
|
|
7128
|
+
}
|
|
7129
|
+
function relayoutUpdates(nodes, ids, options = {}) {
|
|
7130
|
+
return layoutUpMany(nodes, ids, options);
|
|
7131
|
+
}
|
|
7132
|
+
function createCanvasAutoLayoutController(options) {
|
|
7133
|
+
const layoutOptions = () => ({ measure: options.measure, rectOptions: options.rectOptions });
|
|
7134
|
+
const enabled = () => options.enabled !== false;
|
|
7135
|
+
const pendingIds = /* @__PURE__ */ new Set();
|
|
7136
|
+
const pendingResizes = /* @__PURE__ */ new Map();
|
|
7137
|
+
const resizeStarts = /* @__PURE__ */ new Map();
|
|
7138
|
+
let pendingAll = false;
|
|
7139
|
+
const dragStarts = /* @__PURE__ */ new Map();
|
|
7140
|
+
const pendingDrops = /* @__PURE__ */ new Set();
|
|
7141
|
+
let guides = [];
|
|
7142
|
+
const setGuides = (next) => {
|
|
7143
|
+
if (next.length === 0 && guides.length === 0) return;
|
|
7144
|
+
guides = next;
|
|
7145
|
+
options.onGuidesChange?.(guides);
|
|
7146
|
+
};
|
|
7147
|
+
function run(name, forward, backward, ids, extra = [], restore) {
|
|
7148
|
+
const before = options.getNodes();
|
|
7149
|
+
const structural = forward(before);
|
|
7150
|
+
const withExtra = applyNodeUpdates(structural, extra);
|
|
7151
|
+
const layout2 = enabled() || name === "Auto Layout" ? relayoutUpdates(withExtra, ids(withExtra), layoutOptions()) : [];
|
|
7152
|
+
const updates = mergeNodeUpdates(extra, layout2);
|
|
7153
|
+
if (structural === before && updates.length === 0) return updates;
|
|
7154
|
+
let inverse = invertNodeUpdates(structural, updates);
|
|
7155
|
+
if (restore) {
|
|
7156
|
+
inverse = mergeNodeUpdates(inverse, [{ id: restore.id, position: { ...restore.position } }]);
|
|
7157
|
+
}
|
|
7158
|
+
const execute = () => options.setNodes(applyNodeUpdates(forward(options.getNodes()), updates));
|
|
7159
|
+
const undo = () => options.setNodes(backward(applyNodeUpdates(options.getNodes(), inverse)));
|
|
7160
|
+
if (options.history) options.history.execute({ name, execute, undo });
|
|
7161
|
+
else execute();
|
|
7162
|
+
return updates;
|
|
7163
|
+
}
|
|
7164
|
+
const identity = (nodes) => nodes;
|
|
7165
|
+
const controller = {
|
|
7166
|
+
/** Drop marker of the active reorder drag (flow coordinates); empty otherwise. */
|
|
7167
|
+
get guides() {
|
|
7168
|
+
return guides;
|
|
7169
|
+
},
|
|
7170
|
+
/**
|
|
7171
|
+
* Lays out frame `frameId` and its auto layout ancestors (default: every frame) as one
|
|
7172
|
+
* (optionally undoable) step. Returns the updates.
|
|
7173
|
+
*/
|
|
7174
|
+
applyAutoLayout(frameId) {
|
|
7175
|
+
if (frameId === void 0) {
|
|
7176
|
+
const updates = layoutAll(options.getNodes(), layoutOptions());
|
|
7177
|
+
return run("Auto Layout", identity, identity, () => [], updates);
|
|
7178
|
+
}
|
|
7179
|
+
return run("Auto Layout", identity, identity, () => [frameId]);
|
|
7180
|
+
},
|
|
7181
|
+
/** Updates that relayout around `ids` on the current nodes, without applying them. */
|
|
7182
|
+
layoutUpdates(ids) {
|
|
7183
|
+
return relayoutUpdates(options.getNodes(), ids, layoutOptions());
|
|
7184
|
+
},
|
|
7185
|
+
/**
|
|
7186
|
+
* Applies the relayout around `ids` (frames, or nodes inside frames) directly, not
|
|
7187
|
+
* recorded in history: auto layout is derived state, re-run after any change.
|
|
7188
|
+
*/
|
|
7189
|
+
relayout(ids) {
|
|
7190
|
+
const updates = relayoutUpdates(options.getNodes(), ids, layoutOptions());
|
|
7191
|
+
if (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));
|
|
7192
|
+
return updates;
|
|
7193
|
+
},
|
|
7194
|
+
/**
|
|
7195
|
+
* Adds `node` (into the frame named by its `parentId`, at flow `index` when given) and
|
|
7196
|
+
* lays out its frame, as one step.
|
|
7197
|
+
*/
|
|
7198
|
+
addNode(node, addOptions = {}) {
|
|
7199
|
+
const forward = (nodes) => {
|
|
7200
|
+
if (nodes.some((n) => n.id === node.id)) return nodes;
|
|
7201
|
+
let next = [...nodes, node];
|
|
7202
|
+
if (addOptions.index !== void 0 && node.parentId) next = moveLayoutChild(next, node.id, addOptions.index);
|
|
7203
|
+
return next;
|
|
7204
|
+
};
|
|
7205
|
+
const backward = (nodes) => nodes.filter((n) => n.id !== node.id);
|
|
7206
|
+
return run("Add Node", forward, backward, () => [node.id]);
|
|
7207
|
+
},
|
|
7208
|
+
/** Removes nodes (and their descendants) and lays out the frames they leave, as one step. */
|
|
7209
|
+
removeNodes(ids) {
|
|
7210
|
+
const before = options.getNodes();
|
|
7211
|
+
const remove = new Set(ids);
|
|
7212
|
+
let grew = true;
|
|
7213
|
+
while (grew) {
|
|
7214
|
+
grew = false;
|
|
7215
|
+
for (const n of before) {
|
|
7216
|
+
if (n.parentId && remove.has(n.parentId) && !remove.has(n.id)) {
|
|
7217
|
+
remove.add(n.id);
|
|
7218
|
+
grew = true;
|
|
7219
|
+
}
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
const removed = before.map((n, i) => [i, n]).filter(([, n]) => remove.has(n.id));
|
|
7223
|
+
if (removed.length === 0) return [];
|
|
7224
|
+
const parents = [...new Set(removed.map(([, n]) => n.parentId).filter((p) => !!p && !remove.has(p)))];
|
|
7225
|
+
const forward = (nodes) => nodes.filter((n) => !remove.has(n.id));
|
|
7226
|
+
const backward = (nodes) => {
|
|
7227
|
+
const next = [...nodes];
|
|
7228
|
+
for (const [i, n] of removed) next.splice(Math.min(i, next.length), 0, n);
|
|
7229
|
+
return next;
|
|
7230
|
+
};
|
|
7231
|
+
return run("Delete", forward, backward, () => parents);
|
|
7232
|
+
},
|
|
7233
|
+
/**
|
|
7234
|
+
* Resizes node `id` (its children follow their constraints) and lays out around it,
|
|
7235
|
+
* as one step.
|
|
7236
|
+
*/
|
|
7237
|
+
resizeNode(id, size) {
|
|
7238
|
+
const nodes = options.getNodes();
|
|
7239
|
+
const node = nodes.find((n) => n.id === id);
|
|
7240
|
+
if (!node) return [];
|
|
7241
|
+
const old = getNodeSize(node, options.rectOptions);
|
|
7242
|
+
const update = { id };
|
|
7243
|
+
if (size.width !== void 0) update.width = size.width;
|
|
7244
|
+
if (size.height !== void 0) update.height = size.height;
|
|
7245
|
+
const resized = applyNodeUpdates(nodes, [update]);
|
|
7246
|
+
const constrained = mergeNodeUpdates([update], applyConstraints(resized, id, old, layoutOptions()));
|
|
7247
|
+
return run("Resize", identity, identity, () => [id], constrained);
|
|
7248
|
+
},
|
|
7249
|
+
/** Moves flow child `childId` to flow index `index` in its frame and lays it out, as one step. */
|
|
7250
|
+
moveChild(childId, index) {
|
|
7251
|
+
const before = options.getNodes();
|
|
7252
|
+
const order = before.map((n) => n.id);
|
|
7253
|
+
const forward = (nodes) => moveLayoutChild(nodes, childId, index);
|
|
7254
|
+
const backward = (nodes) => restoreOrder(nodes, order);
|
|
7255
|
+
if (forward(before) === before) return [];
|
|
7256
|
+
return run("Reorder", forward, backward, () => [childId]);
|
|
7257
|
+
},
|
|
7258
|
+
/** Where flow child `id` would drop if released at `point` (default: its centre). */
|
|
7259
|
+
getInsertTarget(id, point) {
|
|
7260
|
+
const nodes = options.getNodes();
|
|
7261
|
+
const frame = getLayoutParent(nodes, id);
|
|
7262
|
+
if (!frame) return void 0;
|
|
7263
|
+
const lookup = createNodeLookup(nodes);
|
|
7264
|
+
const rect = getNodeRect(lookup.get(id), lookup, options.rectOptions);
|
|
7265
|
+
const at = point ?? { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
|
|
7266
|
+
return getAutoLayoutInsertTarget(nodes, frame.id, at, { ...layoutOptions(), excludeId: id });
|
|
7267
|
+
},
|
|
7268
|
+
/** Records the start position of a reorder drag. Optional: `dragReorder` records it lazily. */
|
|
7269
|
+
beginReorderDrag(id) {
|
|
7270
|
+
const node = options.getNodes().find((n) => n.id === id);
|
|
7271
|
+
if (node && getLayoutParent(options.getNodes(), id)) dragStarts.set(id, { ...node.position });
|
|
7272
|
+
},
|
|
7273
|
+
/**
|
|
7274
|
+
* While flow child `id` is dragged (as currently positioned in `getNodes()`): updates the
|
|
7275
|
+
* drop marker. Returns the insert target.
|
|
7276
|
+
*/
|
|
7277
|
+
dragReorder(id, point) {
|
|
7278
|
+
if (options.dragReorder === false) return void 0;
|
|
7279
|
+
if (!dragStarts.has(id)) controller.beginReorderDrag(id);
|
|
7280
|
+
const target = controller.getInsertTarget(id, point);
|
|
7281
|
+
setGuides(target ? [target.guide] : []);
|
|
7282
|
+
return target;
|
|
7283
|
+
},
|
|
7284
|
+
/**
|
|
7285
|
+
* Ends a reorder drag: moves `id` to the slot under `point` (default: its centre) and lays
|
|
7286
|
+
* the frame out, as one undoable step whose undo puts the node back where the drag began.
|
|
7287
|
+
*/
|
|
7288
|
+
endReorderDrag(id, point) {
|
|
7289
|
+
setGuides([]);
|
|
7290
|
+
const start = dragStarts.get(id);
|
|
7291
|
+
dragStarts.delete(id);
|
|
7292
|
+
if (options.dragReorder === false || !getLayoutParent(options.getNodes(), id)) return [];
|
|
7293
|
+
const target = controller.getInsertTarget(id, point);
|
|
7294
|
+
if (!target) return [];
|
|
7295
|
+
const before = options.getNodes();
|
|
7296
|
+
const order = before.map((n) => n.id);
|
|
7297
|
+
const forward = (nodes) => moveLayoutChild(nodes, id, target.index);
|
|
7298
|
+
const backward = (nodes) => restoreOrder(nodes, order);
|
|
7299
|
+
return run("Reorder", forward, backward, () => [id], [], start && { id, position: start });
|
|
7300
|
+
},
|
|
7301
|
+
/**
|
|
7302
|
+
* Notes a batch of React Flow / SvelteFlow style node changes (before or after they are
|
|
7303
|
+
* applied) so `flush` can relayout: dimension changes (measured sizes, resize ends),
|
|
7304
|
+
* added and removed nodes, and position changes of flow children (reorder drags).
|
|
7305
|
+
* Returns the changes unchanged.
|
|
7306
|
+
*/
|
|
7307
|
+
noteChanges(changes) {
|
|
7308
|
+
const nodes = options.getNodes();
|
|
7309
|
+
const lookup = createNodeLookup(nodes);
|
|
7310
|
+
for (const c of changes) {
|
|
7311
|
+
const id = c.id ?? c.item?.id;
|
|
7312
|
+
if (c.type === "add" && c.item) {
|
|
7313
|
+
pendingIds.add(c.item.id);
|
|
7314
|
+
} else if (c.type === "remove" && id) {
|
|
7315
|
+
const parent = lookup.get(id)?.parentId;
|
|
7316
|
+
if (parent) pendingIds.add(parent);
|
|
7317
|
+
else pendingAll = true;
|
|
7318
|
+
} else if (c.type === "replace" && id) {
|
|
7319
|
+
pendingIds.add(id);
|
|
7320
|
+
} else if (c.type === "dimensions" && id) {
|
|
7321
|
+
const node = lookup.get(id);
|
|
7322
|
+
if (c.resizing === true) {
|
|
7323
|
+
if (node && !resizeStarts.has(id)) resizeStarts.set(id, getNodeSize(node, options.rectOptions));
|
|
7324
|
+
continue;
|
|
7325
|
+
}
|
|
7326
|
+
const start = resizeStarts.get(id);
|
|
7327
|
+
resizeStarts.delete(id);
|
|
7328
|
+
if (start) pendingResizes.set(id, start);
|
|
7329
|
+
pendingIds.add(id);
|
|
7330
|
+
} else if (c.type === "position" && id && options.dragReorder !== false) {
|
|
7331
|
+
if (!getLayoutParent(nodes, id)) continue;
|
|
7332
|
+
if (c.dragging === true) {
|
|
7333
|
+
if (!dragStarts.has(id)) {
|
|
7334
|
+
const node = lookup.get(id);
|
|
7335
|
+
if (node) dragStarts.set(id, { ...node.position });
|
|
7336
|
+
}
|
|
7337
|
+
} else if (c.dragging === false || dragStarts.has(id)) {
|
|
7338
|
+
pendingDrops.add(id);
|
|
7339
|
+
}
|
|
7340
|
+
}
|
|
7341
|
+
}
|
|
7342
|
+
return changes;
|
|
7343
|
+
},
|
|
7344
|
+
/**
|
|
7345
|
+
* Notes nodes that were already removed (e.g. SvelteFlow's `ondelete`), so `flush`
|
|
7346
|
+
* relayouts the frames they left. The nodes must still carry their `parentId`.
|
|
7347
|
+
*/
|
|
7348
|
+
noteRemoved(removed) {
|
|
7349
|
+
const gone = /* @__PURE__ */ new Set();
|
|
7350
|
+
const list = [...removed];
|
|
7351
|
+
for (const n of list) gone.add(n.id);
|
|
7352
|
+
for (const n of list) if (n.parentId && !gone.has(n.parentId)) pendingIds.add(n.parentId);
|
|
7353
|
+
},
|
|
7354
|
+
/** Whether `noteChanges` left work for `flush`. */
|
|
7355
|
+
get hasPending() {
|
|
7356
|
+
return pendingAll || pendingIds.size > 0 || pendingResizes.size > 0 || pendingDrops.size > 0 || dragStarts.size > 0;
|
|
7357
|
+
},
|
|
7358
|
+
/**
|
|
7359
|
+
* Applies the work noted by `noteChanges` to the current nodes: drop markers while
|
|
7360
|
+
* dragging, committed reorders on drop (undoable), and a relayout (not recorded) for
|
|
7361
|
+
* size, add and remove changes. Call it once the changes are applied.
|
|
7362
|
+
*/
|
|
7363
|
+
flush() {
|
|
7364
|
+
let applied = [];
|
|
7365
|
+
for (const id of pendingDrops) applied = mergeNodeUpdates(applied, controller.endReorderDrag(id));
|
|
7366
|
+
pendingDrops.clear();
|
|
7367
|
+
for (const id of dragStarts.keys()) controller.dragReorder(id);
|
|
7368
|
+
if (!enabled()) {
|
|
7369
|
+
pendingIds.clear();
|
|
7370
|
+
pendingResizes.clear();
|
|
7371
|
+
pendingAll = false;
|
|
7372
|
+
return applied;
|
|
7373
|
+
}
|
|
7374
|
+
let nodes = options.getNodes();
|
|
7375
|
+
let updates = [];
|
|
7376
|
+
for (const [id, previous] of pendingResizes) {
|
|
7377
|
+
if (!nodes.some((n) => n.id === id)) continue;
|
|
7378
|
+
const constrained = applyConstraints(nodes, id, previous, layoutOptions());
|
|
7379
|
+
nodes = applyNodeUpdates(nodes, constrained);
|
|
7380
|
+
updates = mergeNodeUpdates(updates, constrained);
|
|
7381
|
+
}
|
|
7382
|
+
const layout2 = pendingAll ? layoutAll(nodes, layoutOptions()) : relayoutUpdates(nodes, [...pendingIds].filter((id) => nodes.some((n) => n.id === id)), layoutOptions());
|
|
7383
|
+
updates = mergeNodeUpdates(updates, layout2);
|
|
7384
|
+
pendingIds.clear();
|
|
7385
|
+
pendingResizes.clear();
|
|
7386
|
+
pendingAll = false;
|
|
7387
|
+
if (updates.length > 0) options.setNodes(applyNodeUpdates(options.getNodes(), updates));
|
|
7388
|
+
return mergeNodeUpdates(applied, updates);
|
|
7389
|
+
},
|
|
7390
|
+
/** Whether `id` is an auto layout frame. */
|
|
7391
|
+
isFrame: (id) => isAutoLayoutFrame(options.getNodes().find((n) => n.id === id))
|
|
7392
|
+
};
|
|
7393
|
+
return controller;
|
|
7394
|
+
}
|
|
7395
|
+
function restoreOrder(nodes, order) {
|
|
7396
|
+
const rank = new Map(order.map((id, i) => [id, i]));
|
|
7397
|
+
const indexed = nodes.map((n, i) => ({ n, r: rank.get(n.id) ?? order.length + i }));
|
|
7398
|
+
indexed.sort((a, b) => a.r - b.r);
|
|
7399
|
+
return indexed.every((e, i) => e.n === nodes[i]) ? nodes : indexed.map((e) => e.n);
|
|
7400
|
+
}
|
|
7401
|
+
|
|
7402
|
+
// src/canvas/tokenBindings.ts
|
|
7403
|
+
var TOKEN_BINDINGS_DATA_KEY = "tokenBindings";
|
|
7404
|
+
var TOKEN_MODES_DATA_KEY = "tokenModes";
|
|
7405
|
+
var kebab = (prop) => prop.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
7406
|
+
function parseDeclarations(style) {
|
|
7407
|
+
const parts = [];
|
|
7408
|
+
let depth = 0;
|
|
7409
|
+
let quote = "";
|
|
7410
|
+
let start = 0;
|
|
7411
|
+
for (let i = 0; i < style.length; i++) {
|
|
7412
|
+
const ch = style[i];
|
|
7413
|
+
if (quote) {
|
|
7414
|
+
if (ch === "\\") i++;
|
|
7415
|
+
else if (ch === quote) quote = "";
|
|
7416
|
+
} else if (ch === '"' || ch === "'") quote = ch;
|
|
7417
|
+
else if (ch === "(") depth++;
|
|
7418
|
+
else if (ch === ")") depth = Math.max(0, depth - 1);
|
|
7419
|
+
else if (ch === ";" && depth === 0) {
|
|
7420
|
+
parts.push(style.slice(start, i));
|
|
7421
|
+
start = i + 1;
|
|
7422
|
+
}
|
|
7423
|
+
}
|
|
7424
|
+
parts.push(style.slice(start));
|
|
7425
|
+
const out = [];
|
|
7426
|
+
for (const part of parts) {
|
|
7427
|
+
const colon = part.indexOf(":");
|
|
7428
|
+
if (colon < 0) continue;
|
|
7429
|
+
const name = part.slice(0, colon).trim();
|
|
7430
|
+
if (name) out.push([name, part.slice(colon + 1).trim()]);
|
|
7431
|
+
}
|
|
7432
|
+
return out;
|
|
7433
|
+
}
|
|
7434
|
+
function readNodeStyleProperty(style, prop) {
|
|
7435
|
+
if (!style) return void 0;
|
|
7436
|
+
if (typeof style === "object") return style[prop];
|
|
7437
|
+
const name = kebab(prop);
|
|
7438
|
+
let found;
|
|
7439
|
+
for (const [key, value] of parseDeclarations(style)) if (key.toLowerCase() === name) found = value;
|
|
7440
|
+
return found;
|
|
7441
|
+
}
|
|
7442
|
+
function writeNodeStyleProperty(style, prop, value, unitless = false) {
|
|
7443
|
+
if (typeof style === "string") {
|
|
7444
|
+
const name = kebab(prop);
|
|
7445
|
+
const text = value === void 0 ? void 0 : typeof value === "number" && !unitless ? `${value}px` : String(value);
|
|
7446
|
+
const decls = [];
|
|
7447
|
+
let placed = text === void 0;
|
|
7448
|
+
for (const [key, current] of parseDeclarations(style)) {
|
|
7449
|
+
if (key.toLowerCase() !== name) decls.push([key, current]);
|
|
7450
|
+
else if (!placed) {
|
|
7451
|
+
decls.push([name, text]);
|
|
7452
|
+
placed = true;
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7455
|
+
if (!placed) decls.push([name, text]);
|
|
7456
|
+
return decls.map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
7457
|
+
}
|
|
7458
|
+
const next = { ...style ?? {} };
|
|
7459
|
+
if (value === void 0) delete next[prop];
|
|
7460
|
+
else next[prop] = value;
|
|
7461
|
+
return next;
|
|
7462
|
+
}
|
|
7463
|
+
function px(value) {
|
|
7464
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
7465
|
+
if (typeof value === "string") {
|
|
7466
|
+
const n = parseFloat(value);
|
|
7467
|
+
return Number.isFinite(n) ? n : void 0;
|
|
7468
|
+
}
|
|
7469
|
+
return void 0;
|
|
7470
|
+
}
|
|
7471
|
+
function withStyle(node, prop, value, unitless = false) {
|
|
7472
|
+
const current = readNodeStyleProperty(node.style, prop);
|
|
7473
|
+
const written = typeof node.style === "string" && typeof value === "number" && !unitless ? `${value}px` : value;
|
|
7474
|
+
if (typeof node.style === "string" ? current === String(written) : current === value) return node;
|
|
7475
|
+
return { ...node, style: writeNodeStyleProperty(node.style, prop, value, unitless) };
|
|
7476
|
+
}
|
|
7477
|
+
function shapeOf(node) {
|
|
7478
|
+
const shape = node.data?.shape;
|
|
7479
|
+
return isShapeDescription(shape) ? shape : null;
|
|
7480
|
+
}
|
|
7481
|
+
function withShape(node, key, value) {
|
|
7482
|
+
const shape = shapeOf(node);
|
|
7483
|
+
if (shape[key] === value) return node;
|
|
7484
|
+
return { ...node, data: { ...node.data, shape: { ...shape, [key]: value } } };
|
|
7485
|
+
}
|
|
7486
|
+
var nonNegative = (v) => Math.max(0, v);
|
|
7487
|
+
function styleOrShape(key, styleProp, convert = (v) => v) {
|
|
7488
|
+
return {
|
|
7489
|
+
read(node) {
|
|
7490
|
+
const shape = shapeOf(node);
|
|
7491
|
+
if (shape) return shape[key];
|
|
7492
|
+
const value = readNodeStyleProperty(node.style, styleProp(node));
|
|
7493
|
+
return key === "strokeWidth" ? px(value) : value;
|
|
7494
|
+
},
|
|
7495
|
+
write(node, value) {
|
|
7496
|
+
const v = convert(value);
|
|
7497
|
+
if (shapeOf(node)) return withShape(node, key, v);
|
|
7498
|
+
let next = withStyle(node, styleProp(node), v);
|
|
7499
|
+
if (key === "strokeWidth" && readNodeStyleProperty(next.style, "borderStyle") === void 0) {
|
|
7500
|
+
next = { ...next, style: writeNodeStyleProperty(next.style, "borderStyle", "solid") };
|
|
7501
|
+
}
|
|
7502
|
+
return next;
|
|
7503
|
+
}
|
|
7504
|
+
};
|
|
7505
|
+
}
|
|
7506
|
+
function backgroundProp(node) {
|
|
7507
|
+
return readNodeStyleProperty(node.style, "backgroundColor") === void 0 && readNodeStyleProperty(node.style, "background") !== void 0 ? "background" : "backgroundColor";
|
|
7508
|
+
}
|
|
7509
|
+
function layoutProperty(key, label, read, write) {
|
|
7510
|
+
return {
|
|
7511
|
+
key,
|
|
7512
|
+
label,
|
|
7513
|
+
type: "number",
|
|
7514
|
+
scope: "GAP",
|
|
7515
|
+
appliesTo: (node) => getAutoLayout(node) !== void 0,
|
|
7516
|
+
read: (node) => {
|
|
7517
|
+
const layout2 = getAutoLayout(node);
|
|
7518
|
+
return layout2 ? read(layout2) : void 0;
|
|
7519
|
+
},
|
|
7520
|
+
write: (node, value) => {
|
|
7521
|
+
const layout2 = getAutoLayout(node);
|
|
7522
|
+
if (!layout2 || read(layout2) === value) return node;
|
|
7523
|
+
return { ...node, data: { ...node.data, [AUTO_LAYOUT_DATA_KEY]: write(layout2, value) } };
|
|
7524
|
+
}
|
|
7525
|
+
};
|
|
7526
|
+
}
|
|
7527
|
+
var SIDES = ["top", "right", "bottom", "left"];
|
|
7528
|
+
function paddingProperty(key, label, sides) {
|
|
7529
|
+
return layoutProperty(
|
|
7530
|
+
key,
|
|
7531
|
+
label,
|
|
7532
|
+
(layout2) => {
|
|
7533
|
+
const p = resolvePadding(layout2.padding);
|
|
7534
|
+
const values = sides.map((s) => p[SIDES.indexOf(s)]);
|
|
7535
|
+
return values.every((v) => v === values[0]) ? values[0] : void 0;
|
|
7536
|
+
},
|
|
7537
|
+
(layout2, value) => {
|
|
7538
|
+
const p = resolvePadding(layout2.padding);
|
|
7539
|
+
const v = Math.max(0, value);
|
|
7540
|
+
const padding = { top: p[0], right: p[1], bottom: p[2], left: p[3] };
|
|
7541
|
+
for (const s of sides) padding[s] = v;
|
|
7542
|
+
return { ...layout2, padding };
|
|
7543
|
+
}
|
|
7544
|
+
);
|
|
7545
|
+
}
|
|
7546
|
+
var CANVAS_TOKEN_PROPERTIES = [
|
|
7547
|
+
{
|
|
7548
|
+
key: "fill",
|
|
7549
|
+
label: "Fill",
|
|
7550
|
+
type: "color",
|
|
7551
|
+
scope: (node) => shapeOf(node) ? "SHAPE_FILL" : node.type === "text" ? "TEXT_FILL" : "FRAME_FILL",
|
|
7552
|
+
appliesTo: () => true,
|
|
7553
|
+
...styleOrShape("fill", backgroundProp)
|
|
7554
|
+
},
|
|
7555
|
+
{
|
|
7556
|
+
key: "stroke",
|
|
7557
|
+
label: "Stroke",
|
|
7558
|
+
type: "color",
|
|
7559
|
+
scope: "STROKE_COLOR",
|
|
7560
|
+
appliesTo: () => true,
|
|
7561
|
+
...styleOrShape("stroke", () => "borderColor")
|
|
7562
|
+
},
|
|
7563
|
+
{
|
|
7564
|
+
key: "strokeWidth",
|
|
7565
|
+
label: "Stroke width",
|
|
7566
|
+
type: "number",
|
|
7567
|
+
scope: "STROKE_FLOAT",
|
|
7568
|
+
appliesTo: () => true,
|
|
7569
|
+
...styleOrShape("strokeWidth", () => "borderWidth", nonNegative)
|
|
7570
|
+
},
|
|
7571
|
+
{
|
|
7572
|
+
key: "opacity",
|
|
7573
|
+
label: "Opacity",
|
|
7574
|
+
type: "number",
|
|
7575
|
+
scope: "OPACITY",
|
|
7576
|
+
appliesTo: () => true,
|
|
7577
|
+
// Opacity variables are percentages (figcraft / Figma): 40 → 0.4, clamped to 0–100.
|
|
7578
|
+
read: (node) => {
|
|
7579
|
+
const v = px(readNodeStyleProperty(node.style, "opacity"));
|
|
7580
|
+
return v === void 0 ? void 0 : Math.round(v * 1e4) / 100;
|
|
7581
|
+
},
|
|
7582
|
+
write: (node, value) => withStyle(node, "opacity", Math.min(1, Math.max(0, value / 100)), true)
|
|
7583
|
+
},
|
|
7584
|
+
{
|
|
7585
|
+
key: "cornerRadius",
|
|
7586
|
+
label: "Corner radius",
|
|
7587
|
+
type: "number",
|
|
7588
|
+
scope: "CORNER_RADIUS",
|
|
7589
|
+
appliesTo: (node) => {
|
|
7590
|
+
const shape = shapeOf(node);
|
|
7591
|
+
return !shape || shape.kind === "rect";
|
|
7592
|
+
},
|
|
7593
|
+
read: (node) => {
|
|
7594
|
+
const shape = shapeOf(node);
|
|
7595
|
+
if (shape) return typeof shape.cornerRadius === "number" ? shape.cornerRadius : void 0;
|
|
7596
|
+
return px(readNodeStyleProperty(node.style, "borderRadius"));
|
|
7597
|
+
},
|
|
7598
|
+
write: (node, value) => {
|
|
7599
|
+
const v = Math.max(0, value);
|
|
7600
|
+
return shapeOf(node) ? withShape(node, "cornerRadius", v) : withStyle(node, "borderRadius", v);
|
|
7601
|
+
}
|
|
7602
|
+
},
|
|
7603
|
+
layoutProperty("gap", "Gap", (l) => typeof l.gap === "number" ? l.gap : 0, (l, v) => ({ ...l, gap: v })),
|
|
7604
|
+
layoutProperty("rowGap", "Row gap", (l) => typeof l.rowGap === "number" ? l.rowGap : void 0, (l, v) => ({ ...l, rowGap: v })),
|
|
7605
|
+
paddingProperty("padding", "Padding", ["top", "right", "bottom", "left"]),
|
|
7606
|
+
paddingProperty("paddingHorizontal", "Horizontal padding", ["left", "right"]),
|
|
7607
|
+
paddingProperty("paddingVertical", "Vertical padding", ["top", "bottom"]),
|
|
7608
|
+
paddingProperty("paddingTop", "Top padding", ["top"]),
|
|
7609
|
+
paddingProperty("paddingRight", "Right padding", ["right"]),
|
|
7610
|
+
paddingProperty("paddingBottom", "Bottom padding", ["bottom"]),
|
|
7611
|
+
paddingProperty("paddingLeft", "Left padding", ["left"]),
|
|
7612
|
+
{
|
|
7613
|
+
key: "textColor",
|
|
7614
|
+
label: "Text colour",
|
|
7615
|
+
type: "color",
|
|
7616
|
+
scope: "TEXT_FILL",
|
|
7617
|
+
appliesTo: (node) => !shapeOf(node),
|
|
7618
|
+
read: (node) => readNodeStyleProperty(node.style, "color"),
|
|
7619
|
+
write: (node, value) => withStyle(node, "color", value)
|
|
7620
|
+
},
|
|
7621
|
+
{
|
|
7622
|
+
key: "fontSize",
|
|
7623
|
+
label: "Font size",
|
|
7624
|
+
type: "number",
|
|
7625
|
+
scope: "FONT_SIZE",
|
|
7626
|
+
appliesTo: (node) => !shapeOf(node),
|
|
7627
|
+
read: (node) => px(readNodeStyleProperty(node.style, "fontSize")),
|
|
7628
|
+
write: (node, value) => withStyle(node, "fontSize", Math.max(0, value))
|
|
7629
|
+
},
|
|
7630
|
+
{
|
|
7631
|
+
key: "visible",
|
|
7632
|
+
label: "Visible",
|
|
7633
|
+
type: "boolean",
|
|
7634
|
+
appliesTo: () => true,
|
|
7635
|
+
read: (node) => !node.hidden,
|
|
7636
|
+
write: (node, value) => !node.hidden === value ? node : { ...node, hidden: !value }
|
|
7637
|
+
}
|
|
7638
|
+
];
|
|
7639
|
+
var EMPTY = Object.freeze({});
|
|
7640
|
+
function stringRecord(value) {
|
|
7641
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return EMPTY;
|
|
7642
|
+
const out = {};
|
|
7643
|
+
for (const [k, v] of Object.entries(value)) if (typeof v === "string") out[k] = v;
|
|
7644
|
+
return out;
|
|
7645
|
+
}
|
|
7646
|
+
function getNodeTokenBindings(node) {
|
|
7647
|
+
return stringRecord(node?.data?.[TOKEN_BINDINGS_DATA_KEY]);
|
|
7648
|
+
}
|
|
7649
|
+
function getNodeTokenModes(node) {
|
|
7650
|
+
return stringRecord(node?.data?.[TOKEN_MODES_DATA_KEY]);
|
|
7651
|
+
}
|
|
7652
|
+
function getCanvasTokenProperty(key, options = {}) {
|
|
7653
|
+
return (options.properties ?? CANVAS_TOKEN_PROPERTIES).find((p) => p.key === key);
|
|
7654
|
+
}
|
|
7655
|
+
function getBindableTokenProperties(node, options = {}) {
|
|
7656
|
+
return (options.properties ?? CANVAS_TOKEN_PROPERTIES).filter((p) => p.appliesTo(node));
|
|
7657
|
+
}
|
|
7658
|
+
function getTokenPropertyScope(property, node) {
|
|
7659
|
+
return typeof property.scope === "function" ? property.scope(node) : property.scope;
|
|
7660
|
+
}
|
|
7661
|
+
function getTokensForProperty(state, property, node) {
|
|
7662
|
+
const scope = getTokenPropertyScope(property, node);
|
|
7663
|
+
return state.variables.filter((v) => v.type === property.type && isTokenInScope(v, scope));
|
|
7664
|
+
}
|
|
7665
|
+
function lookupOf(nodes) {
|
|
7666
|
+
return new Map(nodes.map((n) => [n.id, n]));
|
|
7667
|
+
}
|
|
7668
|
+
function validModes(state, modes, into) {
|
|
7669
|
+
for (const [collectionId, modeId] of Object.entries(modes)) {
|
|
7670
|
+
if (state) {
|
|
7671
|
+
const collection = getTokenCollection(state, collectionId);
|
|
7672
|
+
if (!collection || !collection.modes.some((m) => m.id === modeId)) continue;
|
|
7673
|
+
}
|
|
7674
|
+
into[collectionId] = modeId;
|
|
7675
|
+
}
|
|
7676
|
+
}
|
|
7677
|
+
function getEffectiveTokenModes(node, nodes, state, options = {}) {
|
|
7678
|
+
const lookup = nodes instanceof Map ? nodes : lookupOf(nodes);
|
|
7679
|
+
const chain = [];
|
|
7680
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7681
|
+
for (let at = node; at && !seen.has(at.id); at = at.parentId ? lookup.get(at.parentId) : void 0) {
|
|
7682
|
+
seen.add(at.id);
|
|
7683
|
+
chain.push(at);
|
|
7684
|
+
}
|
|
7685
|
+
const out = {};
|
|
7686
|
+
if (options.modes) validModes(state, options.modes, out);
|
|
7687
|
+
for (let i = chain.length - 1; i >= 0; i--) validModes(state, getNodeTokenModes(chain[i]), out);
|
|
7688
|
+
return out;
|
|
7689
|
+
}
|
|
7690
|
+
function modesResolver(nodes, state, options) {
|
|
7691
|
+
const lookup = lookupOf(nodes);
|
|
7692
|
+
const memo = /* @__PURE__ */ new Map();
|
|
7693
|
+
const root = {};
|
|
7694
|
+
if (options.modes) validModes(state, options.modes, root);
|
|
7695
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
7696
|
+
const modesOf = (node) => {
|
|
7697
|
+
const cached = memo.get(node.id);
|
|
7698
|
+
if (cached) return cached;
|
|
7699
|
+
let base = root;
|
|
7700
|
+
if (node.parentId && !visiting.has(node.id)) {
|
|
7701
|
+
const parent = lookup.get(node.parentId);
|
|
7702
|
+
if (parent) {
|
|
7703
|
+
visiting.add(node.id);
|
|
7704
|
+
base = modesOf(parent);
|
|
7705
|
+
visiting.delete(node.id);
|
|
7706
|
+
}
|
|
7707
|
+
}
|
|
7708
|
+
const own = getNodeTokenModes(node);
|
|
7709
|
+
let result = base;
|
|
7710
|
+
if (Object.keys(own).length > 0) {
|
|
7711
|
+
result = { ...base };
|
|
7712
|
+
validModes(state, own, result);
|
|
7713
|
+
}
|
|
7714
|
+
memo.set(node.id, result);
|
|
7715
|
+
return result;
|
|
7716
|
+
};
|
|
7717
|
+
return { lookup, modesOf };
|
|
7718
|
+
}
|
|
7719
|
+
function applyBindings(node, state, modes, options, only) {
|
|
7720
|
+
const bindings = getNodeTokenBindings(node);
|
|
7721
|
+
let next = node;
|
|
7722
|
+
for (const [key, variableId] of Object.entries(bindings)) {
|
|
7723
|
+
if (only && !only.has(variableId)) continue;
|
|
7724
|
+
const property = getCanvasTokenProperty(key, options);
|
|
7725
|
+
if (!property || !property.appliesTo(next)) continue;
|
|
7726
|
+
const resolved = resolveToken(state, variableId, modes);
|
|
7727
|
+
if (!resolved.ok || resolved.type !== property.type) continue;
|
|
7728
|
+
next = property.write(next, resolved.value);
|
|
7729
|
+
}
|
|
7730
|
+
return next;
|
|
7731
|
+
}
|
|
7732
|
+
function resolveCanvasTokens(nodes, state, options = {}) {
|
|
7733
|
+
const { modesOf } = modesResolver(nodes, state, options);
|
|
7734
|
+
let changed = false;
|
|
7735
|
+
const out = nodes.map((node) => {
|
|
7736
|
+
if (Object.keys(getNodeTokenBindings(node)).length === 0) return node;
|
|
7737
|
+
const next = applyBindings(node, state, modesOf(node), options);
|
|
7738
|
+
if (next !== node) changed = true;
|
|
7739
|
+
return next;
|
|
7740
|
+
});
|
|
7741
|
+
return changed ? out : nodes;
|
|
7742
|
+
}
|
|
7743
|
+
function inspectNodeTokens(node, nodes, state, options = {}) {
|
|
7744
|
+
const modes = getEffectiveTokenModes(node, nodes, state, options);
|
|
7745
|
+
const out = [];
|
|
7746
|
+
for (const [key, variableId] of Object.entries(getNodeTokenBindings(node))) {
|
|
7747
|
+
const property = getCanvasTokenProperty(key, options);
|
|
7748
|
+
if (!property) continue;
|
|
7749
|
+
out.push({ property, variableId, variable: getTokenVariable(state, variableId), resolution: resolveToken(state, variableId, modes) });
|
|
7750
|
+
}
|
|
7751
|
+
return out;
|
|
7752
|
+
}
|
|
7753
|
+
function setDataRecord(node, key, record) {
|
|
7754
|
+
const data = { ...node.data };
|
|
7755
|
+
if (Object.keys(record).length === 0) delete data[key];
|
|
7756
|
+
else data[key] = record;
|
|
7757
|
+
return { ...node, data };
|
|
7758
|
+
}
|
|
7759
|
+
function bindCanvasToken(nodes, ids, propertyKey, variableId, state, options = {}) {
|
|
7760
|
+
const property = getCanvasTokenProperty(propertyKey, options);
|
|
7761
|
+
if (!property) throw new TokenError("not-found", `No bindable property \`${propertyKey}\``);
|
|
7762
|
+
if (variableId !== null) {
|
|
7763
|
+
const variable = getTokenVariable(state, variableId);
|
|
7764
|
+
if (!variable) throw new TokenError("not-found", `No variable \`${variableId}\``);
|
|
7765
|
+
if (variable.type !== property.type) {
|
|
7766
|
+
throw new TokenError("type-mismatch", `${property.label} takes a ${property.type} variable; \`${variable.name}\` is a ${variable.type}`);
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
const targets = new Set(ids);
|
|
7770
|
+
let changed = false;
|
|
7771
|
+
const bound = nodes.map((node) => {
|
|
7772
|
+
if (!targets.has(node.id) || !property.appliesTo(node)) return node;
|
|
7773
|
+
const bindings = { ...getNodeTokenBindings(node) };
|
|
7774
|
+
if (variableId === null) {
|
|
7775
|
+
if (!(propertyKey in bindings)) return node;
|
|
7776
|
+
delete bindings[propertyKey];
|
|
7777
|
+
} else {
|
|
7778
|
+
if (bindings[propertyKey] === variableId) return node;
|
|
7779
|
+
bindings[propertyKey] = variableId;
|
|
7780
|
+
}
|
|
7781
|
+
changed = true;
|
|
7782
|
+
return setDataRecord(node, TOKEN_BINDINGS_DATA_KEY, bindings);
|
|
7783
|
+
});
|
|
7784
|
+
return changed ? resolveCanvasTokens(bound, state, options) : nodes;
|
|
7785
|
+
}
|
|
7786
|
+
function setCanvasTokenMode(nodes, ids, collectionId, modeId, state, options = {}) {
|
|
7787
|
+
if (modeId !== null) {
|
|
7788
|
+
const collection = getTokenCollection(state, collectionId);
|
|
7789
|
+
if (!collection) throw new TokenError("not-found", `No collection \`${collectionId}\``);
|
|
7790
|
+
if (!collection.modes.some((m) => m.id === modeId)) throw new TokenError("not-found", `No mode \`${modeId}\` in \`${collection.name}\``);
|
|
7791
|
+
}
|
|
7792
|
+
const targets = new Set(ids);
|
|
7793
|
+
let changed = false;
|
|
7794
|
+
const next = nodes.map((node) => {
|
|
7795
|
+
if (!targets.has(node.id)) return node;
|
|
7796
|
+
const modes = { ...getNodeTokenModes(node) };
|
|
7797
|
+
if (modeId === null) {
|
|
7798
|
+
if (!(collectionId in modes)) return node;
|
|
7799
|
+
delete modes[collectionId];
|
|
7800
|
+
} else {
|
|
7801
|
+
if (modes[collectionId] === modeId) return node;
|
|
7802
|
+
modes[collectionId] = modeId;
|
|
7803
|
+
}
|
|
7804
|
+
changed = true;
|
|
7805
|
+
return setDataRecord(node, TOKEN_MODES_DATA_KEY, modes);
|
|
7806
|
+
});
|
|
7807
|
+
return changed ? resolveCanvasTokens(next, state, options) : nodes;
|
|
7808
|
+
}
|
|
7809
|
+
function detachTokenBindings(nodes, state, variableIds, options = {}) {
|
|
7810
|
+
const only = variableIds ? new Set(variableIds) : null;
|
|
7811
|
+
const isDetached = (id) => only ? only.has(id) : !getTokenVariable(state, id);
|
|
7812
|
+
const { modesOf } = modesResolver(nodes, state, options);
|
|
7813
|
+
let changed = false;
|
|
7814
|
+
const out = nodes.map((node) => {
|
|
7815
|
+
const bindings = getNodeTokenBindings(node);
|
|
7816
|
+
const gone = Object.entries(bindings).filter(([, id]) => isDetached(id));
|
|
7817
|
+
if (gone.length === 0) return node;
|
|
7818
|
+
let next = applyBindings(node, state, modesOf(node), options, new Set(gone.map(([, id]) => id)));
|
|
7819
|
+
const kept = { ...bindings };
|
|
7820
|
+
for (const [key] of gone) delete kept[key];
|
|
7821
|
+
next = setDataRecord(next, TOKEN_BINDINGS_DATA_KEY, kept);
|
|
7822
|
+
changed = true;
|
|
7823
|
+
return next;
|
|
7824
|
+
});
|
|
7825
|
+
return changed ? out : nodes;
|
|
7826
|
+
}
|
|
7827
|
+
function pruneCanvasTokenModes(nodes, state) {
|
|
7828
|
+
let changed = false;
|
|
7829
|
+
const out = nodes.map((node) => {
|
|
7830
|
+
const modes = getNodeTokenModes(node);
|
|
7831
|
+
const valid = {};
|
|
7832
|
+
validModes(state, modes, valid);
|
|
7833
|
+
if (Object.keys(valid).length === Object.keys(modes).length) return node;
|
|
7834
|
+
changed = true;
|
|
7835
|
+
return setDataRecord(node, TOKEN_MODES_DATA_KEY, valid);
|
|
7836
|
+
});
|
|
7837
|
+
return changed ? out : nodes;
|
|
7838
|
+
}
|
|
3902
7839
|
export {
|
|
7840
|
+
AUTO_LAYOUT_DATA_KEY,
|
|
7841
|
+
CANVAS_TOKEN_PROPERTIES,
|
|
7842
|
+
CHILD_LAYOUT_DATA_KEY,
|
|
3903
7843
|
CONNECTION_LINE_TYPES,
|
|
3904
7844
|
CanvasScenesStore,
|
|
3905
7845
|
DEFAULT_ARRANGE_SHORTCUTS,
|
|
@@ -3909,30 +7849,51 @@ export {
|
|
|
3909
7849
|
LOCKED_DATA_KEY,
|
|
3910
7850
|
LOCKED_PREVIOUS_FLAGS_KEY,
|
|
3911
7851
|
ManualClock,
|
|
7852
|
+
PathOpsError,
|
|
3912
7853
|
RESIZE_HANDLES,
|
|
3913
7854
|
SHAPE_DATA_KEY,
|
|
3914
7855
|
SHAPE_KINDS,
|
|
3915
7856
|
SHAPE_NODE_TYPE,
|
|
3916
7857
|
SHAPE_OPERATIONS,
|
|
3917
7858
|
StreamingLayoutManager,
|
|
7859
|
+
TOKEN_BINDINGS_DATA_KEY,
|
|
7860
|
+
TOKEN_MODES_DATA_KEY,
|
|
3918
7861
|
alignNodes,
|
|
7862
|
+
applyConstraints,
|
|
3919
7863
|
applyNodeUpdates,
|
|
3920
7864
|
applyShapeCombination,
|
|
7865
|
+
applyShapeReplacement,
|
|
7866
|
+
arcToCubics,
|
|
7867
|
+
bezierPathArea,
|
|
7868
|
+
bezierPathBounds,
|
|
7869
|
+
bezierPathLength,
|
|
7870
|
+
bezierPathPointAtLength,
|
|
7871
|
+
bezierPathSignedArea,
|
|
7872
|
+
bezierPathToSvg,
|
|
7873
|
+
bindCanvasToken,
|
|
3921
7874
|
blendLayout,
|
|
3922
7875
|
boundsOf,
|
|
3923
7876
|
calculateDagreReflowDisplacement,
|
|
3924
7877
|
calculateDirectionalDisplacement,
|
|
3925
7878
|
canCombineShapeNodes,
|
|
7879
|
+
canOffsetShapeNodes,
|
|
7880
|
+
canOutlineShapeNodeStrokes,
|
|
3926
7881
|
canvasHeight,
|
|
3927
7882
|
centerNodes,
|
|
3928
7883
|
centre,
|
|
7884
|
+
clampLayoutSize,
|
|
7885
|
+
closeContour,
|
|
3929
7886
|
collectDescendantIds,
|
|
3930
7887
|
combineShapeNodes,
|
|
3931
7888
|
computeNodesBoundingBox,
|
|
3932
7889
|
computeSnapGuides,
|
|
3933
7890
|
connectedOnly,
|
|
7891
|
+
contourSignedArea,
|
|
7892
|
+
contoursToPath,
|
|
7893
|
+
countPathSegments,
|
|
3934
7894
|
createBezierTrajectory,
|
|
3935
7895
|
createCanvasArrangeController,
|
|
7896
|
+
createCanvasAutoLayoutController,
|
|
3936
7897
|
createCanvasScenesStore,
|
|
3937
7898
|
createFanOutTrajectory,
|
|
3938
7899
|
createGuideIndex,
|
|
@@ -3940,12 +7901,17 @@ export {
|
|
|
3940
7901
|
createNodeLookup,
|
|
3941
7902
|
cubicPointAt,
|
|
3942
7903
|
cubicsToPathData,
|
|
7904
|
+
dashContours,
|
|
3943
7905
|
defaultEasing,
|
|
7906
|
+
detachTokenBindings,
|
|
7907
|
+
distanceToBezierPath,
|
|
3944
7908
|
distributeNodes,
|
|
7909
|
+
dividePaths,
|
|
3945
7910
|
dividePolygons,
|
|
3946
7911
|
ensureLayout,
|
|
3947
7912
|
estimateCanvasHeight,
|
|
3948
7913
|
estimateNodeSize,
|
|
7914
|
+
excludePaths,
|
|
3949
7915
|
findConnectorSnap,
|
|
3950
7916
|
fitCubicRun,
|
|
3951
7917
|
fitCubics,
|
|
@@ -3957,18 +7923,36 @@ export {
|
|
|
3957
7923
|
flowSide,
|
|
3958
7924
|
formatNumber2 as formatNumber,
|
|
3959
7925
|
getAbsolutePosition,
|
|
7926
|
+
getAutoLayout,
|
|
7927
|
+
getAutoLayoutGrid,
|
|
7928
|
+
getAutoLayoutInsertTarget,
|
|
7929
|
+
getBindableTokenProperties,
|
|
7930
|
+
getCanvasTokenProperty,
|
|
3960
7931
|
getCenteredNodePosition,
|
|
7932
|
+
getChildLayout,
|
|
7933
|
+
getEffectiveTokenModes,
|
|
7934
|
+
getFlowChildren,
|
|
7935
|
+
getLayoutParent,
|
|
7936
|
+
getLayoutSizing,
|
|
3961
7937
|
getLayoutedNodes,
|
|
3962
7938
|
getNodeRect,
|
|
3963
7939
|
getNodeShape,
|
|
3964
7940
|
getNodeSize,
|
|
7941
|
+
getNodeTokenBindings,
|
|
7942
|
+
getNodeTokenModes,
|
|
3965
7943
|
getResizingNodeRect,
|
|
3966
7944
|
getShapeRenderData,
|
|
3967
7945
|
getShapeSvg,
|
|
7946
|
+
getTokenPropertyScope,
|
|
7947
|
+
getTokensForProperty,
|
|
3968
7948
|
handlePoint,
|
|
7949
|
+
hasVisibleStroke,
|
|
3969
7950
|
inferResizeHandle,
|
|
7951
|
+
inspectNodeTokens,
|
|
7952
|
+
intersectPaths,
|
|
3970
7953
|
intersectPolygons,
|
|
3971
7954
|
invertNodeUpdates,
|
|
7955
|
+
isAutoLayoutFrame,
|
|
3972
7956
|
isEventFromTextInput,
|
|
3973
7957
|
isLocked,
|
|
3974
7958
|
isMeasureModifierHeld,
|
|
@@ -3979,43 +7963,75 @@ export {
|
|
|
3979
7963
|
isValidCanvasNode,
|
|
3980
7964
|
isValidCanvasScene,
|
|
3981
7965
|
isValidUrl,
|
|
7966
|
+
layoutAll,
|
|
3982
7967
|
layoutBounds,
|
|
7968
|
+
layoutFrame,
|
|
7969
|
+
layoutUp,
|
|
7970
|
+
layoutUpMany,
|
|
3983
7971
|
linearPositionTrajectory,
|
|
3984
7972
|
linearTrajectory,
|
|
3985
7973
|
lockNodes,
|
|
7974
|
+
makeSizingCoherent,
|
|
3986
7975
|
matchArrangeShortcut,
|
|
3987
7976
|
matchSize,
|
|
3988
7977
|
measureBetween,
|
|
3989
7978
|
measureLabel,
|
|
3990
7979
|
measureSelection,
|
|
3991
7980
|
measureToLine,
|
|
7981
|
+
mergeNodeUpdates,
|
|
7982
|
+
moveLayoutChild,
|
|
3992
7983
|
multiPolygonArea,
|
|
3993
7984
|
multiPolygonBounds,
|
|
3994
7985
|
nearestHandlePositions,
|
|
7986
|
+
normalizeBezierPath,
|
|
7987
|
+
offsetPath,
|
|
7988
|
+
offsetShapeNodes,
|
|
3995
7989
|
oppositePosition,
|
|
7990
|
+
outlineShapeNodeStrokes,
|
|
7991
|
+
outlineStroke,
|
|
3996
7992
|
overlaps,
|
|
3997
7993
|
pageHidden,
|
|
3998
7994
|
parseCanvasClipboardData,
|
|
3999
7995
|
parseCanvasScenes,
|
|
4000
7996
|
parseSvgPath,
|
|
7997
|
+
pathBoolean,
|
|
7998
|
+
pathToContours,
|
|
4001
7999
|
placeBeside,
|
|
8000
|
+
pointInBezierPath,
|
|
4002
8001
|
pointInMultiPolygon,
|
|
4003
8002
|
pointInRings,
|
|
4004
8003
|
polygonArea,
|
|
4005
8004
|
polygonBoolean,
|
|
4006
8005
|
polygonToSvgPath,
|
|
4007
8006
|
positionToSide,
|
|
8007
|
+
pruneCanvasTokenModes,
|
|
8008
|
+
readNodeStyleProperty,
|
|
4008
8009
|
readStyleSize,
|
|
4009
8010
|
realClock,
|
|
8011
|
+
relayoutUpdates,
|
|
4010
8012
|
remapPastedNodes,
|
|
4011
8013
|
reorderNodes,
|
|
4012
8014
|
repositionNodesTo,
|
|
4013
8015
|
resizeHandleAxes,
|
|
8016
|
+
resolveCanvasTokens,
|
|
8017
|
+
resolvePadding,
|
|
4014
8018
|
revertShapeCombination,
|
|
4015
8019
|
ringSignedArea,
|
|
4016
8020
|
runMultiNodeTransition,
|
|
8021
|
+
segmentBounds,
|
|
8022
|
+
segmentCurvature,
|
|
8023
|
+
segmentDerivative,
|
|
8024
|
+
segmentExtrema,
|
|
8025
|
+
segmentLength,
|
|
8026
|
+
segmentNearest,
|
|
8027
|
+
segmentParamAtLength,
|
|
8028
|
+
segmentPoint,
|
|
8029
|
+
segmentTangent,
|
|
4017
8030
|
serializeCanvasNodes,
|
|
8031
|
+
setCanvasTokenMode,
|
|
8032
|
+
shapeNodeToBezierPath,
|
|
4018
8033
|
shapeNodeToPolygon,
|
|
8034
|
+
shapeToBezierPath,
|
|
4019
8035
|
shapeToPolygon,
|
|
4020
8036
|
sideToPosition,
|
|
4021
8037
|
simplifyRing,
|
|
@@ -4023,12 +8039,22 @@ export {
|
|
|
4023
8039
|
snapNodeResize,
|
|
4024
8040
|
snapResize,
|
|
4025
8041
|
snapToGrid,
|
|
8042
|
+
splitSegment,
|
|
8043
|
+
strokeOutlineRings,
|
|
8044
|
+
subSegment,
|
|
8045
|
+
subtractPaths,
|
|
4026
8046
|
subtractPolygons,
|
|
8047
|
+
toBezierPath,
|
|
4027
8048
|
toggleLockNodes,
|
|
8049
|
+
transformBezierPath,
|
|
8050
|
+
trimSegments,
|
|
4028
8051
|
unionPolygons,
|
|
8052
|
+
unitePaths,
|
|
4029
8053
|
unlockNodes,
|
|
8054
|
+
windingNumber,
|
|
4030
8055
|
withStyleOpacity,
|
|
4031
8056
|
withStyleSize,
|
|
8057
|
+
writeNodeStyleProperty,
|
|
4032
8058
|
xorPolygons
|
|
4033
8059
|
};
|
|
4034
8060
|
//# sourceMappingURL=index.js.map
|