@pixerate/editor 0.54.0 → 0.55.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/dist/agent/index.cjs +6 -6
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.js +6 -6
- package/dist/animation/index.cjs +1 -1
- package/dist/animation/index.cjs.map +1 -1
- package/dist/animation/index.d.cts +2 -1
- package/dist/animation/index.d.ts +2 -1
- package/dist/animation/index.js +1 -1
- package/dist/animation-editor/index.cjs +2221 -0
- package/dist/animation-editor/index.cjs.map +1 -0
- package/dist/animation-editor/index.d.cts +1117 -0
- package/dist/animation-editor/index.d.ts +1117 -0
- package/dist/animation-editor/index.js +2221 -0
- package/dist/animation-editor/index.js.map +1 -0
- package/dist/bezier-BfwIbVTl.d.cts +548 -0
- package/dist/bezier-DYBvLgUH.d.ts +548 -0
- package/dist/canvas/index.cjs +713 -486
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +105 -5
- package/dist/canvas/index.d.ts +105 -5
- package/dist/canvas/index.js +710 -483
- package/dist/canvas/index.js.map +1 -1
- package/dist/chart/index.cjs +1 -1
- package/dist/chart/index.cjs.map +1 -1
- package/dist/chart/index.js +1 -1
- package/dist/{chunk-TO22LX34.js → chunk-GTMJ4Y3S.js} +2208 -2920
- package/dist/chunk-GTMJ4Y3S.js.map +1 -0
- package/dist/chunk-MLREO6J3.cjs +737 -0
- package/dist/chunk-MLREO6J3.cjs.map +1 -0
- package/dist/chunk-NONDTTH7.js +5979 -0
- package/dist/chunk-NONDTTH7.js.map +1 -0
- package/dist/{chunk-YNSK4MGT.cjs → chunk-O2UH7LZE.cjs} +1549 -2261
- package/dist/chunk-O2UH7LZE.cjs.map +1 -0
- package/dist/chunk-VWZB6BCY.js +737 -0
- package/dist/chunk-VWZB6BCY.js.map +1 -0
- package/dist/chunk-XCXQDPID.cjs +5979 -0
- package/dist/chunk-XCXQDPID.cjs.map +1 -0
- package/dist/color-E9pwm--w.d.cts +33 -0
- package/dist/color-E9pwm--w.d.ts +33 -0
- package/dist/commands/index.cjs +2 -2
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.js +3 -3
- package/dist/diagram/index.cjs +25 -4
- package/dist/diagram/index.cjs.map +1 -1
- package/dist/diagram/index.d.cts +23 -0
- package/dist/diagram/index.d.ts +23 -0
- package/dist/diagram/index.js +33 -12
- package/dist/diagram/index.js.map +1 -1
- package/dist/pathfinder-BZL8aSu0.d.cts +469 -0
- package/dist/pathfinder-CNtsbCRK.d.ts +469 -0
- package/dist/{player-DSMjdgQ9.d.cts → player-Dg0FT0UI.d.cts} +1 -33
- package/dist/{player-DdfVrKWq.d.ts → player-W-5DX3Go.d.ts} +1 -33
- package/dist/svg-CLg2zqZA.d.cts +847 -0
- package/dist/svg-CPK82TtU.d.ts +847 -0
- package/dist/vector/index.cjs +1328 -6752
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +6 -846
- package/dist/vector/index.d.ts +6 -846
- package/dist/vector/index.js +645 -6069
- package/dist/vector/index.js.map +1 -1
- package/package.json +6 -1
- package/dist/chunk-TO22LX34.js.map +0 -1
- package/dist/chunk-YNSK4MGT.cjs.map +0 -1
- package/dist/pathfinder-B9xh_pCZ.d.ts +0 -1013
- package/dist/pathfinder-QdG70sr9.d.cts +0 -1013
|
@@ -1,772 +1,283 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const [x1, y1] = ring[i];
|
|
7
|
-
const [x2, y2] = ring[(i + 1) % n];
|
|
8
|
-
sum += x1 * y2 - x2 * y1;
|
|
9
|
-
}
|
|
10
|
-
return sum / 2;
|
|
11
|
-
}
|
|
12
|
-
function polygonArea(polygon) {
|
|
13
|
-
if (polygon.length === 0) return 0;
|
|
14
|
-
let area = Math.abs(ringSignedArea(polygon[0]));
|
|
15
|
-
for (let i = 1; i < polygon.length; i++) area -= Math.abs(ringSignedArea(polygon[i]));
|
|
16
|
-
return area;
|
|
17
|
-
}
|
|
18
|
-
function multiPolygonArea(multi) {
|
|
19
|
-
let total = 0;
|
|
20
|
-
for (const polygon of multi) total += polygonArea(polygon);
|
|
21
|
-
return total;
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;// src/canvas/curveFit.ts
|
|
2
|
+
function formatNumber(value, precision) {
|
|
3
|
+
const factor = 10 ** precision;
|
|
4
|
+
const rounded = Math.round(value * factor) / factor;
|
|
5
|
+
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
22
6
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (y < minY) minY = y;
|
|
32
|
-
if (x > maxX) maxX = x;
|
|
33
|
-
if (y > maxY) maxY = y;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
for (const item of input) {
|
|
37
|
-
if (item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0])) {
|
|
38
|
-
for (const ring of item) visit(ring);
|
|
39
|
-
} else {
|
|
40
|
-
visit(item);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return minX === Infinity ? null : { minX, minY, maxX, maxY };
|
|
7
|
+
var MAX_REPARAM = 6;
|
|
8
|
+
var sub = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
9
|
+
var add = (a, v, s = 1) => [a[0] + v[0] * s, a[1] + v[1] * s];
|
|
10
|
+
var dot = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
11
|
+
var dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
12
|
+
function unit(v) {
|
|
13
|
+
const l = Math.hypot(v[0], v[1]);
|
|
14
|
+
return l > 1e-12 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
|
|
44
15
|
}
|
|
45
|
-
function
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} else if (by <= py && ay > py) {
|
|
56
|
-
if (ax + (py - ay) / (by - ay) * (bx - ax) > px) winding--;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return fillRule === "evenodd" ? (winding & 1) === 1 : winding !== 0;
|
|
16
|
+
function evalCubic(c, t) {
|
|
17
|
+
const s = 1 - t;
|
|
18
|
+
const b0 = s * s * s;
|
|
19
|
+
const b1 = 3 * s * s * t;
|
|
20
|
+
const b2 = 3 * s * t * t;
|
|
21
|
+
const b3 = t * t * t;
|
|
22
|
+
return [
|
|
23
|
+
b0 * c.p0[0] + b1 * c.p1[0] + b2 * c.p2[0] + b3 * c.p3[0],
|
|
24
|
+
b0 * c.p0[1] + b1 * c.p1[1] + b2 * c.p2[1] + b3 * c.p3[1]
|
|
25
|
+
];
|
|
61
26
|
}
|
|
62
|
-
function
|
|
63
|
-
return
|
|
27
|
+
function cubicPointAt(c, t) {
|
|
28
|
+
return evalCubic(c, t);
|
|
64
29
|
}
|
|
65
|
-
function
|
|
30
|
+
function dedup(points) {
|
|
66
31
|
const out = [];
|
|
67
|
-
for (const p of
|
|
32
|
+
for (const p of points) {
|
|
68
33
|
if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
|
|
69
|
-
|
|
70
|
-
if (last && last[0] === p[0] && last[1] === p[1]) continue;
|
|
71
|
-
out.push([p[0], p[1]]);
|
|
72
|
-
}
|
|
73
|
-
while (out.length > 1) {
|
|
74
|
-
const first = out[0];
|
|
75
|
-
const last = out[out.length - 1];
|
|
76
|
-
if (first[0] === last[0] && first[1] === last[1]) out.pop();
|
|
77
|
-
else break;
|
|
34
|
+
if (out.length === 0 || dist(out[out.length - 1], p) > 1e-12) out.push(p);
|
|
78
35
|
}
|
|
79
36
|
return out;
|
|
80
37
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
38
|
+
function chordParams(pts) {
|
|
39
|
+
const u = [0];
|
|
40
|
+
let acc = 0;
|
|
41
|
+
for (let i = 1; i < pts.length; i++) {
|
|
42
|
+
acc += dist(pts[i - 1], pts[i]);
|
|
43
|
+
u.push(acc);
|
|
44
|
+
}
|
|
45
|
+
return acc > 0 ? u.map((x) => x / acc) : u;
|
|
83
46
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
47
|
+
function generate(pts, u, t0, t1) {
|
|
48
|
+
const p0 = pts[0];
|
|
49
|
+
const p3 = pts[pts.length - 1];
|
|
50
|
+
const dirIn = [-t1[0], -t1[1]];
|
|
51
|
+
let c00 = 0;
|
|
52
|
+
let c01 = 0;
|
|
53
|
+
let c11 = 0;
|
|
54
|
+
let x0 = 0;
|
|
55
|
+
let x1 = 0;
|
|
56
|
+
for (let i = 0; i < pts.length; i++) {
|
|
57
|
+
const t = u[i];
|
|
58
|
+
const s = 1 - t;
|
|
59
|
+
const b0 = s * s * s;
|
|
60
|
+
const b1 = 3 * s * s * t;
|
|
61
|
+
const b2 = 3 * s * t * t;
|
|
62
|
+
const b3 = t * t * t;
|
|
63
|
+
const a0 = [t0[0] * b1, t0[1] * b1];
|
|
64
|
+
const a1 = [dirIn[0] * b2, dirIn[1] * b2];
|
|
65
|
+
c00 += dot(a0, a0);
|
|
66
|
+
c01 += dot(a0, a1);
|
|
67
|
+
c11 += dot(a1, a1);
|
|
68
|
+
const tmp = [
|
|
69
|
+
pts[i][0] - (p0[0] * (b0 + b1) + p3[0] * (b2 + b3)),
|
|
70
|
+
pts[i][1] - (p0[1] * (b0 + b1) + p3[1] * (b2 + b3))
|
|
71
|
+
];
|
|
72
|
+
x0 += dot(a0, tmp);
|
|
73
|
+
x1 += dot(a1, tmp);
|
|
90
74
|
}
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
rings.push(area > 0 === wantPositive ? ring : ring.reverse());
|
|
100
|
-
});
|
|
75
|
+
const det = c00 * c11 - c01 * c01;
|
|
76
|
+
const seg = dist(p0, p3);
|
|
77
|
+
const eps = 1e-6 * seg;
|
|
78
|
+
let al = seg / 3;
|
|
79
|
+
let ar = seg / 3;
|
|
80
|
+
if (Math.abs(det) > 1e-12) {
|
|
81
|
+
al = (x0 * c11 - x1 * c01) / det;
|
|
82
|
+
ar = (c00 * x1 - c01 * x0) / det;
|
|
101
83
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
constructor(eps) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);
|
|
106
|
-
this.eps = eps;
|
|
84
|
+
if (!Number.isFinite(al) || !Number.isFinite(ar) || al < eps || ar < eps) {
|
|
85
|
+
al = seg / 3;
|
|
86
|
+
ar = seg / 3;
|
|
107
87
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const v = this.vertices[id];
|
|
122
|
-
const d = Math.hypot(v.x - x, v.y - y);
|
|
123
|
-
if (d <= this.eps && d < bestDist) {
|
|
124
|
-
best = v;
|
|
125
|
-
bestDist = d;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
88
|
+
const cap = seg * 4 + 1e-9;
|
|
89
|
+
al = Math.min(al, cap);
|
|
90
|
+
ar = Math.min(ar, cap);
|
|
91
|
+
return { p0, p1: add(p0, t0, al), p2: add(p3, dirIn, ar), p3 };
|
|
92
|
+
}
|
|
93
|
+
function maxError(pts, c, u) {
|
|
94
|
+
let e = 0;
|
|
95
|
+
let idx = pts.length >> 1;
|
|
96
|
+
for (let i = 0; i < pts.length; i++) {
|
|
97
|
+
const d = dist(evalCubic(c, u[i]), pts[i]);
|
|
98
|
+
if (d > e) {
|
|
99
|
+
e = d;
|
|
100
|
+
idx = i;
|
|
129
101
|
}
|
|
130
|
-
if (best) return best;
|
|
131
|
-
const vertex = { id: this.vertices.length, x, y };
|
|
132
|
-
this.vertices.push(vertex);
|
|
133
|
-
const key = `${cx},${cy}`;
|
|
134
|
-
const bucket = this.cells.get(key);
|
|
135
|
-
if (bucket) bucket.push(vertex.id);
|
|
136
|
-
else this.cells.set(key, [vertex.id]);
|
|
137
|
-
return vertex;
|
|
138
102
|
}
|
|
139
|
-
|
|
140
|
-
function project(a, b, p) {
|
|
141
|
-
const dx = b.x - a.x;
|
|
142
|
-
const dy = b.y - a.y;
|
|
143
|
-
const len2 = dx * dx + dy * dy;
|
|
144
|
-
const t = len2 === 0 ? 0 : ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;
|
|
145
|
-
const cx = a.x + t * dx;
|
|
146
|
-
const cy = a.y + t * dy;
|
|
147
|
-
return { t, dist: Math.hypot(p.x - cx, p.y - cy) };
|
|
103
|
+
return [e, idx];
|
|
148
104
|
}
|
|
149
|
-
function
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
105
|
+
function newton(c, p, t) {
|
|
106
|
+
const s = 1 - t;
|
|
107
|
+
const q = evalCubic(c, t);
|
|
108
|
+
const d1 = [
|
|
109
|
+
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])),
|
|
110
|
+
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]))
|
|
111
|
+
];
|
|
112
|
+
const d2 = [
|
|
113
|
+
6 * (s * (c.p2[0] - 2 * c.p1[0] + c.p0[0]) + t * (c.p3[0] - 2 * c.p2[0] + c.p1[0])),
|
|
114
|
+
6 * (s * (c.p2[1] - 2 * c.p1[1] + c.p0[1]) + t * (c.p3[1] - 2 * c.p2[1] + c.p1[1]))
|
|
115
|
+
];
|
|
116
|
+
const diff = [q[0] - p[0], q[1] - p[1]];
|
|
117
|
+
const num = dot(diff, d1);
|
|
118
|
+
const den = dot(d1, d1) + dot(diff, d2);
|
|
119
|
+
if (Math.abs(den) < 1e-12) return t;
|
|
120
|
+
const nt = t - num / den;
|
|
121
|
+
return Number.isFinite(nt) ? Math.min(1, Math.max(0, nt)) : t;
|
|
153
122
|
}
|
|
154
|
-
function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const bj = order[oj];
|
|
168
|
-
if (bj.minX > bi.maxX + eps) break;
|
|
169
|
-
if (bj.minY > bi.maxY + eps || bj.maxY < bi.minY - eps) continue;
|
|
170
|
-
const s2 = segments[bj.i];
|
|
171
|
-
const sharedA = s1.a.id === s2.a.id || s1.a.id === s2.b.id;
|
|
172
|
-
const sharedB = s1.b.id === s2.a.id || s1.b.id === s2.b.id;
|
|
173
|
-
if (sharedA && sharedB) continue;
|
|
174
|
-
let touched = false;
|
|
175
|
-
for (const [seg, other, split] of [
|
|
176
|
-
[s1, s2, splits[bi.i]],
|
|
177
|
-
[s2, s1, splits[bj.i]]
|
|
178
|
-
]) {
|
|
179
|
-
for (const p of [other.a, other.b]) {
|
|
180
|
-
if (p.id === seg.a.id || p.id === seg.b.id) continue;
|
|
181
|
-
const { t: t2, dist: dist3 } = project(seg.a, seg.b, p);
|
|
182
|
-
const len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);
|
|
183
|
-
if (dist3 <= eps && t2 * len > eps && (1 - t2) * len > eps) {
|
|
184
|
-
addSplit(split, t2, p);
|
|
185
|
-
touched = true;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (touched) continue;
|
|
190
|
-
const d1x = s1.b.x - s1.a.x;
|
|
191
|
-
const d1y = s1.b.y - s1.a.y;
|
|
192
|
-
const d2x = s2.b.x - s2.a.x;
|
|
193
|
-
const d2y = s2.b.y - s2.a.y;
|
|
194
|
-
const denom = d1x * d2y - d1y * d2x;
|
|
195
|
-
const len1 = Math.hypot(d1x, d1y);
|
|
196
|
-
const len2 = Math.hypot(d2x, d2y);
|
|
197
|
-
if (Math.abs(denom) <= 1e-12 * len1 * len2) continue;
|
|
198
|
-
const ex = s2.a.x - s1.a.x;
|
|
199
|
-
const ey = s2.a.y - s1.a.y;
|
|
200
|
-
const t = (ex * d2y - ey * d2x) / denom;
|
|
201
|
-
const u = (ex * d1y - ey * d1x) / denom;
|
|
202
|
-
const tolT = eps / len1;
|
|
203
|
-
const tolU = eps / len2;
|
|
204
|
-
if (t < -tolT || t > 1 + tolT || u < -tolU || u > 1 + tolU) continue;
|
|
205
|
-
if (sharedA || sharedB) continue;
|
|
206
|
-
const v = pool.get(s1.a.x + t * d1x, s1.a.y + t * d1y);
|
|
207
|
-
addSplit(splits[bi.i], project(s1.a, s1.b, v).t, v);
|
|
208
|
-
addSplit(splits[bj.i], project(s2.a, s2.b, v).t, v);
|
|
209
|
-
}
|
|
123
|
+
function fitSingleFrom(pts, u, t0, t1) {
|
|
124
|
+
let best = generate(pts, u, t0, t1);
|
|
125
|
+
let [bestErr, bestSplit] = maxError(pts, best, u);
|
|
126
|
+
const params = u.slice();
|
|
127
|
+
for (let round = 0; round < MAX_REPARAM; round++) {
|
|
128
|
+
for (let i = 0; i < params.length; i++) params[i] = newton(best, pts[i], params[i]);
|
|
129
|
+
const c = generate(pts, params, t0, t1);
|
|
130
|
+
const [e, s] = maxError(pts, c, params);
|
|
131
|
+
if (e < bestErr) {
|
|
132
|
+
best = c;
|
|
133
|
+
bestErr = e;
|
|
134
|
+
bestSplit = s;
|
|
135
|
+
} else break;
|
|
210
136
|
}
|
|
211
|
-
|
|
137
|
+
return [best, bestErr, bestSplit];
|
|
138
|
+
}
|
|
139
|
+
function fitCubicWithTangents(points, params, t0, t1) {
|
|
140
|
+
const [curve, error] = fitSingleFrom(points.slice(), params.slice(), [t0[0], t0[1]], [t1[0], t1[1]]);
|
|
141
|
+
return { curve, error };
|
|
142
|
+
}
|
|
143
|
+
function fitRec(pts, t0, t1, tol, out, depth) {
|
|
144
|
+
const n = pts.length;
|
|
145
|
+
if (n === 2) {
|
|
146
|
+
const d = dist(pts[0], pts[1]) / 3;
|
|
147
|
+
out.push({ p0: pts[0], p1: add(pts[0], t0, d), p2: add(pts[1], t1, -d), p3: pts[1] });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const [c, err, splitAt] = fitSingleFrom(pts, chordParams(pts), t0, t1);
|
|
151
|
+
if (err <= tol || depth > 40) {
|
|
152
|
+
out.push(c);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const split = Math.min(n - 2, Math.max(1, splitAt));
|
|
156
|
+
const tc = _nullishCoalesce(unit(sub(pts[split + 1], pts[split - 1])), () => ( t0));
|
|
157
|
+
fitRec(pts.slice(0, split + 1), t0, tc, tol, out, depth + 1);
|
|
158
|
+
fitRec(pts.slice(split), tc, t1, tol, out, depth + 1);
|
|
159
|
+
}
|
|
160
|
+
function tangentFrom(a, b, c) {
|
|
161
|
+
const chord = unit(sub(b, a));
|
|
162
|
+
if (!c) return chord;
|
|
163
|
+
const d1 = dist(a, b);
|
|
164
|
+
const d2 = dist(b, c);
|
|
165
|
+
if (!(d1 > 0) || !(d2 > 0)) return chord;
|
|
166
|
+
const ka = -(2 * d1 + d2) / (d1 * (d1 + d2));
|
|
167
|
+
const kb = (d1 + d2) / (d1 * d2);
|
|
168
|
+
const kc = -d1 / (d2 * (d1 + d2));
|
|
169
|
+
const est = unit([ka * a[0] + kb * b[0] + kc * c[0], ka * a[1] + kb * b[1] + kc * c[1]]);
|
|
170
|
+
return est && chord && dot(est, chord) > Math.cos(Math.PI / 4) ? est : chord;
|
|
171
|
+
}
|
|
172
|
+
function fitCubicRun(points, tolerance, t0, t1) {
|
|
173
|
+
const pts = dedup(points);
|
|
212
174
|
const out = [];
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
175
|
+
if (pts.length < 2) return out;
|
|
176
|
+
const n = pts.length;
|
|
177
|
+
const start = _nullishCoalesce(_nullishCoalesce((t0 && unit([t0[0], t0[1]])), () => ( tangentFrom(pts[0], pts[1], pts[2]))), () => ( [1, 0]));
|
|
178
|
+
const back = tangentFrom(pts[n - 1], pts[n - 2], pts[n - 3]);
|
|
179
|
+
const end = _nullishCoalesce((t1 && unit([t1[0], t1[1]])), () => ( (back ? [-back[0], -back[1]] : [1, 0])));
|
|
180
|
+
fitRec(pts, start, end, Math.max(tolerance, 1e-9), out, 0);
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
function turnDeg(a, b, c) {
|
|
184
|
+
const u = unit(sub(b, a));
|
|
185
|
+
const v = unit(sub(c, b));
|
|
186
|
+
if (!u || !v) return 0;
|
|
187
|
+
return Math.acos(Math.min(1, Math.max(-1, dot(u, v)))) * 180 / Math.PI;
|
|
188
|
+
}
|
|
189
|
+
function findCorners(pts, closed, cornerAngle, span) {
|
|
190
|
+
const n = pts.length;
|
|
191
|
+
const at = (i) => pts[closed ? (i % n + n) % n : i];
|
|
192
|
+
const reach = (i, step) => {
|
|
193
|
+
for (let k = 1; k < (closed ? n : Infinity); k++) {
|
|
194
|
+
const j = i + step * k;
|
|
195
|
+
if (!closed && (j < 0 || j >= n)) return k > 1 ? j - step : null;
|
|
196
|
+
if (dist(at(j), pts[i]) >= span) return j;
|
|
217
197
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
198
|
+
return null;
|
|
199
|
+
};
|
|
200
|
+
const turns = new Float64Array(n);
|
|
201
|
+
const windows = [];
|
|
202
|
+
for (let i = 0; i < n; i++) {
|
|
203
|
+
windows.push([i, i]);
|
|
204
|
+
if (!closed && (i === 0 || i === n - 1)) continue;
|
|
205
|
+
const prev = reach(i, -1);
|
|
206
|
+
const next = reach(i, 1);
|
|
207
|
+
if (prev === null || next === null) continue;
|
|
208
|
+
windows[i] = [prev, next];
|
|
209
|
+
turns[i] = turnDeg(at(prev), pts[i], at(next));
|
|
210
|
+
}
|
|
211
|
+
const corners = [];
|
|
212
|
+
for (let i = 0; i < n; i++) {
|
|
213
|
+
if (!(turns[i] > cornerAngle)) continue;
|
|
214
|
+
const [lo, hi] = windows[i];
|
|
215
|
+
let sharpest = true;
|
|
216
|
+
for (let j = lo; j <= hi && sharpest; j++) {
|
|
217
|
+
const k = closed ? (j % n + n) % n : j;
|
|
218
|
+
if (k === i || dist(pts[k], pts[i]) >= span) continue;
|
|
219
|
+
if (turns[k] > turns[i] || turns[k] === turns[i] && k < i) sharpest = false;
|
|
224
220
|
}
|
|
225
|
-
if (
|
|
221
|
+
if (sharpest) corners.push(i);
|
|
226
222
|
}
|
|
227
|
-
return
|
|
223
|
+
return corners;
|
|
228
224
|
}
|
|
229
|
-
function
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
225
|
+
function densify(run) {
|
|
226
|
+
if (run.length < 3) return run;
|
|
227
|
+
const lengths = [];
|
|
228
|
+
for (let i = 1; i < run.length; i++) lengths.push(dist(run[i - 1], run[i]));
|
|
229
|
+
const sorted = [...lengths].sort((a, b) => a - b);
|
|
230
|
+
const median = sorted[sorted.length >> 1];
|
|
231
|
+
if (!(median > 0)) return run;
|
|
232
|
+
const out = [run[0]];
|
|
233
|
+
for (let i = 1; i < run.length; i++) {
|
|
234
|
+
const len = lengths[i - 1];
|
|
235
|
+
if (len > 2 * median) {
|
|
236
|
+
const steps = Math.min(64, Math.ceil(len / median));
|
|
237
|
+
const a = run[i - 1];
|
|
238
|
+
const b = run[i];
|
|
239
|
+
for (let k = 1; k < steps; k++) {
|
|
240
|
+
const t = k / steps;
|
|
241
|
+
out.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
|
|
236
242
|
}
|
|
237
243
|
}
|
|
244
|
+
out.push(run[i]);
|
|
238
245
|
}
|
|
239
|
-
return
|
|
246
|
+
return out;
|
|
240
247
|
}
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
248
|
+
function fitCubics(points, tolerance, options = {}) {
|
|
249
|
+
let pts = dedup(points);
|
|
250
|
+
const closed = options.closed === true;
|
|
251
|
+
if (closed && pts.length > 1 && dist(pts[0], pts[pts.length - 1]) <= 1e-12) pts = pts.slice(0, -1);
|
|
252
|
+
if (pts.length < 2) return [];
|
|
253
|
+
const tol = Math.max(tolerance, 1e-6);
|
|
254
|
+
const cornerAngle = _nullishCoalesce(options.cornerAngleDeg, () => ( 30));
|
|
255
|
+
const n = pts.length;
|
|
256
|
+
const span = 4 * tol;
|
|
257
|
+
if (!closed || n < 3) {
|
|
258
|
+
const corners2 = [0, ...n > 2 ? findCorners(pts, false, cornerAngle, span) : [], n - 1];
|
|
259
|
+
const out2 = [];
|
|
260
|
+
for (let k = 1; k < corners2.length; k++) {
|
|
261
|
+
const run = pts.slice(corners2[k - 1], corners2[k] + 1);
|
|
262
|
+
out2.push(...fitCubicRun(densify(run), tol));
|
|
254
263
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const result = splitPass(segments, pool, eps);
|
|
258
|
-
segments = result.segments;
|
|
259
|
-
if (!result.changed) break;
|
|
264
|
+
if (closed && n === 2) out2.push(...fitCubicRun([pts[1], pts[0]], tol));
|
|
265
|
+
return out2;
|
|
260
266
|
}
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
const b = forward ? s.b : s.a;
|
|
267
|
-
const key = `${a.id},${b.id}`;
|
|
268
|
-
let edge = byKey.get(key);
|
|
269
|
-
if (!edge) {
|
|
270
|
-
edge = { a, b, mult: new Array(count).fill(0) };
|
|
271
|
-
byKey.set(key, edge);
|
|
272
|
-
}
|
|
273
|
-
edge.mult[s.operand] += forward ? 1 : -1;
|
|
267
|
+
const corners = findCorners(pts, true, cornerAngle, span);
|
|
268
|
+
if (corners.length === 0) {
|
|
269
|
+
const ring = [...pts, pts[0]];
|
|
270
|
+
const t = _nullishCoalesce(unit(sub(pts[1], pts[n - 1])), () => ( [1, 0]));
|
|
271
|
+
return fitCubicRun(densify(ring), tol, t, t);
|
|
274
272
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
let max = -Infinity;
|
|
284
|
-
for (const e of edges) {
|
|
285
|
-
min = Math.min(min, lo(e));
|
|
286
|
-
max = Math.max(max, hi(e));
|
|
287
|
-
}
|
|
288
|
-
const size = max > min ? (max - min) / bandCount : 1;
|
|
289
|
-
const band = (v) => Math.min(bandCount - 1, Math.max(0, Math.floor((v - min) / size)));
|
|
290
|
-
const bands = Array.from({ length: bandCount }, () => []);
|
|
291
|
-
edges.forEach((e, j) => {
|
|
292
|
-
for (let k = band(lo(e)); k <= band(hi(e)); k++) bands[k].push(j);
|
|
293
|
-
});
|
|
294
|
-
return (v) => bands[band(v)];
|
|
295
|
-
};
|
|
296
|
-
const bandsFor = /* @__PURE__ */ new Map();
|
|
297
|
-
for (let i = 0; i < edges.length; i++) {
|
|
298
|
-
const edge = edges[i];
|
|
299
|
-
const horizontal = Math.abs(edge.b.y - edge.a.y) < Math.abs(edge.b.x - edge.a.x);
|
|
300
|
-
const tx = (v) => horizontal ? v.y : v.x;
|
|
301
|
-
const ty = (v) => horizontal ? -v.x : v.y;
|
|
302
|
-
const ax = tx(edge.a);
|
|
303
|
-
const ay = ty(edge.a);
|
|
304
|
-
const bx = tx(edge.b);
|
|
305
|
-
const by = ty(edge.b);
|
|
306
|
-
const mx = (ax + bx) / 2;
|
|
307
|
-
const my = (ay + by) / 2;
|
|
308
|
-
const winding = new Array(count).fill(0);
|
|
309
|
-
if (!bandsFor.has(horizontal)) bandsFor.set(horizontal, bandIndex(horizontal));
|
|
310
|
-
for (const j of bandsFor.get(horizontal)(my)) {
|
|
311
|
-
if (j === i) continue;
|
|
312
|
-
const other = edges[j];
|
|
313
|
-
const oax = tx(other.a);
|
|
314
|
-
const oay = ty(other.a);
|
|
315
|
-
const obx = tx(other.b);
|
|
316
|
-
const oby = ty(other.b);
|
|
317
|
-
let dir = 0;
|
|
318
|
-
if (oay <= my && oby > my) dir = 1;
|
|
319
|
-
else if (oby <= my && oay > my) dir = -1;
|
|
320
|
-
else continue;
|
|
321
|
-
const x = oax + (my - oay) / (oby - oay) * (obx - oax);
|
|
322
|
-
if (x <= mx) continue;
|
|
323
|
-
for (let k = 0; k < count; k++) {
|
|
324
|
-
if (other.mult[k] !== 0) winding[k] += dir * other.mult[k];
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
const sideIsRight = by > ay;
|
|
328
|
-
const r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);
|
|
329
|
-
right.push(r);
|
|
330
|
-
left.push(r.map((w, k) => w + edge.mult[k]));
|
|
331
|
-
}
|
|
332
|
-
return { edges, right, left, fillRules: operands.map((o) => o.fillRule), eps };
|
|
333
|
-
}
|
|
334
|
-
function insideFlags(windings, fillRules) {
|
|
335
|
-
return windings.map((w, k) => fillRules[k] === "evenodd" ? (w & 1) === 1 : w !== 0);
|
|
336
|
-
}
|
|
337
|
-
function traceRings(directed2) {
|
|
338
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
339
|
-
for (const e of directed2) {
|
|
340
|
-
const list = outgoing.get(e.from.id);
|
|
341
|
-
if (list) list.push(e);
|
|
342
|
-
else outgoing.set(e.from.id, [e]);
|
|
343
|
-
}
|
|
344
|
-
const TWO_PI2 = Math.PI * 2;
|
|
345
|
-
const next = (incoming) => {
|
|
346
|
-
const candidates = outgoing.get(incoming.to.id);
|
|
347
|
-
if (!candidates) return void 0;
|
|
348
|
-
const back = Math.atan2(incoming.from.y - incoming.to.y, incoming.from.x - incoming.to.x);
|
|
349
|
-
let best;
|
|
350
|
-
let bestTurn = Infinity;
|
|
351
|
-
for (const c of candidates) {
|
|
352
|
-
let turn = back - c.angle;
|
|
353
|
-
turn -= Math.floor(turn / TWO_PI2) * TWO_PI2;
|
|
354
|
-
if (turn <= 1e-15) turn = TWO_PI2;
|
|
355
|
-
if (turn < bestTurn) {
|
|
356
|
-
bestTurn = turn;
|
|
357
|
-
best = c;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return best;
|
|
361
|
-
};
|
|
362
|
-
const used = /* @__PURE__ */ new Set();
|
|
363
|
-
const rings = [];
|
|
364
|
-
for (const start of directed2) {
|
|
365
|
-
if (used.has(start)) continue;
|
|
366
|
-
const ring = [];
|
|
367
|
-
let edge = start;
|
|
368
|
-
let guard = directed2.length + 1;
|
|
369
|
-
while (edge && !used.has(edge) && guard-- > 0) {
|
|
370
|
-
used.add(edge);
|
|
371
|
-
ring.push(edge.from);
|
|
372
|
-
edge = next(edge);
|
|
373
|
-
}
|
|
374
|
-
if (ring.length >= 3) rings.push(ring);
|
|
375
|
-
}
|
|
376
|
-
return rings;
|
|
377
|
-
}
|
|
378
|
-
function removeCollinear(ring, eps) {
|
|
379
|
-
let points = ring;
|
|
380
|
-
let changed = true;
|
|
381
|
-
while (changed && points.length > 3) {
|
|
382
|
-
changed = false;
|
|
383
|
-
const out = [];
|
|
384
|
-
const n = points.length;
|
|
385
|
-
for (let i = 0; i < n; i++) {
|
|
386
|
-
const prev = out.length > 0 ? out[out.length - 1] : points[(i - 1 + n) % n];
|
|
387
|
-
const cur = points[i];
|
|
388
|
-
const nxt = points[(i + 1) % n];
|
|
389
|
-
const dx = nxt[0] - prev[0];
|
|
390
|
-
const dy = nxt[1] - prev[1];
|
|
391
|
-
const len = Math.hypot(dx, dy);
|
|
392
|
-
const cross2 = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;
|
|
393
|
-
const dot3 = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;
|
|
394
|
-
if (len > 0 && Math.abs(cross2) / len <= eps && dot3 > 0 && dot3 < len * len) {
|
|
395
|
-
changed = true;
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
out.push(cur);
|
|
399
|
-
}
|
|
400
|
-
if (out.length < 3) return out;
|
|
401
|
-
points = out;
|
|
402
|
-
}
|
|
403
|
-
return points;
|
|
404
|
-
}
|
|
405
|
-
function assemble(rings, eps) {
|
|
406
|
-
const outers = [];
|
|
407
|
-
const holes = [];
|
|
408
|
-
const minArea = eps * eps;
|
|
409
|
-
for (const vertices of rings) {
|
|
410
|
-
const ring = removeCollinear(
|
|
411
|
-
vertices.map((v) => [v.x, v.y]),
|
|
412
|
-
eps
|
|
413
|
-
);
|
|
414
|
-
if (ring.length < 3) continue;
|
|
415
|
-
const area = ringSignedArea(ring);
|
|
416
|
-
if (Math.abs(area) <= minArea) continue;
|
|
417
|
-
(area > 0 ? outers : holes).push({ ring, area: Math.abs(area) });
|
|
418
|
-
}
|
|
419
|
-
outers.sort((a, b) => b.area - a.area);
|
|
420
|
-
const polygons = outers.map((o) => [o.ring]);
|
|
421
|
-
for (const hole of holes) {
|
|
422
|
-
const [p, q] = [hole.ring[0], hole.ring[1]];
|
|
423
|
-
const sample = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
|
|
424
|
-
let owner = -1;
|
|
425
|
-
for (let i = outers.length - 1; i >= 0; i--) {
|
|
426
|
-
if (outers[i].area >= hole.area && pointInRings(sample, [outers[i].ring], "evenodd")) {
|
|
427
|
-
owner = i;
|
|
428
|
-
break;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
if (owner >= 0) polygons[owner].push(hole.ring);
|
|
432
|
-
}
|
|
433
|
-
return polygons;
|
|
434
|
-
}
|
|
435
|
-
function extract(overlay, isInside) {
|
|
436
|
-
const directed2 = [];
|
|
437
|
-
overlay.edges.forEach((edge, i) => {
|
|
438
|
-
const l = isInside(insideFlags(overlay.left[i], overlay.fillRules));
|
|
439
|
-
const r = isInside(insideFlags(overlay.right[i], overlay.fillRules));
|
|
440
|
-
if (l === r) return;
|
|
441
|
-
const [from, to] = l ? [edge.a, edge.b] : [edge.b, edge.a];
|
|
442
|
-
directed2.push({ from, to, angle: Math.atan2(to.y - from.y, to.x - from.x) });
|
|
443
|
-
});
|
|
444
|
-
return assemble(traceRings(directed2), overlay.eps);
|
|
445
|
-
}
|
|
446
|
-
function predicate(operation) {
|
|
447
|
-
switch (operation) {
|
|
448
|
-
case "union":
|
|
449
|
-
return (f) => f.some(Boolean);
|
|
450
|
-
case "intersection":
|
|
451
|
-
return (f) => f.length > 0 && f.every(Boolean);
|
|
452
|
-
case "difference":
|
|
453
|
-
return (f) => f[0] === true && !f.slice(1).some(Boolean);
|
|
454
|
-
case "xor":
|
|
455
|
-
return (f) => f.filter(Boolean).length % 2 === 1;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
function polygonBoolean(operation, operands, options = {}) {
|
|
459
|
-
if (operands.length === 0) return [];
|
|
460
|
-
return extract(buildOverlay(operands, options), predicate(operation));
|
|
461
|
-
}
|
|
462
|
-
var unionPolygons = (a, b, options) => polygonBoolean("union", [a, b], options);
|
|
463
|
-
var intersectPolygons = (a, b, options) => polygonBoolean("intersection", [a, b], options);
|
|
464
|
-
var subtractPolygons = (a, b, options) => polygonBoolean("difference", [a, b], options);
|
|
465
|
-
var xorPolygons = (a, b, options) => polygonBoolean("xor", [a, b], options);
|
|
466
|
-
function dividePolygons(operands, options = {}) {
|
|
467
|
-
if (operands.length === 0) return [];
|
|
468
|
-
const overlay = buildOverlay(operands, options);
|
|
469
|
-
const signatures = /* @__PURE__ */ new Map();
|
|
470
|
-
const note = (windings) => {
|
|
471
|
-
const flags = insideFlags(windings, overlay.fillRules);
|
|
472
|
-
if (!flags.some(Boolean)) return;
|
|
473
|
-
const key = flags.map((f) => f ? "1" : "0").join("");
|
|
474
|
-
if (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => f ? [k] : []));
|
|
475
|
-
};
|
|
476
|
-
overlay.edges.forEach((_, i) => {
|
|
477
|
-
note(overlay.left[i]);
|
|
478
|
-
note(overlay.right[i]);
|
|
479
|
-
});
|
|
480
|
-
const pieces = [];
|
|
481
|
-
const keys = [...signatures.keys()].sort();
|
|
482
|
-
for (const key of keys) {
|
|
483
|
-
const polygons = extract(overlay, (flags) => flags.map((f) => f ? "1" : "0").join("") === key);
|
|
484
|
-
polygons.sort((a, b) => polygonArea(b) - polygonArea(a));
|
|
485
|
-
for (const polygon of polygons) pieces.push({ polygon, operands: signatures.get(key) });
|
|
486
|
-
}
|
|
487
|
-
return pieces;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// src/canvas/curveFit.ts
|
|
491
|
-
function formatNumber(value, precision) {
|
|
492
|
-
const factor = 10 ** precision;
|
|
493
|
-
const rounded = Math.round(value * factor) / factor;
|
|
494
|
-
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
495
|
-
}
|
|
496
|
-
var MAX_REPARAM = 6;
|
|
497
|
-
var sub = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
498
|
-
var add = (a, v, s = 1) => [a[0] + v[0] * s, a[1] + v[1] * s];
|
|
499
|
-
var dot = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
500
|
-
var dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
501
|
-
function unit(v) {
|
|
502
|
-
const l = Math.hypot(v[0], v[1]);
|
|
503
|
-
return l > 1e-12 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
|
|
504
|
-
}
|
|
505
|
-
function evalCubic(c, t) {
|
|
506
|
-
const s = 1 - t;
|
|
507
|
-
const b0 = s * s * s;
|
|
508
|
-
const b1 = 3 * s * s * t;
|
|
509
|
-
const b2 = 3 * s * t * t;
|
|
510
|
-
const b3 = t * t * t;
|
|
511
|
-
return [
|
|
512
|
-
b0 * c.p0[0] + b1 * c.p1[0] + b2 * c.p2[0] + b3 * c.p3[0],
|
|
513
|
-
b0 * c.p0[1] + b1 * c.p1[1] + b2 * c.p2[1] + b3 * c.p3[1]
|
|
514
|
-
];
|
|
515
|
-
}
|
|
516
|
-
function cubicPointAt(c, t) {
|
|
517
|
-
return evalCubic(c, t);
|
|
518
|
-
}
|
|
519
|
-
function dedup(points) {
|
|
520
|
-
const out = [];
|
|
521
|
-
for (const p of points) {
|
|
522
|
-
if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
|
|
523
|
-
if (out.length === 0 || dist(out[out.length - 1], p) > 1e-12) out.push(p);
|
|
524
|
-
}
|
|
525
|
-
return out;
|
|
526
|
-
}
|
|
527
|
-
function chordParams(pts) {
|
|
528
|
-
const u = [0];
|
|
529
|
-
let acc = 0;
|
|
530
|
-
for (let i = 1; i < pts.length; i++) {
|
|
531
|
-
acc += dist(pts[i - 1], pts[i]);
|
|
532
|
-
u.push(acc);
|
|
533
|
-
}
|
|
534
|
-
return acc > 0 ? u.map((x) => x / acc) : u;
|
|
535
|
-
}
|
|
536
|
-
function generate(pts, u, t0, t1) {
|
|
537
|
-
const p0 = pts[0];
|
|
538
|
-
const p3 = pts[pts.length - 1];
|
|
539
|
-
const dirIn = [-t1[0], -t1[1]];
|
|
540
|
-
let c00 = 0;
|
|
541
|
-
let c01 = 0;
|
|
542
|
-
let c11 = 0;
|
|
543
|
-
let x0 = 0;
|
|
544
|
-
let x1 = 0;
|
|
545
|
-
for (let i = 0; i < pts.length; i++) {
|
|
546
|
-
const t = u[i];
|
|
547
|
-
const s = 1 - t;
|
|
548
|
-
const b0 = s * s * s;
|
|
549
|
-
const b1 = 3 * s * s * t;
|
|
550
|
-
const b2 = 3 * s * t * t;
|
|
551
|
-
const b3 = t * t * t;
|
|
552
|
-
const a0 = [t0[0] * b1, t0[1] * b1];
|
|
553
|
-
const a1 = [dirIn[0] * b2, dirIn[1] * b2];
|
|
554
|
-
c00 += dot(a0, a0);
|
|
555
|
-
c01 += dot(a0, a1);
|
|
556
|
-
c11 += dot(a1, a1);
|
|
557
|
-
const tmp = [
|
|
558
|
-
pts[i][0] - (p0[0] * (b0 + b1) + p3[0] * (b2 + b3)),
|
|
559
|
-
pts[i][1] - (p0[1] * (b0 + b1) + p3[1] * (b2 + b3))
|
|
560
|
-
];
|
|
561
|
-
x0 += dot(a0, tmp);
|
|
562
|
-
x1 += dot(a1, tmp);
|
|
563
|
-
}
|
|
564
|
-
const det = c00 * c11 - c01 * c01;
|
|
565
|
-
const seg = dist(p0, p3);
|
|
566
|
-
const eps = 1e-6 * seg;
|
|
567
|
-
let al = seg / 3;
|
|
568
|
-
let ar = seg / 3;
|
|
569
|
-
if (Math.abs(det) > 1e-12) {
|
|
570
|
-
al = (x0 * c11 - x1 * c01) / det;
|
|
571
|
-
ar = (c00 * x1 - c01 * x0) / det;
|
|
572
|
-
}
|
|
573
|
-
if (!Number.isFinite(al) || !Number.isFinite(ar) || al < eps || ar < eps) {
|
|
574
|
-
al = seg / 3;
|
|
575
|
-
ar = seg / 3;
|
|
576
|
-
}
|
|
577
|
-
const cap = seg * 4 + 1e-9;
|
|
578
|
-
al = Math.min(al, cap);
|
|
579
|
-
ar = Math.min(ar, cap);
|
|
580
|
-
return { p0, p1: add(p0, t0, al), p2: add(p3, dirIn, ar), p3 };
|
|
581
|
-
}
|
|
582
|
-
function maxError(pts, c, u) {
|
|
583
|
-
let e = 0;
|
|
584
|
-
let idx = pts.length >> 1;
|
|
585
|
-
for (let i = 0; i < pts.length; i++) {
|
|
586
|
-
const d = dist(evalCubic(c, u[i]), pts[i]);
|
|
587
|
-
if (d > e) {
|
|
588
|
-
e = d;
|
|
589
|
-
idx = i;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
return [e, idx];
|
|
593
|
-
}
|
|
594
|
-
function newton(c, p, t) {
|
|
595
|
-
const s = 1 - t;
|
|
596
|
-
const q = evalCubic(c, t);
|
|
597
|
-
const d1 = [
|
|
598
|
-
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])),
|
|
599
|
-
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]))
|
|
600
|
-
];
|
|
601
|
-
const d2 = [
|
|
602
|
-
6 * (s * (c.p2[0] - 2 * c.p1[0] + c.p0[0]) + t * (c.p3[0] - 2 * c.p2[0] + c.p1[0])),
|
|
603
|
-
6 * (s * (c.p2[1] - 2 * c.p1[1] + c.p0[1]) + t * (c.p3[1] - 2 * c.p2[1] + c.p1[1]))
|
|
604
|
-
];
|
|
605
|
-
const diff = [q[0] - p[0], q[1] - p[1]];
|
|
606
|
-
const num = dot(diff, d1);
|
|
607
|
-
const den = dot(d1, d1) + dot(diff, d2);
|
|
608
|
-
if (Math.abs(den) < 1e-12) return t;
|
|
609
|
-
const nt = t - num / den;
|
|
610
|
-
return Number.isFinite(nt) ? Math.min(1, Math.max(0, nt)) : t;
|
|
611
|
-
}
|
|
612
|
-
function fitSingleFrom(pts, u, t0, t1) {
|
|
613
|
-
let best = generate(pts, u, t0, t1);
|
|
614
|
-
let [bestErr, bestSplit] = maxError(pts, best, u);
|
|
615
|
-
const params = u.slice();
|
|
616
|
-
for (let round = 0; round < MAX_REPARAM; round++) {
|
|
617
|
-
for (let i = 0; i < params.length; i++) params[i] = newton(best, pts[i], params[i]);
|
|
618
|
-
const c = generate(pts, params, t0, t1);
|
|
619
|
-
const [e, s] = maxError(pts, c, params);
|
|
620
|
-
if (e < bestErr) {
|
|
621
|
-
best = c;
|
|
622
|
-
bestErr = e;
|
|
623
|
-
bestSplit = s;
|
|
624
|
-
} else break;
|
|
625
|
-
}
|
|
626
|
-
return [best, bestErr, bestSplit];
|
|
627
|
-
}
|
|
628
|
-
function fitCubicWithTangents(points, params, t0, t1) {
|
|
629
|
-
const [curve, error] = fitSingleFrom(points.slice(), params.slice(), [t0[0], t0[1]], [t1[0], t1[1]]);
|
|
630
|
-
return { curve, error };
|
|
631
|
-
}
|
|
632
|
-
function fitRec(pts, t0, t1, tol, out, depth) {
|
|
633
|
-
const n = pts.length;
|
|
634
|
-
if (n === 2) {
|
|
635
|
-
const d = dist(pts[0], pts[1]) / 3;
|
|
636
|
-
out.push({ p0: pts[0], p1: add(pts[0], t0, d), p2: add(pts[1], t1, -d), p3: pts[1] });
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
const [c, err, splitAt] = fitSingleFrom(pts, chordParams(pts), t0, t1);
|
|
640
|
-
if (err <= tol || depth > 40) {
|
|
641
|
-
out.push(c);
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
const split = Math.min(n - 2, Math.max(1, splitAt));
|
|
645
|
-
const tc = _nullishCoalesce(unit(sub(pts[split + 1], pts[split - 1])), () => ( t0));
|
|
646
|
-
fitRec(pts.slice(0, split + 1), t0, tc, tol, out, depth + 1);
|
|
647
|
-
fitRec(pts.slice(split), tc, t1, tol, out, depth + 1);
|
|
648
|
-
}
|
|
649
|
-
function tangentFrom(a, b, c) {
|
|
650
|
-
const chord = unit(sub(b, a));
|
|
651
|
-
if (!c) return chord;
|
|
652
|
-
const d1 = dist(a, b);
|
|
653
|
-
const d2 = dist(b, c);
|
|
654
|
-
if (!(d1 > 0) || !(d2 > 0)) return chord;
|
|
655
|
-
const ka = -(2 * d1 + d2) / (d1 * (d1 + d2));
|
|
656
|
-
const kb = (d1 + d2) / (d1 * d2);
|
|
657
|
-
const kc = -d1 / (d2 * (d1 + d2));
|
|
658
|
-
const est = unit([ka * a[0] + kb * b[0] + kc * c[0], ka * a[1] + kb * b[1] + kc * c[1]]);
|
|
659
|
-
return est && chord && dot(est, chord) > Math.cos(Math.PI / 4) ? est : chord;
|
|
660
|
-
}
|
|
661
|
-
function fitCubicRun(points, tolerance, t0, t1) {
|
|
662
|
-
const pts = dedup(points);
|
|
663
|
-
const out = [];
|
|
664
|
-
if (pts.length < 2) return out;
|
|
665
|
-
const n = pts.length;
|
|
666
|
-
const start = _nullishCoalesce(_nullishCoalesce((t0 && unit([t0[0], t0[1]])), () => ( tangentFrom(pts[0], pts[1], pts[2]))), () => ( [1, 0]));
|
|
667
|
-
const back = tangentFrom(pts[n - 1], pts[n - 2], pts[n - 3]);
|
|
668
|
-
const end = _nullishCoalesce((t1 && unit([t1[0], t1[1]])), () => ( (back ? [-back[0], -back[1]] : [1, 0])));
|
|
669
|
-
fitRec(pts, start, end, Math.max(tolerance, 1e-9), out, 0);
|
|
670
|
-
return out;
|
|
671
|
-
}
|
|
672
|
-
function turnDeg(a, b, c) {
|
|
673
|
-
const u = unit(sub(b, a));
|
|
674
|
-
const v = unit(sub(c, b));
|
|
675
|
-
if (!u || !v) return 0;
|
|
676
|
-
return Math.acos(Math.min(1, Math.max(-1, dot(u, v)))) * 180 / Math.PI;
|
|
677
|
-
}
|
|
678
|
-
function findCorners(pts, closed, cornerAngle, span2) {
|
|
679
|
-
const n = pts.length;
|
|
680
|
-
const at = (i) => pts[closed ? (i % n + n) % n : i];
|
|
681
|
-
const reach = (i, step) => {
|
|
682
|
-
for (let k = 1; k < (closed ? n : Infinity); k++) {
|
|
683
|
-
const j = i + step * k;
|
|
684
|
-
if (!closed && (j < 0 || j >= n)) return k > 1 ? j - step : null;
|
|
685
|
-
if (dist(at(j), pts[i]) >= span2) return j;
|
|
686
|
-
}
|
|
687
|
-
return null;
|
|
688
|
-
};
|
|
689
|
-
const turns = new Float64Array(n);
|
|
690
|
-
const windows = [];
|
|
691
|
-
for (let i = 0; i < n; i++) {
|
|
692
|
-
windows.push([i, i]);
|
|
693
|
-
if (!closed && (i === 0 || i === n - 1)) continue;
|
|
694
|
-
const prev = reach(i, -1);
|
|
695
|
-
const next = reach(i, 1);
|
|
696
|
-
if (prev === null || next === null) continue;
|
|
697
|
-
windows[i] = [prev, next];
|
|
698
|
-
turns[i] = turnDeg(at(prev), pts[i], at(next));
|
|
699
|
-
}
|
|
700
|
-
const corners = [];
|
|
701
|
-
for (let i = 0; i < n; i++) {
|
|
702
|
-
if (!(turns[i] > cornerAngle)) continue;
|
|
703
|
-
const [lo, hi] = windows[i];
|
|
704
|
-
let sharpest = true;
|
|
705
|
-
for (let j = lo; j <= hi && sharpest; j++) {
|
|
706
|
-
const k = closed ? (j % n + n) % n : j;
|
|
707
|
-
if (k === i || dist(pts[k], pts[i]) >= span2) continue;
|
|
708
|
-
if (turns[k] > turns[i] || turns[k] === turns[i] && k < i) sharpest = false;
|
|
709
|
-
}
|
|
710
|
-
if (sharpest) corners.push(i);
|
|
711
|
-
}
|
|
712
|
-
return corners;
|
|
713
|
-
}
|
|
714
|
-
function densify(run) {
|
|
715
|
-
if (run.length < 3) return run;
|
|
716
|
-
const lengths = [];
|
|
717
|
-
for (let i = 1; i < run.length; i++) lengths.push(dist(run[i - 1], run[i]));
|
|
718
|
-
const sorted = [...lengths].sort((a, b) => a - b);
|
|
719
|
-
const median = sorted[sorted.length >> 1];
|
|
720
|
-
if (!(median > 0)) return run;
|
|
721
|
-
const out = [run[0]];
|
|
722
|
-
for (let i = 1; i < run.length; i++) {
|
|
723
|
-
const len = lengths[i - 1];
|
|
724
|
-
if (len > 2 * median) {
|
|
725
|
-
const steps = Math.min(64, Math.ceil(len / median));
|
|
726
|
-
const a = run[i - 1];
|
|
727
|
-
const b = run[i];
|
|
728
|
-
for (let k = 1; k < steps; k++) {
|
|
729
|
-
const t = k / steps;
|
|
730
|
-
out.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
out.push(run[i]);
|
|
734
|
-
}
|
|
735
|
-
return out;
|
|
736
|
-
}
|
|
737
|
-
function fitCubics(points, tolerance, options = {}) {
|
|
738
|
-
let pts = dedup(points);
|
|
739
|
-
const closed = options.closed === true;
|
|
740
|
-
if (closed && pts.length > 1 && dist(pts[0], pts[pts.length - 1]) <= 1e-12) pts = pts.slice(0, -1);
|
|
741
|
-
if (pts.length < 2) return [];
|
|
742
|
-
const tol = Math.max(tolerance, 1e-6);
|
|
743
|
-
const cornerAngle = _nullishCoalesce(options.cornerAngleDeg, () => ( 30));
|
|
744
|
-
const n = pts.length;
|
|
745
|
-
const span2 = 4 * tol;
|
|
746
|
-
if (!closed || n < 3) {
|
|
747
|
-
const corners2 = [0, ...n > 2 ? findCorners(pts, false, cornerAngle, span2) : [], n - 1];
|
|
748
|
-
const out2 = [];
|
|
749
|
-
for (let k = 1; k < corners2.length; k++) {
|
|
750
|
-
const run = pts.slice(corners2[k - 1], corners2[k] + 1);
|
|
751
|
-
out2.push(...fitCubicRun(densify(run), tol));
|
|
752
|
-
}
|
|
753
|
-
if (closed && n === 2) out2.push(...fitCubicRun([pts[1], pts[0]], tol));
|
|
754
|
-
return out2;
|
|
755
|
-
}
|
|
756
|
-
const corners = findCorners(pts, true, cornerAngle, span2);
|
|
757
|
-
if (corners.length === 0) {
|
|
758
|
-
const ring = [...pts, pts[0]];
|
|
759
|
-
const t = _nullishCoalesce(unit(sub(pts[1], pts[n - 1])), () => ( [1, 0]));
|
|
760
|
-
return fitCubicRun(densify(ring), tol, t, t);
|
|
761
|
-
}
|
|
762
|
-
const out = [];
|
|
763
|
-
for (let k = 0; k < corners.length; k++) {
|
|
764
|
-
const from = corners[k];
|
|
765
|
-
const to = corners[(k + 1) % corners.length];
|
|
766
|
-
const run = [];
|
|
767
|
-
for (let i = from; ; i = (i + 1) % n) {
|
|
768
|
-
run.push(pts[i]);
|
|
769
|
-
if (run.length > 1 && i === to) break;
|
|
273
|
+
const out = [];
|
|
274
|
+
for (let k = 0; k < corners.length; k++) {
|
|
275
|
+
const from = corners[k];
|
|
276
|
+
const to = corners[(k + 1) % corners.length];
|
|
277
|
+
const run = [];
|
|
278
|
+
for (let i = from; ; i = (i + 1) % n) {
|
|
279
|
+
run.push(pts[i]);
|
|
280
|
+
if (run.length > 1 && i === to) break;
|
|
770
281
|
}
|
|
771
282
|
out.push(...fitCubicRun(densify(run), tol));
|
|
772
283
|
}
|
|
@@ -1077,12 +588,12 @@ function isShapeDescription(value) {
|
|
|
1077
588
|
return kind !== "path" || typeof value.d === "string";
|
|
1078
589
|
}
|
|
1079
590
|
var PARAM_COUNTS = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0 };
|
|
1080
|
-
var PathScanner = (
|
|
1081
|
-
constructor(text) {;
|
|
591
|
+
var PathScanner = (_class = class {
|
|
592
|
+
constructor(text) {;_class.prototype.__init.call(this);
|
|
1082
593
|
this.text = text;
|
|
1083
594
|
}
|
|
1084
595
|
|
|
1085
|
-
|
|
596
|
+
__init() {this.pos = 0}
|
|
1086
597
|
skip() {
|
|
1087
598
|
while (this.pos < this.text.length && /[\s,]/.test(this.text[this.pos])) this.pos++;
|
|
1088
599
|
}
|
|
@@ -1117,7 +628,7 @@ var PathScanner = (_class2 = class {
|
|
|
1117
628
|
}
|
|
1118
629
|
return null;
|
|
1119
630
|
}
|
|
1120
|
-
},
|
|
631
|
+
}, _class);
|
|
1121
632
|
function parseSvgPath(d) {
|
|
1122
633
|
const scanner = new PathScanner(_nullishCoalesce(d, () => ( "")));
|
|
1123
634
|
const commands = [];
|
|
@@ -1509,914 +1020,1403 @@ function getShapeRenderData(shape, size = {}, options = {}) {
|
|
|
1509
1020
|
} else if (shape.stroke === void 0) {
|
|
1510
1021
|
strokeStr = "none";
|
|
1511
1022
|
}
|
|
1512
|
-
const data = {
|
|
1513
|
-
viewBox: `0 0 ${formatNumber2(width, 3)} ${formatNumber2(height, 3)}`,
|
|
1514
|
-
width,
|
|
1515
|
-
height,
|
|
1516
|
-
d: svg.d,
|
|
1517
|
-
fillRule: svg.fillRule,
|
|
1518
|
-
fill: fillStr,
|
|
1519
|
-
stroke: strokeStr,
|
|
1520
|
-
strokeWidth: _nullishCoalesce(shape.strokeWidth, () => ( 1))
|
|
1521
|
-
};
|
|
1522
|
-
if (svg.transform) data.transform = svg.transform;
|
|
1523
|
-
if (shape.opacity !== void 0) data.opacity = shape.opacity;
|
|
1524
|
-
if (shape.strokeLinecap) data.strokeLinecap = shape.strokeLinecap;
|
|
1525
|
-
if (shape.strokeLinejoin) data.strokeLinejoin = shape.strokeLinejoin;
|
|
1526
|
-
if (fillGradientDef) data.fillGradient = fillGradientDef;
|
|
1527
|
-
if (strokeGradientDef) data.strokeGradient = strokeGradientDef;
|
|
1528
|
-
return data;
|
|
1023
|
+
const data = {
|
|
1024
|
+
viewBox: `0 0 ${formatNumber2(width, 3)} ${formatNumber2(height, 3)}`,
|
|
1025
|
+
width,
|
|
1026
|
+
height,
|
|
1027
|
+
d: svg.d,
|
|
1028
|
+
fillRule: svg.fillRule,
|
|
1029
|
+
fill: fillStr,
|
|
1030
|
+
stroke: strokeStr,
|
|
1031
|
+
strokeWidth: _nullishCoalesce(shape.strokeWidth, () => ( 1))
|
|
1032
|
+
};
|
|
1033
|
+
if (svg.transform) data.transform = svg.transform;
|
|
1034
|
+
if (shape.opacity !== void 0) data.opacity = shape.opacity;
|
|
1035
|
+
if (shape.strokeLinecap) data.strokeLinecap = shape.strokeLinecap;
|
|
1036
|
+
if (shape.strokeLinejoin) data.strokeLinejoin = shape.strokeLinejoin;
|
|
1037
|
+
if (fillGradientDef) data.fillGradient = fillGradientDef;
|
|
1038
|
+
if (strokeGradientDef) data.strokeGradient = strokeGradientDef;
|
|
1039
|
+
return data;
|
|
1040
|
+
}
|
|
1041
|
+
function clampRatio(value) {
|
|
1042
|
+
if (value === void 0 || !Number.isFinite(value)) return 0.5;
|
|
1043
|
+
return Math.min(1, Math.max(0.01, value));
|
|
1044
|
+
}
|
|
1045
|
+
function validViewBox(vb) {
|
|
1046
|
+
if (!vb || vb.length !== 4 || !vb.every(Number.isFinite) || vb[2] <= 0 || vb[3] <= 0) return null;
|
|
1047
|
+
return vb;
|
|
1048
|
+
}
|
|
1049
|
+
function shapeToPolygon(shape, size, options = {}) {
|
|
1050
|
+
const { width: w, height: h } = size;
|
|
1051
|
+
if (!(w > 0) || !(h > 0)) return { rings: [], fillRule: "nonzero" };
|
|
1052
|
+
let rings;
|
|
1053
|
+
let fillRule = "nonzero";
|
|
1054
|
+
if (shape.kind === "path") {
|
|
1055
|
+
fillRule = _nullishCoalesce(shape.fillRule, () => ( "nonzero"));
|
|
1056
|
+
const vb = validViewBox(shape.viewBox);
|
|
1057
|
+
if (vb) {
|
|
1058
|
+
const sx = w / vb[2];
|
|
1059
|
+
const sy = h / vb[3];
|
|
1060
|
+
const tolerance = (_nullishCoalesce(options.tolerance, () => ( 0.25))) / Math.max(sx, sy);
|
|
1061
|
+
rings = flattenSvgPath(shape.d, { ...options, tolerance }).map(
|
|
1062
|
+
(ring) => ring.map(([x, y]) => [(x - vb[0]) * sx, (y - vb[1]) * sy])
|
|
1063
|
+
);
|
|
1064
|
+
} else {
|
|
1065
|
+
rings = flattenSvgPath(shape.d, options);
|
|
1066
|
+
}
|
|
1067
|
+
} else {
|
|
1068
|
+
const { d } = getShapeSvg({ ...shape, rotation: 0 }, size);
|
|
1069
|
+
rings = flattenSvgPath(d, options);
|
|
1070
|
+
}
|
|
1071
|
+
const rotate = rotationTransform(shape, size);
|
|
1072
|
+
if (rotate) rings = rings.map((ring) => ring.map(rotate));
|
|
1073
|
+
return { rings, fillRule };
|
|
1074
|
+
}
|
|
1075
|
+
function formatNumber2(value, precision = 3) {
|
|
1076
|
+
const factor = 10 ** precision;
|
|
1077
|
+
const rounded = Math.round(value * factor) / factor;
|
|
1078
|
+
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
1079
|
+
}
|
|
1080
|
+
function ringsToPath(rings, f) {
|
|
1081
|
+
let d = "";
|
|
1082
|
+
for (const ring of rings) {
|
|
1083
|
+
if (ring.length < 2) continue;
|
|
1084
|
+
d += `M${f(ring[0][0])} ${f(ring[0][1])}`;
|
|
1085
|
+
for (let i = 1; i < ring.length; i++) d += `L${f(ring[i][0])} ${f(ring[i][1])}`;
|
|
1086
|
+
d += "Z";
|
|
1087
|
+
}
|
|
1088
|
+
return d;
|
|
1089
|
+
}
|
|
1090
|
+
function segmentDistance(p, a, b) {
|
|
1091
|
+
const dx = b[0] - a[0];
|
|
1092
|
+
const dy = b[1] - a[1];
|
|
1093
|
+
const len2 = dx * dx + dy * dy;
|
|
1094
|
+
if (len2 === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);
|
|
1095
|
+
const t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));
|
|
1096
|
+
return Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));
|
|
1097
|
+
}
|
|
1098
|
+
function rdp(points, tolerance) {
|
|
1099
|
+
if (points.length <= 2) return points;
|
|
1100
|
+
const keep = new Uint8Array(points.length);
|
|
1101
|
+
keep[0] = 1;
|
|
1102
|
+
keep[points.length - 1] = 1;
|
|
1103
|
+
const stack = [[0, points.length - 1]];
|
|
1104
|
+
while (stack.length) {
|
|
1105
|
+
const [s, e] = stack.pop();
|
|
1106
|
+
let maxDist = -1;
|
|
1107
|
+
let index = -1;
|
|
1108
|
+
for (let i = s + 1; i < e; i++) {
|
|
1109
|
+
const dist3 = segmentDistance(points[i], points[s], points[e]);
|
|
1110
|
+
if (dist3 > maxDist) {
|
|
1111
|
+
maxDist = dist3;
|
|
1112
|
+
index = i;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (index !== -1 && maxDist > tolerance) {
|
|
1116
|
+
keep[index] = 1;
|
|
1117
|
+
stack.push([s, index], [index, e]);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
return points.filter((_, i) => keep[i]);
|
|
1121
|
+
}
|
|
1122
|
+
function simplifyRing(ring, tolerance) {
|
|
1123
|
+
if (ring.length <= 3 || !(tolerance > 0)) return [...ring];
|
|
1124
|
+
let far = 0;
|
|
1125
|
+
let farDist = -1;
|
|
1126
|
+
for (let i = 1; i < ring.length; i++) {
|
|
1127
|
+
const dist3 = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);
|
|
1128
|
+
if (dist3 > farDist) {
|
|
1129
|
+
farDist = dist3;
|
|
1130
|
+
far = i;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
const first = rdp(ring.slice(0, far + 1), tolerance);
|
|
1134
|
+
const second = rdp([...ring.slice(far), ring[0]], tolerance);
|
|
1135
|
+
const out = [...first.slice(0, -1), ...second.slice(0, -1)];
|
|
1136
|
+
return out.length >= 3 ? out : [];
|
|
1137
|
+
}
|
|
1138
|
+
function polygonToSvgPath(input, options = {}) {
|
|
1139
|
+
const precision = _nullishCoalesce(options.precision, () => ( 3));
|
|
1140
|
+
const f = (n) => formatNumber2(n, precision);
|
|
1141
|
+
const dx = _nullishCoalesce(_optionalChain([options, 'access', _14 => _14.offset, 'optionalAccess', _15 => _15.x]), () => ( 0));
|
|
1142
|
+
const dy = _nullishCoalesce(_optionalChain([options, 'access', _16 => _16.offset, 'optionalAccess', _17 => _17.y]), () => ( 0));
|
|
1143
|
+
const rings = [];
|
|
1144
|
+
const fit = options.curves === "fit";
|
|
1145
|
+
let fitted = "";
|
|
1146
|
+
for (const item of input) {
|
|
1147
|
+
const list = item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0]) ? item : [item];
|
|
1148
|
+
for (const ring of list) {
|
|
1149
|
+
let r = ring;
|
|
1150
|
+
if (options.simplify && options.simplify > 0) r = simplifyRing(r, options.simplify);
|
|
1151
|
+
if (r.length < 3) continue;
|
|
1152
|
+
if (fit) {
|
|
1153
|
+
fitted += fitPathData(r, {
|
|
1154
|
+
tolerance: _nullishCoalesce(options.curveTolerance, () => ( 0.25)),
|
|
1155
|
+
cornerAngleDeg: options.cornerAngleDeg,
|
|
1156
|
+
precision,
|
|
1157
|
+
offset: { x: dx, y: dy }
|
|
1158
|
+
});
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
rings.push(dx || dy ? r.map(([x, y]) => [x + dx, y + dy]) : r);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return fit ? fitted : ringsToPath(rings, f);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// src/canvas/pathops/bezier.ts
|
|
1168
|
+
var sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
1169
|
+
var add2 = (a, b) => [a[0] + b[0], a[1] + b[1]];
|
|
1170
|
+
var scale = (a, s) => [a[0] * s, a[1] * s];
|
|
1171
|
+
var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
1172
|
+
var cross = (a, b) => a[0] * b[1] - a[1] * b[0];
|
|
1173
|
+
var dist2 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
1174
|
+
var lerp = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
1175
|
+
var samePoint = (a, b) => a[0] === b[0] && a[1] === b[1];
|
|
1176
|
+
function unit2(v) {
|
|
1177
|
+
const l = Math.hypot(v[0], v[1]);
|
|
1178
|
+
return l > 0 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
|
|
1179
|
+
}
|
|
1180
|
+
function segmentPoint(seg, t) {
|
|
1181
|
+
const n = seg.length;
|
|
1182
|
+
if (n === 2) return lerp(seg[0], seg[1], t);
|
|
1183
|
+
const mt = 1 - t;
|
|
1184
|
+
if (n === 3) {
|
|
1185
|
+
const a2 = mt * mt;
|
|
1186
|
+
const b2 = 2 * mt * t;
|
|
1187
|
+
const c2 = t * t;
|
|
1188
|
+
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]];
|
|
1189
|
+
}
|
|
1190
|
+
const a = mt * mt * mt;
|
|
1191
|
+
const b = 3 * mt * mt * t;
|
|
1192
|
+
const c = 3 * mt * t * t;
|
|
1193
|
+
const d = t * t * t;
|
|
1194
|
+
return [
|
|
1195
|
+
a * seg[0][0] + b * seg[1][0] + c * seg[2][0] + d * seg[3][0],
|
|
1196
|
+
a * seg[0][1] + b * seg[1][1] + c * seg[2][1] + d * seg[3][1]
|
|
1197
|
+
];
|
|
1198
|
+
}
|
|
1199
|
+
function segmentDerivative(seg, t) {
|
|
1200
|
+
const n = seg.length;
|
|
1201
|
+
if (n === 2) return sub2(seg[1], seg[0]);
|
|
1202
|
+
if (n === 3) {
|
|
1203
|
+
const a2 = sub2(seg[1], seg[0]);
|
|
1204
|
+
const b2 = sub2(seg[2], seg[1]);
|
|
1205
|
+
return [2 * ((1 - t) * a2[0] + t * b2[0]), 2 * ((1 - t) * a2[1] + t * b2[1])];
|
|
1206
|
+
}
|
|
1207
|
+
const a = sub2(seg[1], seg[0]);
|
|
1208
|
+
const b = sub2(seg[2], seg[1]);
|
|
1209
|
+
const c = sub2(seg[3], seg[2]);
|
|
1210
|
+
const mt = 1 - t;
|
|
1211
|
+
return [
|
|
1212
|
+
3 * (mt * mt * a[0] + 2 * mt * t * b[0] + t * t * c[0]),
|
|
1213
|
+
3 * (mt * mt * a[1] + 2 * mt * t * b[1] + t * t * c[1])
|
|
1214
|
+
];
|
|
1215
|
+
}
|
|
1216
|
+
function segmentSecondDerivative(seg, t) {
|
|
1217
|
+
const n = seg.length;
|
|
1218
|
+
if (n === 2) return [0, 0];
|
|
1219
|
+
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])];
|
|
1220
|
+
const a = [seg[2][0] - 2 * seg[1][0] + seg[0][0], seg[2][1] - 2 * seg[1][1] + seg[0][1]];
|
|
1221
|
+
const b = [seg[3][0] - 2 * seg[2][0] + seg[1][0], seg[3][1] - 2 * seg[2][1] + seg[1][1]];
|
|
1222
|
+
return [6 * ((1 - t) * a[0] + t * b[0]), 6 * ((1 - t) * a[1] + t * b[1])];
|
|
1223
|
+
}
|
|
1224
|
+
function segmentTangent(seg, t) {
|
|
1225
|
+
const d = unit2(segmentDerivative(seg, t));
|
|
1226
|
+
if (d) return d;
|
|
1227
|
+
const h = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
|
|
1228
|
+
const e = unit2(segmentDerivative(seg, h));
|
|
1229
|
+
if (e) return e;
|
|
1230
|
+
return _nullishCoalesce(unit2(sub2(seg[seg.length - 1], seg[0])), () => ( [1, 0]));
|
|
1231
|
+
}
|
|
1232
|
+
function segmentCurvature(seg, t) {
|
|
1233
|
+
if (seg.length === 2) return 0;
|
|
1234
|
+
let d = segmentDerivative(seg, t);
|
|
1235
|
+
let tt = t;
|
|
1236
|
+
if (Math.hypot(d[0], d[1]) < 1e-12) {
|
|
1237
|
+
tt = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
|
|
1238
|
+
d = segmentDerivative(seg, tt);
|
|
1239
|
+
}
|
|
1240
|
+
const dd = segmentSecondDerivative(seg, tt);
|
|
1241
|
+
const len = Math.hypot(d[0], d[1]);
|
|
1242
|
+
if (!(len > 0)) return 0;
|
|
1243
|
+
return cross(d, dd) / (len * len * len);
|
|
1529
1244
|
}
|
|
1530
|
-
function
|
|
1531
|
-
|
|
1532
|
-
|
|
1245
|
+
function splitSegment(seg, t) {
|
|
1246
|
+
const n = seg.length;
|
|
1247
|
+
if (n === 2) {
|
|
1248
|
+
const m2 = lerp(seg[0], seg[1], t);
|
|
1249
|
+
return [
|
|
1250
|
+
[seg[0], m2],
|
|
1251
|
+
[m2, seg[1]]
|
|
1252
|
+
];
|
|
1253
|
+
}
|
|
1254
|
+
if (n === 3) {
|
|
1255
|
+
const a2 = lerp(seg[0], seg[1], t);
|
|
1256
|
+
const b2 = lerp(seg[1], seg[2], t);
|
|
1257
|
+
const m2 = lerp(a2, b2, t);
|
|
1258
|
+
return [
|
|
1259
|
+
[seg[0], a2, m2],
|
|
1260
|
+
[m2, b2, seg[2]]
|
|
1261
|
+
];
|
|
1262
|
+
}
|
|
1263
|
+
const a = lerp(seg[0], seg[1], t);
|
|
1264
|
+
const b = lerp(seg[1], seg[2], t);
|
|
1265
|
+
const c = lerp(seg[2], seg[3], t);
|
|
1266
|
+
const ab = lerp(a, b, t);
|
|
1267
|
+
const bc = lerp(b, c, t);
|
|
1268
|
+
const m = lerp(ab, bc, t);
|
|
1269
|
+
return [
|
|
1270
|
+
[seg[0], a, ab, m],
|
|
1271
|
+
[m, bc, c, seg[3]]
|
|
1272
|
+
];
|
|
1533
1273
|
}
|
|
1534
|
-
function
|
|
1535
|
-
if (
|
|
1536
|
-
return
|
|
1274
|
+
function subSegment(seg, t0, t1) {
|
|
1275
|
+
if (t1 < t0) return subSegment(seg, t1, t0).reverse();
|
|
1276
|
+
if (t0 <= 0 && t1 >= 1) return [...seg];
|
|
1277
|
+
let s = [...seg];
|
|
1278
|
+
if (t1 < 1) s = splitSegment(s, t1)[0];
|
|
1279
|
+
if (t0 > 0) s = splitSegment(s, t1 > 0 ? t0 / t1 : 0)[1];
|
|
1280
|
+
return s;
|
|
1537
1281
|
}
|
|
1538
|
-
function
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
const
|
|
1546
|
-
if (
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const tolerance = (_nullishCoalesce(options.tolerance, () => ( 0.25))) / Math.max(sx, sy);
|
|
1550
|
-
rings = flattenSvgPath(shape.d, { ...options, tolerance }).map(
|
|
1551
|
-
(ring) => ring.map(([x, y]) => [(x - vb[0]) * sx, (y - vb[1]) * sy])
|
|
1552
|
-
);
|
|
1553
|
-
} else {
|
|
1554
|
-
rings = flattenSvgPath(shape.d, options);
|
|
1282
|
+
function isStraightSegment(seg, tol) {
|
|
1283
|
+
if (seg.length === 2) return true;
|
|
1284
|
+
const a = seg[0];
|
|
1285
|
+
const b = seg[seg.length - 1];
|
|
1286
|
+
const chord = sub2(b, a);
|
|
1287
|
+
const len = Math.hypot(chord[0], chord[1]);
|
|
1288
|
+
for (let i = 1; i < seg.length - 1; i++) {
|
|
1289
|
+
const v = sub2(seg[i], a);
|
|
1290
|
+
if (len === 0) {
|
|
1291
|
+
if (Math.hypot(v[0], v[1]) > tol) return false;
|
|
1292
|
+
continue;
|
|
1555
1293
|
}
|
|
1556
|
-
|
|
1557
|
-
const
|
|
1558
|
-
|
|
1294
|
+
if (Math.abs(cross(chord, v)) / len > tol) return false;
|
|
1295
|
+
const along = dot2(chord, v) / len;
|
|
1296
|
+
if (along < -tol || along > len + tol) return false;
|
|
1559
1297
|
}
|
|
1560
|
-
|
|
1561
|
-
if (rotate) rings = rings.map((ring) => ring.map(rotate));
|
|
1562
|
-
return { rings, fillRule };
|
|
1563
|
-
}
|
|
1564
|
-
function formatNumber2(value, precision = 3) {
|
|
1565
|
-
const factor = 10 ** precision;
|
|
1566
|
-
const rounded = Math.round(value * factor) / factor;
|
|
1567
|
-
return Object.is(rounded, -0) ? "0" : String(rounded);
|
|
1298
|
+
return true;
|
|
1568
1299
|
}
|
|
1569
|
-
function
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1300
|
+
function quadraticRoots(a, b, c, out) {
|
|
1301
|
+
const push = (t) => {
|
|
1302
|
+
if (t > 1e-12 && t < 1 - 1e-12) out.push(t);
|
|
1303
|
+
};
|
|
1304
|
+
const scaleAbs = Math.max(Math.abs(a), Math.abs(b), Math.abs(c));
|
|
1305
|
+
if (scaleAbs === 0) return;
|
|
1306
|
+
if (Math.abs(a) <= 1e-12 * scaleAbs) {
|
|
1307
|
+
if (b !== 0) push(-c / b);
|
|
1308
|
+
return;
|
|
1576
1309
|
}
|
|
1577
|
-
|
|
1310
|
+
const disc = b * b - 4 * a * c;
|
|
1311
|
+
if (disc < 0) return;
|
|
1312
|
+
const sq = Math.sqrt(disc);
|
|
1313
|
+
const q = -0.5 * (b + (b >= 0 ? sq : -sq));
|
|
1314
|
+
push(q / a);
|
|
1315
|
+
if (q !== 0) push(c / q);
|
|
1578
1316
|
}
|
|
1579
|
-
function
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1317
|
+
function segmentExtrema(seg, axis) {
|
|
1318
|
+
const out = [];
|
|
1319
|
+
if (seg.length === 3) {
|
|
1320
|
+
const d = seg[0][axis] - 2 * seg[1][axis] + seg[2][axis];
|
|
1321
|
+
if (d !== 0) {
|
|
1322
|
+
const t = (seg[0][axis] - seg[1][axis]) / d;
|
|
1323
|
+
if (t > 1e-12 && t < 1 - 1e-12) out.push(t);
|
|
1324
|
+
}
|
|
1325
|
+
} else if (seg.length === 4) {
|
|
1326
|
+
const p0 = seg[0][axis];
|
|
1327
|
+
const p1 = seg[1][axis];
|
|
1328
|
+
const p2 = seg[2][axis];
|
|
1329
|
+
const p3 = seg[3][axis];
|
|
1330
|
+
const a = -p0 + 3 * p1 - 3 * p2 + p3;
|
|
1331
|
+
const b = 2 * (p0 - 2 * p1 + p2);
|
|
1332
|
+
const c = p1 - p0;
|
|
1333
|
+
quadraticRoots(a, b, c, out);
|
|
1334
|
+
}
|
|
1335
|
+
return out.sort((x, y) => x - y);
|
|
1586
1336
|
}
|
|
1587
|
-
function
|
|
1588
|
-
|
|
1589
|
-
const
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1337
|
+
function segmentBounds(seg) {
|
|
1338
|
+
const a = seg[0];
|
|
1339
|
+
const b = seg[seg.length - 1];
|
|
1340
|
+
let minX = Math.min(a[0], b[0]);
|
|
1341
|
+
let maxX = Math.max(a[0], b[0]);
|
|
1342
|
+
let minY = Math.min(a[1], b[1]);
|
|
1343
|
+
let maxY = Math.max(a[1], b[1]);
|
|
1344
|
+
for (const axis of [0, 1]) {
|
|
1345
|
+
for (const t of segmentExtrema(seg, axis)) {
|
|
1346
|
+
const p = segmentPoint(seg, t);
|
|
1347
|
+
if (axis === 0) {
|
|
1348
|
+
minX = Math.min(minX, p[0]);
|
|
1349
|
+
maxX = Math.max(maxX, p[0]);
|
|
1350
|
+
} else {
|
|
1351
|
+
minY = Math.min(minY, p[1]);
|
|
1352
|
+
maxY = Math.max(maxY, p[1]);
|
|
1602
1353
|
}
|
|
1603
1354
|
}
|
|
1604
|
-
if (index !== -1 && maxDist > tolerance) {
|
|
1605
|
-
keep[index] = 1;
|
|
1606
|
-
stack.push([s, index], [index, e]);
|
|
1607
|
-
}
|
|
1608
1355
|
}
|
|
1609
|
-
return
|
|
1356
|
+
return { minX, minY, maxX, maxY };
|
|
1357
|
+
}
|
|
1358
|
+
function monotoneParams(seg) {
|
|
1359
|
+
if (seg.length === 2) return [];
|
|
1360
|
+
const ts = [...segmentExtrema(seg, 0), ...segmentExtrema(seg, 1)].sort((a, b) => a - b);
|
|
1361
|
+
const out = [];
|
|
1362
|
+
for (const t of ts) if (out.length === 0 || t - out[out.length - 1] > 1e-9) out.push(t);
|
|
1363
|
+
return out;
|
|
1364
|
+
}
|
|
1365
|
+
function segmentAreaTerm(seg) {
|
|
1366
|
+
if (seg.length === 2) return (seg[0][0] * seg[1][1] - seg[1][0] * seg[0][1]) / 2;
|
|
1367
|
+
const nodes = [-Math.sqrt(3 / 5), 0, Math.sqrt(3 / 5)];
|
|
1368
|
+
const weights = [5 / 9, 8 / 9, 5 / 9];
|
|
1369
|
+
let sum = 0;
|
|
1370
|
+
for (let i = 0; i < 3; i++) {
|
|
1371
|
+
const t = (nodes[i] + 1) / 2;
|
|
1372
|
+
const p = segmentPoint(seg, t);
|
|
1373
|
+
const d = segmentDerivative(seg, t);
|
|
1374
|
+
sum += weights[i] * (p[0] * d[1] - p[1] * d[0]);
|
|
1375
|
+
}
|
|
1376
|
+
return sum / 4;
|
|
1377
|
+
}
|
|
1378
|
+
var GL_X = [
|
|
1379
|
+
-0.9602898564975363,
|
|
1380
|
+
-0.7966664774136267,
|
|
1381
|
+
-0.525532409916329,
|
|
1382
|
+
-0.1834346424956498,
|
|
1383
|
+
0.1834346424956498,
|
|
1384
|
+
0.525532409916329,
|
|
1385
|
+
0.7966664774136267,
|
|
1386
|
+
0.9602898564975363
|
|
1387
|
+
];
|
|
1388
|
+
var GL_W = [
|
|
1389
|
+
0.1012285362903763,
|
|
1390
|
+
0.2223810344533745,
|
|
1391
|
+
0.3137066458778873,
|
|
1392
|
+
0.362683783378362,
|
|
1393
|
+
0.362683783378362,
|
|
1394
|
+
0.3137066458778873,
|
|
1395
|
+
0.2223810344533745,
|
|
1396
|
+
0.1012285362903763
|
|
1397
|
+
];
|
|
1398
|
+
function glLength(seg, t0, t1) {
|
|
1399
|
+
const half = (t1 - t0) / 2;
|
|
1400
|
+
const mid = (t0 + t1) / 2;
|
|
1401
|
+
let sum = 0;
|
|
1402
|
+
for (let i = 0; i < GL_X.length; i++) {
|
|
1403
|
+
const d = segmentDerivative(seg, mid + half * GL_X[i]);
|
|
1404
|
+
sum += GL_W[i] * Math.hypot(d[0], d[1]);
|
|
1405
|
+
}
|
|
1406
|
+
return sum * half;
|
|
1610
1407
|
}
|
|
1611
|
-
function
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1408
|
+
function adaptiveLength(seg, t0, t1, whole, tol, depth) {
|
|
1409
|
+
const m = (t0 + t1) / 2;
|
|
1410
|
+
const left = glLength(seg, t0, m);
|
|
1411
|
+
const right = glLength(seg, m, t1);
|
|
1412
|
+
if (depth >= 18 || Math.abs(left + right - whole) <= tol) return left + right;
|
|
1413
|
+
return adaptiveLength(seg, t0, m, left, tol / 2, depth + 1) + adaptiveLength(seg, m, t1, right, tol / 2, depth + 1);
|
|
1414
|
+
}
|
|
1415
|
+
function segmentLength(seg, t0 = 0, t1 = 1, tolerance = 1e-9) {
|
|
1416
|
+
if (t1 < t0) return segmentLength(seg, t1, t0, tolerance);
|
|
1417
|
+
if (seg.length === 2) return dist2(seg[0], seg[1]) * (t1 - t0);
|
|
1418
|
+
const whole = glLength(seg, t0, t1);
|
|
1419
|
+
return adaptiveLength(seg, t0, t1, whole, Math.max(tolerance, whole * 1e-12), 0);
|
|
1420
|
+
}
|
|
1421
|
+
function segmentParamAtLength(seg, length, total = segmentLength(seg)) {
|
|
1422
|
+
if (!(total > 0) || length <= 0) return 0;
|
|
1423
|
+
if (length >= total) return 1;
|
|
1424
|
+
if (seg.length === 2) return length / total;
|
|
1425
|
+
let lo = 0;
|
|
1426
|
+
let hi = 1;
|
|
1427
|
+
let t = length / total;
|
|
1428
|
+
for (let i = 0; i < 40; i++) {
|
|
1429
|
+
const err = segmentLength(seg, 0, t) - length;
|
|
1430
|
+
if (Math.abs(err) <= 1e-10 * Math.max(1, total)) break;
|
|
1431
|
+
if (err > 0) hi = t;
|
|
1432
|
+
else lo = t;
|
|
1433
|
+
const d = segmentDerivative(seg, t);
|
|
1434
|
+
const speed = Math.hypot(d[0], d[1]);
|
|
1435
|
+
let next = speed > 0 ? t - err / speed : (lo + hi) / 2;
|
|
1436
|
+
if (!(next > lo && next < hi)) next = (lo + hi) / 2;
|
|
1437
|
+
t = next;
|
|
1621
1438
|
}
|
|
1622
|
-
|
|
1623
|
-
const second = rdp([...ring.slice(far), ring[0]], tolerance);
|
|
1624
|
-
const out = [...first.slice(0, -1), ...second.slice(0, -1)];
|
|
1625
|
-
return out.length >= 3 ? out : [];
|
|
1439
|
+
return t;
|
|
1626
1440
|
}
|
|
1627
|
-
function
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1441
|
+
function segmentNearest(seg, p) {
|
|
1442
|
+
if (seg.length === 2) {
|
|
1443
|
+
const d = sub2(seg[1], seg[0]);
|
|
1444
|
+
const len2 = dot2(d, d);
|
|
1445
|
+
const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, dot2(sub2(p, seg[0]), d) / len2));
|
|
1446
|
+
return { t, distance: dist2(p, segmentPoint(seg, t)) };
|
|
1447
|
+
}
|
|
1448
|
+
const samples = 16;
|
|
1449
|
+
let best = { t: 0, distance: dist2(p, seg[0]) };
|
|
1450
|
+
const candidates = [];
|
|
1451
|
+
let prev = Infinity;
|
|
1452
|
+
let prevT = 0;
|
|
1453
|
+
let descending = false;
|
|
1454
|
+
for (let i = 0; i <= samples; i++) {
|
|
1455
|
+
const t = i / samples;
|
|
1456
|
+
const d = dist2(p, segmentPoint(seg, t));
|
|
1457
|
+
if (d < best.distance) best = { t, distance: d };
|
|
1458
|
+
if (i > 0 && d > prev && descending) candidates.push(prevT);
|
|
1459
|
+
descending = d < prev;
|
|
1460
|
+
prev = d;
|
|
1461
|
+
prevT = t;
|
|
1462
|
+
}
|
|
1463
|
+
candidates.push(best.t, 1);
|
|
1464
|
+
for (const start of candidates) {
|
|
1465
|
+
let t = start;
|
|
1466
|
+
for (let k = 0; k < 12; k++) {
|
|
1467
|
+
const q = segmentPoint(seg, t);
|
|
1468
|
+
const d1 = segmentDerivative(seg, t);
|
|
1469
|
+
const d2 = segmentSecondDerivative(seg, t);
|
|
1470
|
+
const diff = sub2(q, p);
|
|
1471
|
+
const f = dot2(diff, d1);
|
|
1472
|
+
const fp = dot2(d1, d1) + dot2(diff, d2);
|
|
1473
|
+
if (!(Math.abs(fp) > 1e-300)) break;
|
|
1474
|
+
const next = Math.max(0, Math.min(1, t - f / fp));
|
|
1475
|
+
if (Math.abs(next - t) < 1e-14) {
|
|
1476
|
+
t = next;
|
|
1477
|
+
break;
|
|
1649
1478
|
}
|
|
1650
|
-
|
|
1479
|
+
t = next;
|
|
1651
1480
|
}
|
|
1481
|
+
const d = dist2(p, segmentPoint(seg, t));
|
|
1482
|
+
if (d < best.distance) best = { t, distance: d };
|
|
1652
1483
|
}
|
|
1653
|
-
return
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
// src/canvas/pathops/bezier.ts
|
|
1657
|
-
var sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
1658
|
-
var add2 = (a, b) => [a[0] + b[0], a[1] + b[1]];
|
|
1659
|
-
var scale = (a, s) => [a[0] * s, a[1] * s];
|
|
1660
|
-
var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1];
|
|
1661
|
-
var cross = (a, b) => a[0] * b[1] - a[1] * b[0];
|
|
1662
|
-
var dist2 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
1663
|
-
var lerp = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
1664
|
-
var samePoint = (a, b) => a[0] === b[0] && a[1] === b[1];
|
|
1665
|
-
function unit2(v) {
|
|
1666
|
-
const l = Math.hypot(v[0], v[1]);
|
|
1667
|
-
return l > 0 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
|
|
1484
|
+
return best;
|
|
1668
1485
|
}
|
|
1669
|
-
function
|
|
1670
|
-
const
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1486
|
+
function arcToCubics(from, cmd) {
|
|
1487
|
+
const [x0, y0] = from;
|
|
1488
|
+
const { x, y } = cmd;
|
|
1489
|
+
if (x0 === x && y0 === y) return [];
|
|
1490
|
+
let rx = Math.abs(cmd.rx);
|
|
1491
|
+
let ry = Math.abs(cmd.ry);
|
|
1492
|
+
if (rx === 0 || ry === 0) return [[from, [x, y]]];
|
|
1493
|
+
const phi = cmd.rotation * Math.PI / 180;
|
|
1494
|
+
const cos = Math.cos(phi);
|
|
1495
|
+
const sin = Math.sin(phi);
|
|
1496
|
+
const dx = (x0 - x) / 2;
|
|
1497
|
+
const dy = (y0 - y) / 2;
|
|
1498
|
+
const x1p = cos * dx + sin * dy;
|
|
1499
|
+
const y1p = -sin * dx + cos * dy;
|
|
1500
|
+
const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
|
|
1501
|
+
if (lambda > 1) {
|
|
1502
|
+
const s = Math.sqrt(lambda);
|
|
1503
|
+
rx *= s;
|
|
1504
|
+
ry *= s;
|
|
1678
1505
|
}
|
|
1679
|
-
const
|
|
1680
|
-
const
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1506
|
+
const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
|
|
1507
|
+
const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
|
|
1508
|
+
let coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));
|
|
1509
|
+
if (cmd.largeArc === cmd.sweep) coef = -coef;
|
|
1510
|
+
const cxp = coef * rx * y1p / ry;
|
|
1511
|
+
const cyp = -coef * ry * x1p / rx;
|
|
1512
|
+
const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
|
|
1513
|
+
const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
|
|
1514
|
+
const angle = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
1515
|
+
const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
|
|
1516
|
+
let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
|
|
1517
|
+
if (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;
|
|
1518
|
+
else if (cmd.sweep && delta < 0) delta += 2 * Math.PI;
|
|
1519
|
+
const n = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9));
|
|
1520
|
+
const step = delta / n;
|
|
1521
|
+
const k = 4 / 3 * Math.tan(step / 4);
|
|
1522
|
+
const map = (ux, uy) => {
|
|
1523
|
+
const ex = rx * ux;
|
|
1524
|
+
const ey = ry * uy;
|
|
1525
|
+
return [cos * ex - sin * ey + cx, sin * ex + cos * ey + cy];
|
|
1526
|
+
};
|
|
1527
|
+
const out = [];
|
|
1528
|
+
let prev = from;
|
|
1529
|
+
for (let i = 0; i < n; i++) {
|
|
1530
|
+
const a0 = theta1 + step * i;
|
|
1531
|
+
const a1 = a0 + step;
|
|
1532
|
+
const c0 = Math.cos(a0);
|
|
1533
|
+
const s0 = Math.sin(a0);
|
|
1534
|
+
const c1 = Math.cos(a1);
|
|
1535
|
+
const s1 = Math.sin(a1);
|
|
1536
|
+
const end = i === n - 1 ? [x, y] : map(c1, s1);
|
|
1537
|
+
out.push([prev, map(c0 - k * s0, s0 + k * c0), map(c1 + k * s1, s1 - k * c1), end]);
|
|
1538
|
+
prev = end;
|
|
1539
|
+
}
|
|
1540
|
+
return out;
|
|
1687
1541
|
}
|
|
1688
|
-
function
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1542
|
+
function isBezierCommandList(input) {
|
|
1543
|
+
return Array.isArray(input) && !input.some((c) => c && c.type === "A");
|
|
1544
|
+
}
|
|
1545
|
+
function toBezierPath(input) {
|
|
1546
|
+
const commands = typeof input === "string" ? parseSvgPath(input) : isBezierCommandList(input) ? input : input;
|
|
1547
|
+
const out = [];
|
|
1548
|
+
let current = [0, 0];
|
|
1549
|
+
let start = [0, 0];
|
|
1550
|
+
let open = false;
|
|
1551
|
+
const ensureMove = () => {
|
|
1552
|
+
if (!open) {
|
|
1553
|
+
out.push({ type: "M", x: start[0], y: start[1] });
|
|
1554
|
+
current = start;
|
|
1555
|
+
open = true;
|
|
1556
|
+
}
|
|
1557
|
+
};
|
|
1558
|
+
for (const cmd of commands) {
|
|
1559
|
+
switch (cmd.type) {
|
|
1560
|
+
case "M":
|
|
1561
|
+
start = [cmd.x, cmd.y];
|
|
1562
|
+
current = start;
|
|
1563
|
+
out.push({ type: "M", x: cmd.x, y: cmd.y });
|
|
1564
|
+
open = true;
|
|
1565
|
+
break;
|
|
1566
|
+
case "L":
|
|
1567
|
+
ensureMove();
|
|
1568
|
+
out.push({ type: "L", x: cmd.x, y: cmd.y });
|
|
1569
|
+
current = [cmd.x, cmd.y];
|
|
1570
|
+
break;
|
|
1571
|
+
case "Q":
|
|
1572
|
+
ensureMove();
|
|
1573
|
+
out.push({ type: "Q", x1: cmd.x1, y1: cmd.y1, x: cmd.x, y: cmd.y });
|
|
1574
|
+
current = [cmd.x, cmd.y];
|
|
1575
|
+
break;
|
|
1576
|
+
case "C":
|
|
1577
|
+
ensureMove();
|
|
1578
|
+
out.push({ type: "C", x1: cmd.x1, y1: cmd.y1, x2: cmd.x2, y2: cmd.y2, x: cmd.x, y: cmd.y });
|
|
1579
|
+
current = [cmd.x, cmd.y];
|
|
1580
|
+
break;
|
|
1581
|
+
case "A": {
|
|
1582
|
+
ensureMove();
|
|
1583
|
+
for (const c of arcToCubics(current, cmd)) {
|
|
1584
|
+
if (c.length === 2) out.push({ type: "L", x: c[1][0], y: c[1][1] });
|
|
1585
|
+
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] });
|
|
1586
|
+
}
|
|
1587
|
+
current = [cmd.x, cmd.y];
|
|
1588
|
+
break;
|
|
1589
|
+
}
|
|
1590
|
+
case "Z":
|
|
1591
|
+
if (open) out.push({ type: "Z" });
|
|
1592
|
+
open = false;
|
|
1593
|
+
current = start;
|
|
1594
|
+
break;
|
|
1595
|
+
}
|
|
1695
1596
|
}
|
|
1696
|
-
|
|
1697
|
-
const b = sub2(seg[2], seg[1]);
|
|
1698
|
-
const c = sub2(seg[3], seg[2]);
|
|
1699
|
-
const mt = 1 - t;
|
|
1700
|
-
return [
|
|
1701
|
-
3 * (mt * mt * a[0] + 2 * mt * t * b[0] + t * t * c[0]),
|
|
1702
|
-
3 * (mt * mt * a[1] + 2 * mt * t * b[1] + t * t * c[1])
|
|
1703
|
-
];
|
|
1704
|
-
}
|
|
1705
|
-
function segmentSecondDerivative(seg, t) {
|
|
1706
|
-
const n = seg.length;
|
|
1707
|
-
if (n === 2) return [0, 0];
|
|
1708
|
-
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])];
|
|
1709
|
-
const a = [seg[2][0] - 2 * seg[1][0] + seg[0][0], seg[2][1] - 2 * seg[1][1] + seg[0][1]];
|
|
1710
|
-
const b = [seg[3][0] - 2 * seg[2][0] + seg[1][0], seg[3][1] - 2 * seg[2][1] + seg[1][1]];
|
|
1711
|
-
return [6 * ((1 - t) * a[0] + t * b[0]), 6 * ((1 - t) * a[1] + t * b[1])];
|
|
1712
|
-
}
|
|
1713
|
-
function segmentTangent(seg, t) {
|
|
1714
|
-
const d = unit2(segmentDerivative(seg, t));
|
|
1715
|
-
if (d) return d;
|
|
1716
|
-
const h = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
|
|
1717
|
-
const e = unit2(segmentDerivative(seg, h));
|
|
1718
|
-
if (e) return e;
|
|
1719
|
-
return _nullishCoalesce(unit2(sub2(seg[seg.length - 1], seg[0])), () => ( [1, 0]));
|
|
1597
|
+
return out;
|
|
1720
1598
|
}
|
|
1721
|
-
function
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
let
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1599
|
+
function pathToContours(input) {
|
|
1600
|
+
const path = toBezierPath(input);
|
|
1601
|
+
const contours = [];
|
|
1602
|
+
let current = null;
|
|
1603
|
+
let pen = [0, 0];
|
|
1604
|
+
let start = [0, 0];
|
|
1605
|
+
const push = (seg) => {
|
|
1606
|
+
if (!current) {
|
|
1607
|
+
current = { segments: [], closed: false };
|
|
1608
|
+
contours.push(current);
|
|
1609
|
+
}
|
|
1610
|
+
if (seg.every((p) => samePoint(p, seg[0]))) return;
|
|
1611
|
+
current.segments.push(seg);
|
|
1612
|
+
};
|
|
1613
|
+
for (const cmd of path) {
|
|
1614
|
+
switch (cmd.type) {
|
|
1615
|
+
case "M":
|
|
1616
|
+
start = [cmd.x, cmd.y];
|
|
1617
|
+
pen = start;
|
|
1618
|
+
current = { segments: [], closed: false };
|
|
1619
|
+
contours.push(current);
|
|
1620
|
+
break;
|
|
1621
|
+
case "L":
|
|
1622
|
+
push([pen, [cmd.x, cmd.y]]);
|
|
1623
|
+
pen = [cmd.x, cmd.y];
|
|
1624
|
+
break;
|
|
1625
|
+
case "Q":
|
|
1626
|
+
push([pen, [cmd.x1, cmd.y1], [cmd.x, cmd.y]]);
|
|
1627
|
+
pen = [cmd.x, cmd.y];
|
|
1628
|
+
break;
|
|
1629
|
+
case "C":
|
|
1630
|
+
push([pen, [cmd.x1, cmd.y1], [cmd.x2, cmd.y2], [cmd.x, cmd.y]]);
|
|
1631
|
+
pen = [cmd.x, cmd.y];
|
|
1632
|
+
break;
|
|
1633
|
+
case "Z":
|
|
1634
|
+
if (current) current.closed = true;
|
|
1635
|
+
current = null;
|
|
1636
|
+
pen = start;
|
|
1637
|
+
break;
|
|
1638
|
+
}
|
|
1728
1639
|
}
|
|
1729
|
-
|
|
1730
|
-
const len = Math.hypot(d[0], d[1]);
|
|
1731
|
-
if (!(len > 0)) return 0;
|
|
1732
|
-
return cross(d, dd) / (len * len * len);
|
|
1640
|
+
return contours;
|
|
1733
1641
|
}
|
|
1734
|
-
function
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1737
|
-
const
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1642
|
+
function contoursToPath(contours) {
|
|
1643
|
+
const out = [];
|
|
1644
|
+
for (const contour of contours) {
|
|
1645
|
+
const segs = contour.segments;
|
|
1646
|
+
if (segs.length === 0) continue;
|
|
1647
|
+
out.push({ type: "M", x: segs[0][0][0], y: segs[0][0][1] });
|
|
1648
|
+
let count = segs.length;
|
|
1649
|
+
if (contour.closed && count > 1) {
|
|
1650
|
+
const last = segs[count - 1];
|
|
1651
|
+
if (last.length === 2 && samePoint(last[1], segs[0][0])) count--;
|
|
1652
|
+
}
|
|
1653
|
+
for (let i = 0; i < count; i++) {
|
|
1654
|
+
const s = segs[i];
|
|
1655
|
+
const end = s[s.length - 1];
|
|
1656
|
+
if (s.length === 2) out.push({ type: "L", x: end[0], y: end[1] });
|
|
1657
|
+
else if (s.length === 3) out.push({ type: "Q", x1: s[1][0], y1: s[1][1], x: end[0], y: end[1] });
|
|
1658
|
+
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] });
|
|
1659
|
+
}
|
|
1660
|
+
if (contour.closed) out.push({ type: "Z" });
|
|
1742
1661
|
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1662
|
+
return out;
|
|
1663
|
+
}
|
|
1664
|
+
function bezierPathToSvg(input, options = {}) {
|
|
1665
|
+
const path = toBezierPath(input);
|
|
1666
|
+
const precision = _nullishCoalesce(options.precision, () => ( 3));
|
|
1667
|
+
const dx = _nullishCoalesce(_optionalChain([options, 'access', _18 => _18.offset, 'optionalAccess', _19 => _19.x]), () => ( 0));
|
|
1668
|
+
const dy = _nullishCoalesce(_optionalChain([options, 'access', _20 => _20.offset, 'optionalAccess', _21 => _21.y]), () => ( 0));
|
|
1669
|
+
const fx = (n) => formatNumber2(n + dx, precision);
|
|
1670
|
+
const fy = (n) => formatNumber2(n + dy, precision);
|
|
1671
|
+
let d = "";
|
|
1672
|
+
for (const c of path) {
|
|
1673
|
+
switch (c.type) {
|
|
1674
|
+
case "M":
|
|
1675
|
+
d += `M${fx(c.x)} ${fy(c.y)}`;
|
|
1676
|
+
break;
|
|
1677
|
+
case "L":
|
|
1678
|
+
d += `L${fx(c.x)} ${fy(c.y)}`;
|
|
1679
|
+
break;
|
|
1680
|
+
case "Q":
|
|
1681
|
+
d += `Q${fx(c.x1)} ${fy(c.y1)} ${fx(c.x)} ${fy(c.y)}`;
|
|
1682
|
+
break;
|
|
1683
|
+
case "C":
|
|
1684
|
+
d += `C${fx(c.x1)} ${fy(c.y1)} ${fx(c.x2)} ${fy(c.y2)} ${fx(c.x)} ${fy(c.y)}`;
|
|
1685
|
+
break;
|
|
1686
|
+
case "Z":
|
|
1687
|
+
d += "Z";
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1751
1690
|
}
|
|
1752
|
-
|
|
1753
|
-
const b = lerp(seg[1], seg[2], t);
|
|
1754
|
-
const c = lerp(seg[2], seg[3], t);
|
|
1755
|
-
const ab = lerp(a, b, t);
|
|
1756
|
-
const bc = lerp(b, c, t);
|
|
1757
|
-
const m = lerp(ab, bc, t);
|
|
1758
|
-
return [
|
|
1759
|
-
[seg[0], a, ab, m],
|
|
1760
|
-
[m, bc, c, seg[3]]
|
|
1761
|
-
];
|
|
1691
|
+
return d;
|
|
1762
1692
|
}
|
|
1763
|
-
function
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1693
|
+
function transformBezierPath(input, m) {
|
|
1694
|
+
const [a, b, c, d, e, f] = m;
|
|
1695
|
+
const X = (x, y) => a * x + c * y + e;
|
|
1696
|
+
const Y = (x, y) => b * x + d * y + f;
|
|
1697
|
+
return toBezierPath(input).map((cmd) => {
|
|
1698
|
+
switch (cmd.type) {
|
|
1699
|
+
case "M":
|
|
1700
|
+
case "L":
|
|
1701
|
+
return { type: cmd.type, x: X(cmd.x, cmd.y), y: Y(cmd.x, cmd.y) };
|
|
1702
|
+
case "Q":
|
|
1703
|
+
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) };
|
|
1704
|
+
case "C":
|
|
1705
|
+
return {
|
|
1706
|
+
type: "C",
|
|
1707
|
+
x1: X(cmd.x1, cmd.y1),
|
|
1708
|
+
y1: Y(cmd.x1, cmd.y1),
|
|
1709
|
+
x2: X(cmd.x2, cmd.y2),
|
|
1710
|
+
y2: Y(cmd.x2, cmd.y2),
|
|
1711
|
+
x: X(cmd.x, cmd.y),
|
|
1712
|
+
y: Y(cmd.x, cmd.y)
|
|
1713
|
+
};
|
|
1714
|
+
case "Z":
|
|
1715
|
+
return cmd;
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1770
1718
|
}
|
|
1771
|
-
function
|
|
1772
|
-
|
|
1773
|
-
const
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
if (len === 0) {
|
|
1780
|
-
if (Math.hypot(v[0], v[1]) > tol) return false;
|
|
1781
|
-
continue;
|
|
1719
|
+
function countPathSegments(input) {
|
|
1720
|
+
const out = { lines: 0, quads: 0, cubics: 0 };
|
|
1721
|
+
for (const contour of pathToContours(input)) {
|
|
1722
|
+
const segs = closeContour(contour);
|
|
1723
|
+
for (const s of segs) {
|
|
1724
|
+
if (s.length === 2) out.lines++;
|
|
1725
|
+
else if (s.length === 3) out.quads++;
|
|
1726
|
+
else out.cubics++;
|
|
1782
1727
|
}
|
|
1783
|
-
if (Math.abs(cross(chord, v)) / len > tol) return false;
|
|
1784
|
-
const along = dot2(chord, v) / len;
|
|
1785
|
-
if (along < -tol || along > len + tol) return false;
|
|
1786
1728
|
}
|
|
1787
|
-
return
|
|
1729
|
+
return out;
|
|
1788
1730
|
}
|
|
1789
|
-
function
|
|
1790
|
-
const
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
const
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
return;
|
|
1798
|
-
}
|
|
1799
|
-
const disc = b * b - 4 * a * c;
|
|
1800
|
-
if (disc < 0) return;
|
|
1801
|
-
const sq = Math.sqrt(disc);
|
|
1802
|
-
const q = -0.5 * (b + (b >= 0 ? sq : -sq));
|
|
1803
|
-
push(q / a);
|
|
1804
|
-
if (q !== 0) push(c / q);
|
|
1731
|
+
function closeContour(contour, force = false) {
|
|
1732
|
+
const segs = contour.segments;
|
|
1733
|
+
if (segs.length === 0) return [];
|
|
1734
|
+
if (!contour.closed && !force) return segs;
|
|
1735
|
+
const first = segs[0][0];
|
|
1736
|
+
const lastSeg = segs[segs.length - 1];
|
|
1737
|
+
const last = lastSeg[lastSeg.length - 1];
|
|
1738
|
+
return samePoint(first, last) ? segs : [...segs, [last, first]];
|
|
1805
1739
|
}
|
|
1806
|
-
function
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1740
|
+
function bezierPathBounds(input) {
|
|
1741
|
+
let minX = Infinity;
|
|
1742
|
+
let minY = Infinity;
|
|
1743
|
+
let maxX = -Infinity;
|
|
1744
|
+
let maxY = -Infinity;
|
|
1745
|
+
const path = toBezierPath(input);
|
|
1746
|
+
let pen = null;
|
|
1747
|
+
for (const contour of pathToContours(path)) {
|
|
1748
|
+
for (const s of contour.segments) {
|
|
1749
|
+
const b = segmentBounds(s);
|
|
1750
|
+
minX = Math.min(minX, b.minX);
|
|
1751
|
+
minY = Math.min(minY, b.minY);
|
|
1752
|
+
maxX = Math.max(maxX, b.maxX);
|
|
1753
|
+
maxY = Math.max(maxY, b.maxY);
|
|
1813
1754
|
}
|
|
1814
|
-
} else if (seg.length === 4) {
|
|
1815
|
-
const p0 = seg[0][axis];
|
|
1816
|
-
const p1 = seg[1][axis];
|
|
1817
|
-
const p2 = seg[2][axis];
|
|
1818
|
-
const p3 = seg[3][axis];
|
|
1819
|
-
const a = -p0 + 3 * p1 - 3 * p2 + p3;
|
|
1820
|
-
const b = 2 * (p0 - 2 * p1 + p2);
|
|
1821
|
-
const c = p1 - p0;
|
|
1822
|
-
quadraticRoots(a, b, c, out);
|
|
1823
1755
|
}
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
let minY = Math.min(a[1], b[1]);
|
|
1832
|
-
let maxY = Math.max(a[1], b[1]);
|
|
1833
|
-
for (const axis of [0, 1]) {
|
|
1834
|
-
for (const t of segmentExtrema(seg, axis)) {
|
|
1835
|
-
const p = segmentPoint(seg, t);
|
|
1836
|
-
if (axis === 0) {
|
|
1837
|
-
minX = Math.min(minX, p[0]);
|
|
1838
|
-
maxX = Math.max(maxX, p[0]);
|
|
1839
|
-
} else {
|
|
1840
|
-
minY = Math.min(minY, p[1]);
|
|
1841
|
-
maxY = Math.max(maxY, p[1]);
|
|
1842
|
-
}
|
|
1756
|
+
for (const c of path) {
|
|
1757
|
+
if (c.type === "M") pen = [c.x, c.y];
|
|
1758
|
+
if (pen) {
|
|
1759
|
+
minX = Math.min(minX, pen[0]);
|
|
1760
|
+
minY = Math.min(minY, pen[1]);
|
|
1761
|
+
maxX = Math.max(maxX, pen[0]);
|
|
1762
|
+
maxY = Math.max(maxY, pen[1]);
|
|
1843
1763
|
}
|
|
1844
1764
|
}
|
|
1845
|
-
return { minX, minY, maxX, maxY };
|
|
1846
|
-
}
|
|
1847
|
-
function monotoneParams(seg) {
|
|
1848
|
-
if (seg.length === 2) return [];
|
|
1849
|
-
const ts = [...segmentExtrema(seg, 0), ...segmentExtrema(seg, 1)].sort((a, b) => a - b);
|
|
1850
|
-
const out = [];
|
|
1851
|
-
for (const t of ts) if (out.length === 0 || t - out[out.length - 1] > 1e-9) out.push(t);
|
|
1852
|
-
return out;
|
|
1765
|
+
return minX === Infinity ? null : { minX, minY, maxX, maxY };
|
|
1853
1766
|
}
|
|
1854
|
-
function
|
|
1855
|
-
if (seg.length === 2) return (seg[0][0] * seg[1][1] - seg[1][0] * seg[0][1]) / 2;
|
|
1856
|
-
const nodes = [-Math.sqrt(3 / 5), 0, Math.sqrt(3 / 5)];
|
|
1857
|
-
const weights = [5 / 9, 8 / 9, 5 / 9];
|
|
1767
|
+
function bezierPathSignedArea(input) {
|
|
1858
1768
|
let sum = 0;
|
|
1859
|
-
for (
|
|
1860
|
-
|
|
1861
|
-
const p = segmentPoint(seg, t);
|
|
1862
|
-
const d = segmentDerivative(seg, t);
|
|
1863
|
-
sum += weights[i] * (p[0] * d[1] - p[1] * d[0]);
|
|
1864
|
-
}
|
|
1865
|
-
return sum / 4;
|
|
1769
|
+
for (const contour of pathToContours(input)) sum += contourSignedArea(closeContour(contour, true));
|
|
1770
|
+
return sum;
|
|
1866
1771
|
}
|
|
1867
|
-
|
|
1868
|
-
-0.9602898564975363,
|
|
1869
|
-
-0.7966664774136267,
|
|
1870
|
-
-0.525532409916329,
|
|
1871
|
-
-0.1834346424956498,
|
|
1872
|
-
0.1834346424956498,
|
|
1873
|
-
0.525532409916329,
|
|
1874
|
-
0.7966664774136267,
|
|
1875
|
-
0.9602898564975363
|
|
1876
|
-
];
|
|
1877
|
-
var GL_W = [
|
|
1878
|
-
0.1012285362903763,
|
|
1879
|
-
0.2223810344533745,
|
|
1880
|
-
0.3137066458778873,
|
|
1881
|
-
0.362683783378362,
|
|
1882
|
-
0.362683783378362,
|
|
1883
|
-
0.3137066458778873,
|
|
1884
|
-
0.2223810344533745,
|
|
1885
|
-
0.1012285362903763
|
|
1886
|
-
];
|
|
1887
|
-
function glLength(seg, t0, t1) {
|
|
1888
|
-
const half = (t1 - t0) / 2;
|
|
1889
|
-
const mid = (t0 + t1) / 2;
|
|
1772
|
+
function contourSignedArea(segs) {
|
|
1890
1773
|
let sum = 0;
|
|
1891
|
-
for (
|
|
1892
|
-
|
|
1893
|
-
sum += GL_W[i] * Math.hypot(d[0], d[1]);
|
|
1894
|
-
}
|
|
1895
|
-
return sum * half;
|
|
1774
|
+
for (const s of segs) sum += segmentAreaTerm(s);
|
|
1775
|
+
return sum;
|
|
1896
1776
|
}
|
|
1897
|
-
function
|
|
1898
|
-
|
|
1899
|
-
const
|
|
1900
|
-
|
|
1901
|
-
if (depth >= 18 || Math.abs(left + right - whole) <= tol) return left + right;
|
|
1902
|
-
return adaptiveLength(seg, t0, m, left, tol / 2, depth + 1) + adaptiveLength(seg, m, t1, right, tol / 2, depth + 1);
|
|
1777
|
+
function bezierPathLength(input) {
|
|
1778
|
+
let total = 0;
|
|
1779
|
+
for (const contour of pathToContours(input)) for (const s of closeContour(contour)) total += segmentLength(s);
|
|
1780
|
+
return total;
|
|
1903
1781
|
}
|
|
1904
|
-
function
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1782
|
+
function bezierPathPointAtLength(input, length) {
|
|
1783
|
+
const contours = pathToContours(input);
|
|
1784
|
+
let remaining = Math.max(0, length);
|
|
1785
|
+
let last = null;
|
|
1786
|
+
for (let ci = 0; ci < contours.length; ci++) {
|
|
1787
|
+
const segs = closeContour(contours[ci]);
|
|
1788
|
+
for (let si = 0; si < segs.length; si++) {
|
|
1789
|
+
const s = segs[si];
|
|
1790
|
+
const len = segmentLength(s);
|
|
1791
|
+
if (remaining <= len) {
|
|
1792
|
+
const t = segmentParamAtLength(s, remaining, len);
|
|
1793
|
+
const tangent2 = segmentTangent(s, t);
|
|
1794
|
+
return { point: segmentPoint(s, t), tangent: tangent2, angle: Math.atan2(tangent2[1], tangent2[0]), contour: ci, segment: si, t };
|
|
1795
|
+
}
|
|
1796
|
+
remaining -= len;
|
|
1797
|
+
const tangent = segmentTangent(s, 1);
|
|
1798
|
+
last = { point: s[s.length - 1], tangent, angle: Math.atan2(tangent[1], tangent[0]), contour: ci, segment: si, t: 1 };
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
return last;
|
|
1909
1802
|
}
|
|
1910
|
-
function
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1803
|
+
function trimSegments(segs, from, to, lengths) {
|
|
1804
|
+
const lens = _nullishCoalesce(lengths, () => ( segs.map((s) => segmentLength(s))));
|
|
1805
|
+
const out = [];
|
|
1806
|
+
let at = 0;
|
|
1807
|
+
for (let i = 0; i < segs.length; i++) {
|
|
1808
|
+
const len = lens[i];
|
|
1809
|
+
const s0 = at;
|
|
1810
|
+
const s1 = at + len;
|
|
1811
|
+
at = s1;
|
|
1812
|
+
if (s1 <= from || s0 >= to || len <= 0) continue;
|
|
1813
|
+
const t0 = from > s0 ? segmentParamAtLength(segs[i], from - s0, len) : 0;
|
|
1814
|
+
const t1 = to < s1 ? segmentParamAtLength(segs[i], to - s0, len) : 1;
|
|
1815
|
+
if (t1 > t0) out.push(subSegment(segs[i], t0, t1));
|
|
1816
|
+
}
|
|
1817
|
+
return out;
|
|
1818
|
+
}
|
|
1819
|
+
function solveMonotone(seg, axis, value) {
|
|
1820
|
+
const a = seg[0][axis];
|
|
1821
|
+
const b = seg[seg.length - 1][axis];
|
|
1822
|
+
if (seg.length === 2) return b === a ? 0 : (value - a) / (b - a);
|
|
1823
|
+
const increasing = b > a;
|
|
1914
1824
|
let lo = 0;
|
|
1915
1825
|
let hi = 1;
|
|
1916
|
-
let t =
|
|
1917
|
-
for (let i = 0; i <
|
|
1918
|
-
const
|
|
1919
|
-
if (
|
|
1920
|
-
if (
|
|
1826
|
+
let t = b === a ? 0.5 : Math.max(0, Math.min(1, (value - a) / (b - a)));
|
|
1827
|
+
for (let i = 0; i < 60; i++) {
|
|
1828
|
+
const v = segmentPoint(seg, t)[axis] - value;
|
|
1829
|
+
if (v === 0) return t;
|
|
1830
|
+
if (v > 0 === increasing) hi = t;
|
|
1921
1831
|
else lo = t;
|
|
1922
|
-
const d = segmentDerivative(seg, t);
|
|
1923
|
-
|
|
1924
|
-
let next = speed > 0 ? t - err / speed : (lo + hi) / 2;
|
|
1832
|
+
const d = segmentDerivative(seg, t)[axis];
|
|
1833
|
+
let next = d !== 0 ? t - v / d : (lo + hi) / 2;
|
|
1925
1834
|
if (!(next > lo && next < hi)) next = (lo + hi) / 2;
|
|
1835
|
+
if (Math.abs(next - t) < 1e-15) return next;
|
|
1926
1836
|
t = next;
|
|
1927
1837
|
}
|
|
1928
1838
|
return t;
|
|
1929
1839
|
}
|
|
1930
|
-
function
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1840
|
+
function yMonotonePieces(segs) {
|
|
1841
|
+
const out = [];
|
|
1842
|
+
for (const s of segs) {
|
|
1843
|
+
if (s.length === 2) {
|
|
1844
|
+
out.push(s);
|
|
1845
|
+
continue;
|
|
1846
|
+
}
|
|
1847
|
+
const ts = segmentExtrema(s, 1);
|
|
1848
|
+
let prev = 0;
|
|
1849
|
+
for (const t of [...ts, 1]) {
|
|
1850
|
+
out.push(subSegment(s, prev, t));
|
|
1851
|
+
prev = t;
|
|
1852
|
+
}
|
|
1936
1853
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
let
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1854
|
+
return out;
|
|
1855
|
+
}
|
|
1856
|
+
function windingNumber(chains, point) {
|
|
1857
|
+
const [px, py] = point;
|
|
1858
|
+
let winding = 0;
|
|
1859
|
+
for (const chain of chains) {
|
|
1860
|
+
for (const s of yMonotonePieces(chain)) {
|
|
1861
|
+
const ay = s[0][1];
|
|
1862
|
+
const by = s[s.length - 1][1];
|
|
1863
|
+
let dir = 0;
|
|
1864
|
+
if (ay <= py && by > py) dir = 1;
|
|
1865
|
+
else if (by <= py && ay > py) dir = -1;
|
|
1866
|
+
else continue;
|
|
1867
|
+
let minX = Infinity;
|
|
1868
|
+
let maxX = -Infinity;
|
|
1869
|
+
for (const p of s) {
|
|
1870
|
+
if (p[0] < minX) minX = p[0];
|
|
1871
|
+
if (p[0] > maxX) maxX = p[0];
|
|
1872
|
+
}
|
|
1873
|
+
if (minX > px) {
|
|
1874
|
+
winding += dir;
|
|
1875
|
+
continue;
|
|
1876
|
+
}
|
|
1877
|
+
if (maxX <= px) continue;
|
|
1878
|
+
const t = solveMonotone(s, 1, py);
|
|
1879
|
+
if (segmentPoint(s, t)[0] > px) winding += dir;
|
|
1880
|
+
}
|
|
1951
1881
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1882
|
+
return winding;
|
|
1883
|
+
}
|
|
1884
|
+
function pointInBezierPath(input, point, fillRule = "nonzero") {
|
|
1885
|
+
const chains = pathToContours(input).map((c) => closeContour(c, true));
|
|
1886
|
+
const w = windingNumber(chains, point);
|
|
1887
|
+
return fillRule === "evenodd" ? (w & 1) === 1 : w !== 0;
|
|
1888
|
+
}
|
|
1889
|
+
function distanceToBezierPath(input, point) {
|
|
1890
|
+
const contours = Array.isArray(input) && input.length > 0 && input[0].segments ? input : pathToContours(input);
|
|
1891
|
+
let best = Infinity;
|
|
1892
|
+
for (const contour of contours) {
|
|
1893
|
+
for (const s of closeContour(contour)) {
|
|
1894
|
+
let minX = Infinity;
|
|
1895
|
+
let minY = Infinity;
|
|
1896
|
+
let maxX = -Infinity;
|
|
1897
|
+
let maxY = -Infinity;
|
|
1898
|
+
for (const p of s) {
|
|
1899
|
+
minX = Math.min(minX, p[0]);
|
|
1900
|
+
minY = Math.min(minY, p[1]);
|
|
1901
|
+
maxX = Math.max(maxX, p[0]);
|
|
1902
|
+
maxY = Math.max(maxY, p[1]);
|
|
1967
1903
|
}
|
|
1968
|
-
|
|
1904
|
+
const dx = Math.max(minX - point[0], 0, point[0] - maxX);
|
|
1905
|
+
const dy = Math.max(minY - point[1], 0, point[1] - maxY);
|
|
1906
|
+
if (Math.hypot(dx, dy) >= best) continue;
|
|
1907
|
+
best = Math.min(best, segmentNearest(s, point).distance);
|
|
1969
1908
|
}
|
|
1970
|
-
|
|
1971
|
-
if (d < best.distance) best = { t, distance: d };
|
|
1909
|
+
if (contour.segments.length === 0) continue;
|
|
1972
1910
|
}
|
|
1973
1911
|
return best;
|
|
1974
1912
|
}
|
|
1975
|
-
function
|
|
1976
|
-
const [x0, y0] = from;
|
|
1977
|
-
const { x, y } = cmd;
|
|
1978
|
-
if (x0 === x && y0 === y) return [];
|
|
1979
|
-
let rx = Math.abs(cmd.rx);
|
|
1980
|
-
let ry = Math.abs(cmd.ry);
|
|
1981
|
-
if (rx === 0 || ry === 0) return [[from, [x, y]]];
|
|
1982
|
-
const phi = cmd.rotation * Math.PI / 180;
|
|
1983
|
-
const cos = Math.cos(phi);
|
|
1984
|
-
const sin = Math.sin(phi);
|
|
1985
|
-
const dx = (x0 - x) / 2;
|
|
1986
|
-
const dy = (y0 - y) / 2;
|
|
1987
|
-
const x1p = cos * dx + sin * dy;
|
|
1988
|
-
const y1p = -sin * dx + cos * dy;
|
|
1989
|
-
const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
|
|
1990
|
-
if (lambda > 1) {
|
|
1991
|
-
const s = Math.sqrt(lambda);
|
|
1992
|
-
rx *= s;
|
|
1993
|
-
ry *= s;
|
|
1994
|
-
}
|
|
1995
|
-
const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
|
|
1996
|
-
const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
|
|
1997
|
-
let coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));
|
|
1998
|
-
if (cmd.largeArc === cmd.sweep) coef = -coef;
|
|
1999
|
-
const cxp = coef * rx * y1p / ry;
|
|
2000
|
-
const cyp = -coef * ry * x1p / rx;
|
|
2001
|
-
const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
|
|
2002
|
-
const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
|
|
2003
|
-
const angle = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
2004
|
-
const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
|
|
2005
|
-
let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
|
|
2006
|
-
if (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;
|
|
2007
|
-
else if (cmd.sweep && delta < 0) delta += 2 * Math.PI;
|
|
2008
|
-
const n = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9));
|
|
2009
|
-
const step = delta / n;
|
|
2010
|
-
const k = 4 / 3 * Math.tan(step / 4);
|
|
2011
|
-
const map = (ux, uy) => {
|
|
2012
|
-
const ex = rx * ux;
|
|
2013
|
-
const ey = ry * uy;
|
|
2014
|
-
return [cos * ex - sin * ey + cx, sin * ex + cos * ey + cy];
|
|
2015
|
-
};
|
|
1913
|
+
function flattenChain(segs, tolerance) {
|
|
2016
1914
|
const out = [];
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
1915
|
+
for (const s of segs) {
|
|
1916
|
+
if (out.length === 0) out.push(s[0]);
|
|
1917
|
+
if (s.length === 2) {
|
|
1918
|
+
out.push(s[1]);
|
|
1919
|
+
continue;
|
|
1920
|
+
}
|
|
1921
|
+
let dd = 0;
|
|
1922
|
+
for (let i = 0; i + 2 < s.length; i++) {
|
|
1923
|
+
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]));
|
|
1924
|
+
}
|
|
1925
|
+
const deg = s.length - 1;
|
|
1926
|
+
const n = Math.max(1, Math.min(256, Math.ceil(Math.sqrt(deg * (deg - 1) * dd / (8 * tolerance)))));
|
|
1927
|
+
for (let k = 1; k <= n; k++) out.push(segmentPoint(s, k / n));
|
|
2028
1928
|
}
|
|
1929
|
+
if (out.length > 1 && samePoint(out[0], out[out.length - 1])) out.pop();
|
|
2029
1930
|
return out;
|
|
2030
1931
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
1932
|
+
|
|
1933
|
+
// src/canvas/polygonBoolean.ts
|
|
1934
|
+
function ringSignedArea(ring) {
|
|
1935
|
+
let sum = 0;
|
|
1936
|
+
const n = ring.length;
|
|
1937
|
+
for (let i = 0; i < n; i++) {
|
|
1938
|
+
const [x1, y1] = ring[i];
|
|
1939
|
+
const [x2, y2] = ring[(i + 1) % n];
|
|
1940
|
+
sum += x1 * y2 - x2 * y1;
|
|
1941
|
+
}
|
|
1942
|
+
return sum / 2;
|
|
2033
1943
|
}
|
|
2034
|
-
function
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
let
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
1944
|
+
function polygonArea(polygon) {
|
|
1945
|
+
if (polygon.length === 0) return 0;
|
|
1946
|
+
let area = Math.abs(ringSignedArea(polygon[0]));
|
|
1947
|
+
for (let i = 1; i < polygon.length; i++) area -= Math.abs(ringSignedArea(polygon[i]));
|
|
1948
|
+
return area;
|
|
1949
|
+
}
|
|
1950
|
+
function multiPolygonArea(multi) {
|
|
1951
|
+
let total = 0;
|
|
1952
|
+
for (const polygon of multi) total += polygonArea(polygon);
|
|
1953
|
+
return total;
|
|
1954
|
+
}
|
|
1955
|
+
function multiPolygonBounds(input) {
|
|
1956
|
+
let minX = Infinity;
|
|
1957
|
+
let minY = Infinity;
|
|
1958
|
+
let maxX = -Infinity;
|
|
1959
|
+
let maxY = -Infinity;
|
|
1960
|
+
const visit = (ring) => {
|
|
1961
|
+
for (const [x, y] of ring) {
|
|
1962
|
+
if (x < minX) minX = x;
|
|
1963
|
+
if (y < minY) minY = y;
|
|
1964
|
+
if (x > maxX) maxX = x;
|
|
1965
|
+
if (y > maxY) maxY = y;
|
|
2045
1966
|
}
|
|
2046
1967
|
};
|
|
2047
|
-
for (const
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
1968
|
+
for (const item of input) {
|
|
1969
|
+
if (item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0])) {
|
|
1970
|
+
for (const ring of item) visit(ring);
|
|
1971
|
+
} else {
|
|
1972
|
+
visit(item);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
return minX === Infinity ? null : { minX, minY, maxX, maxY };
|
|
1976
|
+
}
|
|
1977
|
+
function pointInRings(point, rings, fillRule = "nonzero") {
|
|
1978
|
+
const [px, py] = point;
|
|
1979
|
+
let winding = 0;
|
|
1980
|
+
for (const ring of rings) {
|
|
1981
|
+
const n = ring.length;
|
|
1982
|
+
for (let i = 0; i < n; i++) {
|
|
1983
|
+
const [ax, ay] = ring[i];
|
|
1984
|
+
const [bx, by] = ring[(i + 1) % n];
|
|
1985
|
+
if (ay <= py && by > py) {
|
|
1986
|
+
if (ax + (py - ay) / (by - ay) * (bx - ax) > px) winding++;
|
|
1987
|
+
} else if (by <= py && ay > py) {
|
|
1988
|
+
if (ax + (py - ay) / (by - ay) * (bx - ax) > px) winding--;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
return fillRule === "evenodd" ? (winding & 1) === 1 : winding !== 0;
|
|
1993
|
+
}
|
|
1994
|
+
function pointInMultiPolygon(point, multi) {
|
|
1995
|
+
return multi.some((polygon) => pointInRings(point, polygon, "evenodd"));
|
|
1996
|
+
}
|
|
1997
|
+
function cleanRing(ring) {
|
|
1998
|
+
const out = [];
|
|
1999
|
+
for (const p of ring) {
|
|
2000
|
+
if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
|
|
2001
|
+
const last = out[out.length - 1];
|
|
2002
|
+
if (last && last[0] === p[0] && last[1] === p[1]) continue;
|
|
2003
|
+
out.push([p[0], p[1]]);
|
|
2004
|
+
}
|
|
2005
|
+
while (out.length > 1) {
|
|
2006
|
+
const first = out[0];
|
|
2007
|
+
const last = out[out.length - 1];
|
|
2008
|
+
if (first[0] === last[0] && first[1] === last[1]) out.pop();
|
|
2009
|
+
else break;
|
|
2010
|
+
}
|
|
2011
|
+
return out;
|
|
2012
|
+
}
|
|
2013
|
+
function isOperand(input) {
|
|
2014
|
+
return !Array.isArray(input) && typeof input === "object" && input !== null && Array.isArray(input.rings);
|
|
2015
|
+
}
|
|
2016
|
+
function normalizeInput(input) {
|
|
2017
|
+
if (isOperand(input)) {
|
|
2018
|
+
return {
|
|
2019
|
+
rings: input.rings.map(cleanRing).filter((r) => r.length >= 3),
|
|
2020
|
+
fillRule: _nullishCoalesce(input.fillRule, () => ( "nonzero"))
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
const rings = [];
|
|
2024
|
+
for (const polygon of _nullishCoalesce(input, () => ( []))) {
|
|
2025
|
+
polygon.forEach((raw, index) => {
|
|
2026
|
+
const ring = cleanRing(raw);
|
|
2027
|
+
if (ring.length < 3) return;
|
|
2028
|
+
const area = ringSignedArea(ring);
|
|
2029
|
+
if (area === 0) return;
|
|
2030
|
+
const wantPositive = index === 0;
|
|
2031
|
+
rings.push(area > 0 === wantPositive ? ring : ring.reverse());
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
return { rings, fillRule: "nonzero" };
|
|
2035
|
+
}
|
|
2036
|
+
var PointPool = (_class2 = class {
|
|
2037
|
+
constructor(eps) {;_class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);
|
|
2038
|
+
this.eps = eps;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
__init2() {this.vertices = []}
|
|
2042
|
+
__init3() {this.cells = /* @__PURE__ */ new Map()}
|
|
2043
|
+
get(x, y) {
|
|
2044
|
+
const cx = Math.floor(x / this.eps);
|
|
2045
|
+
const cy = Math.floor(y / this.eps);
|
|
2046
|
+
let best = null;
|
|
2047
|
+
let bestDist = Infinity;
|
|
2048
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
2049
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
2050
|
+
const ids = this.cells.get(`${cx + dx},${cy + dy}`);
|
|
2051
|
+
if (!ids) continue;
|
|
2052
|
+
for (const id of ids) {
|
|
2053
|
+
const v = this.vertices[id];
|
|
2054
|
+
const d = Math.hypot(v.x - x, v.y - y);
|
|
2055
|
+
if (d <= this.eps && d < bestDist) {
|
|
2056
|
+
best = v;
|
|
2057
|
+
bestDist = d;
|
|
2058
|
+
}
|
|
2075
2059
|
}
|
|
2076
|
-
current = [cmd.x, cmd.y];
|
|
2077
|
-
break;
|
|
2078
2060
|
}
|
|
2079
|
-
case "Z":
|
|
2080
|
-
if (open) out.push({ type: "Z" });
|
|
2081
|
-
open = false;
|
|
2082
|
-
current = start;
|
|
2083
|
-
break;
|
|
2084
2061
|
}
|
|
2062
|
+
if (best) return best;
|
|
2063
|
+
const vertex = { id: this.vertices.length, x, y };
|
|
2064
|
+
this.vertices.push(vertex);
|
|
2065
|
+
const key = `${cx},${cy}`;
|
|
2066
|
+
const bucket = this.cells.get(key);
|
|
2067
|
+
if (bucket) bucket.push(vertex.id);
|
|
2068
|
+
else this.cells.set(key, [vertex.id]);
|
|
2069
|
+
return vertex;
|
|
2085
2070
|
}
|
|
2086
|
-
|
|
2071
|
+
}, _class2);
|
|
2072
|
+
function project(a, b, p) {
|
|
2073
|
+
const dx = b.x - a.x;
|
|
2074
|
+
const dy = b.y - a.y;
|
|
2075
|
+
const len2 = dx * dx + dy * dy;
|
|
2076
|
+
const t = len2 === 0 ? 0 : ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;
|
|
2077
|
+
const cx = a.x + t * dx;
|
|
2078
|
+
const cy = a.y + t * dy;
|
|
2079
|
+
return { t, dist: Math.hypot(p.x - cx, p.y - cy) };
|
|
2087
2080
|
}
|
|
2088
|
-
function
|
|
2089
|
-
const
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
const
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
};
|
|
2102
|
-
for (
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2081
|
+
function addSplit(splits, t, v) {
|
|
2082
|
+
const { a, b } = splits.segment;
|
|
2083
|
+
if (v.id === a.id || v.id === b.id) return;
|
|
2084
|
+
if (!splits.points.has(v.id)) splits.points.set(v.id, { t, v });
|
|
2085
|
+
}
|
|
2086
|
+
function splitPass(segments, pool, eps) {
|
|
2087
|
+
const order = segments.map((s, i) => ({
|
|
2088
|
+
i,
|
|
2089
|
+
minX: Math.min(s.a.x, s.b.x),
|
|
2090
|
+
maxX: Math.max(s.a.x, s.b.x),
|
|
2091
|
+
minY: Math.min(s.a.y, s.b.y),
|
|
2092
|
+
maxY: Math.max(s.a.y, s.b.y)
|
|
2093
|
+
})).sort((p, q) => p.minX - q.minX);
|
|
2094
|
+
const splits = segments.map((segment) => ({ segment, points: /* @__PURE__ */ new Map() }));
|
|
2095
|
+
for (let oi = 0; oi < order.length; oi++) {
|
|
2096
|
+
const bi = order[oi];
|
|
2097
|
+
const s1 = segments[bi.i];
|
|
2098
|
+
for (let oj = oi + 1; oj < order.length; oj++) {
|
|
2099
|
+
const bj = order[oj];
|
|
2100
|
+
if (bj.minX > bi.maxX + eps) break;
|
|
2101
|
+
if (bj.minY > bi.maxY + eps || bj.maxY < bi.minY - eps) continue;
|
|
2102
|
+
const s2 = segments[bj.i];
|
|
2103
|
+
const sharedA = s1.a.id === s2.a.id || s1.a.id === s2.b.id;
|
|
2104
|
+
const sharedB = s1.b.id === s2.a.id || s1.b.id === s2.b.id;
|
|
2105
|
+
if (sharedA && sharedB) continue;
|
|
2106
|
+
let touched = false;
|
|
2107
|
+
for (const [seg, other, split] of [
|
|
2108
|
+
[s1, s2, splits[bi.i]],
|
|
2109
|
+
[s2, s1, splits[bj.i]]
|
|
2110
|
+
]) {
|
|
2111
|
+
for (const p of [other.a, other.b]) {
|
|
2112
|
+
if (p.id === seg.a.id || p.id === seg.b.id) continue;
|
|
2113
|
+
const { t: t2, dist: dist3 } = project(seg.a, seg.b, p);
|
|
2114
|
+
const len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);
|
|
2115
|
+
if (dist3 <= eps && t2 * len > eps && (1 - t2) * len > eps) {
|
|
2116
|
+
addSplit(split, t2, p);
|
|
2117
|
+
touched = true;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
if (touched) continue;
|
|
2122
|
+
const d1x = s1.b.x - s1.a.x;
|
|
2123
|
+
const d1y = s1.b.y - s1.a.y;
|
|
2124
|
+
const d2x = s2.b.x - s2.a.x;
|
|
2125
|
+
const d2y = s2.b.y - s2.a.y;
|
|
2126
|
+
const denom = d1x * d2y - d1y * d2x;
|
|
2127
|
+
const len1 = Math.hypot(d1x, d1y);
|
|
2128
|
+
const len2 = Math.hypot(d2x, d2y);
|
|
2129
|
+
if (Math.abs(denom) <= 1e-12 * len1 * len2) continue;
|
|
2130
|
+
const ex = s2.a.x - s1.a.x;
|
|
2131
|
+
const ey = s2.a.y - s1.a.y;
|
|
2132
|
+
const t = (ex * d2y - ey * d2x) / denom;
|
|
2133
|
+
const u = (ex * d1y - ey * d1x) / denom;
|
|
2134
|
+
const tolT = eps / len1;
|
|
2135
|
+
const tolU = eps / len2;
|
|
2136
|
+
if (t < -tolT || t > 1 + tolT || u < -tolU || u > 1 + tolU) continue;
|
|
2137
|
+
if (sharedA || sharedB) continue;
|
|
2138
|
+
const v = pool.get(s1.a.x + t * d1x, s1.a.y + t * d1y);
|
|
2139
|
+
addSplit(splits[bi.i], project(s1.a, s1.b, v).t, v);
|
|
2140
|
+
addSplit(splits[bj.i], project(s2.a, s2.b, v).t, v);
|
|
2127
2141
|
}
|
|
2128
2142
|
}
|
|
2129
|
-
|
|
2130
|
-
}
|
|
2131
|
-
function contoursToPath(contours) {
|
|
2143
|
+
let changed = false;
|
|
2132
2144
|
const out = [];
|
|
2133
|
-
for (const
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
let count = segs.length;
|
|
2138
|
-
if (contour.closed && count > 1) {
|
|
2139
|
-
const last = segs[count - 1];
|
|
2140
|
-
if (last.length === 2 && samePoint(last[1], segs[0][0])) count--;
|
|
2145
|
+
for (const { segment, points } of splits) {
|
|
2146
|
+
if (points.size === 0) {
|
|
2147
|
+
out.push(segment);
|
|
2148
|
+
continue;
|
|
2141
2149
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2150
|
+
changed = true;
|
|
2151
|
+
const ordered = [...points.values()].sort((p, q) => p.t - q.t);
|
|
2152
|
+
let prev = segment.a;
|
|
2153
|
+
for (const { v } of ordered) {
|
|
2154
|
+
if (v.id !== prev.id) out.push({ a: prev, b: v, operand: segment.operand });
|
|
2155
|
+
prev = v;
|
|
2148
2156
|
}
|
|
2149
|
-
if (
|
|
2157
|
+
if (prev.id !== segment.b.id) out.push({ a: prev, b: segment.b, operand: segment.operand });
|
|
2150
2158
|
}
|
|
2151
|
-
return out;
|
|
2159
|
+
return { segments: out, changed };
|
|
2152
2160
|
}
|
|
2153
|
-
function
|
|
2154
|
-
|
|
2155
|
-
const
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
for (const c of path) {
|
|
2162
|
-
switch (c.type) {
|
|
2163
|
-
case "M":
|
|
2164
|
-
d += `M${fx(c.x)} ${fy(c.y)}`;
|
|
2165
|
-
break;
|
|
2166
|
-
case "L":
|
|
2167
|
-
d += `L${fx(c.x)} ${fy(c.y)}`;
|
|
2168
|
-
break;
|
|
2169
|
-
case "Q":
|
|
2170
|
-
d += `Q${fx(c.x1)} ${fy(c.y1)} ${fx(c.x)} ${fy(c.y)}`;
|
|
2171
|
-
break;
|
|
2172
|
-
case "C":
|
|
2173
|
-
d += `C${fx(c.x1)} ${fy(c.y1)} ${fx(c.x2)} ${fy(c.y2)} ${fx(c.x)} ${fy(c.y)}`;
|
|
2174
|
-
break;
|
|
2175
|
-
case "Z":
|
|
2176
|
-
d += "Z";
|
|
2177
|
-
break;
|
|
2161
|
+
function defaultEpsilon(operands) {
|
|
2162
|
+
let max = 0;
|
|
2163
|
+
for (const op of operands) {
|
|
2164
|
+
for (const ring of op.rings) {
|
|
2165
|
+
for (const [x, y] of ring) {
|
|
2166
|
+
const m = Math.max(Math.abs(x), Math.abs(y));
|
|
2167
|
+
if (m > max) max = m;
|
|
2168
|
+
}
|
|
2178
2169
|
}
|
|
2179
2170
|
}
|
|
2180
|
-
return
|
|
2171
|
+
return Math.max(1e-9, max * 1e-9);
|
|
2181
2172
|
}
|
|
2182
|
-
function
|
|
2183
|
-
const
|
|
2184
|
-
const
|
|
2185
|
-
const
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
type: "C",
|
|
2196
|
-
x1: X(cmd.x1, cmd.y1),
|
|
2197
|
-
y1: Y(cmd.x1, cmd.y1),
|
|
2198
|
-
x2: X(cmd.x2, cmd.y2),
|
|
2199
|
-
y2: Y(cmd.x2, cmd.y2),
|
|
2200
|
-
x: X(cmd.x, cmd.y),
|
|
2201
|
-
y: Y(cmd.x, cmd.y)
|
|
2202
|
-
};
|
|
2203
|
-
case "Z":
|
|
2204
|
-
return cmd;
|
|
2173
|
+
function buildOverlay(inputs, options) {
|
|
2174
|
+
const operands = inputs.map(normalizeInput);
|
|
2175
|
+
const eps = options.epsilon && options.epsilon > 0 ? options.epsilon : defaultEpsilon(operands);
|
|
2176
|
+
const pool = new PointPool(eps);
|
|
2177
|
+
let segments = [];
|
|
2178
|
+
operands.forEach((op, operand) => {
|
|
2179
|
+
for (const ring of op.rings) {
|
|
2180
|
+
const verts = ring.map(([x, y]) => pool.get(x, y));
|
|
2181
|
+
for (let i = 0; i < verts.length; i++) {
|
|
2182
|
+
const a = verts[i];
|
|
2183
|
+
const b = verts[(i + 1) % verts.length];
|
|
2184
|
+
if (a.id !== b.id) segments.push({ a, b, operand });
|
|
2185
|
+
}
|
|
2205
2186
|
}
|
|
2206
2187
|
});
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
const segs = closeContour(contour);
|
|
2212
|
-
for (const s of segs) {
|
|
2213
|
-
if (s.length === 2) out.lines++;
|
|
2214
|
-
else if (s.length === 3) out.quads++;
|
|
2215
|
-
else out.cubics++;
|
|
2216
|
-
}
|
|
2188
|
+
for (let pass = 0; pass < 8; pass++) {
|
|
2189
|
+
const result = splitPass(segments, pool, eps);
|
|
2190
|
+
segments = result.segments;
|
|
2191
|
+
if (!result.changed) break;
|
|
2217
2192
|
}
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
function bezierPathBounds(input) {
|
|
2230
|
-
let minX = Infinity;
|
|
2231
|
-
let minY = Infinity;
|
|
2232
|
-
let maxX = -Infinity;
|
|
2233
|
-
let maxY = -Infinity;
|
|
2234
|
-
const path = toBezierPath(input);
|
|
2235
|
-
let pen = null;
|
|
2236
|
-
for (const contour of pathToContours(path)) {
|
|
2237
|
-
for (const s of contour.segments) {
|
|
2238
|
-
const b = segmentBounds(s);
|
|
2239
|
-
minX = Math.min(minX, b.minX);
|
|
2240
|
-
minY = Math.min(minY, b.minY);
|
|
2241
|
-
maxX = Math.max(maxX, b.maxX);
|
|
2242
|
-
maxY = Math.max(maxY, b.maxY);
|
|
2193
|
+
const count = operands.length;
|
|
2194
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2195
|
+
for (const s of segments) {
|
|
2196
|
+
const forward = s.a.id < s.b.id;
|
|
2197
|
+
const a = forward ? s.a : s.b;
|
|
2198
|
+
const b = forward ? s.b : s.a;
|
|
2199
|
+
const key = `${a.id},${b.id}`;
|
|
2200
|
+
let edge = byKey.get(key);
|
|
2201
|
+
if (!edge) {
|
|
2202
|
+
edge = { a, b, mult: new Array(count).fill(0) };
|
|
2203
|
+
byKey.set(key, edge);
|
|
2243
2204
|
}
|
|
2205
|
+
edge.mult[s.operand] += forward ? 1 : -1;
|
|
2244
2206
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2207
|
+
const edges = [...byKey.values()].filter((e) => e.mult.some((m) => m !== 0));
|
|
2208
|
+
const right = [];
|
|
2209
|
+
const left = [];
|
|
2210
|
+
const bandCount = Math.max(1, Math.min(4096, Math.ceil(Math.sqrt(edges.length))));
|
|
2211
|
+
const bandIndex = (horizontal) => {
|
|
2212
|
+
const lo = (e) => horizontal ? Math.min(-e.a.x, -e.b.x) : Math.min(e.a.y, e.b.y);
|
|
2213
|
+
const hi = (e) => horizontal ? Math.max(-e.a.x, -e.b.x) : Math.max(e.a.y, e.b.y);
|
|
2214
|
+
let min = Infinity;
|
|
2215
|
+
let max = -Infinity;
|
|
2216
|
+
for (const e of edges) {
|
|
2217
|
+
min = Math.min(min, lo(e));
|
|
2218
|
+
max = Math.max(max, hi(e));
|
|
2252
2219
|
}
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
let
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
const
|
|
2279
|
-
const
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2220
|
+
const size = max > min ? (max - min) / bandCount : 1;
|
|
2221
|
+
const band = (v) => Math.min(bandCount - 1, Math.max(0, Math.floor((v - min) / size)));
|
|
2222
|
+
const bands = Array.from({ length: bandCount }, () => []);
|
|
2223
|
+
edges.forEach((e, j) => {
|
|
2224
|
+
for (let k = band(lo(e)); k <= band(hi(e)); k++) bands[k].push(j);
|
|
2225
|
+
});
|
|
2226
|
+
return (v) => bands[band(v)];
|
|
2227
|
+
};
|
|
2228
|
+
const bandsFor = /* @__PURE__ */ new Map();
|
|
2229
|
+
for (let i = 0; i < edges.length; i++) {
|
|
2230
|
+
const edge = edges[i];
|
|
2231
|
+
const horizontal = Math.abs(edge.b.y - edge.a.y) < Math.abs(edge.b.x - edge.a.x);
|
|
2232
|
+
const tx = (v) => horizontal ? v.y : v.x;
|
|
2233
|
+
const ty = (v) => horizontal ? -v.x : v.y;
|
|
2234
|
+
const ax = tx(edge.a);
|
|
2235
|
+
const ay = ty(edge.a);
|
|
2236
|
+
const bx = tx(edge.b);
|
|
2237
|
+
const by = ty(edge.b);
|
|
2238
|
+
const mx = (ax + bx) / 2;
|
|
2239
|
+
const my = (ay + by) / 2;
|
|
2240
|
+
const winding = new Array(count).fill(0);
|
|
2241
|
+
if (!bandsFor.has(horizontal)) bandsFor.set(horizontal, bandIndex(horizontal));
|
|
2242
|
+
for (const j of bandsFor.get(horizontal)(my)) {
|
|
2243
|
+
if (j === i) continue;
|
|
2244
|
+
const other = edges[j];
|
|
2245
|
+
const oax = tx(other.a);
|
|
2246
|
+
const oay = ty(other.a);
|
|
2247
|
+
const obx = tx(other.b);
|
|
2248
|
+
const oby = ty(other.b);
|
|
2249
|
+
let dir = 0;
|
|
2250
|
+
if (oay <= my && oby > my) dir = 1;
|
|
2251
|
+
else if (oby <= my && oay > my) dir = -1;
|
|
2252
|
+
else continue;
|
|
2253
|
+
const x = oax + (my - oay) / (oby - oay) * (obx - oax);
|
|
2254
|
+
if (x <= mx) continue;
|
|
2255
|
+
for (let k = 0; k < count; k++) {
|
|
2256
|
+
if (other.mult[k] !== 0) winding[k] += dir * other.mult[k];
|
|
2284
2257
|
}
|
|
2285
|
-
remaining -= len;
|
|
2286
|
-
const tangent = segmentTangent(s, 1);
|
|
2287
|
-
last = { point: s[s.length - 1], tangent, angle: Math.atan2(tangent[1], tangent[0]), contour: ci, segment: si, t: 1 };
|
|
2288
2258
|
}
|
|
2259
|
+
const sideIsRight = by > ay;
|
|
2260
|
+
const r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);
|
|
2261
|
+
right.push(r);
|
|
2262
|
+
left.push(r.map((w, k) => w + edge.mult[k]));
|
|
2289
2263
|
}
|
|
2290
|
-
return
|
|
2264
|
+
return { edges, right, left, fillRules: operands.map((o) => o.fillRule), eps };
|
|
2291
2265
|
}
|
|
2292
|
-
function
|
|
2293
|
-
|
|
2294
|
-
const out = [];
|
|
2295
|
-
let at = 0;
|
|
2296
|
-
for (let i = 0; i < segs.length; i++) {
|
|
2297
|
-
const len = lens[i];
|
|
2298
|
-
const s0 = at;
|
|
2299
|
-
const s1 = at + len;
|
|
2300
|
-
at = s1;
|
|
2301
|
-
if (s1 <= from || s0 >= to || len <= 0) continue;
|
|
2302
|
-
const t0 = from > s0 ? segmentParamAtLength(segs[i], from - s0, len) : 0;
|
|
2303
|
-
const t1 = to < s1 ? segmentParamAtLength(segs[i], to - s0, len) : 1;
|
|
2304
|
-
if (t1 > t0) out.push(subSegment(segs[i], t0, t1));
|
|
2305
|
-
}
|
|
2306
|
-
return out;
|
|
2266
|
+
function insideFlags(windings, fillRules) {
|
|
2267
|
+
return windings.map((w, k) => fillRules[k] === "evenodd" ? (w & 1) === 1 : w !== 0);
|
|
2307
2268
|
}
|
|
2308
|
-
function
|
|
2309
|
-
const
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
let hi = 1;
|
|
2315
|
-
let t = b === a ? 0.5 : Math.max(0, Math.min(1, (value - a) / (b - a)));
|
|
2316
|
-
for (let i = 0; i < 60; i++) {
|
|
2317
|
-
const v = segmentPoint(seg, t)[axis] - value;
|
|
2318
|
-
if (v === 0) return t;
|
|
2319
|
-
if (v > 0 === increasing) hi = t;
|
|
2320
|
-
else lo = t;
|
|
2321
|
-
const d = segmentDerivative(seg, t)[axis];
|
|
2322
|
-
let next = d !== 0 ? t - v / d : (lo + hi) / 2;
|
|
2323
|
-
if (!(next > lo && next < hi)) next = (lo + hi) / 2;
|
|
2324
|
-
if (Math.abs(next - t) < 1e-15) return next;
|
|
2325
|
-
t = next;
|
|
2269
|
+
function traceRings(directed2) {
|
|
2270
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
2271
|
+
for (const e of directed2) {
|
|
2272
|
+
const list = outgoing.get(e.from.id);
|
|
2273
|
+
if (list) list.push(e);
|
|
2274
|
+
else outgoing.set(e.from.id, [e]);
|
|
2326
2275
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2276
|
+
const TWO_PI2 = Math.PI * 2;
|
|
2277
|
+
const next = (incoming) => {
|
|
2278
|
+
const candidates = outgoing.get(incoming.to.id);
|
|
2279
|
+
if (!candidates) return void 0;
|
|
2280
|
+
const back = Math.atan2(incoming.from.y - incoming.to.y, incoming.from.x - incoming.to.x);
|
|
2281
|
+
let best;
|
|
2282
|
+
let bestTurn = Infinity;
|
|
2283
|
+
for (const c of candidates) {
|
|
2284
|
+
let turn = back - c.angle;
|
|
2285
|
+
turn -= Math.floor(turn / TWO_PI2) * TWO_PI2;
|
|
2286
|
+
if (turn <= 1e-15) turn = TWO_PI2;
|
|
2287
|
+
if (turn < bestTurn) {
|
|
2288
|
+
bestTurn = turn;
|
|
2289
|
+
best = c;
|
|
2290
|
+
}
|
|
2335
2291
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2292
|
+
return best;
|
|
2293
|
+
};
|
|
2294
|
+
const used = /* @__PURE__ */ new Set();
|
|
2295
|
+
const rings = [];
|
|
2296
|
+
for (const start of directed2) {
|
|
2297
|
+
if (used.has(start)) continue;
|
|
2298
|
+
const ring = [];
|
|
2299
|
+
let edge = start;
|
|
2300
|
+
let guard = directed2.length + 1;
|
|
2301
|
+
while (edge && !used.has(edge) && guard-- > 0) {
|
|
2302
|
+
used.add(edge);
|
|
2303
|
+
ring.push(edge.from);
|
|
2304
|
+
edge = next(edge);
|
|
2341
2305
|
}
|
|
2306
|
+
if (ring.length >= 3) rings.push(ring);
|
|
2342
2307
|
}
|
|
2343
|
-
return
|
|
2308
|
+
return rings;
|
|
2344
2309
|
}
|
|
2345
|
-
function
|
|
2346
|
-
|
|
2347
|
-
let
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
winding += dir;
|
|
2310
|
+
function removeCollinear(ring, eps) {
|
|
2311
|
+
let points = ring;
|
|
2312
|
+
let changed = true;
|
|
2313
|
+
while (changed && points.length > 3) {
|
|
2314
|
+
changed = false;
|
|
2315
|
+
const out = [];
|
|
2316
|
+
const n = points.length;
|
|
2317
|
+
for (let i = 0; i < n; i++) {
|
|
2318
|
+
const prev = out.length > 0 ? out[out.length - 1] : points[(i - 1 + n) % n];
|
|
2319
|
+
const cur = points[i];
|
|
2320
|
+
const nxt = points[(i + 1) % n];
|
|
2321
|
+
const dx = nxt[0] - prev[0];
|
|
2322
|
+
const dy = nxt[1] - prev[1];
|
|
2323
|
+
const len = Math.hypot(dx, dy);
|
|
2324
|
+
const cross2 = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;
|
|
2325
|
+
const dot3 = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;
|
|
2326
|
+
if (len > 0 && Math.abs(cross2) / len <= eps && dot3 > 0 && dot3 < len * len) {
|
|
2327
|
+
changed = true;
|
|
2364
2328
|
continue;
|
|
2365
2329
|
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2330
|
+
out.push(cur);
|
|
2331
|
+
}
|
|
2332
|
+
if (out.length < 3) return out;
|
|
2333
|
+
points = out;
|
|
2334
|
+
}
|
|
2335
|
+
return points;
|
|
2336
|
+
}
|
|
2337
|
+
function assemble(rings, eps) {
|
|
2338
|
+
const outers = [];
|
|
2339
|
+
const holes = [];
|
|
2340
|
+
const minArea = eps * eps;
|
|
2341
|
+
for (const vertices of rings) {
|
|
2342
|
+
const ring = removeCollinear(
|
|
2343
|
+
vertices.map((v) => [v.x, v.y]),
|
|
2344
|
+
eps
|
|
2345
|
+
);
|
|
2346
|
+
if (ring.length < 3) continue;
|
|
2347
|
+
const area = ringSignedArea(ring);
|
|
2348
|
+
if (Math.abs(area) <= minArea) continue;
|
|
2349
|
+
(area > 0 ? outers : holes).push({ ring, area: Math.abs(area) });
|
|
2350
|
+
}
|
|
2351
|
+
outers.sort((a, b) => b.area - a.area);
|
|
2352
|
+
const polygons = outers.map((o) => [o.ring]);
|
|
2353
|
+
for (const hole of holes) {
|
|
2354
|
+
const [p, q] = [hole.ring[0], hole.ring[1]];
|
|
2355
|
+
const sample = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
|
|
2356
|
+
let owner = -1;
|
|
2357
|
+
for (let i = outers.length - 1; i >= 0; i--) {
|
|
2358
|
+
if (outers[i].area >= hole.area && pointInRings(sample, [outers[i].ring], "evenodd")) {
|
|
2359
|
+
owner = i;
|
|
2360
|
+
break;
|
|
2361
|
+
}
|
|
2369
2362
|
}
|
|
2363
|
+
if (owner >= 0) polygons[owner].push(hole.ring);
|
|
2370
2364
|
}
|
|
2371
|
-
return
|
|
2365
|
+
return polygons;
|
|
2372
2366
|
}
|
|
2373
|
-
function
|
|
2374
|
-
const
|
|
2375
|
-
|
|
2376
|
-
|
|
2367
|
+
function extract(overlay, isInside) {
|
|
2368
|
+
const directed2 = [];
|
|
2369
|
+
overlay.edges.forEach((edge, i) => {
|
|
2370
|
+
const l = isInside(insideFlags(overlay.left[i], overlay.fillRules));
|
|
2371
|
+
const r = isInside(insideFlags(overlay.right[i], overlay.fillRules));
|
|
2372
|
+
if (l === r) return;
|
|
2373
|
+
const [from, to] = l ? [edge.a, edge.b] : [edge.b, edge.a];
|
|
2374
|
+
directed2.push({ from, to, angle: Math.atan2(to.y - from.y, to.x - from.x) });
|
|
2375
|
+
});
|
|
2376
|
+
return assemble(traceRings(directed2), overlay.eps);
|
|
2377
2377
|
}
|
|
2378
|
-
function
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
minX = Math.min(minX, p[0]);
|
|
2389
|
-
minY = Math.min(minY, p[1]);
|
|
2390
|
-
maxX = Math.max(maxX, p[0]);
|
|
2391
|
-
maxY = Math.max(maxY, p[1]);
|
|
2392
|
-
}
|
|
2393
|
-
const dx = Math.max(minX - point[0], 0, point[0] - maxX);
|
|
2394
|
-
const dy = Math.max(minY - point[1], 0, point[1] - maxY);
|
|
2395
|
-
if (Math.hypot(dx, dy) >= best) continue;
|
|
2396
|
-
best = Math.min(best, segmentNearest(s, point).distance);
|
|
2397
|
-
}
|
|
2398
|
-
if (contour.segments.length === 0) continue;
|
|
2378
|
+
function predicate(operation) {
|
|
2379
|
+
switch (operation) {
|
|
2380
|
+
case "union":
|
|
2381
|
+
return (f) => f.some(Boolean);
|
|
2382
|
+
case "intersection":
|
|
2383
|
+
return (f) => f.length > 0 && f.every(Boolean);
|
|
2384
|
+
case "difference":
|
|
2385
|
+
return (f) => f[0] === true && !f.slice(1).some(Boolean);
|
|
2386
|
+
case "xor":
|
|
2387
|
+
return (f) => f.filter(Boolean).length % 2 === 1;
|
|
2399
2388
|
}
|
|
2400
|
-
return best;
|
|
2401
2389
|
}
|
|
2402
|
-
function
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
const
|
|
2416
|
-
|
|
2390
|
+
function polygonBoolean(operation, operands, options = {}) {
|
|
2391
|
+
if (operands.length === 0) return [];
|
|
2392
|
+
return extract(buildOverlay(operands, options), predicate(operation));
|
|
2393
|
+
}
|
|
2394
|
+
var unionPolygons = (a, b, options) => polygonBoolean("union", [a, b], options);
|
|
2395
|
+
var intersectPolygons = (a, b, options) => polygonBoolean("intersection", [a, b], options);
|
|
2396
|
+
var subtractPolygons = (a, b, options) => polygonBoolean("difference", [a, b], options);
|
|
2397
|
+
var xorPolygons = (a, b, options) => polygonBoolean("xor", [a, b], options);
|
|
2398
|
+
function dividePolygons(operands, options = {}) {
|
|
2399
|
+
if (operands.length === 0) return [];
|
|
2400
|
+
const overlay = buildOverlay(operands, options);
|
|
2401
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
2402
|
+
const note = (windings) => {
|
|
2403
|
+
const flags = insideFlags(windings, overlay.fillRules);
|
|
2404
|
+
if (!flags.some(Boolean)) return;
|
|
2405
|
+
const key = flags.map((f) => f ? "1" : "0").join("");
|
|
2406
|
+
if (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => f ? [k] : []));
|
|
2407
|
+
};
|
|
2408
|
+
overlay.edges.forEach((_, i) => {
|
|
2409
|
+
note(overlay.left[i]);
|
|
2410
|
+
note(overlay.right[i]);
|
|
2411
|
+
});
|
|
2412
|
+
const pieces = [];
|
|
2413
|
+
const keys = [...signatures.keys()].sort();
|
|
2414
|
+
for (const key of keys) {
|
|
2415
|
+
const polygons = extract(overlay, (flags) => flags.map((f) => f ? "1" : "0").join("") === key);
|
|
2416
|
+
polygons.sort((a, b) => polygonArea(b) - polygonArea(a));
|
|
2417
|
+
for (const polygon of polygons) pieces.push({ polygon, operands: signatures.get(key) });
|
|
2417
2418
|
}
|
|
2418
|
-
|
|
2419
|
-
return out;
|
|
2419
|
+
return pieces;
|
|
2420
2420
|
}
|
|
2421
2421
|
|
|
2422
2422
|
// src/canvas/pathops/boolean.ts
|
|
@@ -3660,8 +3660,8 @@ function deepPoint(rings) {
|
|
|
3660
3660
|
}
|
|
3661
3661
|
xs.sort((p, q) => p - q);
|
|
3662
3662
|
for (let i = 0; i + 1 < xs.length; i += 2) {
|
|
3663
|
-
const
|
|
3664
|
-
if (!best ||
|
|
3663
|
+
const span = xs[i + 1] - xs[i];
|
|
3664
|
+
if (!best || span > best.span) best = { span, p: [(xs[i] + xs[i + 1]) / 2, y] };
|
|
3665
3665
|
}
|
|
3666
3666
|
}
|
|
3667
3667
|
return _nullishCoalesce(_optionalChain([best, 'optionalAccess', _36 => _36.p]), () => ( null));
|
|
@@ -3804,718 +3804,6 @@ function offsetPath(input, distance, options = {}) {
|
|
|
3804
3804
|
}
|
|
3805
3805
|
}
|
|
3806
3806
|
|
|
3807
|
-
// src/canvas/pathops/planar.ts
|
|
3808
|
-
var twin = (h) => h ^ 1;
|
|
3809
|
-
function boundsOf(segs) {
|
|
3810
|
-
let minX = Infinity;
|
|
3811
|
-
let minY = Infinity;
|
|
3812
|
-
let maxX = -Infinity;
|
|
3813
|
-
let maxY = -Infinity;
|
|
3814
|
-
for (const s of segs) {
|
|
3815
|
-
for (const [x, y] of s) {
|
|
3816
|
-
if (x < minX) minX = x;
|
|
3817
|
-
if (x > maxX) maxX = x;
|
|
3818
|
-
if (y < minY) minY = y;
|
|
3819
|
-
if (y > maxY) maxY = y;
|
|
3820
|
-
}
|
|
3821
|
-
}
|
|
3822
|
-
return { minX, minY, maxX, maxY };
|
|
3823
|
-
}
|
|
3824
|
-
var inBounds = (b, p) => p[0] >= b.minX && p[0] <= b.maxX && p[1] >= b.minY && p[1] <= b.maxY;
|
|
3825
|
-
function withoutSpurs(cycle) {
|
|
3826
|
-
const out = [];
|
|
3827
|
-
for (const h of cycle) {
|
|
3828
|
-
if (out.length && out[out.length - 1] === twin(h)) out.pop();
|
|
3829
|
-
else out.push(h);
|
|
3830
|
-
}
|
|
3831
|
-
while (out.length >= 2 && twin(out[0]) === out[out.length - 1]) {
|
|
3832
|
-
out.pop();
|
|
3833
|
-
out.shift();
|
|
3834
|
-
}
|
|
3835
|
-
return out;
|
|
3836
|
-
}
|
|
3837
|
-
function pinEnds(pts, a, b) {
|
|
3838
|
-
const n = pts.length;
|
|
3839
|
-
if (n === 2) return [a, b];
|
|
3840
|
-
const out = pts.map((p) => [p[0], p[1]]);
|
|
3841
|
-
const d0 = [a[0] - pts[0][0], a[1] - pts[0][1]];
|
|
3842
|
-
const d1 = [b[0] - pts[n - 1][0], b[1] - pts[n - 1][1]];
|
|
3843
|
-
out[0] = a;
|
|
3844
|
-
out[n - 1] = b;
|
|
3845
|
-
if (n === 3) {
|
|
3846
|
-
out[1] = [pts[1][0] + (d0[0] + d1[0]) / 2, pts[1][1] + (d0[1] + d1[1]) / 2];
|
|
3847
|
-
} else {
|
|
3848
|
-
out[1] = [pts[1][0] + d0[0], pts[1][1] + d0[1]];
|
|
3849
|
-
out[2] = [pts[2][0] + d1[0], pts[2][1] + d1[1]];
|
|
3850
|
-
}
|
|
3851
|
-
return out;
|
|
3852
|
-
}
|
|
3853
|
-
function interiorPoint(path) {
|
|
3854
|
-
const chains = Array.isArray(path) && path.length > 0 && Array.isArray(path[0]) && Array.isArray(_optionalChain([path, 'access', _37 => _37[0], 'access', _38 => _38[0], 'optionalAccess', _39 => _39[0]])) ? path : pathToContours(path).map((c) => closeContour(c, true));
|
|
3855
|
-
const b = boundsOf(chains.flat());
|
|
3856
|
-
if (!(b.maxX > b.minX) || !(b.maxY > b.minY)) return null;
|
|
3857
|
-
const tol = Math.max(b.maxX - b.minX, b.maxY - b.minY) * 1e-3;
|
|
3858
|
-
const rings = chains.filter((c) => c.length).map((c) => flattenChain(c, tol));
|
|
3859
|
-
let best = null;
|
|
3860
|
-
for (const f of [0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 0.0625, 0.9375]) {
|
|
3861
|
-
const y = b.minY + (b.maxY - b.minY) * (f + 61803e-7);
|
|
3862
|
-
const xs = [];
|
|
3863
|
-
for (const ring of rings) {
|
|
3864
|
-
for (let i = 0; i < ring.length; i++) {
|
|
3865
|
-
const a = ring[i];
|
|
3866
|
-
const c = ring[(i + 1) % ring.length];
|
|
3867
|
-
if (a[1] <= y !== c[1] <= y) xs.push(a[0] + (y - a[1]) / (c[1] - a[1]) * (c[0] - a[0]));
|
|
3868
|
-
}
|
|
3869
|
-
}
|
|
3870
|
-
xs.sort((p, q) => p - q);
|
|
3871
|
-
for (let i = 0; i + 1 < xs.length; i++) {
|
|
3872
|
-
const width = xs[i + 1] - xs[i];
|
|
3873
|
-
if (!(width > (_nullishCoalesce(_optionalChain([best, 'optionalAccess', _40 => _40.width]), () => ( 0))))) continue;
|
|
3874
|
-
const m = [(xs[i] + xs[i + 1]) / 2, y];
|
|
3875
|
-
if (windingNumber(chains, m) !== 0) best = { width, p: m };
|
|
3876
|
-
}
|
|
3877
|
-
}
|
|
3878
|
-
return _nullishCoalesce(_optionalChain([best, 'optionalAccess', _41 => _41.p]), () => ( null));
|
|
3879
|
-
}
|
|
3880
|
-
var PlanarMap = (_class4 = class _PlanarMap {
|
|
3881
|
-
constructor(vertices, edges, sources, eps, inputCount, inputs) {;_class4.prototype.__init6.call(this);_class4.prototype.__init7.call(this);
|
|
3882
|
-
this.vertices = vertices;
|
|
3883
|
-
this.edges = edges;
|
|
3884
|
-
this.sources = sources;
|
|
3885
|
-
this.eps = eps;
|
|
3886
|
-
this.inputCount = inputCount;
|
|
3887
|
-
this.fills = inputs.map((input) => {
|
|
3888
|
-
const chains = input.fills === false ? [] : pathToContours(input.path).filter((c) => c.closed).map((c) => closeContour(c));
|
|
3889
|
-
return { chains, fillRule: _nullishCoalesce(input.fillRule, () => ( "nonzero")), bounds: boundsOf(chains.flat()) };
|
|
3890
|
-
});
|
|
3891
|
-
const nv = vertices.length;
|
|
3892
|
-
this.around = Array.from({ length: nv }, () => []);
|
|
3893
|
-
for (let h = 0; h < edges.length * 2; h++) this.around[this.origin(h)].push(h);
|
|
3894
|
-
this.slot = new Array(edges.length * 2).fill(0);
|
|
3895
|
-
this.around.forEach((list, v) => {
|
|
3896
|
-
if (list.length === 0) return;
|
|
3897
|
-
const at = vertices[v];
|
|
3898
|
-
let r = Infinity;
|
|
3899
|
-
for (const h of list) r = Math.min(r, dist2(at, vertices[this.origin(twin(h))]));
|
|
3900
|
-
r *= 0.5;
|
|
3901
|
-
const keyed = list.map((h) => ({ h, angle: this.leavingAngle(h, at, r) }));
|
|
3902
|
-
keyed.sort((a, b) => a.angle - b.angle);
|
|
3903
|
-
list.length = 0;
|
|
3904
|
-
keyed.forEach(({ h }, k) => {
|
|
3905
|
-
list.push(h);
|
|
3906
|
-
this.slot[h] = k;
|
|
3907
|
-
});
|
|
3908
|
-
});
|
|
3909
|
-
const parent = Array.from({ length: nv }, (_, i) => i);
|
|
3910
|
-
const find = (v) => {
|
|
3911
|
-
while (parent[v] !== v) {
|
|
3912
|
-
parent[v] = parent[parent[v]];
|
|
3913
|
-
v = parent[v];
|
|
3914
|
-
}
|
|
3915
|
-
return v;
|
|
3916
|
-
};
|
|
3917
|
-
for (const e of edges) parent[find(e.a)] = find(e.b);
|
|
3918
|
-
this.component = Array.from({ length: nv }, (_, v) => find(v));
|
|
3919
|
-
}
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
__init6() {this.cyclesCache = null}
|
|
3929
|
-
__init7() {this.sourcesCache = []}
|
|
3930
|
-
|
|
3931
|
-
/**
|
|
3932
|
-
* Maps `inputs` (their subpaths as given: open ones stay open). Null when they cannot be mapped
|
|
3933
|
-
* (non-finite points, more than `maxSegments` segments or an intersection search that does
|
|
3934
|
-
* not settle) or have no edges.
|
|
3935
|
-
*/
|
|
3936
|
-
static build(inputs, options = {}) {
|
|
3937
|
-
const chains = inputs.map((input) => pathToContours(input.path).map((c) => closeContour(c)).filter((c) => c.length));
|
|
3938
|
-
const count = chains.reduce((n, cs) => n + cs.reduce((m, c) => m + c.length, 0), 0);
|
|
3939
|
-
if (count === 0 || count > (_nullishCoalesce(options.maxSegments, () => ( 4096)))) return null;
|
|
3940
|
-
let split;
|
|
3941
|
-
try {
|
|
3942
|
-
split = planarSplit(chains, options);
|
|
3943
|
-
} catch (e6) {
|
|
3944
|
-
return null;
|
|
3945
|
-
}
|
|
3946
|
-
const edges = split.edges.filter((e) => e.a !== e.b).map((e) => ({ ...e, source: _nullishCoalesce(e.operands[e.operands.length - 1], () => ( -1)) }));
|
|
3947
|
-
if (!edges.length) return null;
|
|
3948
|
-
const gap = _nullishCoalesce(options.gap, () => ( 0));
|
|
3949
|
-
if (gap > split.eps) closeGaps(split.vertices, edges, gap);
|
|
3950
|
-
return new _PlanarMap(split.vertices, edges, split.sources, split.eps, inputs.length, inputs);
|
|
3951
|
-
}
|
|
3952
|
-
// -- half-edges ------------------------------------------------------------
|
|
3953
|
-
origin(h) {
|
|
3954
|
-
const e = this.edges[h >> 1];
|
|
3955
|
-
return h & 1 ? e.b : e.a;
|
|
3956
|
-
}
|
|
3957
|
-
halfPts(h) {
|
|
3958
|
-
const pts = this.edges[h >> 1].pts;
|
|
3959
|
-
return h & 1 ? [...pts].reverse() : pts;
|
|
3960
|
-
}
|
|
3961
|
-
/** The half-edge after `h` on the face to its left: the sharpest left turn at its end. */
|
|
3962
|
-
next(h) {
|
|
3963
|
-
const back = twin(h);
|
|
3964
|
-
const list = this.around[this.origin(back)];
|
|
3965
|
-
return list[(this.slot[back] + list.length - 1) % list.length];
|
|
3966
|
-
}
|
|
3967
|
-
/** Direction (angle) in which half-edge `h`, leaving `at`, first gets `r` away from it. */
|
|
3968
|
-
leavingAngle(h, at, r) {
|
|
3969
|
-
const pts = this.halfPts(h);
|
|
3970
|
-
const d = (t2) => dist2(segmentPoint(pts, t2), at);
|
|
3971
|
-
let lo = 0;
|
|
3972
|
-
let hi = 1;
|
|
3973
|
-
const steps = pts.length === 2 ? 1 : 16;
|
|
3974
|
-
for (let k = 1; k <= steps; k++) {
|
|
3975
|
-
if (d(k / steps) >= r) {
|
|
3976
|
-
hi = k / steps;
|
|
3977
|
-
break;
|
|
3978
|
-
}
|
|
3979
|
-
lo = k / steps;
|
|
3980
|
-
}
|
|
3981
|
-
for (let i = 0; i < 40 && hi - lo > 1e-12; i++) {
|
|
3982
|
-
const m = (lo + hi) / 2;
|
|
3983
|
-
if (d(m) >= r) hi = m;
|
|
3984
|
-
else lo = m;
|
|
3985
|
-
}
|
|
3986
|
-
const p = segmentPoint(pts, hi);
|
|
3987
|
-
if (dist2(p, at) > 0) return Math.atan2(p[1] - at[1], p[0] - at[0]);
|
|
3988
|
-
const t = segmentTangent(pts, 0);
|
|
3989
|
-
return Math.atan2(t[1], t[0]);
|
|
3990
|
-
}
|
|
3991
|
-
/** Segments of a run of half-edges (a loop unless `closed` is false), pieces of one input segment joined again. */
|
|
3992
|
-
cycleSegments(halves, closed = true) {
|
|
3993
|
-
const runs = halves.map((h) => {
|
|
3994
|
-
const e = this.edges[h >> 1];
|
|
3995
|
-
return { h, src: e.source < 0 ? -1 : e.src, from: h & 1 ? e.sb : e.sa, to: h & 1 ? e.sa : e.sb };
|
|
3996
|
-
});
|
|
3997
|
-
const joins = (a, b) => a.src >= 0 && a.src === b.src && Math.abs(a.to - b.from) < 1e-9 && a.to - a.from > 0 === b.to - b.from > 0;
|
|
3998
|
-
const n = runs.length;
|
|
3999
|
-
let start = 0;
|
|
4000
|
-
for (let k = 0; closed && k < n; k++) {
|
|
4001
|
-
if (!joins(runs[(k + n - 1) % n], runs[k])) {
|
|
4002
|
-
start = k;
|
|
4003
|
-
break;
|
|
4004
|
-
}
|
|
4005
|
-
}
|
|
4006
|
-
const ordered = [...runs.slice(start), ...runs.slice(0, start)];
|
|
4007
|
-
const out = [];
|
|
4008
|
-
let i = 0;
|
|
4009
|
-
while (i < ordered.length) {
|
|
4010
|
-
let j = i + 1;
|
|
4011
|
-
while (j < ordered.length && joins(ordered[j - 1], ordered[j])) j++;
|
|
4012
|
-
const first = ordered[i];
|
|
4013
|
-
const last = ordered[j - 1];
|
|
4014
|
-
if (j - i === 1) out.push(this.halfPts(first.h));
|
|
4015
|
-
else {
|
|
4016
|
-
const a = this.vertices[this.origin(first.h)];
|
|
4017
|
-
const b = this.vertices[this.origin(twin(last.h))];
|
|
4018
|
-
const seg = this.sources[first.src];
|
|
4019
|
-
out.push(seg.length === 2 ? [a, b] : pinEnds(subSegment(seg, first.from, last.to), a, b));
|
|
4020
|
-
}
|
|
4021
|
-
i = j;
|
|
4022
|
-
}
|
|
4023
|
-
return out;
|
|
4024
|
-
}
|
|
4025
|
-
cycles() {
|
|
4026
|
-
if (this.cyclesCache) return this.cyclesCache;
|
|
4027
|
-
const halves = this.edges.length * 2;
|
|
4028
|
-
const seen = new Uint8Array(halves);
|
|
4029
|
-
const faces = [];
|
|
4030
|
-
const holes = [];
|
|
4031
|
-
const sliver = this.eps * 10;
|
|
4032
|
-
for (let start = 0; start < halves; start++) {
|
|
4033
|
-
if (seen[start]) continue;
|
|
4034
|
-
const walk = [];
|
|
4035
|
-
let h = start;
|
|
4036
|
-
while (!seen[h]) {
|
|
4037
|
-
seen[h] = 1;
|
|
4038
|
-
walk.push(h);
|
|
4039
|
-
h = this.next(h);
|
|
4040
|
-
}
|
|
4041
|
-
const cycle = withoutSpurs(walk);
|
|
4042
|
-
if (!cycle.length) continue;
|
|
4043
|
-
const segments = this.cycleSegments(cycle);
|
|
4044
|
-
const area = contourSignedArea(segments);
|
|
4045
|
-
let perimeter = 0;
|
|
4046
|
-
for (const s of segments) perimeter += s.length === 2 ? dist2(s[0], s[1]) : segmentLength(s, 0, 1, 1e-6);
|
|
4047
|
-
if (!(perimeter > 0)) continue;
|
|
4048
|
-
const entry = { halves: cycle, segments, area, bounds: boundsOf(segments) };
|
|
4049
|
-
if (area > 0 && 2 * area / perimeter >= sliver) faces.push(entry);
|
|
4050
|
-
else if (area < 0 && -2 * area / perimeter >= sliver) holes.push(entry);
|
|
4051
|
-
}
|
|
4052
|
-
const faceHoles = faces.map(() => []);
|
|
4053
|
-
holes.forEach((hole, k) => {
|
|
4054
|
-
const v = this.origin(hole.halves[0]);
|
|
4055
|
-
const p = this.vertices[v];
|
|
4056
|
-
const own = this.component[v];
|
|
4057
|
-
let best = -1;
|
|
4058
|
-
faces.forEach((face, i) => {
|
|
4059
|
-
if (this.component[this.origin(face.halves[0])] === own || !inBounds(face.bounds, p)) return;
|
|
4060
|
-
if (best >= 0 && faces[best].area <= face.area) return;
|
|
4061
|
-
if (windingNumber([face.segments], p) !== 0) best = i;
|
|
4062
|
-
});
|
|
4063
|
-
if (best >= 0) faceHoles[best].push(k);
|
|
4064
|
-
});
|
|
4065
|
-
const faceOf = new Int32Array(halves).fill(-1);
|
|
4066
|
-
faces.forEach((face, i) => {
|
|
4067
|
-
for (const h of face.halves) faceOf[h] = i;
|
|
4068
|
-
for (const k of faceHoles[i]) for (const h of holes[k].halves) faceOf[h] = i;
|
|
4069
|
-
});
|
|
4070
|
-
this.cyclesCache = { faces, holes, faceHoles, faceOf };
|
|
4071
|
-
this.sourcesCache = faces.map(() => null);
|
|
4072
|
-
return this.cyclesCache;
|
|
4073
|
-
}
|
|
4074
|
-
faceChains(i) {
|
|
4075
|
-
const { faces, holes, faceHoles } = this.cycles();
|
|
4076
|
-
return [faces[i].segments, ...faceHoles[i].map((k) => holes[k].segments)];
|
|
4077
|
-
}
|
|
4078
|
-
/** The bounded faces of the map. */
|
|
4079
|
-
get faces() {
|
|
4080
|
-
const { faces, holes, faceHoles } = this.cycles();
|
|
4081
|
-
return faces.map((f, i) => {
|
|
4082
|
-
const chains = this.faceChains(i);
|
|
4083
|
-
const area = f.area + faceHoles[i].reduce((sum, k) => sum + holes[k].area, 0);
|
|
4084
|
-
return { path: contoursToPath(chains.map((segments) => ({ segments, closed: true }))), area, bounds: f.bounds };
|
|
4085
|
-
});
|
|
4086
|
-
}
|
|
4087
|
-
/** The inputs whose fill covers face `i`, ascending (empty for an area only outlines enclose). */
|
|
4088
|
-
faceSources(i) {
|
|
4089
|
-
this.cycles();
|
|
4090
|
-
const cached = this.sourcesCache[i];
|
|
4091
|
-
if (cached) return cached;
|
|
4092
|
-
const p = interiorPoint(this.faceChains(i));
|
|
4093
|
-
const out = [];
|
|
4094
|
-
if (p) {
|
|
4095
|
-
this.fills.forEach((fill, k) => {
|
|
4096
|
-
if (!fill.chains.length || !inBounds(fill.bounds, p)) return;
|
|
4097
|
-
const w = windingNumber(fill.chains, p);
|
|
4098
|
-
if (fill.fillRule === "evenodd" ? (w & 1) === 1 : w !== 0) out.push(k);
|
|
4099
|
-
});
|
|
4100
|
-
}
|
|
4101
|
-
this.sourcesCache[i] = out;
|
|
4102
|
-
return out;
|
|
4103
|
-
}
|
|
4104
|
-
/** A point well inside face `i`. */
|
|
4105
|
-
faceInterior(i) {
|
|
4106
|
-
return interiorPoint(this.faceChains(i));
|
|
4107
|
-
}
|
|
4108
|
-
/** The face under `p` (the smallest one around it), or -1. */
|
|
4109
|
-
faceAt(p) {
|
|
4110
|
-
const { faces } = this.cycles();
|
|
4111
|
-
let best = -1;
|
|
4112
|
-
faces.forEach((face, i) => {
|
|
4113
|
-
if (!inBounds(face.bounds, p) || best >= 0 && faces[best].area <= face.area) return;
|
|
4114
|
-
if (windingNumber(this.faceChains(i), p) !== 0) best = i;
|
|
4115
|
-
});
|
|
4116
|
-
return best;
|
|
4117
|
-
}
|
|
4118
|
-
/**
|
|
4119
|
-
* The union of faces, traced along the map's own edges: those between two merged faces drop
|
|
4120
|
-
* out exactly, so no seam or sliver is left where a boolean union of their outlines could
|
|
4121
|
-
* round shared corners apart. Outer subpaths come out counter-clockwise (y-up), holes clockwise.
|
|
4122
|
-
*/
|
|
4123
|
-
merge(faceIndices) {
|
|
4124
|
-
const { faceOf } = this.cycles();
|
|
4125
|
-
const picked = new Set(faceIndices);
|
|
4126
|
-
const merged = (h) => faceOf[h] >= 0 && picked.has(faceOf[h]);
|
|
4127
|
-
const boundary = (h) => merged(h) && !merged(twin(h));
|
|
4128
|
-
const used = new Uint8Array(this.edges.length * 2);
|
|
4129
|
-
const contours = [];
|
|
4130
|
-
for (let start = 0; start < used.length; start++) {
|
|
4131
|
-
if (used[start] || !boundary(start)) continue;
|
|
4132
|
-
const cycle = [];
|
|
4133
|
-
let h = start;
|
|
4134
|
-
let ok = true;
|
|
4135
|
-
while (!used[h]) {
|
|
4136
|
-
used[h] = 1;
|
|
4137
|
-
cycle.push(h);
|
|
4138
|
-
const back = twin(h);
|
|
4139
|
-
const list = this.around[this.origin(back)];
|
|
4140
|
-
const k = this.slot[back];
|
|
4141
|
-
const n = list.length;
|
|
4142
|
-
let found = -1;
|
|
4143
|
-
for (let j = 1; j <= n; j++) {
|
|
4144
|
-
const g = list[(k - j + n * 2) % n];
|
|
4145
|
-
if (boundary(g)) {
|
|
4146
|
-
found = g;
|
|
4147
|
-
break;
|
|
4148
|
-
}
|
|
4149
|
-
}
|
|
4150
|
-
if (found < 0) {
|
|
4151
|
-
ok = false;
|
|
4152
|
-
break;
|
|
4153
|
-
}
|
|
4154
|
-
h = found;
|
|
4155
|
-
}
|
|
4156
|
-
if (!ok || h !== start) continue;
|
|
4157
|
-
contours.push({ segments: this.cycleSegments(cycle), closed: true });
|
|
4158
|
-
}
|
|
4159
|
-
return contoursToPath(contours);
|
|
4160
|
-
}
|
|
4161
|
-
/**
|
|
4162
|
-
* The edges joined into runs between junctions: vertices where other than two edges meet, or
|
|
4163
|
-
* where the two edges lie on different inputs. Each run is keyed by its input.
|
|
4164
|
-
*/
|
|
4165
|
-
chains() {
|
|
4166
|
-
const passes = (v) => {
|
|
4167
|
-
const list = this.around[v];
|
|
4168
|
-
if (list.length !== 2) return null;
|
|
4169
|
-
return this.edges[list[0] >> 1].source === this.edges[list[1] >> 1].source ? [list[0], list[1]] : null;
|
|
4170
|
-
};
|
|
4171
|
-
const used = new Uint8Array(this.edges.length);
|
|
4172
|
-
const out = [];
|
|
4173
|
-
const walk = (h0) => {
|
|
4174
|
-
const source = this.edges[h0 >> 1].source;
|
|
4175
|
-
const halves = [];
|
|
4176
|
-
let h = h0;
|
|
4177
|
-
let closed = false;
|
|
4178
|
-
for (; ; ) {
|
|
4179
|
-
if (used[h >> 1]) {
|
|
4180
|
-
closed = true;
|
|
4181
|
-
break;
|
|
4182
|
-
}
|
|
4183
|
-
used[h >> 1] = 1;
|
|
4184
|
-
halves.push(h);
|
|
4185
|
-
const v = this.origin(twin(h));
|
|
4186
|
-
const pass = passes(v);
|
|
4187
|
-
if (!pass) {
|
|
4188
|
-
closed = v === this.origin(h0);
|
|
4189
|
-
break;
|
|
4190
|
-
}
|
|
4191
|
-
h = pass[0] === twin(h) ? pass[1] : pass[0];
|
|
4192
|
-
}
|
|
4193
|
-
if (source < 0) return;
|
|
4194
|
-
const segments = this.cycleSegments(halves, closed);
|
|
4195
|
-
out.push({ segments, closed, source });
|
|
4196
|
-
};
|
|
4197
|
-
this.around.forEach((list, v) => {
|
|
4198
|
-
if (passes(v)) return;
|
|
4199
|
-
for (const h of list) if (!used[h >> 1]) walk(h);
|
|
4200
|
-
});
|
|
4201
|
-
for (let e = 0; e < this.edges.length; e++) if (!used[e]) walk(e * 2);
|
|
4202
|
-
return out;
|
|
4203
|
-
}
|
|
4204
|
-
// -- fill regions (cadcraft `boundary_at_gap`) -------------------------------
|
|
4205
|
-
/**
|
|
4206
|
-
* The region enclosing `p`, traced as cadcraft does it: a ray from `p` towards +x meets the
|
|
4207
|
-
* edges in order; for each, the half-edge with `p` on its left is followed by always taking the
|
|
4208
|
-
* sharpest left turn, until a loop that runs counter-clockwise round `p` comes back. The pieces
|
|
4209
|
-
* of the map nested inside that loop (islands) become its holes. Null when nothing encloses `p`.
|
|
4210
|
-
*/
|
|
4211
|
-
regionAt(p) {
|
|
4212
|
-
const { faces, faceOf } = this.cycles();
|
|
4213
|
-
const hits = [];
|
|
4214
|
-
this.edges.forEach((e, i) => {
|
|
4215
|
-
const ay = this.vertices[e.a][1];
|
|
4216
|
-
const by = this.vertices[e.b][1];
|
|
4217
|
-
if (ay > p[1] === by > p[1]) return;
|
|
4218
|
-
const t = solveMonotone(e.pts, 1, p[1]);
|
|
4219
|
-
const x = segmentPoint(e.pts, t)[0];
|
|
4220
|
-
if (x > p[0]) hits.push({ x, e: i });
|
|
4221
|
-
});
|
|
4222
|
-
hits.sort((a, b) => a.x - b.x);
|
|
4223
|
-
for (const { e } of hits.slice(0, 64)) {
|
|
4224
|
-
const ay = this.vertices[this.edges[e].a][1];
|
|
4225
|
-
const by = this.vertices[this.edges[e].b][1];
|
|
4226
|
-
const h = by > ay ? e * 2 : e * 2 + 1;
|
|
4227
|
-
const walk = [];
|
|
4228
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4229
|
-
let g = h;
|
|
4230
|
-
while (!seen.has(g)) {
|
|
4231
|
-
seen.add(g);
|
|
4232
|
-
walk.push(g);
|
|
4233
|
-
g = this.next(g);
|
|
4234
|
-
}
|
|
4235
|
-
if (g !== h) continue;
|
|
4236
|
-
const cycle = withoutSpurs(walk);
|
|
4237
|
-
if (!cycle.length) continue;
|
|
4238
|
-
const segments = this.cycleSegments(cycle);
|
|
4239
|
-
if (!(contourSignedArea(segments) > 0) || windingNumber([segments], p) === 0) continue;
|
|
4240
|
-
const face = faceOf[cycle[0]];
|
|
4241
|
-
if (face >= 0 && faces[face].halves.includes(cycle[0])) {
|
|
4242
|
-
return contoursToPath(this.faceChains(face).map((s) => ({ segments: s, closed: true })));
|
|
4243
|
-
}
|
|
4244
|
-
return contoursToPath([{ segments, closed: true }]);
|
|
4245
|
-
}
|
|
4246
|
-
return null;
|
|
4247
|
-
}
|
|
4248
|
-
}, _class4);
|
|
4249
|
-
function closeGaps(vertices, edges, gap) {
|
|
4250
|
-
const degree = new Array(vertices.length).fill(0);
|
|
4251
|
-
const linked = /* @__PURE__ */ new Set();
|
|
4252
|
-
for (const e of edges) {
|
|
4253
|
-
degree[e.a]++;
|
|
4254
|
-
degree[e.b]++;
|
|
4255
|
-
linked.add(`${Math.min(e.a, e.b)},${Math.max(e.a, e.b)}`);
|
|
4256
|
-
}
|
|
4257
|
-
const ends = degree.flatMap((d, v) => d === 1 ? [v] : []).sort((a, b) => vertices[a][0] - vertices[b][0]);
|
|
4258
|
-
const joined = /* @__PURE__ */ new Set();
|
|
4259
|
-
let lo = 0;
|
|
4260
|
-
for (let i = 0; i < ends.length; i++) {
|
|
4261
|
-
const u = ends[i];
|
|
4262
|
-
if (joined.has(u)) continue;
|
|
4263
|
-
const pu = vertices[u];
|
|
4264
|
-
while (lo < ends.length && vertices[ends[lo]][0] < pu[0] - gap) lo++;
|
|
4265
|
-
let best = -1;
|
|
4266
|
-
let bestD = Infinity;
|
|
4267
|
-
for (let j = lo; j < ends.length; j++) {
|
|
4268
|
-
const v = ends[j];
|
|
4269
|
-
const pv = vertices[v];
|
|
4270
|
-
if (pv[0] > pu[0] + gap) break;
|
|
4271
|
-
if (v === u || joined.has(v) || linked.has(`${Math.min(u, v)},${Math.max(u, v)}`)) continue;
|
|
4272
|
-
const d = dist2(pu, pv);
|
|
4273
|
-
if (d <= gap && d < bestD) {
|
|
4274
|
-
best = v;
|
|
4275
|
-
bestD = d;
|
|
4276
|
-
}
|
|
4277
|
-
}
|
|
4278
|
-
if (best < 0) continue;
|
|
4279
|
-
const a = Math.min(u, best);
|
|
4280
|
-
const b = Math.max(u, best);
|
|
4281
|
-
edges.push({ a, b, pts: [vertices[a], vertices[b]], operands: [], src: -1, sa: 0, sb: 1, source: -1 });
|
|
4282
|
-
joined.add(u);
|
|
4283
|
-
joined.add(best);
|
|
4284
|
-
}
|
|
4285
|
-
}
|
|
4286
|
-
function contourEnclosesArea(contour) {
|
|
4287
|
-
const segs = closeContour(contour, true);
|
|
4288
|
-
if (!segs.length) return false;
|
|
4289
|
-
const b = boundsOf(segs);
|
|
4290
|
-
const w = b.maxX - b.minX;
|
|
4291
|
-
const h = b.maxY - b.minY;
|
|
4292
|
-
return Math.abs(contourSignedArea(segs)) > 1e-9 * (w * w + h * h);
|
|
4293
|
-
}
|
|
4294
|
-
function nearestU(segs, p) {
|
|
4295
|
-
let best = null;
|
|
4296
|
-
segs.forEach((seg, k) => {
|
|
4297
|
-
const n = segmentNearest(seg, p);
|
|
4298
|
-
if (!best || n.distance < best.d) best = { u: k + n.t, d: n.distance };
|
|
4299
|
-
});
|
|
4300
|
-
return best;
|
|
4301
|
-
}
|
|
4302
|
-
function span(segs, u0, u1) {
|
|
4303
|
-
const n = segs.length;
|
|
4304
|
-
if (!n || !Number.isFinite(u0) || !Number.isFinite(u1)) return [];
|
|
4305
|
-
const first = Math.max(0, Math.floor(u0));
|
|
4306
|
-
const last = Math.min(Math.max(0, Math.ceil(u1)), first + 2 * n);
|
|
4307
|
-
const out = [];
|
|
4308
|
-
for (let k = first; k < last; k++) {
|
|
4309
|
-
const t0 = Math.min(1, Math.max(0, u0 - k));
|
|
4310
|
-
const t1 = Math.min(1, Math.max(0, u1 - k));
|
|
4311
|
-
if (t1 - t0 > 1e-12) out.push(subSegment(segs[k % n], t0, t1));
|
|
4312
|
-
}
|
|
4313
|
-
return out;
|
|
4314
|
-
}
|
|
4315
|
-
function snapEnds2(segs, to, tol) {
|
|
4316
|
-
if (!segs.length) return segs;
|
|
4317
|
-
const out = segs.map((s) => [...s]);
|
|
4318
|
-
const move = (seg, end, handle) => {
|
|
4319
|
-
const q = to.find((t) => dist2(t, seg[end]) <= tol);
|
|
4320
|
-
if (!q) return;
|
|
4321
|
-
const d = [q[0] - seg[end][0], q[1] - seg[end][1]];
|
|
4322
|
-
seg[end] = q;
|
|
4323
|
-
if (seg.length === 4) seg[handle] = [seg[handle][0] + d[0], seg[handle][1] + d[1]];
|
|
4324
|
-
};
|
|
4325
|
-
move(out[0], 0, 1);
|
|
4326
|
-
const last = out[out.length - 1];
|
|
4327
|
-
move(last, last.length - 1, last.length - 2);
|
|
4328
|
-
return out;
|
|
4329
|
-
}
|
|
4330
|
-
function cutOutPiece(path, piece, fillCloses, tol) {
|
|
4331
|
-
const segs = piece.segments;
|
|
4332
|
-
if (!segs.length) return null;
|
|
4333
|
-
let mid = null;
|
|
4334
|
-
let longest = -1;
|
|
4335
|
-
let perimeter = 0;
|
|
4336
|
-
for (const seg of segs) {
|
|
4337
|
-
const len2 = segmentLength(seg);
|
|
4338
|
-
perimeter += len2;
|
|
4339
|
-
if (len2 > longest) {
|
|
4340
|
-
longest = len2;
|
|
4341
|
-
mid = segmentPoint(seg, 0.5);
|
|
4342
|
-
}
|
|
4343
|
-
}
|
|
4344
|
-
if (!mid || perimeter <= tol) return null;
|
|
4345
|
-
const a = segs[0][0];
|
|
4346
|
-
const lastSeg = segs[segs.length - 1];
|
|
4347
|
-
const b = lastSeg[lastSeg.length - 1];
|
|
4348
|
-
const whole = !!piece.closed || dist2(a, b) <= tol;
|
|
4349
|
-
const contours = pathToContours(path);
|
|
4350
|
-
let found = null;
|
|
4351
|
-
contours.forEach((c, index2) => {
|
|
4352
|
-
if (!c.segments.length) return;
|
|
4353
|
-
const closed2 = c.closed || fillCloses && contourEnclosesArea(c);
|
|
4354
|
-
const ring2 = closeContour(c, closed2);
|
|
4355
|
-
const n = nearestU(ring2, mid);
|
|
4356
|
-
if (n && n.d <= tol && (!found || n.d < found.d)) found = { index: index2, ring: ring2, closed: closed2, um: n.u, d: n.d };
|
|
4357
|
-
});
|
|
4358
|
-
if (!found) return null;
|
|
4359
|
-
const { index, ring, closed, um } = found;
|
|
4360
|
-
const len = ring.length;
|
|
4361
|
-
const real = closeContour(contours[index]).length;
|
|
4362
|
-
const at = (p) => {
|
|
4363
|
-
const n = nearestU(ring, p);
|
|
4364
|
-
return n && n.d <= tol ? n.u : null;
|
|
4365
|
-
};
|
|
4366
|
-
let keep = [];
|
|
4367
|
-
if (!whole) {
|
|
4368
|
-
const ua = at(a);
|
|
4369
|
-
const ub = at(b);
|
|
4370
|
-
if (ua === null || ub === null) return null;
|
|
4371
|
-
if (closed) {
|
|
4372
|
-
const fwd = (from, to) => ((to - from) % len + len) % len;
|
|
4373
|
-
const [start, end] = fwd(ua, um) <= fwd(ua, ub) ? [ub, ua] : [ua, ub];
|
|
4374
|
-
const rest = fwd(start, end);
|
|
4375
|
-
if (rest > 1e-9 && len - rest > 1e-9) {
|
|
4376
|
-
const parts = real >= len ? [[0, 2 * len]] : [[0, real], [len, len + real]];
|
|
4377
|
-
keep = parts.map(([lo, hi]) => [Math.max(start, lo), Math.min(start + rest, hi)]).filter(([x, y]) => y - x > 1e-9);
|
|
4378
|
-
}
|
|
4379
|
-
} else {
|
|
4380
|
-
const lo = Math.min(ua, ub);
|
|
4381
|
-
const hi = Math.max(ua, ub);
|
|
4382
|
-
if (um < lo - 1e-9 || um > hi + 1e-9) return null;
|
|
4383
|
-
keep = [[0, lo], [hi, len]].filter(([x, y]) => y - x > 1e-9);
|
|
4384
|
-
}
|
|
4385
|
-
}
|
|
4386
|
-
const out = contours.slice(0, index);
|
|
4387
|
-
for (const [u0, u1] of keep) {
|
|
4388
|
-
const run = span(ring, u0, u1);
|
|
4389
|
-
if (run.length) out.push({ segments: snapEnds2(run, [a, b], tol), closed: false });
|
|
4390
|
-
}
|
|
4391
|
-
out.push(...contours.slice(index + 1));
|
|
4392
|
-
return contoursToPath(out);
|
|
4393
|
-
}
|
|
4394
|
-
|
|
4395
|
-
// src/canvas/pathops/hatch.ts
|
|
4396
|
-
function hatchPath(path, options = {}) {
|
|
4397
|
-
const out = [];
|
|
4398
|
-
const b = bezierPathBounds(path);
|
|
4399
|
-
if (!b || b.maxX - b.minX <= 0 || b.maxY - b.minY <= 0) return out;
|
|
4400
|
-
const want = Math.max(_nullishCoalesce(options.gap, () => ( 4)), 1e-3);
|
|
4401
|
-
const s = Math.SQRT1_2;
|
|
4402
|
-
const rot = (p) => [(p[0] + p[1]) * s, (p[1] - p[0]) * s];
|
|
4403
|
-
const unrot = (x, y) => ({ x: (x - y) * s, y: (x + y) * s });
|
|
4404
|
-
const edges = [];
|
|
4405
|
-
for (const contour of pathToContours(path)) {
|
|
4406
|
-
const pts = flattenChain(closeContour(contour, true), want * 0.1).map(rot);
|
|
4407
|
-
for (let i = 0; i < pts.length; i++) {
|
|
4408
|
-
const p = pts[i];
|
|
4409
|
-
const q = pts[(i + 1) % pts.length];
|
|
4410
|
-
if (p[0] !== q[0] || p[1] !== q[1]) edges.push([p, q]);
|
|
4411
|
-
}
|
|
4412
|
-
}
|
|
4413
|
-
let y0 = Infinity;
|
|
4414
|
-
let y1 = -Infinity;
|
|
4415
|
-
for (const [p, q] of edges) {
|
|
4416
|
-
y0 = Math.min(y0, p[1], q[1]);
|
|
4417
|
-
y1 = Math.max(y1, p[1], q[1]);
|
|
4418
|
-
}
|
|
4419
|
-
if (!(y1 > y0)) return out;
|
|
4420
|
-
const gap = Math.max(want, (y1 - y0) / Math.max(1, _nullishCoalesce(options.maxLines, () => ( 400))), 1e-6);
|
|
4421
|
-
const xs = [];
|
|
4422
|
-
for (let y = Math.floor(y0 / gap) * gap + gap / 2; y < y1; y += gap) {
|
|
4423
|
-
xs.length = 0;
|
|
4424
|
-
for (const [p, q] of edges) if (p[1] <= y !== q[1] <= y) xs.push(p[0] + (y - p[1]) / (q[1] - p[1]) * (q[0] - p[0]));
|
|
4425
|
-
xs.sort((m, n) => m - n);
|
|
4426
|
-
for (let i = 0; i + 1 < xs.length; i += 2) {
|
|
4427
|
-
out.push({ type: "M", ...unrot(xs[i], y) }, { type: "L", ...unrot(xs[i + 1], y) });
|
|
4428
|
-
}
|
|
4429
|
-
}
|
|
4430
|
-
return out;
|
|
4431
|
-
}
|
|
4432
|
-
|
|
4433
|
-
// src/canvas/pathops/pathfinder.ts
|
|
4434
|
-
var PATHFINDER_OPERATIONS = ["divide", "trim", "merge", "crop", "outline", "minusBack"];
|
|
4435
|
-
var top = (covering) => covering[covering.length - 1];
|
|
4436
|
-
function pathfinder(op, shapes, options = {}) {
|
|
4437
|
-
const n = shapes.length;
|
|
4438
|
-
if (n === 0) return { pieces: [], engine: "curve" };
|
|
4439
|
-
const filled = shapes.filter((s) => !s.line);
|
|
4440
|
-
const operands = shapes.map((s) => ({ path: s.path, fillRule: _nullishCoalesce(s.fillRule, () => ( "nonzero")) }));
|
|
4441
|
-
if (op === "outline") {
|
|
4442
|
-
const map = PlanarMap.build(
|
|
4443
|
-
shapes.map((s) => ({ path: s.line ? s.path : closeAll(s.path), fills: false })),
|
|
4444
|
-
options
|
|
4445
|
-
);
|
|
4446
|
-
if (!map) return { pieces: [], engine: "curve", error: "could not map the outlines" };
|
|
4447
|
-
const pieces = map.chains().map((c) => ({ path: contoursToPath([{ segments: c.segments, closed: c.closed }]), source: c.source, open: !c.closed }));
|
|
4448
|
-
return { pieces, engine: "curve" };
|
|
4449
|
-
}
|
|
4450
|
-
if (op === "divide") {
|
|
4451
|
-
if (filled.length !== n) {
|
|
4452
|
-
const map = PlanarMap.build(shapes.map((s) => ({ path: s.line ? s.path : closeAll(s.path), fillRule: s.fillRule, fills: !s.line })), options);
|
|
4453
|
-
if (!map) return { pieces: [], engine: "curve", error: "could not map the shapes" };
|
|
4454
|
-
const pieces = [];
|
|
4455
|
-
map.faces.forEach((face, i) => {
|
|
4456
|
-
const covering = map.faceSources(i);
|
|
4457
|
-
if (covering.length) pieces.push({ path: face.path, source: top(covering) });
|
|
4458
|
-
});
|
|
4459
|
-
return { pieces, engine: "curve" };
|
|
4460
|
-
}
|
|
4461
|
-
const result = dividePaths(operands, options);
|
|
4462
|
-
return { ...report(result), pieces: result.pieces.map((p) => ({ path: p.path, source: top(p.operands) })) };
|
|
4463
|
-
}
|
|
4464
|
-
const index = shapes.flatMap((s, i) => s.line ? [] : [i]);
|
|
4465
|
-
const m = index.length;
|
|
4466
|
-
if (m === 0) return { pieces: [], engine: "curve" };
|
|
4467
|
-
const ops = index.map((i) => operands[i]);
|
|
4468
|
-
const key = (k) => _nullishCoalesce(shapes[index[k]].key, () => ( index[k]));
|
|
4469
|
-
const select = (selectors) => pathBooleanSelect(ops, selectors, options);
|
|
4470
|
-
const joined = (parts) => parts.flat();
|
|
4471
|
-
switch (op) {
|
|
4472
|
-
case "trim": {
|
|
4473
|
-
const result = select(index.map((_, k) => (c) => c.length > 0 && top(c) === k));
|
|
4474
|
-
const pieces = result.parts.flatMap((parts, k) => parts.length ? [{ path: joined(parts), source: index[k] }] : []);
|
|
4475
|
-
return { ...report(result), pieces };
|
|
4476
|
-
}
|
|
4477
|
-
case "merge": {
|
|
4478
|
-
const keys = [];
|
|
4479
|
-
for (let k = 0; k < m; k++) if (!keys.includes(key(k))) keys.push(key(k));
|
|
4480
|
-
const result = select(keys.map((kk) => (c) => c.length > 0 && key(top(c)) === kk));
|
|
4481
|
-
const pieces = [];
|
|
4482
|
-
result.parts.forEach((parts, j) => {
|
|
4483
|
-
let source = 0;
|
|
4484
|
-
for (let k = 0; k < m; k++) if (key(k) === keys[j]) source = index[k];
|
|
4485
|
-
for (const part of parts) pieces.push({ path: part, source });
|
|
4486
|
-
});
|
|
4487
|
-
return { ...report(result), pieces };
|
|
4488
|
-
}
|
|
4489
|
-
case "crop": {
|
|
4490
|
-
if (m < 2) return { pieces: [], engine: "curve" };
|
|
4491
|
-
const front = m - 1;
|
|
4492
|
-
const result = select(
|
|
4493
|
-
index.slice(0, front).map((_, k) => (c) => c.length >= 2 && top(c) === front && c[c.length - 2] === k)
|
|
4494
|
-
);
|
|
4495
|
-
const pieces = result.parts.flatMap((parts, k) => parts.length ? [{ path: joined(parts), source: index[k] }] : []);
|
|
4496
|
-
return { ...report(result), pieces };
|
|
4497
|
-
}
|
|
4498
|
-
case "minusBack": {
|
|
4499
|
-
const front = m - 1;
|
|
4500
|
-
const result = select([(c) => c.length === 1 && c[0] === front]);
|
|
4501
|
-
const parts = result.parts[0];
|
|
4502
|
-
return { ...report(result), pieces: parts.length ? [{ path: joined(parts), source: index[front] }] : [] };
|
|
4503
|
-
}
|
|
4504
|
-
}
|
|
4505
|
-
return { pieces: [], engine: "curve" };
|
|
4506
|
-
}
|
|
4507
|
-
function report(r) {
|
|
4508
|
-
return { engine: r.engine, ...r.fallbackReason ? { fallbackReason: r.fallbackReason } : {}, ...r.error ? { error: r.error } : {} };
|
|
4509
|
-
}
|
|
4510
|
-
function closeAll(path) {
|
|
4511
|
-
return contoursToPath(pathToContours(path).map((c) => ({ segments: c.segments, closed: true })));
|
|
4512
|
-
}
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
3807
|
|
|
4520
3808
|
|
|
4521
3809
|
|
|
@@ -4601,5 +3889,5 @@ function closeAll(path) {
|
|
|
4601
3889
|
|
|
4602
3890
|
|
|
4603
3891
|
|
|
4604
|
-
exports.
|
|
4605
|
-
//# sourceMappingURL=chunk-
|
|
3892
|
+
exports.cubicPointAt = cubicPointAt; exports.fitCubicWithTangents = fitCubicWithTangents; exports.fitCubicRun = fitCubicRun; exports.fitCubics = fitCubics; exports.isStraightCubic = isStraightCubic; exports.cubicsToPathData = cubicsToPathData; exports.fitPathData = fitPathData; exports.fitFreehandPath = fitFreehandPath; exports.PIXERATE_BRAND_GRADIENT = PIXERATE_BRAND_GRADIENT; exports.CANVAS_GRADIENT_PRESETS = CANVAS_GRADIENT_PRESETS; exports.isCanvasGradient = isCanvasGradient; exports.parseCssGradient = parseCssGradient; exports.resolveCanvasGradient = resolveCanvasGradient; exports.angleToSvgLinearCoords = angleToSvgLinearCoords; exports.gradientToSvgDef = gradientToSvgDef; exports.SHAPE_KINDS = SHAPE_KINDS; exports.isShapeDescription = isShapeDescription; exports.parseSvgPath = parseSvgPath; exports.flattenSvgPath = flattenSvgPath; exports.getShapeSvg = getShapeSvg; exports.getShapeRenderData = getShapeRenderData; exports.shapeToPolygon = shapeToPolygon; exports.formatNumber = formatNumber2; exports.simplifyRing = simplifyRing; exports.polygonToSvgPath = polygonToSvgPath; exports.dist = dist2; exports.segmentPoint = segmentPoint; exports.segmentDerivative = segmentDerivative; exports.segmentTangent = segmentTangent; exports.segmentCurvature = segmentCurvature; exports.splitSegment = splitSegment; exports.subSegment = subSegment; exports.segmentExtrema = segmentExtrema; exports.segmentBounds = segmentBounds; exports.segmentLength = segmentLength; exports.segmentParamAtLength = segmentParamAtLength; exports.segmentNearest = segmentNearest; exports.arcToCubics = arcToCubics; exports.toBezierPath = toBezierPath; exports.pathToContours = pathToContours; exports.contoursToPath = contoursToPath; exports.bezierPathToSvg = bezierPathToSvg; exports.transformBezierPath = transformBezierPath; exports.countPathSegments = countPathSegments; exports.closeContour = closeContour; exports.bezierPathBounds = bezierPathBounds; exports.bezierPathSignedArea = bezierPathSignedArea; exports.contourSignedArea = contourSignedArea; exports.bezierPathLength = bezierPathLength; exports.bezierPathPointAtLength = bezierPathPointAtLength; exports.trimSegments = trimSegments; exports.solveMonotone = solveMonotone; exports.windingNumber = windingNumber; exports.pointInBezierPath = pointInBezierPath; exports.distanceToBezierPath = distanceToBezierPath; exports.flattenChain = flattenChain; exports.ringSignedArea = ringSignedArea; exports.polygonArea = polygonArea; exports.multiPolygonArea = multiPolygonArea; exports.multiPolygonBounds = multiPolygonBounds; exports.pointInRings = pointInRings; exports.pointInMultiPolygon = pointInMultiPolygon; exports.polygonBoolean = polygonBoolean; exports.unionPolygons = unionPolygons; exports.intersectPolygons = intersectPolygons; exports.subtractPolygons = subtractPolygons; exports.xorPolygons = xorPolygons; exports.dividePolygons = dividePolygons; exports.PathOpsError = PathOpsError; exports.segmentIntersections = segmentIntersections; exports.pathBoolean = pathBoolean; exports.unitePaths = unitePaths; exports.intersectPaths = intersectPaths; exports.subtractPaths = subtractPaths; exports.excludePaths = excludePaths; exports.dividePaths = dividePaths; exports.normalizeBezierPath = normalizeBezierPath; exports.bezierPathArea = bezierPathArea; exports.planarSplit = planarSplit; exports.pathBooleanSelect = pathBooleanSelect; exports.dashContours = dashContours; exports.strokeOutlineRings = strokeOutlineRings; exports.outlineStroke = outlineStroke; exports.offsetPath = offsetPath;
|
|
3893
|
+
//# sourceMappingURL=chunk-O2UH7LZE.cjs.map
|