@weasel-js/geom 1.13.0 → 1.14.1

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.
Files changed (50) hide show
  1. package/README.md +6 -2
  2. package/dist/3d/index.d.ts +8 -1
  3. package/dist/3d/index.js +25 -1
  4. package/dist/3d/index.js.map +1 -1
  5. package/dist/booleans/index.d.ts +6 -5
  6. package/dist/booleans/index.js +583 -73
  7. package/dist/booleans/index.js.map +1 -1
  8. package/dist/{chunk-CE7IY7TB.js → chunk-2N6OWA7E.js} +3 -3
  9. package/dist/{chunk-CE7IY7TB.js.map → chunk-2N6OWA7E.js.map} +1 -1
  10. package/dist/{chunk-Q66NA2FU.js → chunk-3HQJGI63.js} +8 -5
  11. package/dist/chunk-3HQJGI63.js.map +1 -0
  12. package/dist/{chunk-YPWFGFQX.js → chunk-3YH5XU3H.js} +125 -8
  13. package/dist/chunk-3YH5XU3H.js.map +1 -0
  14. package/dist/{chunk-UI3FUWXM.js → chunk-42TZDN5C.js} +4 -4
  15. package/dist/{chunk-UI3FUWXM.js.map → chunk-42TZDN5C.js.map} +1 -1
  16. package/dist/{chunk-B53BERKG.js → chunk-B3D6KWW3.js} +110 -8
  17. package/dist/chunk-B3D6KWW3.js.map +1 -0
  18. package/dist/{chunk-2PKPKU4P.js → chunk-EURWZUNY.js} +25 -22
  19. package/dist/chunk-EURWZUNY.js.map +1 -0
  20. package/dist/{chunk-VHXOCDZ2.js → chunk-FROQPADA.js} +6 -6
  21. package/dist/{chunk-VHXOCDZ2.js.map → chunk-FROQPADA.js.map} +1 -1
  22. package/dist/{chunk-UGE57OTX.js → chunk-GWSJQSQE.js} +11 -8
  23. package/dist/chunk-GWSJQSQE.js.map +1 -0
  24. package/dist/chunk-XXNK4N7Z.js +683 -0
  25. package/dist/chunk-XXNK4N7Z.js.map +1 -0
  26. package/dist/chunk-ZOMUTU7J.js +249 -0
  27. package/dist/chunk-ZOMUTU7J.js.map +1 -0
  28. package/dist/{curvature-PkoLduGc.d.ts → curvature-CDOI32hv.d.ts} +46 -3
  29. package/dist/curves/index.d.ts +2 -2
  30. package/dist/curves/index.js +4 -4
  31. package/dist/index.d.ts +172 -45
  32. package/dist/index.js +244 -178
  33. package/dist/index.js.map +1 -1
  34. package/dist/nd/index.d.ts +28 -1
  35. package/dist/nd/index.js +27 -675
  36. package/dist/nd/index.js.map +1 -1
  37. package/dist/{path-BW8d_2RY.d.ts → path-t80OoJnM.d.ts} +11 -0
  38. package/dist/tessellate/index.d.ts +11 -4
  39. package/dist/tessellate/index.js +4 -4
  40. package/dist/warp/index.d.ts +1 -1
  41. package/dist/warp/index.js +238 -64
  42. package/dist/warp/index.js.map +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-2PKPKU4P.js.map +0 -1
  45. package/dist/chunk-B53BERKG.js.map +0 -1
  46. package/dist/chunk-Q66NA2FU.js.map +0 -1
  47. package/dist/chunk-STGSBIOL.js +0 -152
  48. package/dist/chunk-STGSBIOL.js.map +0 -1
  49. package/dist/chunk-UGE57OTX.js.map +0 -1
  50. package/dist/chunk-YPWFGFQX.js.map +0 -1
@@ -1,8 +1,8 @@
1
- import { orientBoundaries, parseContours, contoursIntersect, assembleRegions, signedArea, negligibleArea, regionArea, toPath, parseSubpaths, writePath, signChanges, sideCoeffs, evalSeg, extentOf, keepCorners, windingAt, reverseContour, sampleContour, subSeg, endSide } from '../chunk-YPWFGFQX.js';
2
- import '../chunk-CE7IY7TB.js';
3
- import { resolveCorners, DEFAULT_FLATTEN_TOLERANCE, fillsOpenSubpaths, flattenCubic, flattenQuadratic, pointInPolygon } from '../chunk-2PKPKU4P.js';
1
+ import { orientBoundaries, parseContours, contoursIntersect, assembleRegions, signedArea, negligibleArea, regionArea, toPath, parseSubpaths, writePath, signChanges, sideCoeffs, evalSeg, extentOf, keepCorners, windingAt, reverseContour, sampleContour, subSeg, endSide, intersectSegments, derivSeg } from '../chunk-3YH5XU3H.js';
2
+ import { nearestOnLine, nearestOnQuadratic, nearestOnCubic } from '../chunk-2N6OWA7E.js';
3
+ import { resolveCorners, DEFAULT_FLATTEN_TOLERANCE, fillsOpenSubpaths, flattenCubic, flattenQuadratic, pointInPolygon } from '../chunk-EURWZUNY.js';
4
4
  import '../chunk-755Y4YZS.js';
5
- import { PATH_Z, PATH_Q, elevateQuadraticToCubic, PATH_C, PATH_L, PATH_M } from '../chunk-B53BERKG.js';
5
+ import { worldCoords, PATH_Z, PATH_Q, elevateQuadraticToCubic, PATH_C, PATH_L, PATH_M, originFor, coordsFrom } from '../chunk-B3D6KWW3.js';
6
6
  import polygonClipping from 'polygon-clipping';
7
7
 
8
8
  // src/booleans/adapter.ts
