@pixerate/editor 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3335 @@
1
+ // src/canvas/polygonBoolean.ts
2
+ function ringSignedArea(ring) {
3
+ let sum = 0;
4
+ const n = ring.length;
5
+ for (let i = 0; i < n; i++) {
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;
22
+ }
23
+ function multiPolygonBounds(input) {
24
+ let minX = Infinity;
25
+ let minY = Infinity;
26
+ let maxX = -Infinity;
27
+ let maxY = -Infinity;
28
+ const visit = (ring) => {
29
+ for (const [x, y] of ring) {
30
+ if (x < minX) minX = x;
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 };
44
+ }
45
+ function pointInRings(point, rings, fillRule = "nonzero") {
46
+ const [px, py] = point;
47
+ let winding = 0;
48
+ for (const ring of rings) {
49
+ const n = ring.length;
50
+ for (let i = 0; i < n; i++) {
51
+ const [ax, ay] = ring[i];
52
+ const [bx, by] = ring[(i + 1) % n];
53
+ if (ay <= py && by > py) {
54
+ if (ax + (py - ay) / (by - ay) * (bx - ax) > px) winding++;
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;
61
+ }
62
+ function pointInMultiPolygon(point, multi) {
63
+ return multi.some((polygon) => pointInRings(point, polygon, "evenodd"));
64
+ }
65
+ function cleanRing(ring) {
66
+ const out = [];
67
+ for (const p of ring) {
68
+ if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
69
+ const last = out[out.length - 1];
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;
78
+ }
79
+ return out;
80
+ }
81
+ function isOperand(input) {
82
+ return !Array.isArray(input) && typeof input === "object" && input !== null && Array.isArray(input.rings);
83
+ }
84
+ function normalizeInput(input) {
85
+ if (isOperand(input)) {
86
+ return {
87
+ rings: input.rings.map(cleanRing).filter((r) => r.length >= 3),
88
+ fillRule: input.fillRule ?? "nonzero"
89
+ };
90
+ }
91
+ const rings = [];
92
+ for (const polygon of input ?? []) {
93
+ polygon.forEach((raw, index) => {
94
+ const ring = cleanRing(raw);
95
+ if (ring.length < 3) return;
96
+ const area = ringSignedArea(ring);
97
+ if (area === 0) return;
98
+ const wantPositive = index === 0;
99
+ rings.push(area > 0 === wantPositive ? ring : ring.reverse());
100
+ });
101
+ }
102
+ return { rings, fillRule: "nonzero" };
103
+ }
104
+ var PointPool = class {
105
+ constructor(eps) {
106
+ this.eps = eps;
107
+ }
108
+ eps;
109
+ vertices = [];
110
+ cells = /* @__PURE__ */ new Map();
111
+ get(x, y) {
112
+ const cx = Math.floor(x / this.eps);
113
+ const cy = Math.floor(y / this.eps);
114
+ let best = null;
115
+ let bestDist = Infinity;
116
+ for (let dx = -1; dx <= 1; dx++) {
117
+ for (let dy = -1; dy <= 1; dy++) {
118
+ const ids = this.cells.get(`${cx + dx},${cy + dy}`);
119
+ if (!ids) continue;
120
+ for (const id of ids) {
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
+ }
129
+ }
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
+ }
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) };
148
+ }
149
+ function addSplit(splits, t, v) {
150
+ const { a, b } = splits.segment;
151
+ if (v.id === a.id || v.id === b.id) return;
152
+ if (!splits.points.has(v.id)) splits.points.set(v.id, { t, v });
153
+ }
154
+ function splitPass(segments, pool, eps) {
155
+ const order = segments.map((s, i) => ({
156
+ i,
157
+ minX: Math.min(s.a.x, s.b.x),
158
+ maxX: Math.max(s.a.x, s.b.x),
159
+ minY: Math.min(s.a.y, s.b.y),
160
+ maxY: Math.max(s.a.y, s.b.y)
161
+ })).sort((p, q) => p.minX - q.minX);
162
+ const splits = segments.map((segment) => ({ segment, points: /* @__PURE__ */ new Map() }));
163
+ for (let oi = 0; oi < order.length; oi++) {
164
+ const bi = order[oi];
165
+ const s1 = segments[bi.i];
166
+ for (let oj = oi + 1; oj < order.length; oj++) {
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
+ }
210
+ }
211
+ let changed = false;
212
+ const out = [];
213
+ for (const { segment, points } of splits) {
214
+ if (points.size === 0) {
215
+ out.push(segment);
216
+ continue;
217
+ }
218
+ changed = true;
219
+ const ordered = [...points.values()].sort((p, q) => p.t - q.t);
220
+ let prev = segment.a;
221
+ for (const { v } of ordered) {
222
+ if (v.id !== prev.id) out.push({ a: prev, b: v, operand: segment.operand });
223
+ prev = v;
224
+ }
225
+ if (prev.id !== segment.b.id) out.push({ a: prev, b: segment.b, operand: segment.operand });
226
+ }
227
+ return { segments: out, changed };
228
+ }
229
+ function defaultEpsilon(operands) {
230
+ let max = 0;
231
+ for (const op of operands) {
232
+ for (const ring of op.rings) {
233
+ for (const [x, y] of ring) {
234
+ const m = Math.max(Math.abs(x), Math.abs(y));
235
+ if (m > max) max = m;
236
+ }
237
+ }
238
+ }
239
+ return Math.max(1e-9, max * 1e-9);
240
+ }
241
+ function buildOverlay(inputs, options) {
242
+ const operands = inputs.map(normalizeInput);
243
+ const eps = options.epsilon && options.epsilon > 0 ? options.epsilon : defaultEpsilon(operands);
244
+ const pool = new PointPool(eps);
245
+ let segments = [];
246
+ operands.forEach((op, operand) => {
247
+ for (const ring of op.rings) {
248
+ const verts = ring.map(([x, y]) => pool.get(x, y));
249
+ for (let i = 0; i < verts.length; i++) {
250
+ const a = verts[i];
251
+ const b = verts[(i + 1) % verts.length];
252
+ if (a.id !== b.id) segments.push({ a, b, operand });
253
+ }
254
+ }
255
+ });
256
+ for (let pass = 0; pass < 8; pass++) {
257
+ const result = splitPass(segments, pool, eps);
258
+ segments = result.segments;
259
+ if (!result.changed) break;
260
+ }
261
+ const count = operands.length;
262
+ const byKey = /* @__PURE__ */ new Map();
263
+ for (const s of segments) {
264
+ const forward = s.a.id < s.b.id;
265
+ const a = forward ? s.a : s.b;
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;
274
+ }
275
+ const edges = [...byKey.values()].filter((e) => e.mult.some((m) => m !== 0));
276
+ const right = [];
277
+ const left = [];
278
+ for (let i = 0; i < edges.length; i++) {
279
+ const edge = edges[i];
280
+ const horizontal = Math.abs(edge.b.y - edge.a.y) < Math.abs(edge.b.x - edge.a.x);
281
+ const tx = (v) => horizontal ? v.y : v.x;
282
+ const ty = (v) => horizontal ? -v.x : v.y;
283
+ const ax = tx(edge.a);
284
+ const ay = ty(edge.a);
285
+ const bx = tx(edge.b);
286
+ const by = ty(edge.b);
287
+ const mx = (ax + bx) / 2;
288
+ const my = (ay + by) / 2;
289
+ const winding = new Array(count).fill(0);
290
+ for (let j = 0; j < edges.length; j++) {
291
+ if (j === i) continue;
292
+ const other = edges[j];
293
+ const oax = tx(other.a);
294
+ const oay = ty(other.a);
295
+ const obx = tx(other.b);
296
+ const oby = ty(other.b);
297
+ let dir = 0;
298
+ if (oay <= my && oby > my) dir = 1;
299
+ else if (oby <= my && oay > my) dir = -1;
300
+ else continue;
301
+ const x = oax + (my - oay) / (oby - oay) * (obx - oax);
302
+ if (x <= mx) continue;
303
+ for (let k = 0; k < count; k++) {
304
+ if (other.mult[k] !== 0) winding[k] += dir * other.mult[k];
305
+ }
306
+ }
307
+ const sideIsRight = by > ay;
308
+ const r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);
309
+ right.push(r);
310
+ left.push(r.map((w, k) => w + edge.mult[k]));
311
+ }
312
+ return { edges, right, left, fillRules: operands.map((o) => o.fillRule), eps };
313
+ }
314
+ function insideFlags(windings, fillRules) {
315
+ return windings.map((w, k) => fillRules[k] === "evenodd" ? (w & 1) === 1 : w !== 0);
316
+ }
317
+ function traceRings(directed2) {
318
+ const outgoing = /* @__PURE__ */ new Map();
319
+ for (const e of directed2) {
320
+ const list = outgoing.get(e.from.id);
321
+ if (list) list.push(e);
322
+ else outgoing.set(e.from.id, [e]);
323
+ }
324
+ const TWO_PI2 = Math.PI * 2;
325
+ const next = (incoming) => {
326
+ const candidates = outgoing.get(incoming.to.id);
327
+ if (!candidates) return void 0;
328
+ const back = Math.atan2(incoming.from.y - incoming.to.y, incoming.from.x - incoming.to.x);
329
+ let best;
330
+ let bestTurn = Infinity;
331
+ for (const c of candidates) {
332
+ let turn = back - c.angle;
333
+ turn -= Math.floor(turn / TWO_PI2) * TWO_PI2;
334
+ if (turn <= 1e-15) turn = TWO_PI2;
335
+ if (turn < bestTurn) {
336
+ bestTurn = turn;
337
+ best = c;
338
+ }
339
+ }
340
+ return best;
341
+ };
342
+ const used = /* @__PURE__ */ new Set();
343
+ const rings = [];
344
+ for (const start of directed2) {
345
+ if (used.has(start)) continue;
346
+ const ring = [];
347
+ let edge = start;
348
+ let guard = directed2.length + 1;
349
+ while (edge && !used.has(edge) && guard-- > 0) {
350
+ used.add(edge);
351
+ ring.push(edge.from);
352
+ edge = next(edge);
353
+ }
354
+ if (ring.length >= 3) rings.push(ring);
355
+ }
356
+ return rings;
357
+ }
358
+ function removeCollinear(ring, eps) {
359
+ let points = ring;
360
+ let changed = true;
361
+ while (changed && points.length > 3) {
362
+ changed = false;
363
+ const out = [];
364
+ const n = points.length;
365
+ for (let i = 0; i < n; i++) {
366
+ const prev = out.length > 0 ? out[out.length - 1] : points[(i - 1 + n) % n];
367
+ const cur = points[i];
368
+ const nxt = points[(i + 1) % n];
369
+ const dx = nxt[0] - prev[0];
370
+ const dy = nxt[1] - prev[1];
371
+ const len = Math.hypot(dx, dy);
372
+ const cross2 = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;
373
+ const dot3 = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;
374
+ if (len > 0 && Math.abs(cross2) / len <= eps && dot3 > 0 && dot3 < len * len) {
375
+ changed = true;
376
+ continue;
377
+ }
378
+ out.push(cur);
379
+ }
380
+ if (out.length < 3) return out;
381
+ points = out;
382
+ }
383
+ return points;
384
+ }
385
+ function assemble(rings, eps) {
386
+ const outers = [];
387
+ const holes = [];
388
+ const minArea = eps * eps;
389
+ for (const vertices of rings) {
390
+ const ring = removeCollinear(
391
+ vertices.map((v) => [v.x, v.y]),
392
+ eps
393
+ );
394
+ if (ring.length < 3) continue;
395
+ const area = ringSignedArea(ring);
396
+ if (Math.abs(area) <= minArea) continue;
397
+ (area > 0 ? outers : holes).push({ ring, area: Math.abs(area) });
398
+ }
399
+ outers.sort((a, b) => b.area - a.area);
400
+ const polygons = outers.map((o) => [o.ring]);
401
+ for (const hole of holes) {
402
+ const [p, q] = [hole.ring[0], hole.ring[1]];
403
+ const sample = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
404
+ let owner = -1;
405
+ for (let i = outers.length - 1; i >= 0; i--) {
406
+ if (outers[i].area >= hole.area && pointInRings(sample, [outers[i].ring], "evenodd")) {
407
+ owner = i;
408
+ break;
409
+ }
410
+ }
411
+ if (owner >= 0) polygons[owner].push(hole.ring);
412
+ }
413
+ return polygons;
414
+ }
415
+ function extract(overlay, isInside) {
416
+ const directed2 = [];
417
+ overlay.edges.forEach((edge, i) => {
418
+ const l = isInside(insideFlags(overlay.left[i], overlay.fillRules));
419
+ const r = isInside(insideFlags(overlay.right[i], overlay.fillRules));
420
+ if (l === r) return;
421
+ const [from, to] = l ? [edge.a, edge.b] : [edge.b, edge.a];
422
+ directed2.push({ from, to, angle: Math.atan2(to.y - from.y, to.x - from.x) });
423
+ });
424
+ return assemble(traceRings(directed2), overlay.eps);
425
+ }
426
+ function predicate(operation) {
427
+ switch (operation) {
428
+ case "union":
429
+ return (f) => f.some(Boolean);
430
+ case "intersection":
431
+ return (f) => f.length > 0 && f.every(Boolean);
432
+ case "difference":
433
+ return (f) => f[0] === true && !f.slice(1).some(Boolean);
434
+ case "xor":
435
+ return (f) => f.filter(Boolean).length % 2 === 1;
436
+ }
437
+ }
438
+ function polygonBoolean(operation, operands, options = {}) {
439
+ if (operands.length === 0) return [];
440
+ return extract(buildOverlay(operands, options), predicate(operation));
441
+ }
442
+ var unionPolygons = (a, b, options) => polygonBoolean("union", [a, b], options);
443
+ var intersectPolygons = (a, b, options) => polygonBoolean("intersection", [a, b], options);
444
+ var subtractPolygons = (a, b, options) => polygonBoolean("difference", [a, b], options);
445
+ var xorPolygons = (a, b, options) => polygonBoolean("xor", [a, b], options);
446
+ function dividePolygons(operands, options = {}) {
447
+ if (operands.length === 0) return [];
448
+ const overlay = buildOverlay(operands, options);
449
+ const signatures = /* @__PURE__ */ new Map();
450
+ const note = (windings) => {
451
+ const flags = insideFlags(windings, overlay.fillRules);
452
+ if (!flags.some(Boolean)) return;
453
+ const key = flags.map((f) => f ? "1" : "0").join("");
454
+ if (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => f ? [k] : []));
455
+ };
456
+ overlay.edges.forEach((_, i) => {
457
+ note(overlay.left[i]);
458
+ note(overlay.right[i]);
459
+ });
460
+ const pieces = [];
461
+ const keys = [...signatures.keys()].sort();
462
+ for (const key of keys) {
463
+ const polygons = extract(overlay, (flags) => flags.map((f) => f ? "1" : "0").join("") === key);
464
+ polygons.sort((a, b) => polygonArea(b) - polygonArea(a));
465
+ for (const polygon of polygons) pieces.push({ polygon, operands: signatures.get(key) });
466
+ }
467
+ return pieces;
468
+ }
469
+
470
+ // src/canvas/curveFit.ts
471
+ function formatNumber(value, precision) {
472
+ const factor = 10 ** precision;
473
+ const rounded = Math.round(value * factor) / factor;
474
+ return Object.is(rounded, -0) ? "0" : String(rounded);
475
+ }
476
+ var MAX_REPARAM = 6;
477
+ var sub = (a, b) => [a[0] - b[0], a[1] - b[1]];
478
+ var add = (a, v, s = 1) => [a[0] + v[0] * s, a[1] + v[1] * s];
479
+ var dot = (a, b) => a[0] * b[0] + a[1] * b[1];
480
+ var dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
481
+ function unit(v) {
482
+ const l = Math.hypot(v[0], v[1]);
483
+ return l > 1e-12 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
484
+ }
485
+ function evalCubic(c, t) {
486
+ const s = 1 - t;
487
+ const b0 = s * s * s;
488
+ const b1 = 3 * s * s * t;
489
+ const b2 = 3 * s * t * t;
490
+ const b3 = t * t * t;
491
+ return [
492
+ b0 * c.p0[0] + b1 * c.p1[0] + b2 * c.p2[0] + b3 * c.p3[0],
493
+ b0 * c.p0[1] + b1 * c.p1[1] + b2 * c.p2[1] + b3 * c.p3[1]
494
+ ];
495
+ }
496
+ function cubicPointAt(c, t) {
497
+ return evalCubic(c, t);
498
+ }
499
+ function dedup(points) {
500
+ const out = [];
501
+ for (const p of points) {
502
+ if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
503
+ if (out.length === 0 || dist(out[out.length - 1], p) > 1e-12) out.push(p);
504
+ }
505
+ return out;
506
+ }
507
+ function chordParams(pts) {
508
+ const u = [0];
509
+ let acc = 0;
510
+ for (let i = 1; i < pts.length; i++) {
511
+ acc += dist(pts[i - 1], pts[i]);
512
+ u.push(acc);
513
+ }
514
+ return acc > 0 ? u.map((x) => x / acc) : u;
515
+ }
516
+ function generate(pts, u, t0, t1) {
517
+ const p0 = pts[0];
518
+ const p3 = pts[pts.length - 1];
519
+ const dirIn = [-t1[0], -t1[1]];
520
+ let c00 = 0;
521
+ let c01 = 0;
522
+ let c11 = 0;
523
+ let x0 = 0;
524
+ let x1 = 0;
525
+ for (let i = 0; i < pts.length; i++) {
526
+ const t = u[i];
527
+ const s = 1 - t;
528
+ const b0 = s * s * s;
529
+ const b1 = 3 * s * s * t;
530
+ const b2 = 3 * s * t * t;
531
+ const b3 = t * t * t;
532
+ const a0 = [t0[0] * b1, t0[1] * b1];
533
+ const a1 = [dirIn[0] * b2, dirIn[1] * b2];
534
+ c00 += dot(a0, a0);
535
+ c01 += dot(a0, a1);
536
+ c11 += dot(a1, a1);
537
+ const tmp = [
538
+ pts[i][0] - (p0[0] * (b0 + b1) + p3[0] * (b2 + b3)),
539
+ pts[i][1] - (p0[1] * (b0 + b1) + p3[1] * (b2 + b3))
540
+ ];
541
+ x0 += dot(a0, tmp);
542
+ x1 += dot(a1, tmp);
543
+ }
544
+ const det = c00 * c11 - c01 * c01;
545
+ const seg = dist(p0, p3);
546
+ const eps = 1e-6 * seg;
547
+ let al = seg / 3;
548
+ let ar = seg / 3;
549
+ if (Math.abs(det) > 1e-12) {
550
+ al = (x0 * c11 - x1 * c01) / det;
551
+ ar = (c00 * x1 - c01 * x0) / det;
552
+ }
553
+ if (!Number.isFinite(al) || !Number.isFinite(ar) || al < eps || ar < eps) {
554
+ al = seg / 3;
555
+ ar = seg / 3;
556
+ }
557
+ const cap = seg * 4 + 1e-9;
558
+ al = Math.min(al, cap);
559
+ ar = Math.min(ar, cap);
560
+ return { p0, p1: add(p0, t0, al), p2: add(p3, dirIn, ar), p3 };
561
+ }
562
+ function maxError(pts, c, u) {
563
+ let e = 0;
564
+ let idx = pts.length >> 1;
565
+ for (let i = 0; i < pts.length; i++) {
566
+ const d = dist(evalCubic(c, u[i]), pts[i]);
567
+ if (d > e) {
568
+ e = d;
569
+ idx = i;
570
+ }
571
+ }
572
+ return [e, idx];
573
+ }
574
+ function newton(c, p, t) {
575
+ const s = 1 - t;
576
+ const q = evalCubic(c, t);
577
+ const d1 = [
578
+ 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])),
579
+ 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]))
580
+ ];
581
+ const d2 = [
582
+ 6 * (s * (c.p2[0] - 2 * c.p1[0] + c.p0[0]) + t * (c.p3[0] - 2 * c.p2[0] + c.p1[0])),
583
+ 6 * (s * (c.p2[1] - 2 * c.p1[1] + c.p0[1]) + t * (c.p3[1] - 2 * c.p2[1] + c.p1[1]))
584
+ ];
585
+ const diff = [q[0] - p[0], q[1] - p[1]];
586
+ const num = dot(diff, d1);
587
+ const den = dot(d1, d1) + dot(diff, d2);
588
+ if (Math.abs(den) < 1e-12) return t;
589
+ const nt = t - num / den;
590
+ return Number.isFinite(nt) ? Math.min(1, Math.max(0, nt)) : t;
591
+ }
592
+ function fitSingleFrom(pts, u, t0, t1) {
593
+ let best = generate(pts, u, t0, t1);
594
+ let [bestErr, bestSplit] = maxError(pts, best, u);
595
+ const params = u.slice();
596
+ for (let round = 0; round < MAX_REPARAM; round++) {
597
+ for (let i = 0; i < params.length; i++) params[i] = newton(best, pts[i], params[i]);
598
+ const c = generate(pts, params, t0, t1);
599
+ const [e, s] = maxError(pts, c, params);
600
+ if (e < bestErr) {
601
+ best = c;
602
+ bestErr = e;
603
+ bestSplit = s;
604
+ } else break;
605
+ }
606
+ return [best, bestErr, bestSplit];
607
+ }
608
+ function fitRec(pts, t0, t1, tol, out, depth) {
609
+ const n = pts.length;
610
+ if (n === 2) {
611
+ const d = dist(pts[0], pts[1]) / 3;
612
+ out.push({ p0: pts[0], p1: add(pts[0], t0, d), p2: add(pts[1], t1, -d), p3: pts[1] });
613
+ return;
614
+ }
615
+ const [c, err, splitAt] = fitSingleFrom(pts, chordParams(pts), t0, t1);
616
+ if (err <= tol || depth > 40) {
617
+ out.push(c);
618
+ return;
619
+ }
620
+ const split = Math.min(n - 2, Math.max(1, splitAt));
621
+ const tc = unit(sub(pts[split + 1], pts[split - 1])) ?? t0;
622
+ fitRec(pts.slice(0, split + 1), t0, tc, tol, out, depth + 1);
623
+ fitRec(pts.slice(split), tc, t1, tol, out, depth + 1);
624
+ }
625
+ function tangentFrom(a, b, c) {
626
+ const chord = unit(sub(b, a));
627
+ if (!c) return chord;
628
+ const d1 = dist(a, b);
629
+ const d2 = dist(b, c);
630
+ if (!(d1 > 0) || !(d2 > 0)) return chord;
631
+ const ka = -(2 * d1 + d2) / (d1 * (d1 + d2));
632
+ const kb = (d1 + d2) / (d1 * d2);
633
+ const kc = -d1 / (d2 * (d1 + d2));
634
+ const est = unit([ka * a[0] + kb * b[0] + kc * c[0], ka * a[1] + kb * b[1] + kc * c[1]]);
635
+ return est && chord && dot(est, chord) > Math.cos(Math.PI / 4) ? est : chord;
636
+ }
637
+ function fitCubicRun(points, tolerance, t0, t1) {
638
+ const pts = dedup(points);
639
+ const out = [];
640
+ if (pts.length < 2) return out;
641
+ const n = pts.length;
642
+ const start = (t0 && unit([t0[0], t0[1]])) ?? tangentFrom(pts[0], pts[1], pts[2]) ?? [1, 0];
643
+ const back = tangentFrom(pts[n - 1], pts[n - 2], pts[n - 3]);
644
+ const end = (t1 && unit([t1[0], t1[1]])) ?? (back ? [-back[0], -back[1]] : [1, 0]);
645
+ fitRec(pts, start, end, Math.max(tolerance, 1e-9), out, 0);
646
+ return out;
647
+ }
648
+ function turnDeg(a, b, c) {
649
+ const u = unit(sub(b, a));
650
+ const v = unit(sub(c, b));
651
+ if (!u || !v) return 0;
652
+ return Math.acos(Math.min(1, Math.max(-1, dot(u, v)))) * 180 / Math.PI;
653
+ }
654
+ function findCorners(pts, closed, cornerAngle, span) {
655
+ const n = pts.length;
656
+ const at = (i) => pts[closed ? (i % n + n) % n : i];
657
+ const reach = (i, step) => {
658
+ for (let k = 1; k < (closed ? n : Infinity); k++) {
659
+ const j = i + step * k;
660
+ if (!closed && (j < 0 || j >= n)) return k > 1 ? j - step : null;
661
+ if (dist(at(j), pts[i]) >= span) return j;
662
+ }
663
+ return null;
664
+ };
665
+ const turns = new Float64Array(n);
666
+ const windows = [];
667
+ for (let i = 0; i < n; i++) {
668
+ windows.push([i, i]);
669
+ if (!closed && (i === 0 || i === n - 1)) continue;
670
+ const prev = reach(i, -1);
671
+ const next = reach(i, 1);
672
+ if (prev === null || next === null) continue;
673
+ windows[i] = [prev, next];
674
+ turns[i] = turnDeg(at(prev), pts[i], at(next));
675
+ }
676
+ const corners = [];
677
+ for (let i = 0; i < n; i++) {
678
+ if (!(turns[i] > cornerAngle)) continue;
679
+ const [lo, hi] = windows[i];
680
+ let sharpest = true;
681
+ for (let j = lo; j <= hi && sharpest; j++) {
682
+ const k = closed ? (j % n + n) % n : j;
683
+ if (k === i || dist(pts[k], pts[i]) >= span) continue;
684
+ if (turns[k] > turns[i] || turns[k] === turns[i] && k < i) sharpest = false;
685
+ }
686
+ if (sharpest) corners.push(i);
687
+ }
688
+ return corners;
689
+ }
690
+ function densify(run) {
691
+ if (run.length < 3) return run;
692
+ const lengths = [];
693
+ for (let i = 1; i < run.length; i++) lengths.push(dist(run[i - 1], run[i]));
694
+ const sorted = [...lengths].sort((a, b) => a - b);
695
+ const median = sorted[sorted.length >> 1];
696
+ if (!(median > 0)) return run;
697
+ const out = [run[0]];
698
+ for (let i = 1; i < run.length; i++) {
699
+ const len = lengths[i - 1];
700
+ if (len > 2 * median) {
701
+ const steps = Math.min(64, Math.ceil(len / median));
702
+ const a = run[i - 1];
703
+ const b = run[i];
704
+ for (let k = 1; k < steps; k++) {
705
+ const t = k / steps;
706
+ out.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
707
+ }
708
+ }
709
+ out.push(run[i]);
710
+ }
711
+ return out;
712
+ }
713
+ function fitCubics(points, tolerance, options = {}) {
714
+ let pts = dedup(points);
715
+ const closed = options.closed === true;
716
+ if (closed && pts.length > 1 && dist(pts[0], pts[pts.length - 1]) <= 1e-12) pts = pts.slice(0, -1);
717
+ if (pts.length < 2) return [];
718
+ const tol = Math.max(tolerance, 1e-6);
719
+ const cornerAngle = options.cornerAngleDeg ?? 30;
720
+ const n = pts.length;
721
+ const span = 4 * tol;
722
+ if (!closed || n < 3) {
723
+ const corners2 = [0, ...n > 2 ? findCorners(pts, false, cornerAngle, span) : [], n - 1];
724
+ const out2 = [];
725
+ for (let k = 1; k < corners2.length; k++) {
726
+ const run = pts.slice(corners2[k - 1], corners2[k] + 1);
727
+ out2.push(...fitCubicRun(densify(run), tol));
728
+ }
729
+ if (closed && n === 2) out2.push(...fitCubicRun([pts[1], pts[0]], tol));
730
+ return out2;
731
+ }
732
+ const corners = findCorners(pts, true, cornerAngle, span);
733
+ if (corners.length === 0) {
734
+ const ring = [...pts, pts[0]];
735
+ const t = unit(sub(pts[1], pts[n - 1])) ?? [1, 0];
736
+ return fitCubicRun(densify(ring), tol, t, t);
737
+ }
738
+ const out = [];
739
+ for (let k = 0; k < corners.length; k++) {
740
+ const from = corners[k];
741
+ const to = corners[(k + 1) % corners.length];
742
+ const run = [];
743
+ for (let i = from; ; i = (i + 1) % n) {
744
+ run.push(pts[i]);
745
+ if (run.length > 1 && i === to) break;
746
+ }
747
+ out.push(...fitCubicRun(densify(run), tol));
748
+ }
749
+ return out;
750
+ }
751
+ function isStraightCubic(c, tol) {
752
+ const chord = sub(c.p3, c.p0);
753
+ const len = Math.hypot(chord[0], chord[1]);
754
+ if (len < 1e-12) return dist(c.p1, c.p0) <= tol && dist(c.p2, c.p0) <= tol;
755
+ const dir = [chord[0] / len, chord[1] / len];
756
+ return [c.p1, c.p2].every((h) => {
757
+ const v = sub(h, c.p0);
758
+ const along = dot(v, dir);
759
+ const across = Math.abs(v[0] * dir[1] - v[1] * dir[0]);
760
+ return across <= tol && along >= -tol && along <= len + tol;
761
+ });
762
+ }
763
+ function cubicsToPathData(segments, options = {}) {
764
+ if (segments.length === 0) return "";
765
+ const f = (n) => formatNumber(n, options.precision ?? 3);
766
+ const dx = options.offset?.x ?? 0;
767
+ const dy = options.offset?.y ?? 0;
768
+ const pt = (p) => `${f(p[0] + dx)} ${f(p[1] + dy)}`;
769
+ const straight = options.straightTolerance ?? 1e-3;
770
+ let d = `M${pt(segments[0].p0)}`;
771
+ segments.forEach((c, i) => {
772
+ const last = options.closed && i === segments.length - 1;
773
+ if (isStraightCubic(c, straight)) {
774
+ if (!last) d += `L${pt(c.p3)}`;
775
+ } else d += `C${pt(c.p1)} ${pt(c.p2)} ${pt(c.p3)}`;
776
+ });
777
+ return options.closed ? `${d}Z` : d;
778
+ }
779
+ function polylineToPathData(points, closed, options) {
780
+ if (points.length < 2) return "";
781
+ const f = (n) => formatNumber(n, options.precision ?? 3);
782
+ const dx = options.offset?.x ?? 0;
783
+ const dy = options.offset?.y ?? 0;
784
+ let d = `M${f(points[0][0] + dx)} ${f(points[0][1] + dy)}`;
785
+ for (let i = 1; i < points.length; i++) d += `L${f(points[i][0] + dx)} ${f(points[i][1] + dy)}`;
786
+ return closed ? `${d}Z` : d;
787
+ }
788
+ function fitPathData(ring, options = {}) {
789
+ const closed = options.closed ?? true;
790
+ const tolerance = options.tolerance ?? 0.5;
791
+ const pts = dedup(ring);
792
+ const segments = fitCubics(pts, tolerance, { ...options, closed });
793
+ if (segments.length === 0) return "";
794
+ const edges = closed ? pts.length : pts.length - 1;
795
+ if (options.noWorse !== false) {
796
+ const straight = segments.filter((c) => isStraightCubic(c, tolerance * 0.02)).length;
797
+ if (segments.length >= edges && straight === segments.length) return polylineToPathData(pts, closed, options);
798
+ if (segments.length > edges) return polylineToPathData(pts, closed, options);
799
+ }
800
+ return cubicsToPathData(segments, {
801
+ precision: options.precision,
802
+ offset: options.offset,
803
+ closed,
804
+ straightTolerance: tolerance * 0.02
805
+ });
806
+ }
807
+ function fitFreehandPath(points, options = {}) {
808
+ return fitPathData(points, { tolerance: 1, ...options, closed: false });
809
+ }
810
+
811
+ // src/canvas/gradient.ts
812
+ var CANVAS_GRADIENT_PRESETS = {
813
+ sunset: {
814
+ type: "linear",
815
+ angle: 90,
816
+ stops: [
817
+ { offset: 0, color: "#f97316" },
818
+ { offset: 1, color: "#ec4899" }
819
+ ]
820
+ },
821
+ ocean: {
822
+ type: "linear",
823
+ angle: 90,
824
+ stops: [
825
+ { offset: 0, color: "#06b6d4" },
826
+ { offset: 1, color: "#3b82f6" }
827
+ ]
828
+ },
829
+ aurora: {
830
+ type: "linear",
831
+ angle: 90,
832
+ stops: [
833
+ { offset: 0, color: "#10b981" },
834
+ { offset: 1, color: "#6366f1" }
835
+ ]
836
+ },
837
+ neon: {
838
+ type: "linear",
839
+ angle: 90,
840
+ stops: [
841
+ { offset: 0, color: "#a855f7" },
842
+ { offset: 1, color: "#ec4899" }
843
+ ]
844
+ },
845
+ amber: {
846
+ type: "linear",
847
+ angle: 90,
848
+ stops: [
849
+ { offset: 0, color: "#f59e0b" },
850
+ { offset: 1, color: "#ef4444" }
851
+ ]
852
+ },
853
+ cosmic: {
854
+ type: "linear",
855
+ angle: 90,
856
+ stops: [
857
+ { offset: 0, color: "#3b82f6" },
858
+ { offset: 0.5, color: "#8b5cf6" },
859
+ { offset: 1, color: "#ec4899" }
860
+ ]
861
+ }
862
+ };
863
+ function isCanvasGradient(value) {
864
+ if (!value || typeof value !== "object") return false;
865
+ const val = value;
866
+ if (val.type !== "linear" && val.type !== "radial") return false;
867
+ if (!Array.isArray(val.stops)) return false;
868
+ return true;
869
+ }
870
+ function splitCssArgs(str) {
871
+ const parts = [];
872
+ let depth = 0;
873
+ let current = "";
874
+ for (let i = 0; i < str.length; i++) {
875
+ const char = str[i];
876
+ if (char === "(") depth++;
877
+ else if (char === ")") depth = Math.max(0, depth - 1);
878
+ if (char === "," && depth === 0) {
879
+ if (current.trim()) parts.push(current.trim());
880
+ current = "";
881
+ } else {
882
+ current += char;
883
+ }
884
+ }
885
+ if (current.trim()) parts.push(current.trim());
886
+ return parts;
887
+ }
888
+ function parseCssAngle(val) {
889
+ const trimmed = val.trim().toLowerCase();
890
+ if (trimmed.endsWith("deg")) {
891
+ return parseFloat(trimmed);
892
+ }
893
+ if (trimmed.endsWith("turn")) {
894
+ return parseFloat(trimmed) * 360;
895
+ }
896
+ if (trimmed.endsWith("rad")) {
897
+ return parseFloat(trimmed) * 180 / Math.PI;
898
+ }
899
+ if (trimmed.startsWith("to ")) {
900
+ const dir = trimmed.slice(3).trim();
901
+ if (dir === "bottom") return 180;
902
+ if (dir === "top") return 0;
903
+ if (dir === "right") return 90;
904
+ if (dir === "left") return 270;
905
+ if (dir === "bottom right" || dir === "right bottom") return 135;
906
+ if (dir === "bottom left" || dir === "left bottom") return 225;
907
+ if (dir === "top right" || dir === "right top") return 45;
908
+ if (dir === "top left" || dir === "left top") return 315;
909
+ }
910
+ const n = parseFloat(trimmed);
911
+ return Number.isFinite(n) ? n : 180;
912
+ }
913
+ function parseCssGradient(css) {
914
+ const trimmed = css.trim();
915
+ const linearMatch = trimmed.match(/^linear-gradient\s*\((.*)\)$/is);
916
+ if (linearMatch) {
917
+ const rawArgs = splitCssArgs(linearMatch[1]);
918
+ if (rawArgs.length === 0) return null;
919
+ let angle = 180;
920
+ let stopStartIndex = 0;
921
+ const first = rawArgs[0].trim().toLowerCase();
922
+ if (first.endsWith("deg") || first.endsWith("turn") || first.endsWith("rad") || first.startsWith("to ")) {
923
+ angle = parseCssAngle(first);
924
+ stopStartIndex = 1;
925
+ }
926
+ const stopStrings = rawArgs.slice(stopStartIndex);
927
+ if (stopStrings.length === 0) return null;
928
+ const stops = parseStopStrings(stopStrings);
929
+ if (stops.length === 0) return null;
930
+ return {
931
+ type: "linear",
932
+ angle,
933
+ stops
934
+ };
935
+ }
936
+ const radialMatch = trimmed.match(/^radial-gradient\s*\((.*)\)$/is);
937
+ if (radialMatch) {
938
+ const rawArgs = splitCssArgs(radialMatch[1]);
939
+ if (rawArgs.length === 0) return null;
940
+ let stopStartIndex = 0;
941
+ const first = rawArgs[0].trim().toLowerCase();
942
+ if (first.startsWith("circle") || first.startsWith("ellipse") || first.includes("at ")) {
943
+ stopStartIndex = 1;
944
+ }
945
+ const stopStrings = rawArgs.slice(stopStartIndex);
946
+ if (stopStrings.length === 0) return null;
947
+ const stops = parseStopStrings(stopStrings);
948
+ if (stops.length === 0) return null;
949
+ return {
950
+ type: "radial",
951
+ cx: 0.5,
952
+ cy: 0.5,
953
+ r: 0.5,
954
+ stops
955
+ };
956
+ }
957
+ return null;
958
+ }
959
+ function parseStopStrings(stopStrings) {
960
+ const count = stopStrings.length;
961
+ return stopStrings.map((str, idx) => {
962
+ const parts = str.trim().split(/\s+(?=[0-9%])/);
963
+ const color = parts[0];
964
+ let offset = count === 1 ? 0 : idx / (count - 1);
965
+ if (parts.length > 1) {
966
+ const posStr = parts[1].trim();
967
+ if (posStr.endsWith("%")) {
968
+ offset = parseFloat(posStr) / 100;
969
+ } else {
970
+ const val = parseFloat(posStr);
971
+ if (Number.isFinite(val)) offset = val;
972
+ }
973
+ }
974
+ return {
975
+ offset: Math.min(1, Math.max(0, offset)),
976
+ color
977
+ };
978
+ });
979
+ }
980
+ function resolveCanvasGradient(value) {
981
+ if (!value) return null;
982
+ if (isCanvasGradient(value)) return value;
983
+ if (typeof value === "string") {
984
+ const lower = value.trim().toLowerCase();
985
+ if (CANVAS_GRADIENT_PRESETS[lower]) {
986
+ return CANVAS_GRADIENT_PRESETS[lower];
987
+ }
988
+ if (lower.startsWith("linear-gradient") || lower.startsWith("radial-gradient")) {
989
+ return parseCssGradient(value);
990
+ }
991
+ }
992
+ return null;
993
+ }
994
+ function angleToSvgLinearCoords(angleDeg = 180) {
995
+ const rad = (90 - angleDeg) * Math.PI / 180;
996
+ const dx = Math.cos(rad);
997
+ const dy = -Math.sin(rad);
998
+ const half = 0.5;
999
+ return {
1000
+ x1: Math.round((half - dx * half) * 1e3) / 1e3,
1001
+ y1: Math.round((half - dy * half) * 1e3) / 1e3,
1002
+ x2: Math.round((half + dx * half) * 1e3) / 1e3,
1003
+ y2: Math.round((half + dy * half) * 1e3) / 1e3
1004
+ };
1005
+ }
1006
+ function gradientToSvgDef(gradient, id) {
1007
+ const stops = (gradient.stops ?? []).map((s) => ({
1008
+ offset: `${Math.round(s.offset * 1e3) / 10}%`,
1009
+ stopColor: s.color,
1010
+ stopOpacity: s.opacity
1011
+ }));
1012
+ if (gradient.type === "radial") {
1013
+ return {
1014
+ id,
1015
+ type: "radial",
1016
+ attrs: {
1017
+ cx: gradient.cx !== void 0 ? `${gradient.cx * 100}%` : "50%",
1018
+ cy: gradient.cy !== void 0 ? `${gradient.cy * 100}%` : "50%",
1019
+ r: gradient.r !== void 0 ? `${gradient.r * 100}%` : "50%"
1020
+ },
1021
+ stops
1022
+ };
1023
+ }
1024
+ const coords = angleToSvgLinearCoords(gradient.angle ?? 180);
1025
+ return {
1026
+ id,
1027
+ type: "linear",
1028
+ attrs: {
1029
+ x1: `${coords.x1 * 100}%`,
1030
+ y1: `${coords.y1 * 100}%`,
1031
+ x2: `${coords.x2 * 100}%`,
1032
+ y2: `${coords.y2 * 100}%`
1033
+ },
1034
+ stops
1035
+ };
1036
+ }
1037
+
1038
+ // src/canvas/shapeGeometry.ts
1039
+ var SHAPE_KINDS = ["rect", "ellipse", "polygon", "star", "path"];
1040
+ function isShapeDescription(value) {
1041
+ if (!value || typeof value !== "object") return false;
1042
+ const kind = value.kind;
1043
+ if (!SHAPE_KINDS.includes(kind)) return false;
1044
+ return kind !== "path" || typeof value.d === "string";
1045
+ }
1046
+ var PARAM_COUNTS = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0 };
1047
+ var PathScanner = class {
1048
+ constructor(text) {
1049
+ this.text = text;
1050
+ }
1051
+ text;
1052
+ pos = 0;
1053
+ skip() {
1054
+ while (this.pos < this.text.length && /[\s,]/.test(this.text[this.pos])) this.pos++;
1055
+ }
1056
+ /** The next command letter, or `null` when a number (or the end) follows. */
1057
+ command() {
1058
+ this.skip();
1059
+ const ch = this.text[this.pos];
1060
+ if (ch && /[MmLlHhVvCcSsQqTtAaZz]/.test(ch)) {
1061
+ this.pos++;
1062
+ return ch;
1063
+ }
1064
+ return null;
1065
+ }
1066
+ done() {
1067
+ this.skip();
1068
+ return this.pos >= this.text.length;
1069
+ }
1070
+ number() {
1071
+ this.skip();
1072
+ const match = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(this.text.slice(this.pos, this.pos + 64));
1073
+ if (!match) return null;
1074
+ this.pos += match[0].length;
1075
+ const value = Number(match[0]);
1076
+ return Number.isFinite(value) ? value : null;
1077
+ }
1078
+ flag() {
1079
+ this.skip();
1080
+ const ch = this.text[this.pos];
1081
+ if (ch === "0" || ch === "1") {
1082
+ this.pos++;
1083
+ return ch === "1" ? 1 : 0;
1084
+ }
1085
+ return null;
1086
+ }
1087
+ };
1088
+ function parseSvgPath(d) {
1089
+ const scanner = new PathScanner(d ?? "");
1090
+ const commands = [];
1091
+ let cx = 0;
1092
+ let cy = 0;
1093
+ let startX = 0;
1094
+ let startY = 0;
1095
+ let lastControl = null;
1096
+ let command = "";
1097
+ while (!scanner.done()) {
1098
+ const letter = scanner.command();
1099
+ if (letter) {
1100
+ command = letter;
1101
+ if (command === "z" || command === "Z") {
1102
+ commands.push({ type: "Z" });
1103
+ cx = startX;
1104
+ cy = startY;
1105
+ lastControl = null;
1106
+ continue;
1107
+ }
1108
+ } else if (!command || command === "z" || command === "Z") {
1109
+ break;
1110
+ }
1111
+ const lower = command.toLowerCase();
1112
+ const count = PARAM_COUNTS[lower];
1113
+ const args = [];
1114
+ for (let k = 0; k < count; k++) {
1115
+ const value = lower === "a" && (k === 3 || k === 4) ? scanner.flag() : scanner.number();
1116
+ if (value === null) return commands;
1117
+ args.push(value);
1118
+ }
1119
+ const rel = command !== command.toUpperCase();
1120
+ const ox = rel ? cx : 0;
1121
+ const oy = rel ? cy : 0;
1122
+ switch (lower) {
1123
+ case "m": {
1124
+ cx = args[0] + ox;
1125
+ cy = args[1] + oy;
1126
+ startX = cx;
1127
+ startY = cy;
1128
+ commands.push({ type: "M", x: cx, y: cy });
1129
+ lastControl = null;
1130
+ command = rel ? "l" : "L";
1131
+ break;
1132
+ }
1133
+ case "l":
1134
+ cx = args[0] + ox;
1135
+ cy = args[1] + oy;
1136
+ commands.push({ type: "L", x: cx, y: cy });
1137
+ lastControl = null;
1138
+ break;
1139
+ case "h":
1140
+ cx = args[0] + (rel ? cx : 0);
1141
+ commands.push({ type: "L", x: cx, y: cy });
1142
+ lastControl = null;
1143
+ break;
1144
+ case "v":
1145
+ cy = args[0] + (rel ? cy : 0);
1146
+ commands.push({ type: "L", x: cx, y: cy });
1147
+ lastControl = null;
1148
+ break;
1149
+ case "c": {
1150
+ const c = {
1151
+ type: "C",
1152
+ x1: args[0] + ox,
1153
+ y1: args[1] + oy,
1154
+ x2: args[2] + ox,
1155
+ y2: args[3] + oy,
1156
+ x: args[4] + ox,
1157
+ y: args[5] + oy
1158
+ };
1159
+ commands.push(c);
1160
+ lastControl = { x: c.x2, y: c.y2, type: "C" };
1161
+ cx = c.x;
1162
+ cy = c.y;
1163
+ break;
1164
+ }
1165
+ case "s": {
1166
+ const x1 = lastControl?.type === "C" ? 2 * cx - lastControl.x : cx;
1167
+ const y1 = lastControl?.type === "C" ? 2 * cy - lastControl.y : cy;
1168
+ const c = {
1169
+ type: "C",
1170
+ x1,
1171
+ y1,
1172
+ x2: args[0] + ox,
1173
+ y2: args[1] + oy,
1174
+ x: args[2] + ox,
1175
+ y: args[3] + oy
1176
+ };
1177
+ commands.push(c);
1178
+ lastControl = { x: c.x2, y: c.y2, type: "C" };
1179
+ cx = c.x;
1180
+ cy = c.y;
1181
+ break;
1182
+ }
1183
+ case "q": {
1184
+ const c = { type: "Q", x1: args[0] + ox, y1: args[1] + oy, x: args[2] + ox, y: args[3] + oy };
1185
+ commands.push(c);
1186
+ lastControl = { x: c.x1, y: c.y1, type: "Q" };
1187
+ cx = c.x;
1188
+ cy = c.y;
1189
+ break;
1190
+ }
1191
+ case "t": {
1192
+ const x1 = lastControl?.type === "Q" ? 2 * cx - lastControl.x : cx;
1193
+ const y1 = lastControl?.type === "Q" ? 2 * cy - lastControl.y : cy;
1194
+ const c = { type: "Q", x1, y1, x: args[0] + ox, y: args[1] + oy };
1195
+ commands.push(c);
1196
+ lastControl = { x: x1, y: y1, type: "Q" };
1197
+ cx = c.x;
1198
+ cy = c.y;
1199
+ break;
1200
+ }
1201
+ case "a": {
1202
+ const c = {
1203
+ type: "A",
1204
+ rx: Math.abs(args[0]),
1205
+ ry: Math.abs(args[1]),
1206
+ rotation: args[2],
1207
+ largeArc: args[3] !== 0,
1208
+ sweep: args[4] !== 0,
1209
+ x: args[5] + ox,
1210
+ y: args[6] + oy
1211
+ };
1212
+ commands.push(c);
1213
+ lastControl = null;
1214
+ cx = c.x;
1215
+ cy = c.y;
1216
+ break;
1217
+ }
1218
+ }
1219
+ }
1220
+ return commands;
1221
+ }
1222
+ function segmentCount(curvature, tolerance, max) {
1223
+ if (!(curvature > 0)) return 1;
1224
+ return Math.max(1, Math.min(max, Math.ceil(Math.sqrt(curvature / tolerance))));
1225
+ }
1226
+ function flattenCubic(out, p0, x1, y1, x2, y2, x, y, tolerance, max) {
1227
+ const dd = Math.max(
1228
+ Math.hypot(p0[0] - 2 * x1 + x2, p0[1] - 2 * y1 + y2),
1229
+ Math.hypot(x1 - 2 * x2 + x, y1 - 2 * y2 + y)
1230
+ );
1231
+ const n = segmentCount(6 * dd / 8, tolerance, max);
1232
+ for (let k = 1; k <= n; k++) {
1233
+ const t = k / n;
1234
+ const mt = 1 - t;
1235
+ const a = mt * mt * mt;
1236
+ const b = 3 * mt * mt * t;
1237
+ const c = 3 * mt * t * t;
1238
+ const e = t * t * t;
1239
+ out.push([a * p0[0] + b * x1 + c * x2 + e * x, a * p0[1] + b * y1 + c * y2 + e * y]);
1240
+ }
1241
+ }
1242
+ function flattenQuadratic(out, p0, x1, y1, x, y, tolerance, max) {
1243
+ const dd = Math.hypot(p0[0] - 2 * x1 + x, p0[1] - 2 * y1 + y);
1244
+ const n = segmentCount(2 * dd / 8, tolerance, max);
1245
+ for (let k = 1; k <= n; k++) {
1246
+ const t = k / n;
1247
+ const mt = 1 - t;
1248
+ out.push([mt * mt * p0[0] + 2 * mt * t * x1 + t * t * x, mt * mt * p0[1] + 2 * mt * t * y1 + t * t * y]);
1249
+ }
1250
+ }
1251
+ function arcSteps(radius, sweep, tolerance, max) {
1252
+ if (radius <= tolerance) return Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2)));
1253
+ const step = 2 * Math.acos(1 - tolerance / radius);
1254
+ return Math.max(1, Math.min(max, Math.ceil(Math.abs(sweep) / step)));
1255
+ }
1256
+ function flattenArc(out, p0, cmd, tolerance, max) {
1257
+ const [x0, y0] = p0;
1258
+ let { rx, ry } = cmd;
1259
+ const { x, y } = cmd;
1260
+ if (x0 === x && y0 === y) return;
1261
+ if (rx === 0 || ry === 0) {
1262
+ out.push([x, y]);
1263
+ return;
1264
+ }
1265
+ const phi = cmd.rotation * Math.PI / 180;
1266
+ const cos = Math.cos(phi);
1267
+ const sin = Math.sin(phi);
1268
+ const dx = (x0 - x) / 2;
1269
+ const dy = (y0 - y) / 2;
1270
+ const x1p = cos * dx + sin * dy;
1271
+ const y1p = -sin * dx + cos * dy;
1272
+ const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
1273
+ if (lambda > 1) {
1274
+ const s = Math.sqrt(lambda);
1275
+ rx *= s;
1276
+ ry *= s;
1277
+ }
1278
+ const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
1279
+ const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
1280
+ let coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));
1281
+ if (cmd.largeArc === cmd.sweep) coef = -coef;
1282
+ const cxp = coef * rx * y1p / ry;
1283
+ const cyp = -coef * ry * x1p / rx;
1284
+ const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
1285
+ const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
1286
+ const angle = (ux, uy, vx, vy) => {
1287
+ const a = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
1288
+ return a;
1289
+ };
1290
+ const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
1291
+ let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
1292
+ if (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;
1293
+ else if (cmd.sweep && delta < 0) delta += 2 * Math.PI;
1294
+ const n = arcSteps(Math.max(rx, ry), delta, tolerance, max);
1295
+ for (let k = 1; k < n; k++) {
1296
+ const t = theta1 + delta * k / n;
1297
+ const ex = rx * Math.cos(t);
1298
+ const ey = ry * Math.sin(t);
1299
+ out.push([cos * ex - sin * ey + cx, sin * ex + cos * ey + cy]);
1300
+ }
1301
+ out.push([x, y]);
1302
+ }
1303
+ function flattenSvgPath(d, options = {}) {
1304
+ const tolerance = options.tolerance && options.tolerance > 0 ? options.tolerance : 0.25;
1305
+ const max = options.maxSegments ?? 512;
1306
+ const commands = typeof d === "string" ? parseSvgPath(d) : d;
1307
+ const rings = [];
1308
+ let current = [];
1309
+ let start = [0, 0];
1310
+ const flush = () => {
1311
+ if (current.length >= 3) rings.push(current);
1312
+ current = [];
1313
+ };
1314
+ const last = () => current[current.length - 1] ?? start;
1315
+ for (const cmd of commands) {
1316
+ switch (cmd.type) {
1317
+ case "M":
1318
+ flush();
1319
+ start = [cmd.x, cmd.y];
1320
+ current = [start];
1321
+ break;
1322
+ case "L":
1323
+ if (current.length === 0) current = [start];
1324
+ current.push([cmd.x, cmd.y]);
1325
+ break;
1326
+ case "C":
1327
+ if (current.length === 0) current = [start];
1328
+ flattenCubic(current, last(), cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y, tolerance, max);
1329
+ break;
1330
+ case "Q":
1331
+ if (current.length === 0) current = [start];
1332
+ flattenQuadratic(current, last(), cmd.x1, cmd.y1, cmd.x, cmd.y, tolerance, max);
1333
+ break;
1334
+ case "A":
1335
+ if (current.length === 0) current = [start];
1336
+ flattenArc(current, last(), cmd, tolerance, max);
1337
+ break;
1338
+ case "Z":
1339
+ flush();
1340
+ break;
1341
+ }
1342
+ }
1343
+ flush();
1344
+ return rings.map(dedupeRing).filter((r) => r.length >= 3);
1345
+ }
1346
+ function dedupeRing(ring) {
1347
+ const out = [];
1348
+ for (const p of ring) {
1349
+ const prev = out[out.length - 1];
1350
+ if (!prev || prev[0] !== p[0] || prev[1] !== p[1]) out.push(p);
1351
+ }
1352
+ while (out.length > 1 && out[0][0] === out[out.length - 1][0] && out[0][1] === out[out.length - 1][1]) out.pop();
1353
+ return out;
1354
+ }
1355
+ function cornerRadii(shape, width, height) {
1356
+ const raw = shape.cornerRadius ?? 0;
1357
+ let radii = (typeof raw === "number" ? [raw, raw, raw, raw] : [...raw]).map(
1358
+ (r) => Number.isFinite(r) && r > 0 ? r : 0
1359
+ );
1360
+ const factor = Math.min(
1361
+ 1,
1362
+ width / Math.max(radii[0] + radii[1], 1e-12),
1363
+ height / Math.max(radii[1] + radii[2], 1e-12),
1364
+ width / Math.max(radii[2] + radii[3], 1e-12),
1365
+ height / Math.max(radii[3] + radii[0], 1e-12)
1366
+ );
1367
+ if (factor < 1) radii = radii.map((r) => r * factor);
1368
+ return radii;
1369
+ }
1370
+ function polygonVertices(count, inner, width, height) {
1371
+ const raw = [];
1372
+ const total = inner === null ? count : count * 2;
1373
+ for (let k = 0; k < total; k++) {
1374
+ const angle = -Math.PI / 2 + k * 2 * Math.PI / total;
1375
+ const r = inner !== null && k % 2 === 1 ? inner : 1;
1376
+ raw.push([r * Math.cos(angle), r * Math.sin(angle)]);
1377
+ }
1378
+ let minX = Infinity;
1379
+ let minY = Infinity;
1380
+ let maxX = -Infinity;
1381
+ let maxY = -Infinity;
1382
+ for (const [x, y] of raw) {
1383
+ minX = Math.min(minX, x);
1384
+ minY = Math.min(minY, y);
1385
+ maxX = Math.max(maxX, x);
1386
+ maxY = Math.max(maxY, y);
1387
+ }
1388
+ const sx = width / (maxX - minX || 1);
1389
+ const sy = height / (maxY - minY || 1);
1390
+ return raw.map(([x, y]) => [(x - minX) * sx, (y - minY) * sy]);
1391
+ }
1392
+ function rotationTransform(shape, size) {
1393
+ const deg = shape.rotation ?? 0;
1394
+ if (!deg || !Number.isFinite(deg)) return null;
1395
+ const rad = deg * Math.PI / 180;
1396
+ const cos = Math.cos(rad);
1397
+ const sin = Math.sin(rad);
1398
+ const cx = size.width / 2;
1399
+ const cy = size.height / 2;
1400
+ return ([x, y]) => [cx + (x - cx) * cos - (y - cy) * sin, cy + (x - cx) * sin + (y - cy) * cos];
1401
+ }
1402
+ function getShapeSvg(shape, size) {
1403
+ const { width: w, height: h } = size;
1404
+ const f = (n) => formatNumber2(n, 3);
1405
+ const transforms = [];
1406
+ if (shape.rotation) transforms.push(`rotate(${f(shape.rotation)} ${f(w / 2)} ${f(h / 2)})`);
1407
+ let d = "";
1408
+ let fillRule = "nonzero";
1409
+ switch (shape.kind) {
1410
+ case "rect": {
1411
+ const [tl, tr, br, bl] = cornerRadii(shape, w, h);
1412
+ if (tl + tr + br + bl === 0) {
1413
+ d = `M0 0H${f(w)}V${f(h)}H0Z`;
1414
+ } else {
1415
+ const arc = (r, x, y) => r > 0 ? `A${f(r)} ${f(r)} 0 0 1 ${f(x)} ${f(y)}` : "";
1416
+ d = `M${f(tl)} 0H${f(w - tr)}${arc(tr, w, tr)}V${f(h - br)}${arc(br, w - br, h)}H${f(bl)}${arc(bl, 0, h - bl)}V${f(tl)}${arc(tl, tl, 0)}Z`;
1417
+ }
1418
+ break;
1419
+ }
1420
+ case "ellipse": {
1421
+ const rx = w / 2;
1422
+ const ry = h / 2;
1423
+ d = `M0 ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 ${f(w)} ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 0 ${f(ry)}Z`;
1424
+ break;
1425
+ }
1426
+ case "polygon":
1427
+ case "star": {
1428
+ const points = shape.kind === "polygon" ? polygonVertices(Math.max(3, Math.floor(shape.sides ?? 6)), null, w, h) : polygonVertices(Math.max(3, Math.floor(shape.points ?? 5)), clampRatio(shape.innerRadius), w, h);
1429
+ d = ringsToPath([points], f);
1430
+ break;
1431
+ }
1432
+ case "path": {
1433
+ d = shape.d;
1434
+ fillRule = shape.fillRule ?? "nonzero";
1435
+ const vb = validViewBox(shape.viewBox);
1436
+ if (vb) {
1437
+ transforms.push(
1438
+ `scale(${f(w / vb[2])} ${f(h / vb[3])})`,
1439
+ `translate(${f(-vb[0])} ${f(-vb[1])})`
1440
+ );
1441
+ }
1442
+ break;
1443
+ }
1444
+ }
1445
+ return transforms.length > 0 ? { d, transform: transforms.join(" "), fillRule } : { d, fillRule };
1446
+ }
1447
+ function getShapeRenderData(shape, size = {}, options = {}) {
1448
+ const vb = shape.kind === "path" ? validViewBox(shape.viewBox) : null;
1449
+ const width = size.width && size.width > 0 ? size.width : vb ? vb[2] : 100;
1450
+ const height = size.height && size.height > 0 ? size.height : vb ? vb[3] : 100;
1451
+ const svg = getShapeSvg(shape, { width, height });
1452
+ const baseId = options.id || Math.random().toString(36).slice(2, 9);
1453
+ let fillStr = "currentColor";
1454
+ let fillGradientDef;
1455
+ const rawFill = shape.fillGradient ?? shape.fill;
1456
+ const resolvedFillGradient = resolveCanvasGradient(rawFill);
1457
+ if (resolvedFillGradient) {
1458
+ const gradId = `shape-fill-${baseId}`;
1459
+ fillGradientDef = gradientToSvgDef(resolvedFillGradient, gradId);
1460
+ fillStr = `url(#${gradId})`;
1461
+ } else if (typeof shape.fill === "string") {
1462
+ fillStr = shape.fill;
1463
+ } else if (shape.fill === void 0) {
1464
+ fillStr = "currentColor";
1465
+ }
1466
+ let strokeStr = "none";
1467
+ let strokeGradientDef;
1468
+ const rawStroke = shape.strokeGradient ?? shape.stroke;
1469
+ const resolvedStrokeGradient = resolveCanvasGradient(rawStroke);
1470
+ if (resolvedStrokeGradient) {
1471
+ const gradId = `shape-stroke-${baseId}`;
1472
+ strokeGradientDef = gradientToSvgDef(resolvedStrokeGradient, gradId);
1473
+ strokeStr = `url(#${gradId})`;
1474
+ } else if (typeof shape.stroke === "string") {
1475
+ strokeStr = shape.stroke;
1476
+ } else if (shape.stroke === void 0) {
1477
+ strokeStr = "none";
1478
+ }
1479
+ const data = {
1480
+ viewBox: `0 0 ${formatNumber2(width, 3)} ${formatNumber2(height, 3)}`,
1481
+ width,
1482
+ height,
1483
+ d: svg.d,
1484
+ fillRule: svg.fillRule,
1485
+ fill: fillStr,
1486
+ stroke: strokeStr,
1487
+ strokeWidth: shape.strokeWidth ?? 1
1488
+ };
1489
+ if (svg.transform) data.transform = svg.transform;
1490
+ if (shape.opacity !== void 0) data.opacity = shape.opacity;
1491
+ if (shape.strokeLinecap) data.strokeLinecap = shape.strokeLinecap;
1492
+ if (shape.strokeLinejoin) data.strokeLinejoin = shape.strokeLinejoin;
1493
+ if (fillGradientDef) data.fillGradient = fillGradientDef;
1494
+ if (strokeGradientDef) data.strokeGradient = strokeGradientDef;
1495
+ return data;
1496
+ }
1497
+ function clampRatio(value) {
1498
+ if (value === void 0 || !Number.isFinite(value)) return 0.5;
1499
+ return Math.min(1, Math.max(0.01, value));
1500
+ }
1501
+ function validViewBox(vb) {
1502
+ if (!vb || vb.length !== 4 || !vb.every(Number.isFinite) || vb[2] <= 0 || vb[3] <= 0) return null;
1503
+ return vb;
1504
+ }
1505
+ function shapeToPolygon(shape, size, options = {}) {
1506
+ const { width: w, height: h } = size;
1507
+ if (!(w > 0) || !(h > 0)) return { rings: [], fillRule: "nonzero" };
1508
+ let rings;
1509
+ let fillRule = "nonzero";
1510
+ if (shape.kind === "path") {
1511
+ fillRule = shape.fillRule ?? "nonzero";
1512
+ const vb = validViewBox(shape.viewBox);
1513
+ if (vb) {
1514
+ const sx = w / vb[2];
1515
+ const sy = h / vb[3];
1516
+ const tolerance = (options.tolerance ?? 0.25) / Math.max(sx, sy);
1517
+ rings = flattenSvgPath(shape.d, { ...options, tolerance }).map(
1518
+ (ring) => ring.map(([x, y]) => [(x - vb[0]) * sx, (y - vb[1]) * sy])
1519
+ );
1520
+ } else {
1521
+ rings = flattenSvgPath(shape.d, options);
1522
+ }
1523
+ } else {
1524
+ const { d } = getShapeSvg({ ...shape, rotation: 0 }, size);
1525
+ rings = flattenSvgPath(d, options);
1526
+ }
1527
+ const rotate = rotationTransform(shape, size);
1528
+ if (rotate) rings = rings.map((ring) => ring.map(rotate));
1529
+ return { rings, fillRule };
1530
+ }
1531
+ function formatNumber2(value, precision = 3) {
1532
+ const factor = 10 ** precision;
1533
+ const rounded = Math.round(value * factor) / factor;
1534
+ return Object.is(rounded, -0) ? "0" : String(rounded);
1535
+ }
1536
+ function ringsToPath(rings, f) {
1537
+ let d = "";
1538
+ for (const ring of rings) {
1539
+ if (ring.length < 2) continue;
1540
+ d += `M${f(ring[0][0])} ${f(ring[0][1])}`;
1541
+ for (let i = 1; i < ring.length; i++) d += `L${f(ring[i][0])} ${f(ring[i][1])}`;
1542
+ d += "Z";
1543
+ }
1544
+ return d;
1545
+ }
1546
+ function segmentDistance(p, a, b) {
1547
+ const dx = b[0] - a[0];
1548
+ const dy = b[1] - a[1];
1549
+ const len2 = dx * dx + dy * dy;
1550
+ if (len2 === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);
1551
+ const t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));
1552
+ return Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));
1553
+ }
1554
+ function rdp(points, tolerance) {
1555
+ if (points.length <= 2) return points;
1556
+ const keep = new Uint8Array(points.length);
1557
+ keep[0] = 1;
1558
+ keep[points.length - 1] = 1;
1559
+ const stack = [[0, points.length - 1]];
1560
+ while (stack.length) {
1561
+ const [s, e] = stack.pop();
1562
+ let maxDist = -1;
1563
+ let index = -1;
1564
+ for (let i = s + 1; i < e; i++) {
1565
+ const dist3 = segmentDistance(points[i], points[s], points[e]);
1566
+ if (dist3 > maxDist) {
1567
+ maxDist = dist3;
1568
+ index = i;
1569
+ }
1570
+ }
1571
+ if (index !== -1 && maxDist > tolerance) {
1572
+ keep[index] = 1;
1573
+ stack.push([s, index], [index, e]);
1574
+ }
1575
+ }
1576
+ return points.filter((_, i) => keep[i]);
1577
+ }
1578
+ function simplifyRing(ring, tolerance) {
1579
+ if (ring.length <= 3 || !(tolerance > 0)) return [...ring];
1580
+ let far = 0;
1581
+ let farDist = -1;
1582
+ for (let i = 1; i < ring.length; i++) {
1583
+ const dist3 = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);
1584
+ if (dist3 > farDist) {
1585
+ farDist = dist3;
1586
+ far = i;
1587
+ }
1588
+ }
1589
+ const first = rdp(ring.slice(0, far + 1), tolerance);
1590
+ const second = rdp([...ring.slice(far), ring[0]], tolerance);
1591
+ const out = [...first.slice(0, -1), ...second.slice(0, -1)];
1592
+ return out.length >= 3 ? out : [];
1593
+ }
1594
+ function polygonToSvgPath(input, options = {}) {
1595
+ const precision = options.precision ?? 3;
1596
+ const f = (n) => formatNumber2(n, precision);
1597
+ const dx = options.offset?.x ?? 0;
1598
+ const dy = options.offset?.y ?? 0;
1599
+ const rings = [];
1600
+ const fit = options.curves === "fit";
1601
+ let fitted = "";
1602
+ for (const item of input) {
1603
+ const list = item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0]) ? item : [item];
1604
+ for (const ring of list) {
1605
+ let r = ring;
1606
+ if (options.simplify && options.simplify > 0) r = simplifyRing(r, options.simplify);
1607
+ if (r.length < 3) continue;
1608
+ if (fit) {
1609
+ fitted += fitPathData(r, {
1610
+ tolerance: options.curveTolerance ?? 0.25,
1611
+ cornerAngleDeg: options.cornerAngleDeg,
1612
+ precision,
1613
+ offset: { x: dx, y: dy }
1614
+ });
1615
+ continue;
1616
+ }
1617
+ rings.push(dx || dy ? r.map(([x, y]) => [x + dx, y + dy]) : r);
1618
+ }
1619
+ }
1620
+ return fit ? fitted : ringsToPath(rings, f);
1621
+ }
1622
+
1623
+ // src/canvas/pathops/bezier.ts
1624
+ var sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]];
1625
+ var add2 = (a, b) => [a[0] + b[0], a[1] + b[1]];
1626
+ var scale = (a, s) => [a[0] * s, a[1] * s];
1627
+ var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1];
1628
+ var cross = (a, b) => a[0] * b[1] - a[1] * b[0];
1629
+ var dist2 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
1630
+ var lerp = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
1631
+ var samePoint = (a, b) => a[0] === b[0] && a[1] === b[1];
1632
+ function unit2(v) {
1633
+ const l = Math.hypot(v[0], v[1]);
1634
+ return l > 0 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
1635
+ }
1636
+ function segmentPoint(seg, t) {
1637
+ const n = seg.length;
1638
+ if (n === 2) return lerp(seg[0], seg[1], t);
1639
+ const mt = 1 - t;
1640
+ if (n === 3) {
1641
+ const a2 = mt * mt;
1642
+ const b2 = 2 * mt * t;
1643
+ const c2 = t * t;
1644
+ 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]];
1645
+ }
1646
+ const a = mt * mt * mt;
1647
+ const b = 3 * mt * mt * t;
1648
+ const c = 3 * mt * t * t;
1649
+ const d = t * t * t;
1650
+ return [
1651
+ a * seg[0][0] + b * seg[1][0] + c * seg[2][0] + d * seg[3][0],
1652
+ a * seg[0][1] + b * seg[1][1] + c * seg[2][1] + d * seg[3][1]
1653
+ ];
1654
+ }
1655
+ function segmentDerivative(seg, t) {
1656
+ const n = seg.length;
1657
+ if (n === 2) return sub2(seg[1], seg[0]);
1658
+ if (n === 3) {
1659
+ const a2 = sub2(seg[1], seg[0]);
1660
+ const b2 = sub2(seg[2], seg[1]);
1661
+ return [2 * ((1 - t) * a2[0] + t * b2[0]), 2 * ((1 - t) * a2[1] + t * b2[1])];
1662
+ }
1663
+ const a = sub2(seg[1], seg[0]);
1664
+ const b = sub2(seg[2], seg[1]);
1665
+ const c = sub2(seg[3], seg[2]);
1666
+ const mt = 1 - t;
1667
+ return [
1668
+ 3 * (mt * mt * a[0] + 2 * mt * t * b[0] + t * t * c[0]),
1669
+ 3 * (mt * mt * a[1] + 2 * mt * t * b[1] + t * t * c[1])
1670
+ ];
1671
+ }
1672
+ function segmentSecondDerivative(seg, t) {
1673
+ const n = seg.length;
1674
+ if (n === 2) return [0, 0];
1675
+ 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])];
1676
+ const a = [seg[2][0] - 2 * seg[1][0] + seg[0][0], seg[2][1] - 2 * seg[1][1] + seg[0][1]];
1677
+ const b = [seg[3][0] - 2 * seg[2][0] + seg[1][0], seg[3][1] - 2 * seg[2][1] + seg[1][1]];
1678
+ return [6 * ((1 - t) * a[0] + t * b[0]), 6 * ((1 - t) * a[1] + t * b[1])];
1679
+ }
1680
+ function segmentTangent(seg, t) {
1681
+ const d = unit2(segmentDerivative(seg, t));
1682
+ if (d) return d;
1683
+ const h = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
1684
+ const e = unit2(segmentDerivative(seg, h));
1685
+ if (e) return e;
1686
+ return unit2(sub2(seg[seg.length - 1], seg[0])) ?? [1, 0];
1687
+ }
1688
+ function segmentCurvature(seg, t) {
1689
+ if (seg.length === 2) return 0;
1690
+ let d = segmentDerivative(seg, t);
1691
+ let tt = t;
1692
+ if (Math.hypot(d[0], d[1]) < 1e-12) {
1693
+ tt = t < 0.5 ? Math.min(1, t + 1e-6) : Math.max(0, t - 1e-6);
1694
+ d = segmentDerivative(seg, tt);
1695
+ }
1696
+ const dd = segmentSecondDerivative(seg, tt);
1697
+ const len = Math.hypot(d[0], d[1]);
1698
+ if (!(len > 0)) return 0;
1699
+ return cross(d, dd) / (len * len * len);
1700
+ }
1701
+ function splitSegment(seg, t) {
1702
+ const n = seg.length;
1703
+ if (n === 2) {
1704
+ const m2 = lerp(seg[0], seg[1], t);
1705
+ return [
1706
+ [seg[0], m2],
1707
+ [m2, seg[1]]
1708
+ ];
1709
+ }
1710
+ if (n === 3) {
1711
+ const a2 = lerp(seg[0], seg[1], t);
1712
+ const b2 = lerp(seg[1], seg[2], t);
1713
+ const m2 = lerp(a2, b2, t);
1714
+ return [
1715
+ [seg[0], a2, m2],
1716
+ [m2, b2, seg[2]]
1717
+ ];
1718
+ }
1719
+ const a = lerp(seg[0], seg[1], t);
1720
+ const b = lerp(seg[1], seg[2], t);
1721
+ const c = lerp(seg[2], seg[3], t);
1722
+ const ab = lerp(a, b, t);
1723
+ const bc = lerp(b, c, t);
1724
+ const m = lerp(ab, bc, t);
1725
+ return [
1726
+ [seg[0], a, ab, m],
1727
+ [m, bc, c, seg[3]]
1728
+ ];
1729
+ }
1730
+ function subSegment(seg, t0, t1) {
1731
+ if (t1 < t0) return subSegment(seg, t1, t0).reverse();
1732
+ if (t0 <= 0 && t1 >= 1) return [...seg];
1733
+ let s = [...seg];
1734
+ if (t1 < 1) s = splitSegment(s, t1)[0];
1735
+ if (t0 > 0) s = splitSegment(s, t1 > 0 ? t0 / t1 : 0)[1];
1736
+ return s;
1737
+ }
1738
+ function isStraightSegment(seg, tol) {
1739
+ if (seg.length === 2) return true;
1740
+ const a = seg[0];
1741
+ const b = seg[seg.length - 1];
1742
+ const chord = sub2(b, a);
1743
+ const len = Math.hypot(chord[0], chord[1]);
1744
+ for (let i = 1; i < seg.length - 1; i++) {
1745
+ const v = sub2(seg[i], a);
1746
+ if (len === 0) {
1747
+ if (Math.hypot(v[0], v[1]) > tol) return false;
1748
+ continue;
1749
+ }
1750
+ if (Math.abs(cross(chord, v)) / len > tol) return false;
1751
+ const along = dot2(chord, v) / len;
1752
+ if (along < -tol || along > len + tol) return false;
1753
+ }
1754
+ return true;
1755
+ }
1756
+ function quadraticRoots(a, b, c, out) {
1757
+ const push = (t) => {
1758
+ if (t > 1e-12 && t < 1 - 1e-12) out.push(t);
1759
+ };
1760
+ const scaleAbs = Math.max(Math.abs(a), Math.abs(b), Math.abs(c));
1761
+ if (scaleAbs === 0) return;
1762
+ if (Math.abs(a) <= 1e-12 * scaleAbs) {
1763
+ if (b !== 0) push(-c / b);
1764
+ return;
1765
+ }
1766
+ const disc = b * b - 4 * a * c;
1767
+ if (disc < 0) return;
1768
+ const sq = Math.sqrt(disc);
1769
+ const q = -0.5 * (b + (b >= 0 ? sq : -sq));
1770
+ push(q / a);
1771
+ if (q !== 0) push(c / q);
1772
+ }
1773
+ function segmentExtrema(seg, axis) {
1774
+ const out = [];
1775
+ if (seg.length === 3) {
1776
+ const d = seg[0][axis] - 2 * seg[1][axis] + seg[2][axis];
1777
+ if (d !== 0) {
1778
+ const t = (seg[0][axis] - seg[1][axis]) / d;
1779
+ if (t > 1e-12 && t < 1 - 1e-12) out.push(t);
1780
+ }
1781
+ } else if (seg.length === 4) {
1782
+ const p0 = seg[0][axis];
1783
+ const p1 = seg[1][axis];
1784
+ const p2 = seg[2][axis];
1785
+ const p3 = seg[3][axis];
1786
+ const a = -p0 + 3 * p1 - 3 * p2 + p3;
1787
+ const b = 2 * (p0 - 2 * p1 + p2);
1788
+ const c = p1 - p0;
1789
+ quadraticRoots(a, b, c, out);
1790
+ }
1791
+ return out.sort((x, y) => x - y);
1792
+ }
1793
+ function segmentBounds(seg) {
1794
+ const a = seg[0];
1795
+ const b = seg[seg.length - 1];
1796
+ let minX = Math.min(a[0], b[0]);
1797
+ let maxX = Math.max(a[0], b[0]);
1798
+ let minY = Math.min(a[1], b[1]);
1799
+ let maxY = Math.max(a[1], b[1]);
1800
+ for (const axis of [0, 1]) {
1801
+ for (const t of segmentExtrema(seg, axis)) {
1802
+ const p = segmentPoint(seg, t);
1803
+ if (axis === 0) {
1804
+ minX = Math.min(minX, p[0]);
1805
+ maxX = Math.max(maxX, p[0]);
1806
+ } else {
1807
+ minY = Math.min(minY, p[1]);
1808
+ maxY = Math.max(maxY, p[1]);
1809
+ }
1810
+ }
1811
+ }
1812
+ return { minX, minY, maxX, maxY };
1813
+ }
1814
+ function monotoneParams(seg) {
1815
+ if (seg.length === 2) return [];
1816
+ const ts = [...segmentExtrema(seg, 0), ...segmentExtrema(seg, 1)].sort((a, b) => a - b);
1817
+ const out = [];
1818
+ for (const t of ts) if (out.length === 0 || t - out[out.length - 1] > 1e-9) out.push(t);
1819
+ return out;
1820
+ }
1821
+ function segmentAreaTerm(seg) {
1822
+ if (seg.length === 2) return (seg[0][0] * seg[1][1] - seg[1][0] * seg[0][1]) / 2;
1823
+ const nodes = [-Math.sqrt(3 / 5), 0, Math.sqrt(3 / 5)];
1824
+ const weights = [5 / 9, 8 / 9, 5 / 9];
1825
+ let sum = 0;
1826
+ for (let i = 0; i < 3; i++) {
1827
+ const t = (nodes[i] + 1) / 2;
1828
+ const p = segmentPoint(seg, t);
1829
+ const d = segmentDerivative(seg, t);
1830
+ sum += weights[i] * (p[0] * d[1] - p[1] * d[0]);
1831
+ }
1832
+ return sum / 4;
1833
+ }
1834
+ var GL_X = [
1835
+ -0.9602898564975363,
1836
+ -0.7966664774136267,
1837
+ -0.525532409916329,
1838
+ -0.1834346424956498,
1839
+ 0.1834346424956498,
1840
+ 0.525532409916329,
1841
+ 0.7966664774136267,
1842
+ 0.9602898564975363
1843
+ ];
1844
+ var GL_W = [
1845
+ 0.1012285362903763,
1846
+ 0.2223810344533745,
1847
+ 0.3137066458778873,
1848
+ 0.362683783378362,
1849
+ 0.362683783378362,
1850
+ 0.3137066458778873,
1851
+ 0.2223810344533745,
1852
+ 0.1012285362903763
1853
+ ];
1854
+ function glLength(seg, t0, t1) {
1855
+ const half = (t1 - t0) / 2;
1856
+ const mid = (t0 + t1) / 2;
1857
+ let sum = 0;
1858
+ for (let i = 0; i < GL_X.length; i++) {
1859
+ const d = segmentDerivative(seg, mid + half * GL_X[i]);
1860
+ sum += GL_W[i] * Math.hypot(d[0], d[1]);
1861
+ }
1862
+ return sum * half;
1863
+ }
1864
+ function adaptiveLength(seg, t0, t1, whole, tol, depth) {
1865
+ const m = (t0 + t1) / 2;
1866
+ const left = glLength(seg, t0, m);
1867
+ const right = glLength(seg, m, t1);
1868
+ if (depth >= 18 || Math.abs(left + right - whole) <= tol) return left + right;
1869
+ return adaptiveLength(seg, t0, m, left, tol / 2, depth + 1) + adaptiveLength(seg, m, t1, right, tol / 2, depth + 1);
1870
+ }
1871
+ function segmentLength(seg, t0 = 0, t1 = 1, tolerance = 1e-9) {
1872
+ if (t1 < t0) return segmentLength(seg, t1, t0, tolerance);
1873
+ if (seg.length === 2) return dist2(seg[0], seg[1]) * (t1 - t0);
1874
+ const whole = glLength(seg, t0, t1);
1875
+ return adaptiveLength(seg, t0, t1, whole, Math.max(tolerance, whole * 1e-12), 0);
1876
+ }
1877
+ function segmentParamAtLength(seg, length, total = segmentLength(seg)) {
1878
+ if (!(total > 0) || length <= 0) return 0;
1879
+ if (length >= total) return 1;
1880
+ if (seg.length === 2) return length / total;
1881
+ let lo = 0;
1882
+ let hi = 1;
1883
+ let t = length / total;
1884
+ for (let i = 0; i < 40; i++) {
1885
+ const err = segmentLength(seg, 0, t) - length;
1886
+ if (Math.abs(err) <= 1e-10 * Math.max(1, total)) break;
1887
+ if (err > 0) hi = t;
1888
+ else lo = t;
1889
+ const d = segmentDerivative(seg, t);
1890
+ const speed = Math.hypot(d[0], d[1]);
1891
+ let next = speed > 0 ? t - err / speed : (lo + hi) / 2;
1892
+ if (!(next > lo && next < hi)) next = (lo + hi) / 2;
1893
+ t = next;
1894
+ }
1895
+ return t;
1896
+ }
1897
+ function segmentNearest(seg, p) {
1898
+ if (seg.length === 2) {
1899
+ const d = sub2(seg[1], seg[0]);
1900
+ const len2 = dot2(d, d);
1901
+ const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, dot2(sub2(p, seg[0]), d) / len2));
1902
+ return { t, distance: dist2(p, segmentPoint(seg, t)) };
1903
+ }
1904
+ const samples = 16;
1905
+ let best = { t: 0, distance: dist2(p, seg[0]) };
1906
+ const candidates = [];
1907
+ let prev = Infinity;
1908
+ let prevT = 0;
1909
+ let descending = false;
1910
+ for (let i = 0; i <= samples; i++) {
1911
+ const t = i / samples;
1912
+ const d = dist2(p, segmentPoint(seg, t));
1913
+ if (d < best.distance) best = { t, distance: d };
1914
+ if (i > 0 && d > prev && descending) candidates.push(prevT);
1915
+ descending = d < prev;
1916
+ prev = d;
1917
+ prevT = t;
1918
+ }
1919
+ candidates.push(best.t, 1);
1920
+ for (const start of candidates) {
1921
+ let t = start;
1922
+ for (let k = 0; k < 12; k++) {
1923
+ const q = segmentPoint(seg, t);
1924
+ const d1 = segmentDerivative(seg, t);
1925
+ const d2 = segmentSecondDerivative(seg, t);
1926
+ const diff = sub2(q, p);
1927
+ const f = dot2(diff, d1);
1928
+ const fp = dot2(d1, d1) + dot2(diff, d2);
1929
+ if (!(Math.abs(fp) > 1e-300)) break;
1930
+ const next = Math.max(0, Math.min(1, t - f / fp));
1931
+ if (Math.abs(next - t) < 1e-14) {
1932
+ t = next;
1933
+ break;
1934
+ }
1935
+ t = next;
1936
+ }
1937
+ const d = dist2(p, segmentPoint(seg, t));
1938
+ if (d < best.distance) best = { t, distance: d };
1939
+ }
1940
+ return best;
1941
+ }
1942
+ function arcToCubics(from, cmd) {
1943
+ const [x0, y0] = from;
1944
+ const { x, y } = cmd;
1945
+ if (x0 === x && y0 === y) return [];
1946
+ let rx = Math.abs(cmd.rx);
1947
+ let ry = Math.abs(cmd.ry);
1948
+ if (rx === 0 || ry === 0) return [[from, [x, y]]];
1949
+ const phi = cmd.rotation * Math.PI / 180;
1950
+ const cos = Math.cos(phi);
1951
+ const sin = Math.sin(phi);
1952
+ const dx = (x0 - x) / 2;
1953
+ const dy = (y0 - y) / 2;
1954
+ const x1p = cos * dx + sin * dy;
1955
+ const y1p = -sin * dx + cos * dy;
1956
+ const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
1957
+ if (lambda > 1) {
1958
+ const s = Math.sqrt(lambda);
1959
+ rx *= s;
1960
+ ry *= s;
1961
+ }
1962
+ const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
1963
+ const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
1964
+ let coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));
1965
+ if (cmd.largeArc === cmd.sweep) coef = -coef;
1966
+ const cxp = coef * rx * y1p / ry;
1967
+ const cyp = -coef * ry * x1p / rx;
1968
+ const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
1969
+ const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
1970
+ const angle = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
1971
+ const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
1972
+ let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
1973
+ if (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;
1974
+ else if (cmd.sweep && delta < 0) delta += 2 * Math.PI;
1975
+ const n = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 2) - 1e-9));
1976
+ const step = delta / n;
1977
+ const k = 4 / 3 * Math.tan(step / 4);
1978
+ const map = (ux, uy) => {
1979
+ const ex = rx * ux;
1980
+ const ey = ry * uy;
1981
+ return [cos * ex - sin * ey + cx, sin * ex + cos * ey + cy];
1982
+ };
1983
+ const out = [];
1984
+ let prev = from;
1985
+ for (let i = 0; i < n; i++) {
1986
+ const a0 = theta1 + step * i;
1987
+ const a1 = a0 + step;
1988
+ const c0 = Math.cos(a0);
1989
+ const s0 = Math.sin(a0);
1990
+ const c1 = Math.cos(a1);
1991
+ const s1 = Math.sin(a1);
1992
+ const end = i === n - 1 ? [x, y] : map(c1, s1);
1993
+ out.push([prev, map(c0 - k * s0, s0 + k * c0), map(c1 + k * s1, s1 - k * c1), end]);
1994
+ prev = end;
1995
+ }
1996
+ return out;
1997
+ }
1998
+ function isBezierCommandList(input) {
1999
+ return Array.isArray(input) && !input.some((c) => c && c.type === "A");
2000
+ }
2001
+ function toBezierPath(input) {
2002
+ const commands = typeof input === "string" ? parseSvgPath(input) : isBezierCommandList(input) ? input : input;
2003
+ const out = [];
2004
+ let current = [0, 0];
2005
+ let start = [0, 0];
2006
+ let open = false;
2007
+ const ensureMove = () => {
2008
+ if (!open) {
2009
+ out.push({ type: "M", x: start[0], y: start[1] });
2010
+ current = start;
2011
+ open = true;
2012
+ }
2013
+ };
2014
+ for (const cmd of commands) {
2015
+ switch (cmd.type) {
2016
+ case "M":
2017
+ start = [cmd.x, cmd.y];
2018
+ current = start;
2019
+ out.push({ type: "M", x: cmd.x, y: cmd.y });
2020
+ open = true;
2021
+ break;
2022
+ case "L":
2023
+ ensureMove();
2024
+ out.push({ type: "L", x: cmd.x, y: cmd.y });
2025
+ current = [cmd.x, cmd.y];
2026
+ break;
2027
+ case "Q":
2028
+ ensureMove();
2029
+ out.push({ type: "Q", x1: cmd.x1, y1: cmd.y1, x: cmd.x, y: cmd.y });
2030
+ current = [cmd.x, cmd.y];
2031
+ break;
2032
+ case "C":
2033
+ ensureMove();
2034
+ out.push({ type: "C", x1: cmd.x1, y1: cmd.y1, x2: cmd.x2, y2: cmd.y2, x: cmd.x, y: cmd.y });
2035
+ current = [cmd.x, cmd.y];
2036
+ break;
2037
+ case "A": {
2038
+ ensureMove();
2039
+ for (const c of arcToCubics(current, cmd)) {
2040
+ if (c.length === 2) out.push({ type: "L", x: c[1][0], y: c[1][1] });
2041
+ 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] });
2042
+ }
2043
+ current = [cmd.x, cmd.y];
2044
+ break;
2045
+ }
2046
+ case "Z":
2047
+ if (open) out.push({ type: "Z" });
2048
+ open = false;
2049
+ current = start;
2050
+ break;
2051
+ }
2052
+ }
2053
+ return out;
2054
+ }
2055
+ function pathToContours(input) {
2056
+ const path = toBezierPath(input);
2057
+ const contours = [];
2058
+ let current = null;
2059
+ let pen = [0, 0];
2060
+ let start = [0, 0];
2061
+ const push = (seg) => {
2062
+ if (!current) {
2063
+ current = { segments: [], closed: false };
2064
+ contours.push(current);
2065
+ }
2066
+ if (seg.every((p) => samePoint(p, seg[0]))) return;
2067
+ current.segments.push(seg);
2068
+ };
2069
+ for (const cmd of path) {
2070
+ switch (cmd.type) {
2071
+ case "M":
2072
+ start = [cmd.x, cmd.y];
2073
+ pen = start;
2074
+ current = { segments: [], closed: false };
2075
+ contours.push(current);
2076
+ break;
2077
+ case "L":
2078
+ push([pen, [cmd.x, cmd.y]]);
2079
+ pen = [cmd.x, cmd.y];
2080
+ break;
2081
+ case "Q":
2082
+ push([pen, [cmd.x1, cmd.y1], [cmd.x, cmd.y]]);
2083
+ pen = [cmd.x, cmd.y];
2084
+ break;
2085
+ case "C":
2086
+ push([pen, [cmd.x1, cmd.y1], [cmd.x2, cmd.y2], [cmd.x, cmd.y]]);
2087
+ pen = [cmd.x, cmd.y];
2088
+ break;
2089
+ case "Z":
2090
+ if (current) current.closed = true;
2091
+ current = null;
2092
+ pen = start;
2093
+ break;
2094
+ }
2095
+ }
2096
+ return contours;
2097
+ }
2098
+ function contoursToPath(contours) {
2099
+ const out = [];
2100
+ for (const contour of contours) {
2101
+ const segs = contour.segments;
2102
+ if (segs.length === 0) continue;
2103
+ out.push({ type: "M", x: segs[0][0][0], y: segs[0][0][1] });
2104
+ let count = segs.length;
2105
+ if (contour.closed && count > 1) {
2106
+ const last = segs[count - 1];
2107
+ if (last.length === 2 && samePoint(last[1], segs[0][0])) count--;
2108
+ }
2109
+ for (let i = 0; i < count; i++) {
2110
+ const s = segs[i];
2111
+ const end = s[s.length - 1];
2112
+ if (s.length === 2) out.push({ type: "L", x: end[0], y: end[1] });
2113
+ else if (s.length === 3) out.push({ type: "Q", x1: s[1][0], y1: s[1][1], x: end[0], y: end[1] });
2114
+ 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] });
2115
+ }
2116
+ if (contour.closed) out.push({ type: "Z" });
2117
+ }
2118
+ return out;
2119
+ }
2120
+ function bezierPathToSvg(input, options = {}) {
2121
+ const path = toBezierPath(input);
2122
+ const precision = options.precision ?? 3;
2123
+ const dx = options.offset?.x ?? 0;
2124
+ const dy = options.offset?.y ?? 0;
2125
+ const fx = (n) => formatNumber2(n + dx, precision);
2126
+ const fy = (n) => formatNumber2(n + dy, precision);
2127
+ let d = "";
2128
+ for (const c of path) {
2129
+ switch (c.type) {
2130
+ case "M":
2131
+ d += `M${fx(c.x)} ${fy(c.y)}`;
2132
+ break;
2133
+ case "L":
2134
+ d += `L${fx(c.x)} ${fy(c.y)}`;
2135
+ break;
2136
+ case "Q":
2137
+ d += `Q${fx(c.x1)} ${fy(c.y1)} ${fx(c.x)} ${fy(c.y)}`;
2138
+ break;
2139
+ case "C":
2140
+ d += `C${fx(c.x1)} ${fy(c.y1)} ${fx(c.x2)} ${fy(c.y2)} ${fx(c.x)} ${fy(c.y)}`;
2141
+ break;
2142
+ case "Z":
2143
+ d += "Z";
2144
+ break;
2145
+ }
2146
+ }
2147
+ return d;
2148
+ }
2149
+ function transformBezierPath(input, m) {
2150
+ const [a, b, c, d, e, f] = m;
2151
+ const X = (x, y) => a * x + c * y + e;
2152
+ const Y = (x, y) => b * x + d * y + f;
2153
+ return toBezierPath(input).map((cmd) => {
2154
+ switch (cmd.type) {
2155
+ case "M":
2156
+ case "L":
2157
+ return { type: cmd.type, x: X(cmd.x, cmd.y), y: Y(cmd.x, cmd.y) };
2158
+ case "Q":
2159
+ 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) };
2160
+ case "C":
2161
+ return {
2162
+ type: "C",
2163
+ x1: X(cmd.x1, cmd.y1),
2164
+ y1: Y(cmd.x1, cmd.y1),
2165
+ x2: X(cmd.x2, cmd.y2),
2166
+ y2: Y(cmd.x2, cmd.y2),
2167
+ x: X(cmd.x, cmd.y),
2168
+ y: Y(cmd.x, cmd.y)
2169
+ };
2170
+ case "Z":
2171
+ return cmd;
2172
+ }
2173
+ });
2174
+ }
2175
+ function countPathSegments(input) {
2176
+ const out = { lines: 0, quads: 0, cubics: 0 };
2177
+ for (const contour of pathToContours(input)) {
2178
+ const segs = closeContour(contour);
2179
+ for (const s of segs) {
2180
+ if (s.length === 2) out.lines++;
2181
+ else if (s.length === 3) out.quads++;
2182
+ else out.cubics++;
2183
+ }
2184
+ }
2185
+ return out;
2186
+ }
2187
+ function closeContour(contour, force = false) {
2188
+ const segs = contour.segments;
2189
+ if (segs.length === 0) return [];
2190
+ if (!contour.closed && !force) return segs;
2191
+ const first = segs[0][0];
2192
+ const lastSeg = segs[segs.length - 1];
2193
+ const last = lastSeg[lastSeg.length - 1];
2194
+ return samePoint(first, last) ? segs : [...segs, [last, first]];
2195
+ }
2196
+ function bezierPathBounds(input) {
2197
+ let minX = Infinity;
2198
+ let minY = Infinity;
2199
+ let maxX = -Infinity;
2200
+ let maxY = -Infinity;
2201
+ const path = toBezierPath(input);
2202
+ let pen = null;
2203
+ for (const contour of pathToContours(path)) {
2204
+ for (const s of contour.segments) {
2205
+ const b = segmentBounds(s);
2206
+ minX = Math.min(minX, b.minX);
2207
+ minY = Math.min(minY, b.minY);
2208
+ maxX = Math.max(maxX, b.maxX);
2209
+ maxY = Math.max(maxY, b.maxY);
2210
+ }
2211
+ }
2212
+ for (const c of path) {
2213
+ if (c.type === "M") pen = [c.x, c.y];
2214
+ if (pen) {
2215
+ minX = Math.min(minX, pen[0]);
2216
+ minY = Math.min(minY, pen[1]);
2217
+ maxX = Math.max(maxX, pen[0]);
2218
+ maxY = Math.max(maxY, pen[1]);
2219
+ }
2220
+ }
2221
+ return minX === Infinity ? null : { minX, minY, maxX, maxY };
2222
+ }
2223
+ function bezierPathSignedArea(input) {
2224
+ let sum = 0;
2225
+ for (const contour of pathToContours(input)) sum += contourSignedArea(closeContour(contour, true));
2226
+ return sum;
2227
+ }
2228
+ function contourSignedArea(segs) {
2229
+ let sum = 0;
2230
+ for (const s of segs) sum += segmentAreaTerm(s);
2231
+ return sum;
2232
+ }
2233
+ function bezierPathLength(input) {
2234
+ let total = 0;
2235
+ for (const contour of pathToContours(input)) for (const s of closeContour(contour)) total += segmentLength(s);
2236
+ return total;
2237
+ }
2238
+ function bezierPathPointAtLength(input, length) {
2239
+ const contours = pathToContours(input);
2240
+ let remaining = Math.max(0, length);
2241
+ let last = null;
2242
+ for (let ci = 0; ci < contours.length; ci++) {
2243
+ const segs = closeContour(contours[ci]);
2244
+ for (let si = 0; si < segs.length; si++) {
2245
+ const s = segs[si];
2246
+ const len = segmentLength(s);
2247
+ if (remaining <= len) {
2248
+ const t = segmentParamAtLength(s, remaining, len);
2249
+ const tangent2 = segmentTangent(s, t);
2250
+ return { point: segmentPoint(s, t), tangent: tangent2, angle: Math.atan2(tangent2[1], tangent2[0]), contour: ci, segment: si, t };
2251
+ }
2252
+ remaining -= len;
2253
+ const tangent = segmentTangent(s, 1);
2254
+ last = { point: s[s.length - 1], tangent, angle: Math.atan2(tangent[1], tangent[0]), contour: ci, segment: si, t: 1 };
2255
+ }
2256
+ }
2257
+ return last;
2258
+ }
2259
+ function trimSegments(segs, from, to, lengths) {
2260
+ const lens = lengths ?? segs.map((s) => segmentLength(s));
2261
+ const out = [];
2262
+ let at = 0;
2263
+ for (let i = 0; i < segs.length; i++) {
2264
+ const len = lens[i];
2265
+ const s0 = at;
2266
+ const s1 = at + len;
2267
+ at = s1;
2268
+ if (s1 <= from || s0 >= to || len <= 0) continue;
2269
+ const t0 = from > s0 ? segmentParamAtLength(segs[i], from - s0, len) : 0;
2270
+ const t1 = to < s1 ? segmentParamAtLength(segs[i], to - s0, len) : 1;
2271
+ if (t1 > t0) out.push(subSegment(segs[i], t0, t1));
2272
+ }
2273
+ return out;
2274
+ }
2275
+ function solveMonotone(seg, axis, value) {
2276
+ const a = seg[0][axis];
2277
+ const b = seg[seg.length - 1][axis];
2278
+ if (seg.length === 2) return b === a ? 0 : (value - a) / (b - a);
2279
+ const increasing = b > a;
2280
+ let lo = 0;
2281
+ let hi = 1;
2282
+ let t = b === a ? 0.5 : Math.max(0, Math.min(1, (value - a) / (b - a)));
2283
+ for (let i = 0; i < 60; i++) {
2284
+ const v = segmentPoint(seg, t)[axis] - value;
2285
+ if (v === 0) return t;
2286
+ if (v > 0 === increasing) hi = t;
2287
+ else lo = t;
2288
+ const d = segmentDerivative(seg, t)[axis];
2289
+ let next = d !== 0 ? t - v / d : (lo + hi) / 2;
2290
+ if (!(next > lo && next < hi)) next = (lo + hi) / 2;
2291
+ if (Math.abs(next - t) < 1e-15) return next;
2292
+ t = next;
2293
+ }
2294
+ return t;
2295
+ }
2296
+ function yMonotonePieces(segs) {
2297
+ const out = [];
2298
+ for (const s of segs) {
2299
+ if (s.length === 2) {
2300
+ out.push(s);
2301
+ continue;
2302
+ }
2303
+ const ts = segmentExtrema(s, 1);
2304
+ let prev = 0;
2305
+ for (const t of [...ts, 1]) {
2306
+ out.push(subSegment(s, prev, t));
2307
+ prev = t;
2308
+ }
2309
+ }
2310
+ return out;
2311
+ }
2312
+ function windingNumber(chains, point) {
2313
+ const [px, py] = point;
2314
+ let winding = 0;
2315
+ for (const chain of chains) {
2316
+ for (const s of yMonotonePieces(chain)) {
2317
+ const ay = s[0][1];
2318
+ const by = s[s.length - 1][1];
2319
+ let dir = 0;
2320
+ if (ay <= py && by > py) dir = 1;
2321
+ else if (by <= py && ay > py) dir = -1;
2322
+ else continue;
2323
+ let minX = Infinity;
2324
+ let maxX = -Infinity;
2325
+ for (const p of s) {
2326
+ if (p[0] < minX) minX = p[0];
2327
+ if (p[0] > maxX) maxX = p[0];
2328
+ }
2329
+ if (minX > px) {
2330
+ winding += dir;
2331
+ continue;
2332
+ }
2333
+ if (maxX <= px) continue;
2334
+ const t = solveMonotone(s, 1, py);
2335
+ if (segmentPoint(s, t)[0] > px) winding += dir;
2336
+ }
2337
+ }
2338
+ return winding;
2339
+ }
2340
+ function pointInBezierPath(input, point, fillRule = "nonzero") {
2341
+ const chains = pathToContours(input).map((c) => closeContour(c, true));
2342
+ const w = windingNumber(chains, point);
2343
+ return fillRule === "evenodd" ? (w & 1) === 1 : w !== 0;
2344
+ }
2345
+ function distanceToBezierPath(input, point) {
2346
+ const contours = Array.isArray(input) && input.length > 0 && input[0].segments ? input : pathToContours(input);
2347
+ let best = Infinity;
2348
+ for (const contour of contours) {
2349
+ for (const s of closeContour(contour)) {
2350
+ let minX = Infinity;
2351
+ let minY = Infinity;
2352
+ let maxX = -Infinity;
2353
+ let maxY = -Infinity;
2354
+ for (const p of s) {
2355
+ minX = Math.min(minX, p[0]);
2356
+ minY = Math.min(minY, p[1]);
2357
+ maxX = Math.max(maxX, p[0]);
2358
+ maxY = Math.max(maxY, p[1]);
2359
+ }
2360
+ const dx = Math.max(minX - point[0], 0, point[0] - maxX);
2361
+ const dy = Math.max(minY - point[1], 0, point[1] - maxY);
2362
+ if (Math.hypot(dx, dy) >= best) continue;
2363
+ best = Math.min(best, segmentNearest(s, point).distance);
2364
+ }
2365
+ if (contour.segments.length === 0) continue;
2366
+ }
2367
+ return best;
2368
+ }
2369
+ function flattenChain(segs, tolerance) {
2370
+ const out = [];
2371
+ for (const s of segs) {
2372
+ if (out.length === 0) out.push(s[0]);
2373
+ if (s.length === 2) {
2374
+ out.push(s[1]);
2375
+ continue;
2376
+ }
2377
+ let dd = 0;
2378
+ for (let i = 0; i + 2 < s.length; i++) {
2379
+ 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]));
2380
+ }
2381
+ const deg = s.length - 1;
2382
+ const n = Math.max(1, Math.min(256, Math.ceil(Math.sqrt(deg * (deg - 1) * dd / (8 * tolerance)))));
2383
+ for (let k = 1; k <= n; k++) out.push(segmentPoint(s, k / n));
2384
+ }
2385
+ if (out.length > 1 && samePoint(out[0], out[out.length - 1])) out.pop();
2386
+ return out;
2387
+ }
2388
+
2389
+ // src/canvas/pathops/boolean.ts
2390
+ var PathOpsError = class extends Error {
2391
+ constructor(message) {
2392
+ super(message);
2393
+ this.name = "PathOpsError";
2394
+ }
2395
+ };
2396
+ function isOperandObject(input) {
2397
+ return !!input && typeof input === "object" && !Array.isArray(input) && "path" in input;
2398
+ }
2399
+ function normalizeOperand(input) {
2400
+ const path = isOperandObject(input) ? input.path : input;
2401
+ const fillRule = isOperandObject(input) ? input.fillRule ?? "nonzero" : "nonzero";
2402
+ const chains = [];
2403
+ for (const contour of pathToContours(path ?? "")) {
2404
+ const chain = closeContour(contour, true);
2405
+ if (chain.length === 0) continue;
2406
+ for (const seg of chain) {
2407
+ for (const p of seg) {
2408
+ if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) throw new PathOpsError(NON_FINITE);
2409
+ }
2410
+ }
2411
+ chains.push(chain);
2412
+ }
2413
+ return { chains, fillRule };
2414
+ }
2415
+ function scaleOf(operands) {
2416
+ let max = 0;
2417
+ for (const op of operands) {
2418
+ for (const chain of op.chains) {
2419
+ for (const seg of chain) {
2420
+ for (const [x, y] of seg) max = Math.max(max, Math.abs(x), Math.abs(y));
2421
+ }
2422
+ }
2423
+ }
2424
+ return Math.max(1, max);
2425
+ }
2426
+ var VertexPool = class {
2427
+ constructor(eps) {
2428
+ this.eps = eps;
2429
+ }
2430
+ eps;
2431
+ vertices = [];
2432
+ cells = /* @__PURE__ */ new Map();
2433
+ get(x, y) {
2434
+ const cx = Math.floor(x / this.eps);
2435
+ const cy = Math.floor(y / this.eps);
2436
+ let best = null;
2437
+ let bestDist = Infinity;
2438
+ for (let dx = -1; dx <= 1; dx++) {
2439
+ for (let dy = -1; dy <= 1; dy++) {
2440
+ const ids = this.cells.get(`${cx + dx},${cy + dy}`);
2441
+ if (!ids) continue;
2442
+ for (const id of ids) {
2443
+ const v = this.vertices[id];
2444
+ const d = Math.hypot(v.x - x, v.y - y);
2445
+ if (d <= this.eps && d < bestDist) {
2446
+ best = v;
2447
+ bestDist = d;
2448
+ }
2449
+ }
2450
+ }
2451
+ }
2452
+ if (best) return best;
2453
+ const vertex = { id: this.vertices.length, x, y, p: [x, y] };
2454
+ this.vertices.push(vertex);
2455
+ const key = `${cx},${cy}`;
2456
+ const bucket = this.cells.get(key);
2457
+ if (bucket) bucket.push(vertex.id);
2458
+ else this.cells.set(key, [vertex.id]);
2459
+ return vertex;
2460
+ }
2461
+ };
2462
+ function hull(pts) {
2463
+ let minX = Infinity;
2464
+ let maxX = -Infinity;
2465
+ let minY = Infinity;
2466
+ let maxY = -Infinity;
2467
+ for (const [x, y] of pts) {
2468
+ if (x < minX) minX = x;
2469
+ if (x > maxX) maxX = x;
2470
+ if (y < minY) minY = y;
2471
+ if (y > maxY) maxY = y;
2472
+ }
2473
+ return { minX, maxX, minY, maxY };
2474
+ }
2475
+ function snapEnds(pts, a, b) {
2476
+ const n = pts.length;
2477
+ if (n === 2) return [a, b];
2478
+ const d0 = sub2(a, pts[0]);
2479
+ const d1 = sub2(b, pts[n - 1]);
2480
+ if (n === 3) {
2481
+ return [a, [pts[1][0] + (d0[0] + d1[0]) / 2, pts[1][1] + (d0[1] + d1[1]) / 2], b];
2482
+ }
2483
+ return [a, [pts[1][0] + d0[0], pts[1][1] + d0[1]], [pts[2][0] + d1[0], pts[2][1] + d1[1]], b];
2484
+ }
2485
+ function makePiece(pts, a, b, operand, src, s0, s1) {
2486
+ const snapped = snapEnds(pts, a.p, b.p);
2487
+ return { pts: snapped, a, b, operand, src, s0, s1, dirty: true, ...hull(snapped) };
2488
+ }
2489
+ var NON_FINITE = "non-finite coordinates";
2490
+ function flatness(pts) {
2491
+ if (pts.length === 2) return 0;
2492
+ const a = pts[0];
2493
+ const b = pts[pts.length - 1];
2494
+ const chord = sub2(b, a);
2495
+ const len = Math.hypot(chord[0], chord[1]);
2496
+ let worst = 0;
2497
+ for (let i = 1; i < pts.length - 1; i++) {
2498
+ const v = sub2(pts[i], a);
2499
+ const d = len > 0 ? Math.abs(cross(chord, v)) / len : Math.hypot(v[0], v[1]);
2500
+ const along = len > 0 ? dot2(chord, v) / len : 0;
2501
+ const over = Math.max(0, -along, along - len);
2502
+ worst = Math.max(worst, d, over);
2503
+ }
2504
+ return worst;
2505
+ }
2506
+ function split2(pts) {
2507
+ const n = pts.length;
2508
+ const mid = (p, q) => [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
2509
+ if (n === 2) {
2510
+ const m2 = mid(pts[0], pts[1]);
2511
+ return [
2512
+ [pts[0], m2],
2513
+ [m2, pts[1]]
2514
+ ];
2515
+ }
2516
+ if (n === 3) {
2517
+ const a2 = mid(pts[0], pts[1]);
2518
+ const b2 = mid(pts[1], pts[2]);
2519
+ const m2 = mid(a2, b2);
2520
+ return [
2521
+ [pts[0], a2, m2],
2522
+ [m2, b2, pts[2]]
2523
+ ];
2524
+ }
2525
+ const a = mid(pts[0], pts[1]);
2526
+ const b = mid(pts[1], pts[2]);
2527
+ const c = mid(pts[2], pts[3]);
2528
+ const ab = mid(a, b);
2529
+ const bc = mid(b, c);
2530
+ const m = mid(ab, bc);
2531
+ return [
2532
+ [pts[0], a, ab, m],
2533
+ [m, bc, c, pts[3]]
2534
+ ];
2535
+ }
2536
+ function closestSegmentPoints(a0, a1, b0, b1) {
2537
+ const d1 = sub2(a1, a0);
2538
+ const d2 = sub2(b1, b0);
2539
+ const r = sub2(a0, b0);
2540
+ const a = dot2(d1, d1);
2541
+ const e = dot2(d2, d2);
2542
+ const f = dot2(d2, r);
2543
+ let s = 0;
2544
+ let u = 0;
2545
+ if (a <= 1e-300 && e <= 1e-300) return { s: 0, u: 0, d: dist2(a0, b0) };
2546
+ if (a <= 1e-300) {
2547
+ u = Math.max(0, Math.min(1, f / e));
2548
+ } else {
2549
+ const c = dot2(d1, r);
2550
+ if (e <= 1e-300) {
2551
+ s = Math.max(0, Math.min(1, -c / a));
2552
+ } else {
2553
+ const b = dot2(d1, d2);
2554
+ const denom = a * e - b * b;
2555
+ s = denom > 0 ? Math.max(0, Math.min(1, (b * f - c * e) / denom)) : 0;
2556
+ u = (b * s + f) / e;
2557
+ if (u < 0) {
2558
+ u = 0;
2559
+ s = Math.max(0, Math.min(1, -c / a));
2560
+ } else if (u > 1) {
2561
+ u = 1;
2562
+ s = Math.max(0, Math.min(1, (b - c) / a));
2563
+ }
2564
+ }
2565
+ }
2566
+ const pa = [a0[0] + d1[0] * s, a0[1] + d1[1] * s];
2567
+ const pb = [b0[0] + d2[0] * u, b0[1] + d2[1] * u];
2568
+ return { s, u, d: dist2(pa, pb) };
2569
+ }
2570
+ function polish(P, Q, s, u) {
2571
+ let best = { s, u, d: dist2(segmentPoint(P, s), segmentPoint(Q, u)) };
2572
+ for (let i = 0; i < 10 && best.d > 0; i++) {
2573
+ const f = sub2(segmentPoint(P, s), segmentPoint(Q, u));
2574
+ const dp = segmentDerivative(P, s);
2575
+ const dq = segmentDerivative(Q, u);
2576
+ const det = cross(dp, [-dq[0], -dq[1]]);
2577
+ if (!(Math.abs(det) > 1e-300)) break;
2578
+ const ds = cross([-f[0], -f[1]], [-dq[0], -dq[1]]) / det;
2579
+ const du = cross(dp, [-f[0], -f[1]]) / det;
2580
+ s = Math.max(0, Math.min(1, s + ds));
2581
+ u = Math.max(0, Math.min(1, u + du));
2582
+ const d = dist2(segmentPoint(P, s), segmentPoint(Q, u));
2583
+ if (d < best.d) best = { s, u, d };
2584
+ else break;
2585
+ }
2586
+ return best;
2587
+ }
2588
+ function intersectSubdivided(P, p0, p1, Q, q0, q1, fullP, fullQ, ctx, out, depth) {
2589
+ const hp = hull(P);
2590
+ const hq = hull(Q);
2591
+ const e = ctx.eps;
2592
+ if (hp.minX > hq.maxX + e || hq.minX > hp.maxX + e || hp.minY > hq.maxY + e || hq.minY > hp.maxY + e) return;
2593
+ const fp = flatness(P);
2594
+ const fq = flatness(Q);
2595
+ const flatTol = e * 0.5;
2596
+ if (fp <= flatTol && fq <= flatTol || depth >= 52) {
2597
+ if (++ctx.leaves > ctx.budget) throw new PathOpsError("curve intersection did not converge");
2598
+ const a0 = P[0];
2599
+ const a1 = P[P.length - 1];
2600
+ const b0 = Q[0];
2601
+ const b1 = Q[Q.length - 1];
2602
+ const d1 = sub2(a1, a0);
2603
+ const d2 = sub2(b1, b0);
2604
+ const denom = cross(d1, d2);
2605
+ let s = 0;
2606
+ let u = 0;
2607
+ let hit = false;
2608
+ const l1 = Math.hypot(d1[0], d1[1]);
2609
+ const l2 = Math.hypot(d2[0], d2[1]);
2610
+ if (Math.abs(denom) > 1e-12 * l1 * l2) {
2611
+ const r = sub2(b0, a0);
2612
+ s = cross(r, d2) / denom;
2613
+ u = cross(r, d1) / denom;
2614
+ const ts = l1 > 0 ? e / l1 : 1;
2615
+ const tu = l2 > 0 ? e / l2 : 1;
2616
+ hit = s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu;
2617
+ }
2618
+ if (!hit) {
2619
+ const c = closestSegmentPoints(a0, a1, b0, b1);
2620
+ if (c.d > e) return;
2621
+ s = c.s;
2622
+ u = c.u;
2623
+ }
2624
+ s = Math.max(0, Math.min(1, s));
2625
+ u = Math.max(0, Math.min(1, u));
2626
+ const best = polish(fullP, fullQ, p0 + (p1 - p0) * s, q0 + (q1 - q0) * u);
2627
+ if (best.d > e * 2) return;
2628
+ const a = segmentPoint(fullP, best.s);
2629
+ const b = segmentPoint(fullQ, best.u);
2630
+ out.push({ tp: best.s, tq: best.u, point: [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2] });
2631
+ return;
2632
+ }
2633
+ const splitP = fp > flatTol;
2634
+ const splitQ = fq > flatTol;
2635
+ const pm = (p0 + p1) / 2;
2636
+ const qm = (q0 + q1) / 2;
2637
+ if (splitP && splitQ) {
2638
+ const [pa, pb] = split2(P);
2639
+ const [qa, qb] = split2(Q);
2640
+ intersectSubdivided(pa, p0, pm, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
2641
+ intersectSubdivided(pa, p0, pm, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
2642
+ intersectSubdivided(pb, pm, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
2643
+ intersectSubdivided(pb, pm, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
2644
+ } else if (splitP) {
2645
+ const [pa, pb] = split2(P);
2646
+ intersectSubdivided(pa, p0, pm, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);
2647
+ intersectSubdivided(pb, pm, p1, Q, q0, q1, fullP, fullQ, ctx, out, depth + 1);
2648
+ } else {
2649
+ const [qa, qb] = split2(Q);
2650
+ intersectSubdivided(P, p0, p1, qa, q0, qm, fullP, fullQ, ctx, out, depth + 1);
2651
+ intersectSubdivided(P, p0, p1, qb, qm, q1, fullP, fullQ, ctx, out, depth + 1);
2652
+ }
2653
+ }
2654
+ function nearestOn(piece, p, eps) {
2655
+ if (p[0] < piece.minX - eps || p[0] > piece.maxX + eps || p[1] < piece.minY - eps || p[1] > piece.maxY + eps) return null;
2656
+ return segmentNearest(piece.pts, p);
2657
+ }
2658
+ function staysClose(P, Q, e1, e2, tol) {
2659
+ for (const f of [0.25, 0.5, 0.75]) {
2660
+ const tp = e1.tp + (e2.tp - e1.tp) * f;
2661
+ const q = segmentNearest(Q.pts, segmentPoint(P.pts, tp));
2662
+ if (q.distance > tol) return false;
2663
+ }
2664
+ return true;
2665
+ }
2666
+ function pairEvents(P, Q, ctx) {
2667
+ const eps = ctx.eps;
2668
+ const anchors = [];
2669
+ for (const [va, tp] of [
2670
+ [P.a, 0],
2671
+ [P.b, 1]
2672
+ ]) {
2673
+ if (va === Q.a) anchors.push({ tp, tq: 0, point: va.p, anchor: true, shared: true });
2674
+ else if (va === Q.b) anchors.push({ tp, tq: 1, point: va.p, anchor: true, shared: true });
2675
+ }
2676
+ for (const [v, tq] of [
2677
+ [Q.a, 0],
2678
+ [Q.b, 1]
2679
+ ]) {
2680
+ if (v === P.a || v === P.b) continue;
2681
+ const near = nearestOn(P, v.p, eps);
2682
+ if (near && near.distance <= eps) anchors.push({ tp: near.t, tq, point: v.p, anchor: true });
2683
+ }
2684
+ for (const [v, tp] of [
2685
+ [P.a, 0],
2686
+ [P.b, 1]
2687
+ ]) {
2688
+ if (v === Q.a || v === Q.b) continue;
2689
+ const near = nearestOn(Q, v.p, eps);
2690
+ if (near && near.distance <= eps) anchors.push({ tp, tq: near.t, point: v.p, anchor: true });
2691
+ }
2692
+ if (anchors.length >= 2) {
2693
+ const sorted = [...anchors].sort((x, y) => x.tp - y.tp);
2694
+ const first = sorted[0];
2695
+ const last = sorted[sorted.length - 1];
2696
+ if (dist2(first.point, last.point) > eps && staysClose(P, Q, first, last, eps * 4)) {
2697
+ return anchors.filter((a) => !a.shared);
2698
+ }
2699
+ }
2700
+ const found = [];
2701
+ if (P.pts.length === 2 && Q.pts.length === 2) {
2702
+ const a0 = P.pts[0];
2703
+ const d1 = sub2(P.pts[1], a0);
2704
+ const b0 = Q.pts[0];
2705
+ const d2 = sub2(Q.pts[1], b0);
2706
+ const denom = cross(d1, d2);
2707
+ const l1 = Math.hypot(d1[0], d1[1]);
2708
+ const l2 = Math.hypot(d2[0], d2[1]);
2709
+ if (Math.abs(denom) > 1e-12 * l1 * l2) {
2710
+ const r = sub2(b0, a0);
2711
+ const s = cross(r, d2) / denom;
2712
+ const u = cross(r, d1) / denom;
2713
+ const ts = eps / l1;
2714
+ const tu = eps / l2;
2715
+ if (s >= -ts && s <= 1 + ts && u >= -tu && u <= 1 + tu) {
2716
+ const sc = Math.max(0, Math.min(1, s));
2717
+ const uc = Math.max(0, Math.min(1, u));
2718
+ found.push({ tp: sc, tq: uc, point: [a0[0] + d1[0] * sc, a0[1] + d1[1] * sc] });
2719
+ }
2720
+ }
2721
+ } else {
2722
+ ctx.leaves = 0;
2723
+ intersectSubdivided(P.pts, 0, 1, Q.pts, 0, 1, P.pts, Q.pts, ctx, found, 0);
2724
+ }
2725
+ if (found.length === 0) return anchors.filter((a) => !a.shared);
2726
+ const all = [...anchors, ...found].sort((x, y) => x.tp - y.tp || x.tq - y.tq);
2727
+ const clusters = [];
2728
+ for (const ev of all) {
2729
+ const cluster = clusters[clusters.length - 1];
2730
+ const prev = cluster?.[cluster.length - 1];
2731
+ if (prev && (dist2(prev.point, ev.point) <= eps * 2 || staysClose(P, Q, prev, ev, eps * 2))) cluster.push(ev);
2732
+ else clusters.push([ev]);
2733
+ }
2734
+ const out = [];
2735
+ for (const cluster of clusters) {
2736
+ const shared = cluster.find((e) => e.shared);
2737
+ if (shared) continue;
2738
+ const anchor = cluster.find((e) => e.anchor);
2739
+ out.push(anchor ?? cluster[Math.floor(cluster.length / 2)]);
2740
+ }
2741
+ return out;
2742
+ }
2743
+ function addSplit2(map, piece, t, v) {
2744
+ if (v === piece.a || v === piece.b) return;
2745
+ const list = map.get(piece);
2746
+ if (list) list.push({ t, v });
2747
+ else map.set(piece, [{ t, v }]);
2748
+ }
2749
+ function eventVertex(P, Q, ev, pool, eps) {
2750
+ for (const v of [P.a, P.b, Q.a, Q.b]) if (dist2(v.p, ev.point) <= eps) return v;
2751
+ return pool.get(ev.point[0], ev.point[1]);
2752
+ }
2753
+ function splitPass2(pieces, pool, ctx) {
2754
+ const eps = ctx.eps;
2755
+ const order = pieces.map((_, i) => i).sort((i, j) => pieces[i].minX - pieces[j].minX);
2756
+ const splits = /* @__PURE__ */ new Map();
2757
+ for (let oi = 0; oi < order.length; oi++) {
2758
+ const P = pieces[order[oi]];
2759
+ for (let oj = oi + 1; oj < order.length; oj++) {
2760
+ const Q = pieces[order[oj]];
2761
+ if (Q.minX > P.maxX + eps) break;
2762
+ if (!P.dirty && !Q.dirty) continue;
2763
+ if (Q.minY > P.maxY + eps || Q.maxY < P.minY - eps) continue;
2764
+ if (P.a === Q.a && P.b === Q.b && P.pts.length === 2 && Q.pts.length === 2) continue;
2765
+ if (P.a === Q.b && P.b === Q.a && P.pts.length === 2 && Q.pts.length === 2) continue;
2766
+ for (const ev of pairEvents(P, Q, ctx)) {
2767
+ const v = eventVertex(P, Q, ev, pool, eps);
2768
+ addSplit2(splits, P, ev.tp, v);
2769
+ addSplit2(splits, Q, ev.tq, v);
2770
+ }
2771
+ }
2772
+ }
2773
+ for (const p of pieces) p.dirty = false;
2774
+ if (splits.size === 0) return { pieces, changed: false };
2775
+ const out = [];
2776
+ for (const piece of pieces) {
2777
+ const list = splits.get(piece);
2778
+ if (!list) {
2779
+ out.push(piece);
2780
+ continue;
2781
+ }
2782
+ list.sort((x, y) => x.t - y.t);
2783
+ let prevT = 0;
2784
+ let prevV = piece.a;
2785
+ const range = piece.s1 - piece.s0;
2786
+ const emit = (t, v) => {
2787
+ if (v === prevV || t <= prevT) return;
2788
+ const pts = subSegment(piece.pts, prevT, t);
2789
+ out.push(makePiece(pts, prevV, v, piece.operand, piece.src, piece.s0 + range * prevT, piece.s0 + range * t));
2790
+ prevT = t;
2791
+ prevV = v;
2792
+ };
2793
+ const seen = /* @__PURE__ */ new Set();
2794
+ for (const { t, v } of list) {
2795
+ if (seen.has(v) || t <= 0 || t >= 1) continue;
2796
+ seen.add(v);
2797
+ emit(t, v);
2798
+ }
2799
+ if (prevV !== piece.b) {
2800
+ const pts = subSegment(piece.pts, prevT, 1);
2801
+ out.push(makePiece(pts, prevV, piece.b, piece.operand, piece.src, piece.s0 + range * prevT, piece.s1));
2802
+ }
2803
+ }
2804
+ return { pieces: out, changed: true };
2805
+ }
2806
+ function buildArrangement(inputs, options) {
2807
+ const operands = inputs.map(normalizeOperand);
2808
+ const scale2 = scaleOf(operands);
2809
+ const eps = options.epsilon && options.epsilon > 0 ? options.epsilon : 1e-9 * scale2;
2810
+ const pool = new VertexPool(eps);
2811
+ const sources = [];
2812
+ let pieces = [];
2813
+ operands.forEach((op, operand) => {
2814
+ for (const chain of op.chains) {
2815
+ for (const raw of chain) {
2816
+ const straight = raw.length > 2 && isStraightSegment(raw, eps * 0.01);
2817
+ const seg = straight ? [raw[0], raw[raw.length - 1]] : raw;
2818
+ const src = sources.length;
2819
+ sources.push({ seg, line: seg.length === 2 });
2820
+ const ts = [0, ...monotoneParams(seg), 1];
2821
+ for (let i = 0; i + 1 < ts.length; i++) {
2822
+ const pts = subSegment(seg, ts[i], ts[i + 1]);
2823
+ const a = pool.get(pts[0][0], pts[0][1]);
2824
+ const b = pool.get(pts[pts.length - 1][0], pts[pts.length - 1][1]);
2825
+ if (a === b) continue;
2826
+ pieces.push(makePiece(pts, a, b, operand, src, ts[i], ts[i + 1]));
2827
+ }
2828
+ }
2829
+ }
2830
+ });
2831
+ const ctx = { eps, leaves: 0, budget: options.subdivisionBudget ?? 2e4 };
2832
+ let settled = false;
2833
+ for (let pass = 0; pass < 6; pass++) {
2834
+ const result = splitPass2(pieces, pool, ctx);
2835
+ pieces = result.pieces;
2836
+ if (!result.changed) {
2837
+ settled = true;
2838
+ break;
2839
+ }
2840
+ }
2841
+ if (!settled) throw new PathOpsError("intersections did not settle");
2842
+ const count = operands.length;
2843
+ const groups = /* @__PURE__ */ new Map();
2844
+ const edges = [];
2845
+ for (const piece of pieces) {
2846
+ const forward = piece.a.id < piece.b.id;
2847
+ const a = forward ? piece.a : piece.b;
2848
+ const b = forward ? piece.b : piece.a;
2849
+ const pts = forward ? piece.pts : [...piece.pts].reverse();
2850
+ const key = `${a.id},${b.id}`;
2851
+ const group = groups.get(key) ?? [];
2852
+ if (!groups.has(key)) groups.set(key, group);
2853
+ const mid = segmentPoint(pts, 0.5);
2854
+ let edge = group.find((e) => {
2855
+ if (e.pts.length === 2 && pts.length === 2) return true;
2856
+ return segmentNearest(e.pts, mid).distance <= eps * 4 && segmentNearest(pts, segmentPoint(e.pts, 0.5)).distance <= eps * 4;
2857
+ });
2858
+ if (!edge) {
2859
+ edge = {
2860
+ a,
2861
+ b,
2862
+ pts,
2863
+ mult: new Array(count).fill(0),
2864
+ src: piece.src,
2865
+ sa: forward ? piece.s0 : piece.s1,
2866
+ sb: forward ? piece.s1 : piece.s0,
2867
+ ...hull(pts)
2868
+ };
2869
+ group.push(edge);
2870
+ edges.push(edge);
2871
+ }
2872
+ edge.mult[piece.operand] += forward ? 1 : -1;
2873
+ }
2874
+ const live = edges.filter((e) => e.mult.some((m) => m !== 0));
2875
+ const byY = new BandIndex(live, (e) => e.minY, (e) => e.maxY);
2876
+ const byX = new BandIndex(live, (e) => e.minX, (e) => e.maxX);
2877
+ const right = [];
2878
+ const left = [];
2879
+ for (let i = 0; i < live.length; i++) {
2880
+ const edge = live[i];
2881
+ const mid = segmentPoint(edge.pts, 0.5);
2882
+ const tan = segmentTangent(edge.pts, 0.5);
2883
+ const horizontal = Math.abs(tan[1]) < Math.abs(tan[0]);
2884
+ const winding = new Array(count).fill(0);
2885
+ const [mx, my] = mid;
2886
+ for (const j of horizontal ? byX.at(mx) : byY.at(my)) {
2887
+ if (j === i) continue;
2888
+ const o = live[j];
2889
+ let dir = 0;
2890
+ if (!horizontal) {
2891
+ if (o.maxX <= mx) continue;
2892
+ const ay = o.a.y;
2893
+ const by = o.b.y;
2894
+ if (ay <= my && by > my) dir = 1;
2895
+ else if (by <= my && ay > my) dir = -1;
2896
+ else continue;
2897
+ if (o.minX <= mx) {
2898
+ const t = solveMonotone(o.pts, 1, my);
2899
+ if (!(segmentPoint(o.pts, t)[0] > mx)) continue;
2900
+ }
2901
+ } else {
2902
+ if (o.maxY <= my) continue;
2903
+ const ax = o.a.x;
2904
+ const bx = o.b.x;
2905
+ if (bx < mx && mx <= ax) dir = 1;
2906
+ else if (ax < mx && mx <= bx) dir = -1;
2907
+ else continue;
2908
+ if (o.minY <= my) {
2909
+ const t = solveMonotone(o.pts, 0, mx);
2910
+ if (!(segmentPoint(o.pts, t)[1] > my)) continue;
2911
+ }
2912
+ }
2913
+ for (let k = 0; k < count; k++) if (o.mult[k] !== 0) winding[k] += dir * o.mult[k];
2914
+ }
2915
+ const sideIsRight = horizontal ? edge.b.x < edge.a.x : edge.b.y > edge.a.y;
2916
+ const r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);
2917
+ right.push(r);
2918
+ left.push(r.map((w, k) => w + edge.mult[k]));
2919
+ }
2920
+ return { edges: live, left, right, fillRules: operands.map((o) => o.fillRule), sources, eps, scale: scale2 };
2921
+ }
2922
+ var BandIndex = class {
2923
+ bands;
2924
+ min;
2925
+ size;
2926
+ constructor(edges, lo, hi) {
2927
+ let min = Infinity;
2928
+ let max = -Infinity;
2929
+ for (const e of edges) {
2930
+ min = Math.min(min, lo(e));
2931
+ max = Math.max(max, hi(e));
2932
+ }
2933
+ const count = Math.max(1, Math.min(4096, Math.ceil(Math.sqrt(edges.length) * 2)));
2934
+ this.min = min;
2935
+ this.size = max > min ? (max - min) / count : 1;
2936
+ this.bands = Array.from({ length: count }, () => []);
2937
+ edges.forEach((e, i) => {
2938
+ const from = this.index(lo(e));
2939
+ const to = this.index(hi(e));
2940
+ for (let b = from; b <= to; b++) this.bands[b].push(i);
2941
+ });
2942
+ }
2943
+ index(v) {
2944
+ const k = Math.floor((v - this.min) / this.size);
2945
+ return Math.max(0, Math.min(this.bands.length - 1, Number.isFinite(k) ? k : 0));
2946
+ }
2947
+ at(v) {
2948
+ return this.bands[this.index(v)] ?? [];
2949
+ }
2950
+ };
2951
+ function insideFlags2(windings, fillRules) {
2952
+ return windings.map((w, k) => fillRules[k] === "evenodd" ? (w & 1) === 1 : w !== 0);
2953
+ }
2954
+ var TWO_PI = Math.PI * 2;
2955
+ var TIE = 1e-3;
2956
+ function directed(edge, forward) {
2957
+ const pts = forward ? edge.pts : [...edge.pts].reverse();
2958
+ const out = segmentTangent(pts, 0);
2959
+ const end = segmentTangent(pts, 1);
2960
+ return {
2961
+ from: forward ? edge.a : edge.b,
2962
+ to: forward ? edge.b : edge.a,
2963
+ pts,
2964
+ src: edge.src,
2965
+ sFrom: forward ? edge.sa : edge.sb,
2966
+ sTo: forward ? edge.sb : edge.sa,
2967
+ outAngle: Math.atan2(out[1], out[0]),
2968
+ backAngle: Math.atan2(-end[1], -end[0]),
2969
+ length: dist2(pts[0], pts[pts.length - 1]) + (pts.length > 2 ? flatness(pts) : 0)
2970
+ };
2971
+ }
2972
+ function chordAngle(pts, t) {
2973
+ const p = segmentPoint(pts, t);
2974
+ return Math.atan2(p[1] - pts[0][1], p[0] - pts[0][0]);
2975
+ }
2976
+ function mod2pi(a) {
2977
+ const r = a - Math.floor(a / TWO_PI) * TWO_PI;
2978
+ return r >= TWO_PI ? 0 : r;
2979
+ }
2980
+ function traceRings2(edges, scale2) {
2981
+ const outgoing = /* @__PURE__ */ new Map();
2982
+ for (const e of edges) {
2983
+ const list = outgoing.get(e.from.id);
2984
+ if (list) list.push(e);
2985
+ else outgoing.set(e.from.id, [e]);
2986
+ }
2987
+ const next = (incoming) => {
2988
+ const candidates = outgoing.get(incoming.to.id)?.filter((c) => !used.has(c) || c === start);
2989
+ if (!candidates || candidates.length === 0) return void 0;
2990
+ if (candidates.length === 1) return candidates[0];
2991
+ const turns = candidates.map((c) => mod2pi(incoming.backAngle - c.outAngle));
2992
+ const tied = (x) => x < TIE || x > TWO_PI - TIE;
2993
+ let ambiguous = turns.some(tied);
2994
+ if (!ambiguous) {
2995
+ const sorted = [...turns].sort((a, b) => a - b);
2996
+ for (let i = 1; i < sorted.length; i++) if (sorted[i] - sorted[i - 1] < TIE) ambiguous = true;
2997
+ }
2998
+ if (ambiguous) {
2999
+ const back = [...incoming.pts].reverse();
3000
+ let delta = Math.min(incoming.length, ...candidates.map((c) => c.length));
3001
+ delta = Math.min(delta * 0.25, scale2 * 0.02);
3002
+ const frac = (e) => Math.min(1, Math.max(1e-6, delta / Math.max(e.length, 1e-300)));
3003
+ const backChord = chordAngle(back, frac(incoming));
3004
+ for (let i = 0; i < candidates.length; i++) {
3005
+ turns[i] = mod2pi(backChord - chordAngle(candidates[i].pts, frac(candidates[i])));
3006
+ if (turns[i] < 1e-12) turns[i] = TWO_PI;
3007
+ }
3008
+ } else {
3009
+ for (let i = 0; i < turns.length; i++) if (turns[i] < 1e-15) turns[i] = TWO_PI;
3010
+ }
3011
+ let best = 0;
3012
+ for (let i = 1; i < candidates.length; i++) if (turns[i] < turns[best]) best = i;
3013
+ return candidates[best];
3014
+ };
3015
+ const used = /* @__PURE__ */ new Set();
3016
+ const rings = [];
3017
+ let start = null;
3018
+ for (const first of edges) {
3019
+ if (used.has(first)) continue;
3020
+ start = first;
3021
+ const ring = [];
3022
+ let edge = first;
3023
+ let guard = edges.length + 1;
3024
+ let closed = false;
3025
+ while (edge && guard-- > 0) {
3026
+ used.add(edge);
3027
+ ring.push(edge);
3028
+ edge = next(edge);
3029
+ if (edge === first) {
3030
+ closed = true;
3031
+ break;
3032
+ }
3033
+ }
3034
+ if (!closed) throw new PathOpsError("result outline does not close");
3035
+ rings.push(ring);
3036
+ }
3037
+ return rings;
3038
+ }
3039
+ function mergeable(a, b, sources, eps) {
3040
+ if (a.src === b.src && Math.abs(a.sTo - b.sFrom) < 1e-9 && a.sTo - a.sFrom > 0 === b.sTo - b.sFrom > 0) return true;
3041
+ if (a.pts.length === 2 && b.pts.length === 2) {
3042
+ const p = a.pts[0];
3043
+ const q = b.pts[1];
3044
+ const m = a.pts[1];
3045
+ const chord = sub2(q, p);
3046
+ const len = Math.hypot(chord[0], chord[1]);
3047
+ if (len === 0) return false;
3048
+ const off = Math.abs(cross(chord, sub2(m, p))) / len;
3049
+ const along = dot2(chord, sub2(m, p));
3050
+ return off <= eps && along > 0 && along < len * len;
3051
+ }
3052
+ void sources;
3053
+ return false;
3054
+ }
3055
+ function rebuildRing(ring, sources, eps) {
3056
+ const n = ring.length;
3057
+ let startIndex = 0;
3058
+ for (let k = 0; k < n; k++) {
3059
+ if (!mergeable(ring[(k + n - 1) % n], ring[k], sources, eps)) {
3060
+ startIndex = k;
3061
+ break;
3062
+ }
3063
+ }
3064
+ const ordered = [...ring.slice(startIndex), ...ring.slice(0, startIndex)];
3065
+ const out = [];
3066
+ let i = 0;
3067
+ while (i < ordered.length) {
3068
+ const first = ordered[i];
3069
+ let j = i + 1;
3070
+ while (j < ordered.length && mergeable(ordered[j - 1], ordered[j], sources, eps)) j++;
3071
+ const last = ordered[j - 1];
3072
+ if (j - i === 1) {
3073
+ out.push(first.pts);
3074
+ } else if (first.pts.length === 2 && ordered.slice(i, j).every((e) => e.pts.length === 2)) {
3075
+ out.push([first.from.p, last.to.p]);
3076
+ } else {
3077
+ const source = sources[first.src];
3078
+ out.push(snapEnds(subSegment(source.seg, first.sFrom, last.sTo), first.from.p, last.to.p));
3079
+ }
3080
+ i = j;
3081
+ }
3082
+ return out;
3083
+ }
3084
+ function extractRings(arr, isInside) {
3085
+ const kept = [];
3086
+ arr.edges.forEach((edge, i) => {
3087
+ const l = isInside(insideFlags2(arr.left[i], arr.fillRules));
3088
+ const r = isInside(insideFlags2(arr.right[i], arr.fillRules));
3089
+ if (l === r) return;
3090
+ kept.push(directed(edge, l));
3091
+ });
3092
+ const rings = [];
3093
+ const sliver = Math.max(arr.eps * 10, 1e-7 * arr.scale);
3094
+ for (const ring of traceRings2(kept, arr.scale)) {
3095
+ const segs = rebuildRing(ring, arr.sources, arr.eps);
3096
+ const area = contourSignedArea(segs);
3097
+ let perimeter = 0;
3098
+ for (const s of segs) perimeter += s.length === 2 ? dist2(s[0], s[1]) : segmentLength(s, 0, 1, 1e-6);
3099
+ if (!(perimeter > 0) || 2 * Math.abs(area) / perimeter < sliver) continue;
3100
+ rings.push({ segs, area });
3101
+ }
3102
+ return rings;
3103
+ }
3104
+ function ringSample(ring) {
3105
+ let best = ring.segs[0];
3106
+ let bestLen = -1;
3107
+ for (const s of ring.segs) {
3108
+ const l = dist2(s[0], s[s.length - 1]);
3109
+ if (l > bestLen) {
3110
+ best = s;
3111
+ bestLen = l;
3112
+ }
3113
+ }
3114
+ return segmentPoint(best, 0.5);
3115
+ }
3116
+ function groupRings(rings) {
3117
+ const outers = rings.filter((r) => r.area > 0).sort((a, b) => b.area - a.area);
3118
+ const holes = rings.filter((r) => r.area < 0);
3119
+ const groups = outers.map((o) => [o]);
3120
+ for (const hole of holes) {
3121
+ const sample = ringSample(hole);
3122
+ for (let i = outers.length - 1; i >= 0; i--) {
3123
+ if (outers[i].area >= -hole.area && windingNumber([outers[i].segs], sample) !== 0) {
3124
+ groups[i].push(hole);
3125
+ break;
3126
+ }
3127
+ }
3128
+ }
3129
+ return groups;
3130
+ }
3131
+ function ringsToPath2(rings) {
3132
+ return contoursToPath(rings.map((r) => ({ segments: r.segs, closed: true })));
3133
+ }
3134
+ function predicate2(operation) {
3135
+ switch (operation) {
3136
+ case "union":
3137
+ return (f) => f.some(Boolean);
3138
+ case "intersection":
3139
+ return (f) => f.length > 0 && f.every(Boolean);
3140
+ case "difference":
3141
+ return (f) => f[0] === true && !f.slice(1).some(Boolean);
3142
+ case "xor":
3143
+ return (f) => f.filter(Boolean).length % 2 === 1;
3144
+ }
3145
+ }
3146
+ function flattenOperand(input, tolerance) {
3147
+ const path = isOperandObject(input) ? input.path : input;
3148
+ const fillRule = isOperandObject(input) ? input.fillRule ?? "nonzero" : "nonzero";
3149
+ const rings = pathToContours(path ?? "").map((c) => flattenChain(closeContour(c, true), tolerance)).filter((r) => r.length >= 3 && r.every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1])));
3150
+ return { rings, fillRule };
3151
+ }
3152
+ function multiToPath(multi, tolerance) {
3153
+ if (multi.length === 0) return [];
3154
+ return toBezierPath(polygonToSvgPath(multi, { curves: "fit", curveTolerance: tolerance, precision: 9 }));
3155
+ }
3156
+ function reasonOf(error) {
3157
+ return error instanceof Error ? error.message : String(error);
3158
+ }
3159
+ function pathBoolean(operation, operands, options = {}) {
3160
+ if (operands.length === 0) return { path: [], engine: "curve" };
3161
+ try {
3162
+ const arr = buildArrangement(operands, options);
3163
+ return { path: ringsToPath2(extractRings(arr, predicate2(operation))), engine: "curve" };
3164
+ } catch (error) {
3165
+ const reason = reasonOf(error);
3166
+ if (reason === NON_FINITE) return { path: [], engine: "curve", error: reason };
3167
+ if (options.fallback === false) throw error;
3168
+ options.onFallback?.(reason);
3169
+ const tolerance = options.fallbackTolerance ?? 0.05;
3170
+ const multi = polygonBoolean(
3171
+ operation,
3172
+ operands.map((o) => flattenOperand(o, tolerance)),
3173
+ { epsilon: options.epsilon }
3174
+ );
3175
+ return { path: multiToPath(multi, tolerance), engine: "polygon", fallbackReason: reason };
3176
+ }
3177
+ }
3178
+ var unitePaths = (a, b, options) => pathBoolean("union", [a, b], options).path;
3179
+ var intersectPaths = (a, b, options) => pathBoolean("intersection", [a, b], options).path;
3180
+ var subtractPaths = (a, b, options) => pathBoolean("difference", [a, b], options).path;
3181
+ var excludePaths = (a, b, options) => pathBoolean("xor", [a, b], options).path;
3182
+ function dividePaths(operands, options = {}) {
3183
+ if (operands.length === 0) return { pieces: [], engine: "curve" };
3184
+ try {
3185
+ const arr = buildArrangement(operands, options);
3186
+ const signatures = /* @__PURE__ */ new Map();
3187
+ const note = (windings) => {
3188
+ const flags = insideFlags2(windings, arr.fillRules);
3189
+ if (!flags.some(Boolean)) return;
3190
+ const key = flags.map((f) => f ? "1" : "0").join("");
3191
+ if (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => f ? [k] : []));
3192
+ };
3193
+ arr.edges.forEach((_, i) => {
3194
+ note(arr.left[i]);
3195
+ note(arr.right[i]);
3196
+ });
3197
+ const pieces = [];
3198
+ for (const key of [...signatures.keys()].sort()) {
3199
+ const rings = extractRings(arr, (flags) => flags.map((f) => f ? "1" : "0").join("") === key);
3200
+ const groups = groupRings(rings).sort((a, b) => b[0].area - a[0].area);
3201
+ for (const group of groups) pieces.push({ path: ringsToPath2(group), operands: signatures.get(key) });
3202
+ }
3203
+ return { pieces, engine: "curve" };
3204
+ } catch (error) {
3205
+ const reason = reasonOf(error);
3206
+ if (reason === NON_FINITE) return { pieces: [], engine: "curve", error: reason };
3207
+ if (options.fallback === false) throw error;
3208
+ options.onFallback?.(reason);
3209
+ const tolerance = options.fallbackTolerance ?? 0.05;
3210
+ const pieces = dividePolygons(
3211
+ operands.map((o) => flattenOperand(o, tolerance)),
3212
+ { epsilon: options.epsilon }
3213
+ ).map((p) => ({ path: multiToPath([p.polygon], tolerance), operands: p.operands }));
3214
+ return { pieces, engine: "polygon", fallbackReason: reason };
3215
+ }
3216
+ }
3217
+ function normalizeBezierPath(path, fillRule = "nonzero", options = {}) {
3218
+ return pathBoolean("union", [{ path, fillRule }], options).path;
3219
+ }
3220
+ function bezierPathArea(path, fillRule = "nonzero", options = {}) {
3221
+ const normalized = normalizeBezierPath(path, fillRule, options);
3222
+ let sum = 0;
3223
+ for (const contour of pathToContours(normalized)) sum += contourSignedArea(closeContour(contour, true));
3224
+ return Math.abs(sum);
3225
+ }
3226
+ function pathBooleanRings(operation, operands, options = {}) {
3227
+ const arr = buildArrangement(operands, options);
3228
+ const rings = extractRings(arr, typeof operation === "function" ? operation : predicate2(operation));
3229
+ const order = rings.map((_, i) => i).sort((a, b) => Math.abs(rings[a].area) - Math.abs(rings[b].area));
3230
+ return rings.map((ring, i) => {
3231
+ const sample = ringSample(ring);
3232
+ let parent = -1;
3233
+ for (const j of order) {
3234
+ if (j === i || Math.abs(rings[j].area) <= Math.abs(ring.area)) continue;
3235
+ if (windingNumber([rings[j].segs], sample) !== 0) {
3236
+ parent = j;
3237
+ break;
3238
+ }
3239
+ }
3240
+ return { segments: ring.segs, area: ring.area, parent };
3241
+ });
3242
+ }
3243
+ function nestedRingsToPath(rings, keep = () => true) {
3244
+ const contours = [];
3245
+ rings.forEach((r, i) => {
3246
+ if (keep(i)) contours.push({ segments: r.segments, closed: true });
3247
+ });
3248
+ return contoursToPath(contours);
3249
+ }
3250
+
3251
+ export {
3252
+ ringSignedArea,
3253
+ polygonArea,
3254
+ multiPolygonArea,
3255
+ multiPolygonBounds,
3256
+ pointInRings,
3257
+ pointInMultiPolygon,
3258
+ polygonBoolean,
3259
+ unionPolygons,
3260
+ intersectPolygons,
3261
+ subtractPolygons,
3262
+ xorPolygons,
3263
+ dividePolygons,
3264
+ cubicPointAt,
3265
+ fitCubicRun,
3266
+ fitCubics,
3267
+ isStraightCubic,
3268
+ cubicsToPathData,
3269
+ fitPathData,
3270
+ fitFreehandPath,
3271
+ CANVAS_GRADIENT_PRESETS,
3272
+ isCanvasGradient,
3273
+ parseCssGradient,
3274
+ resolveCanvasGradient,
3275
+ angleToSvgLinearCoords,
3276
+ gradientToSvgDef,
3277
+ SHAPE_KINDS,
3278
+ isShapeDescription,
3279
+ parseSvgPath,
3280
+ flattenSvgPath,
3281
+ getShapeSvg,
3282
+ getShapeRenderData,
3283
+ shapeToPolygon,
3284
+ formatNumber2 as formatNumber,
3285
+ simplifyRing,
3286
+ polygonToSvgPath,
3287
+ sub2 as sub,
3288
+ add2 as add,
3289
+ scale,
3290
+ dot2 as dot,
3291
+ cross,
3292
+ dist2 as dist,
3293
+ samePoint,
3294
+ segmentPoint,
3295
+ segmentDerivative,
3296
+ segmentTangent,
3297
+ segmentCurvature,
3298
+ splitSegment,
3299
+ subSegment,
3300
+ segmentExtrema,
3301
+ segmentBounds,
3302
+ segmentLength,
3303
+ segmentParamAtLength,
3304
+ segmentNearest,
3305
+ arcToCubics,
3306
+ toBezierPath,
3307
+ pathToContours,
3308
+ contoursToPath,
3309
+ bezierPathToSvg,
3310
+ transformBezierPath,
3311
+ countPathSegments,
3312
+ closeContour,
3313
+ bezierPathBounds,
3314
+ bezierPathSignedArea,
3315
+ contourSignedArea,
3316
+ bezierPathLength,
3317
+ bezierPathPointAtLength,
3318
+ trimSegments,
3319
+ windingNumber,
3320
+ pointInBezierPath,
3321
+ distanceToBezierPath,
3322
+ flattenChain,
3323
+ PathOpsError,
3324
+ pathBoolean,
3325
+ unitePaths,
3326
+ intersectPaths,
3327
+ subtractPaths,
3328
+ excludePaths,
3329
+ dividePaths,
3330
+ normalizeBezierPath,
3331
+ bezierPathArea,
3332
+ pathBooleanRings,
3333
+ nestedRingsToPath
3334
+ };
3335
+ //# sourceMappingURL=chunk-GQTXV2EQ.js.map