@pixerate/editor 0.30.0 → 0.32.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 +532 -3423
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +13 -472
- package/dist/canvas/index.d.ts +13 -472
- package/dist/canvas/index.js +146 -3037
- 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 +3243 -0
- package/dist/vector/index.cjs.map +1 -0
- package/dist/vector/index.d.cts +933 -0
- package/dist/vector/index.d.ts +933 -0
- package/dist/vector/index.js +3243 -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,2998 +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/shapeGeometry.ts
|
|
2456
|
-
var SHAPE_KINDS = ["rect", "ellipse", "polygon", "star", "path"];
|
|
2457
|
-
function isShapeDescription(value) {
|
|
2458
|
-
if (!value || typeof value !== "object") return false;
|
|
2459
|
-
const kind = value.kind;
|
|
2460
|
-
if (!SHAPE_KINDS.includes(kind)) return false;
|
|
2461
|
-
return kind !== "path" || typeof value.d === "string";
|
|
2462
|
-
}
|
|
2463
|
-
var PARAM_COUNTS = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0 };
|
|
2464
|
-
var PathScanner = class {
|
|
2465
|
-
constructor(text) {
|
|
2466
|
-
this.text = text;
|
|
2467
|
-
}
|
|
2468
|
-
text;
|
|
2469
|
-
pos = 0;
|
|
2470
|
-
skip() {
|
|
2471
|
-
while (this.pos < this.text.length && /[\s,]/.test(this.text[this.pos])) this.pos++;
|
|
2472
|
-
}
|
|
2473
|
-
/** The next command letter, or `null` when a number (or the end) follows. */
|
|
2474
|
-
command() {
|
|
2475
|
-
this.skip();
|
|
2476
|
-
const ch = this.text[this.pos];
|
|
2477
|
-
if (ch && /[MmLlHhVvCcSsQqTtAaZz]/.test(ch)) {
|
|
2478
|
-
this.pos++;
|
|
2479
|
-
return ch;
|
|
2480
|
-
}
|
|
2481
|
-
return null;
|
|
2482
|
-
}
|
|
2483
|
-
done() {
|
|
2484
|
-
this.skip();
|
|
2485
|
-
return this.pos >= this.text.length;
|
|
2486
|
-
}
|
|
2487
|
-
number() {
|
|
2488
|
-
this.skip();
|
|
2489
|
-
const match = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(this.text.slice(this.pos, this.pos + 64));
|
|
2490
|
-
if (!match) return null;
|
|
2491
|
-
this.pos += match[0].length;
|
|
2492
|
-
const value = Number(match[0]);
|
|
2493
|
-
return Number.isFinite(value) ? value : null;
|
|
2494
|
-
}
|
|
2495
|
-
flag() {
|
|
2496
|
-
this.skip();
|
|
2497
|
-
const ch = this.text[this.pos];
|
|
2498
|
-
if (ch === "0" || ch === "1") {
|
|
2499
|
-
this.pos++;
|
|
2500
|
-
return ch === "1" ? 1 : 0;
|
|
2501
|
-
}
|
|
2502
|
-
return null;
|
|
2503
|
-
}
|
|
2504
|
-
};
|
|
2505
|
-
function parseSvgPath(d) {
|
|
2506
|
-
const scanner = new PathScanner(d ?? "");
|
|
2507
|
-
const commands = [];
|
|
2508
|
-
let cx = 0;
|
|
2509
|
-
let cy = 0;
|
|
2510
|
-
let startX = 0;
|
|
2511
|
-
let startY = 0;
|
|
2512
|
-
let lastControl = null;
|
|
2513
|
-
let command = "";
|
|
2514
|
-
while (!scanner.done()) {
|
|
2515
|
-
const letter = scanner.command();
|
|
2516
|
-
if (letter) {
|
|
2517
|
-
command = letter;
|
|
2518
|
-
if (command === "z" || command === "Z") {
|
|
2519
|
-
commands.push({ type: "Z" });
|
|
2520
|
-
cx = startX;
|
|
2521
|
-
cy = startY;
|
|
2522
|
-
lastControl = null;
|
|
2523
|
-
continue;
|
|
2524
|
-
}
|
|
2525
|
-
} else if (!command || command === "z" || command === "Z") {
|
|
2526
|
-
break;
|
|
2527
|
-
}
|
|
2528
|
-
const lower = command.toLowerCase();
|
|
2529
|
-
const count = PARAM_COUNTS[lower];
|
|
2530
|
-
const args = [];
|
|
2531
|
-
for (let k = 0; k < count; k++) {
|
|
2532
|
-
const value = lower === "a" && (k === 3 || k === 4) ? scanner.flag() : scanner.number();
|
|
2533
|
-
if (value === null) return commands;
|
|
2534
|
-
args.push(value);
|
|
2535
|
-
}
|
|
2536
|
-
const rel = command !== command.toUpperCase();
|
|
2537
|
-
const ox = rel ? cx : 0;
|
|
2538
|
-
const oy = rel ? cy : 0;
|
|
2539
|
-
switch (lower) {
|
|
2540
|
-
case "m": {
|
|
2541
|
-
cx = args[0] + ox;
|
|
2542
|
-
cy = args[1] + oy;
|
|
2543
|
-
startX = cx;
|
|
2544
|
-
startY = cy;
|
|
2545
|
-
commands.push({ type: "M", x: cx, y: cy });
|
|
2546
|
-
lastControl = null;
|
|
2547
|
-
command = rel ? "l" : "L";
|
|
2548
|
-
break;
|
|
2549
|
-
}
|
|
2550
|
-
case "l":
|
|
2551
|
-
cx = args[0] + ox;
|
|
2552
|
-
cy = args[1] + oy;
|
|
2553
|
-
commands.push({ type: "L", x: cx, y: cy });
|
|
2554
|
-
lastControl = null;
|
|
2555
|
-
break;
|
|
2556
|
-
case "h":
|
|
2557
|
-
cx = args[0] + (rel ? cx : 0);
|
|
2558
|
-
commands.push({ type: "L", x: cx, y: cy });
|
|
2559
|
-
lastControl = null;
|
|
2560
|
-
break;
|
|
2561
|
-
case "v":
|
|
2562
|
-
cy = args[0] + (rel ? cy : 0);
|
|
2563
|
-
commands.push({ type: "L", x: cx, y: cy });
|
|
2564
|
-
lastControl = null;
|
|
2565
|
-
break;
|
|
2566
|
-
case "c": {
|
|
2567
|
-
const c = {
|
|
2568
|
-
type: "C",
|
|
2569
|
-
x1: args[0] + ox,
|
|
2570
|
-
y1: args[1] + oy,
|
|
2571
|
-
x2: args[2] + ox,
|
|
2572
|
-
y2: args[3] + oy,
|
|
2573
|
-
x: args[4] + ox,
|
|
2574
|
-
y: args[5] + oy
|
|
2575
|
-
};
|
|
2576
|
-
commands.push(c);
|
|
2577
|
-
lastControl = { x: c.x2, y: c.y2, type: "C" };
|
|
2578
|
-
cx = c.x;
|
|
2579
|
-
cy = c.y;
|
|
2580
|
-
break;
|
|
2581
|
-
}
|
|
2582
|
-
case "s": {
|
|
2583
|
-
const x1 = lastControl?.type === "C" ? 2 * cx - lastControl.x : cx;
|
|
2584
|
-
const y1 = lastControl?.type === "C" ? 2 * cy - lastControl.y : cy;
|
|
2585
|
-
const c = {
|
|
2586
|
-
type: "C",
|
|
2587
|
-
x1,
|
|
2588
|
-
y1,
|
|
2589
|
-
x2: args[0] + ox,
|
|
2590
|
-
y2: args[1] + oy,
|
|
2591
|
-
x: args[2] + ox,
|
|
2592
|
-
y: args[3] + oy
|
|
2593
|
-
};
|
|
2594
|
-
commands.push(c);
|
|
2595
|
-
lastControl = { x: c.x2, y: c.y2, type: "C" };
|
|
2596
|
-
cx = c.x;
|
|
2597
|
-
cy = c.y;
|
|
2598
|
-
break;
|
|
2599
|
-
}
|
|
2600
|
-
case "q": {
|
|
2601
|
-
const c = { type: "Q", x1: args[0] + ox, y1: args[1] + oy, x: args[2] + ox, y: args[3] + oy };
|
|
2602
|
-
commands.push(c);
|
|
2603
|
-
lastControl = { x: c.x1, y: c.y1, type: "Q" };
|
|
2604
|
-
cx = c.x;
|
|
2605
|
-
cy = c.y;
|
|
2606
|
-
break;
|
|
2607
|
-
}
|
|
2608
|
-
case "t": {
|
|
2609
|
-
const x1 = lastControl?.type === "Q" ? 2 * cx - lastControl.x : cx;
|
|
2610
|
-
const y1 = lastControl?.type === "Q" ? 2 * cy - lastControl.y : cy;
|
|
2611
|
-
const c = { type: "Q", x1, y1, x: args[0] + ox, y: args[1] + oy };
|
|
2612
|
-
commands.push(c);
|
|
2613
|
-
lastControl = { x: x1, y: y1, type: "Q" };
|
|
2614
|
-
cx = c.x;
|
|
2615
|
-
cy = c.y;
|
|
2616
|
-
break;
|
|
2617
|
-
}
|
|
2618
|
-
case "a": {
|
|
2619
|
-
const c = {
|
|
2620
|
-
type: "A",
|
|
2621
|
-
rx: Math.abs(args[0]),
|
|
2622
|
-
ry: Math.abs(args[1]),
|
|
2623
|
-
rotation: args[2],
|
|
2624
|
-
largeArc: args[3] !== 0,
|
|
2625
|
-
sweep: args[4] !== 0,
|
|
2626
|
-
x: args[5] + ox,
|
|
2627
|
-
y: args[6] + oy
|
|
2628
|
-
};
|
|
2629
|
-
commands.push(c);
|
|
2630
|
-
lastControl = null;
|
|
2631
|
-
cx = c.x;
|
|
2632
|
-
cy = c.y;
|
|
2633
|
-
break;
|
|
2634
|
-
}
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
2637
|
-
return commands;
|
|
2638
|
-
}
|
|
2639
|
-
function segmentCount(curvature, tolerance, max) {
|
|
2640
|
-
if (!(curvature > 0)) return 1;
|
|
2641
|
-
return Math.max(1, Math.min(max, Math.ceil(Math.sqrt(curvature / tolerance))));
|
|
2642
|
-
}
|
|
2643
|
-
function flattenCubic(out, p0, x1, y1, x2, y2, x, y, tolerance, max) {
|
|
2644
|
-
const dd = Math.max(
|
|
2645
|
-
Math.hypot(p0[0] - 2 * x1 + x2, p0[1] - 2 * y1 + y2),
|
|
2646
|
-
Math.hypot(x1 - 2 * x2 + x, y1 - 2 * y2 + y)
|
|
2647
|
-
);
|
|
2648
|
-
const n = segmentCount(6 * dd / 8, tolerance, max);
|
|
2649
|
-
for (let k = 1; k <= n; k++) {
|
|
2650
|
-
const t = k / n;
|
|
2651
|
-
const mt = 1 - t;
|
|
2652
|
-
const a = mt * mt * mt;
|
|
2653
|
-
const b = 3 * mt * mt * t;
|
|
2654
|
-
const c = 3 * mt * t * t;
|
|
2655
|
-
const e = t * t * t;
|
|
2656
|
-
out.push([a * p0[0] + b * x1 + c * x2 + e * x, a * p0[1] + b * y1 + c * y2 + e * y]);
|
|
2657
|
-
}
|
|
2658
|
-
}
|
|
2659
|
-
function flattenQuadratic(out, p0, x1, y1, x, y, tolerance, max) {
|
|
2660
|
-
const dd = Math.hypot(p0[0] - 2 * x1 + x, p0[1] - 2 * y1 + y);
|
|
2661
|
-
const n = segmentCount(2 * dd / 8, tolerance, max);
|
|
2662
|
-
for (let k = 1; k <= n; k++) {
|
|
2663
|
-
const t = k / n;
|
|
2664
|
-
const mt = 1 - t;
|
|
2665
|
-
out.push([mt * mt * p0[0] + 2 * mt * t * x1 + t * t * x, mt * mt * p0[1] + 2 * mt * t * y1 + t * t * y]);
|
|
2666
|
-
}
|
|
2667
|
-
}
|
|
2668
|
-
function arcSteps(radius, sweep, tolerance, max) {
|
|
2669
|
-
if (radius <= tolerance) return Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2)));
|
|
2670
|
-
const step = 2 * Math.acos(1 - tolerance / radius);
|
|
2671
|
-
return Math.max(1, Math.min(max, Math.ceil(Math.abs(sweep) / step)));
|
|
2672
|
-
}
|
|
2673
|
-
function flattenArc(out, p0, cmd, tolerance, max) {
|
|
2674
|
-
const [x0, y0] = p0;
|
|
2675
|
-
let { rx, ry } = cmd;
|
|
2676
|
-
const { x, y } = cmd;
|
|
2677
|
-
if (x0 === x && y0 === y) return;
|
|
2678
|
-
if (rx === 0 || ry === 0) {
|
|
2679
|
-
out.push([x, y]);
|
|
2680
|
-
return;
|
|
2681
|
-
}
|
|
2682
|
-
const phi = cmd.rotation * Math.PI / 180;
|
|
2683
|
-
const cos = Math.cos(phi);
|
|
2684
|
-
const sin = Math.sin(phi);
|
|
2685
|
-
const dx = (x0 - x) / 2;
|
|
2686
|
-
const dy = (y0 - y) / 2;
|
|
2687
|
-
const x1p = cos * dx + sin * dy;
|
|
2688
|
-
const y1p = -sin * dx + cos * dy;
|
|
2689
|
-
const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
|
|
2690
|
-
if (lambda > 1) {
|
|
2691
|
-
const s = Math.sqrt(lambda);
|
|
2692
|
-
rx *= s;
|
|
2693
|
-
ry *= s;
|
|
2694
|
-
}
|
|
2695
|
-
const num2 = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
|
|
2696
|
-
const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
|
|
2697
|
-
let coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num2 / den));
|
|
2698
|
-
if (cmd.largeArc === cmd.sweep) coef = -coef;
|
|
2699
|
-
const cxp = coef * rx * y1p / ry;
|
|
2700
|
-
const cyp = -coef * ry * x1p / rx;
|
|
2701
|
-
const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
|
|
2702
|
-
const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
|
|
2703
|
-
const angle = (ux, uy, vx, vy) => {
|
|
2704
|
-
const a = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
2705
|
-
return a;
|
|
2706
|
-
};
|
|
2707
|
-
const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
|
|
2708
|
-
let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
|
|
2709
|
-
if (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;
|
|
2710
|
-
else if (cmd.sweep && delta < 0) delta += 2 * Math.PI;
|
|
2711
|
-
const n = arcSteps(Math.max(rx, ry), delta, tolerance, max);
|
|
2712
|
-
for (let k = 1; k < n; k++) {
|
|
2713
|
-
const t = theta1 + delta * k / n;
|
|
2714
|
-
const ex = rx * Math.cos(t);
|
|
2715
|
-
const ey = ry * Math.sin(t);
|
|
2716
|
-
out.push([cos * ex - sin * ey + cx, sin * ex + cos * ey + cy]);
|
|
2717
|
-
}
|
|
2718
|
-
out.push([x, y]);
|
|
2719
|
-
}
|
|
2720
|
-
function flattenSvgPath(d, options = {}) {
|
|
2721
|
-
const tolerance = options.tolerance && options.tolerance > 0 ? options.tolerance : 0.25;
|
|
2722
|
-
const max = options.maxSegments ?? 512;
|
|
2723
|
-
const commands = typeof d === "string" ? parseSvgPath(d) : d;
|
|
2724
|
-
const rings = [];
|
|
2725
|
-
let current = [];
|
|
2726
|
-
let start = [0, 0];
|
|
2727
|
-
const flush = () => {
|
|
2728
|
-
if (current.length >= 3) rings.push(current);
|
|
2729
|
-
current = [];
|
|
2730
|
-
};
|
|
2731
|
-
const last = () => current[current.length - 1] ?? start;
|
|
2732
|
-
for (const cmd of commands) {
|
|
2733
|
-
switch (cmd.type) {
|
|
2734
|
-
case "M":
|
|
2735
|
-
flush();
|
|
2736
|
-
start = [cmd.x, cmd.y];
|
|
2737
|
-
current = [start];
|
|
2738
|
-
break;
|
|
2739
|
-
case "L":
|
|
2740
|
-
if (current.length === 0) current = [start];
|
|
2741
|
-
current.push([cmd.x, cmd.y]);
|
|
2742
|
-
break;
|
|
2743
|
-
case "C":
|
|
2744
|
-
if (current.length === 0) current = [start];
|
|
2745
|
-
flattenCubic(current, last(), cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y, tolerance, max);
|
|
2746
|
-
break;
|
|
2747
|
-
case "Q":
|
|
2748
|
-
if (current.length === 0) current = [start];
|
|
2749
|
-
flattenQuadratic(current, last(), cmd.x1, cmd.y1, cmd.x, cmd.y, tolerance, max);
|
|
2750
|
-
break;
|
|
2751
|
-
case "A":
|
|
2752
|
-
if (current.length === 0) current = [start];
|
|
2753
|
-
flattenArc(current, last(), cmd, tolerance, max);
|
|
2754
|
-
break;
|
|
2755
|
-
case "Z":
|
|
2756
|
-
flush();
|
|
2757
|
-
break;
|
|
2758
|
-
}
|
|
2759
|
-
}
|
|
2760
|
-
flush();
|
|
2761
|
-
return rings.map(dedupeRing).filter((r) => r.length >= 3);
|
|
2762
|
-
}
|
|
2763
|
-
function dedupeRing(ring) {
|
|
2764
|
-
const out = [];
|
|
2765
|
-
for (const p of ring) {
|
|
2766
|
-
const prev = out[out.length - 1];
|
|
2767
|
-
if (!prev || prev[0] !== p[0] || prev[1] !== p[1]) out.push(p);
|
|
2768
|
-
}
|
|
2769
|
-
while (out.length > 1 && out[0][0] === out[out.length - 1][0] && out[0][1] === out[out.length - 1][1]) out.pop();
|
|
2770
|
-
return out;
|
|
2771
|
-
}
|
|
2772
|
-
function cornerRadii(shape, width, height) {
|
|
2773
|
-
const raw = shape.cornerRadius ?? 0;
|
|
2774
|
-
let radii = (typeof raw === "number" ? [raw, raw, raw, raw] : [...raw]).map(
|
|
2775
|
-
(r) => Number.isFinite(r) && r > 0 ? r : 0
|
|
2776
|
-
);
|
|
2777
|
-
const factor = Math.min(
|
|
2778
|
-
1,
|
|
2779
|
-
width / Math.max(radii[0] + radii[1], 1e-12),
|
|
2780
|
-
height / Math.max(radii[1] + radii[2], 1e-12),
|
|
2781
|
-
width / Math.max(radii[2] + radii[3], 1e-12),
|
|
2782
|
-
height / Math.max(radii[3] + radii[0], 1e-12)
|
|
2783
|
-
);
|
|
2784
|
-
if (factor < 1) radii = radii.map((r) => r * factor);
|
|
2785
|
-
return radii;
|
|
2786
|
-
}
|
|
2787
|
-
function polygonVertices(count, inner, width, height) {
|
|
2788
|
-
const raw = [];
|
|
2789
|
-
const total = inner === null ? count : count * 2;
|
|
2790
|
-
for (let k = 0; k < total; k++) {
|
|
2791
|
-
const angle = -Math.PI / 2 + k * 2 * Math.PI / total;
|
|
2792
|
-
const r = inner !== null && k % 2 === 1 ? inner : 1;
|
|
2793
|
-
raw.push([r * Math.cos(angle), r * Math.sin(angle)]);
|
|
2794
|
-
}
|
|
2795
|
-
let minX = Infinity;
|
|
2796
|
-
let minY = Infinity;
|
|
2797
|
-
let maxX = -Infinity;
|
|
2798
|
-
let maxY = -Infinity;
|
|
2799
|
-
for (const [x, y] of raw) {
|
|
2800
|
-
minX = Math.min(minX, x);
|
|
2801
|
-
minY = Math.min(minY, y);
|
|
2802
|
-
maxX = Math.max(maxX, x);
|
|
2803
|
-
maxY = Math.max(maxY, y);
|
|
2804
|
-
}
|
|
2805
|
-
const sx = width / (maxX - minX || 1);
|
|
2806
|
-
const sy = height / (maxY - minY || 1);
|
|
2807
|
-
return raw.map(([x, y]) => [(x - minX) * sx, (y - minY) * sy]);
|
|
2808
|
-
}
|
|
2809
|
-
function rotationTransform(shape, size) {
|
|
2810
|
-
const deg = shape.rotation ?? 0;
|
|
2811
|
-
if (!deg || !Number.isFinite(deg)) return null;
|
|
2812
|
-
const rad = deg * Math.PI / 180;
|
|
2813
|
-
const cos = Math.cos(rad);
|
|
2814
|
-
const sin = Math.sin(rad);
|
|
2815
|
-
const cx = size.width / 2;
|
|
2816
|
-
const cy = size.height / 2;
|
|
2817
|
-
return ([x, y]) => [cx + (x - cx) * cos - (y - cy) * sin, cy + (x - cx) * sin + (y - cy) * cos];
|
|
2818
|
-
}
|
|
2819
|
-
function getShapeSvg(shape, size) {
|
|
2820
|
-
const { width: w, height: h } = size;
|
|
2821
|
-
const f = (n) => formatNumber2(n, 3);
|
|
2822
|
-
const transforms = [];
|
|
2823
|
-
if (shape.rotation) transforms.push(`rotate(${f(shape.rotation)} ${f(w / 2)} ${f(h / 2)})`);
|
|
2824
|
-
let d = "";
|
|
2825
|
-
let fillRule = "nonzero";
|
|
2826
|
-
switch (shape.kind) {
|
|
2827
|
-
case "rect": {
|
|
2828
|
-
const [tl, tr, br, bl] = cornerRadii(shape, w, h);
|
|
2829
|
-
if (tl + tr + br + bl === 0) {
|
|
2830
|
-
d = `M0 0H${f(w)}V${f(h)}H0Z`;
|
|
2831
|
-
} else {
|
|
2832
|
-
const arc = (r, x, y) => r > 0 ? `A${f(r)} ${f(r)} 0 0 1 ${f(x)} ${f(y)}` : "";
|
|
2833
|
-
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`;
|
|
2834
|
-
}
|
|
2835
|
-
break;
|
|
2836
|
-
}
|
|
2837
|
-
case "ellipse": {
|
|
2838
|
-
const rx = w / 2;
|
|
2839
|
-
const ry = h / 2;
|
|
2840
|
-
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`;
|
|
2841
|
-
break;
|
|
2842
|
-
}
|
|
2843
|
-
case "polygon":
|
|
2844
|
-
case "star": {
|
|
2845
|
-
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);
|
|
2846
|
-
d = ringsToPath([points], f);
|
|
2847
|
-
break;
|
|
2848
|
-
}
|
|
2849
|
-
case "path": {
|
|
2850
|
-
d = shape.d;
|
|
2851
|
-
fillRule = shape.fillRule ?? "nonzero";
|
|
2852
|
-
const vb = validViewBox(shape.viewBox);
|
|
2853
|
-
if (vb) {
|
|
2854
|
-
transforms.push(
|
|
2855
|
-
`scale(${f(w / vb[2])} ${f(h / vb[3])})`,
|
|
2856
|
-
`translate(${f(-vb[0])} ${f(-vb[1])})`
|
|
2857
|
-
);
|
|
2858
|
-
}
|
|
2859
|
-
break;
|
|
2860
|
-
}
|
|
2861
|
-
}
|
|
2862
|
-
return transforms.length > 0 ? { d, transform: transforms.join(" "), fillRule } : { d, fillRule };
|
|
2863
|
-
}
|
|
2864
|
-
function getShapeRenderData(shape, size = {}) {
|
|
2865
|
-
const vb = shape.kind === "path" ? validViewBox(shape.viewBox) : null;
|
|
2866
|
-
const width = size.width && size.width > 0 ? size.width : vb ? vb[2] : 100;
|
|
2867
|
-
const height = size.height && size.height > 0 ? size.height : vb ? vb[3] : 100;
|
|
2868
|
-
const svg = getShapeSvg(shape, { width, height });
|
|
2869
|
-
const data = {
|
|
2870
|
-
viewBox: `0 0 ${formatNumber2(width, 3)} ${formatNumber2(height, 3)}`,
|
|
2871
|
-
width,
|
|
2872
|
-
height,
|
|
2873
|
-
d: svg.d,
|
|
2874
|
-
fillRule: svg.fillRule,
|
|
2875
|
-
fill: shape.fill ?? "currentColor",
|
|
2876
|
-
stroke: shape.stroke ?? "none",
|
|
2877
|
-
strokeWidth: shape.strokeWidth ?? 1
|
|
2878
|
-
};
|
|
2879
|
-
if (svg.transform) data.transform = svg.transform;
|
|
2880
|
-
if (shape.opacity !== void 0) data.opacity = shape.opacity;
|
|
2881
|
-
return data;
|
|
2882
|
-
}
|
|
2883
|
-
function clampRatio(value) {
|
|
2884
|
-
if (value === void 0 || !Number.isFinite(value)) return 0.5;
|
|
2885
|
-
return Math.min(1, Math.max(0.01, value));
|
|
2886
|
-
}
|
|
2887
|
-
function validViewBox(vb) {
|
|
2888
|
-
if (!vb || vb.length !== 4 || !vb.every(Number.isFinite) || vb[2] <= 0 || vb[3] <= 0) return null;
|
|
2889
|
-
return vb;
|
|
2890
|
-
}
|
|
2891
|
-
function shapeToPolygon(shape, size, options = {}) {
|
|
2892
|
-
const { width: w, height: h } = size;
|
|
2893
|
-
if (!(w > 0) || !(h > 0)) return { rings: [], fillRule: "nonzero" };
|
|
2894
|
-
let rings;
|
|
2895
|
-
let fillRule = "nonzero";
|
|
2896
|
-
if (shape.kind === "path") {
|
|
2897
|
-
fillRule = shape.fillRule ?? "nonzero";
|
|
2898
|
-
const vb = validViewBox(shape.viewBox);
|
|
2899
|
-
if (vb) {
|
|
2900
|
-
const sx = w / vb[2];
|
|
2901
|
-
const sy = h / vb[3];
|
|
2902
|
-
const tolerance = (options.tolerance ?? 0.25) / Math.max(sx, sy);
|
|
2903
|
-
rings = flattenSvgPath(shape.d, { ...options, tolerance }).map(
|
|
2904
|
-
(ring) => ring.map(([x, y]) => [(x - vb[0]) * sx, (y - vb[1]) * sy])
|
|
2905
|
-
);
|
|
2906
|
-
} else {
|
|
2907
|
-
rings = flattenSvgPath(shape.d, options);
|
|
2908
|
-
}
|
|
2909
|
-
} else {
|
|
2910
|
-
const { d } = getShapeSvg({ ...shape, rotation: 0 }, size);
|
|
2911
|
-
rings = flattenSvgPath(d, options);
|
|
2912
|
-
}
|
|
2913
|
-
const rotate = rotationTransform(shape, size);
|
|
2914
|
-
if (rotate) rings = rings.map((ring) => ring.map(rotate));
|
|
2915
|
-
return { rings, fillRule };
|
|
2916
|
-
}
|
|
2917
|
-
function formatNumber2(value, precision = 3) {
|
|
2918
|
-
const factor = 10 ** precision;
|
|
2919
|
-
const rounded = Math.round(value * factor) / factor;
|
|
2920
|
-
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
2921
|
-
}
|
|
2922
|
-
function ringsToPath(rings, f) {
|
|
2923
|
-
let d = "";
|
|
2924
|
-
for (const ring of rings) {
|
|
2925
|
-
if (ring.length < 2) continue;
|
|
2926
|
-
d += `M${f(ring[0][0])} ${f(ring[0][1])}`;
|
|
2927
|
-
for (let i = 1; i < ring.length; i++) d += `L${f(ring[i][0])} ${f(ring[i][1])}`;
|
|
2928
|
-
d += "Z";
|
|
2929
|
-
}
|
|
2930
|
-
return d;
|
|
2931
|
-
}
|
|
2932
|
-
function segmentDistance(p, a, b) {
|
|
2933
|
-
const dx = b[0] - a[0];
|
|
2934
|
-
const dy = b[1] - a[1];
|
|
2935
|
-
const len2 = dx * dx + dy * dy;
|
|
2936
|
-
if (len2 === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);
|
|
2937
|
-
const t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));
|
|
2938
|
-
return Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));
|
|
2939
|
-
}
|
|
2940
|
-
function rdp(points, tolerance) {
|
|
2941
|
-
if (points.length <= 2) return points;
|
|
2942
|
-
const keep = new Uint8Array(points.length);
|
|
2943
|
-
keep[0] = 1;
|
|
2944
|
-
keep[points.length - 1] = 1;
|
|
2945
|
-
const stack = [[0, points.length - 1]];
|
|
2946
|
-
while (stack.length) {
|
|
2947
|
-
const [s, e] = stack.pop();
|
|
2948
|
-
let maxDist = -1;
|
|
2949
|
-
let index = -1;
|
|
2950
|
-
for (let i = s + 1; i < e; i++) {
|
|
2951
|
-
const dist3 = segmentDistance(points[i], points[s], points[e]);
|
|
2952
|
-
if (dist3 > maxDist) {
|
|
2953
|
-
maxDist = dist3;
|
|
2954
|
-
index = i;
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
if (index !== -1 && maxDist > tolerance) {
|
|
2958
|
-
keep[index] = 1;
|
|
2959
|
-
stack.push([s, index], [index, e]);
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
return points.filter((_, i) => keep[i]);
|
|
2963
|
-
}
|
|
2964
|
-
function simplifyRing(ring, tolerance) {
|
|
2965
|
-
if (ring.length <= 3 || !(tolerance > 0)) return [...ring];
|
|
2966
|
-
let far = 0;
|
|
2967
|
-
let farDist = -1;
|
|
2968
|
-
for (let i = 1; i < ring.length; i++) {
|
|
2969
|
-
const dist3 = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);
|
|
2970
|
-
if (dist3 > farDist) {
|
|
2971
|
-
farDist = dist3;
|
|
2972
|
-
far = i;
|
|
2973
|
-
}
|
|
2974
|
-
}
|
|
2975
|
-
const first = rdp(ring.slice(0, far + 1), tolerance);
|
|
2976
|
-
const second = rdp([...ring.slice(far), ring[0]], tolerance);
|
|
2977
|
-
const out = [...first.slice(0, -1), ...second.slice(0, -1)];
|
|
2978
|
-
return out.length >= 3 ? out : [];
|
|
2979
|
-
}
|
|
2980
|
-
function polygonToSvgPath(input, options = {}) {
|
|
2981
|
-
const precision = options.precision ?? 3;
|
|
2982
|
-
const f = (n) => formatNumber2(n, precision);
|
|
2983
|
-
const dx = options.offset?.x ?? 0;
|
|
2984
|
-
const dy = options.offset?.y ?? 0;
|
|
2985
|
-
const rings = [];
|
|
2986
|
-
const fit = options.curves === "fit";
|
|
2987
|
-
let fitted = "";
|
|
2988
|
-
for (const item of input) {
|
|
2989
|
-
const list = item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0]) ? item : [item];
|
|
2990
|
-
for (const ring of list) {
|
|
2991
|
-
let r = ring;
|
|
2992
|
-
if (options.simplify && options.simplify > 0) r = simplifyRing(r, options.simplify);
|
|
2993
|
-
if (r.length < 3) continue;
|
|
2994
|
-
if (fit) {
|
|
2995
|
-
fitted += fitPathData(r, {
|
|
2996
|
-
tolerance: options.curveTolerance ?? 0.25,
|
|
2997
|
-
cornerAngleDeg: options.cornerAngleDeg,
|
|
2998
|
-
precision,
|
|
2999
|
-
offset: { x: dx, y: dy }
|
|
3000
|
-
});
|
|
3001
|
-
continue;
|
|
3002
|
-
}
|
|
3003
|
-
rings.push(dx || dy ? r.map(([x, y]) => [x + dx, y + dy]) : r);
|
|
3004
|
-
}
|
|
3005
|
-
}
|
|
3006
|
-
return fit ? fitted : ringsToPath(rings, f);
|
|
3007
|
-
}
|
|
3008
|
-
|
|
3009
|
-
// src/canvas/pathops/bezier.ts
|
|
3010
|
-
var sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
3011
|
-
var add2 = (a, b) => [a[0] + b[0], a[1] + b[1]];
|
|
3012
|
-
var scale = (a, s) => [a[0] * s, a[1] * s];
|
|
3013
|
-
var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
3014
|
-
var cross = (a, b) => a[0] * b[1] - a[1] * b[0];
|
|
3015
|
-
var dist2 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
3016
|
-
var lerp = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
3017
|
-
var samePoint = (a, b) => a[0] === b[0] && a[1] === b[1];
|
|
3018
|
-
function unit2(v) {
|
|
3019
|
-
const l = Math.hypot(v[0], v[1]);
|
|
3020
|
-
return l > 0 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
|
|
3021
|
-
}
|
|
3022
|
-
function segmentPoint(seg, t) {
|
|
3023
|
-
const n = seg.length;
|
|
3024
|
-
if (n === 2) return lerp(seg[0], seg[1], t);
|
|
3025
|
-
const mt = 1 - t;
|
|
3026
|
-
if (n === 3) {
|
|
3027
|
-
const a2 = mt * mt;
|
|
3028
|
-
const b2 = 2 * mt * t;
|
|
3029
|
-
const c2 = t * t;
|
|
3030
|
-
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]];
|
|
3031
|
-
}
|
|
3032
|
-
const a = mt * mt * mt;
|
|
3033
|
-
const b = 3 * mt * mt * t;
|
|
3034
|
-
const c = 3 * mt * t * t;
|
|
3035
|
-
const d = t * t * t;
|
|
3036
|
-
return [
|
|
3037
|
-
a * seg[0][0] + b * seg[1][0] + c * seg[2][0] + d * seg[3][0],
|
|
3038
|
-
a * seg[0][1] + b * seg[1][1] + c * seg[2][1] + d * seg[3][1]
|
|
3039
|
-
];
|
|
3040
|
-
}
|
|
3041
|
-
function segmentDerivative(seg, t) {
|
|
3042
|
-
const n = seg.length;
|
|
3043
|
-
if (n === 2) return sub2(seg[1], seg[0]);
|
|
3044
|
-
if (n === 3) {
|
|
3045
|
-
const a2 = sub2(seg[1], seg[0]);
|
|
3046
|
-
const b2 = sub2(seg[2], seg[1]);
|
|
3047
|
-
return [2 * ((1 - t) * a2[0] + t * b2[0]), 2 * ((1 - t) * a2[1] + t * b2[1])];
|
|
3048
|
-
}
|
|
3049
|
-
const a = sub2(seg[1], seg[0]);
|
|
3050
|
-
const b = sub2(seg[2], seg[1]);
|
|
3051
|
-
const c = sub2(seg[3], seg[2]);
|
|
3052
|
-
const mt = 1 - t;
|
|
3053
|
-
return [
|
|
3054
|
-
3 * (mt * mt * a[0] + 2 * mt * t * b[0] + t * t * c[0]),
|
|
3055
|
-
3 * (mt * mt * a[1] + 2 * mt * t * b[1] + t * t * c[1])
|
|
3056
|
-
];
|
|
3057
|
-
}
|
|
3058
|
-
function segmentSecondDerivative(seg, t) {
|
|
3059
|
-
const n = seg.length;
|
|
3060
|
-
if (n === 2) return [0, 0];
|
|
3061
|
-
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])];
|
|
3062
|
-
const a = [seg[2][0] - 2 * seg[1][0] + seg[0][0], seg[2][1] - 2 * seg[1][1] + seg[0][1]];
|
|
3063
|
-
const b = [seg[3][0] - 2 * seg[2][0] + seg[1][0], seg[3][1] - 2 * seg[2][1] + seg[1][1]];
|
|
3064
|
-
return [6 * ((1 - t) * a[0] + t * b[0]), 6 * ((1 - t) * a[1] + t * b[1])];
|
|
3065
|
-
}
|
|
3066
|
-
function segmentTangent(seg, t) {
|
|
3067
|
-
const d = unit2(segmentDerivative(seg, t));
|
|
3068
|
-
if (d) return d;
|
|
3069
|
-
const h = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
|
|
3070
|
-
const e = unit2(segmentDerivative(seg, h));
|
|
3071
|
-
if (e) return e;
|
|
3072
|
-
return unit2(sub2(seg[seg.length - 1], seg[0])) ?? [1, 0];
|
|
3073
|
-
}
|
|
3074
|
-
function segmentCurvature(seg, t) {
|
|
3075
|
-
if (seg.length === 2) return 0;
|
|
3076
|
-
let d = segmentDerivative(seg, t);
|
|
3077
|
-
let tt = t;
|
|
3078
|
-
if (Math.hypot(d[0], d[1]) < 1e-12) {
|
|
3079
|
-
tt = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
|
|
3080
|
-
d = segmentDerivative(seg, tt);
|
|
3081
|
-
}
|
|
3082
|
-
const dd = segmentSecondDerivative(seg, tt);
|
|
3083
|
-
const len = Math.hypot(d[0], d[1]);
|
|
3084
|
-
if (!(len > 0)) return 0;
|
|
3085
|
-
return cross(d, dd) / (len * len * len);
|
|
3086
|
-
}
|
|
3087
|
-
function splitSegment(seg, t) {
|
|
3088
|
-
const n = seg.length;
|
|
3089
|
-
if (n === 2) {
|
|
3090
|
-
const m2 = lerp(seg[0], seg[1], t);
|
|
3091
|
-
return [
|
|
3092
|
-
[seg[0], m2],
|
|
3093
|
-
[m2, seg[1]]
|
|
3094
|
-
];
|
|
3095
|
-
}
|
|
3096
|
-
if (n === 3) {
|
|
3097
|
-
const a2 = lerp(seg[0], seg[1], t);
|
|
3098
|
-
const b2 = lerp(seg[1], seg[2], t);
|
|
3099
|
-
const m2 = lerp(a2, b2, t);
|
|
3100
|
-
return [
|
|
3101
|
-
[seg[0], a2, m2],
|
|
3102
|
-
[m2, b2, seg[2]]
|
|
3103
|
-
];
|
|
3104
|
-
}
|
|
3105
|
-
const a = lerp(seg[0], seg[1], t);
|
|
3106
|
-
const b = lerp(seg[1], seg[2], t);
|
|
3107
|
-
const c = lerp(seg[2], seg[3], t);
|
|
3108
|
-
const ab = lerp(a, b, t);
|
|
3109
|
-
const bc = lerp(b, c, t);
|
|
3110
|
-
const m = lerp(ab, bc, t);
|
|
3111
|
-
return [
|
|
3112
|
-
[seg[0], a, ab, m],
|
|
3113
|
-
[m, bc, c, seg[3]]
|
|
3114
|
-
];
|
|
3115
|
-
}
|
|
3116
|
-
function subSegment(seg, t0, t1) {
|
|
3117
|
-
if (t1 < t0) return subSegment(seg, t1, t0).reverse();
|
|
3118
|
-
if (t0 <= 0 && t1 >= 1) return [...seg];
|
|
3119
|
-
let s = [...seg];
|
|
3120
|
-
if (t1 < 1) s = splitSegment(s, t1)[0];
|
|
3121
|
-
if (t0 > 0) s = splitSegment(s, t1 > 0 ? t0 / t1 : 0)[1];
|
|
3122
|
-
return s;
|
|
3123
|
-
}
|
|
3124
|
-
function isStraightSegment(seg, tol) {
|
|
3125
|
-
if (seg.length === 2) return true;
|
|
3126
|
-
const a = seg[0];
|
|
3127
|
-
const b = seg[seg.length - 1];
|
|
3128
|
-
const chord = sub2(b, a);
|
|
3129
|
-
const len = Math.hypot(chord[0], chord[1]);
|
|
3130
|
-
for (let i = 1; i < seg.length - 1; i++) {
|
|
3131
|
-
const v = sub2(seg[i], a);
|
|
3132
|
-
if (len === 0) {
|
|
3133
|
-
if (Math.hypot(v[0], v[1]) > tol) return false;
|
|
3134
|
-
continue;
|
|
3135
|
-
}
|
|
3136
|
-
if (Math.abs(cross(chord, v)) / len > tol) return false;
|
|
3137
|
-
const along = dot2(chord, v) / len;
|
|
3138
|
-
if (along < -tol || along > len + tol) return false;
|
|
3139
|
-
}
|
|
3140
|
-
return true;
|
|
3141
|
-
}
|
|
3142
|
-
function quadraticRoots(a, b, c, out) {
|
|
3143
|
-
const push = (t) => {
|
|
3144
|
-
if (t > 1e-12 && t < 1 - 1e-12) out.push(t);
|
|
3145
|
-
};
|
|
3146
|
-
const scaleAbs = Math.max(Math.abs(a), Math.abs(b), Math.abs(c));
|
|
3147
|
-
if (scaleAbs === 0) return;
|
|
3148
|
-
if (Math.abs(a) <= 1e-12 * scaleAbs) {
|
|
3149
|
-
if (b !== 0) push(-c / b);
|
|
3150
|
-
return;
|
|
3151
|
-
}
|
|
3152
|
-
const disc = b * b - 4 * a * c;
|
|
3153
|
-
if (disc < 0) return;
|
|
3154
|
-
const sq = Math.sqrt(disc);
|
|
3155
|
-
const q = -0.5 * (b + (b >= 0 ? sq : -sq));
|
|
3156
|
-
push(q / a);
|
|
3157
|
-
if (q !== 0) push(c / q);
|
|
3158
|
-
}
|
|
3159
|
-
function segmentExtrema(seg, axis) {
|
|
3160
|
-
const out = [];
|
|
3161
|
-
if (seg.length === 3) {
|
|
3162
|
-
const d = seg[0][axis] - 2 * seg[1][axis] + seg[2][axis];
|
|
3163
|
-
if (d !== 0) {
|
|
3164
|
-
const t = (seg[0][axis] - seg[1][axis]) / d;
|
|
3165
|
-
if (t > 1e-12 && t < 1 - 1e-12) out.push(t);
|
|
3166
|
-
}
|
|
3167
|
-
} else if (seg.length === 4) {
|
|
3168
|
-
const p0 = seg[0][axis];
|
|
3169
|
-
const p1 = seg[1][axis];
|
|
3170
|
-
const p2 = seg[2][axis];
|
|
3171
|
-
const p3 = seg[3][axis];
|
|
3172
|
-
const a = -p0 + 3 * p1 - 3 * p2 + p3;
|
|
3173
|
-
const b = 2 * (p0 - 2 * p1 + p2);
|
|
3174
|
-
const c = p1 - p0;
|
|
3175
|
-
quadraticRoots(a, b, c, out);
|
|
3176
|
-
}
|
|
3177
|
-
return out.sort((x, y) => x - y);
|
|
3178
|
-
}
|
|
3179
|
-
function segmentBounds(seg) {
|
|
3180
|
-
const a = seg[0];
|
|
3181
|
-
const b = seg[seg.length - 1];
|
|
3182
|
-
let minX = Math.min(a[0], b[0]);
|
|
3183
|
-
let maxX = Math.max(a[0], b[0]);
|
|
3184
|
-
let minY = Math.min(a[1], b[1]);
|
|
3185
|
-
let maxY = Math.max(a[1], b[1]);
|
|
3186
|
-
for (const axis of [0, 1]) {
|
|
3187
|
-
for (const t of segmentExtrema(seg, axis)) {
|
|
3188
|
-
const p = segmentPoint(seg, t);
|
|
3189
|
-
if (axis === 0) {
|
|
3190
|
-
minX = Math.min(minX, p[0]);
|
|
3191
|
-
maxX = Math.max(maxX, p[0]);
|
|
3192
|
-
} else {
|
|
3193
|
-
minY = Math.min(minY, p[1]);
|
|
3194
|
-
maxY = Math.max(maxY, p[1]);
|
|
3195
|
-
}
|
|
3196
|
-
}
|
|
3197
|
-
}
|
|
3198
|
-
return { minX, minY, maxX, maxY };
|
|
3199
|
-
}
|
|
3200
|
-
function monotoneParams(seg) {
|
|
3201
|
-
if (seg.length === 2) return [];
|
|
3202
|
-
const ts = [...segmentExtrema(seg, 0), ...segmentExtrema(seg, 1)].sort((a, b) => a - b);
|
|
3203
|
-
const out = [];
|
|
3204
|
-
for (const t of ts) if (out.length === 0 || t - out[out.length - 1] > 1e-9) out.push(t);
|
|
3205
|
-
return out;
|
|
3206
|
-
}
|
|
3207
|
-
function segmentAreaTerm(seg) {
|
|
3208
|
-
if (seg.length === 2) return (seg[0][0] * seg[1][1] - seg[1][0] * seg[0][1]) / 2;
|
|
3209
|
-
const nodes = [-Math.sqrt(3 / 5), 0, Math.sqrt(3 / 5)];
|
|
3210
|
-
const weights = [5 / 9, 8 / 9, 5 / 9];
|
|
3211
|
-
let sum = 0;
|
|
3212
|
-
for (let i = 0; i < 3; i++) {
|
|
3213
|
-
const t = (nodes[i] + 1) / 2;
|
|
3214
|
-
const p = segmentPoint(seg, t);
|
|
3215
|
-
const d = segmentDerivative(seg, t);
|
|
3216
|
-
sum += weights[i] * (p[0] * d[1] - p[1] * d[0]);
|
|
3217
|
-
}
|
|
3218
|
-
return sum / 4;
|
|
3219
|
-
}
|
|
3220
|
-
var GL_X = [
|
|
3221
|
-
-0.9602898564975363,
|
|
3222
|
-
-0.7966664774136267,
|
|
3223
|
-
-0.525532409916329,
|
|
3224
|
-
-0.1834346424956498,
|
|
3225
|
-
0.1834346424956498,
|
|
3226
|
-
0.525532409916329,
|
|
3227
|
-
0.7966664774136267,
|
|
3228
|
-
0.9602898564975363
|
|
3229
|
-
];
|
|
3230
|
-
var GL_W = [
|
|
3231
|
-
0.1012285362903763,
|
|
3232
|
-
0.2223810344533745,
|
|
3233
|
-
0.3137066458778873,
|
|
3234
|
-
0.362683783378362,
|
|
3235
|
-
0.362683783378362,
|
|
3236
|
-
0.3137066458778873,
|
|
3237
|
-
0.2223810344533745,
|
|
3238
|
-
0.1012285362903763
|
|
3239
|
-
];
|
|
3240
|
-
function glLength(seg, t0, t1) {
|
|
3241
|
-
const half = (t1 - t0) / 2;
|
|
3242
|
-
const mid = (t0 + t1) / 2;
|
|
3243
|
-
let sum = 0;
|
|
3244
|
-
for (let i = 0; i < GL_X.length; i++) {
|
|
3245
|
-
const d = segmentDerivative(seg, mid + half * GL_X[i]);
|
|
3246
|
-
sum += GL_W[i] * Math.hypot(d[0], d[1]);
|
|
3247
|
-
}
|
|
3248
|
-
return sum * half;
|
|
3249
|
-
}
|
|
3250
|
-
function adaptiveLength(seg, t0, t1, whole, tol, depth) {
|
|
3251
|
-
const m = (t0 + t1) / 2;
|
|
3252
|
-
const left = glLength(seg, t0, m);
|
|
3253
|
-
const right = glLength(seg, m, t1);
|
|
3254
|
-
if (depth >= 18 || Math.abs(left + right - whole) <= tol) return left + right;
|
|
3255
|
-
return adaptiveLength(seg, t0, m, left, tol / 2, depth + 1) + adaptiveLength(seg, m, t1, right, tol / 2, depth + 1);
|
|
3256
|
-
}
|
|
3257
|
-
function segmentLength(seg, t0 = 0, t1 = 1, tolerance = 1e-9) {
|
|
3258
|
-
if (t1 < t0) return segmentLength(seg, t1, t0, tolerance);
|
|
3259
|
-
if (seg.length === 2) return dist2(seg[0], seg[1]) * (t1 - t0);
|
|
3260
|
-
const whole = glLength(seg, t0, t1);
|
|
3261
|
-
return adaptiveLength(seg, t0, t1, whole, Math.max(tolerance, whole * 1e-12), 0);
|
|
3262
|
-
}
|
|
3263
|
-
function segmentParamAtLength(seg, length, total = segmentLength(seg)) {
|
|
3264
|
-
if (!(total > 0) || length <= 0) return 0;
|
|
3265
|
-
if (length >= total) return 1;
|
|
3266
|
-
if (seg.length === 2) return length / total;
|
|
3267
|
-
let lo = 0;
|
|
3268
|
-
let hi = 1;
|
|
3269
|
-
let t = length / total;
|
|
3270
|
-
for (let i = 0; i < 40; i++) {
|
|
3271
|
-
const err = segmentLength(seg, 0, t) - length;
|
|
3272
|
-
if (Math.abs(err) <= 1e-10 * Math.max(1, total)) break;
|
|
3273
|
-
if (err > 0) hi = t;
|
|
3274
|
-
else lo = t;
|
|
3275
|
-
const d = segmentDerivative(seg, t);
|
|
3276
|
-
const speed = Math.hypot(d[0], d[1]);
|
|
3277
|
-
let next = speed > 0 ? t - err / speed : (lo + hi) / 2;
|
|
3278
|
-
if (!(next > lo && next < hi)) next = (lo + hi) / 2;
|
|
3279
|
-
t = next;
|
|
3280
|
-
}
|
|
3281
|
-
return t;
|
|
3282
|
-
}
|
|
3283
|
-
function segmentNearest(seg, p) {
|
|
3284
|
-
if (seg.length === 2) {
|
|
3285
|
-
const d = sub2(seg[1], seg[0]);
|
|
3286
|
-
const len2 = dot2(d, d);
|
|
3287
|
-
const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, dot2(sub2(p, seg[0]), d) / len2));
|
|
3288
|
-
return { t, distance: dist2(p, segmentPoint(seg, t)) };
|
|
3289
|
-
}
|
|
3290
|
-
const samples = 16;
|
|
3291
|
-
let best = { t: 0, distance: dist2(p, seg[0]) };
|
|
3292
|
-
const candidates = [];
|
|
3293
|
-
let prev = Infinity;
|
|
3294
|
-
let prevT = 0;
|
|
3295
|
-
let descending = false;
|
|
3296
|
-
for (let i = 0; i <= samples; i++) {
|
|
3297
|
-
const t = i / samples;
|
|
3298
|
-
const d = dist2(p, segmentPoint(seg, t));
|
|
3299
|
-
if (d < best.distance) best = { t, distance: d };
|
|
3300
|
-
if (i > 0 && d > prev && descending) candidates.push(prevT);
|
|
3301
|
-
descending = d < prev;
|
|
3302
|
-
prev = d;
|
|
3303
|
-
prevT = t;
|
|
3304
|
-
}
|
|
3305
|
-
candidates.push(best.t, 1);
|
|
3306
|
-
for (const start of candidates) {
|
|
3307
|
-
let t = start;
|
|
3308
|
-
for (let k = 0; k < 12; k++) {
|
|
3309
|
-
const q = segmentPoint(seg, t);
|
|
3310
|
-
const d1 = segmentDerivative(seg, t);
|
|
3311
|
-
const d2 = segmentSecondDerivative(seg, t);
|
|
3312
|
-
const diff = sub2(q, p);
|
|
3313
|
-
const f = dot2(diff, d1);
|
|
3314
|
-
const fp = dot2(d1, d1) + dot2(diff, d2);
|
|
3315
|
-
if (!(Math.abs(fp) > 1e-300)) break;
|
|
3316
|
-
const next = Math.max(0, Math.min(1, t - f / fp));
|
|
3317
|
-
if (Math.abs(next - t) < 1e-14) {
|
|
3318
|
-
t = next;
|
|
3319
|
-
break;
|
|
3320
|
-
}
|
|
3321
|
-
t = next;
|
|
3322
|
-
}
|
|
3323
|
-
const d = dist2(p, segmentPoint(seg, t));
|
|
3324
|
-
if (d < best.distance) best = { t, distance: d };
|
|
3325
|
-
}
|
|
3326
|
-
return best;
|
|
3327
|
-
}
|
|
3328
|
-
function arcToCubics(from, cmd) {
|
|
3329
|
-
const [x0, y0] = from;
|
|
3330
|
-
const { x, y } = cmd;
|
|
3331
|
-
if (x0 === x && y0 === y) return [];
|
|
3332
|
-
let rx = Math.abs(cmd.rx);
|
|
3333
|
-
let ry = Math.abs(cmd.ry);
|
|
3334
|
-
if (rx === 0 || ry === 0) return [[from, [x, y]]];
|
|
3335
|
-
const phi = cmd.rotation * Math.PI / 180;
|
|
3336
|
-
const cos = Math.cos(phi);
|
|
3337
|
-
const sin = Math.sin(phi);
|
|
3338
|
-
const dx = (x0 - x) / 2;
|
|
3339
|
-
const dy = (y0 - y) / 2;
|
|
3340
|
-
const x1p = cos * dx + sin * dy;
|
|
3341
|
-
const y1p = -sin * dx + cos * dy;
|
|
3342
|
-
const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
|
|
3343
|
-
if (lambda > 1) {
|
|
3344
|
-
const s = Math.sqrt(lambda);
|
|
3345
|
-
rx *= s;
|
|
3346
|
-
ry *= s;
|
|
3347
|
-
}
|
|
3348
|
-
const num2 = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
|
|
3349
|
-
const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
|
|
3350
|
-
let coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num2 / den));
|
|
3351
|
-
if (cmd.largeArc === cmd.sweep) coef = -coef;
|
|
3352
|
-
const cxp = coef * rx * y1p / ry;
|
|
3353
|
-
const cyp = -coef * ry * x1p / rx;
|
|
3354
|
-
const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
|
|
3355
|
-
const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
|
|
3356
|
-
const angle = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
3357
|
-
const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
|
|
3358
|
-
let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
|
|
3359
|
-
if (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;
|
|
3360
|
-
else if (cmd.sweep && delta < 0) delta += 2 * Math.PI;
|
|
3361
|
-
const n = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9));
|
|
3362
|
-
const step = delta / n;
|
|
3363
|
-
const k = 4 / 3 * Math.tan(step / 4);
|
|
3364
|
-
const map = (ux, uy) => {
|
|
3365
|
-
const ex = rx * ux;
|
|
3366
|
-
const ey = ry * uy;
|
|
3367
|
-
return [cos * ex - sin * ey + cx, sin * ex + cos * ey + cy];
|
|
3368
|
-
};
|
|
3369
|
-
const out = [];
|
|
3370
|
-
let prev = from;
|
|
3371
|
-
for (let i = 0; i < n; i++) {
|
|
3372
|
-
const a0 = theta1 + step * i;
|
|
3373
|
-
const a1 = a0 + step;
|
|
3374
|
-
const c0 = Math.cos(a0);
|
|
3375
|
-
const s0 = Math.sin(a0);
|
|
3376
|
-
const c1 = Math.cos(a1);
|
|
3377
|
-
const s1 = Math.sin(a1);
|
|
3378
|
-
const end = i === n - 1 ? [x, y] : map(c1, s1);
|
|
3379
|
-
out.push([prev, map(c0 - k * s0, s0 + k * c0), map(c1 + k * s1, s1 - k * c1), end]);
|
|
3380
|
-
prev = end;
|
|
3381
|
-
}
|
|
3382
|
-
return out;
|
|
3383
|
-
}
|
|
3384
|
-
function isBezierCommandList(input) {
|
|
3385
|
-
return Array.isArray(input) && !input.some((c) => c && c.type === "A");
|
|
3386
|
-
}
|
|
3387
|
-
function toBezierPath(input) {
|
|
3388
|
-
const commands = typeof input === "string" ? parseSvgPath(input) : isBezierCommandList(input) ? input : input;
|
|
3389
|
-
const out = [];
|
|
3390
|
-
let current = [0, 0];
|
|
3391
|
-
let start = [0, 0];
|
|
3392
|
-
let open = false;
|
|
3393
|
-
const ensureMove = () => {
|
|
3394
|
-
if (!open) {
|
|
3395
|
-
out.push({ type: "M", x: start[0], y: start[1] });
|
|
3396
|
-
current = start;
|
|
3397
|
-
open = true;
|
|
3398
|
-
}
|
|
3399
|
-
};
|
|
3400
|
-
for (const cmd of commands) {
|
|
3401
|
-
switch (cmd.type) {
|
|
3402
|
-
case "M":
|
|
3403
|
-
start = [cmd.x, cmd.y];
|
|
3404
|
-
current = start;
|
|
3405
|
-
out.push({ type: "M", x: cmd.x, y: cmd.y });
|
|
3406
|
-
open = true;
|
|
3407
|
-
break;
|
|
3408
|
-
case "L":
|
|
3409
|
-
ensureMove();
|
|
3410
|
-
out.push({ type: "L", x: cmd.x, y: cmd.y });
|
|
3411
|
-
current = [cmd.x, cmd.y];
|
|
3412
|
-
break;
|
|
3413
|
-
case "Q":
|
|
3414
|
-
ensureMove();
|
|
3415
|
-
out.push({ type: "Q", x1: cmd.x1, y1: cmd.y1, x: cmd.x, y: cmd.y });
|
|
3416
|
-
current = [cmd.x, cmd.y];
|
|
3417
|
-
break;
|
|
3418
|
-
case "C":
|
|
3419
|
-
ensureMove();
|
|
3420
|
-
out.push({ type: "C", x1: cmd.x1, y1: cmd.y1, x2: cmd.x2, y2: cmd.y2, x: cmd.x, y: cmd.y });
|
|
3421
|
-
current = [cmd.x, cmd.y];
|
|
3422
|
-
break;
|
|
3423
|
-
case "A": {
|
|
3424
|
-
ensureMove();
|
|
3425
|
-
for (const c of arcToCubics(current, cmd)) {
|
|
3426
|
-
if (c.length === 2) out.push({ type: "L", x: c[1][0], y: c[1][1] });
|
|
3427
|
-
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] });
|
|
3428
|
-
}
|
|
3429
|
-
current = [cmd.x, cmd.y];
|
|
3430
|
-
break;
|
|
3431
|
-
}
|
|
3432
|
-
case "Z":
|
|
3433
|
-
if (open) out.push({ type: "Z" });
|
|
3434
|
-
open = false;
|
|
3435
|
-
current = start;
|
|
3436
|
-
break;
|
|
3437
|
-
}
|
|
3438
|
-
}
|
|
3439
|
-
return out;
|
|
3440
|
-
}
|
|
3441
|
-
function pathToContours(input) {
|
|
3442
|
-
const path = toBezierPath(input);
|
|
3443
|
-
const contours = [];
|
|
3444
|
-
let current = null;
|
|
3445
|
-
let pen = [0, 0];
|
|
3446
|
-
let start = [0, 0];
|
|
3447
|
-
const push = (seg) => {
|
|
3448
|
-
if (!current) {
|
|
3449
|
-
current = { segments: [], closed: false };
|
|
3450
|
-
contours.push(current);
|
|
3451
|
-
}
|
|
3452
|
-
if (seg.every((p) => samePoint(p, seg[0]))) return;
|
|
3453
|
-
current.segments.push(seg);
|
|
3454
|
-
};
|
|
3455
|
-
for (const cmd of path) {
|
|
3456
|
-
switch (cmd.type) {
|
|
3457
|
-
case "M":
|
|
3458
|
-
start = [cmd.x, cmd.y];
|
|
3459
|
-
pen = start;
|
|
3460
|
-
current = { segments: [], closed: false };
|
|
3461
|
-
contours.push(current);
|
|
3462
|
-
break;
|
|
3463
|
-
case "L":
|
|
3464
|
-
push([pen, [cmd.x, cmd.y]]);
|
|
3465
|
-
pen = [cmd.x, cmd.y];
|
|
3466
|
-
break;
|
|
3467
|
-
case "Q":
|
|
3468
|
-
push([pen, [cmd.x1, cmd.y1], [cmd.x, cmd.y]]);
|
|
3469
|
-
pen = [cmd.x, cmd.y];
|
|
3470
|
-
break;
|
|
3471
|
-
case "C":
|
|
3472
|
-
push([pen, [cmd.x1, cmd.y1], [cmd.x2, cmd.y2], [cmd.x, cmd.y]]);
|
|
3473
|
-
pen = [cmd.x, cmd.y];
|
|
3474
|
-
break;
|
|
3475
|
-
case "Z":
|
|
3476
|
-
if (current) current.closed = true;
|
|
3477
|
-
current = null;
|
|
3478
|
-
pen = start;
|
|
3479
|
-
break;
|
|
3480
|
-
}
|
|
3481
|
-
}
|
|
3482
|
-
return contours;
|
|
3483
|
-
}
|
|
3484
|
-
function contoursToPath(contours) {
|
|
3485
|
-
const out = [];
|
|
3486
|
-
for (const contour of contours) {
|
|
3487
|
-
const segs = contour.segments;
|
|
3488
|
-
if (segs.length === 0) continue;
|
|
3489
|
-
out.push({ type: "M", x: segs[0][0][0], y: segs[0][0][1] });
|
|
3490
|
-
let count = segs.length;
|
|
3491
|
-
if (contour.closed && count > 1) {
|
|
3492
|
-
const last = segs[count - 1];
|
|
3493
|
-
if (last.length === 2 && samePoint(last[1], segs[0][0])) count--;
|
|
3494
|
-
}
|
|
3495
|
-
for (let i = 0; i < count; i++) {
|
|
3496
|
-
const s = segs[i];
|
|
3497
|
-
const end = s[s.length - 1];
|
|
3498
|
-
if (s.length === 2) out.push({ type: "L", x: end[0], y: end[1] });
|
|
3499
|
-
else if (s.length === 3) out.push({ type: "Q", x1: s[1][0], y1: s[1][1], x: end[0], y: end[1] });
|
|
3500
|
-
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] });
|
|
3501
|
-
}
|
|
3502
|
-
if (contour.closed) out.push({ type: "Z" });
|
|
3503
|
-
}
|
|
3504
|
-
return out;
|
|
3505
|
-
}
|
|
3506
|
-
function bezierPathToSvg(input, options = {}) {
|
|
3507
|
-
const path = toBezierPath(input);
|
|
3508
|
-
const precision = options.precision ?? 3;
|
|
3509
|
-
const dx = options.offset?.x ?? 0;
|
|
3510
|
-
const dy = options.offset?.y ?? 0;
|
|
3511
|
-
const fx = (n) => formatNumber2(n + dx, precision);
|
|
3512
|
-
const fy = (n) => formatNumber2(n + dy, precision);
|
|
3513
|
-
let d = "";
|
|
3514
|
-
for (const c of path) {
|
|
3515
|
-
switch (c.type) {
|
|
3516
|
-
case "M":
|
|
3517
|
-
d += `M${fx(c.x)} ${fy(c.y)}`;
|
|
3518
|
-
break;
|
|
3519
|
-
case "L":
|
|
3520
|
-
d += `L${fx(c.x)} ${fy(c.y)}`;
|
|
3521
|
-
break;
|
|
3522
|
-
case "Q":
|
|
3523
|
-
d += `Q${fx(c.x1)} ${fy(c.y1)} ${fx(c.x)} ${fy(c.y)}`;
|
|
3524
|
-
break;
|
|
3525
|
-
case "C":
|
|
3526
|
-
d += `C${fx(c.x1)} ${fy(c.y1)} ${fx(c.x2)} ${fy(c.y2)} ${fx(c.x)} ${fy(c.y)}`;
|
|
3527
|
-
break;
|
|
3528
|
-
case "Z":
|
|
3529
|
-
d += "Z";
|
|
3530
|
-
break;
|
|
3531
|
-
}
|
|
3532
|
-
}
|
|
3533
|
-
return d;
|
|
3534
|
-
}
|
|
3535
|
-
function transformBezierPath(input, m) {
|
|
3536
|
-
const [a, b, c, d, e, f] = m;
|
|
3537
|
-
const X = (x, y) => a * x + c * y + e;
|
|
3538
|
-
const Y = (x, y) => b * x + d * y + f;
|
|
3539
|
-
return toBezierPath(input).map((cmd) => {
|
|
3540
|
-
switch (cmd.type) {
|
|
3541
|
-
case "M":
|
|
3542
|
-
case "L":
|
|
3543
|
-
return { type: cmd.type, x: X(cmd.x, cmd.y), y: Y(cmd.x, cmd.y) };
|
|
3544
|
-
case "Q":
|
|
3545
|
-
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) };
|
|
3546
|
-
case "C":
|
|
3547
|
-
return {
|
|
3548
|
-
type: "C",
|
|
3549
|
-
x1: X(cmd.x1, cmd.y1),
|
|
3550
|
-
y1: Y(cmd.x1, cmd.y1),
|
|
3551
|
-
x2: X(cmd.x2, cmd.y2),
|
|
3552
|
-
y2: Y(cmd.x2, cmd.y2),
|
|
3553
|
-
x: X(cmd.x, cmd.y),
|
|
3554
|
-
y: Y(cmd.x, cmd.y)
|
|
3555
|
-
};
|
|
3556
|
-
case "Z":
|
|
3557
|
-
return cmd;
|
|
3558
|
-
}
|
|
3559
|
-
});
|
|
3560
|
-
}
|
|
3561
|
-
function countPathSegments(input) {
|
|
3562
|
-
const out = { lines: 0, quads: 0, cubics: 0 };
|
|
3563
|
-
for (const contour of pathToContours(input)) {
|
|
3564
|
-
const segs = closeContour(contour);
|
|
3565
|
-
for (const s of segs) {
|
|
3566
|
-
if (s.length === 2) out.lines++;
|
|
3567
|
-
else if (s.length === 3) out.quads++;
|
|
3568
|
-
else out.cubics++;
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
return out;
|
|
3572
|
-
}
|
|
3573
|
-
function closeContour(contour, force = false) {
|
|
3574
|
-
const segs = contour.segments;
|
|
3575
|
-
if (segs.length === 0) return [];
|
|
3576
|
-
if (!contour.closed && !force) return segs;
|
|
3577
|
-
const first = segs[0][0];
|
|
3578
|
-
const lastSeg = segs[segs.length - 1];
|
|
3579
|
-
const last = lastSeg[lastSeg.length - 1];
|
|
3580
|
-
return samePoint(first, last) ? segs : [...segs, [last, first]];
|
|
3581
|
-
}
|
|
3582
|
-
function bezierPathBounds(input) {
|
|
3583
|
-
let minX = Infinity;
|
|
3584
|
-
let minY = Infinity;
|
|
3585
|
-
let maxX = -Infinity;
|
|
3586
|
-
let maxY = -Infinity;
|
|
3587
|
-
const path = toBezierPath(input);
|
|
3588
|
-
let pen = null;
|
|
3589
|
-
for (const contour of pathToContours(path)) {
|
|
3590
|
-
for (const s of contour.segments) {
|
|
3591
|
-
const b = segmentBounds(s);
|
|
3592
|
-
minX = Math.min(minX, b.minX);
|
|
3593
|
-
minY = Math.min(minY, b.minY);
|
|
3594
|
-
maxX = Math.max(maxX, b.maxX);
|
|
3595
|
-
maxY = Math.max(maxY, b.maxY);
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
for (const c of path) {
|
|
3599
|
-
if (c.type === "M") pen = [c.x, c.y];
|
|
3600
|
-
if (pen) {
|
|
3601
|
-
minX = Math.min(minX, pen[0]);
|
|
3602
|
-
minY = Math.min(minY, pen[1]);
|
|
3603
|
-
maxX = Math.max(maxX, pen[0]);
|
|
3604
|
-
maxY = Math.max(maxY, pen[1]);
|
|
3605
|
-
}
|
|
3606
|
-
}
|
|
3607
|
-
return minX === Infinity ? null : { minX, minY, maxX, maxY };
|
|
3608
|
-
}
|
|
3609
|
-
function bezierPathSignedArea(input) {
|
|
3610
|
-
let sum = 0;
|
|
3611
|
-
for (const contour of pathToContours(input)) sum += contourSignedArea(closeContour(contour, true));
|
|
3612
|
-
return sum;
|
|
3613
|
-
}
|
|
3614
|
-
function contourSignedArea(segs) {
|
|
3615
|
-
let sum = 0;
|
|
3616
|
-
for (const s of segs) sum += segmentAreaTerm(s);
|
|
3617
|
-
return sum;
|
|
3618
|
-
}
|
|
3619
|
-
function bezierPathLength(input) {
|
|
3620
|
-
let total = 0;
|
|
3621
|
-
for (const contour of pathToContours(input)) for (const s of closeContour(contour)) total += segmentLength(s);
|
|
3622
|
-
return total;
|
|
3623
|
-
}
|
|
3624
|
-
function bezierPathPointAtLength(input, length) {
|
|
3625
|
-
const contours = pathToContours(input);
|
|
3626
|
-
let remaining = Math.max(0, length);
|
|
3627
|
-
let last = null;
|
|
3628
|
-
for (let ci = 0; ci < contours.length; ci++) {
|
|
3629
|
-
const segs = closeContour(contours[ci]);
|
|
3630
|
-
for (let si = 0; si < segs.length; si++) {
|
|
3631
|
-
const s = segs[si];
|
|
3632
|
-
const len = segmentLength(s);
|
|
3633
|
-
if (remaining <= len) {
|
|
3634
|
-
const t = segmentParamAtLength(s, remaining, len);
|
|
3635
|
-
const tangent2 = segmentTangent(s, t);
|
|
3636
|
-
return { point: segmentPoint(s, t), tangent: tangent2, angle: Math.atan2(tangent2[1], tangent2[0]), contour: ci, segment: si, t };
|
|
3637
|
-
}
|
|
3638
|
-
remaining -= len;
|
|
3639
|
-
const tangent = segmentTangent(s, 1);
|
|
3640
|
-
last = { point: s[s.length - 1], tangent, angle: Math.atan2(tangent[1], tangent[0]), contour: ci, segment: si, t: 1 };
|
|
3641
|
-
}
|
|
3642
|
-
}
|
|
3643
|
-
return last;
|
|
3644
|
-
}
|
|
3645
|
-
function trimSegments(segs, from, to, lengths) {
|
|
3646
|
-
const lens = lengths ?? segs.map((s) => segmentLength(s));
|
|
3647
|
-
const out = [];
|
|
3648
|
-
let at = 0;
|
|
3649
|
-
for (let i = 0; i < segs.length; i++) {
|
|
3650
|
-
const len = lens[i];
|
|
3651
|
-
const s0 = at;
|
|
3652
|
-
const s1 = at + len;
|
|
3653
|
-
at = s1;
|
|
3654
|
-
if (s1 <= from || s0 >= to || len <= 0) continue;
|
|
3655
|
-
const t0 = from > s0 ? segmentParamAtLength(segs[i], from - s0, len) : 0;
|
|
3656
|
-
const t1 = to < s1 ? segmentParamAtLength(segs[i], to - s0, len) : 1;
|
|
3657
|
-
if (t1 > t0) out.push(subSegment(segs[i], t0, t1));
|
|
3658
|
-
}
|
|
3659
|
-
return out;
|
|
3660
|
-
}
|
|
3661
|
-
function solveMonotone(seg, axis, value) {
|
|
3662
|
-
const a = seg[0][axis];
|
|
3663
|
-
const b = seg[seg.length - 1][axis];
|
|
3664
|
-
if (seg.length === 2) return b === a ? 0 : (value - a) / (b - a);
|
|
3665
|
-
const increasing = b > a;
|
|
3666
|
-
let lo = 0;
|
|
3667
|
-
let hi = 1;
|
|
3668
|
-
let t = b === a ? 0.5 : Math.max(0, Math.min(1, (value - a) / (b - a)));
|
|
3669
|
-
for (let i = 0; i < 60; i++) {
|
|
3670
|
-
const v = segmentPoint(seg, t)[axis] - value;
|
|
3671
|
-
if (v === 0) return t;
|
|
3672
|
-
if (v > 0 === increasing) hi = t;
|
|
3673
|
-
else lo = t;
|
|
3674
|
-
const d = segmentDerivative(seg, t)[axis];
|
|
3675
|
-
let next = d !== 0 ? t - v / d : (lo + hi) / 2;
|
|
3676
|
-
if (!(next > lo && next < hi)) next = (lo + hi) / 2;
|
|
3677
|
-
if (Math.abs(next - t) < 1e-15) return next;
|
|
3678
|
-
t = next;
|
|
3679
|
-
}
|
|
3680
|
-
return t;
|
|
3681
|
-
}
|
|
3682
|
-
function yMonotonePieces(segs) {
|
|
3683
|
-
const out = [];
|
|
3684
|
-
for (const s of segs) {
|
|
3685
|
-
if (s.length === 2) {
|
|
3686
|
-
out.push(s);
|
|
3687
|
-
continue;
|
|
3688
|
-
}
|
|
3689
|
-
const ts = segmentExtrema(s, 1);
|
|
3690
|
-
let prev = 0;
|
|
3691
|
-
for (const t of [...ts, 1]) {
|
|
3692
|
-
out.push(subSegment(s, prev, t));
|
|
3693
|
-
prev = t;
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
return out;
|
|
3697
|
-
}
|
|
3698
|
-
function windingNumber(chains, point) {
|
|
3699
|
-
const [px2, py] = point;
|
|
3700
|
-
let winding = 0;
|
|
3701
|
-
for (const chain of chains) {
|
|
3702
|
-
for (const s of yMonotonePieces(chain)) {
|
|
3703
|
-
const ay = s[0][1];
|
|
3704
|
-
const by = s[s.length - 1][1];
|
|
3705
|
-
let dir = 0;
|
|
3706
|
-
if (ay <= py && by > py) dir = 1;
|
|
3707
|
-
else if (by <= py && ay > py) dir = -1;
|
|
3708
|
-
else continue;
|
|
3709
|
-
let minX = Infinity;
|
|
3710
|
-
let maxX = -Infinity;
|
|
3711
|
-
for (const p of s) {
|
|
3712
|
-
if (p[0] < minX) minX = p[0];
|
|
3713
|
-
if (p[0] > maxX) maxX = p[0];
|
|
3714
|
-
}
|
|
3715
|
-
if (minX > px2) {
|
|
3716
|
-
winding += dir;
|
|
3717
|
-
continue;
|
|
3718
|
-
}
|
|
3719
|
-
if (maxX <= px2) continue;
|
|
3720
|
-
const t = solveMonotone(s, 1, py);
|
|
3721
|
-
if (segmentPoint(s, t)[0] > px2) winding += dir;
|
|
3722
|
-
}
|
|
3723
|
-
}
|
|
3724
|
-
return winding;
|
|
3725
|
-
}
|
|
3726
|
-
function pointInBezierPath(input, point, fillRule = "nonzero") {
|
|
3727
|
-
const chains = pathToContours(input).map((c) => closeContour(c, true));
|
|
3728
|
-
const w = windingNumber(chains, point);
|
|
3729
|
-
return fillRule === "evenodd" ? (w & 1) === 1 : w !== 0;
|
|
3730
|
-
}
|
|
3731
|
-
function distanceToBezierPath(input, point) {
|
|
3732
|
-
const contours = Array.isArray(input) && input.length > 0 && input[0].segments ? input : pathToContours(input);
|
|
3733
|
-
let best = Infinity;
|
|
3734
|
-
for (const contour of contours) {
|
|
3735
|
-
for (const s of closeContour(contour)) {
|
|
3736
|
-
let minX = Infinity;
|
|
3737
|
-
let minY = Infinity;
|
|
3738
|
-
let maxX = -Infinity;
|
|
3739
|
-
let maxY = -Infinity;
|
|
3740
|
-
for (const p of s) {
|
|
3741
|
-
minX = Math.min(minX, p[0]);
|
|
3742
|
-
minY = Math.min(minY, p[1]);
|
|
3743
|
-
maxX = Math.max(maxX, p[0]);
|
|
3744
|
-
maxY = Math.max(maxY, p[1]);
|
|
3745
|
-
}
|
|
3746
|
-
const dx = Math.max(minX - point[0], 0, point[0] - maxX);
|
|
3747
|
-
const dy = Math.max(minY - point[1], 0, point[1] - maxY);
|
|
3748
|
-
if (Math.hypot(dx, dy) >= best) continue;
|
|
3749
|
-
best = Math.min(best, segmentNearest(s, point).distance);
|
|
3750
|
-
}
|
|
3751
|
-
if (contour.segments.length === 0) continue;
|
|
3752
|
-
}
|
|
3753
|
-
return best;
|
|
3754
|
-
}
|
|
3755
|
-
function flattenChain(segs, tolerance) {
|
|
3756
|
-
const out = [];
|
|
3757
|
-
for (const s of segs) {
|
|
3758
|
-
if (out.length === 0) out.push(s[0]);
|
|
3759
|
-
if (s.length === 2) {
|
|
3760
|
-
out.push(s[1]);
|
|
3761
|
-
continue;
|
|
3762
|
-
}
|
|
3763
|
-
let dd = 0;
|
|
3764
|
-
for (let i = 0; i + 2 < s.length; i++) {
|
|
3765
|
-
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]));
|
|
3766
|
-
}
|
|
3767
|
-
const deg = s.length - 1;
|
|
3768
|
-
const n = Math.max(1, Math.min(256, Math.ceil(Math.sqrt(deg * (deg - 1) * dd / (8 * tolerance)))));
|
|
3769
|
-
for (let k = 1; k <= n; k++) out.push(segmentPoint(s, k / n));
|
|
3770
|
-
}
|
|
3771
|
-
if (out.length > 1 && samePoint(out[0], out[out.length - 1])) out.pop();
|
|
3772
|
-
return out;
|
|
3773
|
-
}
|
|
3774
|
-
|
|
3775
|
-
// src/canvas/pathops/boolean.ts
|
|
3776
|
-
var PathOpsError = class extends Error {
|
|
3777
|
-
constructor(message) {
|
|
3778
|
-
super(message);
|
|
3779
|
-
this.name = "PathOpsError";
|
|
3780
|
-
}
|
|
3781
|
-
};
|
|
3782
|
-
function isOperandObject(input) {
|
|
3783
|
-
return !!input && typeof input === "object" && !Array.isArray(input) && "path" in input;
|
|
3784
|
-
}
|
|
3785
|
-
function normalizeOperand(input) {
|
|
3786
|
-
const path = isOperandObject(input) ? input.path : input;
|
|
3787
|
-
const fillRule = isOperandObject(input) ? input.fillRule ?? "nonzero" : "nonzero";
|
|
3788
|
-
const chains = [];
|
|
3789
|
-
for (const contour of pathToContours(path ?? "")) {
|
|
3790
|
-
const chain = closeContour(contour, true);
|
|
3791
|
-
if (chain.length === 0) continue;
|
|
3792
|
-
for (const seg of chain) {
|
|
3793
|
-
for (const p of seg) {
|
|
3794
|
-
if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) throw new PathOpsError(NON_FINITE);
|
|
3795
|
-
}
|
|
3796
|
-
}
|
|
3797
|
-
chains.push(chain);
|
|
3798
|
-
}
|
|
3799
|
-
return { chains, fillRule };
|
|
3800
|
-
}
|
|
3801
|
-
function scaleOf(operands) {
|
|
3802
|
-
let max = 0;
|
|
3803
|
-
for (const op of operands) {
|
|
3804
|
-
for (const chain of op.chains) {
|
|
3805
|
-
for (const seg of chain) {
|
|
3806
|
-
for (const [x, y] of seg) max = Math.max(max, Math.abs(x), Math.abs(y));
|
|
3807
|
-
}
|
|
3808
|
-
}
|
|
3809
|
-
}
|
|
3810
|
-
return Math.max(1, max);
|
|
3811
|
-
}
|
|
3812
|
-
var VertexPool = class {
|
|
3813
|
-
constructor(eps) {
|
|
3814
|
-
this.eps = eps;
|
|
3815
|
-
}
|
|
3816
|
-
eps;
|
|
3817
|
-
vertices = [];
|
|
3818
|
-
cells = /* @__PURE__ */ new Map();
|
|
3819
|
-
get(x, y) {
|
|
3820
|
-
const cx = Math.floor(x / this.eps);
|
|
3821
|
-
const cy = Math.floor(y / this.eps);
|
|
3822
|
-
let best = null;
|
|
3823
|
-
let bestDist = Infinity;
|
|
3824
|
-
for (let dx = -1; dx <= 1; dx++) {
|
|
3825
|
-
for (let dy = -1; dy <= 1; dy++) {
|
|
3826
|
-
const ids = this.cells.get(`${cx + dx},${cy + dy}`);
|
|
3827
|
-
if (!ids) continue;
|
|
3828
|
-
for (const id of ids) {
|
|
3829
|
-
const v = this.vertices[id];
|
|
3830
|
-
const d = Math.hypot(v.x - x, v.y - y);
|
|
3831
|
-
if (d <= this.eps && d < bestDist) {
|
|
3832
|
-
best = v;
|
|
3833
|
-
bestDist = d;
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
if (best) return best;
|
|
3839
|
-
const vertex = { id: this.vertices.length, x, y, p: [x, y] };
|
|
3840
|
-
this.vertices.push(vertex);
|
|
3841
|
-
const key = `${cx},${cy}`;
|
|
3842
|
-
const bucket = this.cells.get(key);
|
|
3843
|
-
if (bucket) bucket.push(vertex.id);
|
|
3844
|
-
else this.cells.set(key, [vertex.id]);
|
|
3845
|
-
return vertex;
|
|
3846
|
-
}
|
|
3847
|
-
};
|
|
3848
|
-
function hull(pts) {
|
|
3849
|
-
let minX = Infinity;
|
|
3850
|
-
let maxX = -Infinity;
|
|
3851
|
-
let minY = Infinity;
|
|
3852
|
-
let maxY = -Infinity;
|
|
3853
|
-
for (const [x, y] of pts) {
|
|
3854
|
-
if (x < minX) minX = x;
|
|
3855
|
-
if (x > maxX) maxX = x;
|
|
3856
|
-
if (y < minY) minY = y;
|
|
3857
|
-
if (y > maxY) maxY = y;
|
|
3858
|
-
}
|
|
3859
|
-
return { minX, maxX, minY, maxY };
|
|
3860
|
-
}
|
|
3861
|
-
function snapEnds(pts, a, b) {
|
|
3862
|
-
const n = pts.length;
|
|
3863
|
-
if (n === 2) return [a, b];
|
|
3864
|
-
const d0 = sub2(a, pts[0]);
|
|
3865
|
-
const d1 = sub2(b, pts[n - 1]);
|
|
3866
|
-
if (n === 3) {
|
|
3867
|
-
return [a, [pts[1][0] + (d0[0] + d1[0]) / 2, pts[1][1] + (d0[1] + d1[1]) / 2], b];
|
|
3868
|
-
}
|
|
3869
|
-
return [a, [pts[1][0] + d0[0], pts[1][1] + d0[1]], [pts[2][0] + d1[0], pts[2][1] + d1[1]], b];
|
|
3870
|
-
}
|
|
3871
|
-
function makePiece(pts, a, b, operand, src, s0, s1) {
|
|
3872
|
-
const snapped = snapEnds(pts, a.p, b.p);
|
|
3873
|
-
return { pts: snapped, a, b, operand, src, s0, s1, dirty: true, ...hull(snapped) };
|
|
3874
|
-
}
|
|
3875
|
-
var NON_FINITE = "non-finite coordinates";
|
|
3876
|
-
function flatness(pts) {
|
|
3877
|
-
if (pts.length === 2) return 0;
|
|
3878
|
-
const a = pts[0];
|
|
3879
|
-
const b = pts[pts.length - 1];
|
|
3880
|
-
const chord = sub2(b, a);
|
|
3881
|
-
const len = Math.hypot(chord[0], chord[1]);
|
|
3882
|
-
let worst = 0;
|
|
3883
|
-
for (let i = 1; i < pts.length - 1; i++) {
|
|
3884
|
-
const v = sub2(pts[i], a);
|
|
3885
|
-
const d = len > 0 ? Math.abs(cross(chord, v)) / len : Math.hypot(v[0], v[1]);
|
|
3886
|
-
const along = len > 0 ? dot2(chord, v) / len : 0;
|
|
3887
|
-
const over = Math.max(0, -along, along - len);
|
|
3888
|
-
worst = Math.max(worst, d, over);
|
|
3889
|
-
}
|
|
3890
|
-
return worst;
|
|
3891
|
-
}
|
|
3892
|
-
function split2(pts) {
|
|
3893
|
-
const n = pts.length;
|
|
3894
|
-
const mid = (p, q) => [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
|
|
3895
|
-
if (n === 2) {
|
|
3896
|
-
const m2 = mid(pts[0], pts[1]);
|
|
3897
|
-
return [
|
|
3898
|
-
[pts[0], m2],
|
|
3899
|
-
[m2, pts[1]]
|
|
3900
|
-
];
|
|
3901
|
-
}
|
|
3902
|
-
if (n === 3) {
|
|
3903
|
-
const a2 = mid(pts[0], pts[1]);
|
|
3904
|
-
const b2 = mid(pts[1], pts[2]);
|
|
3905
|
-
const m2 = mid(a2, b2);
|
|
3906
|
-
return [
|
|
3907
|
-
[pts[0], a2, m2],
|
|
3908
|
-
[m2, b2, pts[2]]
|
|
3909
|
-
];
|
|
3910
|
-
}
|
|
3911
|
-
const a = mid(pts[0], pts[1]);
|
|
3912
|
-
const b = mid(pts[1], pts[2]);
|
|
3913
|
-
const c = mid(pts[2], pts[3]);
|
|
3914
|
-
const ab = mid(a, b);
|
|
3915
|
-
const bc = mid(b, c);
|
|
3916
|
-
const m = mid(ab, bc);
|
|
3917
|
-
return [
|
|
3918
|
-
[pts[0], a, ab, m],
|
|
3919
|
-
[m, bc, c, pts[3]]
|
|
3920
|
-
];
|
|
3921
|
-
}
|
|
3922
|
-
function closestSegmentPoints(a0, a1, b0, b1) {
|
|
3923
|
-
const d1 = sub2(a1, a0);
|
|
3924
|
-
const d2 = sub2(b1, b0);
|
|
3925
|
-
const r = sub2(a0, b0);
|
|
3926
|
-
const a = dot2(d1, d1);
|
|
3927
|
-
const e = dot2(d2, d2);
|
|
3928
|
-
const f = dot2(d2, r);
|
|
3929
|
-
let s = 0;
|
|
3930
|
-
let u = 0;
|
|
3931
|
-
if (a <= 1e-300 && e <= 1e-300) return { s: 0, u: 0, d: dist2(a0, b0) };
|
|
3932
|
-
if (a <= 1e-300) {
|
|
3933
|
-
u = Math.max(0, Math.min(1, f / e));
|
|
3934
|
-
} else {
|
|
3935
|
-
const c = dot2(d1, r);
|
|
3936
|
-
if (e <= 1e-300) {
|
|
3937
|
-
s = Math.max(0, Math.min(1, -c / a));
|
|
3938
|
-
} else {
|
|
3939
|
-
const b = dot2(d1, d2);
|
|
3940
|
-
const denom = a * e - b * b;
|
|
3941
|
-
s = denom > 0 ? Math.max(0, Math.min(1, (b * f - c * e) / denom)) : 0;
|
|
3942
|
-
u = (b * s + f) / e;
|
|
3943
|
-
if (u < 0) {
|
|
3944
|
-
u = 0;
|
|
3945
|
-
s = Math.max(0, Math.min(1, -c / a));
|
|
3946
|
-
} else if (u > 1) {
|
|
3947
|
-
u = 1;
|
|
3948
|
-
s = Math.max(0, Math.min(1, (b - c) / a));
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3951
|
-
}
|
|
3952
|
-
const pa = [a0[0] + d1[0] * s, a0[1] + d1[1] * s];
|
|
3953
|
-
const pb = [b0[0] + d2[0] * u, b0[1] + d2[1] * u];
|
|
3954
|
-
return { s, u, d: dist2(pa, pb) };
|
|
3955
|
-
}
|
|
3956
|
-
function polish(P, Q, s, u) {
|
|
3957
|
-
let best = { s, u, d: dist2(segmentPoint(P, s), segmentPoint(Q, u)) };
|
|
3958
|
-
for (let i = 0; i < 10 && best.d > 0; i++) {
|
|
3959
|
-
const f = sub2(segmentPoint(P, s), segmentPoint(Q, u));
|
|
3960
|
-
const dp = segmentDerivative(P, s);
|
|
3961
|
-
const dq = segmentDerivative(Q, u);
|
|
3962
|
-
const det = cross(dp, [-dq[0], -dq[1]]);
|
|
3963
|
-
if (!(Math.abs(det) > 1e-300)) break;
|
|
3964
|
-
const ds = cross([-f[0], -f[1]], [-dq[0], -dq[1]]) / det;
|
|
3965
|
-
const du = cross(dp, [-f[0], -f[1]]) / det;
|
|
3966
|
-
s = Math.max(0, Math.min(1, s + ds));
|
|
3967
|
-
u = Math.max(0, Math.min(1, u + du));
|
|
3968
|
-
const d = dist2(segmentPoint(P, s), segmentPoint(Q, u));
|
|
3969
|
-
if (d < best.d) best = { s, u, d };
|
|
3970
|
-
else break;
|
|
3971
|
-
}
|
|
3972
|
-
return best;
|
|
3973
|
-
}
|
|
3974
|
-
function intersectSubdivided(P, p0, p1, Q, q0, q1, fullP, fullQ, ctx, out, depth) {
|
|
3975
|
-
const hp = hull(P);
|
|
3976
|
-
const hq = hull(Q);
|
|
3977
|
-
const e = ctx.eps;
|
|
3978
|
-
if (hp.minX > hq.maxX + e || hq.minX > hp.maxX + e || hp.minY > hq.maxY + e || hq.minY > hp.maxY + e) return;
|
|
3979
|
-
const fp = flatness(P);
|
|
3980
|
-
const fq = flatness(Q);
|
|
3981
|
-
const flatTol = e * 0.5;
|
|
3982
|
-
if (fp <= flatTol && fq <= flatTol || depth >= 52) {
|
|
3983
|
-
if (++ctx.leaves > ctx.budget) throw new PathOpsError("curve intersection did not converge");
|
|
3984
|
-
const a0 = P[0];
|
|
3985
|
-
const a1 = P[P.length - 1];
|
|
3986
|
-
const b0 = Q[0];
|
|
3987
|
-
const b1 = Q[Q.length - 1];
|
|
3988
|
-
const d1 = sub2(a1, a0);
|
|
3989
|
-
const d2 = sub2(b1, b0);
|
|
3990
|
-
const denom = cross(d1, d2);
|
|
3991
|
-
let s = 0;
|
|
3992
|
-
let u = 0;
|
|
3993
|
-
let hit = false;
|
|
3994
|
-
const l1 = Math.hypot(d1[0], d1[1]);
|
|
3995
|
-
const l2 = Math.hypot(d2[0], d2[1]);
|
|
3996
|
-
if (Math.abs(denom) > 1e-12 * l1 * l2) {
|
|
3997
|
-
const r = sub2(b0, a0);
|
|
3998
|
-
s = cross(r, d2) / denom;
|
|
3999
|
-
u = cross(r, d1) / denom;
|
|
4000
|
-
const ts = l1 > 0 ? e / l1 : 1;
|
|
4001
|
-
const tu = l2 > 0 ? e / l2 : 1;
|
|
4002
|
-
hit = s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu;
|
|
4003
|
-
}
|
|
4004
|
-
if (!hit) {
|
|
4005
|
-
const c = closestSegmentPoints(a0, a1, b0, b1);
|
|
4006
|
-
if (c.d > e) return;
|
|
4007
|
-
s = c.s;
|
|
4008
|
-
u = c.u;
|
|
4009
|
-
}
|
|
4010
|
-
s = Math.max(0, Math.min(1, s));
|
|
4011
|
-
u = Math.max(0, Math.min(1, u));
|
|
4012
|
-
const best = polish(fullP, fullQ, p0 + (p1 - p0) * s, q0 + (q1 - q0) * u);
|
|
4013
|
-
if (best.d > e * 2) return;
|
|
4014
|
-
const a = segmentPoint(fullP, best.s);
|
|
4015
|
-
const b = segmentPoint(fullQ, best.u);
|
|
4016
|
-
out.push({ tp: best.s, tq: best.u, point: [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2] });
|
|
4017
|
-
return;
|
|
4018
|
-
}
|
|
4019
|
-
const splitP = fp > flatTol;
|
|
4020
|
-
const splitQ = fq > flatTol;
|
|
4021
|
-
const pm = (p0 + p1) / 2;
|
|
4022
|
-
const qm = (q0 + q1) / 2;
|
|
4023
|
-
if (splitP && splitQ) {
|
|
4024
|
-
const [pa, pb] = split2(P);
|
|
4025
|
-
const [qa, qb] = split2(Q);
|
|
4026
|
-
intersectSubdivided(pa, p0, pm, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
|
|
4027
|
-
intersectSubdivided(pa, p0, pm, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4028
|
-
intersectSubdivided(pb, pm, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
|
|
4029
|
-
intersectSubdivided(pb, pm, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4030
|
-
} else if (splitP) {
|
|
4031
|
-
const [pa, pb] = split2(P);
|
|
4032
|
-
intersectSubdivided(pa, p0, pm, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4033
|
-
intersectSubdivided(pb, pm, p1, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4034
|
-
} else {
|
|
4035
|
-
const [qa, qb] = split2(Q);
|
|
4036
|
-
intersectSubdivided(P, p0, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
|
|
4037
|
-
intersectSubdivided(P, p0, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
|
|
4038
|
-
}
|
|
4039
|
-
}
|
|
4040
|
-
function nearestOn(piece, p, eps) {
|
|
4041
|
-
if (p[0] < piece.minX - eps || p[0] > piece.maxX + eps || p[1] < piece.minY - eps || p[1] > piece.maxY + eps) return null;
|
|
4042
|
-
return segmentNearest(piece.pts, p);
|
|
4043
|
-
}
|
|
4044
|
-
function staysClose(P, Q, e1, e2, tol) {
|
|
4045
|
-
for (const f of [0.25, 0.5, 0.75]) {
|
|
4046
|
-
const tp = e1.tp + (e2.tp - e1.tp) * f;
|
|
4047
|
-
const q = segmentNearest(Q.pts, segmentPoint(P.pts, tp));
|
|
4048
|
-
if (q.distance > tol) return false;
|
|
4049
|
-
}
|
|
4050
|
-
return true;
|
|
4051
|
-
}
|
|
4052
|
-
function pairEvents(P, Q, ctx) {
|
|
4053
|
-
const eps = ctx.eps;
|
|
4054
|
-
const anchors = [];
|
|
4055
|
-
for (const [va, tp] of [
|
|
4056
|
-
[P.a, 0],
|
|
4057
|
-
[P.b, 1]
|
|
4058
|
-
]) {
|
|
4059
|
-
if (va === Q.a) anchors.push({ tp, tq: 0, point: va.p, anchor: true, shared: true });
|
|
4060
|
-
else if (va === Q.b) anchors.push({ tp, tq: 1, point: va.p, anchor: true, shared: true });
|
|
4061
|
-
}
|
|
4062
|
-
for (const [v, tq] of [
|
|
4063
|
-
[Q.a, 0],
|
|
4064
|
-
[Q.b, 1]
|
|
4065
|
-
]) {
|
|
4066
|
-
if (v === P.a || v === P.b) continue;
|
|
4067
|
-
const near = nearestOn(P, v.p, eps);
|
|
4068
|
-
if (near && near.distance <= eps) anchors.push({ tp: near.t, tq, point: v.p, anchor: true });
|
|
4069
|
-
}
|
|
4070
|
-
for (const [v, tp] of [
|
|
4071
|
-
[P.a, 0],
|
|
4072
|
-
[P.b, 1]
|
|
4073
|
-
]) {
|
|
4074
|
-
if (v === Q.a || v === Q.b) continue;
|
|
4075
|
-
const near = nearestOn(Q, v.p, eps);
|
|
4076
|
-
if (near && near.distance <= eps) anchors.push({ tp, tq: near.t, point: v.p, anchor: true });
|
|
4077
|
-
}
|
|
4078
|
-
if (anchors.length >= 2) {
|
|
4079
|
-
const sorted = [...anchors].sort((x, y) => x.tp - y.tp);
|
|
4080
|
-
const first = sorted[0];
|
|
4081
|
-
const last = sorted[sorted.length - 1];
|
|
4082
|
-
if (dist2(first.point, last.point) > eps && staysClose(P, Q, first, last, eps * 4)) {
|
|
4083
|
-
return anchors.filter((a) => !a.shared);
|
|
4084
|
-
}
|
|
4085
|
-
}
|
|
4086
|
-
const found = [];
|
|
4087
|
-
if (P.pts.length === 2 && Q.pts.length === 2) {
|
|
4088
|
-
const a0 = P.pts[0];
|
|
4089
|
-
const d1 = sub2(P.pts[1], a0);
|
|
4090
|
-
const b0 = Q.pts[0];
|
|
4091
|
-
const d2 = sub2(Q.pts[1], b0);
|
|
4092
|
-
const denom = cross(d1, d2);
|
|
4093
|
-
const l1 = Math.hypot(d1[0], d1[1]);
|
|
4094
|
-
const l2 = Math.hypot(d2[0], d2[1]);
|
|
4095
|
-
if (Math.abs(denom) > 1e-12 * l1 * l2) {
|
|
4096
|
-
const r = sub2(b0, a0);
|
|
4097
|
-
const s = cross(r, d2) / denom;
|
|
4098
|
-
const u = cross(r, d1) / denom;
|
|
4099
|
-
const ts = eps / l1;
|
|
4100
|
-
const tu = eps / l2;
|
|
4101
|
-
if (s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu) {
|
|
4102
|
-
const sc = Math.max(0, Math.min(1, s));
|
|
4103
|
-
const uc = Math.max(0, Math.min(1, u));
|
|
4104
|
-
found.push({ tp: sc, tq: uc, point: [a0[0] + d1[0] * sc, a0[1] + d1[1] * sc] });
|
|
4105
|
-
}
|
|
4106
|
-
}
|
|
4107
|
-
} else {
|
|
4108
|
-
ctx.leaves = 0;
|
|
4109
|
-
intersectSubdivided(P.pts, 0, 1, Q.pts, 0, 1, P.pts, Q.pts, ctx, found, 0);
|
|
4110
|
-
}
|
|
4111
|
-
if (found.length === 0) return anchors.filter((a) => !a.shared);
|
|
4112
|
-
const all = [...anchors, ...found].sort((x, y) => x.tp - y.tp || x.tq - y.tq);
|
|
4113
|
-
const clusters = [];
|
|
4114
|
-
for (const ev of all) {
|
|
4115
|
-
const cluster = clusters[clusters.length - 1];
|
|
4116
|
-
const prev = cluster?.[cluster.length - 1];
|
|
4117
|
-
if (prev && (dist2(prev.point, ev.point) <= eps * 2 || staysClose(P, Q, prev, ev, eps * 2))) cluster.push(ev);
|
|
4118
|
-
else clusters.push([ev]);
|
|
4119
|
-
}
|
|
4120
|
-
const out = [];
|
|
4121
|
-
for (const cluster of clusters) {
|
|
4122
|
-
const shared = cluster.find((e) => e.shared);
|
|
4123
|
-
if (shared) continue;
|
|
4124
|
-
const anchor = cluster.find((e) => e.anchor);
|
|
4125
|
-
out.push(anchor ?? cluster[Math.floor(cluster.length / 2)]);
|
|
4126
|
-
}
|
|
4127
|
-
return out;
|
|
4128
|
-
}
|
|
4129
|
-
function addSplit2(map, piece, t, v) {
|
|
4130
|
-
if (v === piece.a || v === piece.b) return;
|
|
4131
|
-
const list = map.get(piece);
|
|
4132
|
-
if (list) list.push({ t, v });
|
|
4133
|
-
else map.set(piece, [{ t, v }]);
|
|
4134
|
-
}
|
|
4135
|
-
function eventVertex(P, Q, ev, pool, eps) {
|
|
4136
|
-
for (const v of [P.a, P.b, Q.a, Q.b]) if (dist2(v.p, ev.point) <= eps) return v;
|
|
4137
|
-
return pool.get(ev.point[0], ev.point[1]);
|
|
4138
|
-
}
|
|
4139
|
-
function splitPass2(pieces, pool, ctx) {
|
|
4140
|
-
const eps = ctx.eps;
|
|
4141
|
-
const order = pieces.map((_, i) => i).sort((i, j) => pieces[i].minX - pieces[j].minX);
|
|
4142
|
-
const splits = /* @__PURE__ */ new Map();
|
|
4143
|
-
for (let oi = 0; oi < order.length; oi++) {
|
|
4144
|
-
const P = pieces[order[oi]];
|
|
4145
|
-
for (let oj = oi + 1; oj < order.length; oj++) {
|
|
4146
|
-
const Q = pieces[order[oj]];
|
|
4147
|
-
if (Q.minX > P.maxX + eps) break;
|
|
4148
|
-
if (!P.dirty && !Q.dirty) continue;
|
|
4149
|
-
if (Q.minY > P.maxY + eps || Q.maxY < P.minY - eps) continue;
|
|
4150
|
-
if (P.a === Q.a && P.b === Q.b && P.pts.length === 2 && Q.pts.length === 2) continue;
|
|
4151
|
-
if (P.a === Q.b && P.b === Q.a && P.pts.length === 2 && Q.pts.length === 2) continue;
|
|
4152
|
-
for (const ev of pairEvents(P, Q, ctx)) {
|
|
4153
|
-
const v = eventVertex(P, Q, ev, pool, eps);
|
|
4154
|
-
addSplit2(splits, P, ev.tp, v);
|
|
4155
|
-
addSplit2(splits, Q, ev.tq, v);
|
|
4156
|
-
}
|
|
4157
|
-
}
|
|
4158
|
-
}
|
|
4159
|
-
for (const p of pieces) p.dirty = false;
|
|
4160
|
-
if (splits.size === 0) return { pieces, changed: false };
|
|
4161
|
-
const out = [];
|
|
4162
|
-
for (const piece of pieces) {
|
|
4163
|
-
const list = splits.get(piece);
|
|
4164
|
-
if (!list) {
|
|
4165
|
-
out.push(piece);
|
|
4166
|
-
continue;
|
|
4167
|
-
}
|
|
4168
|
-
list.sort((x, y) => x.t - y.t);
|
|
4169
|
-
let prevT = 0;
|
|
4170
|
-
let prevV = piece.a;
|
|
4171
|
-
const range = piece.s1 - piece.s0;
|
|
4172
|
-
const emit = (t, v) => {
|
|
4173
|
-
if (v === prevV || t <= prevT) return;
|
|
4174
|
-
const pts = subSegment(piece.pts, prevT, t);
|
|
4175
|
-
out.push(makePiece(pts, prevV, v, piece.operand, piece.src, piece.s0 + range * prevT, piece.s0 + range * t));
|
|
4176
|
-
prevT = t;
|
|
4177
|
-
prevV = v;
|
|
4178
|
-
};
|
|
4179
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4180
|
-
for (const { t, v } of list) {
|
|
4181
|
-
if (seen.has(v) || t <= 0 || t >= 1) continue;
|
|
4182
|
-
seen.add(v);
|
|
4183
|
-
emit(t, v);
|
|
4184
|
-
}
|
|
4185
|
-
if (prevV !== piece.b) {
|
|
4186
|
-
const pts = subSegment(piece.pts, prevT, 1);
|
|
4187
|
-
out.push(makePiece(pts, prevV, piece.b, piece.operand, piece.src, piece.s0 + range * prevT, piece.s1));
|
|
4188
|
-
}
|
|
4189
|
-
}
|
|
4190
|
-
return { pieces: out, changed: true };
|
|
4191
|
-
}
|
|
4192
|
-
function buildArrangement(inputs, options) {
|
|
4193
|
-
const operands = inputs.map(normalizeOperand);
|
|
4194
|
-
const scale2 = scaleOf(operands);
|
|
4195
|
-
const eps = options.epsilon && options.epsilon > 0 ? options.epsilon : 1e-9 * scale2;
|
|
4196
|
-
const pool = new VertexPool(eps);
|
|
4197
|
-
const sources = [];
|
|
4198
|
-
let pieces = [];
|
|
4199
|
-
operands.forEach((op, operand) => {
|
|
4200
|
-
for (const chain of op.chains) {
|
|
4201
|
-
for (const raw of chain) {
|
|
4202
|
-
const straight = raw.length > 2 && isStraightSegment(raw, eps * 0.01);
|
|
4203
|
-
const seg = straight ? [raw[0], raw[raw.length - 1]] : raw;
|
|
4204
|
-
const src = sources.length;
|
|
4205
|
-
sources.push({ seg, line: seg.length === 2 });
|
|
4206
|
-
const ts = [0, ...monotoneParams(seg), 1];
|
|
4207
|
-
for (let i = 0; i + 1 < ts.length; i++) {
|
|
4208
|
-
const pts = subSegment(seg, ts[i], ts[i + 1]);
|
|
4209
|
-
const a = pool.get(pts[0][0], pts[0][1]);
|
|
4210
|
-
const b = pool.get(pts[pts.length - 1][0], pts[pts.length - 1][1]);
|
|
4211
|
-
if (a === b) continue;
|
|
4212
|
-
pieces.push(makePiece(pts, a, b, operand, src, ts[i], ts[i + 1]));
|
|
4213
|
-
}
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
});
|
|
4217
|
-
const ctx = { eps, leaves: 0, budget: options.subdivisionBudget ?? 2e4 };
|
|
4218
|
-
let settled = false;
|
|
4219
|
-
for (let pass = 0; pass < 6; pass++) {
|
|
4220
|
-
const result = splitPass2(pieces, pool, ctx);
|
|
4221
|
-
pieces = result.pieces;
|
|
4222
|
-
if (!result.changed) {
|
|
4223
|
-
settled = true;
|
|
4224
|
-
break;
|
|
4225
|
-
}
|
|
4226
|
-
}
|
|
4227
|
-
if (!settled) throw new PathOpsError("intersections did not settle");
|
|
4228
|
-
const count = operands.length;
|
|
4229
|
-
const groups = /* @__PURE__ */ new Map();
|
|
4230
|
-
const edges = [];
|
|
4231
|
-
for (const piece of pieces) {
|
|
4232
|
-
const forward = piece.a.id < piece.b.id;
|
|
4233
|
-
const a = forward ? piece.a : piece.b;
|
|
4234
|
-
const b = forward ? piece.b : piece.a;
|
|
4235
|
-
const pts = forward ? piece.pts : [...piece.pts].reverse();
|
|
4236
|
-
const key = `${a.id},${b.id}`;
|
|
4237
|
-
const group = groups.get(key) ?? [];
|
|
4238
|
-
if (!groups.has(key)) groups.set(key, group);
|
|
4239
|
-
const mid = segmentPoint(pts, 0.5);
|
|
4240
|
-
let edge = group.find((e) => {
|
|
4241
|
-
if (e.pts.length === 2 && pts.length === 2) return true;
|
|
4242
|
-
return segmentNearest(e.pts, mid).distance <= eps * 4 && segmentNearest(pts, segmentPoint(e.pts, 0.5)).distance <= eps * 4;
|
|
4243
|
-
});
|
|
4244
|
-
if (!edge) {
|
|
4245
|
-
edge = {
|
|
4246
|
-
a,
|
|
4247
|
-
b,
|
|
4248
|
-
pts,
|
|
4249
|
-
mult: new Array(count).fill(0),
|
|
4250
|
-
src: piece.src,
|
|
4251
|
-
sa: forward ? piece.s0 : piece.s1,
|
|
4252
|
-
sb: forward ? piece.s1 : piece.s0,
|
|
4253
|
-
...hull(pts)
|
|
4254
|
-
};
|
|
4255
|
-
group.push(edge);
|
|
4256
|
-
edges.push(edge);
|
|
4257
|
-
}
|
|
4258
|
-
edge.mult[piece.operand] += forward ? 1 : -1;
|
|
4259
|
-
}
|
|
4260
|
-
const live = edges.filter((e) => e.mult.some((m) => m !== 0));
|
|
4261
|
-
const byY = new BandIndex(live, (e) => e.minY, (e) => e.maxY);
|
|
4262
|
-
const byX = new BandIndex(live, (e) => e.minX, (e) => e.maxX);
|
|
4263
|
-
const right = [];
|
|
4264
|
-
const left = [];
|
|
4265
|
-
for (let i = 0; i < live.length; i++) {
|
|
4266
|
-
const edge = live[i];
|
|
4267
|
-
const mid = segmentPoint(edge.pts, 0.5);
|
|
4268
|
-
const tan = segmentTangent(edge.pts, 0.5);
|
|
4269
|
-
const horizontal = Math.abs(tan[1]) < Math.abs(tan[0]);
|
|
4270
|
-
const winding = new Array(count).fill(0);
|
|
4271
|
-
const [mx, my] = mid;
|
|
4272
|
-
for (const j of horizontal ? byX.at(mx) : byY.at(my)) {
|
|
4273
|
-
if (j === i) continue;
|
|
4274
|
-
const o = live[j];
|
|
4275
|
-
let dir = 0;
|
|
4276
|
-
if (!horizontal) {
|
|
4277
|
-
if (o.maxX <= mx) continue;
|
|
4278
|
-
const ay = o.a.y;
|
|
4279
|
-
const by = o.b.y;
|
|
4280
|
-
if (ay <= my && by > my) dir = 1;
|
|
4281
|
-
else if (by <= my && ay > my) dir = -1;
|
|
4282
|
-
else continue;
|
|
4283
|
-
if (o.minX <= mx) {
|
|
4284
|
-
const t = solveMonotone(o.pts, 1, my);
|
|
4285
|
-
if (!(segmentPoint(o.pts, t)[0] > mx)) continue;
|
|
4286
|
-
}
|
|
4287
|
-
} else {
|
|
4288
|
-
if (o.maxY <= my) continue;
|
|
4289
|
-
const ax = o.a.x;
|
|
4290
|
-
const bx = o.b.x;
|
|
4291
|
-
if (bx < mx && mx <= ax) dir = 1;
|
|
4292
|
-
else if (ax < mx && mx <= bx) dir = -1;
|
|
4293
|
-
else continue;
|
|
4294
|
-
if (o.minY <= my) {
|
|
4295
|
-
const t = solveMonotone(o.pts, 0, mx);
|
|
4296
|
-
if (!(segmentPoint(o.pts, t)[1] > my)) continue;
|
|
4297
|
-
}
|
|
4298
|
-
}
|
|
4299
|
-
for (let k = 0; k < count; k++) if (o.mult[k] !== 0) winding[k] += dir * o.mult[k];
|
|
4300
|
-
}
|
|
4301
|
-
const sideIsRight = horizontal ? edge.b.x < edge.a.x : edge.b.y > edge.a.y;
|
|
4302
|
-
const r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);
|
|
4303
|
-
right.push(r);
|
|
4304
|
-
left.push(r.map((w, k) => w + edge.mult[k]));
|
|
4305
|
-
}
|
|
4306
|
-
return { edges: live, left, right, fillRules: operands.map((o) => o.fillRule), sources, eps, scale: scale2 };
|
|
4307
|
-
}
|
|
4308
|
-
var BandIndex = class {
|
|
4309
|
-
bands;
|
|
4310
|
-
min;
|
|
4311
|
-
size;
|
|
4312
|
-
constructor(edges, lo, hi) {
|
|
4313
|
-
let min = Infinity;
|
|
4314
|
-
let max = -Infinity;
|
|
4315
|
-
for (const e of edges) {
|
|
4316
|
-
min = Math.min(min, lo(e));
|
|
4317
|
-
max = Math.max(max, hi(e));
|
|
4318
|
-
}
|
|
4319
|
-
const count = Math.max(1, Math.min(4096, Math.ceil(Math.sqrt(edges.length) * 2)));
|
|
4320
|
-
this.min = min;
|
|
4321
|
-
this.size = max > min ? (max - min) / count : 1;
|
|
4322
|
-
this.bands = Array.from({ length: count }, () => []);
|
|
4323
|
-
edges.forEach((e, i) => {
|
|
4324
|
-
const from = this.index(lo(e));
|
|
4325
|
-
const to = this.index(hi(e));
|
|
4326
|
-
for (let b = from; b <= to; b++) this.bands[b].push(i);
|
|
4327
|
-
});
|
|
4328
|
-
}
|
|
4329
|
-
index(v) {
|
|
4330
|
-
const k = Math.floor((v - this.min) / this.size);
|
|
4331
|
-
return Math.max(0, Math.min(this.bands.length - 1, Number.isFinite(k) ? k : 0));
|
|
4332
|
-
}
|
|
4333
|
-
at(v) {
|
|
4334
|
-
return this.bands[this.index(v)] ?? [];
|
|
4335
|
-
}
|
|
4336
|
-
};
|
|
4337
|
-
function insideFlags2(windings, fillRules) {
|
|
4338
|
-
return windings.map((w, k) => fillRules[k] === "evenodd" ? (w & 1) === 1 : w !== 0);
|
|
4339
|
-
}
|
|
4340
|
-
var TWO_PI = Math.PI * 2;
|
|
4341
|
-
var TIE = 1e-3;
|
|
4342
|
-
function directed(edge, forward) {
|
|
4343
|
-
const pts = forward ? edge.pts : [...edge.pts].reverse();
|
|
4344
|
-
const out = segmentTangent(pts, 0);
|
|
4345
|
-
const end = segmentTangent(pts, 1);
|
|
4346
|
-
return {
|
|
4347
|
-
from: forward ? edge.a : edge.b,
|
|
4348
|
-
to: forward ? edge.b : edge.a,
|
|
4349
|
-
pts,
|
|
4350
|
-
src: edge.src,
|
|
4351
|
-
sFrom: forward ? edge.sa : edge.sb,
|
|
4352
|
-
sTo: forward ? edge.sb : edge.sa,
|
|
4353
|
-
outAngle: Math.atan2(out[1], out[0]),
|
|
4354
|
-
backAngle: Math.atan2(-end[1], -end[0]),
|
|
4355
|
-
length: dist2(pts[0], pts[pts.length - 1]) + (pts.length > 2 ? flatness(pts) : 0)
|
|
4356
|
-
};
|
|
4357
|
-
}
|
|
4358
|
-
function chordAngle(pts, t) {
|
|
4359
|
-
const p = segmentPoint(pts, t);
|
|
4360
|
-
return Math.atan2(p[1] - pts[0][1], p[0] - pts[0][0]);
|
|
4361
|
-
}
|
|
4362
|
-
function mod2pi(a) {
|
|
4363
|
-
const r = a - Math.floor(a / TWO_PI) * TWO_PI;
|
|
4364
|
-
return r >= TWO_PI ? 0 : r;
|
|
4365
|
-
}
|
|
4366
|
-
function traceRings2(edges, scale2) {
|
|
4367
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
4368
|
-
for (const e of edges) {
|
|
4369
|
-
const list = outgoing.get(e.from.id);
|
|
4370
|
-
if (list) list.push(e);
|
|
4371
|
-
else outgoing.set(e.from.id, [e]);
|
|
4372
|
-
}
|
|
4373
|
-
const next = (incoming) => {
|
|
4374
|
-
const candidates = outgoing.get(incoming.to.id)?.filter((c) => !used.has(c) || c === start);
|
|
4375
|
-
if (!candidates || candidates.length === 0) return void 0;
|
|
4376
|
-
if (candidates.length === 1) return candidates[0];
|
|
4377
|
-
const turns = candidates.map((c) => mod2pi(incoming.backAngle - c.outAngle));
|
|
4378
|
-
const tied = (x) => x < TIE || x > TWO_PI - TIE;
|
|
4379
|
-
let ambiguous = turns.some(tied);
|
|
4380
|
-
if (!ambiguous) {
|
|
4381
|
-
const sorted = [...turns].sort((a, b) => a - b);
|
|
4382
|
-
for (let i = 1; i < sorted.length; i++) if (sorted[i] - sorted[i - 1] < TIE) ambiguous = true;
|
|
4383
|
-
}
|
|
4384
|
-
if (ambiguous) {
|
|
4385
|
-
const back = [...incoming.pts].reverse();
|
|
4386
|
-
let delta = Math.min(incoming.length, ...candidates.map((c) => c.length));
|
|
4387
|
-
delta = Math.min(delta * 0.25, scale2 * 0.02);
|
|
4388
|
-
const frac = (e) => Math.min(1, Math.max(1e-6, delta / Math.max(e.length, 1e-300)));
|
|
4389
|
-
const backChord = chordAngle(back, frac(incoming));
|
|
4390
|
-
for (let i = 0; i < candidates.length; i++) {
|
|
4391
|
-
turns[i] = mod2pi(backChord - chordAngle(candidates[i].pts, frac(candidates[i])));
|
|
4392
|
-
if (turns[i] < 1e-12) turns[i] = TWO_PI;
|
|
4393
|
-
}
|
|
4394
|
-
} else {
|
|
4395
|
-
for (let i = 0; i < turns.length; i++) if (turns[i] < 1e-15) turns[i] = TWO_PI;
|
|
4396
|
-
}
|
|
4397
|
-
let best = 0;
|
|
4398
|
-
for (let i = 1; i < candidates.length; i++) if (turns[i] < turns[best]) best = i;
|
|
4399
|
-
return candidates[best];
|
|
4400
|
-
};
|
|
4401
|
-
const used = /* @__PURE__ */ new Set();
|
|
4402
|
-
const rings = [];
|
|
4403
|
-
let start = null;
|
|
4404
|
-
for (const first of edges) {
|
|
4405
|
-
if (used.has(first)) continue;
|
|
4406
|
-
start = first;
|
|
4407
|
-
const ring = [];
|
|
4408
|
-
let edge = first;
|
|
4409
|
-
let guard = edges.length + 1;
|
|
4410
|
-
let closed = false;
|
|
4411
|
-
while (edge && guard-- > 0) {
|
|
4412
|
-
used.add(edge);
|
|
4413
|
-
ring.push(edge);
|
|
4414
|
-
edge = next(edge);
|
|
4415
|
-
if (edge === first) {
|
|
4416
|
-
closed = true;
|
|
4417
|
-
break;
|
|
4418
|
-
}
|
|
4419
|
-
}
|
|
4420
|
-
if (!closed) throw new PathOpsError("result outline does not close");
|
|
4421
|
-
rings.push(ring);
|
|
4422
|
-
}
|
|
4423
|
-
return rings;
|
|
4424
|
-
}
|
|
4425
|
-
function mergeable(a, b, sources, eps) {
|
|
4426
|
-
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;
|
|
4427
|
-
if (a.pts.length === 2 && b.pts.length === 2) {
|
|
4428
|
-
const p = a.pts[0];
|
|
4429
|
-
const q = b.pts[1];
|
|
4430
|
-
const m = a.pts[1];
|
|
4431
|
-
const chord = sub2(q, p);
|
|
4432
|
-
const len = Math.hypot(chord[0], chord[1]);
|
|
4433
|
-
if (len === 0) return false;
|
|
4434
|
-
const off = Math.abs(cross(chord, sub2(m, p))) / len;
|
|
4435
|
-
const along = dot2(chord, sub2(m, p));
|
|
4436
|
-
return off <= eps && along > 0 && along < len * len;
|
|
4437
|
-
}
|
|
4438
|
-
void sources;
|
|
4439
|
-
return false;
|
|
4440
|
-
}
|
|
4441
|
-
function rebuildRing(ring, sources, eps) {
|
|
4442
|
-
const n = ring.length;
|
|
4443
|
-
let startIndex = 0;
|
|
4444
|
-
for (let k = 0; k < n; k++) {
|
|
4445
|
-
if (!mergeable(ring[(k + n - 1) % n], ring[k], sources, eps)) {
|
|
4446
|
-
startIndex = k;
|
|
4447
|
-
break;
|
|
4448
|
-
}
|
|
4449
|
-
}
|
|
4450
|
-
const ordered = [...ring.slice(startIndex), ...ring.slice(0, startIndex)];
|
|
4451
|
-
const out = [];
|
|
4452
|
-
let i = 0;
|
|
4453
|
-
while (i < ordered.length) {
|
|
4454
|
-
const first = ordered[i];
|
|
4455
|
-
let j = i + 1;
|
|
4456
|
-
while (j < ordered.length && mergeable(ordered[j - 1], ordered[j], sources, eps)) j++;
|
|
4457
|
-
const last = ordered[j - 1];
|
|
4458
|
-
if (j - i === 1) {
|
|
4459
|
-
out.push(first.pts);
|
|
4460
|
-
} else if (first.pts.length === 2 && ordered.slice(i, j).every((e) => e.pts.length === 2)) {
|
|
4461
|
-
out.push([first.from.p, last.to.p]);
|
|
4462
|
-
} else {
|
|
4463
|
-
const source = sources[first.src];
|
|
4464
|
-
out.push(snapEnds(subSegment(source.seg, first.sFrom, last.sTo), first.from.p, last.to.p));
|
|
4465
|
-
}
|
|
4466
|
-
i = j;
|
|
4467
|
-
}
|
|
4468
|
-
return out;
|
|
4469
|
-
}
|
|
4470
|
-
function extractRings(arr, isInside) {
|
|
4471
|
-
const kept = [];
|
|
4472
|
-
arr.edges.forEach((edge, i) => {
|
|
4473
|
-
const l = isInside(insideFlags2(arr.left[i], arr.fillRules));
|
|
4474
|
-
const r = isInside(insideFlags2(arr.right[i], arr.fillRules));
|
|
4475
|
-
if (l === r) return;
|
|
4476
|
-
kept.push(directed(edge, l));
|
|
4477
|
-
});
|
|
4478
|
-
const rings = [];
|
|
4479
|
-
const sliver = Math.max(arr.eps * 10, 1e-7 * arr.scale);
|
|
4480
|
-
for (const ring of traceRings2(kept, arr.scale)) {
|
|
4481
|
-
const segs = rebuildRing(ring, arr.sources, arr.eps);
|
|
4482
|
-
const area = contourSignedArea(segs);
|
|
4483
|
-
let perimeter = 0;
|
|
4484
|
-
for (const s of segs) perimeter += s.length === 2 ? dist2(s[0], s[1]) : segmentLength(s, 0, 1, 1e-6);
|
|
4485
|
-
if (!(perimeter > 0) || 2 * Math.abs(area) / perimeter < sliver) continue;
|
|
4486
|
-
rings.push({ segs, area });
|
|
4487
|
-
}
|
|
4488
|
-
return rings;
|
|
4489
|
-
}
|
|
4490
|
-
function ringSample(ring) {
|
|
4491
|
-
let best = ring.segs[0];
|
|
4492
|
-
let bestLen = -1;
|
|
4493
|
-
for (const s of ring.segs) {
|
|
4494
|
-
const l = dist2(s[0], s[s.length - 1]);
|
|
4495
|
-
if (l > bestLen) {
|
|
4496
|
-
best = s;
|
|
4497
|
-
bestLen = l;
|
|
4498
|
-
}
|
|
4499
|
-
}
|
|
4500
|
-
return segmentPoint(best, 0.5);
|
|
4501
|
-
}
|
|
4502
|
-
function groupRings(rings) {
|
|
4503
|
-
const outers = rings.filter((r) => r.area > 0).sort((a, b) => b.area - a.area);
|
|
4504
|
-
const holes = rings.filter((r) => r.area < 0);
|
|
4505
|
-
const groups = outers.map((o) => [o]);
|
|
4506
|
-
for (const hole of holes) {
|
|
4507
|
-
const sample = ringSample(hole);
|
|
4508
|
-
for (let i = outers.length - 1; i >= 0; i--) {
|
|
4509
|
-
if (outers[i].area >= -hole.area && windingNumber([outers[i].segs], sample) !== 0) {
|
|
4510
|
-
groups[i].push(hole);
|
|
4511
|
-
break;
|
|
4512
|
-
}
|
|
4513
|
-
}
|
|
4514
|
-
}
|
|
4515
|
-
return groups;
|
|
4516
|
-
}
|
|
4517
|
-
function ringsToPath2(rings) {
|
|
4518
|
-
return contoursToPath(rings.map((r) => ({ segments: r.segs, closed: true })));
|
|
4519
|
-
}
|
|
4520
|
-
function predicate2(operation) {
|
|
4521
|
-
switch (operation) {
|
|
4522
|
-
case "union":
|
|
4523
|
-
return (f) => f.some(Boolean);
|
|
4524
|
-
case "intersection":
|
|
4525
|
-
return (f) => f.length > 0 && f.every(Boolean);
|
|
4526
|
-
case "difference":
|
|
4527
|
-
return (f) => f[0] === true && !f.slice(1).some(Boolean);
|
|
4528
|
-
case "xor":
|
|
4529
|
-
return (f) => f.filter(Boolean).length % 2 === 1;
|
|
4530
|
-
}
|
|
4531
|
-
}
|
|
4532
|
-
function flattenOperand(input, tolerance) {
|
|
4533
|
-
const path = isOperandObject(input) ? input.path : input;
|
|
4534
|
-
const fillRule = isOperandObject(input) ? input.fillRule ?? "nonzero" : "nonzero";
|
|
4535
|
-
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])));
|
|
4536
|
-
return { rings, fillRule };
|
|
4537
|
-
}
|
|
4538
|
-
function multiToPath(multi, tolerance) {
|
|
4539
|
-
if (multi.length === 0) return [];
|
|
4540
|
-
return toBezierPath(polygonToSvgPath(multi, { curves: "fit", curveTolerance: tolerance, precision: 9 }));
|
|
4541
|
-
}
|
|
4542
|
-
function reasonOf(error) {
|
|
4543
|
-
return error instanceof Error ? error.message : String(error);
|
|
4544
|
-
}
|
|
4545
|
-
function pathBoolean(operation, operands, options = {}) {
|
|
4546
|
-
if (operands.length === 0) return { path: [], engine: "curve" };
|
|
4547
|
-
try {
|
|
4548
|
-
const arr = buildArrangement(operands, options);
|
|
4549
|
-
return { path: ringsToPath2(extractRings(arr, predicate2(operation))), engine: "curve" };
|
|
4550
|
-
} catch (error) {
|
|
4551
|
-
const reason = reasonOf(error);
|
|
4552
|
-
if (reason === NON_FINITE) return { path: [], engine: "curve", error: reason };
|
|
4553
|
-
if (options.fallback === false) throw error;
|
|
4554
|
-
options.onFallback?.(reason);
|
|
4555
|
-
const tolerance = options.fallbackTolerance ?? 0.05;
|
|
4556
|
-
const multi = polygonBoolean(
|
|
4557
|
-
operation,
|
|
4558
|
-
operands.map((o) => flattenOperand(o, tolerance)),
|
|
4559
|
-
{ epsilon: options.epsilon }
|
|
4560
|
-
);
|
|
4561
|
-
return { path: multiToPath(multi, tolerance), engine: "polygon", fallbackReason: reason };
|
|
4562
|
-
}
|
|
4563
|
-
}
|
|
4564
|
-
var unitePaths = (a, b, options) => pathBoolean("union", [a, b], options).path;
|
|
4565
|
-
var intersectPaths = (a, b, options) => pathBoolean("intersection", [a, b], options).path;
|
|
4566
|
-
var subtractPaths = (a, b, options) => pathBoolean("difference", [a, b], options).path;
|
|
4567
|
-
var excludePaths = (a, b, options) => pathBoolean("xor", [a, b], options).path;
|
|
4568
|
-
function dividePaths(operands, options = {}) {
|
|
4569
|
-
if (operands.length === 0) return { pieces: [], engine: "curve" };
|
|
4570
|
-
try {
|
|
4571
|
-
const arr = buildArrangement(operands, options);
|
|
4572
|
-
const signatures = /* @__PURE__ */ new Map();
|
|
4573
|
-
const note = (windings) => {
|
|
4574
|
-
const flags = insideFlags2(windings, arr.fillRules);
|
|
4575
|
-
if (!flags.some(Boolean)) return;
|
|
4576
|
-
const key = flags.map((f) => f ? "1" : "0").join("");
|
|
4577
|
-
if (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => f ? [k] : []));
|
|
4578
|
-
};
|
|
4579
|
-
arr.edges.forEach((_, i) => {
|
|
4580
|
-
note(arr.left[i]);
|
|
4581
|
-
note(arr.right[i]);
|
|
4582
|
-
});
|
|
4583
|
-
const pieces = [];
|
|
4584
|
-
for (const key of [...signatures.keys()].sort()) {
|
|
4585
|
-
const rings = extractRings(arr, (flags) => flags.map((f) => f ? "1" : "0").join("") === key);
|
|
4586
|
-
const groups = groupRings(rings).sort((a, b) => b[0].area - a[0].area);
|
|
4587
|
-
for (const group of groups) pieces.push({ path: ringsToPath2(group), operands: signatures.get(key) });
|
|
4588
|
-
}
|
|
4589
|
-
return { pieces, engine: "curve" };
|
|
4590
|
-
} catch (error) {
|
|
4591
|
-
const reason = reasonOf(error);
|
|
4592
|
-
if (reason === NON_FINITE) return { pieces: [], engine: "curve", error: reason };
|
|
4593
|
-
if (options.fallback === false) throw error;
|
|
4594
|
-
options.onFallback?.(reason);
|
|
4595
|
-
const tolerance = options.fallbackTolerance ?? 0.05;
|
|
4596
|
-
const pieces = dividePolygons(
|
|
4597
|
-
operands.map((o) => flattenOperand(o, tolerance)),
|
|
4598
|
-
{ epsilon: options.epsilon }
|
|
4599
|
-
).map((p) => ({ path: multiToPath([p.polygon], tolerance), operands: p.operands }));
|
|
4600
|
-
return { pieces, engine: "polygon", fallbackReason: reason };
|
|
4601
|
-
}
|
|
4602
|
-
}
|
|
4603
|
-
function normalizeBezierPath(path, fillRule = "nonzero", options = {}) {
|
|
4604
|
-
return pathBoolean("union", [{ path, fillRule }], options).path;
|
|
4605
|
-
}
|
|
4606
|
-
function bezierPathArea(path, fillRule = "nonzero", options = {}) {
|
|
4607
|
-
const normalized = normalizeBezierPath(path, fillRule, options);
|
|
4608
|
-
let sum = 0;
|
|
4609
|
-
for (const contour of pathToContours(normalized)) sum += contourSignedArea(closeContour(contour, true));
|
|
4610
|
-
return Math.abs(sum);
|
|
4611
|
-
}
|
|
4612
|
-
function pathBooleanRings(operation, operands, options = {}) {
|
|
4613
|
-
const arr = buildArrangement(operands, options);
|
|
4614
|
-
const rings = extractRings(arr, typeof operation === "function" ? operation : predicate2(operation));
|
|
4615
|
-
const order = rings.map((_, i) => i).sort((a, b) => Math.abs(rings[a].area) - Math.abs(rings[b].area));
|
|
4616
|
-
return rings.map((ring, i) => {
|
|
4617
|
-
const sample = ringSample(ring);
|
|
4618
|
-
let parent = -1;
|
|
4619
|
-
for (const j of order) {
|
|
4620
|
-
if (j === i || Math.abs(rings[j].area) <= Math.abs(ring.area)) continue;
|
|
4621
|
-
if (windingNumber([rings[j].segs], sample) !== 0) {
|
|
4622
|
-
parent = j;
|
|
4623
|
-
break;
|
|
4624
|
-
}
|
|
4625
|
-
}
|
|
4626
|
-
return { segments: ring.segs, area: ring.area, parent };
|
|
4627
|
-
});
|
|
4628
|
-
}
|
|
4629
|
-
function nestedRingsToPath(rings, keep = () => true) {
|
|
4630
|
-
const contours = [];
|
|
4631
|
-
rings.forEach((r, i) => {
|
|
4632
|
-
if (keep(i)) contours.push({ segments: r.segments, closed: true });
|
|
4633
|
-
});
|
|
4634
|
-
return contoursToPath(contours);
|
|
4635
|
-
}
|
|
4636
|
-
|
|
4637
1729
|
// src/canvas/pathops/stroke.ts
|
|
4638
1730
|
var DEFAULT_TOLERANCE = 0.01;
|
|
4639
1731
|
var normal = (t) => [-t[1], t[0]];
|
|
@@ -4643,30 +1735,30 @@ function raise(seg) {
|
|
|
4643
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];
|
|
4644
1736
|
}
|
|
4645
1737
|
function isDegenerate(seg, tol) {
|
|
4646
|
-
return seg.every((p) =>
|
|
1738
|
+
return seg.every((p) => dist(p, seg[0]) <= tol);
|
|
4647
1739
|
}
|
|
4648
1740
|
function offsetSegment(seg, d, tol, out, depth = 0) {
|
|
4649
1741
|
if (seg.length === 2) {
|
|
4650
1742
|
const n = normal(segmentTangent(seg, 0));
|
|
4651
|
-
out.push([
|
|
1743
|
+
out.push([add(seg[0], scale(n, d)), add(seg[1], scale(n, d))]);
|
|
4652
1744
|
return;
|
|
4653
1745
|
}
|
|
4654
1746
|
const c = raise(seg);
|
|
4655
1747
|
const t0 = segmentTangent(c, 0);
|
|
4656
1748
|
const t1 = segmentTangent(c, 1);
|
|
4657
|
-
const q0 =
|
|
4658
|
-
const q3 =
|
|
1749
|
+
const q0 = add(c[0], scale(normal(t0), d));
|
|
1750
|
+
const q3 = add(c[3], scale(normal(t1), d));
|
|
4659
1751
|
const k0 = segmentCurvature(c, 0);
|
|
4660
1752
|
const k1 = segmentCurvature(c, 1);
|
|
4661
1753
|
const f0 = Number.isFinite(k0) ? 1 - d * k0 : 1;
|
|
4662
1754
|
const f1 = Number.isFinite(k1) ? 1 - d * k1 : 1;
|
|
4663
|
-
const q1 =
|
|
4664
|
-
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));
|
|
4665
1757
|
const approx = [q0, q1, q2, q3];
|
|
4666
1758
|
if (depth < 10) {
|
|
4667
1759
|
let err = 0;
|
|
4668
1760
|
for (const t of [0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875]) {
|
|
4669
|
-
const target =
|
|
1761
|
+
const target = add(segmentPoint(c, t), scale(normal(segmentTangent(c, t)), d));
|
|
4670
1762
|
err = Math.max(err, segmentNearest(approx, target).distance);
|
|
4671
1763
|
if (err > tol) break;
|
|
4672
1764
|
}
|
|
@@ -4710,10 +1802,10 @@ function lineTo(out, from, to) {
|
|
|
4710
1802
|
if (!samePoint(from, to)) out.push([from, to]);
|
|
4711
1803
|
}
|
|
4712
1804
|
function addJoin(out, p, tin, tout, d, style) {
|
|
4713
|
-
const from =
|
|
4714
|
-
const to =
|
|
1805
|
+
const from = add(p, scale(normal(tin), d));
|
|
1806
|
+
const to = add(p, scale(normal(tout), d));
|
|
4715
1807
|
const turn = cross(tin, tout);
|
|
4716
|
-
const cos =
|
|
1808
|
+
const cos = dot(tin, tout);
|
|
4717
1809
|
if (Math.abs(turn) < 1e-9 && cos > 0) {
|
|
4718
1810
|
lineTo(out, from, to);
|
|
4719
1811
|
return;
|
|
@@ -4727,7 +1819,7 @@ function addJoin(out, p, tin, tout, d, style) {
|
|
|
4727
1819
|
}
|
|
4728
1820
|
const theta = reversal ? d > 0 ? -Math.PI : Math.PI : Math.atan2(turn, cos);
|
|
4729
1821
|
if (style.join === "round") {
|
|
4730
|
-
const v =
|
|
1822
|
+
const v = sub(from, p);
|
|
4731
1823
|
pushArc(out, p, Math.abs(d), Math.atan2(v[1], v[0]), theta, from, to);
|
|
4732
1824
|
return;
|
|
4733
1825
|
}
|
|
@@ -4737,8 +1829,8 @@ function addJoin(out, p, tin, tout, d, style) {
|
|
|
4737
1829
|
if (ratio <= style.miterLimit) {
|
|
4738
1830
|
const nin = normal(tin);
|
|
4739
1831
|
const nout = normal(tout);
|
|
4740
|
-
const s = d / (1 +
|
|
4741
|
-
const miter =
|
|
1832
|
+
const s = d / (1 + dot(nin, nout));
|
|
1833
|
+
const miter = add(p, scale(add(nin, nout), s));
|
|
4742
1834
|
lineTo(out, from, miter);
|
|
4743
1835
|
lineTo(out, miter, to);
|
|
4744
1836
|
return;
|
|
@@ -4772,13 +1864,13 @@ function reverseChain(chain) {
|
|
|
4772
1864
|
}
|
|
4773
1865
|
function addCap(out, p, t, h, cap) {
|
|
4774
1866
|
const n = normal(t);
|
|
4775
|
-
const from =
|
|
4776
|
-
const to =
|
|
1867
|
+
const from = add(p, scale(n, h));
|
|
1868
|
+
const to = add(p, scale(n, -h));
|
|
4777
1869
|
if (cap === "round") {
|
|
4778
1870
|
pushArc(out, p, h, Math.atan2(n[1], n[0]), -Math.PI, from, to);
|
|
4779
1871
|
} else if (cap === "square") {
|
|
4780
|
-
const a =
|
|
4781
|
-
const b =
|
|
1872
|
+
const a = add(from, scale(t, h));
|
|
1873
|
+
const b = add(to, scale(t, h));
|
|
4782
1874
|
lineTo(out, from, a);
|
|
4783
1875
|
lineTo(out, a, b);
|
|
4784
1876
|
lineTo(out, b, to);
|
|
@@ -4823,7 +1915,7 @@ function strokeContour(contour, h, style, tol) {
|
|
|
4823
1915
|
if (ring.length > 0) joinTo(ring[0][0]);
|
|
4824
1916
|
return [ring];
|
|
4825
1917
|
}
|
|
4826
|
-
function
|
|
1918
|
+
function ringsToPath(rings) {
|
|
4827
1919
|
return contoursToPath(rings.filter((r) => r.length > 0).map((segments) => ({ segments, closed: true })));
|
|
4828
1920
|
}
|
|
4829
1921
|
var MAX_DASHES = 2e5;
|
|
@@ -4958,7 +2050,7 @@ function mergeSmoothRuns(path, tolerance) {
|
|
|
4958
2050
|
if (a.length !== 4 || b.length !== 4) return false;
|
|
4959
2051
|
const ta = segmentTangent(a, 1);
|
|
4960
2052
|
const tb = segmentTangent(b, 0);
|
|
4961
|
-
return
|
|
2053
|
+
return dot(ta, tb) > 0.9998 && Math.abs(cross(ta, tb)) < 0.02;
|
|
4962
2054
|
};
|
|
4963
2055
|
let start = 0;
|
|
4964
2056
|
for (let k = 0; k < n; k++) {
|
|
@@ -5009,11 +2101,11 @@ function strokeOutlineRings(input, options) {
|
|
|
5009
2101
|
const dashed = dashContours(contours, options.dash, options.dashOffset ?? 0);
|
|
5010
2102
|
if (dashed) {
|
|
5011
2103
|
contours = dashed.contours;
|
|
5012
|
-
for (const
|
|
2104
|
+
for (const dot2 of dashed.dots) rings.push(...dotRings(dot2.point, dot2.tangent, h, style.cap));
|
|
5013
2105
|
}
|
|
5014
2106
|
}
|
|
5015
2107
|
for (const contour of contours) rings.push(...strokeContour(contour, h, style, tol));
|
|
5016
|
-
return
|
|
2108
|
+
return ringsToPath(rings);
|
|
5017
2109
|
}
|
|
5018
2110
|
function outlineStroke(input, options) {
|
|
5019
2111
|
const h = options.width / 2;
|
|
@@ -5313,12 +2405,18 @@ function revertShapeCombination(nodes, change, previous) {
|
|
|
5313
2405
|
}
|
|
5314
2406
|
var INVISIBLE_PAINT = /* @__PURE__ */ new Set(["none", "transparent"]);
|
|
5315
2407
|
function hasVisibleStroke(shape) {
|
|
5316
|
-
if (!shape
|
|
2408
|
+
if (!shape) return false;
|
|
2409
|
+
const stroke = shape.strokeGradient ?? shape.stroke;
|
|
2410
|
+
if (stroke === void 0 || stroke === null) return false;
|
|
2411
|
+
if (typeof stroke === "string" && INVISIBLE_PAINT.has(stroke.trim().toLowerCase())) return false;
|
|
5317
2412
|
const width = shape.strokeWidth ?? 1;
|
|
5318
2413
|
return Number.isFinite(width) && width > 0;
|
|
5319
2414
|
}
|
|
5320
2415
|
function hasVisibleFill(shape) {
|
|
5321
|
-
|
|
2416
|
+
if (shape.fillGradient) return true;
|
|
2417
|
+
if (shape.fill === void 0) return true;
|
|
2418
|
+
if (typeof shape.fill === "object" && shape.fill !== null) return true;
|
|
2419
|
+
return !INVISIBLE_PAINT.has(String(shape.fill).trim().toLowerCase());
|
|
5322
2420
|
}
|
|
5323
2421
|
function shapeTargets(nodes, ids) {
|
|
5324
2422
|
const idSet = new Set(ids);
|
|
@@ -5557,8 +2655,8 @@ function snapResize(rect, handle, targets, options = {}) {
|
|
|
5557
2655
|
const ratio = rect.height > 0 ? rect.width / rect.height : 0;
|
|
5558
2656
|
if (options.keepAspectRatio && ratio > 0 && (hitX || hitY)) {
|
|
5559
2657
|
if (axes.x !== 0 && axes.y !== 0) {
|
|
5560
|
-
const
|
|
5561
|
-
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);
|
|
5562
2660
|
if (useX && hitX) {
|
|
5563
2661
|
setEdge(true, hitX.value);
|
|
5564
2662
|
const height = (nx1 - nx0) / ratio;
|
|
@@ -5937,13 +3035,13 @@ function layoutFrameInner(s, frame) {
|
|
|
5937
3035
|
let used = 0;
|
|
5938
3036
|
items.forEach((it, i) => {
|
|
5939
3037
|
const sz = it.fillMain ? it.minMain ?? 0 : it.main;
|
|
5940
|
-
const
|
|
5941
|
-
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) {
|
|
5942
3040
|
lines.push(cur);
|
|
5943
3041
|
cur = [];
|
|
5944
3042
|
used = sz;
|
|
5945
3043
|
} else {
|
|
5946
|
-
used +=
|
|
3044
|
+
used += add2;
|
|
5947
3045
|
}
|
|
5948
3046
|
cur.push(i);
|
|
5949
3047
|
});
|
|
@@ -6161,11 +3259,11 @@ function resolveTracks(tracks, content, avail) {
|
|
|
6161
3259
|
let used = out.reduce((sum, v, i) => trackSpec(tracks, i).sizing === "fill" ? sum : sum + v, 0);
|
|
6162
3260
|
while (flex.length > 0) {
|
|
6163
3261
|
const total = flex.reduce((sum, i) => sum + fr(i), 0);
|
|
6164
|
-
const
|
|
6165
|
-
const open = flex.filter((i) =>
|
|
6166
|
-
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]);
|
|
6167
3265
|
if (frozen.length === 0) {
|
|
6168
|
-
for (const i of open) out[i] =
|
|
3266
|
+
for (const i of open) out[i] = unit * fr(i);
|
|
6169
3267
|
break;
|
|
6170
3268
|
}
|
|
6171
3269
|
used += frozen.reduce((sum, i) => sum + out[i], 0);
|
|
@@ -9350,8 +6448,8 @@ function sizeOf(node) {
|
|
|
9350
6448
|
return { width: node.width, height: node.height, styleWidth: s.width, styleHeight: s.height };
|
|
9351
6449
|
}
|
|
9352
6450
|
var splitGroup = (group) => {
|
|
9353
|
-
const
|
|
9354
|
-
return
|
|
6451
|
+
const dot2 = group.indexOf(".");
|
|
6452
|
+
return dot2 < 0 ? [group, ""] : [group.slice(0, dot2), group.slice(dot2 + 1)];
|
|
9355
6453
|
};
|
|
9356
6454
|
function readOverrideGroup(node, group) {
|
|
9357
6455
|
const data = dataOf(node);
|
|
@@ -9464,7 +6562,7 @@ function listGroups(node, out) {
|
|
|
9464
6562
|
if (LINK_KEYS.has(k)) continue;
|
|
9465
6563
|
const prefix = NESTED_KEYS[k];
|
|
9466
6564
|
if (prefix) {
|
|
9467
|
-
if (isRecord(v)) for (const
|
|
6565
|
+
if (isRecord(v)) for (const sub2 of Object.keys(v)) out.add(`${prefix}.${sub2}`);
|
|
9468
6566
|
continue;
|
|
9469
6567
|
}
|
|
9470
6568
|
out.add(`data.${k}`);
|
|
@@ -10777,11 +7875,11 @@ function restoreCanvasMainComponent(nodes, id, options = {}) {
|
|
|
10777
7875
|
if (g.get(inst.componentId)) return fail(nodes, "invalid", "The main component still exists.");
|
|
10778
7876
|
if (enclosingInstanceIn(g, id) !== void 0) return fail(nodes, "nested-instance", "Select the outer instance.");
|
|
10779
7877
|
const compId = inst.componentId;
|
|
10780
|
-
const
|
|
7878
|
+
const sub2 = g.subtree(id);
|
|
10781
7879
|
const map = /* @__PURE__ */ new Map([[id, compId]]);
|
|
10782
7880
|
const taken = new Set(nodes.map((n) => n.id));
|
|
10783
7881
|
taken.add(compId);
|
|
10784
|
-
for (const d of
|
|
7882
|
+
for (const d of sub2) {
|
|
10785
7883
|
if (d === id) continue;
|
|
10786
7884
|
const src = enclosingInstanceIn(g, d) === id ? getComponentSourceId(g.get(d)) : void 0;
|
|
10787
7885
|
if (src && !taken.has(src)) {
|
|
@@ -10791,7 +7889,7 @@ function restoreCanvasMainComponent(nodes, id, options = {}) {
|
|
|
10791
7889
|
}
|
|
10792
7890
|
const nested = [];
|
|
10793
7891
|
const replaced = /* @__PURE__ */ new Map();
|
|
10794
|
-
for (const d of
|
|
7892
|
+
for (const d of sub2) {
|
|
10795
7893
|
let n = g.get(d);
|
|
10796
7894
|
const nid = map.get(d) ?? d;
|
|
10797
7895
|
const parentId = n.parentId !== void 0 ? map.get(n.parentId) ?? n.parentId : void 0;
|
|
@@ -10958,15 +8056,15 @@ function addCanvasVariant(nodes, setId, options = {}) {
|
|
|
10958
8056
|
const taken = new Set(g.byId.keys());
|
|
10959
8057
|
taken.add(id);
|
|
10960
8058
|
const map = /* @__PURE__ */ new Map([[fromId, id]]);
|
|
10961
|
-
const
|
|
10962
|
-
for (const d of
|
|
8059
|
+
const sub2 = g.subtree(fromId);
|
|
8060
|
+
for (const d of sub2) {
|
|
10963
8061
|
if (d === fromId) continue;
|
|
10964
8062
|
const nid = uniqueId(`${id}-${d}`, taken);
|
|
10965
8063
|
taken.add(nid);
|
|
10966
8064
|
map.set(d, nid);
|
|
10967
8065
|
}
|
|
10968
8066
|
const order = variantOrder([...edit.variants.map((v) => v.id), id], edit.values);
|
|
10969
|
-
const subSet = new Set(
|
|
8067
|
+
const subSet = new Set(sub2);
|
|
10970
8068
|
const copies = nodes.filter((n) => subSet.has(n.id)).map((n) => {
|
|
10971
8069
|
const next = { ...n, id: map.get(n.id) };
|
|
10972
8070
|
if (n.parentId !== void 0 && n.id !== fromId) next.parentId = map.get(n.parentId) ?? n.parentId;
|
|
@@ -11483,6 +8581,8 @@ function strokeToShapeNode(stroke, options = {}) {
|
|
|
11483
8581
|
});
|
|
11484
8582
|
const width = Math.max(bounds.width, 1);
|
|
11485
8583
|
const height = Math.max(bounds.height, 1);
|
|
8584
|
+
const strokePaint = stroke.style.gradient ?? stroke.style.color;
|
|
8585
|
+
const strokeGradient = stroke.style.gradient ?? (typeof stroke.style.color === "object" && stroke.style.color ? stroke.style.color : void 0);
|
|
11486
8586
|
return {
|
|
11487
8587
|
id: options.id ?? stroke.id,
|
|
11488
8588
|
type: "shape",
|
|
@@ -11498,9 +8598,12 @@ function strokeToShapeNode(stroke, options = {}) {
|
|
|
11498
8598
|
kind: "path",
|
|
11499
8599
|
d,
|
|
11500
8600
|
viewBox: [0, 0, width, height],
|
|
11501
|
-
stroke:
|
|
8601
|
+
stroke: strokePaint,
|
|
11502
8602
|
strokeWidth,
|
|
11503
8603
|
opacity: stroke.style.opacity,
|
|
8604
|
+
strokeLinecap: stroke.style.cap ?? "round",
|
|
8605
|
+
strokeLinejoin: stroke.style.join ?? "round",
|
|
8606
|
+
...strokeGradient ? { strokeGradient } : {},
|
|
11504
8607
|
fill: "none"
|
|
11505
8608
|
},
|
|
11506
8609
|
tool: stroke.tool
|
|
@@ -11520,8 +8623,8 @@ function findStrokesAtPoint(point, nodes, radius = 12) {
|
|
|
11520
8623
|
}
|
|
11521
8624
|
const localPoint = [point[0] - rect.x, point[1] - rect.y];
|
|
11522
8625
|
try {
|
|
11523
|
-
const
|
|
11524
|
-
if (
|
|
8626
|
+
const dist2 = distanceToBezierPath(shape.d, localPoint);
|
|
8627
|
+
if (dist2 <= effectiveRadius) {
|
|
11525
8628
|
matches2.push(node);
|
|
11526
8629
|
}
|
|
11527
8630
|
} catch {
|
|
@@ -11709,6 +8812,7 @@ export {
|
|
|
11709
8812
|
AnimationEditError,
|
|
11710
8813
|
AnimationPlayer,
|
|
11711
8814
|
CANVAS_COMPONENT_DRAG_TYPE,
|
|
8815
|
+
CANVAS_GRADIENT_PRESETS,
|
|
11712
8816
|
CANVAS_TOKEN_PROPERTIES,
|
|
11713
8817
|
CHILD_LAYOUT_DATA_KEY,
|
|
11714
8818
|
COMPONENT_DATA_KEY,
|
|
@@ -11741,6 +8845,7 @@ export {
|
|
|
11741
8845
|
addCanvasVariant,
|
|
11742
8846
|
addKeyframesAtTime,
|
|
11743
8847
|
alignNodes,
|
|
8848
|
+
angleToSvgLinearCoords,
|
|
11744
8849
|
animateNode,
|
|
11745
8850
|
applyAnimation,
|
|
11746
8851
|
applyAnimationOverrides,
|
|
@@ -11832,7 +8937,7 @@ export {
|
|
|
11832
8937
|
flattenSvgPath,
|
|
11833
8938
|
flowSide,
|
|
11834
8939
|
formatAnimationTime,
|
|
11835
|
-
|
|
8940
|
+
formatNumber,
|
|
11836
8941
|
formatOklab,
|
|
11837
8942
|
formatVariantName,
|
|
11838
8943
|
getAbsolutePosition,
|
|
@@ -11888,6 +8993,7 @@ export {
|
|
|
11888
8993
|
getVariantOptions,
|
|
11889
8994
|
getVariantSet,
|
|
11890
8995
|
getVariantValues,
|
|
8996
|
+
gradientToSvgDef,
|
|
11891
8997
|
handlePoint,
|
|
11892
8998
|
hasVisibleStroke,
|
|
11893
8999
|
inferResizeHandle,
|
|
@@ -11902,6 +9008,7 @@ export {
|
|
|
11902
9008
|
isAutoLayoutFrame,
|
|
11903
9009
|
isCanvasComponent,
|
|
11904
9010
|
isCanvasComponentSet,
|
|
9011
|
+
isCanvasGradient,
|
|
11905
9012
|
isCanvasInstance,
|
|
11906
9013
|
isCssExportExact,
|
|
11907
9014
|
isEventFromTextInput,
|
|
@@ -11953,6 +9060,7 @@ export {
|
|
|
11953
9060
|
parseCanvasClipboardData,
|
|
11954
9061
|
parseCanvasScenes,
|
|
11955
9062
|
parseCssEasing,
|
|
9063
|
+
parseCssGradient,
|
|
11956
9064
|
parseKeyframeClipboard,
|
|
11957
9065
|
parseNodeStyleDeclarations,
|
|
11958
9066
|
parseOklab,
|
|
@@ -11988,6 +9096,7 @@ export {
|
|
|
11988
9096
|
repositionNodesTo,
|
|
11989
9097
|
resetCanvasInstanceOverrides,
|
|
11990
9098
|
resizeHandleAxes,
|
|
9099
|
+
resolveCanvasGradient,
|
|
11991
9100
|
resolveCanvasTokens,
|
|
11992
9101
|
resolveEasing,
|
|
11993
9102
|
resolvePadding,
|