@@ -25,7 +25,8 @@ function pathToMultiPolygon(given, opts = {}) {
25
25
  let cx = 0, cy = 0;
26
26
  let startX = 0, startY = 0;
27
27
  let ci = 0;
28
- const { commands, coords } = path;
28
+ const { commands } = path;
29
+ const coords = worldCoords(path);
29
30
  const fillsOpen = fillsOpenSubpaths(path);
30
31
  const finalizeCurrent = (closed = fillsOpen) => {
31
32
  if (!closed) current = null;
@@ -158,25 +159,25 @@ function groupRings(rings, fillRule) {
158
159
  parent[i] = best;
159
160
  }
160
161
  for (let i = 0; i < n; i++) if (parent[i] >= 0) children[parent[i]].push(i);
161
- const filled = new Array(n);
162
+ const filled2 = new Array(n);
162
163
  for (let i = 0; i < n; i++) {
163
164
  let depth = 0;
164
- let winding = 0;
165
+ let winding2 = 0;
165
166
  for (let a = i; a >= 0; a = parent[a]) {
166
- winding += orient[a];
167
+ winding2 += orient[a];
167
168
  if (a !== i) depth++;
168
169
  }
169
- filled[i] = fillRule === "evenodd" ? depth % 2 === 0 : winding !== 0;
170
+ filled2[i] = fillRule === "evenodd" ? depth % 2 === 0 : winding2 !== 0;
170
171
  }
171
172
  const out = [];
172
173
  for (let i = 0; i < n; i++) {
173
- if (!filled[i]) continue;
174
- if (parent[i] >= 0 && filled[parent[i]]) continue;
174
+ if (!filled2[i]) continue;
175
+ if (parent[i] >= 0 && filled2[parent[i]]) continue;
175
176
  const polygon = [rings[i]];
176
177
  const queue = [...children[i]];
177
178
  while (queue.length > 0) {
178
179
  const c = queue.pop();
179
- if (filled[c]) queue.push(...children[c]);
180
+ if (filled2[c]) queue.push(...children[c]);
180
181
  else polygon.push(rings[c]);
181
182
  }
182
183
  out.push(polygon);
@@ -195,7 +196,7 @@ function multiPolygonToPath(mp) {
195
196
  }
196
197
  }
197
198
  const commands = new Uint8Array(nCmds);
198
- const coords = new Float32Array(nCoords);
199
+ const world = new Float64Array(nCoords);
199
200
  let ci = 0;
200
201
  let pi = 0;
201
202
  for (const poly of mp) {
@@ -203,17 +204,18 @@ function multiPolygonToPath(mp) {
203
204
  if (ring.length < 4) continue;
204
205
  const unique = ring.length - 1;
205
206
  commands[ci++] = PATH_M;
206
- coords[pi++] = ring[0][0];
207
- coords[pi++] = ring[0][1];
207
+ world[pi++] = ring[0][0];
208
+ world[pi++] = ring[0][1];
208
209
  for (let k = 1; k < unique; k++) {
209
210
  commands[ci++] = PATH_L;
210
- coords[pi++] = ring[k][0];
211
- coords[pi++] = ring[k][1];
211
+ world[pi++] = ring[k][0];
212
+ world[pi++] = ring[k][1];
212
213
  }
213
214
  commands[ci++] = PATH_Z;
214
215
  }
215
216
  }
216
- return { kind: "polygon", commands, coords, fillRule: "nonzero" };
217
+ const origin = originFor(world);
218
+ return { kind: "polygon", commands, coords: coordsFrom(origin, world), fillRule: "nonzero", ...origin ? { origin } : {} };
217
219
  }
218
220
 
219
221
  // src/booleans/arrangement.ts
@@ -319,15 +321,15 @@ function cutAll(segs, eps) {
319
321
  }
320
322
  function linkFaces(xs, ys, origin) {
321
323
  const out = xs.map(() => []);
322
- const angle = [];
324
+ const angle2 = [];
323
325
  for (let h = 0; h < origin.length; h++) {
324
326
  const u = origin[h], v = origin[h ^ 1];
325
327
  out[u].push(h);
326
- angle[h] = Math.atan2(ys[v] - ys[u], xs[v] - xs[u]);
328
+ angle2[h] = Math.atan2(ys[v] - ys[u], xs[v] - xs[u]);
327
329
  }
328
330
  const slot = [];
329
331
  for (const list of out) {
330
- list.sort((a, b) => angle[a] - angle[b]);
332
+ list.sort((a, b) => angle2[a] - angle2[b]);
331
333
  list.forEach((h, k) => {
332
334
  slot[h] = k;
333
335
  });
@@ -483,30 +485,530 @@ function arrangementFaces(inputs) {
483
485
  return [...faces2.values()].sort((a, b) => popcount(a.mask) - popcount(b.mask) || (a.mask < b.mask ? -1 : a.mask > b.mask ? 1 : 0) || left(a) - left(b) || low(a) - low(b));
484
486
  }
485
487
 
488
+ // src/booleans/curveOverlap.ts
489
+ function nearestOnSeg(s, p) {
490
+ if (s.length === 2) return nearestOnLine(p.x, p.y, s[0].x, s[0].y, s[1].x, s[1].y);
491
+ if (s.length === 3) return nearestOnQuadratic(p.x, p.y, s[0].x, s[0].y, s[1].x, s[1].y, s[2].x, s[2].y);
492
+ return nearestOnCubic(p.x, p.y, s[0].x, s[0].y, s[1].x, s[1].y, s[2].x, s[2].y, s[3].x, s[3].y);
493
+ }
494
+ var SAMPLES = [0.25, 0.5, 0.75];
495
+ function sharedStretch(a, b, snap) {
496
+ const ends = [];
497
+ const add = (e) => {
498
+ if (!ends.some((f) => Math.hypot(f.p.x - e.p.x, f.p.y - e.p.y) <= snap)) ends.push(e);
499
+ };
500
+ for (const [tb, p] of [[0, b[0]], [1, b[b.length - 1]]]) {
501
+ const n = nearestOnSeg(a, p);
502
+ if (n.dist <= snap) add({ ta: n.t, tb, p });
503
+ }
504
+ for (const [ta, p] of [[0, a[0]], [1, a[a.length - 1]]]) {
505
+ const n = nearestOnSeg(b, p);
506
+ if (n.dist <= snap) add({ ta, tb: n.t, p });
507
+ }
508
+ let best = null;
509
+ for (let i = 0; i < ends.length; i++) {
510
+ for (let j = i + 1; j < ends.length; j++) {
511
+ const [e, f] = ends[i].ta <= ends[j].ta ? [ends[i], ends[j]] : [ends[j], ends[i]];
512
+ if (best && f.ta - e.ta <= best[1].ta - best[0].ta) continue;
513
+ const on = SAMPLES.every((s) => nearestOnSeg(b, evalSeg(a, e.ta + (f.ta - e.ta) * s)).dist <= snap);
514
+ if (on) best = [e, f];
515
+ }
516
+ }
517
+ return best;
518
+ }
519
+ function coincide(e, f, snap) {
520
+ const same = e.from === f.from && e.to === f.to;
521
+ if (!same && !(e.from === f.to && e.to === f.from)) return false;
522
+ return SAMPLES.every((s) => nearestOnSeg(f.seg, evalSeg(e.seg, s)).dist <= snap);
523
+ }
524
+ function mergeCoincident(edges, snap) {
525
+ const byEnds = /* @__PURE__ */ new Map();
526
+ const out = [];
527
+ for (const e of edges) {
528
+ const key = e.from < e.to ? `${e.from},${e.to}` : `${e.to},${e.from}`;
529
+ const list = byEnds.get(key) ?? [];
530
+ const twin = list.find((f) => coincide(f, e, snap));
531
+ if (twin) {
532
+ (twin.also ??= []).push({ operand: e.operand, sign: twin.from === e.from ? 1 : -1 });
533
+ continue;
534
+ }
535
+ list.push(e);
536
+ byEnds.set(key, list);
537
+ out.push(e);
538
+ }
539
+ return out;
540
+ }
541
+
542
+ // src/booleans/curveEdges.ts
543
+ var OVERLAP_HITS = 16;
544
+ var END_T = 1e-9;
545
+ var Vertices = class {
546
+ constructor(snap) {
547
+ this.snap = snap;
548
+ }
549
+ snap;
550
+ points = [];
551
+ grid = /* @__PURE__ */ new Map();
552
+ id(p) {
553
+ const gx = Math.floor(p.x / this.snap), gy = Math.floor(p.y / this.snap);
554
+ for (let dx = -1; dx <= 1; dx++) {
555
+ for (let dy = -1; dy <= 1; dy++) {
556
+ for (const i2 of this.grid.get(`${gx + dx},${gy + dy}`) ?? []) {
557
+ const q = this.points[i2];
558
+ if (Math.hypot(q.x - p.x, q.y - p.y) <= this.snap) return i2;
559
+ }
560
+ }
561
+ }
562
+ const i = this.points.length;
563
+ this.points.push({ x: p.x, y: p.y });
564
+ const key = `${gx},${gy}`;
565
+ const cell = this.grid.get(key);
566
+ if (cell) cell.push(i);
567
+ else this.grid.set(key, [i]);
568
+ return i;
569
+ }
570
+ };
571
+ function refsOf(operands) {
572
+ const out = [];
573
+ operands.forEach((contours, operand) => contours.forEach((c, contour) => c.forEach((seg, index) => {
574
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
575
+ for (const p of seg) {
576
+ x0 = Math.min(x0, p.x);
577
+ x1 = Math.max(x1, p.x);
578
+ y0 = Math.min(y0, p.y);
579
+ y1 = Math.max(y1, p.y);
580
+ }
581
+ out.push({ seg, operand, contour, index, count: c.length, box: [x0, y0, x1, y1] });
582
+ })));
583
+ return out;
584
+ }
585
+ var adjacent = (a, b) => {
586
+ if (a.operand !== b.operand || a.contour !== b.contour) return false;
587
+ const d = Math.abs(a.index - b.index);
588
+ return d === 1 || d === a.count - 1;
589
+ };
590
+ function loops(seg) {
591
+ if (seg.length < 4) return false;
592
+ const mid = evalSeg(seg, 0.5);
593
+ const l = subSeg(seg, 0, 0.5, seg[0], mid);
594
+ const r = subSeg(seg, 0.5, 1, mid, seg[3]);
595
+ if (!l || !r) return false;
596
+ return intersectSegments(l, r).some((h) => h.ta < 1 - 1e-6 || h.tb > 1e-6);
597
+ }
598
+ function cutEdges(operands, snap) {
599
+ const refs = refsOf(operands);
600
+ if (refs.some((r) => loops(r.seg))) return null;
601
+ const verts = new Vertices(snap);
602
+ const ends = refs.map(({ seg }) => [verts.id(seg[0]), verts.id(seg[seg.length - 1])]);
603
+ const stops = refs.map(() => []);
604
+ for (let i = 0; i < refs.length; i++) {
605
+ const a = refs[i];
606
+ for (let j = i + 1; j < refs.length; j++) {
607
+ const b = refs[j];
608
+ if (a.box[0] > b.box[2] + snap || b.box[0] > a.box[2] + snap || a.box[1] > b.box[3] + snap || b.box[1] > a.box[3] + snap) continue;
609
+ const shared = sharedStretch(a.seg, b.seg, snap);
610
+ if (shared) {
611
+ for (const end of shared) {
612
+ const v = verts.id(end.p);
613
+ stops[i].push({ t: end.ta, v });
614
+ stops[j].push({ t: end.tb, v });
615
+ }
616
+ continue;
617
+ }
618
+ const hits = intersectSegments(a.seg, b.seg);
619
+ if (hits.length >= OVERLAP_HITS) return null;
620
+ const near = adjacent(a, b);
621
+ for (const h of hits) {
622
+ if (near && (h.ta > 1 - END_T && h.tb < END_T || h.ta < END_T && h.tb > 1 - END_T)) continue;
623
+ const v = verts.id(h);
624
+ stops[i].push({ t: h.ta, v });
625
+ stops[j].push({ t: h.tb, v });
626
+ }
627
+ }
628
+ }
629
+ const edges = [];
630
+ refs.forEach((ref, i) => {
631
+ const { seg } = ref;
632
+ const list = [{ t: 0, v: ends[i][0] }, ...stops[i].sort((p, q) => p.t - q.t), { t: 1, v: ends[i][1] }];
633
+ const kept = [list[0]];
634
+ for (let k = 1; k < list.length; k++) {
635
+ const s = list[k];
636
+ if (s.v !== kept[kept.length - 1].v) kept.push(s);
637
+ else if (k === list.length - 1 && kept.length > 1) kept[kept.length - 1] = s;
638
+ }
639
+ const size = Math.hypot(ref.box[2] - ref.box[0], ref.box[3] - ref.box[1]);
640
+ if (kept.length === 1 && seg.length > 2 && size > 100 * snap) kept.push(list[list.length - 1]);
641
+ for (let k = 0; k + 1 < kept.length; k++) {
642
+ const a = kept[k], b = kept[k + 1];
643
+ const piece = a.t === 0 && b.t === 1 ? seg.slice() : subSeg(seg, a.t, b.t, verts.points[a.v], verts.points[b.v]);
644
+ if (!piece) continue;
645
+ piece[0] = verts.points[a.v];
646
+ piece[piece.length - 1] = verts.points[b.v];
647
+ edges.push({ seg: piece, from: a.v, to: b.v, operand: ref.operand, source: seg, t0: a.t, t1: b.t, key: i + a.t * 0.5, reversed: false });
648
+ }
649
+ });
650
+ return { edges: mergeCoincident(edges, snap), points: verts.points };
651
+ }
652
+
653
+ // src/booleans/curveClassify.ts
654
+ function windingSet(contours, fillRule) {
655
+ const segs = contours.flat();
656
+ const boxes = new Float64Array(segs.length * 4);
657
+ segs.forEach((s, i) => {
658
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
659
+ for (const p of s) {
660
+ x0 = Math.min(x0, p.x);
661
+ x1 = Math.max(x1, p.x);
662
+ y0 = Math.min(y0, p.y);
663
+ y1 = Math.max(y1, p.y);
664
+ }
665
+ boxes.set([x0, y0, x1, y1], i * 4);
666
+ });
667
+ return { segs, boxes, fillRule };
668
+ }
669
+ var RAY = { x: 1, y: 0 };
670
+ function winding(q, set) {
671
+ let w = 0;
672
+ const { segs, boxes } = set;
673
+ for (let i = 0; i < segs.length; i++) {
674
+ if (q.y < boxes[i * 4 + 1] || q.y > boxes[i * 4 + 3] || boxes[i * 4 + 2] < q.x) continue;
675
+ const s = segs[i];
676
+ for (const c of signChanges(sideCoeffs(s, q, RAY), true)) {
677
+ if (evalSeg(s, c.t).x > q.x) w += c.after > 0 ? 1 : -1;
678
+ }
679
+ }
680
+ return w;
681
+ }
682
+ var filled = (w, rule) => rule === "evenodd" ? (w & 1) !== 0 : w !== 0;
683
+ var reversed = (e) => ({ ...e, seg: e.seg.slice().reverse(), from: e.to, to: e.from, reversed: true });
684
+ function runs(e, operands) {
685
+ const n = new Array(operands).fill(0);
686
+ n[e.operand] = 1;
687
+ for (const { operand, sign } of e.also ?? []) n[operand] += sign;
688
+ return n;
689
+ }
690
+ function coverEdges(edges, sets, probe) {
691
+ const out = [];
692
+ for (const e of edges) {
693
+ const tm = (e.t0 + e.t1) / 2;
694
+ const m = evalSeg(e.source, tm);
695
+ let tan = derivSeg(e.source, tm);
696
+ let len = Math.hypot(tan.x, tan.y);
697
+ if (len === 0) {
698
+ const a = e.seg[0], b = e.seg[e.seg.length - 1];
699
+ tan = { x: b.x - a.x, y: b.y - a.y };
700
+ len = Math.hypot(tan.x, tan.y);
701
+ if (len === 0) continue;
702
+ }
703
+ const nx = -tan.y / len * probe, ny = tan.x / len * probe;
704
+ const L = { x: m.x + nx, y: m.y + ny }, R = { x: m.x - nx, y: m.y - ny };
705
+ const along2 = runs(e, sets.length);
706
+ const inL = new Array(sets.length);
707
+ const inR = new Array(sets.length);
708
+ for (let k = 0; k < sets.length; k++) {
709
+ const wl = winding(L, sets[k]), wr = winding(R, sets[k]);
710
+ if (Math.abs(wl - wr) !== Math.abs(along2[k])) return null;
711
+ inL[k] = filled(wl, sets[k].fillRule);
712
+ inR[k] = filled(wr, sets[k].fillRule);
713
+ }
714
+ out.push({ edge: e, inL, inR });
715
+ }
716
+ return out;
717
+ }
718
+
719
+ // src/booleans/curveTrace.ts
720
+ var TAU = Math.PI * 2;
721
+ var TIE = 1e-7;
722
+ function leaving(s) {
723
+ const a = s[0];
724
+ for (let i = 1; i < s.length; i++) if (s[i].x !== a.x || s[i].y !== a.y) return { x: s[i].x - a.x, y: s[i].y - a.y };
725
+ return { x: 0, y: 0 };
726
+ }
727
+ var backward = (s) => s.slice().reverse();
728
+ function along(s, dist) {
729
+ let len = 0;
730
+ for (let i = 0; i + 1 < s.length; i++) len += Math.hypot(s[i + 1].x - s[i].x, s[i + 1].y - s[i].y);
731
+ const p = evalSeg(s, len === 0 ? 0 : Math.min(1, dist / len));
732
+ return { x: p.x - s[0].x, y: p.y - s[0].y };
733
+ }
734
+ var angle = (d) => Math.atan2(d.y, d.x);
735
+ function clockwise(from, to) {
736
+ const d = (from - to) % TAU;
737
+ return d <= 0 ? d + TAU : d;
738
+ }
739
+ function chordLength(s) {
740
+ const a = s[0], b = s[s.length - 1];
741
+ return Math.hypot(b.x - a.x, b.y - a.y) || Math.hypot(s[1].x - a.x, s[1].y - a.y);
742
+ }
743
+ function nextEdge(e, out) {
744
+ const cands = out[e.to];
745
+ if (!cands || cands.length === 0) return -1;
746
+ if (cands.length === 1) return cands[0].key;
747
+ const back = backward(e.seg);
748
+ const turns = (dir) => {
749
+ const from = angle(dir(back));
750
+ return cands.map((c) => clockwise(from, angle(dir(c.seg))));
751
+ };
752
+ const tied = (t2) => {
753
+ const sorted = t2.slice().sort((x, y) => x - y);
754
+ return sorted[0] < TIE || sorted[sorted.length - 1] > TAU - TIE || sorted[1] - sorted[0] < TIE;
755
+ };
756
+ let t = turns(leaving);
757
+ if (tied(t)) {
758
+ const dist = 1e-3 * Math.min(chordLength(e.seg), ...cands.map((c) => chordLength(c.seg)));
759
+ t = turns((s) => along(s, dist));
760
+ if (tied(t)) return -1;
761
+ }
762
+ let best = 0;
763
+ for (let i = 1; i < t.length; i++) if (t[i] < t[best]) best = i;
764
+ return cands[best].key;
765
+ }
766
+ function joined(e, f) {
767
+ if (e.source !== f.source || e.reversed !== f.reversed) return null;
768
+ const [lo, hi] = e.reversed ? [f, e] : [e, f];
769
+ if (lo.t1 !== hi.t0) return null;
770
+ const t0 = lo.t0, t1 = hi.t1;
771
+ const a = lo.seg[e.reversed ? lo.seg.length - 1 : 0], b = hi.seg[e.reversed ? 0 : hi.seg.length - 1];
772
+ const seg = t0 === 0 && t1 === 1 ? lo.source.slice() : subSeg(lo.source, t0, t1, a, b);
773
+ if (!seg) return null;
774
+ seg[0] = a;
775
+ seg[seg.length - 1] = b;
776
+ if (e.reversed) seg.reverse();
777
+ return { ...e, seg, from: e.from, to: f.to, t0, t1 };
778
+ }
779
+ function rejoin(loop) {
780
+ const out = [];
781
+ for (const e of loop) {
782
+ const j = out.length > 0 ? joined(out[out.length - 1], e) : null;
783
+ if (j) out[out.length - 1] = j;
784
+ else out.push(e);
785
+ }
786
+ while (out.length > 1) {
787
+ const j = joined(out[out.length - 1], out[0]);
788
+ if (!j) break;
789
+ out.pop();
790
+ out[0] = j;
791
+ }
792
+ return out.map((e) => e.seg);
793
+ }
794
+ function traceLoops(edges, vertexCount) {
795
+ const byKey = /* @__PURE__ */ new Map();
796
+ const out = Array.from({ length: vertexCount }, () => []);
797
+ edges.forEach((e, i) => {
798
+ byKey.set(e.key, i);
799
+ out[e.from].push(e);
800
+ });
801
+ if (byKey.size !== edges.length) return null;
802
+ const next = edges.map((e) => {
803
+ const k = nextEdge(e, out);
804
+ return k < 0 ? -1 : byKey.get(k);
805
+ });
806
+ if (next.includes(-1)) return null;
807
+ const loopOf = new Int32Array(edges.length).fill(-1);
808
+ const loops2 = [];
809
+ for (let s = 0; s < edges.length; s++) {
810
+ if (loopOf[s] >= 0) continue;
811
+ const loop = [];
812
+ let e = s;
813
+ do {
814
+ if (loopOf[e] >= 0) return null;
815
+ loopOf[e] = loops2.length;
816
+ loop.push(e);
817
+ e = next[e];
818
+ } while (e !== s);
819
+ let first = 0;
820
+ for (let i = 1; i < loop.length; i++) if (edges[loop[i]].key < edges[loop[first]].key) first = i;
821
+ loops2.push(rejoin([...loop.slice(first), ...loop.slice(0, first)].map((i) => edges[i])));
822
+ }
823
+ return loops2;
824
+ }
825
+
826
+ // src/booleans/curveBoolean.ts
827
+ var MAX_SEGMENTS = 4096;
828
+ function hasCurve(path) {
829
+ const p = resolveCorners(path);
830
+ return p.kind === "polygon" && p.commands.some((c) => c === PATH_C || c === PATH_Q);
831
+ }
832
+ function closingTrimmed(loop) {
833
+ if (loop.length < 2) return loop;
834
+ const last = loop[loop.length - 1];
835
+ return last.length === 2 ? loop.slice(0, -1) : loop;
836
+ }
837
+ function cutPaths(paths) {
838
+ if (!paths.some(hasCurve)) return null;
839
+ const operands = paths.map((p) => parseContours(p));
840
+ const count = operands.reduce((n, cs) => n + cs.reduce((m, c) => m + c.length, 0), 0);
841
+ if (count === 0 || count > MAX_SEGMENTS) return null;
842
+ const extent = extentOf(operands.flat()) || 1;
843
+ const arrangement = cutEdges(operands, extent * 1e-6);
844
+ if (!arrangement) return null;
845
+ const sets = operands.map((cs, i) => {
846
+ const p = paths[i];
847
+ return windingSet(cs, p.kind === "polygon" ? p.fillRule : "nonzero");
848
+ });
849
+ const edges = coverEdges(arrangement.edges, sets, extent * 1e-7);
850
+ if (!edges) return null;
851
+ return { edges, vertexCount: arrangement.points.length, clockwise: operands.map(clockwiseFirst) };
852
+ }
853
+ function curveRegions(cut, label) {
854
+ const sides = /* @__PURE__ */ new Map();
855
+ const add = (k, e) => {
856
+ const list = sides.get(k);
857
+ if (list) list.push(e);
858
+ else sides.set(k, [e]);
859
+ };
860
+ for (const { edge, inL, inR } of cut.edges) {
861
+ const l = label(inL), r = label(inR);
862
+ if (l === r) continue;
863
+ if (l !== -1) add(l, edge);
864
+ if (r !== -1) add(r, reversed(edge));
865
+ }
866
+ const out = /* @__PURE__ */ new Map();
867
+ for (const [k, edges] of sides) {
868
+ const loops2 = traceLoops(edges, cut.vertexCount);
869
+ if (!loops2) return null;
870
+ out.set(k, loops2);
871
+ }
872
+ return out;
873
+ }
874
+ function writeLoops(loops2, clockwise2) {
875
+ const turned = clockwise2 ? loops2.map(reversedFromFirst) : loops2;
876
+ return writePath(turned.map((l) => ({ segs: closingTrimmed(l), closed: true })), "nonzero");
877
+ }
878
+ function curveBoolean(paths, keep) {
879
+ const cut = cutPaths(paths);
880
+ if (!cut) return null;
881
+ const regions = curveRegions(cut, (inside) => keep(inside) ? 0 : -1);
882
+ if (!regions) return null;
883
+ return writeLoops(regions.get(0) ?? [], cut.clockwise[0]);
884
+ }
885
+ function clockwiseFirst(contours) {
886
+ let best = 0;
887
+ for (const c of contours) {
888
+ const a = signedArea(c);
889
+ if (Math.abs(a) > Math.abs(best)) best = a;
890
+ }
891
+ return best < 0;
892
+ }
893
+ function reversedFromFirst(loop) {
894
+ const rev = reverseContour(loop);
895
+ return [rev[rev.length - 1], ...rev.slice(0, -1)];
896
+ }
897
+
898
+ // src/booleans/curvePieces.ts
899
+ var covering = (inside) => {
900
+ const s = [];
901
+ inside.forEach((v, i) => {
902
+ if (v) s.push(i);
903
+ });
904
+ return s;
905
+ };
906
+ var maskOf = (sources) => sources.reduce((m, i) => m | 1n << BigInt(i), 0n);
907
+ function corner(loops2) {
908
+ const pts = sampleContour(loops2[0]);
909
+ let x = Infinity, y = Infinity;
910
+ for (let i = 0; i < pts.length; i += 2) {
911
+ x = Math.min(x, pts[i]);
912
+ y = Math.min(y, pts[i + 1]);
913
+ }
914
+ return [x, y];
915
+ }
916
+ function connected(loops2) {
917
+ const outers = [], holes = [];
918
+ for (const l of loops2) (signedArea(l) > 0 ? outers : holes).push(l);
919
+ return assembleRegions(outers, holes);
920
+ }
921
+ function curveDivide(paths, split) {
922
+ const cut = cutPaths(paths);
923
+ if (!cut) return null;
924
+ const ids = /* @__PURE__ */ new Map();
925
+ const sets = [];
926
+ const regions = curveRegions(cut, (inside) => {
927
+ const s = covering(inside);
928
+ if (s.length === 0) return -1;
929
+ const key = s.join(",");
930
+ let id = ids.get(key);
931
+ if (id === void 0) {
932
+ id = sets.length;
933
+ ids.set(key, id);
934
+ sets.push(s);
935
+ }
936
+ return id;
937
+ });
938
+ if (!regions) return null;
939
+ const out = [];
940
+ for (const [id, loops2] of regions) {
941
+ const sources = sets[id];
942
+ const mask = maskOf(sources);
943
+ for (const face of split ? connected(loops2) : [loops2]) {
944
+ out.push({ piece: { path: writeLoops(face, cut.clockwise[sources[0]]), sources }, mask, at: corner(face) });
945
+ }
946
+ }
947
+ return out.sort((a, b) => a.piece.sources.length - b.piece.sources.length || (a.mask < b.mask ? -1 : a.mask > b.mask ? 1 : 0) || a.at[0] - b.at[0] || a.at[1] - b.at[1]).map((o) => o.piece);
948
+ }
949
+ var topmost = (inside) => inside.lastIndexOf(true);
950
+ function shown(cut) {
951
+ return curveRegions(cut, topmost);
952
+ }
953
+ function curveTrim(paths) {
954
+ const cut = cutPaths(paths);
955
+ const regions = cut && shown(cut);
956
+ if (!cut || !regions) return null;
957
+ return [...regions.keys()].sort((a, b) => a - b).map((i) => ({ path: writeLoops(regions.get(i), cut.clockwise[i]), sources: [i] }));
958
+ }
959
+ function curveMerge(paths, keyOf) {
960
+ const cut = cutPaths(paths);
961
+ const visible = cut && shown(cut);
962
+ if (!cut || !visible) return null;
963
+ const groupIds = /* @__PURE__ */ new Map();
964
+ const groupOf = paths.map((_, i) => {
965
+ const key = keyOf(i);
966
+ let g = groupIds.get(key);
967
+ if (g === void 0) {
968
+ g = groupIds.size;
969
+ groupIds.set(key, g);
970
+ }
971
+ return g;
972
+ });
973
+ const regions = curveRegions(cut, (inside) => {
974
+ const top = topmost(inside);
975
+ return top < 0 ? -1 : groupOf[top];
976
+ });
977
+ if (!regions) return null;
978
+ const pieces = [];
979
+ for (const [g, loops2] of regions) {
980
+ const sources = [...visible.keys()].filter((i) => groupOf[i] === g).sort((a, b) => a - b);
981
+ pieces.push({ path: writeLoops(loops2, cut.clockwise[sources[0]]), sources });
982
+ }
983
+ return pieces.sort((a, b) => a.sources[a.sources.length - 1] - b.sources[b.sources.length - 1]);
984
+ }
985
+
486
986
  // src/booleans/ops.ts
487
987
  function toPath2(mp, sources) {
488
988
  return keepCorners(sources, multiPolygonToPath(mp));
489
989
  }
490
- function pathUnion(...paths) {
990
+ function boolean(paths, keep, clip) {
491
991
  if (paths.length === 0) return multiPolygonToPath([]);
492
- const mps = paths.map((p) => pathToMultiPolygon(p));
493
- const [head, ...rest] = mps;
494
- return toPath2(polygonClipping.union(head, ...rest), paths);
992
+ const curved = curveBoolean(paths, keep);
993
+ if (curved) return keepCorners(paths, curved);
994
+ const [head, ...rest] = paths.map((p) => pathToMultiPolygon(p));
995
+ return toPath2(clip(head, ...rest), paths);
996
+ }
997
+ var any = (inside) => inside.includes(true);
998
+ var all = (inside) => !inside.includes(false);
999
+ var aNotB = (inside) => inside[0] && !inside[1];
1000
+ var odd = (inside) => inside.filter(Boolean).length % 2 === 1;
1001
+ function pathUnion(...paths) {
1002
+ return boolean(paths, any, polygonClipping.union);
495
1003
  }
496
1004
  function pathIntersect(...paths) {
497
- if (paths.length === 0) return multiPolygonToPath([]);
498
- const mps = paths.map((p) => pathToMultiPolygon(p));
499
- const [head, ...rest] = mps;
500
- return toPath2(polygonClipping.intersection(head, ...rest), paths);
1005
+ return boolean(paths, all, polygonClipping.intersection);
501
1006
  }
502
1007
  function pathSubtract(a, b) {
503
- return toPath2(polygonClipping.difference(pathToMultiPolygon(a), pathToMultiPolygon(b)), [a, b]);
1008
+ return boolean([a, b], aNotB, polygonClipping.difference);
504
1009
  }
505
1010
  function pathExclude(...paths) {
506
- if (paths.length === 0) return multiPolygonToPath([]);
507
- const mps = paths.map((p) => pathToMultiPolygon(p));
508
- const [head, ...rest] = mps;
509
- return toPath2(polygonClipping.xor(head, ...rest), paths);
1011
+ return boolean(paths, odd, polygonClipping.xor);
510
1012
  }
511
1013
  function pathDivide(...paths) {
512
1014
  return pathDividePieces(...paths).map((p) => p.path);
@@ -514,6 +1016,7 @@ function pathDivide(...paths) {
514
1016
  function pathCrop(...paths) {
515
1017
  return pathCropPieces(...paths).map((p) => p.path);
516
1018
  }
1019
+ var cornered = (pieces, sources) => pieces.map(({ path, sources: s }) => ({ path: keepCorners(sources, path), sources: s }));
517
1020
  function faces(paths) {
518
1021
  const clean = paths.map((p) => {
519
1022
  const mp = pathToMultiPolygon(p);
@@ -526,6 +1029,8 @@ function faces(paths) {
526
1029
  });
527
1030
  }
528
1031
  function pathDividePieces(...paths) {
1032
+ const curved = curveDivide(paths, false);
1033
+ if (curved) return cornered(curved, paths);
529
1034
  const regions = /* @__PURE__ */ new Map();
530
1035
  for (const { polygon, sources } of faces(paths)) {
531
1036
  const key = sources.join(",");
@@ -536,20 +1041,26 @@ function pathDividePieces(...paths) {
536
1041
  return [...regions.values()].map(({ polygons, sources }) => ({ path: toPath2(polygons, paths), sources }));
537
1042
  }
538
1043
  function pathFacePieces(...paths) {
1044
+ const curved = curveDivide(paths, true);
1045
+ if (curved) return cornered(curved, paths);
539
1046
  return faces(paths).map(({ polygon, sources }) => ({ path: toPath2([polygon], paths), sources }));
540
1047
  }
541
1048
  function pathTrim(...paths) {
1049
+ const curved = curveTrim(paths);
1050
+ if (curved) return cornered(curved, paths);
542
1051
  const mps = paths.map((p) => pathToMultiPolygon(p));
543
1052
  const out = [];
544
1053
  let cover = null;
545
1054
  for (let i = mps.length - 1; i >= 0; i--) {
546
- const shown = cover ? polygonClipping.difference(mps[i], cover) : mps[i];
547
- if (shown.length > 0) out.unshift({ path: toPath2(shown, paths), sources: [i] });
1055
+ const shown2 = cover ? polygonClipping.difference(mps[i], cover) : mps[i];
1056
+ if (shown2.length > 0) out.unshift({ path: toPath2(shown2, paths), sources: [i] });
548
1057
  cover = cover ? polygonClipping.union(cover, mps[i]) : mps[i];
549
1058
  }
550
1059
  return out;
551
1060
  }
552
1061
  function pathMerge(paths, keyOf) {
1062
+ const curved = curveMerge(paths, keyOf);
1063
+ if (curved) return cornered(curved, paths);
553
1064
  const groups = /* @__PURE__ */ new Map();
554
1065
  for (const piece of pathTrim(...paths)) {
555
1066
  const key = keyOf(piece.sources[0]);
@@ -565,12 +1076,11 @@ function pathMerge(paths, keyOf) {
565
1076
  }
566
1077
  function pathCropPieces(...paths) {
567
1078
  if (paths.length < 2) return [];
568
- const mps = paths.map((p) => pathToMultiPolygon(p));
569
- const mask = mps[mps.length - 1];
1079
+ const mask = paths[paths.length - 1];
570
1080
  const out = [];
571
- for (let i = 0; i < mps.length - 1; i++) {
572
- const clipped = polygonClipping.intersection(mps[i], mask);
573
- if (clipped.length > 0) out.push({ path: toPath2(clipped, paths), sources: [i] });
1081
+ for (let i = 0; i < paths.length - 1; i++) {
1082
+ const clipped = pathIntersect(paths[i], mask);
1083
+ if (clipped.commands.length > 0) out.push({ path: clipped, sources: [i] });
574
1084
  }
575
1085
  return out;
576
1086
  }
@@ -658,8 +1168,8 @@ function meshOutline(rings) {
658
1168
  }
659
1169
  }
660
1170
  }
661
- const loops = walkLoops(edges).map((loop) => loop.map((k) => points.get(k)));
662
- return loops.length === 0 ? null : bridge(loops);
1171
+ const loops2 = walkLoops(edges).map((loop) => loop.map((k) => points.get(k)));
1172
+ return loops2.length === 0 ? null : bridge(loops2);
663
1173
  }
664
1174
  function walkLoops(edges) {
665
1175
  const out = /* @__PURE__ */ new Map();
@@ -669,7 +1179,7 @@ function walkLoops(edges) {
669
1179
  if (!list) out.set(a, list = []);
670
1180
  for (let i = 0; i < n; i++) list.push(b);
671
1181
  }
672
- const loops = [];
1182
+ const loops2 = [];
673
1183
  for (const start of out.keys()) {
674
1184
  while ((out.get(start)?.length ?? 0) > 0) {
675
1185
  const loop = [start];
@@ -679,13 +1189,13 @@ function walkLoops(edges) {
679
1189
  const next = out.get(at);
680
1190
  at = next.pop();
681
1191
  }
682
- loops.push(loop);
1192
+ loops2.push(loop);
683
1193
  }
684
1194
  }
685
- return loops;
1195
+ return loops2;
686
1196
  }
687
- function bridge(loops) {
688
- const entered = loops.map((loop) => {
1197
+ function bridge(loops2) {
1198
+ const entered = loops2.map((loop) => {
689
1199
  let k = 0;
690
1200
  for (let i = 1; i < loop.length; i++) if (loop[i][0] < loop[k][0] || loop[i][0] === loop[k][0] && loop[i][1] < loop[k][1]) k = i;
691
1201
  return [...loop.slice(k), ...loop.slice(0, k)];
@@ -785,7 +1295,7 @@ function properCrossing(p0, p1, q0, q1) {
785
1295
  }
786
1296
  function splitLoops(run) {
787
1297
  const spine = [run[0]];
788
- const loops = [];
1298
+ const loops2 = [];
789
1299
  for (let i = 1; i < run.length; i++) {
790
1300
  const to = run[i];
791
1301
  for (; ; ) {
@@ -801,17 +1311,17 @@ function splitLoops(run) {
801
1311
  spine.push(to);
802
1312
  break;
803
1313
  }
804
- loops.push([hit.x, ...spine.slice(hit.j + 1)]);
1314
+ loops2.push([hit.x, ...spine.slice(hit.j + 1)]);
805
1315
  spine.length = hit.j + 1;
806
1316
  spine.push(hit.x);
807
1317
  }
808
1318
  }
809
1319
  const last = spine[spine.length - 1];
810
1320
  if (spine.length >= 4 && last.x === spine[0].x && last.y === spine[0].y) {
811
- loops.push(spine.slice(0, -1));
812
- return { spine: [], loops };
1321
+ loops2.push(spine.slice(0, -1));
1322
+ return { spine: [], loops: loops2 };
813
1323
  }
814
- return { spine, loops };
1324
+ return { spine, loops: loops2 };
815
1325
  }
816
1326
  function pointAt(cut, s) {
817
1327
  const k = Math.min(Math.floor(s), cut.length - 2);
@@ -842,7 +1352,7 @@ function interiorLoops(cut, boundaries) {
842
1352
  const last = cut.length - 1;
843
1353
  const stops = [{ s: 0, pt: cut[0] }, ...crossings, { s: last, pt: cut[last] }];
844
1354
  const rings = [];
845
- const loops = [];
1355
+ const loops2 = [];
846
1356
  for (let i = 0; i + 1 < stops.length; i++) {
847
1357
  const a = stops[i], b = stops[i + 1];
848
1358
  if (b.s - a.s <= 1e-9) continue;
@@ -854,15 +1364,15 @@ function interiorLoops(cut, boundaries) {
854
1364
  const ring = [...loop, loop[0]];
855
1365
  if (chords.some((c) => polylinesCross(ring, c)) || rings.some((r) => polylinesCross(ring, r))) continue;
856
1366
  rings.push(ring);
857
- loops.push(loop);
1367
+ loops2.push(loop);
858
1368
  }
859
1369
  }
860
- return loops;
1370
+ return loops2;
861
1371
  }
862
- function cutOutLoops(regions, loops) {
1372
+ function cutOutLoops(regions, loops2) {
863
1373
  let cut = false;
864
1374
  const minArea = negligibleArea(regions.flat());
865
- for (const loop of loops) {
1375
+ for (const loop of loops2) {
866
1376
  let ring = loop.map((p, i) => [p, loop[(i + 1) % loop.length]]);
867
1377
  const a = signedArea(ring);
868
1378
  if (Math.abs(a) <= minArea) continue;
@@ -892,7 +1402,7 @@ function polylinesCross(a, b) {
892
1402
  }
893
1403
 
894
1404
  // src/booleans/cutLoops.ts
895
- function traceLoops(boundaries, partner, chord) {
1405
+ function traceLoops2(boundaries, partner, chord) {
896
1406
  const byContour = /* @__PURE__ */ new Map();
897
1407
  for (const c of partner.keys()) {
898
1408
  const list = byContour.get(c.contour) ?? [];
@@ -913,7 +1423,7 @@ function traceLoops(boundaries, partner, chord) {
913
1423
  arcFrom.set(from, { segs: arcBetween(contour, from, to, cuts.length === 1), end: to });
914
1424
  }
915
1425
  });
916
- const loops = [];
1426
+ const loops2 = [];
917
1427
  const seen = /* @__PURE__ */ new Set();
918
1428
  for (const start of arcFrom.keys()) {
919
1429
  if (seen.has(start)) continue;
@@ -927,9 +1437,9 @@ function traceLoops(boundaries, partner, chord) {
927
1437
  for (let i = 0; i + 1 < run.length; i++) loop.push([run[i], run[i + 1]]);
928
1438
  at = partner.get(arc.end);
929
1439
  }
930
- loops.push(loop);
1440
+ loops2.push(loop);
931
1441
  }
932
- return { loops, untouched };
1442
+ return { loops: loops2, untouched };
933
1443
  }
934
1444
  function arcBetween(contour, from, to, whole) {
935
1445
  const out = [];
@@ -960,11 +1470,11 @@ function splitPathByPolyline(path, cut, opts = {}) {
960
1470
  const boundaries = resolveBoundaries(path, opts);
961
1471
  if (boundaries.length === 0) return null;
962
1472
  pts = openClosedCut(pts, boundaries);
963
- const loops = interiorLoops(pts, boundaries);
1473
+ const loops2 = interiorLoops(pts, boundaries);
964
1474
  const pieces = knife(path, pts, opts, pts.length);
965
- if (loops.length === 0) return pieces;
1475
+ if (loops2.length === 0) return pieces;
966
1476
  const regions = pieces ? pieces.map((p) => orientBoundaries(parseContours(p), outRule)) : [boundaries];
967
- if (!cutOutLoops(regions, loops)) return pieces;
1477
+ if (!cutOutLoops(regions, loops2)) return pieces;
968
1478
  return regions.map((r) => toPath(r, outRule));
969
1479
  }
970
1480
  var fillRuleOf = (path) => path.kind === "polygon" ? path.fillRule : "nonzero";
@@ -999,8 +1509,8 @@ function knife(path, cut, opts, budget) {
999
1509
  chord.set(c1, run.slice().reverse());
1000
1510
  }
1001
1511
  if (partner.size === 0) return null;
1002
- const { loops, untouched } = traceLoops(boundaries, partner, chord);
1003
- const traced = assembleRegions(loops, untouched.filter((c) => signedArea(c) < 0));
1512
+ const { loops: loops2, untouched } = traceLoops2(boundaries, partner, chord);
1513
+ const traced = assembleRegions(loops2, untouched.filter((c) => signedArea(c) < 0));
1004
1514
  const islands = untouched.filter((c) => signedArea(c) > 0);
1005
1515
  const minArea = negligibleArea(boundaries);
1006
1516
  const regions = traced.filter((r) => Math.abs(regionArea(r)) > minArea);
@@ -1016,7 +1526,7 @@ function snipPathByPolyline(path, cut) {
1016
1526
  const pts = distinctPoints(cut);
1017
1527
  if (pts.length < 2) return null;
1018
1528
  const rule = path.kind === "polygon" ? path.fillRule : "nonzero";
1019
- const runs = [];
1529
+ const runs2 = [];
1020
1530
  const missed = [];
1021
1531
  for (const sub of parseSubpaths(path, false)) {
1022
1532
  const stops = snipStations(sub, pts);
@@ -1027,20 +1537,20 @@ function snipPathByPolyline(path, cut) {
1027
1537
  const segs = sub.segs;
1028
1538
  if (sub.closed) {
1029
1539
  for (let i = 0; i < stops.length; i++) {
1030
- runs.push(arcBetween(segs, stops[i], stops[(i + 1) % stops.length], stops.length === 1));
1540
+ runs2.push(arcBetween(segs, stops[i], stops[(i + 1) % stops.length], stops.length === 1));
1031
1541
  }
1032
1542
  } else {
1033
1543
  const last = segs[segs.length - 1];
1034
- const all = [
1544
+ const all2 = [
1035
1545
  { seg: 0, t: 0, pt: segs[0][0] },
1036
1546
  ...stops,
1037
1547
  { seg: segs.length - 1, t: 1, pt: last[last.length - 1] }
1038
1548
  ];
1039
- for (let i = 0; i + 1 < all.length; i++) runs.push(arcBetween(segs, all[i], all[i + 1], false));
1549
+ for (let i = 0; i + 1 < all2.length; i++) runs2.push(arcBetween(segs, all2[i], all2[i + 1], false));
1040
1550
  }
1041
1551
  }
1042
- if (runs.length === 0) return null;
1043
- const pieces = runs.filter((r) => r.length > 0).map((r) => writePath([{ segs: r, closed: false }], rule));
1552
+ if (runs2.length === 0) return null;
1553
+ const pieces = runs2.filter((r) => r.length > 0).map((r) => writePath([{ segs: r, closed: false }], rule));
1044
1554
  if (missed.length > 0) pieces.push(writePath(missed, rule));
1045
1555
  return pieces;
1046
1556
  }