@pixerate/editor 0.31.0 → 0.33.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 +2 -1
- package/dist/agent/index.d.cts +3 -2
- package/dist/agent/index.d.ts +3 -2
- package/dist/{arrange-BICutYPk.d.cts → arrange-B6GH5Qcc.d.cts} +2 -216
- package/dist/{arrange-BICutYPk.d.ts → arrange-BPAXGdMm.d.ts} +2 -216
- package/dist/bezier-6KPx3w7Z.d.cts +547 -0
- package/dist/bezier-DJPYiVbA.d.ts +547 -0
- package/dist/{builtins-DxC3aUR2.d.cts → builtins-BLud97fC.d.cts} +1 -1
- package/dist/{builtins-NGxNxQfJ.d.ts → builtins-D0BDyAgw.d.ts} +1 -1
- package/dist/canvas/index.cjs +518 -3684
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +7 -547
- package/dist/canvas/index.d.ts +7 -547
- package/dist/canvas/index.js +126 -3292
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-GQTXV2EQ.js +3335 -0
- package/dist/chunk-GQTXV2EQ.js.map +1 -0
- package/dist/chunk-M4XE4UMT.cjs +3335 -0
- package/dist/chunk-M4XE4UMT.cjs.map +1 -0
- package/dist/commands/index.d.cts +3 -2
- package/dist/commands/index.d.ts +3 -2
- package/dist/types-BFkYA4eF.d.cts +217 -0
- package/dist/types-BFkYA4eF.d.ts +217 -0
- package/dist/vector/index.cjs +3471 -0
- package/dist/vector/index.cjs.map +1 -0
- package/dist/vector/index.d.cts +1052 -0
- package/dist/vector/index.d.ts +1052 -0
- package/dist/vector/index.js +3471 -0
- package/dist/vector/index.js.map +1 -0
- package/package.json +6 -1
package/dist/canvas/index.js
CHANGED
|
@@ -1,3 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CANVAS_GRADIENT_PRESETS,
|
|
3
|
+
PathOpsError,
|
|
4
|
+
SHAPE_KINDS,
|
|
5
|
+
add,
|
|
6
|
+
angleToSvgLinearCoords,
|
|
7
|
+
arcToCubics,
|
|
8
|
+
bezierPathArea,
|
|
9
|
+
bezierPathBounds,
|
|
10
|
+
bezierPathLength,
|
|
11
|
+
bezierPathPointAtLength,
|
|
12
|
+
bezierPathSignedArea,
|
|
13
|
+
bezierPathToSvg,
|
|
14
|
+
closeContour,
|
|
15
|
+
contourSignedArea,
|
|
16
|
+
contoursToPath,
|
|
17
|
+
countPathSegments,
|
|
18
|
+
cross,
|
|
19
|
+
cubicPointAt,
|
|
20
|
+
cubicsToPathData,
|
|
21
|
+
dist,
|
|
22
|
+
distanceToBezierPath,
|
|
23
|
+
dividePaths,
|
|
24
|
+
dividePolygons,
|
|
25
|
+
dot,
|
|
26
|
+
excludePaths,
|
|
27
|
+
fitCubicRun,
|
|
28
|
+
fitCubics,
|
|
29
|
+
fitFreehandPath,
|
|
30
|
+
fitPathData,
|
|
31
|
+
flattenChain,
|
|
32
|
+
flattenSvgPath,
|
|
33
|
+
formatNumber,
|
|
34
|
+
getShapeRenderData,
|
|
35
|
+
getShapeSvg,
|
|
36
|
+
gradientToSvgDef,
|
|
37
|
+
intersectPaths,
|
|
38
|
+
intersectPolygons,
|
|
39
|
+
isCanvasGradient,
|
|
40
|
+
isShapeDescription,
|
|
41
|
+
isStraightCubic,
|
|
42
|
+
multiPolygonArea,
|
|
43
|
+
multiPolygonBounds,
|
|
44
|
+
nestedRingsToPath,
|
|
45
|
+
normalizeBezierPath,
|
|
46
|
+
parseCssGradient,
|
|
47
|
+
parseSvgPath,
|
|
48
|
+
pathBoolean,
|
|
49
|
+
pathBooleanRings,
|
|
50
|
+
pathToContours,
|
|
51
|
+
pointInBezierPath,
|
|
52
|
+
pointInMultiPolygon,
|
|
53
|
+
pointInRings,
|
|
54
|
+
polygonArea,
|
|
55
|
+
polygonBoolean,
|
|
56
|
+
polygonToSvgPath,
|
|
57
|
+
resolveCanvasGradient,
|
|
58
|
+
ringSignedArea,
|
|
59
|
+
samePoint,
|
|
60
|
+
scale,
|
|
61
|
+
segmentBounds,
|
|
62
|
+
segmentCurvature,
|
|
63
|
+
segmentDerivative,
|
|
64
|
+
segmentExtrema,
|
|
65
|
+
segmentLength,
|
|
66
|
+
segmentNearest,
|
|
67
|
+
segmentParamAtLength,
|
|
68
|
+
segmentPoint,
|
|
69
|
+
segmentTangent,
|
|
70
|
+
shapeToPolygon,
|
|
71
|
+
simplifyRing,
|
|
72
|
+
splitSegment,
|
|
73
|
+
sub,
|
|
74
|
+
subSegment,
|
|
75
|
+
subtractPaths,
|
|
76
|
+
subtractPolygons,
|
|
77
|
+
toBezierPath,
|
|
78
|
+
transformBezierPath,
|
|
79
|
+
trimSegments,
|
|
80
|
+
unionPolygons,
|
|
81
|
+
unitePaths,
|
|
82
|
+
windingNumber,
|
|
83
|
+
xorPolygons
|
|
84
|
+
} from "../chunk-GQTXV2EQ.js";
|
|
1
85
|
import {
|
|
2
86
|
DEFAULT_NODE_HEIGHT,
|
|
3
87
|
DEFAULT_NODE_WIDTH,
|
|
@@ -1642,3256 +1726,6 @@ function fitViewportToNodes(nodes, size, ids, options = {}) {
|
|
|
1642
1726
|
return bounds ? fitViewportToRect(bounds, size, options) : null;
|
|
1643
1727
|
}
|
|
1644
1728
|
|
|
1645
|
-
// src/canvas/polygonBoolean.ts
|
|
1646
|
-
function ringSignedArea(ring) {
|
|
1647
|
-
let sum = 0;
|
|
1648
|
-
const n = ring.length;
|
|
1649
|
-
for (let i = 0; i < n; i++) {
|
|
1650
|
-
const [x1, y1] = ring[i];
|
|
1651
|
-
const [x2, y2] = ring[(i + 1) % n];
|
|
1652
|
-
sum += x1 * y2 - x2 * y1;
|
|
1653
|
-
}
|
|
1654
|
-
return sum / 2;
|
|
1655
|
-
}
|
|
1656
|
-
function polygonArea(polygon) {
|
|
1657
|
-
if (polygon.length === 0) return 0;
|
|
1658
|
-
let area = Math.abs(ringSignedArea(polygon[0]));
|
|
1659
|
-
for (let i = 1; i < polygon.length; i++) area -= Math.abs(ringSignedArea(polygon[i]));
|
|
1660
|
-
return area;
|
|
1661
|
-
}
|
|
1662
|
-
function multiPolygonArea(multi) {
|
|
1663
|
-
let total = 0;
|
|
1664
|
-
for (const polygon of multi) total += polygonArea(polygon);
|
|
1665
|
-
return total;
|
|
1666
|
-
}
|
|
1667
|
-
function multiPolygonBounds(input) {
|
|
1668
|
-
let minX = Infinity;
|
|
1669
|
-
let minY = Infinity;
|
|
1670
|
-
let maxX = -Infinity;
|
|
1671
|
-
let maxY = -Infinity;
|
|
1672
|
-
const visit = (ring) => {
|
|
1673
|
-
for (const [x, y] of ring) {
|
|
1674
|
-
if (x < minX) minX = x;
|
|
1675
|
-
if (y < minY) minY = y;
|
|
1676
|
-
if (x > maxX) maxX = x;
|
|
1677
|
-
if (y > maxY) maxY = y;
|
|
1678
|
-
}
|
|
1679
|
-
};
|
|
1680
|
-
for (const item of input) {
|
|
1681
|
-
if (item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0])) {
|
|
1682
|
-
for (const ring of item) visit(ring);
|
|
1683
|
-
} else {
|
|
1684
|
-
visit(item);
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
return minX === Infinity ? null : { minX, minY, maxX, maxY };
|
|
1688
|
-
}
|
|
1689
|
-
function pointInRings(point, rings, fillRule = "nonzero") {
|
|
1690
|
-
const [px2, py] = point;
|
|
1691
|
-
let winding = 0;
|
|
1692
|
-
for (const ring of rings) {
|
|
1693
|
-
const n = ring.length;
|
|
1694
|
-
for (let i = 0; i < n; i++) {
|
|
1695
|
-
const [ax, ay] = ring[i];
|
|
1696
|
-
const [bx, by] = ring[(i + 1) % n];
|
|
1697
|
-
if (ay <= py && by > py) {
|
|
1698
|
-
if (ax + (py - ay) / (by - ay) * (bx - ax) > px2) winding++;
|
|
1699
|
-
} else if (by <= py && ay > py) {
|
|
1700
|
-
if (ax + (py - ay) / (by - ay) * (bx - ax) > px2) winding--;
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
return fillRule === "evenodd" ? (winding & 1) === 1 : winding !== 0;
|
|
1705
|
-
}
|
|
1706
|
-
function pointInMultiPolygon(point, multi) {
|
|
1707
|
-
return multi.some((polygon) => pointInRings(point, polygon, "evenodd"));
|
|
1708
|
-
}
|
|
1709
|
-
function cleanRing(ring) {
|
|
1710
|
-
const out = [];
|
|
1711
|
-
for (const p of ring) {
|
|
1712
|
-
if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
|
|
1713
|
-
const last = out[out.length - 1];
|
|
1714
|
-
if (last && last[0] === p[0] && last[1] === p[1]) continue;
|
|
1715
|
-
out.push([p[0], p[1]]);
|
|
1716
|
-
}
|
|
1717
|
-
while (out.length > 1) {
|
|
1718
|
-
const first = out[0];
|
|
1719
|
-
const last = out[out.length - 1];
|
|
1720
|
-
if (first[0] === last[0] && first[1] === last[1]) out.pop();
|
|
1721
|
-
else break;
|
|
1722
|
-
}
|
|
1723
|
-
return out;
|
|
1724
|
-
}
|
|
1725
|
-
function isOperand(input) {
|
|
1726
|
-
return !Array.isArray(input) && typeof input === "object" && input !== null && Array.isArray(input.rings);
|
|
1727
|
-
}
|
|
1728
|
-
function normalizeInput(input) {
|
|
1729
|
-
if (isOperand(input)) {
|
|
1730
|
-
return {
|
|
1731
|
-
rings: input.rings.map(cleanRing).filter((r) => r.length >= 3),
|
|
1732
|
-
fillRule: input.fillRule ?? "nonzero"
|
|
1733
|
-
};
|
|
1734
|
-
}
|
|
1735
|
-
const rings = [];
|
|
1736
|
-
for (const polygon of input ?? []) {
|
|
1737
|
-
polygon.forEach((raw, index) => {
|
|
1738
|
-
const ring = cleanRing(raw);
|
|
1739
|
-
if (ring.length < 3) return;
|
|
1740
|
-
const area = ringSignedArea(ring);
|
|
1741
|
-
if (area === 0) return;
|
|
1742
|
-
const wantPositive = index === 0;
|
|
1743
|
-
rings.push(area > 0 === wantPositive ? ring : ring.reverse());
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1746
|
-
return { rings, fillRule: "nonzero" };
|
|
1747
|
-
}
|
|
1748
|
-
var PointPool = class {
|
|
1749
|
-
constructor(eps) {
|
|
1750
|
-
this.eps = eps;
|
|
1751
|
-
}
|
|
1752
|
-
eps;
|
|
1753
|
-
vertices = [];
|
|
1754
|
-
cells = /* @__PURE__ */ new Map();
|
|
1755
|
-
get(x, y) {
|
|
1756
|
-
const cx = Math.floor(x / this.eps);
|
|
1757
|
-
const cy = Math.floor(y / this.eps);
|
|
1758
|
-
let best = null;
|
|
1759
|
-
let bestDist = Infinity;
|
|
1760
|
-
for (let dx = -1; dx <= 1; dx++) {
|
|
1761
|
-
for (let dy = -1; dy <= 1; dy++) {
|
|
1762
|
-
const ids = this.cells.get(`${cx + dx},${cy + dy}`);
|
|
1763
|
-
if (!ids) continue;
|
|
1764
|
-
for (const id of ids) {
|
|
1765
|
-
const v = this.vertices[id];
|
|
1766
|
-
const d = Math.hypot(v.x - x, v.y - y);
|
|
1767
|
-
if (d <= this.eps && d < bestDist) {
|
|
1768
|
-
best = v;
|
|
1769
|
-
bestDist = d;
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
if (best) return best;
|
|
1775
|
-
const vertex = { id: this.vertices.length, x, y };
|
|
1776
|
-
this.vertices.push(vertex);
|
|
1777
|
-
const key = `${cx},${cy}`;
|
|
1778
|
-
const bucket = this.cells.get(key);
|
|
1779
|
-
if (bucket) bucket.push(vertex.id);
|
|
1780
|
-
else this.cells.set(key, [vertex.id]);
|
|
1781
|
-
return vertex;
|
|
1782
|
-
}
|
|
1783
|
-
};
|
|
1784
|
-
function project(a, b, p) {
|
|
1785
|
-
const dx = b.x - a.x;
|
|
1786
|
-
const dy = b.y - a.y;
|
|
1787
|
-
const len2 = dx * dx + dy * dy;
|
|
1788
|
-
const t = len2 === 0 ? 0 : ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;
|
|
1789
|
-
const cx = a.x + t * dx;
|
|
1790
|
-
const cy = a.y + t * dy;
|
|
1791
|
-
return { t, dist: Math.hypot(p.x - cx, p.y - cy) };
|
|
1792
|
-
}
|
|
1793
|
-
function addSplit(splits, t, v) {
|
|
1794
|
-
const { a, b } = splits.segment;
|
|
1795
|
-
if (v.id === a.id || v.id === b.id) return;
|
|
1796
|
-
if (!splits.points.has(v.id)) splits.points.set(v.id, { t, v });
|
|
1797
|
-
}
|
|
1798
|
-
function splitPass(segments, pool, eps) {
|
|
1799
|
-
const order = segments.map((s, i) => ({
|
|
1800
|
-
i,
|
|
1801
|
-
minX: Math.min(s.a.x, s.b.x),
|
|
1802
|
-
maxX: Math.max(s.a.x, s.b.x),
|
|
1803
|
-
minY: Math.min(s.a.y, s.b.y),
|
|
1804
|
-
maxY: Math.max(s.a.y, s.b.y)
|
|
1805
|
-
})).sort((p, q) => p.minX - q.minX);
|
|
1806
|
-
const splits = segments.map((segment) => ({ segment, points: /* @__PURE__ */ new Map() }));
|
|
1807
|
-
for (let oi = 0; oi < order.length; oi++) {
|
|
1808
|
-
const bi = order[oi];
|
|
1809
|
-
const s1 = segments[bi.i];
|
|
1810
|
-
for (let oj = oi + 1; oj < order.length; oj++) {
|
|
1811
|
-
const bj = order[oj];
|
|
1812
|
-
if (bj.minX > bi.maxX + eps) break;
|
|
1813
|
-
if (bj.minY > bi.maxY + eps || bj.maxY < bi.minY - eps) continue;
|
|
1814
|
-
const s2 = segments[bj.i];
|
|
1815
|
-
const sharedA = s1.a.id === s2.a.id || s1.a.id === s2.b.id;
|
|
1816
|
-
const sharedB = s1.b.id === s2.a.id || s1.b.id === s2.b.id;
|
|
1817
|
-
if (sharedA && sharedB) continue;
|
|
1818
|
-
let touched = false;
|
|
1819
|
-
for (const [seg, other, split] of [
|
|
1820
|
-
[s1, s2, splits[bi.i]],
|
|
1821
|
-
[s2, s1, splits[bj.i]]
|
|
1822
|
-
]) {
|
|
1823
|
-
for (const p of [other.a, other.b]) {
|
|
1824
|
-
if (p.id === seg.a.id || p.id === seg.b.id) continue;
|
|
1825
|
-
const { t: t2, dist: dist3 } = project(seg.a, seg.b, p);
|
|
1826
|
-
const len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);
|
|
1827
|
-
if (dist3 <= eps && t2 * len > eps && (1 - t2) * len > eps) {
|
|
1828
|
-
addSplit(split, t2, p);
|
|
1829
|
-
touched = true;
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
if (touched) continue;
|
|
1834
|
-
const d1x = s1.b.x - s1.a.x;
|
|
1835
|
-
const d1y = s1.b.y - s1.a.y;
|
|
1836
|
-
const d2x = s2.b.x - s2.a.x;
|
|
1837
|
-
const d2y = s2.b.y - s2.a.y;
|
|
1838
|
-
const denom = d1x * d2y - d1y * d2x;
|
|
1839
|
-
const len1 = Math.hypot(d1x, d1y);
|
|
1840
|
-
const len2 = Math.hypot(d2x, d2y);
|
|
1841
|
-
if (Math.abs(denom) <= 1e-12 * len1 * len2) continue;
|
|
1842
|
-
const ex = s2.a.x - s1.a.x;
|
|
1843
|
-
const ey = s2.a.y - s1.a.y;
|
|
1844
|
-
const t = (ex * d2y - ey * d2x) / denom;
|
|
1845
|
-
const u = (ex * d1y - ey * d1x) / denom;
|
|
1846
|
-
const tolT = eps / len1;
|
|
1847
|
-
const tolU = eps / len2;
|
|
1848
|
-
if (t < -tolT || t > 1 + tolT || u < -tolU || u > 1 + tolU) continue;
|
|
1849
|
-
if (sharedA || sharedB) continue;
|
|
1850
|
-
const v = pool.get(s1.a.x + t * d1x, s1.a.y + t * d1y);
|
|
1851
|
-
addSplit(splits[bi.i], project(s1.a, s1.b, v).t, v);
|
|
1852
|
-
addSplit(splits[bj.i], project(s2.a, s2.b, v).t, v);
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
let changed = false;
|
|
1856
|
-
const out = [];
|
|
1857
|
-
for (const { segment, points } of splits) {
|
|
1858
|
-
if (points.size === 0) {
|
|
1859
|
-
out.push(segment);
|
|
1860
|
-
continue;
|
|
1861
|
-
}
|
|
1862
|
-
changed = true;
|
|
1863
|
-
const ordered = [...points.values()].sort((p, q) => p.t - q.t);
|
|
1864
|
-
let prev = segment.a;
|
|
1865
|
-
for (const { v } of ordered) {
|
|
1866
|
-
if (v.id !== prev.id) out.push({ a: prev, b: v, operand: segment.operand });
|
|
1867
|
-
prev = v;
|
|
1868
|
-
}
|
|
1869
|
-
if (prev.id !== segment.b.id) out.push({ a: prev, b: segment.b, operand: segment.operand });
|
|
1870
|
-
}
|
|
1871
|
-
return { segments: out, changed };
|
|
1872
|
-
}
|
|
1873
|
-
function defaultEpsilon(operands) {
|
|
1874
|
-
let max = 0;
|
|
1875
|
-
for (const op of operands) {
|
|
1876
|
-
for (const ring of op.rings) {
|
|
1877
|
-
for (const [x, y] of ring) {
|
|
1878
|
-
const m = Math.max(Math.abs(x), Math.abs(y));
|
|
1879
|
-
if (m > max) max = m;
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
return Math.max(1e-9, max * 1e-9);
|
|
1884
|
-
}
|
|
1885
|
-
function buildOverlay(inputs, options) {
|
|
1886
|
-
const operands = inputs.map(normalizeInput);
|
|
1887
|
-
const eps = options.epsilon && options.epsilon > 0 ? options.epsilon : defaultEpsilon(operands);
|
|
1888
|
-
const pool = new PointPool(eps);
|
|
1889
|
-
let segments = [];
|
|
1890
|
-
operands.forEach((op, operand) => {
|
|
1891
|
-
for (const ring of op.rings) {
|
|
1892
|
-
const verts = ring.map(([x, y]) => pool.get(x, y));
|
|
1893
|
-
for (let i = 0; i < verts.length; i++) {
|
|
1894
|
-
const a = verts[i];
|
|
1895
|
-
const b = verts[(i + 1) % verts.length];
|
|
1896
|
-
if (a.id !== b.id) segments.push({ a, b, operand });
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
});
|
|
1900
|
-
for (let pass = 0; pass < 8; pass++) {
|
|
1901
|
-
const result = splitPass(segments, pool, eps);
|
|
1902
|
-
segments = result.segments;
|
|
1903
|
-
if (!result.changed) break;
|
|
1904
|
-
}
|
|
1905
|
-
const count = operands.length;
|
|
1906
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
1907
|
-
for (const s of segments) {
|
|
1908
|
-
const forward = s.a.id < s.b.id;
|
|
1909
|
-
const a = forward ? s.a : s.b;
|
|
1910
|
-
const b = forward ? s.b : s.a;
|
|
1911
|
-
const key = `${a.id},${b.id}`;
|
|
1912
|
-
let edge = byKey.get(key);
|
|
1913
|
-
if (!edge) {
|
|
1914
|
-
edge = { a, b, mult: new Array(count).fill(0) };
|
|
1915
|
-
byKey.set(key, edge);
|
|
1916
|
-
}
|
|
1917
|
-
edge.mult[s.operand] += forward ? 1 : -1;
|
|
1918
|
-
}
|
|
1919
|
-
const edges = [...byKey.values()].filter((e) => e.mult.some((m) => m !== 0));
|
|
1920
|
-
const right = [];
|
|
1921
|
-
const left = [];
|
|
1922
|
-
for (let i = 0; i < edges.length; i++) {
|
|
1923
|
-
const edge = edges[i];
|
|
1924
|
-
const horizontal = Math.abs(edge.b.y - edge.a.y) < Math.abs(edge.b.x - edge.a.x);
|
|
1925
|
-
const tx = (v) => horizontal ? v.y : v.x;
|
|
1926
|
-
const ty = (v) => horizontal ? -v.x : v.y;
|
|
1927
|
-
const ax = tx(edge.a);
|
|
1928
|
-
const ay = ty(edge.a);
|
|
1929
|
-
const bx = tx(edge.b);
|
|
1930
|
-
const by = ty(edge.b);
|
|
1931
|
-
const mx = (ax + bx) / 2;
|
|
1932
|
-
const my = (ay + by) / 2;
|
|
1933
|
-
const winding = new Array(count).fill(0);
|
|
1934
|
-
for (let j = 0; j < edges.length; j++) {
|
|
1935
|
-
if (j === i) continue;
|
|
1936
|
-
const other = edges[j];
|
|
1937
|
-
const oax = tx(other.a);
|
|
1938
|
-
const oay = ty(other.a);
|
|
1939
|
-
const obx = tx(other.b);
|
|
1940
|
-
const oby = ty(other.b);
|
|
1941
|
-
let dir = 0;
|
|
1942
|
-
if (oay <= my && oby > my) dir = 1;
|
|
1943
|
-
else if (oby <= my && oay > my) dir = -1;
|
|
1944
|
-
else continue;
|
|
1945
|
-
const x = oax + (my - oay) / (oby - oay) * (obx - oax);
|
|
1946
|
-
if (x <= mx) continue;
|
|
1947
|
-
for (let k = 0; k < count; k++) {
|
|
1948
|
-
if (other.mult[k] !== 0) winding[k] += dir * other.mult[k];
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
const sideIsRight = by > ay;
|
|
1952
|
-
const r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);
|
|
1953
|
-
right.push(r);
|
|
1954
|
-
left.push(r.map((w, k) => w + edge.mult[k]));
|
|
1955
|
-
}
|
|
1956
|
-
return { edges, right, left, fillRules: operands.map((o) => o.fillRule), eps };
|
|
1957
|
-
}
|
|
1958
|
-
function insideFlags(windings, fillRules) {
|
|
1959
|
-
return windings.map((w, k) => fillRules[k] === "evenodd" ? (w & 1) === 1 : w !== 0);
|
|
1960
|
-
}
|
|
1961
|
-
function traceRings(directed2) {
|
|
1962
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
1963
|
-
for (const e of directed2) {
|
|
1964
|
-
const list = outgoing.get(e.from.id);
|
|
1965
|
-
if (list) list.push(e);
|
|
1966
|
-
else outgoing.set(e.from.id, [e]);
|
|
1967
|
-
}
|
|
1968
|
-
const TWO_PI2 = Math.PI * 2;
|
|
1969
|
-
const next = (incoming) => {
|
|
1970
|
-
const candidates = outgoing.get(incoming.to.id);
|
|
1971
|
-
if (!candidates) return void 0;
|
|
1972
|
-
const back = Math.atan2(incoming.from.y - incoming.to.y, incoming.from.x - incoming.to.x);
|
|
1973
|
-
let best;
|
|
1974
|
-
let bestTurn = Infinity;
|
|
1975
|
-
for (const c of candidates) {
|
|
1976
|
-
let turn = back - c.angle;
|
|
1977
|
-
turn -= Math.floor(turn / TWO_PI2) * TWO_PI2;
|
|
1978
|
-
if (turn <= 1e-15) turn = TWO_PI2;
|
|
1979
|
-
if (turn < bestTurn) {
|
|
1980
|
-
bestTurn = turn;
|
|
1981
|
-
best = c;
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
return best;
|
|
1985
|
-
};
|
|
1986
|
-
const used = /* @__PURE__ */ new Set();
|
|
1987
|
-
const rings = [];
|
|
1988
|
-
for (const start of directed2) {
|
|
1989
|
-
if (used.has(start)) continue;
|
|
1990
|
-
const ring = [];
|
|
1991
|
-
let edge = start;
|
|
1992
|
-
let guard = directed2.length + 1;
|
|
1993
|
-
while (edge && !used.has(edge) && guard-- > 0) {
|
|
1994
|
-
used.add(edge);
|
|
1995
|
-
ring.push(edge.from);
|
|
1996
|
-
edge = next(edge);
|
|
1997
|
-
}
|
|
1998
|
-
if (ring.length >= 3) rings.push(ring);
|
|
1999
|
-
}
|
|
2000
|
-
return rings;
|
|
2001
|
-
}
|
|
2002
|
-
function removeCollinear(ring, eps) {
|
|
2003
|
-
let points = ring;
|
|
2004
|
-
let changed = true;
|
|
2005
|
-
while (changed && points.length > 3) {
|
|
2006
|
-
changed = false;
|
|
2007
|
-
const out = [];
|
|
2008
|
-
const n = points.length;
|
|
2009
|
-
for (let i = 0; i < n; i++) {
|
|
2010
|
-
const prev = out.length > 0 ? out[out.length - 1] : points[(i - 1 + n) % n];
|
|
2011
|
-
const cur = points[i];
|
|
2012
|
-
const nxt = points[(i + 1) % n];
|
|
2013
|
-
const dx = nxt[0] - prev[0];
|
|
2014
|
-
const dy = nxt[1] - prev[1];
|
|
2015
|
-
const len = Math.hypot(dx, dy);
|
|
2016
|
-
const cross2 = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;
|
|
2017
|
-
const dot3 = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;
|
|
2018
|
-
if (len > 0 && Math.abs(cross2) / len <= eps && dot3 > 0 && dot3 < len * len) {
|
|
2019
|
-
changed = true;
|
|
2020
|
-
continue;
|
|
2021
|
-
}
|
|
2022
|
-
out.push(cur);
|
|
2023
|
-
}
|
|
2024
|
-
if (out.length < 3) return out;
|
|
2025
|
-
points = out;
|
|
2026
|
-
}
|
|
2027
|
-
return points;
|
|
2028
|
-
}
|
|
2029
|
-
function assemble(rings, eps) {
|
|
2030
|
-
const outers = [];
|
|
2031
|
-
const holes = [];
|
|
2032
|
-
const minArea = eps * eps;
|
|
2033
|
-
for (const vertices of rings) {
|
|
2034
|
-
const ring = removeCollinear(
|
|
2035
|
-
vertices.map((v) => [v.x, v.y]),
|
|
2036
|
-
eps
|
|
2037
|
-
);
|
|
2038
|
-
if (ring.length < 3) continue;
|
|
2039
|
-
const area = ringSignedArea(ring);
|
|
2040
|
-
if (Math.abs(area) <= minArea) continue;
|
|
2041
|
-
(area > 0 ? outers : holes).push({ ring, area: Math.abs(area) });
|
|
2042
|
-
}
|
|
2043
|
-
outers.sort((a, b) => b.area - a.area);
|
|
2044
|
-
const polygons = outers.map((o) => [o.ring]);
|
|
2045
|
-
for (const hole of holes) {
|
|
2046
|
-
const [p, q] = [hole.ring[0], hole.ring[1]];
|
|
2047
|
-
const sample = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
|
|
2048
|
-
let owner = -1;
|
|
2049
|
-
for (let i = outers.length - 1; i >= 0; i--) {
|
|
2050
|
-
if (outers[i].area >= hole.area && pointInRings(sample, [outers[i].ring], "evenodd")) {
|
|
2051
|
-
owner = i;
|
|
2052
|
-
break;
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
if (owner >= 0) polygons[owner].push(hole.ring);
|
|
2056
|
-
}
|
|
2057
|
-
return polygons;
|
|
2058
|
-
}
|
|
2059
|
-
function extract(overlay, isInside) {
|
|
2060
|
-
const directed2 = [];
|
|
2061
|
-
overlay.edges.forEach((edge, i) => {
|
|
2062
|
-
const l = isInside(insideFlags(overlay.left[i], overlay.fillRules));
|
|
2063
|
-
const r = isInside(insideFlags(overlay.right[i], overlay.fillRules));
|
|
2064
|
-
if (l === r) return;
|
|
2065
|
-
const [from, to] = l ? [edge.a, edge.b] : [edge.b, edge.a];
|
|
2066
|
-
directed2.push({ from, to, angle: Math.atan2(to.y - from.y, to.x - from.x) });
|
|
2067
|
-
});
|
|
2068
|
-
return assemble(traceRings(directed2), overlay.eps);
|
|
2069
|
-
}
|
|
2070
|
-
function predicate(operation) {
|
|
2071
|
-
switch (operation) {
|
|
2072
|
-
case "union":
|
|
2073
|
-
return (f) => f.some(Boolean);
|
|
2074
|
-
case "intersection":
|
|
2075
|
-
return (f) => f.length > 0 && f.every(Boolean);
|
|
2076
|
-
case "difference":
|
|
2077
|
-
return (f) => f[0] === true && !f.slice(1).some(Boolean);
|
|
2078
|
-
case "xor":
|
|
2079
|
-
return (f) => f.filter(Boolean).length % 2 === 1;
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
function polygonBoolean(operation, operands, options = {}) {
|
|
2083
|
-
if (operands.length === 0) return [];
|
|
2084
|
-
return extract(buildOverlay(operands, options), predicate(operation));
|
|
2085
|
-
}
|
|
2086
|
-
var unionPolygons = (a, b, options) => polygonBoolean("union", [a, b], options);
|
|
2087
|
-
var intersectPolygons = (a, b, options) => polygonBoolean("intersection", [a, b], options);
|
|
2088
|
-
var subtractPolygons = (a, b, options) => polygonBoolean("difference", [a, b], options);
|
|
2089
|
-
var xorPolygons = (a, b, options) => polygonBoolean("xor", [a, b], options);
|
|
2090
|
-
function dividePolygons(operands, options = {}) {
|
|
2091
|
-
if (operands.length === 0) return [];
|
|
2092
|
-
const overlay = buildOverlay(operands, options);
|
|
2093
|
-
const signatures = /* @__PURE__ */ new Map();
|
|
2094
|
-
const note = (windings) => {
|
|
2095
|
-
const flags = insideFlags(windings, overlay.fillRules);
|
|
2096
|
-
if (!flags.some(Boolean)) return;
|
|
2097
|
-
const key = flags.map((f) => f ? "1" : "0").join("");
|
|
2098
|
-
if (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => f ? [k] : []));
|
|
2099
|
-
};
|
|
2100
|
-
overlay.edges.forEach((_, i) => {
|
|
2101
|
-
note(overlay.left[i]);
|
|
2102
|
-
note(overlay.right[i]);
|
|
2103
|
-
});
|
|
2104
|
-
const pieces = [];
|
|
2105
|
-
const keys = [...signatures.keys()].sort();
|
|
2106
|
-
for (const key of keys) {
|
|
2107
|
-
const polygons = extract(overlay, (flags) => flags.map((f) => f ? "1" : "0").join("") === key);
|
|
2108
|
-
polygons.sort((a, b) => polygonArea(b) - polygonArea(a));
|
|
2109
|
-
for (const polygon of polygons) pieces.push({ polygon, operands: signatures.get(key) });
|
|
2110
|
-
}
|
|
2111
|
-
return pieces;
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
// src/canvas/curveFit.ts
|
|
2115
|
-
function formatNumber(value, precision) {
|
|
2116
|
-
const factor = 10 ** precision;
|
|
2117
|
-
const rounded = Math.round(value * factor) / factor;
|
|
2118
|
-
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
2119
|
-
}
|
|
2120
|
-
var MAX_REPARAM = 6;
|
|
2121
|
-
var sub = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
2122
|
-
var add = (a, v, s = 1) => [a[0] + v[0] * s, a[1] + v[1] * s];
|
|
2123
|
-
var dot = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
2124
|
-
var dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
2125
|
-
function unit(v) {
|
|
2126
|
-
const l = Math.hypot(v[0], v[1]);
|
|
2127
|
-
return l > 1e-12 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
|
|
2128
|
-
}
|
|
2129
|
-
function evalCubic(c, t) {
|
|
2130
|
-
const s = 1 - t;
|
|
2131
|
-
const b0 = s * s * s;
|
|
2132
|
-
const b1 = 3 * s * s * t;
|
|
2133
|
-
const b2 = 3 * s * t * t;
|
|
2134
|
-
const b3 = t * t * t;
|
|
2135
|
-
return [
|
|
2136
|
-
b0 * c.p0[0] + b1 * c.p1[0] + b2 * c.p2[0] + b3 * c.p3[0],
|
|
2137
|
-
b0 * c.p0[1] + b1 * c.p1[1] + b2 * c.p2[1] + b3 * c.p3[1]
|
|
2138
|
-
];
|
|
2139
|
-
}
|
|
2140
|
-
function cubicPointAt(c, t) {
|
|
2141
|
-
return evalCubic(c, t);
|
|
2142
|
-
}
|
|
2143
|
-
function dedup(points) {
|
|
2144
|
-
const out = [];
|
|
2145
|
-
for (const p of points) {
|
|
2146
|
-
if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
|
|
2147
|
-
if (out.length === 0 || dist(out[out.length - 1], p) > 1e-12) out.push(p);
|
|
2148
|
-
}
|
|
2149
|
-
return out;
|
|
2150
|
-
}
|
|
2151
|
-
function chordParams(pts) {
|
|
2152
|
-
const u = [0];
|
|
2153
|
-
let acc = 0;
|
|
2154
|
-
for (let i = 1; i < pts.length; i++) {
|
|
2155
|
-
acc += dist(pts[i - 1], pts[i]);
|
|
2156
|
-
u.push(acc);
|
|
2157
|
-
}
|
|
2158
|
-
return acc > 0 ? u.map((x) => x / acc) : u;
|
|
2159
|
-
}
|
|
2160
|
-
function generate(pts, u, t0, t1) {
|
|
2161
|
-
const p0 = pts[0];
|
|
2162
|
-
const p3 = pts[pts.length - 1];
|
|
2163
|
-
const dirIn = [-t1[0], -t1[1]];
|
|
2164
|
-
let c00 = 0;
|
|
2165
|
-
let c01 = 0;
|
|
2166
|
-
let c11 = 0;
|
|
2167
|
-
let x0 = 0;
|
|
2168
|
-
let x1 = 0;
|
|
2169
|
-
for (let i = 0; i < pts.length; i++) {
|
|
2170
|
-
const t = u[i];
|
|
2171
|
-
const s = 1 - t;
|
|
2172
|
-
const b0 = s * s * s;
|
|
2173
|
-
const b1 = 3 * s * s * t;
|
|
2174
|
-
const b2 = 3 * s * t * t;
|
|
2175
|
-
const b3 = t * t * t;
|
|
2176
|
-
const a0 = [t0[0] * b1, t0[1] * b1];
|
|
2177
|
-
const a1 = [dirIn[0] * b2, dirIn[1] * b2];
|
|
2178
|
-
c00 += dot(a0, a0);
|
|
2179
|
-
c01 += dot(a0, a1);
|
|
2180
|
-
c11 += dot(a1, a1);
|
|
2181
|
-
const tmp = [
|
|
2182
|
-
pts[i][0] - (p0[0] * (b0 + b1) + p3[0] * (b2 + b3)),
|
|
2183
|
-
pts[i][1] - (p0[1] * (b0 + b1) + p3[1] * (b2 + b3))
|
|
2184
|
-
];
|
|
2185
|
-
x0 += dot(a0, tmp);
|
|
2186
|
-
x1 += dot(a1, tmp);
|
|
2187
|
-
}
|
|
2188
|
-
const det = c00 * c11 - c01 * c01;
|
|
2189
|
-
const seg = dist(p0, p3);
|
|
2190
|
-
const eps = 1e-6 * seg;
|
|
2191
|
-
let al = seg / 3;
|
|
2192
|
-
let ar = seg / 3;
|
|
2193
|
-
if (Math.abs(det) > 1e-12) {
|
|
2194
|
-
al = (x0 * c11 - x1 * c01) / det;
|
|
2195
|
-
ar = (c00 * x1 - c01 * x0) / det;
|
|
2196
|
-
}
|
|
2197
|
-
if (!Number.isFinite(al) || !Number.isFinite(ar) || al < eps || ar < eps) {
|
|
2198
|
-
al = seg / 3;
|
|
2199
|
-
ar = seg / 3;
|
|
2200
|
-
}
|
|
2201
|
-
const cap = seg * 4 + 1e-9;
|
|
2202
|
-
al = Math.min(al, cap);
|
|
2203
|
-
ar = Math.min(ar, cap);
|
|
2204
|
-
return { p0, p1: add(p0, t0, al), p2: add(p3, dirIn, ar), p3 };
|
|
2205
|
-
}
|
|
2206
|
-
function maxError(pts, c, u) {
|
|
2207
|
-
let e = 0;
|
|
2208
|
-
let idx = pts.length >> 1;
|
|
2209
|
-
for (let i = 0; i < pts.length; i++) {
|
|
2210
|
-
const d = dist(evalCubic(c, u[i]), pts[i]);
|
|
2211
|
-
if (d > e) {
|
|
2212
|
-
e = d;
|
|
2213
|
-
idx = i;
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
return [e, idx];
|
|
2217
|
-
}
|
|
2218
|
-
function newton(c, p, t) {
|
|
2219
|
-
const s = 1 - t;
|
|
2220
|
-
const q = evalCubic(c, t);
|
|
2221
|
-
const d1 = [
|
|
2222
|
-
3 * (s * s * (c.p1[0] - c.p0[0]) + 2 * s * t * (c.p2[0] - c.p1[0]) + t * t * (c.p3[0] - c.p2[0])),
|
|
2223
|
-
3 * (s * s * (c.p1[1] - c.p0[1]) + 2 * s * t * (c.p2[1] - c.p1[1]) + t * t * (c.p3[1] - c.p2[1]))
|
|
2224
|
-
];
|
|
2225
|
-
const d2 = [
|
|
2226
|
-
6 * (s * (c.p2[0] - 2 * c.p1[0] + c.p0[0]) + t * (c.p3[0] - 2 * c.p2[0] + c.p1[0])),
|
|
2227
|
-
6 * (s * (c.p2[1] - 2 * c.p1[1] + c.p0[1]) + t * (c.p3[1] - 2 * c.p2[1] + c.p1[1]))
|
|
2228
|
-
];
|
|
2229
|
-
const diff = [q[0] - p[0], q[1] - p[1]];
|
|
2230
|
-
const num2 = dot(diff, d1);
|
|
2231
|
-
const den = dot(d1, d1) + dot(diff, d2);
|
|
2232
|
-
if (Math.abs(den) < 1e-12) return t;
|
|
2233
|
-
const nt = t - num2 / den;
|
|
2234
|
-
return Number.isFinite(nt) ? Math.min(1, Math.max(0, nt)) : t;
|
|
2235
|
-
}
|
|
2236
|
-
function fitSingleFrom(pts, u, t0, t1) {
|
|
2237
|
-
let best = generate(pts, u, t0, t1);
|
|
2238
|
-
let [bestErr, bestSplit] = maxError(pts, best, u);
|
|
2239
|
-
const params = u.slice();
|
|
2240
|
-
for (let round2 = 0; round2 < MAX_REPARAM; round2++) {
|
|
2241
|
-
for (let i = 0; i < params.length; i++) params[i] = newton(best, pts[i], params[i]);
|
|
2242
|
-
const c = generate(pts, params, t0, t1);
|
|
2243
|
-
const [e, s] = maxError(pts, c, params);
|
|
2244
|
-
if (e < bestErr) {
|
|
2245
|
-
best = c;
|
|
2246
|
-
bestErr = e;
|
|
2247
|
-
bestSplit = s;
|
|
2248
|
-
} else break;
|
|
2249
|
-
}
|
|
2250
|
-
return [best, bestErr, bestSplit];
|
|
2251
|
-
}
|
|
2252
|
-
function fitRec(pts, t0, t1, tol, out, depth) {
|
|
2253
|
-
const n = pts.length;
|
|
2254
|
-
if (n === 2) {
|
|
2255
|
-
const d = dist(pts[0], pts[1]) / 3;
|
|
2256
|
-
out.push({ p0: pts[0], p1: add(pts[0], t0, d), p2: add(pts[1], t1, -d), p3: pts[1] });
|
|
2257
|
-
return;
|
|
2258
|
-
}
|
|
2259
|
-
const [c, err, splitAt] = fitSingleFrom(pts, chordParams(pts), t0, t1);
|
|
2260
|
-
if (err <= tol || depth > 40) {
|
|
2261
|
-
out.push(c);
|
|
2262
|
-
return;
|
|
2263
|
-
}
|
|
2264
|
-
const split = Math.min(n - 2, Math.max(1, splitAt));
|
|
2265
|
-
const tc = unit(sub(pts[split + 1], pts[split - 1])) ?? t0;
|
|
2266
|
-
fitRec(pts.slice(0, split + 1), t0, tc, tol, out, depth + 1);
|
|
2267
|
-
fitRec(pts.slice(split), tc, t1, tol, out, depth + 1);
|
|
2268
|
-
}
|
|
2269
|
-
function tangentFrom(a, b, c) {
|
|
2270
|
-
const chord = unit(sub(b, a));
|
|
2271
|
-
if (!c) return chord;
|
|
2272
|
-
const d1 = dist(a, b);
|
|
2273
|
-
const d2 = dist(b, c);
|
|
2274
|
-
if (!(d1 > 0) || !(d2 > 0)) return chord;
|
|
2275
|
-
const ka = -(2 * d1 + d2) / (d1 * (d1 + d2));
|
|
2276
|
-
const kb = (d1 + d2) / (d1 * d2);
|
|
2277
|
-
const kc = -d1 / (d2 * (d1 + d2));
|
|
2278
|
-
const est = unit([ka * a[0] + kb * b[0] + kc * c[0], ka * a[1] + kb * b[1] + kc * c[1]]);
|
|
2279
|
-
return est && chord && dot(est, chord) > Math.cos(Math.PI / 4) ? est : chord;
|
|
2280
|
-
}
|
|
2281
|
-
function fitCubicRun(points, tolerance, t0, t1) {
|
|
2282
|
-
const pts = dedup(points);
|
|
2283
|
-
const out = [];
|
|
2284
|
-
if (pts.length < 2) return out;
|
|
2285
|
-
const n = pts.length;
|
|
2286
|
-
const start = (t0 && unit([t0[0], t0[1]])) ?? tangentFrom(pts[0], pts[1], pts[2]) ?? [1, 0];
|
|
2287
|
-
const back = tangentFrom(pts[n - 1], pts[n - 2], pts[n - 3]);
|
|
2288
|
-
const end = (t1 && unit([t1[0], t1[1]])) ?? (back ? [-back[0], -back[1]] : [1, 0]);
|
|
2289
|
-
fitRec(pts, start, end, Math.max(tolerance, 1e-9), out, 0);
|
|
2290
|
-
return out;
|
|
2291
|
-
}
|
|
2292
|
-
function turnDeg(a, b, c) {
|
|
2293
|
-
const u = unit(sub(b, a));
|
|
2294
|
-
const v = unit(sub(c, b));
|
|
2295
|
-
if (!u || !v) return 0;
|
|
2296
|
-
return Math.acos(Math.min(1, Math.max(-1, dot(u, v)))) * 180 / Math.PI;
|
|
2297
|
-
}
|
|
2298
|
-
function findCorners(pts, closed, cornerAngle, span) {
|
|
2299
|
-
const n = pts.length;
|
|
2300
|
-
const at = (i) => pts[closed ? (i % n + n) % n : i];
|
|
2301
|
-
const reach = (i, step) => {
|
|
2302
|
-
for (let k = 1; k < (closed ? n : Infinity); k++) {
|
|
2303
|
-
const j = i + step * k;
|
|
2304
|
-
if (!closed && (j < 0 || j >= n)) return k > 1 ? j - step : null;
|
|
2305
|
-
if (dist(at(j), pts[i]) >= span) return j;
|
|
2306
|
-
}
|
|
2307
|
-
return null;
|
|
2308
|
-
};
|
|
2309
|
-
const turns = new Float64Array(n);
|
|
2310
|
-
const windows = [];
|
|
2311
|
-
for (let i = 0; i < n; i++) {
|
|
2312
|
-
windows.push([i, i]);
|
|
2313
|
-
if (!closed && (i === 0 || i === n - 1)) continue;
|
|
2314
|
-
const prev = reach(i, -1);
|
|
2315
|
-
const next = reach(i, 1);
|
|
2316
|
-
if (prev === null || next === null) continue;
|
|
2317
|
-
windows[i] = [prev, next];
|
|
2318
|
-
turns[i] = turnDeg(at(prev), pts[i], at(next));
|
|
2319
|
-
}
|
|
2320
|
-
const corners = [];
|
|
2321
|
-
for (let i = 0; i < n; i++) {
|
|
2322
|
-
if (!(turns[i] > cornerAngle)) continue;
|
|
2323
|
-
const [lo, hi] = windows[i];
|
|
2324
|
-
let sharpest = true;
|
|
2325
|
-
for (let j = lo; j <= hi && sharpest; j++) {
|
|
2326
|
-
const k = closed ? (j % n + n) % n : j;
|
|
2327
|
-
if (k === i || dist(pts[k], pts[i]) >= span) continue;
|
|
2328
|
-
if (turns[k] > turns[i] || turns[k] === turns[i] && k < i) sharpest = false;
|
|
2329
|
-
}
|
|
2330
|
-
if (sharpest) corners.push(i);
|
|
2331
|
-
}
|
|
2332
|
-
return corners;
|
|
2333
|
-
}
|
|
2334
|
-
function densify(run) {
|
|
2335
|
-
if (run.length < 3) return run;
|
|
2336
|
-
const lengths = [];
|
|
2337
|
-
for (let i = 1; i < run.length; i++) lengths.push(dist(run[i - 1], run[i]));
|
|
2338
|
-
const sorted = [...lengths].sort((a, b) => a - b);
|
|
2339
|
-
const median = sorted[sorted.length >> 1];
|
|
2340
|
-
if (!(median > 0)) return run;
|
|
2341
|
-
const out = [run[0]];
|
|
2342
|
-
for (let i = 1; i < run.length; i++) {
|
|
2343
|
-
const len = lengths[i - 1];
|
|
2344
|
-
if (len > 2 * median) {
|
|
2345
|
-
const steps = Math.min(64, Math.ceil(len / median));
|
|
2346
|
-
const a = run[i - 1];
|
|
2347
|
-
const b = run[i];
|
|
2348
|
-
for (let k = 1; k < steps; k++) {
|
|
2349
|
-
const t = k / steps;
|
|
2350
|
-
out.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
out.push(run[i]);
|
|
2354
|
-
}
|
|
2355
|
-
return out;
|
|
2356
|
-
}
|
|
2357
|
-
function fitCubics(points, tolerance, options = {}) {
|
|
2358
|
-
let pts = dedup(points);
|
|
2359
|
-
const closed = options.closed === true;
|
|
2360
|
-
if (closed && pts.length > 1 && dist(pts[0], pts[pts.length - 1]) <= 1e-12) pts = pts.slice(0, -1);
|
|
2361
|
-
if (pts.length < 2) return [];
|
|
2362
|
-
const tol = Math.max(tolerance, 1e-6);
|
|
2363
|
-
const cornerAngle = options.cornerAngleDeg ?? 30;
|
|
2364
|
-
const n = pts.length;
|
|
2365
|
-
const span = 4 * tol;
|
|
2366
|
-
if (!closed || n < 3) {
|
|
2367
|
-
const corners2 = [0, ...n > 2 ? findCorners(pts, false, cornerAngle, span) : [], n - 1];
|
|
2368
|
-
const out2 = [];
|
|
2369
|
-
for (let k = 1; k < corners2.length; k++) {
|
|
2370
|
-
const run = pts.slice(corners2[k - 1], corners2[k] + 1);
|
|
2371
|
-
out2.push(...fitCubicRun(densify(run), tol));
|
|
2372
|
-
}
|
|
2373
|
-
if (closed && n === 2) out2.push(...fitCubicRun([pts[1], pts[0]], tol));
|
|
2374
|
-
return out2;
|
|
2375
|
-
}
|
|
2376
|
-
const corners = findCorners(pts, true, cornerAngle, span);
|
|
2377
|
-
if (corners.length === 0) {
|
|
2378
|
-
const ring = [...pts, pts[0]];
|
|
2379
|
-
const t = unit(sub(pts[1], pts[n - 1])) ?? [1, 0];
|
|
2380
|
-
return fitCubicRun(densify(ring), tol, t, t);
|
|
2381
|
-
}
|
|
2382
|
-
const out = [];
|
|
2383
|
-
for (let k = 0; k < corners.length; k++) {
|
|
2384
|
-
const from = corners[k];
|
|
2385
|
-
const to = corners[(k + 1) % corners.length];
|
|
2386
|
-
const run = [];
|
|
2387
|
-
for (let i = from; ; i = (i + 1) % n) {
|
|
2388
|
-
run.push(pts[i]);
|
|
2389
|
-
if (run.length > 1 && i === to) break;
|
|
2390
|
-
}
|
|
2391
|
-
out.push(...fitCubicRun(densify(run), tol));
|
|
2392
|
-
}
|
|
2393
|
-
return out;
|
|
2394
|
-
}
|
|
2395
|
-
function isStraightCubic(c, tol) {
|
|
2396
|
-
const chord = sub(c.p3, c.p0);
|
|
2397
|
-
const len = Math.hypot(chord[0], chord[1]);
|
|
2398
|
-
if (len < 1e-12) return dist(c.p1, c.p0) <= tol && dist(c.p2, c.p0) <= tol;
|
|
2399
|
-
const dir = [chord[0] / len, chord[1] / len];
|
|
2400
|
-
return [c.p1, c.p2].every((h) => {
|
|
2401
|
-
const v = sub(h, c.p0);
|
|
2402
|
-
const along = dot(v, dir);
|
|
2403
|
-
const across = Math.abs(v[0] * dir[1] - v[1] * dir[0]);
|
|
2404
|
-
return across <= tol && along >= -tol && along <= len + tol;
|
|
2405
|
-
});
|
|
2406
|
-
}
|
|
2407
|
-
function cubicsToPathData(segments, options = {}) {
|
|
2408
|
-
if (segments.length === 0) return "";
|
|
2409
|
-
const f = (n) => formatNumber(n, options.precision ?? 3);
|
|
2410
|
-
const dx = options.offset?.x ?? 0;
|
|
2411
|
-
const dy = options.offset?.y ?? 0;
|
|
2412
|
-
const pt = (p) => `${f(p[0] + dx)} ${f(p[1] + dy)}`;
|
|
2413
|
-
const straight = options.straightTolerance ?? 1e-3;
|
|
2414
|
-
let d = `M${pt(segments[0].p0)}`;
|
|
2415
|
-
segments.forEach((c, i) => {
|
|
2416
|
-
const last = options.closed && i === segments.length - 1;
|
|
2417
|
-
if (isStraightCubic(c, straight)) {
|
|
2418
|
-
if (!last) d += `L${pt(c.p3)}`;
|
|
2419
|
-
} else d += `C${pt(c.p1)} ${pt(c.p2)} ${pt(c.p3)}`;
|
|
2420
|
-
});
|
|
2421
|
-
return options.closed ? `${d}Z` : d;
|
|
2422
|
-
}
|
|
2423
|
-
function polylineToPathData(points, closed, options) {
|
|
2424
|
-
if (points.length < 2) return "";
|
|
2425
|
-
const f = (n) => formatNumber(n, options.precision ?? 3);
|
|
2426
|
-
const dx = options.offset?.x ?? 0;
|
|
2427
|
-
const dy = options.offset?.y ?? 0;
|
|
2428
|
-
let d = `M${f(points[0][0] + dx)} ${f(points[0][1] + dy)}`;
|
|
2429
|
-
for (let i = 1; i < points.length; i++) d += `L${f(points[i][0] + dx)} ${f(points[i][1] + dy)}`;
|
|
2430
|
-
return closed ? `${d}Z` : d;
|
|
2431
|
-
}
|
|
2432
|
-
function fitPathData(ring, options = {}) {
|
|
2433
|
-
const closed = options.closed ?? true;
|
|
2434
|
-
const tolerance = options.tolerance ?? 0.5;
|
|
2435
|
-
const pts = dedup(ring);
|
|
2436
|
-
const segments = fitCubics(pts, tolerance, { ...options, closed });
|
|
2437
|
-
if (segments.length === 0) return "";
|
|
2438
|
-
const edges = closed ? pts.length : pts.length - 1;
|
|
2439
|
-
if (options.noWorse !== false) {
|
|
2440
|
-
const straight = segments.filter((c) => isStraightCubic(c, tolerance * 0.02)).length;
|
|
2441
|
-
if (segments.length >= edges && straight === segments.length) return polylineToPathData(pts, closed, options);
|
|
2442
|
-
if (segments.length > edges) return polylineToPathData(pts, closed, options);
|
|
2443
|
-
}
|
|
2444
|
-
return cubicsToPathData(segments, {
|
|
2445
|
-
precision: options.precision,
|
|
2446
|
-
offset: options.offset,
|
|
2447
|
-
closed,
|
|
2448
|
-
straightTolerance: tolerance * 0.02
|
|
2449
|
-
});
|
|
2450
|
-
}
|
|
2451
|
-
function fitFreehandPath(points, options = {}) {
|
|
2452
|
-
return fitPathData(points, { tolerance: 1, ...options, closed: false });
|
|
2453
|
-
}
|
|
2454
|
-
|
|
2455
|
-
// src/canvas/gradient.ts
|
|
2456
|
-
var CANVAS_GRADIENT_PRESETS = {
|
|
2457
|
-
sunset: {
|
|
2458
|
-
type: "linear",
|
|
2459
|
-
angle: 90,
|
|
2460
|
-
stops: [
|
|
2461
|
-
{ offset: 0, color: "#f97316" },
|
|
2462
|
-
{ offset: 1, color: "#ec4899" }
|
|
2463
|
-
]
|
|
2464
|
-
},
|
|
2465
|
-
ocean: {
|
|
2466
|
-
type: "linear",
|
|
2467
|
-
angle: 90,
|
|
2468
|
-
stops: [
|
|
2469
|
-
{ offset: 0, color: "#06b6d4" },
|
|
2470
|
-
{ offset: 1, color: "#3b82f6" }
|
|
2471
|
-
]
|
|
2472
|
-
},
|
|
2473
|
-
aurora: {
|
|
2474
|
-
type: "linear",
|
|
2475
|
-
angle: 90,
|
|
2476
|
-
stops: [
|
|
2477
|
-
{ offset: 0, color: "#10b981" },
|
|
2478
|
-
{ offset: 1, color: "#6366f1" }
|
|
2479
|
-
]
|
|
2480
|
-
},
|
|
2481
|
-
neon: {
|
|
2482
|
-
type: "linear",
|
|
2483
|
-
angle: 90,
|
|
2484
|
-
stops: [
|
|
2485
|
-
{ offset: 0, color: "#a855f7" },
|
|
2486
|
-
{ offset: 1, color: "#ec4899" }
|
|
2487
|
-
]
|
|
2488
|
-
},
|
|
2489
|
-
amber: {
|
|
2490
|
-
type: "linear",
|
|
2491
|
-
angle: 90,
|
|
2492
|
-
stops: [
|
|
2493
|
-
{ offset: 0, color: "#f59e0b" },
|
|
2494
|
-
{ offset: 1, color: "#ef4444" }
|
|
2495
|
-
]
|
|
2496
|
-
},
|
|
2497
|
-
cosmic: {
|
|
2498
|
-
type: "linear",
|
|
2499
|
-
angle: 90,
|
|
2500
|
-
stops: [
|
|
2501
|
-
{ offset: 0, color: "#3b82f6" },
|
|
2502
|
-
{ offset: 0.5, color: "#8b5cf6" },
|
|
2503
|
-
{ offset: 1, color: "#ec4899" }
|
|
2504
|
-
]
|
|
2505
|
-
}
|
|
2506
|
-
};
|
|
2507
|
-
function isCanvasGradient(value) {
|
|
2508
|
-
if (!value || typeof value !== "object") return false;
|
|
2509
|
-
const val = value;
|
|
2510
|
-
if (val.type !== "linear" && val.type !== "radial") return false;
|
|
2511
|
-
if (!Array.isArray(val.stops)) return false;
|
|
2512
|
-
return true;
|
|
2513
|
-
}
|
|
2514
|
-
function splitCssArgs(str) {
|
|
2515
|
-
const parts = [];
|
|
2516
|
-
let depth = 0;
|
|
2517
|
-
let current = "";
|
|
2518
|
-
for (let i = 0; i < str.length; i++) {
|
|
2519
|
-
const char = str[i];
|
|
2520
|
-
if (char === "(") depth++;
|
|
2521
|
-
else if (char === ")") depth = Math.max(0, depth - 1);
|
|
2522
|
-
if (char === "," && depth === 0) {
|
|
2523
|
-
if (current.trim()) parts.push(current.trim());
|
|
2524
|
-
current = "";
|
|
2525
|
-
} else {
|
|
2526
|
-
current += char;
|
|
2527
|
-
}
|
|
2528
|
-
}
|
|
2529
|
-
if (current.trim()) parts.push(current.trim());
|
|
2530
|
-
return parts;
|
|
2531
|
-
}
|
|
2532
|
-
function parseCssAngle(val) {
|
|
2533
|
-
const trimmed = val.trim().toLowerCase();
|
|
2534
|
-
if (trimmed.endsWith("deg")) {
|
|
2535
|
-
return parseFloat(trimmed);
|
|
2536
|
-
}
|
|
2537
|
-
if (trimmed.endsWith("turn")) {
|
|
2538
|
-
return parseFloat(trimmed) * 360;
|
|
2539
|
-
}
|
|
2540
|
-
if (trimmed.endsWith("rad")) {
|
|
2541
|
-
return parseFloat(trimmed) * 180 / Math.PI;
|
|
2542
|
-
}
|
|
2543
|
-
if (trimmed.startsWith("to ")) {
|
|
2544
|
-
const dir = trimmed.slice(3).trim();
|
|
2545
|
-
if (dir === "bottom") return 180;
|
|
2546
|
-
if (dir === "top") return 0;
|
|
2547
|
-
if (dir === "right") return 90;
|
|
2548
|
-
if (dir === "left") return 270;
|
|
2549
|
-
if (dir === "bottom right" || dir === "right bottom") return 135;
|
|
2550
|
-
if (dir === "bottom left" || dir === "left bottom") return 225;
|
|
2551
|
-
if (dir === "top right" || dir === "right top") return 45;
|
|
2552
|
-
if (dir === "top left" || dir === "left top") return 315;
|
|
2553
|
-
}
|
|
2554
|
-
const n = parseFloat(trimmed);
|
|
2555
|
-
return Number.isFinite(n) ? n : 180;
|
|
2556
|
-
}
|
|
2557
|
-
function parseCssGradient(css) {
|
|
2558
|
-
const trimmed = css.trim();
|
|
2559
|
-
const linearMatch = trimmed.match(/^linear-gradient\s*\((.*)\)$/is);
|
|
2560
|
-
if (linearMatch) {
|
|
2561
|
-
const rawArgs = splitCssArgs(linearMatch[1]);
|
|
2562
|
-
if (rawArgs.length === 0) return null;
|
|
2563
|
-
let angle = 180;
|
|
2564
|
-
let stopStartIndex = 0;
|
|
2565
|
-
const first = rawArgs[0].trim().toLowerCase();
|
|
2566
|
-
if (first.endsWith("deg") || first.endsWith("turn") || first.endsWith("rad") || first.startsWith("to ")) {
|
|
2567
|
-
angle = parseCssAngle(first);
|
|
2568
|
-
stopStartIndex = 1;
|
|
2569
|
-
}
|
|
2570
|
-
const stopStrings = rawArgs.slice(stopStartIndex);
|
|
2571
|
-
if (stopStrings.length === 0) return null;
|
|
2572
|
-
const stops = parseStopStrings(stopStrings);
|
|
2573
|
-
if (stops.length === 0) return null;
|
|
2574
|
-
return {
|
|
2575
|
-
type: "linear",
|
|
2576
|
-
angle,
|
|
2577
|
-
stops
|
|
2578
|
-
};
|
|
2579
|
-
}
|
|
2580
|
-
const radialMatch = trimmed.match(/^radial-gradient\s*\((.*)\)$/is);
|
|
2581
|
-
if (radialMatch) {
|
|
2582
|
-
const rawArgs = splitCssArgs(radialMatch[1]);
|
|
2583
|
-
if (rawArgs.length === 0) return null;
|
|
2584
|
-
let stopStartIndex = 0;
|
|
2585
|
-
const first = rawArgs[0].trim().toLowerCase();
|
|
2586
|
-
if (first.startsWith("circle") || first.startsWith("ellipse") || first.includes("at ")) {
|
|
2587
|
-
stopStartIndex = 1;
|
|
2588
|
-
}
|
|
2589
|
-
const stopStrings = rawArgs.slice(stopStartIndex);
|
|
2590
|
-
if (stopStrings.length === 0) return null;
|
|
2591
|
-
const stops = parseStopStrings(stopStrings);
|
|
2592
|
-
if (stops.length === 0) return null;
|
|
2593
|
-
return {
|
|
2594
|
-
type: "radial",
|
|
2595
|
-
cx: 0.5,
|
|
2596
|
-
cy: 0.5,
|
|
2597
|
-
r: 0.5,
|
|
2598
|
-
stops
|
|
2599
|
-
};
|
|
2600
|
-
}
|
|
2601
|
-
return null;
|
|
2602
|
-
}
|
|
2603
|
-
function parseStopStrings(stopStrings) {
|
|
2604
|
-
const count = stopStrings.length;
|
|
2605
|
-
return stopStrings.map((str, idx) => {
|
|
2606
|
-
const parts = str.trim().split(/\s+(?=[0-9%])/);
|
|
2607
|
-
const color = parts[0];
|
|
2608
|
-
let offset = count === 1 ? 0 : idx / (count - 1);
|
|
2609
|
-
if (parts.length > 1) {
|
|
2610
|
-
const posStr = parts[1].trim();
|
|
2611
|
-
if (posStr.endsWith("%")) {
|
|
2612
|
-
offset = parseFloat(posStr) / 100;
|
|
2613
|
-
} else {
|
|
2614
|
-
const val = parseFloat(posStr);
|
|
2615
|
-
if (Number.isFinite(val)) offset = val;
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
return {
|
|
2619
|
-
offset: Math.min(1, Math.max(0, offset)),
|
|
2620
|
-
color
|
|
2621
|
-
};
|
|
2622
|
-
});
|
|
2623
|
-
}
|
|
2624
|
-
function resolveCanvasGradient(value) {
|
|
2625
|
-
if (!value) return null;
|
|
2626
|
-
if (isCanvasGradient(value)) return value;
|
|
2627
|
-
if (typeof value === "string") {
|
|
2628
|
-
const lower = value.trim().toLowerCase();
|
|
2629
|
-
if (CANVAS_GRADIENT_PRESETS[lower]) {
|
|
2630
|
-
return CANVAS_GRADIENT_PRESETS[lower];
|
|
2631
|
-
}
|
|
2632
|
-
if (lower.startsWith("linear-gradient") || lower.startsWith("radial-gradient")) {
|
|
2633
|
-
return parseCssGradient(value);
|
|
2634
|
-
}
|
|
2635
|
-
}
|
|
2636
|
-
return null;
|
|
2637
|
-
}
|
|
2638
|
-
function angleToSvgLinearCoords(angleDeg = 180) {
|
|
2639
|
-
const rad = (90 - angleDeg) * Math.PI / 180;
|
|
2640
|
-
const dx = Math.cos(rad);
|
|
2641
|
-
const dy = -Math.sin(rad);
|
|
2642
|
-
const half = 0.5;
|
|
2643
|
-
return {
|
|
2644
|
-
x1: Math.round((half - dx * half) * 1e3) / 1e3,
|
|
2645
|
-
y1: Math.round((half - dy * half) * 1e3) / 1e3,
|
|
2646
|
-
x2: Math.round((half + dx * half) * 1e3) / 1e3,
|
|
2647
|
-
y2: Math.round((half + dy * half) * 1e3) / 1e3
|
|
2648
|
-
};
|
|
2649
|
-
}
|
|
2650
|
-
function gradientToSvgDef(gradient, id) {
|
|
2651
|
-
const stops = (gradient.stops ?? []).map((s) => ({
|
|
2652
|
-
offset: `${Math.round(s.offset * 1e3) / 10}%`,
|
|
2653
|
-
stopColor: s.color,
|
|
2654
|
-
stopOpacity: s.opacity
|
|
2655
|
-
}));
|
|
2656
|
-
if (gradient.type === "radial") {
|
|
2657
|
-
return {
|
|
2658
|
-
id,
|
|
2659
|
-
type: "radial",
|
|
2660
|
-
attrs: {
|
|
2661
|
-
cx: gradient.cx !== void 0 ? `${gradient.cx * 100}%` : "50%",
|
|
2662
|
-
cy: gradient.cy !== void 0 ? `${gradient.cy * 100}%` : "50%",
|
|
2663
|
-
r: gradient.r !== void 0 ? `${gradient.r * 100}%` : "50%"
|
|
2664
|
-
},
|
|
2665
|
-
stops
|
|
2666
|
-
};
|
|
2667
|
-
}
|
|
2668
|
-
const coords = angleToSvgLinearCoords(gradient.angle ?? 180);
|
|
2669
|
-
return {
|
|
2670
|
-
id,
|
|
2671
|
-
type: "linear",
|
|
2672
|
-
attrs: {
|
|
2673
|
-
x1: `${coords.x1 * 100}%`,
|
|
2674
|
-
y1: `${coords.y1 * 100}%`,
|
|
2675
|
-
x2: `${coords.x2 * 100}%`,
|
|
2676
|
-
y2: `${coords.y2 * 100}%`
|
|
2677
|
-
},
|
|
2678
|
-
stops
|
|
2679
|
-
};
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
|
-
// src/canvas/shapeGeometry.ts
|
|
2683
|
-
var SHAPE_KINDS = ["rect", "ellipse", "polygon", "star", "path"];
|
|
2684
|
-
function isShapeDescription(value) {
|
|
2685
|
-
if (!value || typeof value !== "object") return false;
|
|
2686
|
-
const kind = value.kind;
|
|
2687
|
-
if (!SHAPE_KINDS.includes(kind)) return false;
|
|
2688
|
-
return kind !== "path" || typeof value.d === "string";
|
|
2689
|
-
}
|
|
2690
|
-
var PARAM_COUNTS = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0 };
|
|
2691
|
-
var PathScanner = class {
|
|
2692
|
-
constructor(text) {
|
|
2693
|
-
this.text = text;
|
|
2694
|
-
}
|
|
2695
|
-
text;
|
|
2696
|
-
pos = 0;
|
|
2697
|
-
skip() {
|
|
2698
|
-
while (this.pos < this.text.length && /[\s,]/.test(this.text[this.pos])) this.pos++;
|
|
2699
|
-
}
|
|
2700
|
-
/** The next command letter, or `null` when a number (or the end) follows. */
|
|
2701
|
-
command() {
|
|
2702
|
-
this.skip();
|
|
2703
|
-
const ch = this.text[this.pos];
|
|
2704
|
-
if (ch && /[MmLlHhVvCcSsQqTtAaZz]/.test(ch)) {
|
|
2705
|
-
this.pos++;
|
|
2706
|
-
return ch;
|
|
2707
|
-
}
|
|
2708
|
-
return null;
|
|
2709
|
-
}
|
|
2710
|
-
done() {
|
|
2711
|
-
this.skip();
|
|
2712
|
-
return this.pos >= this.text.length;
|
|
2713
|
-
}
|
|
2714
|
-
number() {
|
|
2715
|
-
this.skip();
|
|
2716
|
-
const match = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(this.text.slice(this.pos, this.pos + 64));
|
|
2717
|
-
if (!match) return null;
|
|
2718
|
-
this.pos += match[0].length;
|
|
2719
|
-
const value = Number(match[0]);
|
|
2720
|
-
return Number.isFinite(value) ? value : null;
|
|
2721
|
-
}
|
|
2722
|
-
flag() {
|
|
2723
|
-
this.skip();
|
|
2724
|
-
const ch = this.text[this.pos];
|
|
2725
|
-
if (ch === "0" || ch === "1") {
|
|
2726
|
-
this.pos++;
|
|
2727
|
-
return ch === "1" ? 1 : 0;
|
|
2728
|
-
}
|
|
2729
|
-
return null;
|
|
2730
|
-
}
|
|
2731
|
-
};
|
|
2732
|
-
function parseSvgPath(d) {
|
|
2733
|
-
const scanner = new PathScanner(d ?? "");
|
|
2734
|
-
const commands = [];
|
|
2735
|
-
let cx = 0;
|
|
2736
|
-
let cy = 0;
|
|
2737
|
-
let startX = 0;
|
|
2738
|
-
let startY = 0;
|
|
2739
|
-
let lastControl = null;
|
|
2740
|
-
let command = "";
|
|
2741
|
-
while (!scanner.done()) {
|
|
2742
|
-
const letter = scanner.command();
|
|
2743
|
-
if (letter) {
|
|
2744
|
-
command = letter;
|
|
2745
|
-
if (command === "z" || command === "Z") {
|
|
2746
|
-
commands.push({ type: "Z" });
|
|
2747
|
-
cx = startX;
|
|
2748
|
-
cy = startY;
|
|
2749
|
-
lastControl = null;
|
|
2750
|
-
continue;
|
|
2751
|
-
}
|
|
2752
|
-
} else if (!command || command === "z" || command === "Z") {
|
|
2753
|
-
break;
|
|
2754
|
-
}
|
|
2755
|
-
const lower = command.toLowerCase();
|
|
2756
|
-
const count = PARAM_COUNTS[lower];
|
|
2757
|
-
const args = [];
|
|
2758
|
-
for (let k = 0; k < count; k++) {
|
|
2759
|
-
const value = lower === "a" && (k === 3 || k === 4) ? scanner.flag() : scanner.number();
|
|
2760
|
-
if (value === null) return commands;
|
|
2761
|
-
args.push(value);
|
|
2762
|
-
}
|
|
2763
|
-
const rel = command !== command.toUpperCase();
|
|
2764
|
-
const ox = rel ? cx : 0;
|
|
2765
|
-
const oy = rel ? cy : 0;
|
|
2766
|
-
switch (lower) {
|
|
2767
|
-
case "m": {
|
|
2768
|
-
cx = args[0] + ox;
|
|
2769
|
-
cy = args[1] + oy;
|
|
2770
|
-
startX = cx;
|
|
2771
|
-
startY = cy;
|
|
2772
|
-
commands.push({ type: "M", x: cx, y: cy });
|
|
2773
|
-
lastControl = null;
|
|
2774
|
-
command = rel ? "l" : "L";
|
|
2775
|
-
break;
|
|
2776
|
-
}
|
|
2777
|
-
case "l":
|
|
2778
|
-
cx = args[0] + ox;
|
|
2779
|
-
cy = args[1] + oy;
|
|
2780
|
-
commands.push({ type: "L", x: cx, y: cy });
|
|
2781
|
-
lastControl = null;
|
|
2782
|
-
break;
|
|
2783
|
-
case "h":
|
|
2784
|
-
cx = args[0] + (rel ? cx : 0);
|
|
2785
|
-
commands.push({ type: "L", x: cx, y: cy });
|
|
2786
|
-
lastControl = null;
|
|
2787
|
-
break;
|
|
2788
|
-
case "v":
|
|
2789
|
-
cy = args[0] + (rel ? cy : 0);
|
|
2790
|
-
commands.push({ type: "L", x: cx, y: cy });
|
|
2791
|
-
lastControl = null;
|
|
2792
|
-
break;
|
|
2793
|
-
case "c": {
|
|
2794
|
-
const c = {
|
|
2795
|
-
type: "C",
|
|
2796
|
-
x1: args[0] + ox,
|
|
2797
|
-
y1: args[1] + oy,
|
|
2798
|
-
x2: args[2] + ox,
|
|
2799
|
-
y2: args[3] + oy,
|
|
2800
|
-
x: args[4] + ox,
|
|
2801
|
-
y: args[5] + oy
|
|
2802
|
-
};
|
|
2803
|
-
commands.push(c);
|
|
2804
|
-
lastControl = { x: c.x2, y: c.y2, type: "C" };
|
|
2805
|
-
cx = c.x;
|
|
2806
|
-
cy = c.y;
|
|
2807
|
-
break;
|
|
2808
|
-
}
|
|
2809
|
-
case "s": {
|
|
2810
|
-
const x1 = lastControl?.type === "C" ? 2 * cx - lastControl.x : cx;
|
|
2811
|
-
const y1 = lastControl?.type === "C" ? 2 * cy - lastControl.y : cy;
|
|
2812
|
-
const c = {
|
|
2813
|
-
type: "C",
|
|
2814
|
-
x1,
|
|
2815
|
-
y1,
|
|
2816
|
-
x2: args[0] + ox,
|
|
2817
|
-
y2: args[1] + oy,
|
|
2818
|
-
x: args[2] + ox,
|
|
2819
|
-
y: args[3] + oy
|
|
2820
|
-
};
|
|
2821
|
-
commands.push(c);
|
|
2822
|
-
lastControl = { x: c.x2, y: c.y2, type: "C" };
|
|
2823
|
-
cx = c.x;
|
|
2824
|
-
cy = c.y;
|
|
2825
|
-
break;
|
|
2826
|
-
}
|
|
2827
|
-
case "q": {
|
|
2828
|
-
const c = { type: "Q", x1: args[0] + ox, y1: args[1] + oy, x: args[2] + ox, y: args[3] + oy };
|
|
2829
|
-
commands.push(c);
|
|
2830
|
-
lastControl = { x: c.x1, y: c.y1, type: "Q" };
|
|
2831
|
-
cx = c.x;
|
|
2832
|
-
cy = c.y;
|
|
2833
|
-
break;
|
|
2834
|
-
}
|
|
2835
|
-
case "t": {
|
|
2836
|
-
const x1 = lastControl?.type === "Q" ? 2 * cx - lastControl.x : cx;
|
|
2837
|
-
const y1 = lastControl?.type === "Q" ? 2 * cy - lastControl.y : cy;
|
|
2838
|
-
const c = { type: "Q", x1, y1, x: args[0] + ox, y: args[1] + oy };
|
|
2839
|
-
commands.push(c);
|
|
2840
|
-
lastControl = { x: x1, y: y1, type: "Q" };
|
|
2841
|
-
cx = c.x;
|
|
2842
|
-
cy = c.y;
|
|
2843
|
-
break;
|
|
2844
|
-
}
|
|
2845
|
-
case "a": {
|
|
2846
|
-
const c = {
|
|
2847
|
-
type: "A",
|
|
2848
|
-
rx: Math.abs(args[0]),
|
|
2849
|
-
ry: Math.abs(args[1]),
|
|
2850
|
-
rotation: args[2],
|
|
2851
|
-
largeArc: args[3] !== 0,
|
|
2852
|
-
sweep: args[4] !== 0,
|
|
2853
|
-
x: args[5] + ox,
|
|
2854
|
-
y: args[6] + oy
|
|
2855
|
-
};
|
|
2856
|
-
commands.push(c);
|
|
2857
|
-
lastControl = null;
|
|
2858
|
-
cx = c.x;
|
|
2859
|
-
cy = c.y;
|
|
2860
|
-
break;
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
}
|
|
2864
|
-
return commands;
|
|
2865
|
-
}
|
|
2866
|
-
function segmentCount(curvature, tolerance, max) {
|
|
2867
|
-
if (!(curvature > 0)) return 1;
|
|
2868
|
-
return Math.max(1, Math.min(max, Math.ceil(Math.sqrt(curvature / tolerance))));
|
|
2869
|
-
}
|
|
2870
|
-
function flattenCubic(out, p0, x1, y1, x2, y2, x, y, tolerance, max) {
|
|
2871
|
-
const dd = Math.max(
|
|
2872
|
-
Math.hypot(p0[0] - 2 * x1 + x2, p0[1] - 2 * y1 + y2),
|
|
2873
|
-
Math.hypot(x1 - 2 * x2 + x, y1 - 2 * y2 + y)
|
|
2874
|
-
);
|
|
2875
|
-
const n = segmentCount(6 * dd / 8, tolerance, max);
|
|
2876
|
-
for (let k = 1; k <= n; k++) {
|
|
2877
|
-
const t = k / n;
|
|
2878
|
-
const mt = 1 - t;
|
|
2879
|
-
const a = mt * mt * mt;
|
|
2880
|
-
const b = 3 * mt * mt * t;
|
|
2881
|
-
const c = 3 * mt * t * t;
|
|
2882
|
-
const e = t * t * t;
|
|
2883
|
-
out.push([a * p0[0] + b * x1 + c * x2 + e * x, a * p0[1] + b * y1 + c * y2 + e * y]);
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
function flattenQuadratic(out, p0, x1, y1, x, y, tolerance, max) {
|
|
2887
|
-
const dd = Math.hypot(p0[0] - 2 * x1 + x, p0[1] - 2 * y1 + y);
|
|
2888
|
-
const n = segmentCount(2 * dd / 8, tolerance, max);
|
|
2889
|
-
for (let k = 1; k <= n; k++) {
|
|
2890
|
-
const t = k / n;
|
|
2891
|
-
const mt = 1 - t;
|
|
2892
|
-
out.push([mt * mt * p0[0] + 2 * mt * t * x1 + t * t * x, mt * mt * p0[1] + 2 * mt * t * y1 + t * t * y]);
|
|
2893
|
-
}
|
|
2894
|
-
}
|
|
2895
|
-
function arcSteps(radius, sweep, tolerance, max) {
|
|
2896
|
-
if (radius <= tolerance) return Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2)));
|
|
2897
|
-
const step = 2 * Math.acos(1 - tolerance / radius);
|
|
2898
|
-
return Math.max(1, Math.min(max, Math.ceil(Math.abs(sweep) / step)));
|
|
2899
|
-
}
|
|
2900
|
-
function flattenArc(out, p0, cmd, tolerance, max) {
|
|
2901
|
-
const [x0, y0] = p0;
|
|
2902
|
-
let { rx, ry } = cmd;
|
|
2903
|
-
const { x, y } = cmd;
|
|
2904
|
-
if (x0 === x && y0 === y) return;
|
|
2905
|
-
if (rx === 0 || ry === 0) {
|
|
2906
|
-
out.push([x, y]);
|
|
2907
|
-
return;
|
|
2908
|
-
}
|
|
2909
|
-
const phi = cmd.rotation * Math.PI / 180;
|
|
2910
|
-
const cos = Math.cos(phi);
|
|
2911
|
-
const sin = Math.sin(phi);
|
|
2912
|
-
const dx = (x0 - x) / 2;
|
|
2913
|
-
const dy = (y0 - y) / 2;
|
|
2914
|
-
const x1p = cos * dx + sin * dy;
|
|
2915
|
-
const y1p = -sin * dx + cos * dy;
|
|
2916
|
-
const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
|
|
2917
|
-
if (lambda > 1) {
|
|
2918
|
-
const s = Math.sqrt(lambda);
|
|
2919
|
-
rx *= s;
|
|
2920
|
-
ry *= s;
|
|
2921
|
-
}
|
|
2922
|
-
const num2 = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
|
|
2923
|
-
const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
|
|
2924
|
-
let coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num2 / den));
|
|
2925
|
-
if (cmd.largeArc === cmd.sweep) coef = -coef;
|
|
2926
|
-
const cxp = coef * rx * y1p / ry;
|
|
2927
|
-
const cyp = -coef * ry * x1p / rx;
|
|
2928
|
-
const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
|
|
2929
|
-
const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
|
|
2930
|
-
const angle = (ux, uy, vx, vy) => {
|
|
2931
|
-
const a = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
2932
|
-
return a;
|
|
2933
|
-
};
|
|
2934
|
-
const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
|
|
2935
|
-
let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
|
|
2936
|
-
if (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;
|
|
2937
|
-
else if (cmd.sweep && delta < 0) delta += 2 * Math.PI;
|
|
2938
|
-
const n = arcSteps(Math.max(rx, ry), delta, tolerance, max);
|
|
2939
|
-
for (let k = 1; k < n; k++) {
|
|
2940
|
-
const t = theta1 + delta * k / n;
|
|
2941
|
-
const ex = rx * Math.cos(t);
|
|
2942
|
-
const ey = ry * Math.sin(t);
|
|
2943
|
-
out.push([cos * ex - sin * ey + cx, sin * ex + cos * ey + cy]);
|
|
2944
|
-
}
|
|
2945
|
-
out.push([x, y]);
|
|
2946
|
-
}
|
|
2947
|
-
function flattenSvgPath(d, options = {}) {
|
|
2948
|
-
const tolerance = options.tolerance && options.tolerance > 0 ? options.tolerance : 0.25;
|
|
2949
|
-
const max = options.maxSegments ?? 512;
|
|
2950
|
-
const commands = typeof d === "string" ? parseSvgPath(d) : d;
|
|
2951
|
-
const rings = [];
|
|
2952
|
-
let current = [];
|
|
2953
|
-
let start = [0, 0];
|
|
2954
|
-
const flush = () => {
|
|
2955
|
-
if (current.length >= 3) rings.push(current);
|
|
2956
|
-
current = [];
|
|
2957
|
-
};
|
|
2958
|
-
const last = () => current[current.length - 1] ?? start;
|
|
2959
|
-
for (const cmd of commands) {
|
|
2960
|
-
switch (cmd.type) {
|
|
2961
|
-
case "M":
|
|
2962
|
-
flush();
|
|
2963
|
-
start = [cmd.x, cmd.y];
|
|
2964
|
-
current = [start];
|
|
2965
|
-
break;
|
|
2966
|
-
case "L":
|
|
2967
|
-
if (current.length === 0) current = [start];
|
|
2968
|
-
current.push([cmd.x, cmd.y]);
|
|
2969
|
-
break;
|
|
2970
|
-
case "C":
|
|
2971
|
-
if (current.length === 0) current = [start];
|
|
2972
|
-
flattenCubic(current, last(), cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y, tolerance, max);
|
|
2973
|
-
break;
|
|
2974
|
-
case "Q":
|
|
2975
|
-
if (current.length === 0) current = [start];
|
|
2976
|
-
flattenQuadratic(current, last(), cmd.x1, cmd.y1, cmd.x, cmd.y, tolerance, max);
|
|
2977
|
-
break;
|
|
2978
|
-
case "A":
|
|
2979
|
-
if (current.length === 0) current = [start];
|
|
2980
|
-
flattenArc(current, last(), cmd, tolerance, max);
|
|
2981
|
-
break;
|
|
2982
|
-
case "Z":
|
|
2983
|
-
flush();
|
|
2984
|
-
break;
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
flush();
|
|
2988
|
-
return rings.map(dedupeRing).filter((r) => r.length >= 3);
|
|
2989
|
-
}
|
|
2990
|
-
function dedupeRing(ring) {
|
|
2991
|
-
const out = [];
|
|
2992
|
-
for (const p of ring) {
|
|
2993
|
-
const prev = out[out.length - 1];
|
|
2994
|
-
if (!prev || prev[0] !== p[0] || prev[1] !== p[1]) out.push(p);
|
|
2995
|
-
}
|
|
2996
|
-
while (out.length > 1 && out[0][0] === out[out.length - 1][0] && out[0][1] === out[out.length - 1][1]) out.pop();
|
|
2997
|
-
return out;
|
|
2998
|
-
}
|
|
2999
|
-
function cornerRadii(shape, width, height) {
|
|
3000
|
-
const raw = shape.cornerRadius ?? 0;
|
|
3001
|
-
let radii = (typeof raw === "number" ? [raw, raw, raw, raw] : [...raw]).map(
|
|
3002
|
-
(r) => Number.isFinite(r) && r > 0 ? r : 0
|
|
3003
|
-
);
|
|
3004
|
-
const factor = Math.min(
|
|
3005
|
-
1,
|
|
3006
|
-
width / Math.max(radii[0] + radii[1], 1e-12),
|
|
3007
|
-
height / Math.max(radii[1] + radii[2], 1e-12),
|
|
3008
|
-
width / Math.max(radii[2] + radii[3], 1e-12),
|
|
3009
|
-
height / Math.max(radii[3] + radii[0], 1e-12)
|
|
3010
|
-
);
|
|
3011
|
-
if (factor < 1) radii = radii.map((r) => r * factor);
|
|
3012
|
-
return radii;
|
|
3013
|
-
}
|
|
3014
|
-
function polygonVertices(count, inner, width, height) {
|
|
3015
|
-
const raw = [];
|
|
3016
|
-
const total = inner === null ? count : count * 2;
|
|
3017
|
-
for (let k = 0; k < total; k++) {
|
|
3018
|
-
const angle = -Math.PI / 2 + k * 2 * Math.PI / total;
|
|
3019
|
-
const r = inner !== null && k % 2 === 1 ? inner : 1;
|
|
3020
|
-
raw.push([r * Math.cos(angle), r * Math.sin(angle)]);
|
|
3021
|
-
}
|
|
3022
|
-
let minX = Infinity;
|
|
3023
|
-
let minY = Infinity;
|
|
3024
|
-
let maxX = -Infinity;
|
|
3025
|
-
let maxY = -Infinity;
|
|
3026
|
-
for (const [x, y] of raw) {
|
|
3027
|
-
minX = Math.min(minX, x);
|
|
3028
|
-
minY = Math.min(minY, y);
|
|
3029
|
-
maxX = Math.max(maxX, x);
|
|
3030
|
-
maxY = Math.max(maxY, y);
|
|
3031
|
-
}
|
|
3032
|
-
const sx = width / (maxX - minX || 1);
|
|
3033
|
-
const sy = height / (maxY - minY || 1);
|
|
3034
|
-
return raw.map(([x, y]) => [(x - minX) * sx, (y - minY) * sy]);
|
|
3035
|
-
}
|
|
3036
|
-
function rotationTransform(shape, size) {
|
|
3037
|
-
const deg = shape.rotation ?? 0;
|
|
3038
|
-
if (!deg || !Number.isFinite(deg)) return null;
|
|
3039
|
-
const rad = deg * Math.PI / 180;
|
|
3040
|
-
const cos = Math.cos(rad);
|
|
3041
|
-
const sin = Math.sin(rad);
|
|
3042
|
-
const cx = size.width / 2;
|
|
3043
|
-
const cy = size.height / 2;
|
|
3044
|
-
return ([x, y]) => [cx + (x - cx) * cos - (y - cy) * sin, cy + (x - cx) * sin + (y - cy) * cos];
|
|
3045
|
-
}
|
|
3046
|
-
function getShapeSvg(shape, size) {
|
|
3047
|
-
const { width: w, height: h } = size;
|
|
3048
|
-
const f = (n) => formatNumber2(n, 3);
|
|
3049
|
-
const transforms = [];
|
|
3050
|
-
if (shape.rotation) transforms.push(`rotate(${f(shape.rotation)} ${f(w / 2)} ${f(h / 2)})`);
|
|
3051
|
-
let d = "";
|
|
3052
|
-
let fillRule = "nonzero";
|
|
3053
|
-
switch (shape.kind) {
|
|
3054
|
-
case "rect": {
|
|
3055
|
-
const [tl, tr, br, bl] = cornerRadii(shape, w, h);
|
|
3056
|
-
if (tl + tr + br + bl === 0) {
|
|
3057
|
-
d = `M0 0H${f(w)}V${f(h)}H0Z`;
|
|
3058
|
-
} else {
|
|
3059
|
-
const arc = (r, x, y) => r > 0 ? `A${f(r)} ${f(r)} 0 0 1 ${f(x)} ${f(y)}` : "";
|
|
3060
|
-
d = `M${f(tl)} 0H${f(w - tr)}${arc(tr, w, tr)}V${f(h - br)}${arc(br, w - br, h)}H${f(bl)}${arc(bl, 0, h - bl)}V${f(tl)}${arc(tl, tl, 0)}Z`;
|
|
3061
|
-
}
|
|
3062
|
-
break;
|
|
3063
|
-
}
|
|
3064
|
-
case "ellipse": {
|
|
3065
|
-
const rx = w / 2;
|
|
3066
|
-
const ry = h / 2;
|
|
3067
|
-
d = `M0 ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 ${f(w)} ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 0 ${f(ry)}Z`;
|
|
3068
|
-
break;
|
|
3069
|
-
}
|
|
3070
|
-
case "polygon":
|
|
3071
|
-
case "star": {
|
|
3072
|
-
const points = shape.kind === "polygon" ? polygonVertices(Math.max(3, Math.floor(shape.sides ?? 6)), null, w, h) : polygonVertices(Math.max(3, Math.floor(shape.points ?? 5)), clampRatio(shape.innerRadius), w, h);
|
|
3073
|
-
d = ringsToPath([points], f);
|
|
3074
|
-
break;
|
|
3075
|
-
}
|
|
3076
|
-
case "path": {
|
|
3077
|
-
d = shape.d;
|
|
3078
|
-
fillRule = shape.fillRule ?? "nonzero";
|
|
3079
|
-
const vb = validViewBox(shape.viewBox);
|
|
3080
|
-
if (vb) {
|
|
3081
|
-
transforms.push(
|
|
3082
|
-
`scale(${f(w / vb[2])} ${f(h / vb[3])})`,
|
|
3083
|
-
`translate(${f(-vb[0])} ${f(-vb[1])})`
|
|
3084
|
-
);
|
|
3085
|
-
}
|
|
3086
|
-
break;
|
|
3087
|
-
}
|
|
3088
|
-
}
|
|
3089
|
-
return transforms.length > 0 ? { d, transform: transforms.join(" "), fillRule } : { d, fillRule };
|
|
3090
|
-
}
|
|
3091
|
-
function getShapeRenderData(shape, size = {}, options = {}) {
|
|
3092
|
-
const vb = shape.kind === "path" ? validViewBox(shape.viewBox) : null;
|
|
3093
|
-
const width = size.width && size.width > 0 ? size.width : vb ? vb[2] : 100;
|
|
3094
|
-
const height = size.height && size.height > 0 ? size.height : vb ? vb[3] : 100;
|
|
3095
|
-
const svg = getShapeSvg(shape, { width, height });
|
|
3096
|
-
const baseId = options.id || Math.random().toString(36).slice(2, 9);
|
|
3097
|
-
let fillStr = "currentColor";
|
|
3098
|
-
let fillGradientDef;
|
|
3099
|
-
const rawFill = shape.fillGradient ?? shape.fill;
|
|
3100
|
-
const resolvedFillGradient = resolveCanvasGradient(rawFill);
|
|
3101
|
-
if (resolvedFillGradient) {
|
|
3102
|
-
const gradId = `shape-fill-${baseId}`;
|
|
3103
|
-
fillGradientDef = gradientToSvgDef(resolvedFillGradient, gradId);
|
|
3104
|
-
fillStr = `url(#${gradId})`;
|
|
3105
|
-
} else if (typeof shape.fill === "string") {
|
|
3106
|
-
fillStr = shape.fill;
|
|
3107
|
-
} else if (shape.fill === void 0) {
|
|
3108
|
-
fillStr = "currentColor";
|
|
3109
|
-
}
|
|
3110
|
-
let strokeStr = "none";
|
|
3111
|
-
let strokeGradientDef;
|
|
3112
|
-
const rawStroke = shape.strokeGradient ?? shape.stroke;
|
|
3113
|
-
const resolvedStrokeGradient = resolveCanvasGradient(rawStroke);
|
|
3114
|
-
if (resolvedStrokeGradient) {
|
|
3115
|
-
const gradId = `shape-stroke-${baseId}`;
|
|
3116
|
-
strokeGradientDef = gradientToSvgDef(resolvedStrokeGradient, gradId);
|
|
3117
|
-
strokeStr = `url(#${gradId})`;
|
|
3118
|
-
} else if (typeof shape.stroke === "string") {
|
|
3119
|
-
strokeStr = shape.stroke;
|
|
3120
|
-
} else if (shape.stroke === void 0) {
|
|
3121
|
-
strokeStr = "none";
|
|
3122
|
-
}
|
|
3123
|
-
const data = {
|
|
3124
|
-
viewBox: `0 0 ${formatNumber2(width, 3)} ${formatNumber2(height, 3)}`,
|
|
3125
|
-
width,
|
|
3126
|
-
height,
|
|
3127
|
-
d: svg.d,
|
|
3128
|
-
fillRule: svg.fillRule,
|
|
3129
|
-
fill: fillStr,
|
|
3130
|
-
stroke: strokeStr,
|
|
3131
|
-
strokeWidth: shape.strokeWidth ?? 1
|
|
3132
|
-
};
|
|
3133
|
-
if (svg.transform) data.transform = svg.transform;
|
|
3134
|
-
if (shape.opacity !== void 0) data.opacity = shape.opacity;
|
|
3135
|
-
if (shape.strokeLinecap) data.strokeLinecap = shape.strokeLinecap;
|
|
3136
|
-
if (shape.strokeLinejoin) data.strokeLinejoin = shape.strokeLinejoin;
|
|
3137
|
-
if (fillGradientDef) data.fillGradient = fillGradientDef;
|
|
3138
|
-
if (strokeGradientDef) data.strokeGradient = strokeGradientDef;
|
|
3139
|
-
return data;
|
|
3140
|
-
}
|
|
3141
|
-
function clampRatio(value) {
|
|
3142
|
-
if (value === void 0 || !Number.isFinite(value)) return 0.5;
|
|
3143
|
-
return Math.min(1, Math.max(0.01, value));
|
|
3144
|
-
}
|
|
3145
|
-
function validViewBox(vb) {
|
|
3146
|
-
if (!vb || vb.length !== 4 || !vb.every(Number.isFinite) || vb[2] <= 0 || vb[3] <= 0) return null;
|
|
3147
|
-
return vb;
|
|
3148
|
-
}
|
|
3149
|
-
function shapeToPolygon(shape, size, options = {}) {
|
|
3150
|
-
const { width: w, height: h } = size;
|
|
3151
|
-
if (!(w > 0) || !(h > 0)) return { rings: [], fillRule: "nonzero" };
|
|
3152
|
-
let rings;
|
|
3153
|
-
let fillRule = "nonzero";
|
|
3154
|
-
if (shape.kind === "path") {
|
|
3155
|
-
fillRule = shape.fillRule ?? "nonzero";
|
|
3156
|
-
const vb = validViewBox(shape.viewBox);
|
|
3157
|
-
if (vb) {
|
|
3158
|
-
const sx = w / vb[2];
|
|
3159
|
-
const sy = h / vb[3];
|
|
3160
|
-
const tolerance = (options.tolerance ?? 0.25) / Math.max(sx, sy);
|
|
3161
|
-
rings = flattenSvgPath(shape.d, { ...options, tolerance }).map(
|
|
3162
|
-
(ring) => ring.map(([x, y]) => [(x - vb[0]) * sx, (y - vb[1]) * sy])
|
|
3163
|
-
);
|
|
3164
|
-
} else {
|
|
3165
|
-
rings = flattenSvgPath(shape.d, options);
|
|
3166
|
-
}
|
|
3167
|
-
} else {
|
|
3168
|
-
const { d } = getShapeSvg({ ...shape, rotation: 0 }, size);
|
|
3169
|
-
rings = flattenSvgPath(d, options);
|
|
3170
|
-
}
|
|
3171
|
-
const rotate = rotationTransform(shape, size);
|
|
3172
|
-
if (rotate) rings = rings.map((ring) => ring.map(rotate));
|
|
3173
|
-
return { rings, fillRule };
|
|
3174
|
-
}
|
|
3175
|
-
function formatNumber2(value, precision = 3) {
|
|
3176
|
-
const factor = 10 ** precision;
|
|
3177
|
-
const rounded = Math.round(value * factor) / factor;
|
|
3178
|
-
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
3179
|
-
}
|
|
3180
|
-
function ringsToPath(rings, f) {
|
|
3181
|
-
let d = "";
|
|
3182
|
-
for (const ring of rings) {
|
|
3183
|
-
if (ring.length < 2) continue;
|
|
3184
|
-
d += `M${f(ring[0][0])} ${f(ring[0][1])}`;
|
|
3185
|
-
for (let i = 1; i < ring.length; i++) d += `L${f(ring[i][0])} ${f(ring[i][1])}`;
|
|
3186
|
-
d += "Z";
|
|
3187
|
-
}
|
|
3188
|
-
return d;
|
|
3189
|
-
}
|
|
3190
|
-
function segmentDistance(p, a, b) {
|
|
3191
|
-
const dx = b[0] - a[0];
|
|
3192
|
-
const dy = b[1] - a[1];
|
|
3193
|
-
const len2 = dx * dx + dy * dy;
|
|
3194
|
-
if (len2 === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);
|
|
3195
|
-
const t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));
|
|
3196
|
-
return Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));
|
|
3197
|
-
}
|
|
3198
|
-
function rdp(points, tolerance) {
|
|
3199
|
-
if (points.length <= 2) return points;
|
|
3200
|
-
const keep = new Uint8Array(points.length);
|
|
3201
|
-
keep[0] = 1;
|
|
3202
|
-
keep[points.length - 1] = 1;
|
|
3203
|
-
const stack = [[0, points.length - 1]];
|
|
3204
|
-
while (stack.length) {
|
|
3205
|
-
const [s, e] = stack.pop();
|
|
3206
|
-
let maxDist = -1;
|
|
3207
|
-
let index = -1;
|
|
3208
|
-
for (let i = s + 1; i < e; i++) {
|
|
3209
|
-
const dist3 = segmentDistance(points[i], points[s], points[e]);
|
|
3210
|
-
if (dist3 > maxDist) {
|
|
3211
|
-
maxDist = dist3;
|
|
3212
|
-
index = i;
|
|
3213
|
-
}
|
|
3214
|
-
}
|
|
3215
|
-
if (index !== -1 && maxDist > tolerance) {
|
|
3216
|
-
keep[index] = 1;
|
|
3217
|
-
stack.push([s, index], [index, e]);
|
|
3218
|
-
}
|
|
3219
|
-
}
|
|
3220
|
-
return points.filter((_, i) => keep[i]);
|
|
3221
|
-
}
|
|
3222
|
-
function simplifyRing(ring, tolerance) {
|
|
3223
|
-
if (ring.length <= 3 || !(tolerance > 0)) return [...ring];
|
|
3224
|
-
let far = 0;
|
|
3225
|
-
let farDist = -1;
|
|
3226
|
-
for (let i = 1; i < ring.length; i++) {
|
|
3227
|
-
const dist3 = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);
|
|
3228
|
-
if (dist3 > farDist) {
|
|
3229
|
-
farDist = dist3;
|
|
3230
|
-
far = i;
|
|
3231
|
-
}
|
|
3232
|
-
}
|
|
3233
|
-
const first = rdp(ring.slice(0, far + 1), tolerance);
|
|
3234
|
-
const second = rdp([...ring.slice(far), ring[0]], tolerance);
|
|
3235
|
-
const out = [...first.slice(0, -1), ...second.slice(0, -1)];
|
|
3236
|
-
return out.length >= 3 ? out : [];
|
|
3237
|
-
}
|
|
3238
|
-
function polygonToSvgPath(input, options = {}) {
|
|
3239
|
-
const precision = options.precision ?? 3;
|
|
3240
|
-
const f = (n) => formatNumber2(n, precision);
|
|
3241
|
-
const dx = options.offset?.x ?? 0;
|
|
3242
|
-
const dy = options.offset?.y ?? 0;
|
|
3243
|
-
const rings = [];
|
|
3244
|
-
const fit = options.curves === "fit";
|
|
3245
|
-
let fitted = "";
|
|
3246
|
-
for (const item of input) {
|
|
3247
|
-
const list = item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0]) ? item : [item];
|
|
3248
|
-
for (const ring of list) {
|
|
3249
|
-
let r = ring;
|
|
3250
|
-
if (options.simplify && options.simplify > 0) r = simplifyRing(r, options.simplify);
|
|
3251
|
-
if (r.length < 3) continue;
|
|
3252
|
-
if (fit) {
|
|
3253
|
-
fitted += fitPathData(r, {
|
|
3254
|
-
tolerance: options.curveTolerance ?? 0.25,
|
|
3255
|
-
cornerAngleDeg: options.cornerAngleDeg,
|
|
3256
|
-
precision,
|
|
3257
|
-
offset: { x: dx, y: dy }
|
|
3258
|
-
});
|
|
3259
|
-
continue;
|
|
3260
|
-
}
|
|
3261
|
-
rings.push(dx || dy ? r.map(([x, y]) => [x + dx, y + dy]) : r);
|
|
3262
|
-
}
|
|
3263
|
-
}
|
|
3264
|
-
return fit ? fitted : ringsToPath(rings, f);
|
|
3265
|
-
}
|
|
3266
|
-
|
|
3267
|
-
// src/canvas/pathops/bezier.ts
|
|
3268
|
-
var sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
3269
|
-
var add2 = (a, b) => [a[0] + b[0], a[1] + b[1]];
|
|
3270
|
-
var scale = (a, s) => [a[0] * s, a[1] * s];
|
|
3271
|
-
var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
3272
|
-
var cross = (a, b) => a[0] * b[1] - a[1] * b[0];
|
|
3273
|
-
var dist2 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
3274
|
-
var lerp = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
3275
|
-
var samePoint = (a, b) => a[0] === b[0] && a[1] === b[1];
|
|
3276
|
-
function unit2(v) {
|
|
3277
|
-
const l = Math.hypot(v[0], v[1]);
|
|
3278
|
-
return l > 0 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
|
|
3279
|
-
}
|
|
3280
|
-
function segmentPoint(seg, t) {
|
|
3281
|
-
const n = seg.length;
|
|
3282
|
-
if (n === 2) return lerp(seg[0], seg[1], t);
|
|
3283
|
-
const mt = 1 - t;
|
|
3284
|
-
if (n === 3) {
|
|
3285
|
-
const a2 = mt * mt;
|
|
3286
|
-
const b2 = 2 * mt * t;
|
|
3287
|
-
const c2 = t * t;
|
|
3288
|
-
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]];
|
|
3289
|
-
}
|
|
3290
|
-
const a = mt * mt * mt;
|
|
3291
|
-
const b = 3 * mt * mt * t;
|
|
3292
|
-
const c = 3 * mt * t * t;
|
|
3293
|
-
const d = t * t * t;
|
|
3294
|
-
return [
|
|
3295
|
-
a * seg[0][0] + b * seg[1][0] + c * seg[2][0] + d * seg[3][0],
|
|
3296
|
-
a * seg[0][1] + b * seg[1][1] + c * seg[2][1] + d * seg[3][1]
|
|
3297
|
-
];
|
|
3298
|
-
}
|
|
3299
|
-
function segmentDerivative(seg, t) {
|
|
3300
|
-
const n = seg.length;
|
|
3301
|
-
if (n === 2) return sub2(seg[1], seg[0]);
|
|
3302
|
-
if (n === 3) {
|
|
3303
|
-
const a2 = sub2(seg[1], seg[0]);
|
|
3304
|
-
const b2 = sub2(seg[2], seg[1]);
|
|
3305
|
-
return [2 * ((1 - t) * a2[0] + t * b2[0]), 2 * ((1 - t) * a2[1] + t * b2[1])];
|
|
3306
|
-
}
|
|
3307
|
-
const a = sub2(seg[1], seg[0]);
|
|
3308
|
-
const b = sub2(seg[2], seg[1]);
|
|
3309
|
-
const c = sub2(seg[3], seg[2]);
|
|
3310
|
-
const mt = 1 - t;
|
|
3311
|
-
return [
|
|
3312
|
-
3 * (mt * mt * a[0] + 2 * mt * t * b[0] + t * t * c[0]),
|
|
3313
|
-
3 * (mt * mt * a[1] + 2 * mt * t * b[1] + t * t * c[1])
|
|
3314
|
-
];
|
|
3315
|
-
}
|
|
3316
|
-
function segmentSecondDerivative(seg, t) {
|
|
3317
|
-
const n = seg.length;
|
|
3318
|
-
if (n === 2) return [0, 0];
|
|
3319
|
-
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])];
|
|
3320
|
-
const a = [seg[2][0] - 2 * seg[1][0] + seg[0][0], seg[2][1] - 2 * seg[1][1] + seg[0][1]];
|
|
3321
|
-
const b = [seg[3][0] - 2 * seg[2][0] + seg[1][0], seg[3][1] - 2 * seg[2][1] + seg[1][1]];
|
|
3322
|
-
return [6 * ((1 - t) * a[0] + t * b[0]), 6 * ((1 - t) * a[1] + t * b[1])];
|
|
3323
|
-
}
|
|
3324
|
-
function segmentTangent(seg, t) {
|
|
3325
|
-
const d = unit2(segmentDerivative(seg, t));
|
|
3326
|
-
if (d) return d;
|
|
3327
|
-
const h = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
|
|
3328
|
-
const e = unit2(segmentDerivative(seg, h));
|
|
3329
|
-
if (e) return e;
|
|
3330
|
-
return unit2(sub2(seg[seg.length - 1], seg[0])) ?? [1, 0];
|
|
3331
|
-
}
|
|
3332
|
-
function segmentCurvature(seg, t) {
|
|
3333
|
-
if (seg.length === 2) return 0;
|
|
3334
|
-
let d = segmentDerivative(seg, t);
|
|
3335
|
-
let tt = t;
|
|
3336
|
-
if (Math.hypot(d[0], d[1]) < 1e-12) {
|
|
3337
|
-
tt = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
|
|
3338
|
-
d = segmentDerivative(seg, tt);
|
|
3339
|
-
}
|
|
3340
|
-
const dd = segmentSecondDerivative(seg, tt);
|
|
3341
|
-
const len = Math.hypot(d[0], d[1]);
|
|
3342
|
-
if (!(len > 0)) return 0;
|
|
3343
|
-
return cross(d, dd) / (len * len * len);
|
|
3344
|
-
}
|
|
3345
|
-
function splitSegment(seg, t) {
|
|
3346
|
-
const n = seg.length;
|
|
3347
|
-
if (n === 2) {
|
|
3348
|
-
const m2 = lerp(seg[0], seg[1], t);
|
|
3349
|
-
return [
|
|
3350
|
-
[seg[0], m2],
|
|
3351
|
-
[m2, seg[1]]
|
|
3352
|
-
];
|
|
3353
|
-
}
|
|
3354
|
-
if (n === 3) {
|
|
3355
|
-
const a2 = lerp(seg[0], seg[1], t);
|
|
3356
|
-
const b2 = lerp(seg[1], seg[2], t);
|
|
3357
|
-
const m2 = lerp(a2, b2, t);
|
|
3358
|
-
return [
|
|
3359
|
-
[seg[0], a2, m2],
|
|
3360
|
-
[m2, b2, seg[2]]
|
|
3361
|
-
];
|
|
3362
|
-
}
|
|
3363
|
-
const a = lerp(seg[0], seg[1], t);
|
|
3364
|
-
const b = lerp(seg[1], seg[2], t);
|
|
3365
|
-
const c = lerp(seg[2], seg[3], t);
|
|
3366
|
-
const ab = lerp(a, b, t);
|
|
3367
|
-
const bc = lerp(b, c, t);
|
|
3368
|
-
const m = lerp(ab, bc, t);
|
|
3369
|
-
return [
|
|
3370
|
-
[seg[0], a, ab, m],
|
|
3371
|
-
[m, bc, c, seg[3]]
|
|
3372
|
-
];
|
|
3373
|
-
}
|
|
3374
|
-
function subSegment(seg, t0, t1) {
|
|
3375
|
-
if (t1 < t0) return subSegment(seg, t1, t0).reverse();
|
|
3376
|
-
if (t0 <= 0 && t1 >= 1) return [...seg];
|
|
3377
|
-
let s = [...seg];
|
|
3378
|
-
if (t1 < 1) s = splitSegment(s, t1)[0];
|
|
3379
|
-
if (t0 > 0) s = splitSegment(s, t1 > 0 ? t0 / t1 : 0)[1];
|
|
3380
|
-
return s;
|
|
3381
|
-
}
|
|
3382
|
-
function isStraightSegment(seg, tol) {
|
|
3383
|
-
if (seg.length === 2) return true;
|
|
3384
|
-
const a = seg[0];
|
|
3385
|
-
const b = seg[seg.length - 1];
|
|
3386
|
-
const chord = sub2(b, a);
|
|
3387
|
-
const len = Math.hypot(chord[0], chord[1]);
|
|
3388
|
-
for (let i = 1; i < seg.length - 1; i++) {
|
|
3389
|
-
const v = sub2(seg[i], a);
|
|
3390
|
-
if (len === 0) {
|
|
3391
|
-
if (Math.hypot(v[0], v[1]) > tol) return false;
|
|
3392
|
-
continue;
|
|
3393
|
-
}
|
|
3394
|
-
if (Math.abs(cross(chord, v)) / len > tol) return false;
|
|
3395
|
-
const along = dot2(chord, v) / len;
|
|
3396
|
-
if (along < -tol || along > len + tol) return false;
|
|
3397
|
-
}
|
|
3398
|
-
return true;
|
|
3399
|
-
}
|
|
3400
|
-
function quadraticRoots(a, b, c, out) {
|
|
3401
|
-
const push = (t) => {
|
|
3402
|
-
if (t > 1e-12 && t < 1 - 1e-12) out.push(t);
|
|
3403
|
-
};
|
|
3404
|
-
const scaleAbs = Math.max(Math.abs(a), Math.abs(b), Math.abs(c));
|
|
3405
|
-
if (scaleAbs === 0) return;
|
|
3406
|
-
if (Math.abs(a) <= 1e-12 * scaleAbs) {
|
|
3407
|
-
if (b !== 0) push(-c / b);
|
|
3408
|
-
return;
|
|
3409
|
-
}
|
|
3410
|
-
const disc = b * b - 4 * a * c;
|
|
3411
|
-
if (disc < 0) return;
|
|
3412
|
-
const sq = Math.sqrt(disc);
|
|
3413
|
-
const q = -0.5 * (b + (b >= 0 ? sq : -sq));
|
|
3414
|
-
push(q / a);
|
|
3415
|
-
if (q !== 0) push(c / q);
|
|
3416
|
-
}
|
|
3417
|
-
function segmentExtrema(seg, axis) {
|
|
3418
|
-
const out = [];
|
|
3419
|
-
if (seg.length === 3) {
|
|
3420
|
-
const d = seg[0][axis] - 2 * seg[1][axis] + seg[2][axis];
|
|
3421
|
-
if (d !== 0) {
|
|
3422
|
-
const t = (seg[0][axis] - seg[1][axis]) / d;
|
|
3423
|
-
if (t > 1e-12 && t < 1 - 1e-12) out.push(t);
|
|
3424
|
-
}
|
|
3425
|
-
} else if (seg.length === 4) {
|
|
3426
|
-
const p0 = seg[0][axis];
|
|
3427
|
-
const p1 = seg[1][axis];
|
|
3428
|
-
const p2 = seg[2][axis];
|
|
3429
|
-
const p3 = seg[3][axis];
|
|
3430
|
-
const a = -p0 + 3 * p1 - 3 * p2 + p3;
|
|
3431
|
-
const b = 2 * (p0 - 2 * p1 + p2);
|
|
3432
|
-
const c = p1 - p0;
|
|
3433
|
-
quadraticRoots(a, b, c, out);
|
|
3434
|
-
}
|
|
3435
|
-
return out.sort((x, y) => x - y);
|
|
3436
|
-
}
|
|
3437
|
-
function segmentBounds(seg) {
|
|
3438
|
-
const a = seg[0];
|
|
3439
|
-
const b = seg[seg.length - 1];
|
|
3440
|
-
let minX = Math.min(a[0], b[0]);
|
|
3441
|
-
let maxX = Math.max(a[0], b[0]);
|
|
3442
|
-
let minY = Math.min(a[1], b[1]);
|
|
3443
|
-
let maxY = Math.max(a[1], b[1]);
|
|
3444
|
-
for (const axis of [0, 1]) {
|
|
3445
|
-
for (const t of segmentExtrema(seg, axis)) {
|
|
3446
|
-
const p = segmentPoint(seg, t);
|
|
3447
|
-
if (axis === 0) {
|
|
3448
|
-
minX = Math.min(minX, p[0]);
|
|
3449
|
-
maxX = Math.max(maxX, p[0]);
|
|
3450
|
-
} else {
|
|
3451
|
-
minY = Math.min(minY, p[1]);
|
|
3452
|
-
maxY = Math.max(maxY, p[1]);
|
|
3453
|
-
}
|
|
3454
|
-
}
|
|
3455
|
-
}
|
|
3456
|
-
return { minX, minY, maxX, maxY };
|
|
3457
|
-
}
|
|
3458
|
-
function monotoneParams(seg) {
|
|
3459
|
-
if (seg.length === 2) return [];
|
|
3460
|
-
const ts = [...segmentExtrema(seg, 0), ...segmentExtrema(seg, 1)].sort((a, b) => a - b);
|
|
3461
|
-
const out = [];
|
|
3462
|
-
for (const t of ts) if (out.length === 0 || t - out[out.length - 1] > 1e-9) out.push(t);
|
|
3463
|
-
return out;
|
|
3464
|
-
}
|
|
3465
|
-
function segmentAreaTerm(seg) {
|
|
3466
|
-
if (seg.length === 2) return (seg[0][0] * seg[1][1] - seg[1][0] * seg[0][1]) / 2;
|
|
3467
|
-
const nodes = [-Math.sqrt(3 / 5), 0, Math.sqrt(3 / 5)];
|
|
3468
|
-
const weights = [5 / 9, 8 / 9, 5 / 9];
|
|
3469
|
-
let sum = 0;
|
|
3470
|
-
for (let i = 0; i < 3; i++) {
|
|
3471
|
-
const t = (nodes[i] + 1) / 2;
|
|
3472
|
-
const p = segmentPoint(seg, t);
|
|
3473
|
-
const d = segmentDerivative(seg, t);
|
|
3474
|
-
sum += weights[i] * (p[0] * d[1] - p[1] * d[0]);
|
|
3475
|
-
}
|
|
3476
|
-
return sum / 4;
|
|
3477
|
-
}
|
|
3478
|
-
var GL_X = [
|
|
3479
|
-
-0.9602898564975363,
|
|
3480
|
-
-0.7966664774136267,
|
|
3481
|
-
-0.525532409916329,
|
|
3482
|
-
-0.1834346424956498,
|
|
3483
|
-
0.1834346424956498,
|
|
3484
|
-
0.525532409916329,
|
|
3485
|
-
0.7966664774136267,
|
|
3486
|
-
0.9602898564975363
|
|
3487
|
-
];
|
|
3488
|
-
var GL_W = [
|
|
3489
|
-
0.1012285362903763,
|
|
3490
|
-
0.2223810344533745,
|
|
3491
|
-
0.3137066458778873,
|
|
3492
|
-
0.362683783378362,
|
|
3493
|
-
0.362683783378362,
|
|
3494
|
-
0.3137066458778873,
|
|
3495
|
-
0.2223810344533745,
|
|
3496
|
-
0.1012285362903763
|
|
3497
|
-
];
|
|
3498
|
-
function glLength(seg, t0, t1) {
|
|
3499
|
-
const half = (t1 - t0) / 2;
|
|
3500
|
-
const mid = (t0 + t1) / 2;
|
|
3501
|
-
let sum = 0;
|
|
3502
|
-
for (let i = 0; i < GL_X.length; i++) {
|
|
3503
|
-
const d = segmentDerivative(seg, mid + half * GL_X[i]);
|
|
3504
|
-
sum += GL_W[i] * Math.hypot(d[0], d[1]);
|
|
3505
|
-
}
|
|
3506
|
-
return sum * half;
|
|
3507
|
-
}
|
|
3508
|
-
function adaptiveLength(seg, t0, t1, whole, tol, depth) {
|
|
3509
|
-
const m = (t0 + t1) / 2;
|
|
3510
|
-
const left = glLength(seg, t0, m);
|
|
3511
|
-
const right = glLength(seg, m, t1);
|
|
3512
|
-
if (depth >= 18 || Math.abs(left + right - whole) <= tol) return left + right;
|
|
3513
|
-
return adaptiveLength(seg, t0, m, left, tol / 2, depth + 1) + adaptiveLength(seg, m, t1, right, tol / 2, depth + 1);
|
|
3514
|
-
}
|
|
3515
|
-
function segmentLength(seg, t0 = 0, t1 = 1, tolerance = 1e-9) {
|
|
3516
|
-
if (t1 < t0) return segmentLength(seg, t1, t0, tolerance);
|
|
3517
|
-
if (seg.length === 2) return dist2(seg[0], seg[1]) * (t1 - t0);
|
|
3518
|
-
const whole = glLength(seg, t0, t1);
|
|
3519
|
-
return adaptiveLength(seg, t0, t1, whole, Math.max(tolerance, whole * 1e-12), 0);
|
|
3520
|
-
}
|
|
3521
|
-
function segmentParamAtLength(seg, length, total = segmentLength(seg)) {
|
|
3522
|
-
if (!(total > 0) || length <= 0) return 0;
|
|
3523
|
-
if (length >= total) return 1;
|
|
3524
|
-
if (seg.length === 2) return length / total;
|
|
3525
|
-
let lo = 0;
|
|
3526
|
-
let hi = 1;
|
|
3527
|
-
let t = length / total;
|
|
3528
|
-
for (let i = 0; i < 40; i++) {
|
|
3529
|
-
const err = segmentLength(seg, 0, t) - length;
|
|
3530
|
-
if (Math.abs(err) <= 1e-10 * Math.max(1, total)) break;
|
|
3531
|
-
if (err > 0) hi = t;
|
|
3532
|
-
else lo = t;
|
|
3533
|
-
const d = segmentDerivative(seg, t);
|
|
3534
|
-
const speed = Math.hypot(d[0], d[1]);
|
|
3535
|
-
let next = speed > 0 ? t - err / speed : (lo + hi) / 2;
|
|
3536
|
-
if (!(next > lo && next < hi)) next = (lo + hi) / 2;
|
|
3537
|
-
t = next;
|
|
3538
|
-
}
|
|
3539
|
-
return t;
|
|
3540
|
-
}
|
|
3541
|
-
function segmentNearest(seg, p) {
|
|
3542
|
-
if (seg.length === 2) {
|
|
3543
|
-
const d = sub2(seg[1], seg[0]);
|
|
3544
|
-
const len2 = dot2(d, d);
|
|
3545
|
-
const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, dot2(sub2(p, seg[0]), d) / len2));
|
|
3546
|
-
return { t, distance: dist2(p, segmentPoint(seg, t)) };
|
|
3547
|
-
}
|
|
3548
|
-
const samples = 16;
|
|
3549
|
-
let best = { t: 0, distance: dist2(p, seg[0]) };
|
|
3550
|
-
const candidates = [];
|
|
3551
|
-
let prev = Infinity;
|
|
3552
|
-
let prevT = 0;
|
|
3553
|
-
let descending = false;
|
|
3554
|
-
for (let i = 0; i <= samples; i++) {
|
|
3555
|
-
const t = i / samples;
|
|
3556
|
-
const d = dist2(p, segmentPoint(seg, t));
|
|
3557
|
-
if (d < best.distance) best = { t, distance: d };
|
|
3558
|
-
if (i > 0 && d > prev && descending) candidates.push(prevT);
|
|
3559
|
-
descending = d < prev;
|
|
3560
|
-
prev = d;
|
|
3561
|
-
prevT = t;
|
|
3562
|
-
}
|
|
3563
|
-
candidates.push(best.t, 1);
|
|
3564
|
-
for (const start of candidates) {
|
|
3565
|
-
let t = start;
|
|
3566
|
-
for (let k = 0; k < 12; k++) {
|
|
3567
|
-
const q = segmentPoint(seg, t);
|
|
3568
|
-
const d1 = segmentDerivative(seg, t);
|
|
3569
|
-
const d2 = segmentSecondDerivative(seg, t);
|
|
3570
|
-
const diff = sub2(q, p);
|
|
3571
|
-
const f = dot2(diff, d1);
|
|
3572
|
-
const fp = dot2(d1, d1) + dot2(diff, d2);
|
|
3573
|
-
if (!(Math.abs(fp) > 1e-300)) break;
|
|
3574
|
-
const next = Math.max(0, Math.min(1, t - f / fp));
|
|
3575
|
-
if (Math.abs(next - t) < 1e-14) {
|
|
3576
|
-
t = next;
|
|
3577
|
-
break;
|
|
3578
|
-
}
|
|
3579
|
-
t = next;
|
|
3580
|
-
}
|
|
3581
|
-
const d = dist2(p, segmentPoint(seg, t));
|
|
3582
|
-
if (d < best.distance) best = { t, distance: d };
|
|
3583
|
-
}
|
|
3584
|
-
return best;
|
|
3585
|
-
}
|
|
3586
|
-
function arcToCubics(from, cmd) {
|
|
3587
|
-
const [x0, y0] = from;
|
|
3588
|
-
const { x, y } = cmd;
|
|
3589
|
-
if (x0 === x && y0 === y) return [];
|
|
3590
|
-
let rx = Math.abs(cmd.rx);
|
|
3591
|
-
let ry = Math.abs(cmd.ry);
|
|
3592
|
-
if (rx === 0 || ry === 0) return [[from, [x, y]]];
|
|
3593
|
-
const phi = cmd.rotation * Math.PI / 180;
|
|
3594
|
-
const cos = Math.cos(phi);
|
|
3595
|
-
const sin = Math.sin(phi);
|
|
3596
|
-
const dx = (x0 - x) / 2;
|
|
3597
|
-
const dy = (y0 - y) / 2;
|
|
3598
|
-
const x1p = cos * dx + sin * dy;
|
|
3599
|
-
const y1p = -sin * dx + cos * dy;
|
|
3600
|
-
const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
|
|
3601
|
-
if (lambda > 1) {
|
|
3602
|
-
const s = Math.sqrt(lambda);
|
|
3603
|
-
rx *= s;
|
|
3604
|
-
ry *= s;
|
|
3605
|
-
}
|
|
3606
|
-
const num2 = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
|
|
3607
|
-
const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
|
|
3608
|
-
let coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num2 / den));
|
|
3609
|
-
if (cmd.largeArc === cmd.sweep) coef = -coef;
|
|
3610
|
-
const cxp = coef * rx * y1p / ry;
|
|
3611
|
-
const cyp = -coef * ry * x1p / rx;
|
|
3612
|
-
const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
|
|
3613
|
-
const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
|
|
3614
|
-
const angle = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
3615
|
-
const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
|
|
3616
|
-
let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
|
|
3617
|
-
if (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;
|
|
3618
|
-
else if (cmd.sweep && delta < 0) delta += 2 * Math.PI;
|
|
3619
|
-
const n = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9));
|
|
3620
|
-
const step = delta / n;
|
|
3621
|
-
const k = 4 / 3 * Math.tan(step / 4);
|
|
3622
|
-
const map = (ux, uy) => {
|
|
3623
|
-
const ex = rx * ux;
|
|
3624
|
-
const ey = ry * uy;
|
|
3625
|
-
return [cos * ex - sin * ey + cx, sin * ex + cos * ey + cy];
|
|
3626
|
-
};
|
|
3627
|
-
const out = [];
|
|
3628
|
-
let prev = from;
|
|
3629
|
-
for (let i = 0; i < n; i++) {
|
|
3630
|
-
const a0 = theta1 + step * i;
|
|
3631
|
-
const a1 = a0 + step;
|
|
3632
|
-
const c0 = Math.cos(a0);
|
|
3633
|
-
const s0 = Math.sin(a0);
|
|
3634
|
-
const c1 = Math.cos(a1);
|
|
3635
|
-
const s1 = Math.sin(a1);
|
|
3636
|
-
const end = i === n - 1 ? [x, y] : map(c1, s1);
|
|
3637
|
-
out.push([prev, map(c0 - k * s0, s0 + k * c0), map(c1 + k * s1, s1 - k * c1), end]);
|
|
3638
|
-
prev = end;
|
|
3639
|
-
}
|
|
3640
|
-
return out;
|
|
3641
|
-
}
|
|
3642
|
-
function isBezierCommandList(input) {
|
|
3643
|
-
return Array.isArray(input) && !input.some((c) => c && c.type === "A");
|
|
3644
|
-
}
|
|
3645
|
-
function toBezierPath(input) {
|
|
3646
|
-
const commands = typeof input === "string" ? parseSvgPath(input) : isBezierCommandList(input) ? input : input;
|
|
3647
|
-
const out = [];
|
|
3648
|
-
let current = [0, 0];
|
|
3649
|
-
let start = [0, 0];
|
|
3650
|
-
let open = false;
|
|
3651
|
-
const ensureMove = () => {
|
|
3652
|
-
if (!open) {
|
|
3653
|
-
out.push({ type: "M", x: start[0], y: start[1] });
|
|
3654
|
-
current = start;
|
|
3655
|
-
open = true;
|
|
3656
|
-
}
|
|
3657
|
-
};
|
|
3658
|
-
for (const cmd of commands) {
|
|
3659
|
-
switch (cmd.type) {
|
|
3660
|
-
case "M":
|
|
3661
|
-
start = [cmd.x, cmd.y];
|
|
3662
|
-
current = start;
|
|
3663
|
-
out.push({ type: "M", x: cmd.x, y: cmd.y });
|
|
3664
|
-
open = true;
|
|
3665
|
-
break;
|
|
3666
|
-
case "L":
|
|
3667
|
-
ensureMove();
|
|
3668
|
-
out.push({ type: "L", x: cmd.x, y: cmd.y });
|
|
3669
|
-
current = [cmd.x, cmd.y];
|
|
3670
|
-
break;
|
|
3671
|
-
case "Q":
|
|
3672
|
-
ensureMove();
|
|
3673
|
-
out.push({ type: "Q", x1: cmd.x1, y1: cmd.y1, x: cmd.x, y: cmd.y });
|
|
3674
|
-
current = [cmd.x, cmd.y];
|
|
3675
|
-
break;
|
|
3676
|
-
case "C":
|
|
3677
|
-
ensureMove();
|
|
3678
|
-
out.push({ type: "C", x1: cmd.x1, y1: cmd.y1, x2: cmd.x2, y2: cmd.y2, x: cmd.x, y: cmd.y });
|
|
3679
|
-
current = [cmd.x, cmd.y];
|
|
3680
|
-
break;
|
|
3681
|
-
case "A": {
|
|
3682
|
-
ensureMove();
|
|
3683
|
-
for (const c of arcToCubics(current, cmd)) {
|
|
3684
|
-
if (c.length === 2) out.push({ type: "L", x: c[1][0], y: c[1][1] });
|
|
3685
|
-
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] });
|
|
3686
|
-
}
|
|
3687
|
-
current = [cmd.x, cmd.y];
|
|
3688
|
-
break;
|
|
3689
|
-
}
|
|
3690
|
-
case "Z":
|
|
3691
|
-
if (open) out.push({ type: "Z" });
|
|
3692
|
-
open = false;
|
|
3693
|
-
current = start;
|
|
3694
|
-
break;
|
|
3695
|
-
}
|
|
3696
|
-
}
|
|
3697
|
-
return out;
|
|
3698
|
-
}
|
|
3699
|
-
function pathToContours(input) {
|
|
3700
|
-
const path = toBezierPath(input);
|
|
3701
|
-
const contours = [];
|
|
3702
|
-
let current = null;
|
|
3703
|
-
let pen = [0, 0];
|
|
3704
|
-
let start = [0, 0];
|
|
3705
|
-
const push = (seg) => {
|
|
3706
|
-
if (!current) {
|
|
3707
|
-
current = { segments: [], closed: false };
|
|
3708
|
-
contours.push(current);
|
|
3709
|
-
}
|
|
3710
|
-
if (seg.every((p) => samePoint(p, seg[0]))) return;
|
|
3711
|
-
current.segments.push(seg);
|
|
3712
|
-
};
|
|
3713
|
-
for (const cmd of path) {
|
|
3714
|
-
switch (cmd.type) {
|
|
3715
|
-
case "M":
|
|
3716
|
-
start = [cmd.x, cmd.y];
|
|
3717
|
-
pen = start;
|
|
3718
|
-
current = { segments: [], closed: false };
|
|
3719
|
-
contours.push(current);
|
|
3720
|
-
break;
|
|
3721
|
-
case "L":
|
|
3722
|
-
push([pen, [cmd.x, cmd.y]]);
|
|
3723
|
-
pen = [cmd.x, cmd.y];
|
|
3724
|
-
break;
|
|
3725
|
-
case "Q":
|
|
3726
|
-
push([pen, [cmd.x1, cmd.y1], [cmd.x, cmd.y]]);
|
|
3727
|
-
pen = [cmd.x, cmd.y];
|
|
3728
|
-
break;
|
|
3729
|
-
case "C":
|
|
3730
|
-
push([pen, [cmd.x1, cmd.y1], [cmd.x2, cmd.y2], [cmd.x, cmd.y]]);
|
|
3731
|
-
pen = [cmd.x, cmd.y];
|
|
3732
|
-
break;
|
|
3733
|
-
case "Z":
|
|
3734
|
-
if (current) current.closed = true;
|
|
3735
|
-
current = null;
|
|
3736
|
-
pen = start;
|
|
3737
|
-
break;
|
|
3738
|
-
}
|
|
3739
|
-
}
|
|
3740
|
-
return contours;
|
|
3741
|
-
}
|
|
3742
|
-
function contoursToPath(contours) {
|
|
3743
|
-
const out = [];
|
|
3744
|
-
for (const contour of contours) {
|
|
3745
|
-
const segs = contour.segments;
|
|
3746
|
-
if (segs.length === 0) continue;
|
|
3747
|
-
out.push({ type: "M", x: segs[0][0][0], y: segs[0][0][1] });
|
|
3748
|
-
let count = segs.length;
|
|
3749
|
-
if (contour.closed && count > 1) {
|
|
3750
|
-
const last = segs[count - 1];
|
|
3751
|
-
if (last.length === 2 && samePoint(last[1], segs[0][0])) count--;
|
|
3752
|
-
}
|
|
3753
|
-
for (let i = 0; i < count; i++) {
|
|
3754
|
-
const s = segs[i];
|
|
3755
|
-
const end = s[s.length - 1];
|
|
3756
|
-
if (s.length === 2) out.push({ type: "L", x: end[0], y: end[1] });
|
|
3757
|
-
else if (s.length === 3) out.push({ type: "Q", x1: s[1][0], y1: s[1][1], x: end[0], y: end[1] });
|
|
3758
|
-
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] });
|
|
3759
|
-
}
|
|
3760
|
-
if (contour.closed) out.push({ type: "Z" });
|
|
3761
|
-
}
|
|
3762
|
-
return out;
|
|
3763
|
-
}
|
|
3764
|
-
function bezierPathToSvg(input, options = {}) {
|
|
3765
|
-
const path = toBezierPath(input);
|
|
3766
|
-
const precision = options.precision ?? 3;
|
|
3767
|
-
const dx = options.offset?.x ?? 0;
|
|
3768
|
-
const dy = options.offset?.y ?? 0;
|
|
3769
|
-
const fx = (n) => formatNumber2(n + dx, precision);
|
|
3770
|
-
const fy = (n) => formatNumber2(n + dy, precision);
|
|
3771
|
-
let d = "";
|
|
3772
|
-
for (const c of path) {
|
|
3773
|
-
switch (c.type) {
|
|
3774
|
-
case "M":
|
|
3775
|
-
d += `M${fx(c.x)} ${fy(c.y)}`;
|
|
3776
|
-
break;
|
|
3777
|
-
case "L":
|
|
3778
|
-
d += `L${fx(c.x)} ${fy(c.y)}`;
|
|
3779
|
-
break;
|
|
3780
|
-
case "Q":
|
|
3781
|
-
d += `Q${fx(c.x1)} ${fy(c.y1)} ${fx(c.x)} ${fy(c.y)}`;
|
|
3782
|
-
break;
|
|
3783
|
-
case "C":
|
|
3784
|
-
d += `C${fx(c.x1)} ${fy(c.y1)} ${fx(c.x2)} ${fy(c.y2)} ${fx(c.x)} ${fy(c.y)}`;
|
|
3785
|
-
break;
|
|
3786
|
-
case "Z":
|
|
3787
|
-
d += "Z";
|
|
3788
|
-
break;
|
|
3789
|
-
}
|
|
3790
|
-
}
|
|
3791
|
-
return d;
|
|
3792
|
-
}
|
|
3793
|
-
function transformBezierPath(input, m) {
|
|
3794
|
-
const [a, b, c, d, e, f] = m;
|
|
3795
|
-
const X = (x, y) => a * x + c * y + e;
|
|
3796
|
-
const Y = (x, y) => b * x + d * y + f;
|
|
3797
|
-
return toBezierPath(input).map((cmd) => {
|
|
3798
|
-
switch (cmd.type) {
|
|
3799
|
-
case "M":
|
|
3800
|
-
case "L":
|
|
3801
|
-
return { type: cmd.type, x: X(cmd.x, cmd.y), y: Y(cmd.x, cmd.y) };
|
|
3802
|
-
case "Q":
|
|
3803
|
-
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) };
|
|
3804
|
-
case "C":
|
|
3805
|
-
return {
|
|
3806
|
-
type: "C",
|
|
3807
|
-
x1: X(cmd.x1, cmd.y1),
|
|
3808
|
-
y1: Y(cmd.x1, cmd.y1),
|
|
3809
|
-
x2: X(cmd.x2, cmd.y2),
|
|
3810
|
-
y2: Y(cmd.x2, cmd.y2),
|
|
3811
|
-
x: X(cmd.x, cmd.y),
|
|
3812
|
-
y: Y(cmd.x, cmd.y)
|
|
3813
|
-
};
|
|
3814
|
-
case "Z":
|
|
3815
|
-
return cmd;
|
|
3816
|
-
}
|
|
3817
|
-
});
|
|
3818
|
-
}
|
|
3819
|
-
function countPathSegments(input) {
|
|
3820
|
-
const out = { lines: 0, quads: 0, cubics: 0 };
|
|
3821
|
-
for (const contour of pathToContours(input)) {
|
|
3822
|
-
const segs = closeContour(contour);
|
|
3823
|
-
for (const s of segs) {
|
|
3824
|
-
if (s.length === 2) out.lines++;
|
|
3825
|
-
else if (s.length === 3) out.quads++;
|
|
3826
|
-
else out.cubics++;
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3829
|
-
return out;
|
|
3830
|
-
}
|
|
3831
|
-
function closeContour(contour, force = false) {
|
|
3832
|
-
const segs = contour.segments;
|
|
3833
|
-
if (segs.length === 0) return [];
|
|
3834
|
-
if (!contour.closed && !force) return segs;
|
|
3835
|
-
const first = segs[0][0];
|
|
3836
|
-
const lastSeg = segs[segs.length - 1];
|
|
3837
|
-
const last = lastSeg[lastSeg.length - 1];
|
|
3838
|
-
return samePoint(first, last) ? segs : [...segs, [last, first]];
|
|
3839
|
-
}
|
|
3840
|
-
function bezierPathBounds(input) {
|
|
3841
|
-
let minX = Infinity;
|
|
3842
|
-
let minY = Infinity;
|
|
3843
|
-
let maxX = -Infinity;
|
|
3844
|
-
let maxY = -Infinity;
|
|
3845
|
-
const path = toBezierPath(input);
|
|
3846
|
-
let pen = null;
|
|
3847
|
-
for (const contour of pathToContours(path)) {
|
|
3848
|
-
for (const s of contour.segments) {
|
|
3849
|
-
const b = segmentBounds(s);
|
|
3850
|
-
minX = Math.min(minX, b.minX);
|
|
3851
|
-
minY = Math.min(minY, b.minY);
|
|
3852
|
-
maxX = Math.max(maxX, b.maxX);
|
|
3853
|
-
maxY = Math.max(maxY, b.maxY);
|
|
3854
|
-
}
|
|
3855
|
-
}
|
|
3856
|
-
for (const c of path) {
|
|
3857
|
-
if (c.type === "M") pen = [c.x, c.y];
|
|
3858
|
-
if (pen) {
|
|
3859
|
-
minX = Math.min(minX, pen[0]);
|
|
3860
|
-
minY = Math.min(minY, pen[1]);
|
|
3861
|
-
maxX = Math.max(maxX, pen[0]);
|
|
3862
|
-
maxY = Math.max(maxY, pen[1]);
|
|
3863
|
-
}
|
|
3864
|
-
}
|
|
3865
|
-
return minX === Infinity ? null : { minX, minY, maxX, maxY };
|
|
3866
|
-
}
|
|
3867
|
-
function bezierPathSignedArea(input) {
|
|
3868
|
-
let sum = 0;
|
|
3869
|
-
for (const contour of pathToContours(input)) sum += contourSignedArea(closeContour(contour, true));
|
|
3870
|
-
return sum;
|
|
3871
|
-
}
|
|
3872
|
-
function contourSignedArea(segs) {
|
|
3873
|
-
let sum = 0;
|
|
3874
|
-
for (const s of segs) sum += segmentAreaTerm(s);
|
|
3875
|
-
return sum;
|
|
3876
|
-
}
|
|
3877
|
-
function bezierPathLength(input) {
|
|
3878
|
-
let total = 0;
|
|
3879
|
-
for (const contour of pathToContours(input)) for (const s of closeContour(contour)) total += segmentLength(s);
|
|
3880
|
-
return total;
|
|
3881
|
-
}
|
|
3882
|
-
function bezierPathPointAtLength(input, length) {
|
|
3883
|
-
const contours = pathToContours(input);
|
|
3884
|
-
let remaining = Math.max(0, length);
|
|
3885
|
-
let last = null;
|
|
3886
|
-
for (let ci = 0; ci < contours.length; ci++) {
|
|
3887
|
-
const segs = closeContour(contours[ci]);
|
|
3888
|
-
for (let si = 0; si < segs.length; si++) {
|
|
3889
|
-
const s = segs[si];
|
|
3890
|
-
const len = segmentLength(s);
|
|
3891
|
-
if (remaining <= len) {
|
|
3892
|
-
const t = segmentParamAtLength(s, remaining, len);
|
|
3893
|
-
const tangent2 = segmentTangent(s, t);
|
|
3894
|
-
return { point: segmentPoint(s, t), tangent: tangent2, angle: Math.atan2(tangent2[1], tangent2[0]), contour: ci, segment: si, t };
|
|
3895
|
-
}
|
|
3896
|
-
remaining -= len;
|
|
3897
|
-
const tangent = segmentTangent(s, 1);
|
|
3898
|
-
last = { point: s[s.length - 1], tangent, angle: Math.atan2(tangent[1], tangent[0]), contour: ci, segment: si, t: 1 };
|
|
3899
|
-
}
|
|
3900
|
-
}
|
|
3901
|
-
return last;
|
|
3902
|
-
}
|
|
3903
|
-
function trimSegments(segs, from, to, lengths) {
|
|
3904
|
-
const lens = lengths ?? segs.map((s) => segmentLength(s));
|
|
3905
|
-
const out = [];
|
|
3906
|
-
let at = 0;
|
|
3907
|
-
for (let i = 0; i < segs.length; i++) {
|
|
3908
|
-
const len = lens[i];
|
|
3909
|
-
const s0 = at;
|
|
3910
|
-
const s1 = at + len;
|
|
3911
|
-
at = s1;
|
|
3912
|
-
if (s1 <= from || s0 >= to || len <= 0) continue;
|
|
3913
|
-
const t0 = from > s0 ? segmentParamAtLength(segs[i], from - s0, len) : 0;
|
|
3914
|
-
const t1 = to < s1 ? segmentParamAtLength(segs[i], to - s0, len) : 1;
|
|
3915
|
-
if (t1 > t0) out.push(subSegment(segs[i], t0, t1));
|
|
3916
|
-
}
|
|
3917
|
-
return out;
|
|
3918
|
-
}
|
|
3919
|
-
function solveMonotone(seg, axis, value) {
|
|
3920
|
-
const a = seg[0][axis];
|
|
3921
|
-
const b = seg[seg.length - 1][axis];
|
|
3922
|
-
if (seg.length === 2) return b === a ? 0 : (value - a) / (b - a);
|
|
3923
|
-
const increasing = b > a;
|
|
3924
|
-
let lo = 0;
|
|
3925
|
-
let hi = 1;
|
|
3926
|
-
let t = b === a ? 0.5 : Math.max(0, Math.min(1, (value - a) / (b - a)));
|
|
3927
|
-
for (let i = 0; i < 60; i++) {
|
|
3928
|
-
const v = segmentPoint(seg, t)[axis] - value;
|
|
3929
|
-
if (v === 0) return t;
|
|
3930
|
-
if (v > 0 === increasing) hi = t;
|
|
3931
|
-
else lo = t;
|
|
3932
|
-
const d = segmentDerivative(seg, t)[axis];
|
|
3933
|
-
let next = d !== 0 ? t - v / d : (lo + hi) / 2;
|
|
3934
|
-
if (!(next > lo && next < hi)) next = (lo + hi) / 2;
|
|
3935
|
-
if (Math.abs(next - t) < 1e-15) return next;
|
|
3936
|
-
t = next;
|
|
3937
|
-
}
|
|
3938
|
-
return t;
|
|
3939
|
-
}
|
|
3940
|
-
function yMonotonePieces(segs) {
|
|
3941
|
-
const out = [];
|
|
3942
|
-
for (const s of segs) {
|
|
3943
|
-
if (s.length === 2) {
|
|
3944
|
-
out.push(s);
|
|
3945
|
-
continue;
|
|
3946
|
-
}
|
|
3947
|
-
const ts = segmentExtrema(s, 1);
|
|
3948
|
-
let prev = 0;
|
|
3949
|
-
for (const t of [...ts, 1]) {
|
|
3950
|
-
out.push(subSegment(s, prev, t));
|
|
3951
|
-
prev = t;
|
|
3952
|
-
}
|
|
3953
|
-
}
|
|
3954
|
-
return out;
|
|
3955
|
-
}
|
|
3956
|
-
function windingNumber(chains, point) {
|
|
3957
|
-
const [px2, py] = point;
|
|
3958
|
-
let winding = 0;
|
|
3959
|
-
for (const chain of chains) {
|
|
3960
|
-
for (const s of yMonotonePieces(chain)) {
|
|
3961
|
-
const ay = s[0][1];
|
|
3962
|
-
const by = s[s.length - 1][1];
|
|
3963
|
-
let dir = 0;
|
|
3964
|
-
if (ay <= py && by > py) dir = 1;
|
|
3965
|
-
else if (by <= py && ay > py) dir = -1;
|
|
3966
|
-
else continue;
|
|
3967
|
-
let minX = Infinity;
|
|
3968
|
-
let maxX = -Infinity;
|
|
3969
|
-
for (const p of s) {
|
|
3970
|
-
if (p[0] < minX) minX = p[0];
|
|
3971
|
-
if (p[0] > maxX) maxX = p[0];
|
|
3972
|
-
}
|
|
3973
|
-
if (minX > px2) {
|
|
3974
|
-
winding += dir;
|
|
3975
|
-
continue;
|
|
3976
|
-
}
|
|
3977
|
-
if (maxX <= px2) continue;
|
|
3978
|
-
const t = solveMonotone(s, 1, py);
|
|
3979
|
-
if (segmentPoint(s, t)[0] > px2) winding += dir;
|
|
3980
|
-
}
|
|
3981
|
-
}
|
|
3982
|
-
return winding;
|
|
3983
|
-
}
|
|
3984
|
-
function pointInBezierPath(input, point, fillRule = "nonzero") {
|
|
3985
|
-
const chains = pathToContours(input).map((c) => closeContour(c, true));
|
|
3986
|
-
const w = windingNumber(chains, point);
|
|
3987
|
-
return fillRule === "evenodd" ? (w & 1) === 1 : w !== 0;
|
|
3988
|
-
}
|
|
3989
|
-
function distanceToBezierPath(input, point) {
|
|
3990
|
-
const contours = Array.isArray(input) && input.length > 0 && input[0].segments ? input : pathToContours(input);
|
|
3991
|
-
let best = Infinity;
|
|
3992
|
-
for (const contour of contours) {
|
|
3993
|
-
for (const s of closeContour(contour)) {
|
|
3994
|
-
let minX = Infinity;
|
|
3995
|
-
let minY = Infinity;
|
|
3996
|
-
let maxX = -Infinity;
|
|
3997
|
-
let maxY = -Infinity;
|
|
3998
|
-
for (const p of s) {
|
|
3999
|
-
minX = Math.min(minX, p[0]);
|
|
4000
|
-
minY = Math.min(minY, p[1]);
|
|
4001
|
-
maxX = Math.max(maxX, p[0]);
|
|
4002
|
-
maxY = Math.max(maxY, p[1]);
|
|
4003
|
-
}
|
|
4004
|
-
const dx = Math.max(minX - point[0], 0, point[0] - maxX);
|
|
4005
|
-
const dy = Math.max(minY - point[1], 0, point[1] - maxY);
|
|
4006
|
-
if (Math.hypot(dx, dy) >= best) continue;
|
|
4007
|
-
best = Math.min(best, segmentNearest(s, point).distance);
|
|
4008
|
-
}
|
|
4009
|
-
if (contour.segments.length === 0) continue;
|
|
4010
|
-
}
|
|
4011
|
-
return best;
|
|
4012
|
-
}
|
|
4013
|
-
function flattenChain(segs, tolerance) {
|
|
4014
|
-
const out = [];
|
|
4015
|
-
for (const s of segs) {
|
|
4016
|
-
if (out.length === 0) out.push(s[0]);
|
|
4017
|
-
if (s.length === 2) {
|
|
4018
|
-
out.push(s[1]);
|
|
4019
|
-
continue;
|
|
4020
|
-
}
|
|
4021
|
-
let dd = 0;
|
|
4022
|
-
for (let i = 0; i + 2 < s.length; i++) {
|
|
4023
|
-
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]));
|
|
4024
|
-
}
|
|
4025
|
-
const deg = s.length - 1;
|
|
4026
|
-
const n = Math.max(1, Math.min(256, Math.ceil(Math.sqrt(deg * (deg - 1) * dd / (8 * tolerance)))));
|
|
4027
|
-
for (let k = 1; k <= n; k++) out.push(segmentPoint(s, k / n));
|
|
4028
|
-
}
|
|
4029
|
-
if (out.length > 1 && samePoint(out[0], out[out.length - 1])) out.pop();
|
|
4030
|
-
return out;
|
|
4031
|
-
}
|
|
4032
|
-
|
|
4033
|
-
// src/canvas/pathops/boolean.ts
|
|
4034
|
-
var PathOpsError = class extends Error {
|
|
4035
|
-
constructor(message) {
|
|
4036
|
-
super(message);
|
|
4037
|
-
this.name = "PathOpsError";
|
|
4038
|
-
}
|
|
4039
|
-
};
|
|
4040
|
-
function isOperandObject(input) {
|
|
4041
|
-
return !!input && typeof input === "object" && !Array.isArray(input) && "path" in input;
|
|
4042
|
-
}
|
|
4043
|
-
function normalizeOperand(input) {
|
|
4044
|
-
const path = isOperandObject(input) ? input.path : input;
|
|
4045
|
-
const fillRule = isOperandObject(input) ? input.fillRule ?? "nonzero" : "nonzero";
|
|
4046
|
-
const chains = [];
|
|
4047
|
-
for (const contour of pathToContours(path ?? "")) {
|
|
4048
|
-
const chain = closeContour(contour, true);
|
|
4049
|
-
if (chain.length === 0) continue;
|
|
4050
|
-
for (const seg of chain) {
|
|
4051
|
-
for (const p of seg) {
|
|
4052
|
-
if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) throw new PathOpsError(NON_FINITE);
|
|
4053
|
-
}
|
|
4054
|
-
}
|
|
4055
|
-
chains.push(chain);
|
|
4056
|
-
}
|
|
4057
|
-
return { chains, fillRule };
|
|
4058
|
-
}
|
|
4059
|
-
function scaleOf(operands) {
|
|
4060
|
-
let max = 0;
|
|
4061
|
-
for (const op of operands) {
|
|
4062
|
-
for (const chain of op.chains) {
|
|
4063
|
-
for (const seg of chain) {
|
|
4064
|
-
for (const [x, y] of seg) max = Math.max(max, Math.abs(x), Math.abs(y));
|
|
4065
|
-
}
|
|
4066
|
-
}
|
|
4067
|
-
}
|
|
4068
|
-
return Math.max(1, max);
|
|
4069
|
-
}
|
|
4070
|
-
var VertexPool = class {
|
|
4071
|
-
constructor(eps) {
|
|
4072
|
-
this.eps = eps;
|
|
4073
|
-
}
|
|
4074
|
-
eps;
|
|
4075
|
-
vertices = [];
|
|
4076
|
-
cells = /* @__PURE__ */ new Map();
|
|
4077
|
-
get(x, y) {
|
|
4078
|
-
const cx = Math.floor(x / this.eps);
|
|
4079
|
-
const cy = Math.floor(y / this.eps);
|
|
4080
|
-
let best = null;
|
|
4081
|
-
let bestDist = Infinity;
|
|
4082
|
-
for (let dx = -1; dx <= 1; dx++) {
|
|
4083
|
-
for (let dy = -1; dy <= 1; dy++) {
|
|
4084
|
-
const ids = this.cells.get(`${cx + dx},${cy + dy}`);
|
|
4085
|
-
if (!ids) continue;
|
|
4086
|
-
for (const id of ids) {
|
|
4087
|
-
const v = this.vertices[id];
|
|
4088
|
-
const d = Math.hypot(v.x - x, v.y - y);
|
|
4089
|
-
if (d <= this.eps && d < bestDist) {
|
|
4090
|
-
best = v;
|
|
4091
|
-
bestDist = d;
|
|
4092
|
-
}
|
|
4093
|
-
}
|
|
4094
|
-
}
|
|
4095
|
-
}
|
|
4096
|
-
if (best) return best;
|
|
4097
|
-
const vertex = { id: this.vertices.length, x, y, p: [x, y] };
|
|
4098
|
-
this.vertices.push(vertex);
|
|
4099
|
-
const key = `${cx},${cy}`;
|
|
4100
|
-
const bucket = this.cells.get(key);
|
|
4101
|
-
if (bucket) bucket.push(vertex.id);
|
|
4102
|
-
else this.cells.set(key, [vertex.id]);
|
|
4103
|
-
return vertex;
|
|
4104
|
-
}
|
|
4105
|
-
};
|
|
4106
|
-
function hull(pts) {
|
|
4107
|
-
let minX = Infinity;
|
|
4108
|
-
let maxX = -Infinity;
|
|
4109
|
-
let minY = Infinity;
|
|
4110
|
-
let maxY = -Infinity;
|
|
4111
|
-
for (const [x, y] of pts) {
|
|
4112
|
-
if (x < minX) minX = x;
|
|
4113
|
-
if (x > maxX) maxX = x;
|
|
4114
|
-
if (y < minY) minY = y;
|
|
4115
|
-
if (y > maxY) maxY = y;
|
|
4116
|
-
}
|
|
4117
|
-
return { minX, maxX, minY, maxY };
|
|
4118
|
-
}
|
|
4119
|
-
function snapEnds(pts, a, b) {
|
|
4120
|
-
const n = pts.length;
|
|
4121
|
-
if (n === 2) return [a, b];
|
|
4122
|
-
const d0 = sub2(a, pts[0]);
|
|
4123
|
-
const d1 = sub2(b, pts[n - 1]);
|
|
4124
|
-
if (n === 3) {
|
|
4125
|
-
return [a, [pts[1][0] + (d0[0] + d1[0]) / 2, pts[1][1] + (d0[1] + d1[1]) / 2], b];
|
|
4126
|
-
}
|
|
4127
|
-
return [a, [pts[1][0] + d0[0], pts[1][1] + d0[1]], [pts[2][0] + d1[0], pts[2][1] + d1[1]], b];
|
|
4128
|
-
}
|
|
4129
|
-
function makePiece(pts, a, b, operand, src, s0, s1) {
|
|
4130
|
-
const snapped = snapEnds(pts, a.p, b.p);
|
|
4131
|
-
return { pts: snapped, a, b, operand, src, s0, s1, dirty: true, ...hull(snapped) };
|
|
4132
|
-
}
|
|
4133
|
-
var NON_FINITE = "non-finite coordinates";
|
|
4134
|
-
function flatness(pts) {
|
|
4135
|
-
if (pts.length === 2) return 0;
|
|
4136
|
-
const a = pts[0];
|
|
4137
|
-
const b = pts[pts.length - 1];
|
|
4138
|
-
const chord = sub2(b, a);
|
|
4139
|
-
const len = Math.hypot(chord[0], chord[1]);
|
|
4140
|
-
let worst = 0;
|
|
4141
|
-
for (let i = 1; i < pts.length - 1; i++) {
|
|
4142
|
-
const v = sub2(pts[i], a);
|
|
4143
|
-
const d = len > 0 ? Math.abs(cross(chord, v)) / len : Math.hypot(v[0], v[1]);
|
|
4144
|
-
const along = len > 0 ? dot2(chord, v) / len : 0;
|
|
4145
|
-
const over = Math.max(0, -along, along - len);
|
|
4146
|
-
worst = Math.max(worst, d, over);
|
|
4147
|
-
}
|
|
4148
|
-
return worst;
|
|
4149
|
-
}
|
|
4150
|
-
function split2(pts) {
|
|
4151
|
-
const n = pts.length;
|
|
4152
|
-
const mid = (p, q) => [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
|
|
4153
|
-
if (n === 2) {
|
|
4154
|
-
const m2 = mid(pts[0], pts[1]);
|
|
4155
|
-
return [
|
|
4156
|
-
[pts[0], m2],
|
|
4157
|
-
[m2, pts[1]]
|
|
4158
|
-
];
|
|
4159
|
-
}
|
|
4160
|
-
if (n === 3) {
|
|
4161
|
-
const a2 = mid(pts[0], pts[1]);
|
|
4162
|
-
const b2 = mid(pts[1], pts[2]);
|
|
4163
|
-
const m2 = mid(a2, b2);
|
|
4164
|
-
return [
|
|
4165
|
-
[pts[0], a2, m2],
|
|
4166
|
-
[m2, b2, pts[2]]
|
|
4167
|
-
];
|
|
4168
|
-
}
|
|
4169
|
-
const a = mid(pts[0], pts[1]);
|
|
4170
|
-
const b = mid(pts[1], pts[2]);
|
|
4171
|
-
const c = mid(pts[2], pts[3]);
|
|
4172
|
-
const ab = mid(a, b);
|
|
4173
|
-
const bc = mid(b, c);
|
|
4174
|
-
const m = mid(ab, bc);
|
|
4175
|
-
return [
|
|
4176
|
-
[pts[0], a, ab, m],
|
|
4177
|
-
[m, bc, c, pts[3]]
|
|
4178
|
-
];
|
|
4179
|
-
}
|
|
4180
|
-
function closestSegmentPoints(a0, a1, b0, b1) {
|
|
4181
|
-
const d1 = sub2(a1, a0);
|
|
4182
|
-
const d2 = sub2(b1, b0);
|
|
4183
|
-
const r = sub2(a0, b0);
|
|
4184
|
-
const a = dot2(d1, d1);
|
|
4185
|
-
const e = dot2(d2, d2);
|
|
4186
|
-
const f = dot2(d2, r);
|
|
4187
|
-
let s = 0;
|
|
4188
|
-
let u = 0;
|
|
4189
|
-
if (a <= 1e-300 && e <= 1e-300) return { s: 0, u: 0, d: dist2(a0, b0) };
|
|
4190
|
-
if (a <= 1e-300) {
|
|
4191
|
-
u = Math.max(0, Math.min(1, f / e));
|
|
4192
|
-
} else {
|
|
4193
|
-
const c = dot2(d1, r);
|
|
4194
|
-
if (e <= 1e-300) {
|
|
4195
|
-
s = Math.max(0, Math.min(1, -c / a));
|
|
4196
|
-
} else {
|
|
4197
|
-
const b = dot2(d1, d2);
|
|
4198
|
-
const denom = a * e - b * b;
|
|
4199
|
-
s = denom > 0 ? Math.max(0, Math.min(1, (b * f - c * e) / denom)) : 0;
|
|
4200
|
-
u = (b * s + f) / e;
|
|
4201
|
-
if (u < 0) {
|
|
4202
|
-
u = 0;
|
|
4203
|
-
s = Math.max(0, Math.min(1, -c / a));
|
|
4204
|
-
} else if (u > 1) {
|
|
4205
|
-
u = 1;
|
|
4206
|
-
s = Math.max(0, Math.min(1, (b - c) / a));
|
|
4207
|
-
}
|
|
4208
|
-
}
|
|
4209
|
-
}
|
|
4210
|
-
const pa = [a0[0] + d1[0] * s, a0[1] + d1[1] * s];
|
|
4211
|
-
const pb = [b0[0] + d2[0] * u, b0[1] + d2[1] * u];
|
|
4212
|
-
return { s, u, d: dist2(pa, pb) };
|
|
4213
|
-
}
|
|
4214
|
-
function polish(P, Q, s, u) {
|
|
4215
|
-
let best = { s, u, d: dist2(segmentPoint(P, s), segmentPoint(Q, u)) };
|
|
4216
|
-
for (let i = 0; i < 10 && best.d > 0; i++) {
|
|
4217
|
-
const f = sub2(segmentPoint(P, s), segmentPoint(Q, u));
|
|
4218
|
-
const dp = segmentDerivative(P, s);
|
|
4219
|
-
const dq = segmentDerivative(Q, u);
|
|
4220
|
-
const det = cross(dp, [-dq[0], -dq[1]]);
|
|
4221
|
-
if (!(Math.abs(det) > 1e-300)) break;
|
|
4222
|
-
const ds = cross([-f[0], -f[1]], [-dq[0], -dq[1]]) / det;
|
|
4223
|
-
const du = cross(dp, [-f[0], -f[1]]) / det;
|
|
4224
|
-
s = Math.max(0, Math.min(1, s + ds));
|
|
4225
|
-
u = Math.max(0, Math.min(1, u + du));
|
|
4226
|
-
const d = dist2(segmentPoint(P, s), segmentPoint(Q, u));
|
|
4227
|
-
if (d < best.d) best = { s, u, d };
|
|
4228
|
-
else break;
|
|
4229
|
-
}
|
|
4230
|
-
return best;
|
|
4231
|
-
}
|
|
4232
|
-
function intersectSubdivided(P, p0, p1, Q, q0, q1, fullP, fullQ, ctx, out, depth) {
|
|
4233
|
-
const hp = hull(P);
|
|
4234
|
-
const hq = hull(Q);
|
|
4235
|
-
const e = ctx.eps;
|
|
4236
|
-
if (hp.minX > hq.maxX + e || hq.minX > hp.maxX + e || hp.minY > hq.maxY + e || hq.minY > hp.maxY + e) return;
|
|
4237
|
-
const fp = flatness(P);
|
|
4238
|
-
const fq = flatness(Q);
|
|
4239
|
-
const flatTol = e * 0.5;
|
|
4240
|
-
if (fp <= flatTol && fq <= flatTol || depth >= 52) {
|
|
4241
|
-
if (++ctx.leaves > ctx.budget) throw new PathOpsError("curve intersection did not converge");
|
|
4242
|
-
const a0 = P[0];
|
|
4243
|
-
const a1 = P[P.length - 1];
|
|
4244
|
-
const b0 = Q[0];
|
|
4245
|
-
const b1 = Q[Q.length - 1];
|
|
4246
|
-
const d1 = sub2(a1, a0);
|
|
4247
|
-
const d2 = sub2(b1, b0);
|
|
4248
|
-
const denom = cross(d1, d2);
|
|
4249
|
-
let s = 0;
|
|
4250
|
-
let u = 0;
|
|
4251
|
-
let hit = false;
|
|
4252
|
-
const l1 = Math.hypot(d1[0], d1[1]);
|
|
4253
|
-
const l2 = Math.hypot(d2[0], d2[1]);
|
|
4254
|
-
if (Math.abs(denom) > 1e-12 * l1 * l2) {
|
|
4255
|
-
const r = sub2(b0, a0);
|
|
4256
|
-
s = cross(r, d2) / denom;
|
|
4257
|
-
u = cross(r, d1) / denom;
|
|
4258
|
-
const ts = l1 > 0 ? e / l1 : 1;
|
|
4259
|
-
const tu = l2 > 0 ? e / l2 : 1;
|
|
4260
|
-
hit = s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu;
|
|
4261
|
-
}
|
|
4262
|
-
if (!hit) {
|
|
4263
|
-
const c = closestSegmentPoints(a0, a1, b0, b1);
|
|
4264
|
-
if (c.d > e) return;
|
|
4265
|
-
s = c.s;
|
|
4266
|
-
u = c.u;
|
|
4267
|
-
}
|
|
4268
|
-
s = Math.max(0, Math.min(1, s));
|
|
4269
|
-
u = Math.max(0, Math.min(1, u));
|
|
4270
|
-
const best = polish(fullP, fullQ, p0 + (p1 - p0) * s, q0 + (q1 - q0) * u);
|
|
4271
|
-
if (best.d > e * 2) return;
|
|
4272
|
-
const a = segmentPoint(fullP, best.s);
|
|
4273
|
-
const b = segmentPoint(fullQ, best.u);
|
|
4274
|
-
out.push({ tp: best.s, tq: best.u, point: [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2] });
|
|
4275
|
-
return;
|
|
4276
|
-
}
|
|
4277
|
-
const splitP = fp > flatTol;
|
|
4278
|
-
const splitQ = fq > flatTol;
|
|
4279
|
-
const pm = (p0 + p1) / 2;
|
|
4280
|
-
const qm = (q0 + q1) / 2;
|
|
4281
|
-
if (splitP && splitQ) {
|
|
4282
|
-
const [pa, pb] = split2(P);
|
|
4283
|
-
const [qa, qb] = split2(Q);
|
|
4284
|
-
intersectSubdivided(pa, p0, pm, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
|
|
4285
|
-
intersectSubdivided(pa, p0, pm, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4286
|
-
intersectSubdivided(pb, pm, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
|
|
4287
|
-
intersectSubdivided(pb, pm, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4288
|
-
} else if (splitP) {
|
|
4289
|
-
const [pa, pb] = split2(P);
|
|
4290
|
-
intersectSubdivided(pa, p0, pm, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4291
|
-
intersectSubdivided(pb, pm, p1, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4292
|
-
} else {
|
|
4293
|
-
const [qa, qb] = split2(Q);
|
|
4294
|
-
intersectSubdivided(P, p0, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
|
|
4295
|
-
intersectSubdivided(P, p0, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4296
|
-
}
|
|
4297
|
-
}
|
|
4298
|
-
function nearestOn(piece, p, eps) {
|
|
4299
|
-
if (p[0] < piece.minX - eps || p[0] > piece.maxX + eps || p[1] < piece.minY - eps || p[1] > piece.maxY + eps) return null;
|
|
4300
|
-
return segmentNearest(piece.pts, p);
|
|
4301
|
-
}
|
|
4302
|
-
function staysClose(P, Q, e1, e2, tol) {
|
|
4303
|
-
for (const f of [0.25, 0.5, 0.75]) {
|
|
4304
|
-
const tp = e1.tp + (e2.tp - e1.tp) * f;
|
|
4305
|
-
const q = segmentNearest(Q.pts, segmentPoint(P.pts, tp));
|
|
4306
|
-
if (q.distance > tol) return false;
|
|
4307
|
-
}
|
|
4308
|
-
return true;
|
|
4309
|
-
}
|
|
4310
|
-
function pairEvents(P, Q, ctx) {
|
|
4311
|
-
const eps = ctx.eps;
|
|
4312
|
-
const anchors = [];
|
|
4313
|
-
for (const [va, tp] of [
|
|
4314
|
-
[P.a, 0],
|
|
4315
|
-
[P.b, 1]
|
|
4316
|
-
]) {
|
|
4317
|
-
if (va === Q.a) anchors.push({ tp, tq: 0, point: va.p, anchor: true, shared: true });
|
|
4318
|
-
else if (va === Q.b) anchors.push({ tp, tq: 1, point: va.p, anchor: true, shared: true });
|
|
4319
|
-
}
|
|
4320
|
-
for (const [v, tq] of [
|
|
4321
|
-
[Q.a, 0],
|
|
4322
|
-
[Q.b, 1]
|
|
4323
|
-
]) {
|
|
4324
|
-
if (v === P.a || v === P.b) continue;
|
|
4325
|
-
const near = nearestOn(P, v.p, eps);
|
|
4326
|
-
if (near && near.distance <= eps) anchors.push({ tp: near.t, tq, point: v.p, anchor: true });
|
|
4327
|
-
}
|
|
4328
|
-
for (const [v, tp] of [
|
|
4329
|
-
[P.a, 0],
|
|
4330
|
-
[P.b, 1]
|
|
4331
|
-
]) {
|
|
4332
|
-
if (v === Q.a || v === Q.b) continue;
|
|
4333
|
-
const near = nearestOn(Q, v.p, eps);
|
|
4334
|
-
if (near && near.distance <= eps) anchors.push({ tp, tq: near.t, point: v.p, anchor: true });
|
|
4335
|
-
}
|
|
4336
|
-
if (anchors.length >= 2) {
|
|
4337
|
-
const sorted = [...anchors].sort((x, y) => x.tp - y.tp);
|
|
4338
|
-
const first = sorted[0];
|
|
4339
|
-
const last = sorted[sorted.length - 1];
|
|
4340
|
-
if (dist2(first.point, last.point) > eps && staysClose(P, Q, first, last, eps * 4)) {
|
|
4341
|
-
return anchors.filter((a) => !a.shared);
|
|
4342
|
-
}
|
|
4343
|
-
}
|
|
4344
|
-
const found = [];
|
|
4345
|
-
if (P.pts.length === 2 && Q.pts.length === 2) {
|
|
4346
|
-
const a0 = P.pts[0];
|
|
4347
|
-
const d1 = sub2(P.pts[1], a0);
|
|
4348
|
-
const b0 = Q.pts[0];
|
|
4349
|
-
const d2 = sub2(Q.pts[1], b0);
|
|
4350
|
-
const denom = cross(d1, d2);
|
|
4351
|
-
const l1 = Math.hypot(d1[0], d1[1]);
|
|
4352
|
-
const l2 = Math.hypot(d2[0], d2[1]);
|
|
4353
|
-
if (Math.abs(denom) > 1e-12 * l1 * l2) {
|
|
4354
|
-
const r = sub2(b0, a0);
|
|
4355
|
-
const s = cross(r, d2) / denom;
|
|
4356
|
-
const u = cross(r, d1) / denom;
|
|
4357
|
-
const ts = eps / l1;
|
|
4358
|
-
const tu = eps / l2;
|
|
4359
|
-
if (s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu) {
|
|
4360
|
-
const sc = Math.max(0, Math.min(1, s));
|
|
4361
|
-
const uc = Math.max(0, Math.min(1, u));
|
|
4362
|
-
found.push({ tp: sc, tq: uc, point: [a0[0] + d1[0] * sc, a0[1] + d1[1] * sc] });
|
|
4363
|
-
}
|
|
4364
|
-
}
|
|
4365
|
-
} else {
|
|
4366
|
-
ctx.leaves = 0;
|
|
4367
|
-
intersectSubdivided(P.pts, 0, 1, Q.pts, 0, 1, P.pts, Q.pts, ctx, found, 0);
|
|
4368
|
-
}
|
|
4369
|
-
if (found.length === 0) return anchors.filter((a) => !a.shared);
|
|
4370
|
-
const all = [...anchors, ...found].sort((x, y) => x.tp - y.tp || x.tq - y.tq);
|
|
4371
|
-
const clusters = [];
|
|
4372
|
-
for (const ev of all) {
|
|
4373
|
-
const cluster = clusters[clusters.length - 1];
|
|
4374
|
-
const prev = cluster?.[cluster.length - 1];
|
|
4375
|
-
if (prev && (dist2(prev.point, ev.point) <= eps * 2 || staysClose(P, Q, prev, ev, eps * 2))) cluster.push(ev);
|
|
4376
|
-
else clusters.push([ev]);
|
|
4377
|
-
}
|
|
4378
|
-
const out = [];
|
|
4379
|
-
for (const cluster of clusters) {
|
|
4380
|
-
const shared = cluster.find((e) => e.shared);
|
|
4381
|
-
if (shared) continue;
|
|
4382
|
-
const anchor = cluster.find((e) => e.anchor);
|
|
4383
|
-
out.push(anchor ?? cluster[Math.floor(cluster.length / 2)]);
|
|
4384
|
-
}
|
|
4385
|
-
return out;
|
|
4386
|
-
}
|
|
4387
|
-
function addSplit2(map, piece, t, v) {
|
|
4388
|
-
if (v === piece.a || v === piece.b) return;
|
|
4389
|
-
const list = map.get(piece);
|
|
4390
|
-
if (list) list.push({ t, v });
|
|
4391
|
-
else map.set(piece, [{ t, v }]);
|
|
4392
|
-
}
|
|
4393
|
-
function eventVertex(P, Q, ev, pool, eps) {
|
|
4394
|
-
for (const v of [P.a, P.b, Q.a, Q.b]) if (dist2(v.p, ev.point) <= eps) return v;
|
|
4395
|
-
return pool.get(ev.point[0], ev.point[1]);
|
|
4396
|
-
}
|
|
4397
|
-
function splitPass2(pieces, pool, ctx) {
|
|
4398
|
-
const eps = ctx.eps;
|
|
4399
|
-
const order = pieces.map((_, i) => i).sort((i, j) => pieces[i].minX - pieces[j].minX);
|
|
4400
|
-
const splits = /* @__PURE__ */ new Map();
|
|
4401
|
-
for (let oi = 0; oi < order.length; oi++) {
|
|
4402
|
-
const P = pieces[order[oi]];
|
|
4403
|
-
for (let oj = oi + 1; oj < order.length; oj++) {
|
|
4404
|
-
const Q = pieces[order[oj]];
|
|
4405
|
-
if (Q.minX > P.maxX + eps) break;
|
|
4406
|
-
if (!P.dirty && !Q.dirty) continue;
|
|
4407
|
-
if (Q.minY > P.maxY + eps || Q.maxY < P.minY - eps) continue;
|
|
4408
|
-
if (P.a === Q.a && P.b === Q.b && P.pts.length === 2 && Q.pts.length === 2) continue;
|
|
4409
|
-
if (P.a === Q.b && P.b === Q.a && P.pts.length === 2 && Q.pts.length === 2) continue;
|
|
4410
|
-
for (const ev of pairEvents(P, Q, ctx)) {
|
|
4411
|
-
const v = eventVertex(P, Q, ev, pool, eps);
|
|
4412
|
-
addSplit2(splits, P, ev.tp, v);
|
|
4413
|
-
addSplit2(splits, Q, ev.tq, v);
|
|
4414
|
-
}
|
|
4415
|
-
}
|
|
4416
|
-
}
|
|
4417
|
-
for (const p of pieces) p.dirty = false;
|
|
4418
|
-
if (splits.size === 0) return { pieces, changed: false };
|
|
4419
|
-
const out = [];
|
|
4420
|
-
for (const piece of pieces) {
|
|
4421
|
-
const list = splits.get(piece);
|
|
4422
|
-
if (!list) {
|
|
4423
|
-
out.push(piece);
|
|
4424
|
-
continue;
|
|
4425
|
-
}
|
|
4426
|
-
list.sort((x, y) => x.t - y.t);
|
|
4427
|
-
let prevT = 0;
|
|
4428
|
-
let prevV = piece.a;
|
|
4429
|
-
const range = piece.s1 - piece.s0;
|
|
4430
|
-
const emit = (t, v) => {
|
|
4431
|
-
if (v === prevV || t <= prevT) return;
|
|
4432
|
-
const pts = subSegment(piece.pts, prevT, t);
|
|
4433
|
-
out.push(makePiece(pts, prevV, v, piece.operand, piece.src, piece.s0 + range * prevT, piece.s0 + range * t));
|
|
4434
|
-
prevT = t;
|
|
4435
|
-
prevV = v;
|
|
4436
|
-
};
|
|
4437
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4438
|
-
for (const { t, v } of list) {
|
|
4439
|
-
if (seen.has(v) || t <= 0 || t >= 1) continue;
|
|
4440
|
-
seen.add(v);
|
|
4441
|
-
emit(t, v);
|
|
4442
|
-
}
|
|
4443
|
-
if (prevV !== piece.b) {
|
|
4444
|
-
const pts = subSegment(piece.pts, prevT, 1);
|
|
4445
|
-
out.push(makePiece(pts, prevV, piece.b, piece.operand, piece.src, piece.s0 + range * prevT, piece.s1));
|
|
4446
|
-
}
|
|
4447
|
-
}
|
|
4448
|
-
return { pieces: out, changed: true };
|
|
4449
|
-
}
|
|
4450
|
-
function buildArrangement(inputs, options) {
|
|
4451
|
-
const operands = inputs.map(normalizeOperand);
|
|
4452
|
-
const scale2 = scaleOf(operands);
|
|
4453
|
-
const eps = options.epsilon && options.epsilon > 0 ? options.epsilon : 1e-9 * scale2;
|
|
4454
|
-
const pool = new VertexPool(eps);
|
|
4455
|
-
const sources = [];
|
|
4456
|
-
let pieces = [];
|
|
4457
|
-
operands.forEach((op, operand) => {
|
|
4458
|
-
for (const chain of op.chains) {
|
|
4459
|
-
for (const raw of chain) {
|
|
4460
|
-
const straight = raw.length > 2 && isStraightSegment(raw, eps * 0.01);
|
|
4461
|
-
const seg = straight ? [raw[0], raw[raw.length - 1]] : raw;
|
|
4462
|
-
const src = sources.length;
|
|
4463
|
-
sources.push({ seg, line: seg.length === 2 });
|
|
4464
|
-
const ts = [0, ...monotoneParams(seg), 1];
|
|
4465
|
-
for (let i = 0; i + 1 < ts.length; i++) {
|
|
4466
|
-
const pts = subSegment(seg, ts[i], ts[i + 1]);
|
|
4467
|
-
const a = pool.get(pts[0][0], pts[0][1]);
|
|
4468
|
-
const b = pool.get(pts[pts.length - 1][0], pts[pts.length - 1][1]);
|
|
4469
|
-
if (a === b) continue;
|
|
4470
|
-
pieces.push(makePiece(pts, a, b, operand, src, ts[i], ts[i + 1]));
|
|
4471
|
-
}
|
|
4472
|
-
}
|
|
4473
|
-
}
|
|
4474
|
-
});
|
|
4475
|
-
const ctx = { eps, leaves: 0, budget: options.subdivisionBudget ?? 2e4 };
|
|
4476
|
-
let settled = false;
|
|
4477
|
-
for (let pass = 0; pass < 6; pass++) {
|
|
4478
|
-
const result = splitPass2(pieces, pool, ctx);
|
|
4479
|
-
pieces = result.pieces;
|
|
4480
|
-
if (!result.changed) {
|
|
4481
|
-
settled = true;
|
|
4482
|
-
break;
|
|
4483
|
-
}
|
|
4484
|
-
}
|
|
4485
|
-
if (!settled) throw new PathOpsError("intersections did not settle");
|
|
4486
|
-
const count = operands.length;
|
|
4487
|
-
const groups = /* @__PURE__ */ new Map();
|
|
4488
|
-
const edges = [];
|
|
4489
|
-
for (const piece of pieces) {
|
|
4490
|
-
const forward = piece.a.id < piece.b.id;
|
|
4491
|
-
const a = forward ? piece.a : piece.b;
|
|
4492
|
-
const b = forward ? piece.b : piece.a;
|
|
4493
|
-
const pts = forward ? piece.pts : [...piece.pts].reverse();
|
|
4494
|
-
const key = `${a.id},${b.id}`;
|
|
4495
|
-
const group = groups.get(key) ?? [];
|
|
4496
|
-
if (!groups.has(key)) groups.set(key, group);
|
|
4497
|
-
const mid = segmentPoint(pts, 0.5);
|
|
4498
|
-
let edge = group.find((e) => {
|
|
4499
|
-
if (e.pts.length === 2 && pts.length === 2) return true;
|
|
4500
|
-
return segmentNearest(e.pts, mid).distance <= eps * 4 && segmentNearest(pts, segmentPoint(e.pts, 0.5)).distance <= eps * 4;
|
|
4501
|
-
});
|
|
4502
|
-
if (!edge) {
|
|
4503
|
-
edge = {
|
|
4504
|
-
a,
|
|
4505
|
-
b,
|
|
4506
|
-
pts,
|
|
4507
|
-
mult: new Array(count).fill(0),
|
|
4508
|
-
src: piece.src,
|
|
4509
|
-
sa: forward ? piece.s0 : piece.s1,
|
|
4510
|
-
sb: forward ? piece.s1 : piece.s0,
|
|
4511
|
-
...hull(pts)
|
|
4512
|
-
};
|
|
4513
|
-
group.push(edge);
|
|
4514
|
-
edges.push(edge);
|
|
4515
|
-
}
|
|
4516
|
-
edge.mult[piece.operand] += forward ? 1 : -1;
|
|
4517
|
-
}
|
|
4518
|
-
const live = edges.filter((e) => e.mult.some((m) => m !== 0));
|
|
4519
|
-
const byY = new BandIndex(live, (e) => e.minY, (e) => e.maxY);
|
|
4520
|
-
const byX = new BandIndex(live, (e) => e.minX, (e) => e.maxX);
|
|
4521
|
-
const right = [];
|
|
4522
|
-
const left = [];
|
|
4523
|
-
for (let i = 0; i < live.length; i++) {
|
|
4524
|
-
const edge = live[i];
|
|
4525
|
-
const mid = segmentPoint(edge.pts, 0.5);
|
|
4526
|
-
const tan = segmentTangent(edge.pts, 0.5);
|
|
4527
|
-
const horizontal = Math.abs(tan[1]) < Math.abs(tan[0]);
|
|
4528
|
-
const winding = new Array(count).fill(0);
|
|
4529
|
-
const [mx, my] = mid;
|
|
4530
|
-
for (const j of horizontal ? byX.at(mx) : byY.at(my)) {
|
|
4531
|
-
if (j === i) continue;
|
|
4532
|
-
const o = live[j];
|
|
4533
|
-
let dir = 0;
|
|
4534
|
-
if (!horizontal) {
|
|
4535
|
-
if (o.maxX <= mx) continue;
|
|
4536
|
-
const ay = o.a.y;
|
|
4537
|
-
const by = o.b.y;
|
|
4538
|
-
if (ay <= my && by > my) dir = 1;
|
|
4539
|
-
else if (by <= my && ay > my) dir = -1;
|
|
4540
|
-
else continue;
|
|
4541
|
-
if (o.minX <= mx) {
|
|
4542
|
-
const t = solveMonotone(o.pts, 1, my);
|
|
4543
|
-
if (!(segmentPoint(o.pts, t)[0] > mx)) continue;
|
|
4544
|
-
}
|
|
4545
|
-
} else {
|
|
4546
|
-
if (o.maxY <= my) continue;
|
|
4547
|
-
const ax = o.a.x;
|
|
4548
|
-
const bx = o.b.x;
|
|
4549
|
-
if (bx < mx && mx <= ax) dir = 1;
|
|
4550
|
-
else if (ax < mx && mx <= bx) dir = -1;
|
|
4551
|
-
else continue;
|
|
4552
|
-
if (o.minY <= my) {
|
|
4553
|
-
const t = solveMonotone(o.pts, 0, mx);
|
|
4554
|
-
if (!(segmentPoint(o.pts, t)[1] > my)) continue;
|
|
4555
|
-
}
|
|
4556
|
-
}
|
|
4557
|
-
for (let k = 0; k < count; k++) if (o.mult[k] !== 0) winding[k] += dir * o.mult[k];
|
|
4558
|
-
}
|
|
4559
|
-
const sideIsRight = horizontal ? edge.b.x < edge.a.x : edge.b.y > edge.a.y;
|
|
4560
|
-
const r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);
|
|
4561
|
-
right.push(r);
|
|
4562
|
-
left.push(r.map((w, k) => w + edge.mult[k]));
|
|
4563
|
-
}
|
|
4564
|
-
return { edges: live, left, right, fillRules: operands.map((o) => o.fillRule), sources, eps, scale: scale2 };
|
|
4565
|
-
}
|
|
4566
|
-
var BandIndex = class {
|
|
4567
|
-
bands;
|
|
4568
|
-
min;
|
|
4569
|
-
size;
|
|
4570
|
-
constructor(edges, lo, hi) {
|
|
4571
|
-
let min = Infinity;
|
|
4572
|
-
let max = -Infinity;
|
|
4573
|
-
for (const e of edges) {
|
|
4574
|
-
min = Math.min(min, lo(e));
|
|
4575
|
-
max = Math.max(max, hi(e));
|
|
4576
|
-
}
|
|
4577
|
-
const count = Math.max(1, Math.min(4096, Math.ceil(Math.sqrt(edges.length) * 2)));
|
|
4578
|
-
this.min = min;
|
|
4579
|
-
this.size = max > min ? (max - min) / count : 1;
|
|
4580
|
-
this.bands = Array.from({ length: count }, () => []);
|
|
4581
|
-
edges.forEach((e, i) => {
|
|
4582
|
-
const from = this.index(lo(e));
|
|
4583
|
-
const to = this.index(hi(e));
|
|
4584
|
-
for (let b = from; b <= to; b++) this.bands[b].push(i);
|
|
4585
|
-
});
|
|
4586
|
-
}
|
|
4587
|
-
index(v) {
|
|
4588
|
-
const k = Math.floor((v - this.min) / this.size);
|
|
4589
|
-
return Math.max(0, Math.min(this.bands.length - 1, Number.isFinite(k) ? k : 0));
|
|
4590
|
-
}
|
|
4591
|
-
at(v) {
|
|
4592
|
-
return this.bands[this.index(v)] ?? [];
|
|
4593
|
-
}
|
|
4594
|
-
};
|
|
4595
|
-
function insideFlags2(windings, fillRules) {
|
|
4596
|
-
return windings.map((w, k) => fillRules[k] === "evenodd" ? (w & 1) === 1 : w !== 0);
|
|
4597
|
-
}
|
|
4598
|
-
var TWO_PI = Math.PI * 2;
|
|
4599
|
-
var TIE = 1e-3;
|
|
4600
|
-
function directed(edge, forward) {
|
|
4601
|
-
const pts = forward ? edge.pts : [...edge.pts].reverse();
|
|
4602
|
-
const out = segmentTangent(pts, 0);
|
|
4603
|
-
const end = segmentTangent(pts, 1);
|
|
4604
|
-
return {
|
|
4605
|
-
from: forward ? edge.a : edge.b,
|
|
4606
|
-
to: forward ? edge.b : edge.a,
|
|
4607
|
-
pts,
|
|
4608
|
-
src: edge.src,
|
|
4609
|
-
sFrom: forward ? edge.sa : edge.sb,
|
|
4610
|
-
sTo: forward ? edge.sb : edge.sa,
|
|
4611
|
-
outAngle: Math.atan2(out[1], out[0]),
|
|
4612
|
-
backAngle: Math.atan2(-end[1], -end[0]),
|
|
4613
|
-
length: dist2(pts[0], pts[pts.length - 1]) + (pts.length > 2 ? flatness(pts) : 0)
|
|
4614
|
-
};
|
|
4615
|
-
}
|
|
4616
|
-
function chordAngle(pts, t) {
|
|
4617
|
-
const p = segmentPoint(pts, t);
|
|
4618
|
-
return Math.atan2(p[1] - pts[0][1], p[0] - pts[0][0]);
|
|
4619
|
-
}
|
|
4620
|
-
function mod2pi(a) {
|
|
4621
|
-
const r = a - Math.floor(a / TWO_PI) * TWO_PI;
|
|
4622
|
-
return r >= TWO_PI ? 0 : r;
|
|
4623
|
-
}
|
|
4624
|
-
function traceRings2(edges, scale2) {
|
|
4625
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
4626
|
-
for (const e of edges) {
|
|
4627
|
-
const list = outgoing.get(e.from.id);
|
|
4628
|
-
if (list) list.push(e);
|
|
4629
|
-
else outgoing.set(e.from.id, [e]);
|
|
4630
|
-
}
|
|
4631
|
-
const next = (incoming) => {
|
|
4632
|
-
const candidates = outgoing.get(incoming.to.id)?.filter((c) => !used.has(c) || c === start);
|
|
4633
|
-
if (!candidates || candidates.length === 0) return void 0;
|
|
4634
|
-
if (candidates.length === 1) return candidates[0];
|
|
4635
|
-
const turns = candidates.map((c) => mod2pi(incoming.backAngle - c.outAngle));
|
|
4636
|
-
const tied = (x) => x < TIE || x > TWO_PI - TIE;
|
|
4637
|
-
let ambiguous = turns.some(tied);
|
|
4638
|
-
if (!ambiguous) {
|
|
4639
|
-
const sorted = [...turns].sort((a, b) => a - b);
|
|
4640
|
-
for (let i = 1; i < sorted.length; i++) if (sorted[i] - sorted[i - 1] < TIE) ambiguous = true;
|
|
4641
|
-
}
|
|
4642
|
-
if (ambiguous) {
|
|
4643
|
-
const back = [...incoming.pts].reverse();
|
|
4644
|
-
let delta = Math.min(incoming.length, ...candidates.map((c) => c.length));
|
|
4645
|
-
delta = Math.min(delta * 0.25, scale2 * 0.02);
|
|
4646
|
-
const frac = (e) => Math.min(1, Math.max(1e-6, delta / Math.max(e.length, 1e-300)));
|
|
4647
|
-
const backChord = chordAngle(back, frac(incoming));
|
|
4648
|
-
for (let i = 0; i < candidates.length; i++) {
|
|
4649
|
-
turns[i] = mod2pi(backChord - chordAngle(candidates[i].pts, frac(candidates[i])));
|
|
4650
|
-
if (turns[i] < 1e-12) turns[i] = TWO_PI;
|
|
4651
|
-
}
|
|
4652
|
-
} else {
|
|
4653
|
-
for (let i = 0; i < turns.length; i++) if (turns[i] < 1e-15) turns[i] = TWO_PI;
|
|
4654
|
-
}
|
|
4655
|
-
let best = 0;
|
|
4656
|
-
for (let i = 1; i < candidates.length; i++) if (turns[i] < turns[best]) best = i;
|
|
4657
|
-
return candidates[best];
|
|
4658
|
-
};
|
|
4659
|
-
const used = /* @__PURE__ */ new Set();
|
|
4660
|
-
const rings = [];
|
|
4661
|
-
let start = null;
|
|
4662
|
-
for (const first of edges) {
|
|
4663
|
-
if (used.has(first)) continue;
|
|
4664
|
-
start = first;
|
|
4665
|
-
const ring = [];
|
|
4666
|
-
let edge = first;
|
|
4667
|
-
let guard = edges.length + 1;
|
|
4668
|
-
let closed = false;
|
|
4669
|
-
while (edge && guard-- > 0) {
|
|
4670
|
-
used.add(edge);
|
|
4671
|
-
ring.push(edge);
|
|
4672
|
-
edge = next(edge);
|
|
4673
|
-
if (edge === first) {
|
|
4674
|
-
closed = true;
|
|
4675
|
-
break;
|
|
4676
|
-
}
|
|
4677
|
-
}
|
|
4678
|
-
if (!closed) throw new PathOpsError("result outline does not close");
|
|
4679
|
-
rings.push(ring);
|
|
4680
|
-
}
|
|
4681
|
-
return rings;
|
|
4682
|
-
}
|
|
4683
|
-
function mergeable(a, b, sources, eps) {
|
|
4684
|
-
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;
|
|
4685
|
-
if (a.pts.length === 2 && b.pts.length === 2) {
|
|
4686
|
-
const p = a.pts[0];
|
|
4687
|
-
const q = b.pts[1];
|
|
4688
|
-
const m = a.pts[1];
|
|
4689
|
-
const chord = sub2(q, p);
|
|
4690
|
-
const len = Math.hypot(chord[0], chord[1]);
|
|
4691
|
-
if (len === 0) return false;
|
|
4692
|
-
const off = Math.abs(cross(chord, sub2(m, p))) / len;
|
|
4693
|
-
const along = dot2(chord, sub2(m, p));
|
|
4694
|
-
return off <= eps && along > 0 && along < len * len;
|
|
4695
|
-
}
|
|
4696
|
-
void sources;
|
|
4697
|
-
return false;
|
|
4698
|
-
}
|
|
4699
|
-
function rebuildRing(ring, sources, eps) {
|
|
4700
|
-
const n = ring.length;
|
|
4701
|
-
let startIndex = 0;
|
|
4702
|
-
for (let k = 0; k < n; k++) {
|
|
4703
|
-
if (!mergeable(ring[(k + n - 1) % n], ring[k], sources, eps)) {
|
|
4704
|
-
startIndex = k;
|
|
4705
|
-
break;
|
|
4706
|
-
}
|
|
4707
|
-
}
|
|
4708
|
-
const ordered = [...ring.slice(startIndex), ...ring.slice(0, startIndex)];
|
|
4709
|
-
const out = [];
|
|
4710
|
-
let i = 0;
|
|
4711
|
-
while (i < ordered.length) {
|
|
4712
|
-
const first = ordered[i];
|
|
4713
|
-
let j = i + 1;
|
|
4714
|
-
while (j < ordered.length && mergeable(ordered[j - 1], ordered[j], sources, eps)) j++;
|
|
4715
|
-
const last = ordered[j - 1];
|
|
4716
|
-
if (j - i === 1) {
|
|
4717
|
-
out.push(first.pts);
|
|
4718
|
-
} else if (first.pts.length === 2 && ordered.slice(i, j).every((e) => e.pts.length === 2)) {
|
|
4719
|
-
out.push([first.from.p, last.to.p]);
|
|
4720
|
-
} else {
|
|
4721
|
-
const source = sources[first.src];
|
|
4722
|
-
out.push(snapEnds(subSegment(source.seg, first.sFrom, last.sTo), first.from.p, last.to.p));
|
|
4723
|
-
}
|
|
4724
|
-
i = j;
|
|
4725
|
-
}
|
|
4726
|
-
return out;
|
|
4727
|
-
}
|
|
4728
|
-
function extractRings(arr, isInside) {
|
|
4729
|
-
const kept = [];
|
|
4730
|
-
arr.edges.forEach((edge, i) => {
|
|
4731
|
-
const l = isInside(insideFlags2(arr.left[i], arr.fillRules));
|
|
4732
|
-
const r = isInside(insideFlags2(arr.right[i], arr.fillRules));
|
|
4733
|
-
if (l === r) return;
|
|
4734
|
-
kept.push(directed(edge, l));
|
|
4735
|
-
});
|
|
4736
|
-
const rings = [];
|
|
4737
|
-
const sliver = Math.max(arr.eps * 10, 1e-7 * arr.scale);
|
|
4738
|
-
for (const ring of traceRings2(kept, arr.scale)) {
|
|
4739
|
-
const segs = rebuildRing(ring, arr.sources, arr.eps);
|
|
4740
|
-
const area = contourSignedArea(segs);
|
|
4741
|
-
let perimeter = 0;
|
|
4742
|
-
for (const s of segs) perimeter += s.length === 2 ? dist2(s[0], s[1]) : segmentLength(s, 0, 1, 1e-6);
|
|
4743
|
-
if (!(perimeter > 0) || 2 * Math.abs(area) / perimeter < sliver) continue;
|
|
4744
|
-
rings.push({ segs, area });
|
|
4745
|
-
}
|
|
4746
|
-
return rings;
|
|
4747
|
-
}
|
|
4748
|
-
function ringSample(ring) {
|
|
4749
|
-
let best = ring.segs[0];
|
|
4750
|
-
let bestLen = -1;
|
|
4751
|
-
for (const s of ring.segs) {
|
|
4752
|
-
const l = dist2(s[0], s[s.length - 1]);
|
|
4753
|
-
if (l > bestLen) {
|
|
4754
|
-
best = s;
|
|
4755
|
-
bestLen = l;
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
return segmentPoint(best, 0.5);
|
|
4759
|
-
}
|
|
4760
|
-
function groupRings(rings) {
|
|
4761
|
-
const outers = rings.filter((r) => r.area > 0).sort((a, b) => b.area - a.area);
|
|
4762
|
-
const holes = rings.filter((r) => r.area < 0);
|
|
4763
|
-
const groups = outers.map((o) => [o]);
|
|
4764
|
-
for (const hole of holes) {
|
|
4765
|
-
const sample = ringSample(hole);
|
|
4766
|
-
for (let i = outers.length - 1; i >= 0; i--) {
|
|
4767
|
-
if (outers[i].area >= -hole.area && windingNumber([outers[i].segs], sample) !== 0) {
|
|
4768
|
-
groups[i].push(hole);
|
|
4769
|
-
break;
|
|
4770
|
-
}
|
|
4771
|
-
}
|
|
4772
|
-
}
|
|
4773
|
-
return groups;
|
|
4774
|
-
}
|
|
4775
|
-
function ringsToPath2(rings) {
|
|
4776
|
-
return contoursToPath(rings.map((r) => ({ segments: r.segs, closed: true })));
|
|
4777
|
-
}
|
|
4778
|
-
function predicate2(operation) {
|
|
4779
|
-
switch (operation) {
|
|
4780
|
-
case "union":
|
|
4781
|
-
return (f) => f.some(Boolean);
|
|
4782
|
-
case "intersection":
|
|
4783
|
-
return (f) => f.length > 0 && f.every(Boolean);
|
|
4784
|
-
case "difference":
|
|
4785
|
-
return (f) => f[0] === true && !f.slice(1).some(Boolean);
|
|
4786
|
-
case "xor":
|
|
4787
|
-
return (f) => f.filter(Boolean).length % 2 === 1;
|
|
4788
|
-
}
|
|
4789
|
-
}
|
|
4790
|
-
function flattenOperand(input, tolerance) {
|
|
4791
|
-
const path = isOperandObject(input) ? input.path : input;
|
|
4792
|
-
const fillRule = isOperandObject(input) ? input.fillRule ?? "nonzero" : "nonzero";
|
|
4793
|
-
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])));
|
|
4794
|
-
return { rings, fillRule };
|
|
4795
|
-
}
|
|
4796
|
-
function multiToPath(multi, tolerance) {
|
|
4797
|
-
if (multi.length === 0) return [];
|
|
4798
|
-
return toBezierPath(polygonToSvgPath(multi, { curves: "fit", curveTolerance: tolerance, precision: 9 }));
|
|
4799
|
-
}
|
|
4800
|
-
function reasonOf(error) {
|
|
4801
|
-
return error instanceof Error ? error.message : String(error);
|
|
4802
|
-
}
|
|
4803
|
-
function pathBoolean(operation, operands, options = {}) {
|
|
4804
|
-
if (operands.length === 0) return { path: [], engine: "curve" };
|
|
4805
|
-
try {
|
|
4806
|
-
const arr = buildArrangement(operands, options);
|
|
4807
|
-
return { path: ringsToPath2(extractRings(arr, predicate2(operation))), engine: "curve" };
|
|
4808
|
-
} catch (error) {
|
|
4809
|
-
const reason = reasonOf(error);
|
|
4810
|
-
if (reason === NON_FINITE) return { path: [], engine: "curve", error: reason };
|
|
4811
|
-
if (options.fallback === false) throw error;
|
|
4812
|
-
options.onFallback?.(reason);
|
|
4813
|
-
const tolerance = options.fallbackTolerance ?? 0.05;
|
|
4814
|
-
const multi = polygonBoolean(
|
|
4815
|
-
operation,
|
|
4816
|
-
operands.map((o) => flattenOperand(o, tolerance)),
|
|
4817
|
-
{ epsilon: options.epsilon }
|
|
4818
|
-
);
|
|
4819
|
-
return { path: multiToPath(multi, tolerance), engine: "polygon", fallbackReason: reason };
|
|
4820
|
-
}
|
|
4821
|
-
}
|
|
4822
|
-
var unitePaths = (a, b, options) => pathBoolean("union", [a, b], options).path;
|
|
4823
|
-
var intersectPaths = (a, b, options) => pathBoolean("intersection", [a, b], options).path;
|
|
4824
|
-
var subtractPaths = (a, b, options) => pathBoolean("difference", [a, b], options).path;
|
|
4825
|
-
var excludePaths = (a, b, options) => pathBoolean("xor", [a, b], options).path;
|
|
4826
|
-
function dividePaths(operands, options = {}) {
|
|
4827
|
-
if (operands.length === 0) return { pieces: [], engine: "curve" };
|
|
4828
|
-
try {
|
|
4829
|
-
const arr = buildArrangement(operands, options);
|
|
4830
|
-
const signatures = /* @__PURE__ */ new Map();
|
|
4831
|
-
const note = (windings) => {
|
|
4832
|
-
const flags = insideFlags2(windings, arr.fillRules);
|
|
4833
|
-
if (!flags.some(Boolean)) return;
|
|
4834
|
-
const key = flags.map((f) => f ? "1" : "0").join("");
|
|
4835
|
-
if (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => f ? [k] : []));
|
|
4836
|
-
};
|
|
4837
|
-
arr.edges.forEach((_, i) => {
|
|
4838
|
-
note(arr.left[i]);
|
|
4839
|
-
note(arr.right[i]);
|
|
4840
|
-
});
|
|
4841
|
-
const pieces = [];
|
|
4842
|
-
for (const key of [...signatures.keys()].sort()) {
|
|
4843
|
-
const rings = extractRings(arr, (flags) => flags.map((f) => f ? "1" : "0").join("") === key);
|
|
4844
|
-
const groups = groupRings(rings).sort((a, b) => b[0].area - a[0].area);
|
|
4845
|
-
for (const group of groups) pieces.push({ path: ringsToPath2(group), operands: signatures.get(key) });
|
|
4846
|
-
}
|
|
4847
|
-
return { pieces, engine: "curve" };
|
|
4848
|
-
} catch (error) {
|
|
4849
|
-
const reason = reasonOf(error);
|
|
4850
|
-
if (reason === NON_FINITE) return { pieces: [], engine: "curve", error: reason };
|
|
4851
|
-
if (options.fallback === false) throw error;
|
|
4852
|
-
options.onFallback?.(reason);
|
|
4853
|
-
const tolerance = options.fallbackTolerance ?? 0.05;
|
|
4854
|
-
const pieces = dividePolygons(
|
|
4855
|
-
operands.map((o) => flattenOperand(o, tolerance)),
|
|
4856
|
-
{ epsilon: options.epsilon }
|
|
4857
|
-
).map((p) => ({ path: multiToPath([p.polygon], tolerance), operands: p.operands }));
|
|
4858
|
-
return { pieces, engine: "polygon", fallbackReason: reason };
|
|
4859
|
-
}
|
|
4860
|
-
}
|
|
4861
|
-
function normalizeBezierPath(path, fillRule = "nonzero", options = {}) {
|
|
4862
|
-
return pathBoolean("union", [{ path, fillRule }], options).path;
|
|
4863
|
-
}
|
|
4864
|
-
function bezierPathArea(path, fillRule = "nonzero", options = {}) {
|
|
4865
|
-
const normalized = normalizeBezierPath(path, fillRule, options);
|
|
4866
|
-
let sum = 0;
|
|
4867
|
-
for (const contour of pathToContours(normalized)) sum += contourSignedArea(closeContour(contour, true));
|
|
4868
|
-
return Math.abs(sum);
|
|
4869
|
-
}
|
|
4870
|
-
function pathBooleanRings(operation, operands, options = {}) {
|
|
4871
|
-
const arr = buildArrangement(operands, options);
|
|
4872
|
-
const rings = extractRings(arr, typeof operation === "function" ? operation : predicate2(operation));
|
|
4873
|
-
const order = rings.map((_, i) => i).sort((a, b) => Math.abs(rings[a].area) - Math.abs(rings[b].area));
|
|
4874
|
-
return rings.map((ring, i) => {
|
|
4875
|
-
const sample = ringSample(ring);
|
|
4876
|
-
let parent = -1;
|
|
4877
|
-
for (const j of order) {
|
|
4878
|
-
if (j === i || Math.abs(rings[j].area) <= Math.abs(ring.area)) continue;
|
|
4879
|
-
if (windingNumber([rings[j].segs], sample) !== 0) {
|
|
4880
|
-
parent = j;
|
|
4881
|
-
break;
|
|
4882
|
-
}
|
|
4883
|
-
}
|
|
4884
|
-
return { segments: ring.segs, area: ring.area, parent };
|
|
4885
|
-
});
|
|
4886
|
-
}
|
|
4887
|
-
function nestedRingsToPath(rings, keep = () => true) {
|
|
4888
|
-
const contours = [];
|
|
4889
|
-
rings.forEach((r, i) => {
|
|
4890
|
-
if (keep(i)) contours.push({ segments: r.segments, closed: true });
|
|
4891
|
-
});
|
|
4892
|
-
return contoursToPath(contours);
|
|
4893
|
-
}
|
|
4894
|
-
|
|
4895
1729
|
// src/canvas/pathops/stroke.ts
|
|
4896
1730
|
var DEFAULT_TOLERANCE = 0.01;
|
|
4897
1731
|
var normal = (t) => [-t[1], t[0]];
|
|
@@ -4901,30 +1735,30 @@ function raise(seg) {
|
|
|
4901
1735
|
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];
|
|
4902
1736
|
}
|
|
4903
1737
|
function isDegenerate(seg, tol) {
|
|
4904
|
-
return seg.every((p) =>
|
|
1738
|
+
return seg.every((p) => dist(p, seg[0]) <= tol);
|
|
4905
1739
|
}
|
|
4906
1740
|
function offsetSegment(seg, d, tol, out, depth = 0) {
|
|
4907
1741
|
if (seg.length === 2) {
|
|
4908
1742
|
const n = normal(segmentTangent(seg, 0));
|
|
4909
|
-
out.push([
|
|
1743
|
+
out.push([add(seg[0], scale(n, d)), add(seg[1], scale(n, d))]);
|
|
4910
1744
|
return;
|
|
4911
1745
|
}
|
|
4912
1746
|
const c = raise(seg);
|
|
4913
1747
|
const t0 = segmentTangent(c, 0);
|
|
4914
1748
|
const t1 = segmentTangent(c, 1);
|
|
4915
|
-
const q0 =
|
|
4916
|
-
const q3 =
|
|
1749
|
+
const q0 = add(c[0], scale(normal(t0), d));
|
|
1750
|
+
const q3 = add(c[3], scale(normal(t1), d));
|
|
4917
1751
|
const k0 = segmentCurvature(c, 0);
|
|
4918
1752
|
const k1 = segmentCurvature(c, 1);
|
|
4919
1753
|
const f0 = Number.isFinite(k0) ? 1 - d * k0 : 1;
|
|
4920
1754
|
const f1 = Number.isFinite(k1) ? 1 - d * k1 : 1;
|
|
4921
|
-
const q1 =
|
|
4922
|
-
const q2 =
|
|
1755
|
+
const q1 = add(q0, scale(sub(c[1], c[0]), f0));
|
|
1756
|
+
const q2 = add(q3, scale(sub(c[2], c[3]), f1));
|
|
4923
1757
|
const approx = [q0, q1, q2, q3];
|
|
4924
1758
|
if (depth < 10) {
|
|
4925
1759
|
let err = 0;
|
|
4926
1760
|
for (const t of [0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875]) {
|
|
4927
|
-
const target =
|
|
1761
|
+
const target = add(segmentPoint(c, t), scale(normal(segmentTangent(c, t)), d));
|
|
4928
1762
|
err = Math.max(err, segmentNearest(approx, target).distance);
|
|
4929
1763
|
if (err > tol) break;
|
|
4930
1764
|
}
|
|
@@ -4968,10 +1802,10 @@ function lineTo(out, from, to) {
|
|
|
4968
1802
|
if (!samePoint(from, to)) out.push([from, to]);
|
|
4969
1803
|
}
|
|
4970
1804
|
function addJoin(out, p, tin, tout, d, style) {
|
|
4971
|
-
const from =
|
|
4972
|
-
const to =
|
|
1805
|
+
const from = add(p, scale(normal(tin), d));
|
|
1806
|
+
const to = add(p, scale(normal(tout), d));
|
|
4973
1807
|
const turn = cross(tin, tout);
|
|
4974
|
-
const cos =
|
|
1808
|
+
const cos = dot(tin, tout);
|
|
4975
1809
|
if (Math.abs(turn) < 1e-9 && cos > 0) {
|
|
4976
1810
|
lineTo(out, from, to);
|
|
4977
1811
|
return;
|
|
@@ -4985,7 +1819,7 @@ function addJoin(out, p, tin, tout, d, style) {
|
|
|
4985
1819
|
}
|
|
4986
1820
|
const theta = reversal ? d > 0 ? -Math.PI : Math.PI : Math.atan2(turn, cos);
|
|
4987
1821
|
if (style.join === "round") {
|
|
4988
|
-
const v =
|
|
1822
|
+
const v = sub(from, p);
|
|
4989
1823
|
pushArc(out, p, Math.abs(d), Math.atan2(v[1], v[0]), theta, from, to);
|
|
4990
1824
|
return;
|
|
4991
1825
|
}
|
|
@@ -4995,8 +1829,8 @@ function addJoin(out, p, tin, tout, d, style) {
|
|
|
4995
1829
|
if (ratio <= style.miterLimit) {
|
|
4996
1830
|
const nin = normal(tin);
|
|
4997
1831
|
const nout = normal(tout);
|
|
4998
|
-
const s = d / (1 +
|
|
4999
|
-
const miter =
|
|
1832
|
+
const s = d / (1 + dot(nin, nout));
|
|
1833
|
+
const miter = add(p, scale(add(nin, nout), s));
|
|
5000
1834
|
lineTo(out, from, miter);
|
|
5001
1835
|
lineTo(out, miter, to);
|
|
5002
1836
|
return;
|
|
@@ -5030,13 +1864,13 @@ function reverseChain(chain) {
|
|
|
5030
1864
|
}
|
|
5031
1865
|
function addCap(out, p, t, h, cap) {
|
|
5032
1866
|
const n = normal(t);
|
|
5033
|
-
const from =
|
|
5034
|
-
const to =
|
|
1867
|
+
const from = add(p, scale(n, h));
|
|
1868
|
+
const to = add(p, scale(n, -h));
|
|
5035
1869
|
if (cap === "round") {
|
|
5036
1870
|
pushArc(out, p, h, Math.atan2(n[1], n[0]), -Math.PI, from, to);
|
|
5037
1871
|
} else if (cap === "square") {
|
|
5038
|
-
const a =
|
|
5039
|
-
const b =
|
|
1872
|
+
const a = add(from, scale(t, h));
|
|
1873
|
+
const b = add(to, scale(t, h));
|
|
5040
1874
|
lineTo(out, from, a);
|
|
5041
1875
|
lineTo(out, a, b);
|
|
5042
1876
|
lineTo(out, b, to);
|
|
@@ -5081,7 +1915,7 @@ function strokeContour(contour, h, style, tol) {
|
|
|
5081
1915
|
if (ring.length > 0) joinTo(ring[0][0]);
|
|
5082
1916
|
return [ring];
|
|
5083
1917
|
}
|
|
5084
|
-
function
|
|
1918
|
+
function ringsToPath(rings) {
|
|
5085
1919
|
return contoursToPath(rings.filter((r) => r.length > 0).map((segments) => ({ segments, closed: true })));
|
|
5086
1920
|
}
|
|
5087
1921
|
var MAX_DASHES = 2e5;
|
|
@@ -5216,7 +2050,7 @@ function mergeSmoothRuns(path, tolerance) {
|
|
|
5216
2050
|
if (a.length !== 4 || b.length !== 4) return false;
|
|
5217
2051
|
const ta = segmentTangent(a, 1);
|
|
5218
2052
|
const tb = segmentTangent(b, 0);
|
|
5219
|
-
return
|
|
2053
|
+
return dot(ta, tb) > 0.9998 && Math.abs(cross(ta, tb)) < 0.02;
|
|
5220
2054
|
};
|
|
5221
2055
|
let start = 0;
|
|
5222
2056
|
for (let k = 0; k < n; k++) {
|
|
@@ -5267,11 +2101,11 @@ function strokeOutlineRings(input, options) {
|
|
|
5267
2101
|
const dashed = dashContours(contours, options.dash, options.dashOffset ?? 0);
|
|
5268
2102
|
if (dashed) {
|
|
5269
2103
|
contours = dashed.contours;
|
|
5270
|
-
for (const
|
|
2104
|
+
for (const dot2 of dashed.dots) rings.push(...dotRings(dot2.point, dot2.tangent, h, style.cap));
|
|
5271
2105
|
}
|
|
5272
2106
|
}
|
|
5273
2107
|
for (const contour of contours) rings.push(...strokeContour(contour, h, style, tol));
|
|
5274
|
-
return
|
|
2108
|
+
return ringsToPath(rings);
|
|
5275
2109
|
}
|
|
5276
2110
|
function outlineStroke(input, options) {
|
|
5277
2111
|
const h = options.width / 2;
|
|
@@ -5821,8 +2655,8 @@ function snapResize(rect, handle, targets, options = {}) {
|
|
|
5821
2655
|
const ratio = rect.height > 0 ? rect.width / rect.height : 0;
|
|
5822
2656
|
if (options.keepAspectRatio && ratio > 0 && (hitX || hitY)) {
|
|
5823
2657
|
if (axes.x !== 0 && axes.y !== 0) {
|
|
5824
|
-
const
|
|
5825
|
-
const useX =
|
|
2658
|
+
const dist2 = (h, horizontal) => h ? Math.abs(h.value - (horizontal ? axes.x < 0 ? x0 : x1 : axes.y < 0 ? y0 : y1)) : Infinity;
|
|
2659
|
+
const useX = dist2(hitX, true) <= dist2(hitY, false);
|
|
5826
2660
|
if (useX && hitX) {
|
|
5827
2661
|
setEdge(true, hitX.value);
|
|
5828
2662
|
const height = (nx1 - nx0) / ratio;
|
|
@@ -6201,13 +3035,13 @@ function layoutFrameInner(s, frame) {
|
|
|
6201
3035
|
let used = 0;
|
|
6202
3036
|
items.forEach((it, i) => {
|
|
6203
3037
|
const sz = it.fillMain ? it.minMain ?? 0 : it.main;
|
|
6204
|
-
const
|
|
6205
|
-
if (cur.length > 0 && used +
|
|
3038
|
+
const add2 = cur.length === 0 ? sz : Math.max(gap, 0) + sz;
|
|
3039
|
+
if (cur.length > 0 && used + add2 > innerMainFixed + 1e-6) {
|
|
6206
3040
|
lines.push(cur);
|
|
6207
3041
|
cur = [];
|
|
6208
3042
|
used = sz;
|
|
6209
3043
|
} else {
|
|
6210
|
-
used +=
|
|
3044
|
+
used += add2;
|
|
6211
3045
|
}
|
|
6212
3046
|
cur.push(i);
|
|
6213
3047
|
});
|
|
@@ -6425,11 +3259,11 @@ function resolveTracks(tracks, content, avail) {
|
|
|
6425
3259
|
let used = out.reduce((sum, v, i) => trackSpec(tracks, i).sizing === "fill" ? sum : sum + v, 0);
|
|
6426
3260
|
while (flex.length > 0) {
|
|
6427
3261
|
const total = flex.reduce((sum, i) => sum + fr(i), 0);
|
|
6428
|
-
const
|
|
6429
|
-
const open = flex.filter((i) =>
|
|
6430
|
-
const frozen = flex.filter((i) =>
|
|
3262
|
+
const unit = total > 1e-12 ? Math.max(avail - used, 0) / total : 0;
|
|
3263
|
+
const open = flex.filter((i) => unit * fr(i) >= out[i]);
|
|
3264
|
+
const frozen = flex.filter((i) => unit * fr(i) < out[i]);
|
|
6431
3265
|
if (frozen.length === 0) {
|
|
6432
|
-
for (const i of open) out[i] =
|
|
3266
|
+
for (const i of open) out[i] = unit * fr(i);
|
|
6433
3267
|
break;
|
|
6434
3268
|
}
|
|
6435
3269
|
used += frozen.reduce((sum, i) => sum + out[i], 0);
|
|
@@ -9614,8 +6448,8 @@ function sizeOf(node) {
|
|
|
9614
6448
|
return { width: node.width, height: node.height, styleWidth: s.width, styleHeight: s.height };
|
|
9615
6449
|
}
|
|
9616
6450
|
var splitGroup = (group) => {
|
|
9617
|
-
const
|
|
9618
|
-
return
|
|
6451
|
+
const dot2 = group.indexOf(".");
|
|
6452
|
+
return dot2 < 0 ? [group, ""] : [group.slice(0, dot2), group.slice(dot2 + 1)];
|
|
9619
6453
|
};
|
|
9620
6454
|
function readOverrideGroup(node, group) {
|
|
9621
6455
|
const data = dataOf(node);
|
|
@@ -9728,7 +6562,7 @@ function listGroups(node, out) {
|
|
|
9728
6562
|
if (LINK_KEYS.has(k)) continue;
|
|
9729
6563
|
const prefix = NESTED_KEYS[k];
|
|
9730
6564
|
if (prefix) {
|
|
9731
|
-
if (isRecord(v)) for (const
|
|
6565
|
+
if (isRecord(v)) for (const sub2 of Object.keys(v)) out.add(`${prefix}.${sub2}`);
|
|
9732
6566
|
continue;
|
|
9733
6567
|
}
|
|
9734
6568
|
out.add(`data.${k}`);
|
|
@@ -11041,11 +7875,11 @@ function restoreCanvasMainComponent(nodes, id, options = {}) {
|
|
|
11041
7875
|
if (g.get(inst.componentId)) return fail(nodes, "invalid", "The main component still exists.");
|
|
11042
7876
|
if (enclosingInstanceIn(g, id) !== void 0) return fail(nodes, "nested-instance", "Select the outer instance.");
|
|
11043
7877
|
const compId = inst.componentId;
|
|
11044
|
-
const
|
|
7878
|
+
const sub2 = g.subtree(id);
|
|
11045
7879
|
const map = /* @__PURE__ */ new Map([[id, compId]]);
|
|
11046
7880
|
const taken = new Set(nodes.map((n) => n.id));
|
|
11047
7881
|
taken.add(compId);
|
|
11048
|
-
for (const d of
|
|
7882
|
+
for (const d of sub2) {
|
|
11049
7883
|
if (d === id) continue;
|
|
11050
7884
|
const src = enclosingInstanceIn(g, d) === id ? getComponentSourceId(g.get(d)) : void 0;
|
|
11051
7885
|
if (src && !taken.has(src)) {
|
|
@@ -11055,7 +7889,7 @@ function restoreCanvasMainComponent(nodes, id, options = {}) {
|
|
|
11055
7889
|
}
|
|
11056
7890
|
const nested = [];
|
|
11057
7891
|
const replaced = /* @__PURE__ */ new Map();
|
|
11058
|
-
for (const d of
|
|
7892
|
+
for (const d of sub2) {
|
|
11059
7893
|
let n = g.get(d);
|
|
11060
7894
|
const nid = map.get(d) ?? d;
|
|
11061
7895
|
const parentId = n.parentId !== void 0 ? map.get(n.parentId) ?? n.parentId : void 0;
|
|
@@ -11222,15 +8056,15 @@ function addCanvasVariant(nodes, setId, options = {}) {
|
|
|
11222
8056
|
const taken = new Set(g.byId.keys());
|
|
11223
8057
|
taken.add(id);
|
|
11224
8058
|
const map = /* @__PURE__ */ new Map([[fromId, id]]);
|
|
11225
|
-
const
|
|
11226
|
-
for (const d of
|
|
8059
|
+
const sub2 = g.subtree(fromId);
|
|
8060
|
+
for (const d of sub2) {
|
|
11227
8061
|
if (d === fromId) continue;
|
|
11228
8062
|
const nid = uniqueId(`${id}-${d}`, taken);
|
|
11229
8063
|
taken.add(nid);
|
|
11230
8064
|
map.set(d, nid);
|
|
11231
8065
|
}
|
|
11232
8066
|
const order = variantOrder([...edit.variants.map((v) => v.id), id], edit.values);
|
|
11233
|
-
const subSet = new Set(
|
|
8067
|
+
const subSet = new Set(sub2);
|
|
11234
8068
|
const copies = nodes.filter((n) => subSet.has(n.id)).map((n) => {
|
|
11235
8069
|
const next = { ...n, id: map.get(n.id) };
|
|
11236
8070
|
if (n.parentId !== void 0 && n.id !== fromId) next.parentId = map.get(n.parentId) ?? n.parentId;
|
|
@@ -11789,8 +8623,8 @@ function findStrokesAtPoint(point, nodes, radius = 12) {
|
|
|
11789
8623
|
}
|
|
11790
8624
|
const localPoint = [point[0] - rect.x, point[1] - rect.y];
|
|
11791
8625
|
try {
|
|
11792
|
-
const
|
|
11793
|
-
if (
|
|
8626
|
+
const dist2 = distanceToBezierPath(shape.d, localPoint);
|
|
8627
|
+
if (dist2 <= effectiveRadius) {
|
|
11794
8628
|
matches2.push(node);
|
|
11795
8629
|
}
|
|
11796
8630
|
} catch {
|
|
@@ -12103,7 +8937,7 @@ export {
|
|
|
12103
8937
|
flattenSvgPath,
|
|
12104
8938
|
flowSide,
|
|
12105
8939
|
formatAnimationTime,
|
|
12106
|
-
|
|
8940
|
+
formatNumber,
|
|
12107
8941
|
formatOklab,
|
|
12108
8942
|
formatVariantName,
|
|
12109
8943
|
getAbsolutePosition,
|