@pixerate/editor 0.34.0 → 0.35.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.
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;
2
2
 
3
3
  var _chunkQKLRAA4Tcjs = require('../chunk-QKLRAA4T.cjs');
4
4
 
@@ -17,10 +17,20 @@ var _chunkQKLRAA4Tcjs = require('../chunk-QKLRAA4T.cjs');
17
17
 
18
18
 
19
19
 
20
- var _chunkUG5DVXJKcjs = require('../chunk-UG5DVXJK.cjs');
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+ var _chunkBGNPT5PEcjs = require('../chunk-BGNPT5PE.cjs');
21
31
 
22
32
  // src/vector/types.ts
23
- var VECTOR_TOOLS = ["select", "direct", "pen", "pencil", "rect", "ellipse", "polygon", "star", "line"];
33
+ var VECTOR_TOOLS = ["select", "direct", "pen", "pencil", "rect", "ellipse", "polygon", "star", "line", "scissors", "knife", "eraser"];
24
34
  var VECTOR_TRANSFORM_HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w", "rotate"];
25
35
 
26
36
  // src/vector/geometry.ts
@@ -84,7 +94,7 @@ function vectorNodeToBezier(node) {
84
94
  }
85
95
  function vectorPathData(input, precision = 3) {
86
96
  const subpaths = Array.isArray(input) ? input : input.subpaths;
87
- return _chunkUG5DVXJKcjs.bezierPathToSvg.call(void 0, subpathsToBezier(subpaths), { precision });
97
+ return _chunkBGNPT5PEcjs.bezierPathToSvg.call(void 0, subpathsToBezier(subpaths), { precision });
88
98
  }
89
99
  function inferAnchorType(a) {
90
100
  if (isOnAnchor(a, a.in) || isOnAnchor(a, a.out)) return "corner";
@@ -100,7 +110,7 @@ function inferAnchorType(a) {
100
110
  return Math.abs(li - lo) <= 1e-3 * Math.max(li, lo) ? "symmetric" : "smooth";
101
111
  }
102
112
  function bezierToSubpaths(input) {
103
- const path = _chunkUG5DVXJKcjs.toBezierPath.call(void 0, input);
113
+ const path = _chunkBGNPT5PEcjs.toBezierPath.call(void 0, input);
104
114
  const out = [];
105
115
  let current = null;
106
116
  const last = () => current.anchors[current.anchors.length - 1];
@@ -197,14 +207,14 @@ function nodeMatrix(node) {
197
207
  function vectorPathWorldBezier(node, parent = IDENTITY_MATRIX) {
198
208
  const m = multiplyMatrices(nodeMatrix(node), parent);
199
209
  const path = subpathsToBezier(node.subpaths);
200
- return isIdentityMatrix(m) ? path : _chunkUG5DVXJKcjs.transformBezierPath.call(void 0, path, m);
210
+ return isIdentityMatrix(m) ? path : _chunkBGNPT5PEcjs.transformBezierPath.call(void 0, path, m);
201
211
  }
202
212
  function vectorNodeBounds(node, parent = IDENTITY_MATRIX) {
203
213
  if (node.type === "group") {
204
214
  const m = multiplyMatrices(nodeMatrix(node), parent);
205
215
  return unionBounds(node.children.map((c) => vectorNodeBounds(c, m)));
206
216
  }
207
- const b = _chunkUG5DVXJKcjs.bezierPathBounds.call(void 0, vectorPathWorldBezier(node, parent));
217
+ const b = _chunkBGNPT5PEcjs.bezierPathBounds.call(void 0, vectorPathWorldBezier(node, parent));
208
218
  if (!b) return null;
209
219
  return { x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY };
210
220
  }
@@ -238,9 +248,9 @@ function hitTestVectorNode(node, p, tolerance = 0, parent = IDENTITY_MATRIX) {
238
248
  const path = vectorPathWorldBezier(node, parent);
239
249
  if (path.length === 0) return false;
240
250
  const point = [p.x, p.y];
241
- if (hasPaint(node.fill) && _chunkUG5DVXJKcjs.pointInBezierPath.call(void 0, path, point, _nullishCoalesce(node.fillRule, () => ( "nonzero")))) return true;
251
+ if (hasPaint(node.fill) && _chunkBGNPT5PEcjs.pointInBezierPath.call(void 0, path, point, _nullishCoalesce(node.fillRule, () => ( "nonzero")))) return true;
242
252
  const reach = (hasPaint(node.stroke) ? (_nullishCoalesce(node.strokeWidth, () => ( 1))) / 2 : 0) + tolerance;
243
- return _chunkUG5DVXJKcjs.distanceToBezierPath.call(void 0, path, point) <= reach;
253
+ return _chunkBGNPT5PEcjs.distanceToBezierPath.call(void 0, path, point) <= reach;
244
254
  }
245
255
  function hitTestVectorNodes(nodes, p, tolerance = 0) {
246
256
  for (let i = nodes.length - 1; i >= 0; i--) {
@@ -256,9 +266,9 @@ function nearestSegment(node, p) {
256
266
  const count = subpathSegmentCount(sp);
257
267
  for (let i = 0; i < count; i++) {
258
268
  const seg = subpathSegment(sp, i);
259
- const hit = _chunkUG5DVXJKcjs.segmentNearest.call(void 0, seg, [p.x, p.y]);
269
+ const hit = _chunkBGNPT5PEcjs.segmentNearest.call(void 0, seg, [p.x, p.y]);
260
270
  if (!best || hit.distance < best.distance) {
261
- const pt = _chunkUG5DVXJKcjs.segmentPoint.call(void 0, seg, hit.t);
271
+ const pt = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, hit.t);
262
272
  best = { subpath: s, segment: i, t: hit.t, point: { x: pt[0], y: pt[1] }, distance: hit.distance };
263
273
  }
264
274
  }
@@ -429,7 +439,7 @@ function insertAnchor(sp, segment, t) {
429
439
  const ia = segment;
430
440
  const ib = (segment + 1) % n;
431
441
  const seg = subpathSegment(sp, segment);
432
- const [left, right] = _chunkUG5DVXJKcjs.splitSegment.call(void 0, seg, t);
442
+ const [left, right] = _chunkBGNPT5PEcjs.splitSegment.call(void 0, seg, t);
433
443
  const mid = left[left.length - 1];
434
444
  const anchor = { x: mid[0], y: mid[1] };
435
445
  if (seg.length === 4) {
@@ -537,7 +547,7 @@ var escapeAttr = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;")
537
547
  function vectorPaintToSvg(paint, gradientId) {
538
548
  if (!hasPaint(paint)) return { value: "none", gradient: null };
539
549
  if (typeof paint === "string") return { value: paint, gradient: null };
540
- return { value: `url(#${gradientId})`, gradient: _chunkUG5DVXJKcjs.gradientToSvgDef.call(void 0, paint, gradientId) };
550
+ return { value: `url(#${gradientId})`, gradient: _chunkBGNPT5PEcjs.gradientToSvgDef.call(void 0, paint, gradientId) };
541
551
  }
542
552
  function gradientMarkup(def) {
543
553
  const tag = def.type === "radial" ? "radialGradient" : "linearGradient";
@@ -551,11 +561,11 @@ function vectorDocumentToSvg(doc, options = {}) {
551
561
  const pretty = options.pretty !== false;
552
562
  const defs = [];
553
563
  const lines = [];
554
- const num = (n) => _chunkUG5DVXJKcjs.formatNumber.call(void 0, n, precision);
564
+ const num = (n) => _chunkBGNPT5PEcjs.formatNumber.call(void 0, n, precision);
555
565
  const indent = (depth) => pretty ? " ".repeat(depth) : "";
556
566
  const common = (node) => {
557
567
  let out2 = ` id="${escapeAttr(node.id)}"`;
558
- if (node.transform) out2 += ` transform="matrix(${node.transform.map((n) => _chunkUG5DVXJKcjs.formatNumber.call(void 0, n, 6)).join(" ")})"`;
568
+ if (node.transform) out2 += ` transform="matrix(${node.transform.map((n) => _chunkBGNPT5PEcjs.formatNumber.call(void 0, n, 6)).join(" ")})"`;
559
569
  if (node.opacity !== void 0 && node.opacity < 1) out2 += ` opacity="${num(node.opacity)}"`;
560
570
  if (node.visible === false) out2 += ` display="none"`;
561
571
  return out2;
@@ -1029,13 +1039,13 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
1029
1039
  return budget;
1030
1040
  }
1031
1041
  pushRect(r, kind, centre) {
1032
- const corners = [
1042
+ const corners2 = [
1033
1043
  { x: r.x, y: r.y },
1034
1044
  { x: r.x + r.width, y: r.y },
1035
1045
  { x: r.x + r.width, y: r.y + r.height },
1036
1046
  { x: r.x, y: r.y + r.height }
1037
1047
  ];
1038
- for (const p of corners) {
1048
+ for (const p of corners2) {
1039
1049
  this.xs.push({ v: p.x, from: p, kind });
1040
1050
  this.ys.push({ v: p.y, from: p, kind });
1041
1051
  }
@@ -1136,14 +1146,14 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
1136
1146
  }
1137
1147
  const point = this.nearestPoint(p, tol);
1138
1148
  if (point) return at(point.p, point.kind);
1139
- const near = this.segmentsNear(p, tol);
1140
- const crossing = this.crossing(near, p, tol);
1149
+ const near2 = this.segmentsNear(p, tol);
1150
+ const crossing = this.crossing(near2, p, tol);
1141
1151
  if (crossing) return at(crossing, "intersect");
1142
1152
  if (leave && leave.curves !== false) {
1143
- const foot = this.curvePoint(near, leave.at, p, tol);
1153
+ const foot = this.curvePoint(near2, leave.at, p, tol);
1144
1154
  if (foot) return at(foot.point, foot.kind, [line("construction", leave.at, foot.point)]);
1145
1155
  }
1146
- const on = this.onSegment(near);
1156
+ const on = this.onSegment(near2);
1147
1157
  if (on) return at(on, "path");
1148
1158
  if (_optionalChain([leave, 'optionalAccess', _15 => _15.angles, 'optionalAccess', _16 => _16.length])) {
1149
1159
  const c = construction(leave, p, tol);
@@ -1221,22 +1231,22 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
1221
1231
  const pt = toPoint(p);
1222
1232
  for (const s of this.segments) {
1223
1233
  if (p.x < s.minX - tol || p.x > s.maxX + tol || p.y < s.minY - tol || p.y > s.maxY + tol) continue;
1224
- const hit = _chunkUG5DVXJKcjs.segmentNearest.call(void 0, s.seg, pt);
1234
+ const hit = _chunkBGNPT5PEcjs.segmentNearest.call(void 0, s.seg, pt);
1225
1235
  if (hit.distance <= tol) out.push({ seg: s.seg, t: hit.t, distance: hit.distance });
1226
1236
  }
1227
1237
  return out.sort((a, b) => a.distance - b.distance);
1228
1238
  }
1229
- onSegment(near) {
1230
- return near.length ? toVec(_chunkUG5DVXJKcjs.segmentPoint.call(void 0, near[0].seg, near[0].t)) : null;
1239
+ onSegment(near2) {
1240
+ return near2.length ? toVec(_chunkBGNPT5PEcjs.segmentPoint.call(void 0, near2[0].seg, near2[0].t)) : null;
1231
1241
  }
1232
1242
  /** Where two of the target segments near `p` cross, the crossing nearest `p` within `tol`. */
1233
- crossing(near, p, tol) {
1234
- const list = near.slice(0, CROSSING_PAIRS);
1243
+ crossing(near2, p, tol) {
1244
+ const list = near2.slice(0, CROSSING_PAIRS);
1235
1245
  let best = null;
1236
1246
  let bestD = Infinity;
1237
1247
  for (let i = 0; i < list.length; i++) {
1238
1248
  for (let j = i + 1; j < list.length; j++) {
1239
- for (const hit of _chunkUG5DVXJKcjs.segmentIntersections.call(void 0, list[i].seg, list[j].seg)) {
1249
+ for (const hit of _chunkBGNPT5PEcjs.segmentIntersections.call(void 0, list[i].seg, list[j].seg)) {
1240
1250
  const q = toVec(hit.point);
1241
1251
  const d = distance(q, p);
1242
1252
  if (d <= tol && d < bestD) {
@@ -1253,9 +1263,9 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
1253
1263
  * (within `tol` of it): roots of `(B(t) − base) · B′(t)` and `(B(t) − base) × B′(t)`
1254
1264
  * bracketed by sign changes of samples and narrowed by bisection.
1255
1265
  */
1256
- curvePoint(near, base, p, tol) {
1266
+ curvePoint(near2, base, p, tol) {
1257
1267
  let best = null;
1258
- for (const { seg } of near) {
1268
+ for (const { seg } of near2) {
1259
1269
  for (const kind of ["perpendicular", "tangent"]) {
1260
1270
  if (kind === "tangent" && seg.length === 2) continue;
1261
1271
  for (const q of solveOnSegment(seg, base, kind === "perpendicular" ? dotCondition : crossCondition)) {
@@ -1558,15 +1568,15 @@ var crossCondition = (r, d) => r[0] * d[1] - r[1] * d[0];
1558
1568
  function solveOnSegment(seg, base, cond) {
1559
1569
  const b = [base.x, base.y];
1560
1570
  const g = (t) => {
1561
- const p = _chunkUG5DVXJKcjs.segmentPoint.call(void 0, seg, t);
1562
- return cond([p[0] - b[0], p[1] - b[1]], _chunkUG5DVXJKcjs.segmentDerivative.call(void 0, seg, t));
1571
+ const p = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, t);
1572
+ return cond([p[0] - b[0], p[1] - b[1]], _chunkBGNPT5PEcjs.segmentDerivative.call(void 0, seg, t));
1563
1573
  };
1564
1574
  const vals = [];
1565
1575
  let scale = 0;
1566
1576
  for (let i = 0; i <= CURVE_SAMPLES; i++) {
1567
1577
  const t = i / CURVE_SAMPLES;
1568
- const p = _chunkUG5DVXJKcjs.segmentPoint.call(void 0, seg, t);
1569
- const d = _chunkUG5DVXJKcjs.segmentDerivative.call(void 0, seg, t);
1578
+ const p = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, t);
1579
+ const d = _chunkBGNPT5PEcjs.segmentDerivative.call(void 0, seg, t);
1570
1580
  const r = [p[0] - b[0], p[1] - b[1]];
1571
1581
  vals.push(cond(r, d));
1572
1582
  scale = Math.max(scale, Math.hypot(r[0], r[1]) * Math.hypot(d[0], d[1]));
@@ -1593,8 +1603,8 @@ function solveOnSegment(seg, base, cond) {
1593
1603
  } else hi = m;
1594
1604
  }
1595
1605
  const t = (lo + hi) / 2;
1596
- const p = _chunkUG5DVXJKcjs.segmentPoint.call(void 0, seg, t);
1597
- const dv = _chunkUG5DVXJKcjs.segmentDerivative.call(void 0, seg, t);
1606
+ const p = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, t);
1607
+ const dv = _chunkBGNPT5PEcjs.segmentDerivative.call(void 0, seg, t);
1598
1608
  const r = Math.hypot(p[0] - b[0], p[1] - b[1]);
1599
1609
  const dl = Math.hypot(dv[0], dv[1]);
1600
1610
  if (!(r > 1e-9 && dl > 0) || Math.abs(cond([p[0] - b[0], p[1] - b[1]], dv)) > 1e-6 * r * dl) continue;
@@ -1603,8 +1613,968 @@ function solveOnSegment(seg, base, cond) {
1603
1613
  return out;
1604
1614
  }
1605
1615
 
1616
+ // src/vector/pathEdit.ts
1617
+ var EPS2 = 1e-9;
1618
+ var unit = (x, y) => {
1619
+ const l = Math.hypot(x, y);
1620
+ return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
1621
+ };
1622
+ function cubicOf(sp, i) {
1623
+ const s = subpathSegment(sp, i);
1624
+ if (s.length === 4) return s;
1625
+ const [a, b] = s;
1626
+ return [a, [a[0] + (b[0] - a[0]) / 3, a[1] + (b[1] - a[1]) / 3], [a[0] + 2 * (b[0] - a[0]) / 3, a[1] + 2 * (b[1] - a[1]) / 3], b];
1627
+ }
1628
+ var isLine = (sp, i) => subpathSegment(sp, i).length === 2;
1629
+ function startTangent(c) {
1630
+ for (let k = 1; k < 4; k++) {
1631
+ const u = unit(c[k][0] - c[0][0], c[k][1] - c[0][1]);
1632
+ if (u) return u;
1633
+ }
1634
+ return [1, 0];
1635
+ }
1636
+ function endTangent(c) {
1637
+ for (let k = 2; k >= 0; k--) {
1638
+ const u = unit(c[3][0] - c[k][0], c[3][1] - c[k][1]);
1639
+ if (u) return u;
1640
+ }
1641
+ return [1, 0];
1642
+ }
1643
+ function sample(c, n, out, withStart) {
1644
+ for (let i = withStart ? 0 : 1; i <= n; i++) out.push(_chunkBGNPT5PEcjs.segmentPoint.call(void 0, c, i / n));
1645
+ }
1646
+ var onAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS2 && Math.abs(h.y - a.y) < EPS2;
1647
+ function tidy(a, keepType = true) {
1648
+ const next = cloneAnchor(a);
1649
+ if (onAnchor(next, next.in)) delete next.in;
1650
+ if (onAnchor(next, next.out)) delete next.out;
1651
+ const shown = inferAnchorType(next);
1652
+ const type = keepType && a.type ? a.type === "symmetric" && shown !== "symmetric" ? shown === "corner" ? "corner" : "smooth" : a.type === "smooth" && shown === "corner" ? "corner" : a.type : shown;
1653
+ if (type === "corner") delete next.type;
1654
+ else next.type = type;
1655
+ return next;
1656
+ }
1657
+ function neighbours(sp, i) {
1658
+ const n = sp.anchors.length;
1659
+ const wrap = sp.closed && n > 1;
1660
+ return [i > 0 ? i - 1 : wrap ? n - 1 : -1, i < n - 1 ? i + 1 : wrap ? 0 : -1];
1661
+ }
1662
+ function goldenMin(f, a, b) {
1663
+ const R = 0.618033988749895;
1664
+ let x1 = b - R * (b - a);
1665
+ let x2 = a + R * (b - a);
1666
+ let f1 = f(x1);
1667
+ let f2 = f(x2);
1668
+ for (let i = 0; i < 40; i++) {
1669
+ if (f1.error < f2.error || Number.isNaN(f2.error)) {
1670
+ b = x2;
1671
+ x2 = x1;
1672
+ f2 = f1;
1673
+ x1 = b - R * (b - a);
1674
+ f1 = f(x1);
1675
+ } else {
1676
+ a = x1;
1677
+ x1 = x2;
1678
+ f1 = f2;
1679
+ x2 = a + R * (b - a);
1680
+ f2 = f(x2);
1681
+ }
1682
+ }
1683
+ return f1.error <= f2.error ? f1 : f2;
1684
+ }
1685
+ function fitAcross(pts, N, t0, t1) {
1686
+ const fit = (k) => {
1687
+ const u = [];
1688
+ for (let i = 0; i <= N; i++) u.push(k * i / N);
1689
+ for (let i = 1; i <= N; i++) u.push(k + (1 - k) * i / N);
1690
+ return _chunkBGNPT5PEcjs.fitCubicWithTangents.call(void 0, pts, u, t0, t1);
1691
+ };
1692
+ return goldenMin(fit, 0, 1);
1693
+ }
1694
+ function removeAnchorKeepingShape(sp, index) {
1695
+ const anchors = sp.anchors.map(cloneAnchor);
1696
+ const n = anchors.length;
1697
+ if (index < 0 || index >= n) return { anchors, closed: sp.closed };
1698
+ if ((sp.closed || index > 0 && index < n - 1) && n >= 3) {
1699
+ const pi = (index + n - 1) % n;
1700
+ const ni = (index + 1) % n;
1701
+ if (!(isLine(sp, pi) && isLine(sp, index))) {
1702
+ const left = cubicOf(sp, pi);
1703
+ const right = cubicOf(sp, index);
1704
+ const N = 16;
1705
+ const pts = [];
1706
+ sample(left, N, pts, true);
1707
+ sample(right, N, pts, false);
1708
+ const t0 = startTangent(left);
1709
+ const t1 = endTangent(right);
1710
+ const { curve } = fitAcross(pts, N, t0, t1);
1711
+ if ([curve.p1, curve.p2].every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]))) {
1712
+ anchors[pi] = tidy({ ...anchors[pi], out: { x: curve.p1[0], y: curve.p1[1] } });
1713
+ anchors[ni] = tidy({ ...anchors[ni], in: { x: curve.p2[0], y: curve.p2[1] } });
1714
+ }
1715
+ }
1716
+ }
1717
+ anchors.splice(index, 1);
1718
+ let closed = sp.closed;
1719
+ if (!closed && anchors.length) {
1720
+ anchors[0] = tidy({ ...anchors[0], in: null });
1721
+ const last = anchors.length - 1;
1722
+ anchors[last] = tidy({ ...anchors[last], out: null });
1723
+ }
1724
+ if (anchors.length < 3) closed = closed && anchors.length === 2 && anchors.some((a) => a.in || a.out);
1725
+ return { anchors, closed };
1726
+ }
1727
+ function removeAnchorsKeepingShape(sp, indices) {
1728
+ const order = [...new Set(indices)].filter((i) => i >= 0 && i < sp.anchors.length).sort((a, b) => b - a);
1729
+ return order.reduce((acc, i) => removeAnchorKeepingShape(acc, i), { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed });
1730
+ }
1731
+ function bendSegment(sp, segment, t, p) {
1732
+ const anchors = sp.anchors.map(cloneAnchor);
1733
+ const n = anchors.length;
1734
+ if (segment < 0 || segment >= subpathSegmentCount(sp) || !Number.isFinite(t) || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return { anchors, closed: sp.closed };
1735
+ const tc = Math.min(0.95, Math.max(0.05, t));
1736
+ const ia = segment;
1737
+ const ib = (segment + 1) % n;
1738
+ const a = anchors[ia];
1739
+ const b = anchors[ib];
1740
+ const k = 3 * tc * (1 - tc);
1741
+ const ox = (p.x - (a.x + (b.x - a.x) * tc)) / k;
1742
+ const oy = (p.y - (a.y + (b.y - a.y) * tc)) / k;
1743
+ const c1 = { x: a.x + (b.x - a.x) / 3 + ox, y: a.y + (b.y - a.y) / 3 + oy };
1744
+ const c2 = { x: b.x + (a.x - b.x) / 3 + ox, y: b.y + (a.y - b.y) / 3 + oy };
1745
+ anchors[ia] = moveAnchorHandle(a, "out", c1);
1746
+ anchors[ib] = moveAnchorHandle(b, "in", c2);
1747
+ return { anchors, closed: sp.closed };
1748
+ }
1749
+ function subpathPointAt(sp, segment, t) {
1750
+ const [x, y] = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, subpathSegment(sp, segment), t);
1751
+ return { x, y };
1752
+ }
1753
+ function curvatureMoveAnchor(sp, index, q) {
1754
+ const anchors = sp.anchors.map(cloneAnchor);
1755
+ const a = sp.anchors[index];
1756
+ if (!a) return { anchors, closed: sp.closed };
1757
+ const own = [
1758
+ a.in && !onAnchor(a, a.in) ? { x: a.in.x - a.x, y: a.in.y - a.y } : null,
1759
+ a.out && !onAnchor(a, a.out) ? { x: a.out.x - a.x, y: a.out.y - a.y } : null
1760
+ ];
1761
+ const ends = [null, null];
1762
+ neighbours(sp, index).forEach((j, side) => {
1763
+ if (j < 0 || j === index) return;
1764
+ const b = anchors[j];
1765
+ const old = Math.hypot(a.x - b.x, a.y - b.y);
1766
+ const s = old > 1e-9 ? Math.hypot(q.x - b.x, q.y - b.y) / old : 1;
1767
+ const key = side === 0 ? "out" : "in";
1768
+ const h = b[key];
1769
+ if (h) b[key] = { x: b.x + (h.x - b.x) * s, y: b.y + (h.y - b.y) * s };
1770
+ const o = own[side];
1771
+ if (o) own[side] = { x: o.x * s, y: o.y * s };
1772
+ ends[side] = { x: b.x, y: b.y };
1773
+ });
1774
+ const moved = { x: q.x, y: q.y };
1775
+ if (own[0]) moved.in = { x: q.x + own[0].x, y: q.y + own[0].y };
1776
+ if (own[1]) moved.out = { x: q.x + own[1].x, y: q.y + own[1].y };
1777
+ if (a.type) moved.type = a.type;
1778
+ const [prev, next] = ends;
1779
+ if (a.type && a.type !== "corner" && prev && next) {
1780
+ const dx = next.x - prev.x;
1781
+ const dy = next.y - prev.y;
1782
+ const l = Math.hypot(dx, dy);
1783
+ if (l > 1e-9) {
1784
+ const len = (h, to) => h ? Math.hypot(h.x, h.y) : Math.hypot(q.x - to.x, q.y - to.y) / 3;
1785
+ let li = len(own[0], prev);
1786
+ let lo = len(own[1], next);
1787
+ if (a.type === "symmetric") li = lo = (li + lo) / 2;
1788
+ moved.in = { x: q.x - dx * li / l, y: q.y - dy * li / l };
1789
+ moved.out = { x: q.x + dx * lo / l, y: q.y + dy * lo / l };
1790
+ }
1791
+ }
1792
+ anchors[index] = moved;
1793
+ return { anchors, closed: sp.closed };
1794
+ }
1795
+ function cutSubpathAt(sp, at) {
1796
+ const n = sp.anchors.length;
1797
+ const cuts = [...new Set(at)].filter((i) => Number.isInteger(i) && i >= 0 && i < n && (sp.closed || i > 0 && i < n - 1)).sort((a, b) => a - b);
1798
+ if (!cuts.length || n < 2) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];
1799
+ const first = cuts[0];
1800
+ const run = sp.closed ? Array.from({ length: n + 1 }, (_, i) => sp.anchors[(first + i) % n]) : sp.anchors;
1801
+ const cutSet = new Set(sp.closed ? [...cuts.map((c) => (c - first + n) % n), n] : cuts);
1802
+ const pieces = [];
1803
+ let cur = [];
1804
+ run.forEach((a, i) => {
1805
+ if (!cutSet.has(i)) {
1806
+ cur.push(cloneAnchor(a));
1807
+ return;
1808
+ }
1809
+ if (cur.length) {
1810
+ cur.push(tidy({ ...a, out: null }, false));
1811
+ pieces.push({ anchors: cur, closed: false });
1812
+ cur = [];
1813
+ }
1814
+ if (i < n) cur.push(tidy({ ...a, in: null }, false));
1815
+ });
1816
+ if (cur.length > 1) pieces.push({ anchors: cur, closed: false });
1817
+ return pieces;
1818
+ }
1819
+ function splitSubpathAt(sp, segment, t, endTolerance = 1e-6) {
1820
+ const n = sp.anchors.length;
1821
+ if (segment < 0 || segment >= subpathSegmentCount(sp)) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];
1822
+ if (t <= endTolerance) return cutSubpathAt(sp, [segment]);
1823
+ if (t >= 1 - endTolerance) return cutSubpathAt(sp, [(segment + 1) % n]);
1824
+ const { subpath, index } = insertAnchor(sp, segment, t);
1825
+ return cutSubpathAt(subpath, [index]);
1826
+ }
1827
+ var endOf = (sp, last) => sp.anchors[last ? sp.anchors.length - 1 : 0];
1828
+ var dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
1829
+ function reverse(sp) {
1830
+ return {
1831
+ closed: sp.closed,
1832
+ anchors: sp.anchors.slice().reverse().map((a) => {
1833
+ const next = { x: a.x, y: a.y };
1834
+ if (a.out) next.in = { ...a.out };
1835
+ if (a.in) next.out = { ...a.in };
1836
+ if (a.type) next.type = a.type;
1837
+ return next;
1838
+ })
1839
+ };
1840
+ }
1841
+ function joinSubpaths(subpaths, tolerance) {
1842
+ const closed = [];
1843
+ let open = [];
1844
+ for (const sp of subpaths) {
1845
+ const copy = { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
1846
+ if (sp.closed || sp.anchors.length < 2) {
1847
+ if (sp.anchors.length) closed.push(copy);
1848
+ } else open.push(copy);
1849
+ }
1850
+ if (open.length === 1) {
1851
+ const sp = open[0];
1852
+ const first = sp.anchors[0];
1853
+ const last = sp.anchors[sp.anchors.length - 1];
1854
+ if (sp.anchors.length > 2 && dist(first, last) <= tolerance) {
1855
+ sp.anchors.pop();
1856
+ sp.anchors[0] = tidy({ ...first, in: _nullishCoalesce(last.in, () => ( null)) }, false);
1857
+ } else {
1858
+ sp.anchors[0] = tidy({ ...first, in: null }, false);
1859
+ sp.anchors[sp.anchors.length - 1] = tidy({ ...sp.anchors[sp.anchors.length - 1], out: null }, false);
1860
+ }
1861
+ sp.closed = true;
1862
+ return [...closed, sp];
1863
+ }
1864
+ while (open.length > 1) {
1865
+ let best = { d: Infinity, i: 0, j: 1, ei: true, ej: false };
1866
+ for (let i = 0; i < open.length; i++) {
1867
+ for (let j = i + 1; j < open.length; j++) {
1868
+ for (const ei of [false, true]) {
1869
+ for (const ej of [false, true]) {
1870
+ const d = dist(endOf(open[i], ei), endOf(open[j], ej));
1871
+ if (d < best.d) best = { d, i, j, ei, ej };
1872
+ }
1873
+ }
1874
+ }
1875
+ }
1876
+ let a = open[best.i];
1877
+ let b = open[best.j];
1878
+ open = open.filter((_, k) => k !== best.j);
1879
+ if (!best.ei) a = reverse(a);
1880
+ if (best.ej) b = reverse(b);
1881
+ const anchors = a.anchors;
1882
+ if (best.d <= tolerance) {
1883
+ const [head, ...rest] = b.anchors;
1884
+ const l = anchors[anchors.length - 1];
1885
+ anchors[anchors.length - 1] = tidy({ ...l, out: _nullishCoalesce(head.out, () => ( null)) }, false);
1886
+ anchors.push(...rest);
1887
+ } else {
1888
+ const l = anchors[anchors.length - 1];
1889
+ anchors[anchors.length - 1] = tidy({ ...l, out: null }, false);
1890
+ anchors.push(tidy({ ...b.anchors[0], in: null }, false), ...b.anchors.slice(1));
1891
+ }
1892
+ open[best.i > best.j ? best.i - 1 : best.i] = { anchors, closed: false };
1893
+ }
1894
+ return [...closed, ...open];
1895
+ }
1896
+ function smoothSubpath(sp, amount = 1, only) {
1897
+ const t = Math.min(1, Math.max(0, amount));
1898
+ const orig = sp.anchors;
1899
+ const n = orig.length;
1900
+ const anchors = orig.map(cloneAnchor);
1901
+ if (n < 3 || t === 0) return { anchors, closed: sp.closed };
1902
+ for (let i = 0; i < n; i++) {
1903
+ if (!sp.closed && (i === 0 || i === n - 1)) continue;
1904
+ if (only && !only.has(i)) continue;
1905
+ const prev = orig[(i + n - 1) % n];
1906
+ const next = orig[(i + 1) % n];
1907
+ const p = orig[i];
1908
+ const dir = unit(next.x - prev.x, next.y - prev.y);
1909
+ if (!dir) continue;
1910
+ const li = dist(p, prev) / 3;
1911
+ const lo = dist(p, next) / 3;
1912
+ const hin = _nullishCoalesce(p.in, () => ( p));
1913
+ const hout = _nullishCoalesce(p.out, () => ( p));
1914
+ const target = (h, x, y) => ({ x: h.x + (x - h.x) * t, y: h.y + (y - h.y) * t });
1915
+ anchors[i] = tidy(
1916
+ {
1917
+ x: p.x,
1918
+ y: p.y,
1919
+ in: target(hin, p.x - dir[0] * li, p.y - dir[1] * li),
1920
+ out: target(hout, p.x + dir[0] * lo, p.y + dir[1] * lo)
1921
+ },
1922
+ false
1923
+ );
1924
+ }
1925
+ return { anchors, closed: sp.closed };
1926
+ }
1927
+ function segsOf(sp) {
1928
+ const out = [];
1929
+ for (let i = 0; i < subpathSegmentCount(sp); i++) {
1930
+ const s = subpathSegment(sp, i);
1931
+ out.push(s.length === 2 ? { c: s, line: true } : { c: s, line: false });
1932
+ }
1933
+ return out;
1934
+ }
1935
+ function segsToSubpath(segs, closed, types) {
1936
+ if (!segs.length) return null;
1937
+ const anchors = [];
1938
+ const at = (p) => ({ x: p[0], y: p[1] });
1939
+ anchors.push(at(segs[0].c[0]));
1940
+ for (const s of segs) {
1941
+ const end = s.c[s.c.length - 1];
1942
+ if (!s.line) anchors[anchors.length - 1].out = { x: s.c[1][0], y: s.c[1][1] };
1943
+ const next = at(end);
1944
+ if (!s.line) next.in = { x: s.c[2][0], y: s.c[2][1] };
1945
+ anchors.push(next);
1946
+ }
1947
+ if (closed) {
1948
+ const last = anchors.pop();
1949
+ if (last.in) anchors[0].in = last.in;
1950
+ }
1951
+ const typed = anchors.map((a) => {
1952
+ const type = types.get(`${a.x},${a.y}`);
1953
+ return tidy(type ? { ...a, type } : a, !!type);
1954
+ });
1955
+ return { anchors: typed, closed };
1956
+ }
1957
+ var isZeroSeg = (s) => s.c.every((p) => Math.abs(p[0] - s.c[0][0]) < 1e-9 && Math.abs(p[1] - s.c[0][1]) < 1e-9);
1958
+ function mergePair(a, b, tol) {
1959
+ const a0 = a.c[0];
1960
+ const a3 = a.c[a.c.length - 1];
1961
+ const b3 = b.c[b.c.length - 1];
1962
+ if (a.line && b.line) {
1963
+ const cx = b3[0] - a0[0];
1964
+ const cy = b3[1] - a0[1];
1965
+ const len = Math.hypot(cx, cy);
1966
+ if (len < 1e-12) return null;
1967
+ const mx = a3[0] - a0[0];
1968
+ const my = a3[1] - a0[1];
1969
+ const along = (mx * cx + my * cy) / len;
1970
+ return Math.abs((mx * cy - my * cx) / len) <= tol && along >= 0 && along <= len ? { c: [a0, b3], line: true } : null;
1971
+ }
1972
+ const ca = a.line ? [a0, a0, a3, a3] : a.c;
1973
+ const cb = b.line ? [a3, a3, b3, b3] : b.c;
1974
+ const ta = endTangent(ca);
1975
+ const tb = startTangent(cb);
1976
+ if (ta[0] * tb[0] + ta[1] * tb[1] < 0.99) return null;
1977
+ const pts = [];
1978
+ sample(ca, 16, pts, true);
1979
+ sample(cb, 16, pts, false);
1980
+ const { curve, error } = fitAcross(pts, 16, startTangent(ca), endTangent(cb));
1981
+ return error <= tol ? { c: [curve.p0, curve.p1, curve.p2, curve.p3], line: false } : null;
1982
+ }
1983
+ function removeRedundantPoints(sp, tolerance) {
1984
+ const tol = Math.max(tolerance, 1e-9);
1985
+ const types = new Map(sp.anchors.filter((a) => a.type).map((a) => [`${a.x},${a.y}`, a.type]));
1986
+ const segs = segsOf(sp).filter((s) => !isZeroSeg(s));
1987
+ if (!segs.length) return { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
1988
+ const minSegs = sp.closed ? 2 : 1;
1989
+ let changed = true;
1990
+ while (changed && segs.length > minSegs) {
1991
+ changed = false;
1992
+ const n = segs.length;
1993
+ const joints = sp.closed ? n : n - 1;
1994
+ for (let j = 0; j < joints; j++) {
1995
+ const [i0, i1] = sp.closed ? [(j + n - 1) % n, j] : [j, j + 1];
1996
+ const m = mergePair(segs[i0], segs[i1], tol);
1997
+ if (!m) continue;
1998
+ segs[i0] = m;
1999
+ segs.splice(i1, 1);
2000
+ if (sp.closed && i1 === 0) segs.unshift(segs.pop());
2001
+ changed = true;
2002
+ break;
2003
+ }
2004
+ }
2005
+ return _nullishCoalesce(segsToSubpath(segs, sp.closed, types), () => ( { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }));
2006
+ }
2007
+
2008
+ // src/vector/pathCut.ts
2009
+ var loopArea = (l) => _chunkBGNPT5PEcjs.contourSignedArea.call(void 0, l.segs);
2010
+ function loopSample(l) {
2011
+ const k = l.chord.findIndex((c) => !c);
2012
+ return _chunkBGNPT5PEcjs.segmentPoint.call(void 0, l.segs[k >= 0 ? k : 0], 0.5);
2013
+ }
2014
+ var contains2 = (l, p) => _chunkBGNPT5PEcjs.windingNumber.call(void 0, [l.segs], p) !== 0;
2015
+ function groupFaces(loops) {
2016
+ const usable = loops.filter((l) => l.segs.length && Math.abs(loopArea(l)) > 1e-12);
2017
+ if (!usable.length) return [];
2018
+ const largest = usable.reduce((a, b) => Math.abs(loopArea(b)) > Math.abs(loopArea(a)) ? b : a);
2019
+ const sign = Math.sign(loopArea(largest));
2020
+ const outers = usable.filter((l) => Math.sign(loopArea(l)) === sign).sort((a, b) => Math.abs(loopArea(a)) - Math.abs(loopArea(b)));
2021
+ const faces = new Map(outers.map((o) => [o, { loops: [o] }]));
2022
+ for (const hole of usable.filter((l) => Math.sign(loopArea(l)) !== sign)) {
2023
+ const p = loopSample(hole);
2024
+ const parent = outers.find((o) => Math.abs(loopArea(o)) > Math.abs(loopArea(hole)) && contains2(o, p));
2025
+ if (parent) faces.get(parent).loops.push(hole);
2026
+ }
2027
+ return outers.reverse().map((o) => faces.get(o));
2028
+ }
2029
+ function faceToPath(face) {
2030
+ return _chunkBGNPT5PEcjs.contoursToPath.call(void 0, face.loops.map((l) => ({ segments: l.segs, closed: true })));
2031
+ }
2032
+ function facesOf(path, fillRule) {
2033
+ const normalized = _chunkBGNPT5PEcjs.normalizeBezierPath.call(void 0, path, fillRule);
2034
+ const loops = _chunkBGNPT5PEcjs.pathToContours.call(void 0, normalized).map((c) => {
2035
+ const segs = _chunkBGNPT5PEcjs.closeContour.call(void 0, c, true).map((s) => s.slice());
2036
+ return { segs, chord: segs.map(() => false) };
2037
+ });
2038
+ return groupFaces(loops);
2039
+ }
2040
+ function splitIntoFaces(path, fillRule = "nonzero") {
2041
+ return facesOf(path, fillRule).map(faceToPath);
2042
+ }
2043
+ function rootsInUnit(a, b, c, d, scale) {
2044
+ const f = (t) => ((a * t + b) * t + c) * t + d;
2045
+ const tol = 1e-12 * Math.max(1, scale);
2046
+ const cuts = [0];
2047
+ const qa = 3 * a;
2048
+ const qb = 2 * b;
2049
+ if (Math.abs(qa) > 1e-18) {
2050
+ const disc = qb * qb - 4 * qa * c;
2051
+ if (disc >= 0) {
2052
+ const r = Math.sqrt(disc);
2053
+ for (const t of [(-qb - r) / (2 * qa), (-qb + r) / (2 * qa)].sort((x, y) => x - y)) if (t > 0 && t < 1) cuts.push(t);
2054
+ }
2055
+ } else if (Math.abs(qb) > 1e-18) {
2056
+ const t = -c / qb;
2057
+ if (t > 0 && t < 1) cuts.push(t);
2058
+ }
2059
+ cuts.push(1);
2060
+ const roots = [];
2061
+ const push = (t) => {
2062
+ if (!roots.some((r) => Math.abs(r - t) < 1e-9)) roots.push(t);
2063
+ };
2064
+ for (let i = 0; i < cuts.length - 1; i++) {
2065
+ let lo = cuts[i];
2066
+ let hi = cuts[i + 1];
2067
+ let flo = f(lo);
2068
+ const fhi = f(hi);
2069
+ if (Math.abs(flo) <= tol) push(lo);
2070
+ if (Math.abs(fhi) <= tol) push(hi);
2071
+ if (flo * fhi >= 0) continue;
2072
+ for (let k = 0; k < 80; k++) {
2073
+ const m = (lo + hi) / 2;
2074
+ const fm = f(m);
2075
+ if (fm === 0) {
2076
+ lo = hi = m;
2077
+ break;
2078
+ }
2079
+ if (fm * flo < 0) hi = m;
2080
+ else {
2081
+ lo = m;
2082
+ flo = fm;
2083
+ }
2084
+ }
2085
+ push((lo + hi) / 2);
2086
+ }
2087
+ return roots.sort((x, y) => x - y);
2088
+ }
2089
+ function lineHits(p, q, seg) {
2090
+ const dx = q[0] - p[0];
2091
+ const dy = q[1] - p[1];
2092
+ const len2 = dx * dx + dy * dy;
2093
+ if (len2 < 1e-24) return [];
2094
+ const h = (pt) => (pt[0] - p[0]) * -dy + (pt[1] - p[1]) * dx;
2095
+ let coeffs;
2096
+ if (seg.length === 2) coeffs = [0, 0, h(seg[1]) - h(seg[0]), h(seg[0])];
2097
+ else {
2098
+ const [h0, h1, h2, h3] = seg.map(h);
2099
+ coeffs = [-h0 + 3 * h1 - 3 * h2 + h3, 3 * h0 - 6 * h1 + 3 * h2, -3 * h0 + 3 * h1, h0];
2100
+ }
2101
+ if (coeffs.every((c) => Math.abs(c) < 1e-12 * Math.max(1, len2))) return [];
2102
+ const scale = Math.sqrt(len2) * Math.max(1, ...seg.map((pt) => Math.abs(pt[0]) + Math.abs(pt[1])));
2103
+ const out = [];
2104
+ for (const t of rootsInUnit(...coeffs, scale)) {
2105
+ const point = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, t);
2106
+ const s = ((point[0] - p[0]) * dx + (point[1] - p[1]) * dy) / len2;
2107
+ if (s >= -1e-9 && s <= 1 + 1e-9) out.push({ s: Math.min(1, Math.max(0, s)), t, point });
2108
+ }
2109
+ return out;
2110
+ }
2111
+ var near = (a, b, eps) => Math.abs(a[0] - b[0]) <= eps && Math.abs(a[1] - b[1]) <= eps;
2112
+ var degenerate = (seg, eps) => seg.every((p) => near(p, seg[0], eps));
2113
+ function arc(l, a, b, eps) {
2114
+ const n = l.segs.length;
2115
+ const segs = [];
2116
+ const chord = [];
2117
+ const push = (k, t0, t1) => {
2118
+ const piece = t0 <= 0 && t1 >= 1 ? l.segs[k].slice() : _chunkBGNPT5PEcjs.subSegment.call(void 0, l.segs[k], t0, t1);
2119
+ if (!degenerate(piece, eps)) {
2120
+ segs.push(piece);
2121
+ chord.push(l.chord[k]);
2122
+ }
2123
+ };
2124
+ if (a.seg === b.seg && b.t >= a.t + 1e-12) {
2125
+ push(a.seg, a.t, b.t);
2126
+ return { segs, chord };
2127
+ }
2128
+ push(a.seg, a.t, 1);
2129
+ for (let k = (a.seg + 1) % n; k !== b.seg; k = (k + 1) % n) push(k, 0, 1);
2130
+ push(b.seg, 0, b.t);
2131
+ return { segs, chord };
2132
+ }
2133
+ function polySegs(points, eps) {
2134
+ const out = [];
2135
+ for (let i = 1; i < points.length; i++) if (!near(points[i - 1], points[i], eps)) out.push([points[i - 1], points[i]]);
2136
+ return out;
2137
+ }
2138
+ function knifePath(path, fillRule, knife) {
2139
+ const pts = knife.filter((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]));
2140
+ if (pts.length < 2) return null;
2141
+ let faces = facesOf(path, fillRule);
2142
+ if (!faces.length) return null;
2143
+ const b = _chunkBGNPT5PEcjs.bezierPathBounds.call(void 0, path);
2144
+ const size = b ? Math.max(b.maxX - b.minX, b.maxY - b.minY, 1) : 1;
2145
+ const eps = size * 1e-9;
2146
+ let cursor = -Infinity;
2147
+ let cuts = 0;
2148
+ for (let guard = 0; guard < 512; guard++) {
2149
+ let best = null;
2150
+ faces.forEach((face2, fi2) => {
2151
+ const hits = [];
2152
+ for (let m = 0; m < pts.length - 1; m++) {
2153
+ if (m + 1 < cursor - 1e-9) continue;
2154
+ const kb = { minX: Math.min(pts[m][0], pts[m + 1][0]), maxX: Math.max(pts[m][0], pts[m + 1][0]), minY: Math.min(pts[m][1], pts[m + 1][1]), maxY: Math.max(pts[m][1], pts[m + 1][1]) };
2155
+ face2.loops.forEach(
2156
+ (l, li) => l.segs.forEach((seg, si) => {
2157
+ const sb = _chunkBGNPT5PEcjs.segmentBounds.call(void 0, seg);
2158
+ if (sb.maxX < kb.minX - eps || sb.minX > kb.maxX + eps || sb.maxY < kb.minY - eps || sb.minY > kb.maxY + eps) return;
2159
+ for (const h of lineHits(pts[m], pts[m + 1], seg)) {
2160
+ const s = m + h.s;
2161
+ if (s >= cursor - 1e-9) hits.push({ s, loop: li, seg: si, t: h.t, point: h.point });
2162
+ }
2163
+ })
2164
+ );
2165
+ }
2166
+ hits.sort((x, y) => x.s - y.s);
2167
+ const unique = hits.filter((h, i) => i === 0 || h.s - hits[i - 1].s > 1e-9 || !near(h.point, hits[i - 1].point, eps * 10));
2168
+ const inside = (s) => {
2169
+ const m = Math.min(pts.length - 2, Math.floor(s));
2170
+ const u = s - m;
2171
+ const q = [pts[m][0] + (pts[m + 1][0] - pts[m][0]) * u, pts[m][1] + (pts[m + 1][1] - pts[m][1]) * u];
2172
+ return _chunkBGNPT5PEcjs.windingNumber.call(void 0, face2.loops.map((l) => l.segs), q) !== 0;
2173
+ };
2174
+ for (let i = 0; i < unique.length - 1; i++) {
2175
+ const from2 = unique[i];
2176
+ const to2 = unique[i + 1];
2177
+ if (near(from2.point, to2.point, eps * 10) || !inside((from2.s + to2.s) / 2)) continue;
2178
+ if (!best || from2.s < best.from.s) best = { face: fi2, from: from2, to: to2 };
2179
+ break;
2180
+ }
2181
+ });
2182
+ if (!best) break;
2183
+ const { face: fi, from, to } = best;
2184
+ const face = faces[fi];
2185
+ const corners2 = [from.point];
2186
+ for (let m = Math.floor(from.s) + 1; m <= to.s - 1e-12 && m < pts.length; m++) if (m > from.s) corners2.push(pts[m]);
2187
+ corners2.push(to.point);
2188
+ const forward = polySegs(corners2, eps);
2189
+ const back = polySegs(corners2.slice().reverse(), eps);
2190
+ const la = face.loops[from.loop];
2191
+ const loops = face.loops.filter((_, i) => i !== from.loop && i !== to.loop);
2192
+ if (from.loop === to.loop) {
2193
+ const ab = arc(la, from, to, eps);
2194
+ const ba = arc(la, to, from, eps);
2195
+ loops.push(
2196
+ { segs: [...ab.segs, ...back], chord: [...ab.chord, ...back.map(() => true)] },
2197
+ { segs: [...ba.segs, ...forward], chord: [...ba.chord, ...forward.map(() => true)] }
2198
+ );
2199
+ } else {
2200
+ const lb = face.loops[to.loop];
2201
+ const roundA = arc(la, from, from, eps);
2202
+ const roundB = arc(lb, to, to, eps);
2203
+ loops.push({
2204
+ segs: [...roundA.segs, ...forward, ...roundB.segs, ...back],
2205
+ chord: [...roundA.chord, ...forward.map(() => true), ...roundB.chord, ...back.map(() => true)]
2206
+ });
2207
+ }
2208
+ faces = [...faces.slice(0, fi), ...groupFaces(loops), ...faces.slice(fi + 1)];
2209
+ cursor = to.s;
2210
+ cuts++;
2211
+ }
2212
+ return cuts ? faces.map(faceToPath) : null;
2213
+ }
2214
+ function eraserRegion(points, width) {
2215
+ const pts = points.filter((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]));
2216
+ const r = width / 2;
2217
+ if (!pts.length || !(r > 0)) return [];
2218
+ const moved = pts.some((p) => !near(p, pts[0], 1e-9));
2219
+ if (!moved) {
2220
+ const [cx, cy] = pts[0];
2221
+ const k = r * 0.5522847498307936;
2222
+ return [
2223
+ { type: "M", x: cx, y: cy - r },
2224
+ { type: "C", x1: cx + k, y1: cy - r, x2: cx + r, y2: cy - k, x: cx + r, y: cy },
2225
+ { type: "C", x1: cx + r, y1: cy + k, x2: cx + k, y2: cy + r, x: cx, y: cy + r },
2226
+ { type: "C", x1: cx - k, y1: cy + r, x2: cx - r, y2: cy + k, x: cx - r, y: cy },
2227
+ { type: "C", x1: cx - r, y1: cy - k, x2: cx - k, y2: cy - r, x: cx, y: cy - r },
2228
+ { type: "Z" }
2229
+ ];
2230
+ }
2231
+ const line2 = [{ type: "M", x: pts[0][0], y: pts[0][1] }, ...pts.slice(1).map((p) => ({ type: "L", x: p[0], y: p[1] }))];
2232
+ return _chunkBGNPT5PEcjs.outlineStroke.call(void 0, line2, { width, cap: "round", join: "round" });
2233
+ }
2234
+ function eraseFromFill(path, fillRule, region) {
2235
+ const overlap = _chunkBGNPT5PEcjs.pathBoolean.call(void 0, "intersection", [{ path, fillRule }, { path: region }]).path;
2236
+ if (!_chunkBGNPT5PEcjs.pathToContours.call(void 0, overlap).some((c) => Math.abs(_chunkBGNPT5PEcjs.contourSignedArea.call(void 0, _chunkBGNPT5PEcjs.closeContour.call(void 0, c, true))) > 1e-9)) return null;
2237
+ const rest = _chunkBGNPT5PEcjs.pathBoolean.call(void 0, "difference", [{ path, fillRule }, { path: region }]).path;
2238
+ return splitIntoFaces(rest);
2239
+ }
2240
+ function eraseFromOutline(sp, region) {
2241
+ const regionSegs = _chunkBGNPT5PEcjs.pathToContours.call(void 0, region).flatMap((c) => _chunkBGNPT5PEcjs.closeContour.call(void 0, c, true));
2242
+ if (!regionSegs.length) return null;
2243
+ const rb = _chunkBGNPT5PEcjs.bezierPathBounds.call(void 0, region);
2244
+ const count = subpathSegmentCount(sp);
2245
+ if (!rb || count === 0) return null;
2246
+ const removed = (p) => _chunkBGNPT5PEcjs.pointInBezierPath.call(void 0, region, p, "nonzero");
2247
+ const runs = [];
2248
+ let cur = [];
2249
+ let any = false;
2250
+ for (let i = 0; i < count; i++) {
2251
+ const seg = subpathSegment(sp, i);
2252
+ const sb = _chunkBGNPT5PEcjs.segmentBounds.call(void 0, seg);
2253
+ const ts = [0, 1];
2254
+ if (!(sb.maxX < rb.minX || sb.minX > rb.maxX || sb.maxY < rb.minY || sb.minY > rb.maxY)) {
2255
+ for (const r of regionSegs) {
2256
+ const b = _chunkBGNPT5PEcjs.segmentBounds.call(void 0, r);
2257
+ if (b.maxX < sb.minX || b.minX > sb.maxX || b.maxY < sb.minY || b.minY > sb.maxY) continue;
2258
+ for (const hit of _chunkBGNPT5PEcjs.segmentIntersections.call(void 0, seg, r)) if (hit.ta > 1e-9 && hit.ta < 1 - 1e-9) ts.push(hit.ta);
2259
+ }
2260
+ }
2261
+ ts.sort((a, b) => a - b);
2262
+ for (let k = 0; k < ts.length - 1; k++) {
2263
+ const t0 = ts[k];
2264
+ const t1 = ts[k + 1];
2265
+ if (t1 - t0 < 1e-9) continue;
2266
+ if (removed(_chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, (t0 + t1) / 2))) {
2267
+ any = true;
2268
+ if (cur.length) runs.push(cur);
2269
+ cur = [];
2270
+ } else cur.push({ seg: i, t0, t1 });
2271
+ }
2272
+ }
2273
+ if (cur.length) runs.push(cur);
2274
+ if (!any) return null;
2275
+ if (sp.closed && runs.length >= 2) {
2276
+ const first = runs[0][0];
2277
+ const lastRun = runs[runs.length - 1];
2278
+ const last = lastRun[lastRun.length - 1];
2279
+ if (first.seg === 0 && first.t0 === 0 && last.seg === count - 1 && last.t1 === 1) lastRun.push(...runs.shift());
2280
+ }
2281
+ const out = [];
2282
+ for (const run of runs) {
2283
+ const anchors = [];
2284
+ for (const { seg, t0, t1 } of run) {
2285
+ const c = _chunkBGNPT5PEcjs.subSegment.call(void 0, subpathSegment(sp, seg), t0, t1);
2286
+ const line2 = c.length === 2;
2287
+ const start = c[0];
2288
+ const end = c[c.length - 1];
2289
+ if (!anchors.length) anchors.push({ x: start[0], y: start[1] });
2290
+ if (!line2) anchors[anchors.length - 1].out = { x: c[1][0], y: c[1][1] };
2291
+ const next = { x: end[0], y: end[1] };
2292
+ if (!line2) next.in = { x: c[2][0], y: c[2][1] };
2293
+ anchors.push(next);
2294
+ }
2295
+ if (anchors.length < 2) continue;
2296
+ for (const a of anchors) {
2297
+ const type = inferAnchorType(a);
2298
+ if (type !== "corner") a.type = type;
2299
+ }
2300
+ out.push({ anchors, closed: false });
2301
+ }
2302
+ return out;
2303
+ }
2304
+ function polylineBounds(points, pad = 0) {
2305
+ if (!points.length) return null;
2306
+ let minX = Infinity;
2307
+ let minY = Infinity;
2308
+ let maxX = -Infinity;
2309
+ let maxY = -Infinity;
2310
+ for (const [x, y] of points) {
2311
+ minX = Math.min(minX, x);
2312
+ minY = Math.min(minY, y);
2313
+ maxX = Math.max(maxX, x);
2314
+ maxY = Math.max(maxY, y);
2315
+ }
2316
+ return { x: minX - pad, y: minY - pad, width: maxX - minX + 2 * pad, height: maxY - minY + 2 * pad };
2317
+ }
2318
+ function simplifyPolyline(points, tolerance) {
2319
+ const n = points.length;
2320
+ if (n < 3) return points.slice();
2321
+ const keep = new Array(n).fill(false);
2322
+ keep[0] = keep[n - 1] = true;
2323
+ const stack = [[0, n - 1]];
2324
+ while (stack.length) {
2325
+ const [a, b] = stack.pop();
2326
+ const [ax, ay] = points[a];
2327
+ const dx = points[b][0] - ax;
2328
+ const dy = points[b][1] - ay;
2329
+ const len = Math.hypot(dx, dy);
2330
+ let far = -1;
2331
+ let farD = 0;
2332
+ for (let i = a + 1; i < b; i++) {
2333
+ const [px, py] = points[i];
2334
+ const d = len < 1e-12 ? Math.hypot(px - ax, py - ay) : Math.abs((px - ax) * dy - (py - ay) * dx) / len;
2335
+ if (d > farD) {
2336
+ farD = d;
2337
+ far = i;
2338
+ }
2339
+ }
2340
+ if (far >= 0 && farD > tolerance) {
2341
+ keep[far] = true;
2342
+ stack.push([a, far], [far, b]);
2343
+ }
2344
+ }
2345
+ return points.filter((_, i) => keep[i]);
2346
+ }
2347
+
2348
+ // src/vector/ink.ts
2349
+ var LazyMouse = (_class2 = class {
2350
+ /** `length` is the string length in the units of the points (0 turns smoothing off). */
2351
+ constructor(length2) {;_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);
2352
+ this.length = length2;
2353
+ }
2354
+
2355
+ __init7() {this.pos = null}
2356
+ __init8() {this.last = null}
2357
+ push(p) {
2358
+ this.last = p;
2359
+ if (!(this.length > 0)) return [p];
2360
+ const pos = this.pos;
2361
+ if (!pos) {
2362
+ this.pos = p;
2363
+ return [p];
2364
+ }
2365
+ const dx = p[0] - pos[0];
2366
+ const dy = p[1] - pos[1];
2367
+ const d = Math.hypot(dx, dy);
2368
+ if (d <= this.length) return [];
2369
+ const k = (d - this.length) / d;
2370
+ const next = [pos[0] + dx * k, pos[1] + dy * k];
2371
+ this.pos = next;
2372
+ return [next];
2373
+ }
2374
+ /** The string from the pen to the pointer while they are apart, else null. */
2375
+ string() {
2376
+ const { pos, last } = this;
2377
+ if (!(this.length > 0) || !pos || !last || pos[0] === last[0] && pos[1] === last[1]) return null;
2378
+ return [pos, last];
2379
+ }
2380
+ /** End of stroke: the pen catches up with the pointer (the point to add, if it lagged). */
2381
+ finish() {
2382
+ const { pos, last } = this;
2383
+ if (!(this.length > 0) || !pos || !last || pos[0] === last[0] && pos[1] === last[1]) return [];
2384
+ this.pos = last;
2385
+ return [last];
2386
+ }
2387
+ }, _class2);
2388
+ var VECTOR_INK_MAX_CORNERS = 8;
2389
+ var MAX_POINTS = 1e5;
2390
+ function vectorInkShapeName(shape) {
2391
+ switch (shape.kind) {
2392
+ case "line":
2393
+ return "line";
2394
+ case "rect":
2395
+ return shape.width === shape.height ? "square" : "rectangle";
2396
+ case "ellipse":
2397
+ return shape.width === shape.height ? "circle" : "ellipse";
2398
+ case "polygon":
2399
+ return shape.points.length === 3 ? "triangle" : "polygon";
2400
+ }
2401
+ }
2402
+ var dist2 = (a, b) => Math.hypot(b[0] - a[0], b[1] - a[1]);
2403
+ function bbox(pts) {
2404
+ if (!pts.length) return [0, 0, 0, 0];
2405
+ let [x0, y0] = pts[0];
2406
+ let [x1, y1] = pts[0];
2407
+ for (const [x, y] of pts) {
2408
+ x0 = Math.min(x0, x);
2409
+ y0 = Math.min(y0, y);
2410
+ x1 = Math.max(x1, x);
2411
+ y1 = Math.max(y1, y);
2412
+ }
2413
+ return [x0, y0, x1 - x0, y1 - y0];
2414
+ }
2415
+ function segDistance(a, b, p) {
2416
+ const dx = b[0] - a[0];
2417
+ const dy = b[1] - a[1];
2418
+ const len2 = dx * dx + dy * dy;
2419
+ const t = len2 > 0 ? Math.min(1, Math.max(0, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2)) : 0;
2420
+ return dist2([a[0] + t * dx, a[1] + t * dy], p);
2421
+ }
2422
+ function polygonDistance(c, p) {
2423
+ let best = Infinity;
2424
+ for (let i = 0; i < c.length; i++) best = Math.min(best, segDistance(c[i], c[(i + 1) % c.length], p));
2425
+ return best;
2426
+ }
2427
+ function ellipseRms(p, x0, y0, w, h) {
2428
+ const rx = w / 2;
2429
+ const ry = h / 2;
2430
+ if (rx <= 0 || ry <= 0 || !p.length) return Infinity;
2431
+ const cx = x0 + rx;
2432
+ const cy = y0 + ry;
2433
+ let sum = 0;
2434
+ for (const [x, y] of p) sum += (Math.hypot((x - cx) / rx, (y - cy) / ry) - 1) ** 2;
2435
+ return Math.sqrt(sum / p.length);
2436
+ }
2437
+ function snapSquare(x, y, w, h) {
2438
+ if (Math.abs(w - h) <= 0.12 * Math.max(w, h)) {
2439
+ const s = (w + h) / 2;
2440
+ return [x + (w - s) / 2, y + (h - s) / 2, s, s];
2441
+ }
2442
+ return [x, y, w, h];
2443
+ }
2444
+ function turning(a, b, c) {
2445
+ const u = [b[0] - a[0], b[1] - a[1]];
2446
+ const v = [c[0] - b[0], c[1] - b[1]];
2447
+ const lu = Math.hypot(u[0], u[1]);
2448
+ const lv = Math.hypot(v[0], v[1]);
2449
+ if (lu <= 0 || lv <= 0) return 0;
2450
+ return Math.acos(Math.min(1, Math.max(-1, (u[0] * v[0] + u[1] * v[1]) / (lu * lv)))) * 180 / Math.PI;
2451
+ }
2452
+ function rdp(p, eps) {
2453
+ const n = p.length;
2454
+ if (n < 3) return p.slice();
2455
+ const keep = new Array(n).fill(false);
2456
+ keep[0] = keep[n - 1] = true;
2457
+ const stack = [[0, n - 1]];
2458
+ while (stack.length) {
2459
+ const [a, b] = stack.pop();
2460
+ let far = a;
2461
+ let farD = 0;
2462
+ for (let i = a + 1; i < b; i++) {
2463
+ const d = segDistance(p[a], p[b], p[i]);
2464
+ if (d > farD) {
2465
+ farD = d;
2466
+ far = i;
2467
+ }
2468
+ }
2469
+ if (farD > eps) {
2470
+ keep[far] = true;
2471
+ stack.push([a, far], [far, b]);
2472
+ }
2473
+ }
2474
+ return p.filter((_, i) => keep[i]);
2475
+ }
2476
+ function corners(p, diag) {
2477
+ const eps = 0.05 * diag;
2478
+ const start = p[0];
2479
+ let far = 0;
2480
+ let farD = 0;
2481
+ p.forEach((q, i) => {
2482
+ const d = dist2(start, q);
2483
+ if (d > farD) {
2484
+ farD = d;
2485
+ far = i;
2486
+ }
2487
+ });
2488
+ const a = p.slice(0, far);
2489
+ const b = p.slice(far);
2490
+ const ring = rdp([...a, ...b.slice(0, 1)], eps);
2491
+ const back = rdp([...b, start], eps);
2492
+ ring.pop();
2493
+ back.pop();
2494
+ ring.push(...back);
2495
+ while (ring.length > 3) {
2496
+ const n = ring.length;
2497
+ const turn = (i) => turning(ring[(i + n - 1) % n], ring[i], ring[(i + 1) % n]);
2498
+ let flat = 0;
2499
+ let flatT = Infinity;
2500
+ for (let i = 0; i < n; i++) {
2501
+ const t = turn(i);
2502
+ if (t < flatT) {
2503
+ flatT = t;
2504
+ flat = i;
2505
+ }
2506
+ }
2507
+ let short = -1;
2508
+ for (let j = 0; j < n; j++) {
2509
+ if (dist2(ring[j], ring[(j + 1) % n]) < 0.1 * diag) {
2510
+ short = j;
2511
+ break;
2512
+ }
2513
+ }
2514
+ if (flatT < 30) ring.splice(flat, 1);
2515
+ else if (short >= 0) {
2516
+ const k = turn(short) < turn((short + 1) % n) ? short : (short + 1) % n;
2517
+ ring.splice(k, 1);
2518
+ } else break;
2519
+ }
2520
+ return ring;
2521
+ }
2522
+ function upright(c) {
2523
+ const n = c.length;
2524
+ const dirs = c.map((a, i) => {
2525
+ const b = c[(i + 1) % n];
2526
+ const deg = (Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI % 180 + 180) % 180;
2527
+ if (deg <= 15 || deg >= 165) return "across";
2528
+ if (deg >= 75 && deg <= 105) return "down";
2529
+ return null;
2530
+ });
2531
+ return dirs.every((d) => d !== null) && dirs.every((d, i) => d !== dirs[(i + 1) % n]);
2532
+ }
2533
+ function recognizeInkShape(points, options = {}) {
2534
+ const minSize = _nullishCoalesce(options.minSize, () => ( 6));
2535
+ const p = [];
2536
+ for (const q of points.slice(0, MAX_POINTS)) {
2537
+ if (!Number.isFinite(q[0]) || !Number.isFinite(q[1]) || Math.abs(q[0]) >= 1e9 || Math.abs(q[1]) >= 1e9) continue;
2538
+ const last2 = p[p.length - 1];
2539
+ if (!last2 || last2[0] !== q[0] || last2[1] !== q[1]) p.push([q[0], q[1]]);
2540
+ }
2541
+ if (p.length < 2) return null;
2542
+ const first = p[0];
2543
+ const last = p[p.length - 1];
2544
+ const [x0, y0, w, h] = bbox(p);
2545
+ const size = Math.max(w, h);
2546
+ const diag = Math.hypot(w, h);
2547
+ if (size < minSize) return null;
2548
+ let length2 = 0;
2549
+ for (let i = 1; i < p.length; i++) length2 += dist2(p[i - 1], p[i]);
2550
+ const gap = dist2(first, last);
2551
+ if (gap > 0.85 * length2 && gap >= minSize) {
2552
+ const off = Math.max(...p.map((q) => segDistance(first, last, q)));
2553
+ if (off <= Math.max(0.06 * gap, 1.5 * minSize / 6)) return { kind: "line", a: first, b: last };
2554
+ }
2555
+ if (gap > 0.2 * size || length2 < 2 * size) return null;
2556
+ const rms = ellipseRms(p, x0, y0, w, h);
2557
+ const roundish = w > 0.2 * size && h > 0.2 * size;
2558
+ const ellipse = () => {
2559
+ const [x, y, sw, sh] = snapSquare(x0, y0, w, h);
2560
+ return { kind: "ellipse", x, y, width: sw, height: sh };
2561
+ };
2562
+ if (roundish && rms < 0.06) return ellipse();
2563
+ const c = corners(p, diag);
2564
+ const fits = c.length >= 3 && p.every((q) => polygonDistance(c, q) <= 0.08 * diag);
2565
+ if (fits && c.length === 4 && upright(c)) {
2566
+ const [bx, by, bw, bh] = bbox(c);
2567
+ const [x, y, sw, sh] = snapSquare(bx, by, bw, bh);
2568
+ return { kind: "rect", x, y, width: sw, height: sh };
2569
+ }
2570
+ if (fits && c.length <= VECTOR_INK_MAX_CORNERS) return { kind: "polygon", points: c };
2571
+ return roundish && rms < 0.1 ? ellipse() : null;
2572
+ }
2573
+
1606
2574
  // src/vector/controller.ts
1607
2575
  var DEFAULT_VECTOR_SNAP = { grid: 0, objects: true, guides: true, threshold: 5, angles: VECTOR_CONSTRUCTION_ANGLES };
2576
+ var DEFAULT_VECTOR_PATH_EDIT = { keepShapeOnDelete: true, curvatureMoves: false, pencilSmoothing: 0, recognizeShapes: false, eraserSize: 10 };
2577
+ var VECTOR_PENCIL_STRING_PX = 100;
1608
2578
  var VECTOR_CLIPBOARD_TYPE = "pixerate/vector";
1609
2579
  var DEFAULT_DEFAULTS = {
1610
2580
  fill: "#d4d4d8",
@@ -1723,14 +2693,14 @@ function getVectorSelectionBoxGeometry(frame, viewport) {
1723
2693
  const rot = rotateMatrix(frame.angle, { x: viewport.x, y: viewport.y });
1724
2694
  const toScreen2 = (p) => frame.angle ? applyMatrix(rot, p) : { x: p.x, y: p.y };
1725
2695
  const handles = getTransformHandlePositions(box).map((h) => ({ ...h, screen: toScreen2(h) }));
1726
- const corners = [
2696
+ const corners2 = [
1727
2697
  { x: box.x, y: box.y },
1728
2698
  { x: box.x + box.width, y: box.y },
1729
2699
  { x: box.x + box.width, y: box.y + box.height },
1730
2700
  { x: box.x, y: box.y + box.height }
1731
2701
  ].map(toScreen2);
1732
2702
  const r = (n) => Math.round(n * 1e3) / 1e3;
1733
- return { box, angle: frame.angle, transform: frame.angle ? `rotate(${r(frame.angle)} ${r(viewport.x)} ${r(viewport.y)})` : "", handles, corners };
2703
+ return { box, angle: frame.angle, transform: frame.angle ? `rotate(${r(frame.angle)} ${r(viewport.x)} ${r(viewport.y)})` : "", handles, corners: corners2 };
1734
2704
  }
1735
2705
  var RESIZE_CURSOR_ORDER = ["ew-resize", "nwse-resize", "ns-resize", "nesw-resize"];
1736
2706
  var HANDLE_ANGLES = { e: 0, se: 45, s: 90, sw: 135, w: 180, nw: 225, n: 270, ne: 315 };
@@ -1748,9 +2718,12 @@ var TOOL_LABELS = {
1748
2718
  ellipse: "Ellipse",
1749
2719
  polygon: "Polygon",
1750
2720
  star: "Star",
1751
- line: "Line"
2721
+ line: "Line",
2722
+ scissors: "Scissors",
2723
+ knife: "Knife",
2724
+ eraser: "Eraser"
1752
2725
  };
1753
- var TOOL_KEYS = { v: "select", a: "direct", p: "pen", n: "pencil", r: "rect", o: "ellipse", e: "ellipse", y: "polygon", s: "star", l: "line" };
2726
+ var TOOL_KEYS = { v: "select", a: "direct", p: "pen", n: "pencil", r: "rect", o: "ellipse", y: "polygon", s: "star", l: "line", c: "scissors", k: "knife", e: "eraser" };
1754
2727
  function vectorToolLabel(tool) {
1755
2728
  return TOOL_LABELS[tool];
1756
2729
  }
@@ -1763,7 +2736,10 @@ var VECTOR_TOOL_SHORTCUTS = {
1763
2736
  ellipse: "O",
1764
2737
  polygon: "Y",
1765
2738
  star: "S",
1766
- line: "L"
2739
+ line: "L",
2740
+ scissors: "C",
2741
+ knife: "K",
2742
+ eraser: "E"
1767
2743
  };
1768
2744
  var VECTOR_TOOL_GLYPHS = {
1769
2745
  select: "\u2196",
@@ -1774,8 +2750,12 @@ var VECTOR_TOOL_GLYPHS = {
1774
2750
  ellipse: "\u25EF",
1775
2751
  polygon: "\u2B21",
1776
2752
  star: "\u2606",
1777
- line: "\u2571"
2753
+ line: "\u2571",
2754
+ scissors: "\u2702",
2755
+ knife: "\u27CB",
2756
+ eraser: "\u232B"
1778
2757
  };
2758
+ var SHAPE_TOOLS = /* @__PURE__ */ new Set(["rect", "ellipse", "polygon", "star", "line"]);
1779
2759
  var BOOLEAN_OPS = {
1780
2760
  union: "union",
1781
2761
  subtract: "difference",
@@ -1797,48 +2777,72 @@ function mapTree(nodes, fn) {
1797
2777
  }
1798
2778
  return changed ? out : nodes;
1799
2779
  }
2780
+ function spliceTree(nodes, id, replacements) {
2781
+ let changed = false;
2782
+ const out = [];
2783
+ for (const node of nodes) {
2784
+ if (node.id === id) {
2785
+ changed = true;
2786
+ out.push(...replacements);
2787
+ continue;
2788
+ }
2789
+ if (node.type === "group") {
2790
+ const children = spliceTree(node.children, id, replacements);
2791
+ if (children !== node.children) {
2792
+ changed = true;
2793
+ out.push({ ...node, children });
2794
+ continue;
2795
+ }
2796
+ }
2797
+ out.push(node);
2798
+ }
2799
+ return changed ? out : nodes;
2800
+ }
2801
+ var toPoint2 = (p) => [p.x, p.y];
1800
2802
  var isRoot = (m) => m === IDENTITY_MATRIX || m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
1801
2803
  var idCounter = 0;
1802
2804
  var defaultCreateId = () => `vn-${Date.now().toString(36)}${(++idCounter).toString(36)}`;
1803
- var VectorEditorController = (_class2 = class {
2805
+ var VectorEditorController = (_class3 = class {
1804
2806
 
1805
2807
 
1806
- __init7() {this.selection = []}
1807
- __init8() {this.anchors = []}
1808
- __init9() {this.editingId = null}
1809
- __init10() {this.pen = null}
2808
+ __init9() {this.selection = []}
2809
+ __init10() {this.anchors = []}
2810
+ __init11() {this.editingId = null}
2811
+ __init12() {this.pen = null}
1810
2812
 
1811
2813
 
1812
2814
 
1813
2815
 
1814
2816
 
1815
- __init11() {this.hover = null}
1816
- __init12() {this.pointer = null}
1817
- __init13() {this.drag = null}
1818
- __init14() {this.interaction = null}
1819
- __init15() {this.guides = null}
2817
+
2818
+ __init13() {this.hover = null}
2819
+ __init14() {this.pointer = null}
2820
+ __init15() {this.drag = null}
2821
+ __init16() {this.interaction = null}
2822
+ __init17() {this.guides = null}
1820
2823
  /** Whether the last `snapDrawn` landed on an object (guides may be hidden). */
1821
- __init16() {this.snappedToObject = false}
2824
+ __init18() {this.snappedToObject = false}
1822
2825
  /** Drawing targets for hovering the pen and shape tools, kept while the document and the excluded path stay the same. */
1823
- __init17() {this.hoverTargets = null}
1824
- __init18() {this.spaceHeld = false}
1825
- __init19() {this.clipboard = null}
2826
+ __init19() {this.hoverTargets = null}
2827
+ __init20() {this.spaceHeld = false}
2828
+ __init21() {this.clipboard = null}
1826
2829
 
1827
2830
 
1828
- __init20() {this.listeners = /* @__PURE__ */ new Set()}
1829
- __init21() {this.cleanups = []}
1830
- __init22() {this.applying = false}
2831
+ __init22() {this.listeners = /* @__PURE__ */ new Set()}
2832
+ __init23() {this.cleanups = []}
2833
+ __init24() {this.applying = false}
1831
2834
  /** Set while a commit records a change that was already applied, so `restore` still reports it. */
1832
- __init23() {this.committing = false}
1833
- __init24() {this.revision = 0}
2835
+ __init25() {this.committing = false}
2836
+ __init26() {this.revision = 0}
1834
2837
 
1835
- constructor(options = {}) {;_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);_class2.prototype.__init12.call(this);_class2.prototype.__init13.call(this);_class2.prototype.__init14.call(this);_class2.prototype.__init15.call(this);_class2.prototype.__init16.call(this);_class2.prototype.__init17.call(this);_class2.prototype.__init18.call(this);_class2.prototype.__init19.call(this);_class2.prototype.__init20.call(this);_class2.prototype.__init21.call(this);_class2.prototype.__init22.call(this);_class2.prototype.__init23.call(this);_class2.prototype.__init24.call(this);_class2.prototype.__init25.call(this);_class2.prototype.__init26.call(this);
2838
+ constructor(options = {}) {;_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);_class3.prototype.__init17.call(this);_class3.prototype.__init18.call(this);_class3.prototype.__init19.call(this);_class3.prototype.__init20.call(this);_class3.prototype.__init21.call(this);_class3.prototype.__init22.call(this);_class3.prototype.__init23.call(this);_class3.prototype.__init24.call(this);_class3.prototype.__init25.call(this);_class3.prototype.__init26.call(this);_class3.prototype.__init27.call(this);_class3.prototype.__init28.call(this);
1836
2839
  this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkQKLRAA4Tcjs.HistoryManager)()));
1837
2840
  this.doc = options.document ? cloneVectorDocument(options.document) : { width: 800, height: 600, nodes: [] };
1838
2841
  this.tool = _nullishCoalesce(options.tool, () => ( "select"));
1839
2842
  this.defaults = { ...DEFAULT_DEFAULTS, ..._nullishCoalesce(options.defaults, () => ( {})) };
1840
2843
  this.viewport = { x: 0, y: 0, zoom: 1, ..._nullishCoalesce(options.viewport, () => ( {})) };
1841
2844
  this.snap = { ...DEFAULT_VECTOR_SNAP, ..._nullishCoalesce(options.snap, () => ( {})) };
2845
+ this.editing = { ...DEFAULT_VECTOR_PATH_EDIT, ..._nullishCoalesce(options.editing, () => ( {})) };
1842
2846
  this.hitRadius = _nullishCoalesce(options.hitRadius, () => ( 6));
1843
2847
  this.createId = _nullishCoalesce(options.createId, () => ( defaultCreateId));
1844
2848
  this.onChange = options.onChange;
@@ -1860,9 +2864,9 @@ var VectorEditorController = (_class2 = class {
1860
2864
  // State
1861
2865
  // -------------------------------------------------------------------------
1862
2866
  /** The current snapshot (stable until the next change). */
1863
- __init25() {this.getState = () => this.state}
2867
+ __init27() {this.getState = () => this.state}
1864
2868
  /** Calls `listener` after every change. Returns an unsubscribe function. */
1865
- __init26() {this.subscribe = (listener) => {
2869
+ __init28() {this.subscribe = (listener) => {
1866
2870
  this.listeners.add(listener);
1867
2871
  return () => {
1868
2872
  this.listeners.delete(listener);
@@ -1887,6 +2891,7 @@ var VectorEditorController = (_class2 = class {
1887
2891
  viewport: this.viewport,
1888
2892
  defaults: this.defaults,
1889
2893
  snap: this.snap,
2894
+ editing: this.editing,
1890
2895
  guides: this.guides,
1891
2896
  hitRadius: this.hitRadius,
1892
2897
  hover: this.hover,
@@ -2088,6 +3093,14 @@ var VectorEditorController = (_class2 = class {
2088
3093
  if (!this.snap.guides || !this.snap.objects) this.guides = null;
2089
3094
  this.notify();
2090
3095
  }
3096
+ /** Changes how deleting, dragging anchors, the pencil and the eraser behave. */
3097
+ setEditing(editing) {
3098
+ const next = { ...this.editing, ...editing };
3099
+ next.pencilSmoothing = Math.min(1, Math.max(0, Number.isFinite(next.pencilSmoothing) ? next.pencilSmoothing : 0));
3100
+ if (!(next.eraserSize > 0)) next.eraserSize = this.editing.eraserSize;
3101
+ this.editing = next;
3102
+ this.notify();
3103
+ }
2091
3104
  /** Turns object snapping on or off (the toolbar's snapping toggle). */
2092
3105
  toggleSnapping(on = !this.snap.objects) {
2093
3106
  this.setSnap({ objects: on });
@@ -2150,7 +3163,7 @@ var VectorEditorController = (_class2 = class {
2150
3163
  const leave = last ? { at: { x: last.x, y: last.y }, constrain: input.shift ? "angle" : null } : null;
2151
3164
  return this.snapDrawn(p, leave, input, () => this.drawingTargets(_nullishCoalesce(_optionalChain([this, 'access', _67 => _67.pen, 'optionalAccess', _68 => _68.nodeId]), () => ( null))));
2152
3165
  }
2153
- if (this.tool !== "select" && this.tool !== "direct" && this.tool !== "pencil") return this.snapDrawn(p, null, input, () => this.drawingTargets(null));
3166
+ if (SHAPE_TOOLS.has(this.tool)) return this.snapDrawn(p, null, input, () => this.drawingTargets(null));
2154
3167
  this.setGuides(null);
2155
3168
  return p;
2156
3169
  }
@@ -2653,7 +3666,7 @@ var VectorEditorController = (_class2 = class {
2653
3666
  });
2654
3667
  const paths = [...new Set(order)].map((id2) => this.getNode(id2));
2655
3668
  if (paths.length < 2) return null;
2656
- const result = _chunkUG5DVXJKcjs.pathBoolean.call(void 0,
3669
+ const result = _chunkBGNPT5PEcjs.pathBoolean.call(void 0,
2657
3670
  BOOLEAN_OPS[op],
2658
3671
  paths.map((p) => ({
2659
3672
  path: vectorPathWorldBezier(
@@ -2703,7 +3716,7 @@ var VectorEditorController = (_class2 = class {
2703
3716
  /** A document-space path mapped into the parent space of node `id`. */
2704
3717
  intoParentSpace(id, path) {
2705
3718
  const parent = _nullishCoalesce(vectorParentMatrix(this.doc.nodes, id), () => ( IDENTITY_MATRIX));
2706
- return isRoot(parent) ? path : _chunkUG5DVXJKcjs.transformBezierPath.call(void 0, path, invertMatrix(parent));
3719
+ return isRoot(parent) ? path : _chunkBGNPT5PEcjs.transformBezierPath.call(void 0, path, invertMatrix(parent));
2707
3720
  }
2708
3721
  /** Splits selected compound paths into one path per subpath. */
2709
3722
  breakApart(options = {}) {
@@ -2753,12 +3766,47 @@ var VectorEditorController = (_class2 = class {
2753
3766
  return translateAnchor(a, d.x, d.y);
2754
3767
  });
2755
3768
  }
2756
- /** Moves one anchor to a document position (handles follow). */
2757
- moveAnchor(ref, to) {
3769
+ /**
3770
+ * Moves one anchor to a document position. Its handles follow; with `curvature` (default:
3771
+ * the `curvatureMoves` setting) its two segments bend instead, as the Curvature tool does.
3772
+ */
3773
+ moveAnchor(ref, to, options = {}) {
2758
3774
  const a = getAnchorWorld(this.doc, ref);
2759
3775
  if (!a) return { ok: false, message: "There is no such anchor." };
3776
+ const curvature = _nullishCoalesce(options.curvature, () => ( this.editing.curvatureMoves));
2760
3777
  this.change("Move Point", () => {
2761
- this.doc = this.movedAnchors(this.doc, [ref], to.x - a.x, to.y - a.y);
3778
+ this.doc = curvature ? this.curvatureMoved(this.doc, ref, to) : this.movedAnchors(this.doc, [ref], to.x - a.x, to.y - a.y);
3779
+ });
3780
+ return { ok: true };
3781
+ }
3782
+ /** `doc` with anchor `ref` moved to document point `to` by a Curvature-tool move. */
3783
+ curvatureMoved(doc, ref, to) {
3784
+ const node = findVectorNode(doc.nodes, ref.nodeId);
3785
+ if (_optionalChain([node, 'optionalAccess', _79 => _79.type]) !== "path" || node.locked || !node.subpaths[ref.subpath]) return doc;
3786
+ const q = this.toLocal(ref.nodeId, to, doc);
3787
+ const subpaths = node.subpaths.map((sp, i) => i === ref.subpath ? curvatureMoveAnchor(sp, ref.index, q) : sp);
3788
+ const nodes = mapTree(doc.nodes, (n) => n.id === ref.nodeId ? { ...node, subpaths } : n);
3789
+ return { ...doc, nodes };
3790
+ }
3791
+ /** `doc` with segment `ref` bent through document point `p` at parameter `t`. */
3792
+ bent(doc, ref, t, p) {
3793
+ const node = findVectorNode(doc.nodes, ref.nodeId);
3794
+ if (_optionalChain([node, 'optionalAccess', _80 => _80.type]) !== "path" || node.locked || !node.subpaths[ref.subpath]) return doc;
3795
+ const q = this.toLocal(ref.nodeId, p, doc);
3796
+ const subpaths = node.subpaths.map((sp, i) => i === ref.subpath ? bendSegment(sp, ref.segment, t, q) : sp);
3797
+ return { ...doc, nodes: mapTree(doc.nodes, (n) => n.id === ref.nodeId ? { ...node, subpaths } : n) };
3798
+ }
3799
+ /**
3800
+ * Bends a segment so the curve passes through document point `to` at parameter `t`
3801
+ * (default 0.5, clamped to 0.05–0.95); see `bendSegment` in `pathEdit`.
3802
+ */
3803
+ bendSegment(ref, to, t = 0.5) {
3804
+ const node = this.getNode(ref.nodeId);
3805
+ const sp = _optionalChain([node, 'optionalAccess', _81 => _81.type]) === "path" ? node.subpaths[ref.subpath] : void 0;
3806
+ if (!sp || ref.segment < 0 || ref.segment >= (sp.closed ? sp.anchors.length : sp.anchors.length - 1) || sp.anchors.length < 2) return { ok: false, message: "There is no such segment." };
3807
+ if (node.locked) return { ok: false, message: "The path is locked." };
3808
+ this.change("Bend Segment", () => {
3809
+ this.doc = this.bent(this.doc, ref, t, to);
2762
3810
  });
2763
3811
  return { ok: true };
2764
3812
  }
@@ -2789,7 +3837,7 @@ var VectorEditorController = (_class2 = class {
2789
3837
  /** Adds an anchor on the nearest segment of `nodeId` to document point `point` (shape unchanged). */
2790
3838
  insertAnchorAt(nodeId, point) {
2791
3839
  const node = this.getNode(nodeId);
2792
- if (_optionalChain([node, 'optionalAccess', _79 => _79.type]) !== "path") return null;
3840
+ if (_optionalChain([node, 'optionalAccess', _82 => _82.type]) !== "path") return null;
2793
3841
  const hit = nearestSegment(node, this.toLocal(nodeId, point));
2794
3842
  if (!hit) return null;
2795
3843
  const { subpath, index } = insertAnchor(node.subpaths[hit.subpath], hit.segment, hit.t);
@@ -2801,15 +3849,24 @@ var VectorEditorController = (_class2 = class {
2801
3849
  });
2802
3850
  return ref;
2803
3851
  }
2804
- /** Deletes the selected anchors; empty subpaths and paths go too. */
2805
- deleteAnchors(refs = this.anchors) {
3852
+ /**
3853
+ * Deletes the selected (or given) anchors; subpaths left with one anchor and empty paths go
3854
+ * too. With `keepShape` (default: the `keepShapeOnDelete` setting, on) the neighbours'
3855
+ * handles are refitted so the path keeps its shape; off, the neighbours are joined as they are.
3856
+ */
3857
+ deleteAnchors(refs = this.anchors, options = {}) {
2806
3858
  if (!refs.length) return { ok: false, message: "Select points to delete." };
3859
+ const keepShape = _nullishCoalesce(options.keepShape, () => ( this.editing.keepShapeOnDelete));
2807
3860
  this.change("Delete Points", () => {
2808
3861
  const nodes = mapTree(this.doc.nodes, (n) => {
2809
- if (n.type !== "path") return n;
3862
+ if (n.type !== "path" || n.locked) return n;
2810
3863
  const mine = refs.filter((r) => r.nodeId === n.id);
2811
3864
  if (!mine.length) return n;
2812
- const subpaths = n.subpaths.map((sp, s) => removeAnchors(sp, mine.filter((r) => r.subpath === s).map((r) => r.index))).filter((sp) => sp.anchors.length > 1);
3865
+ const subpaths = n.subpaths.map((sp, s) => {
3866
+ const indices = mine.filter((r) => r.subpath === s).map((r) => r.index);
3867
+ if (!indices.length) return sp;
3868
+ return keepShape ? removeAnchorsKeepingShape(sp, indices) : removeAnchors(sp, indices);
3869
+ }).filter((sp) => sp.anchors.length > 1);
2813
3870
  return subpaths.length ? { ...n, subpaths } : null;
2814
3871
  });
2815
3872
  this.setDoc(nodes);
@@ -2824,7 +3881,7 @@ var VectorEditorController = (_class2 = class {
2824
3881
  setClosed(closed, nodeId) {
2825
3882
  const targets = nodeId ? (() => {
2826
3883
  const n = this.getNode(nodeId);
2827
- return _optionalChain([n, 'optionalAccess', _80 => _80.type]) === "path" ? n.subpaths.map((_, s) => ({ nodeId, subpath: s })) : [];
3884
+ return _optionalChain([n, 'optionalAccess', _83 => _83.type]) === "path" ? n.subpaths.map((_, s) => ({ nodeId, subpath: s })) : [];
2828
3885
  })() : this.anchors.map((r) => ({ nodeId: r.nodeId, subpath: r.subpath }));
2829
3886
  if (!targets.length) return { ok: false, message: "Select a path or points." };
2830
3887
  this.change(closed ? "Close Path" : "Open Path", () => {
@@ -2847,11 +3904,274 @@ var VectorEditorController = (_class2 = class {
2847
3904
  });
2848
3905
  return { ok: true };
2849
3906
  }
3907
+ // -------------------------------------------------------------------------
3908
+ // Path clean-up, Scissors, Knife and Eraser
3909
+ // -------------------------------------------------------------------------
3910
+ /** Unlocked path nodes in the given roots (default: the paths being edited, else the selection). */
3911
+ pathTargets(ids) {
3912
+ const roots = _nullishCoalesce(ids, () => ( (this.tool === "direct" && this.anchors.length ? [...new Set(this.anchors.map((r) => r.nodeId))] : this.selection)));
3913
+ const out = /* @__PURE__ */ new Map();
3914
+ for (const id of roots) {
3915
+ const node = this.getNode(id);
3916
+ if (node && !node.locked) {
3917
+ for (const p of collectPathNodes(node)) if (!p.locked) out.set(p.id, p);
3918
+ }
3919
+ }
3920
+ const order = [];
3921
+ walkVectorNodes(this.doc.nodes, (n) => {
3922
+ if (out.has(n.id)) order.push(n.id);
3923
+ });
3924
+ return order.map((id) => out.get(id));
3925
+ }
3926
+ /** Subpaths of node `from` mapped into node `to`'s own space. */
3927
+ subpathsIn(from, to) {
3928
+ const m = multiplyMatrices(this.worldOf(from.id), invertMatrix(this.worldOf(to)));
3929
+ if (isRoot(m)) return from.subpaths;
3930
+ const map = (p) => applyMatrix(m, p);
3931
+ return from.subpaths.map((sp) => ({
3932
+ closed: sp.closed,
3933
+ anchors: sp.anchors.map((a) => ({ ...map(a), ...a.in ? { in: map(a.in) } : {}, ...a.out ? { out: map(a.out) } : {}, ...a.type ? { type: a.type } : {} }))
3934
+ }));
3935
+ }
3936
+ /**
3937
+ * Replaces a path with pieces: each entry becomes one path (the first keeps the node's id and
3938
+ * place, the others go just above it with the same style). No pieces removes the path.
3939
+ * Returns the ids of the paths now standing in for it.
3940
+ */
3941
+ replaceWithPieces(node, pieces) {
3942
+ const nodes = pieces.filter((p) => p.length).map((subpaths, i) => ({ ...node, id: i === 0 ? node.id : this.newId(), subpaths }));
3943
+ this.setDoc(spliceTree(this.doc.nodes, node.id, nodes));
3944
+ return nodes.map((n) => n.id);
3945
+ }
3946
+ /** Clears anchor selections, editing and pen state that no longer point at anything. */
3947
+ dropStaleRefs() {
3948
+ this.selection = this.selection.filter((id) => this.getNode(id));
3949
+ this.anchors = this.anchors.filter((r) => getAnchor(this.doc, r));
3950
+ if (this.editingId && !this.getNode(this.editingId)) this.editingId = null;
3951
+ if (this.pen && !this.getNode(this.pen.nodeId)) this.pen = null;
3952
+ }
3953
+ /**
3954
+ * Join: the open subpaths of the selected (or given) paths become one open path, joined
3955
+ * nearest endpoints first (ends within `tolerance` document units merge into one anchor,
3956
+ * others get a straight segment); a single open subpath is closed. The result replaces the
3957
+ * lowest path, in its space and style. Default tolerance: the pick distance.
3958
+ */
3959
+ joinPaths(options = {}) {
3960
+ const paths = this.pathTargets(options.ids);
3961
+ if (!paths.length) return { ok: false, message: "Select paths to join." };
3962
+ const base = paths[0];
3963
+ const all = paths.flatMap((p) => this.subpathsIn(p, base.id));
3964
+ if (!all.some((sp) => !sp.closed && sp.anchors.length > 1)) return { ok: false, message: "There are no open paths to join." };
3965
+ const subpaths = joinSubpaths(all, _nullishCoalesce(options.tolerance, () => ( this.tolerance())));
3966
+ const drop = new Set(paths.slice(1).map((p) => p.id));
3967
+ this.change("Join", () => {
3968
+ this.setDoc(mapTree(this.doc.nodes, (n) => n.id === base.id ? { ...base, subpaths } : drop.has(n.id) ? null : n));
3969
+ this.selection = [base.id];
3970
+ this.anchors = [];
3971
+ if (this.editingId && drop.has(this.editingId)) this.editingId = base.id;
3972
+ this.dropStaleRefs();
3973
+ });
3974
+ return { ok: true };
3975
+ }
3976
+ /** Average: moves the selected (or given) anchors onto their average position along `axis`, in document space. */
3977
+ averageAnchors(axis = "both", refs = this.anchors) {
3978
+ const points = refs.map((r) => ({ ref: r, at: getAnchorWorld(this.doc, r) })).filter((p) => !!p.at);
3979
+ if (points.length < 2) return { ok: false, message: "Select at least two points to average." };
3980
+ const cx = points.reduce((s, p) => s + p.at.x, 0) / points.length;
3981
+ const cy = points.reduce((s, p) => s + p.at.y, 0) / points.length;
3982
+ this.change("Average", () => {
3983
+ for (const { ref, at } of points) {
3984
+ const dx = axis === "horizontal" ? 0 : cx - at.x;
3985
+ const dy = axis === "vertical" ? 0 : cy - at.y;
3986
+ if (dx || dy) this.doc = this.movedAnchors(this.doc, [ref], dx, dy);
3987
+ }
3988
+ });
3989
+ return { ok: true };
3990
+ }
3991
+ /** Applies `fn` to every subpath of the target paths as one undo step. */
3992
+ editSubpaths(label, ids, fn) {
3993
+ const paths = this.pathTargets(ids);
3994
+ if (!paths.length) return { ok: false, message: "Select a path." };
3995
+ const set = new Set(paths.map((p) => p.id));
3996
+ this.change(label, () => {
3997
+ this.setDoc(mapTree(this.doc.nodes, (n) => n.type === "path" && set.has(n.id) ? { ...n, subpaths: n.subpaths.map((sp, i) => fn(sp, n, i)) } : n));
3998
+ this.dropStaleRefs();
3999
+ });
4000
+ return { ok: true };
4001
+ }
4002
+ /**
4003
+ * Remove Redundant Points: drops anchors of the selected (or given) paths that can go
4004
+ * without moving the outline more than `tolerance` document units (default 0.5).
4005
+ */
4006
+ removeRedundantPoints(options = {}) {
4007
+ const tolerance = _nullishCoalesce(options.tolerance, () => ( 0.5));
4008
+ return this.editSubpaths("Remove Redundant Points", options.ids, (sp) => removeRedundantPoints(sp, tolerance));
4009
+ }
4010
+ /**
4011
+ * Smooth: pulls handles towards smooth tangents by `amount` (0–1, default 1). With the direct
4012
+ * tool and points selected, only those points change.
4013
+ */
4014
+ smoothPaths(options = {}) {
4015
+ const refs = _nullishCoalesce(options.refs, () => ( (!options.ids && this.tool === "direct" && this.anchors.length ? this.anchors : null)));
4016
+ const ids = _nullishCoalesce(options.ids, () => ( (refs ? [...new Set(refs.map((r) => r.nodeId))] : void 0)));
4017
+ return this.editSubpaths("Smooth", ids, (sp, node, i) => {
4018
+ const only = refs ? new Set(refs.filter((r) => r.nodeId === node.id && r.subpath === i).map((r) => r.index)) : void 0;
4019
+ return only && !only.size ? sp : smoothSubpath(sp, _nullishCoalesce(options.amount, () => ( 1)), only);
4020
+ });
4021
+ }
4022
+ /** Splits a cut subpath into pieces: separate paths for a one-subpath node, subpaths in place for a compound one. */
4023
+ applyCuts(node, cut) {
4024
+ const results = node.subpaths.map((sp, i) => cut(sp, i));
4025
+ if (results.every((r) => !r)) return null;
4026
+ const subpaths = node.subpaths.flatMap((sp, i) => _nullishCoalesce(results[i], () => ( [sp])));
4027
+ if (node.subpaths.length === 1) return this.replaceWithPieces(node, subpaths.map((sp) => [sp]));
4028
+ this.replaceNode(node.id, { ...node, subpaths });
4029
+ return [node.id];
4030
+ }
4031
+ /**
4032
+ * Scissors: cuts path `nodeId` at the outline point nearest document point `point` (at an
4033
+ * anchor when within the pick distance of one). A closed subpath opens there; an open one
4034
+ * splits in two (a one-subpath path becomes two paths). Returns the ids of the paths.
4035
+ */
4036
+ cutPathAt(nodeId, point) {
4037
+ const node = this.getNode(nodeId);
4038
+ if (_optionalChain([node, 'optionalAccess', _84 => _84.type]) !== "path" || node.locked) return null;
4039
+ const local = this.toLocal(nodeId, point);
4040
+ const hit = nearestSegment(node, local);
4041
+ if (!hit) return null;
4042
+ const sp = node.subpaths[hit.subpath];
4043
+ const n = sp.anchors.length;
4044
+ const near2 = [hit.segment, (hit.segment + 1) % n].find((i) => Math.hypot(sp.anchors[i].x - hit.point.x, sp.anchors[i].y - hit.point.y) <= this.tolerance());
4045
+ if (!sp.closed && n > 1 && (near2 === 0 || near2 === n - 1) && !(hit.t > 1e-6 && hit.t < 1 - 1e-6)) return null;
4046
+ let ids = null;
4047
+ this.change("Cut Path", () => {
4048
+ ids = this.applyCuts(node, (s, i) => i !== hit.subpath ? null : near2 !== void 0 ? cutSubpathAt(s, [near2]) : splitSubpathAt(s, hit.segment, hit.t));
4049
+ if (ids) {
4050
+ this.selection = ids;
4051
+ this.anchors = [];
4052
+ if (this.editingId === nodeId) this.editingId = null;
4053
+ }
4054
+ this.dropStaleRefs();
4055
+ });
4056
+ return ids;
4057
+ }
4058
+ /** Scissors on the selected (or given) anchors: each cuts its subpath there (see `cutPathAt`). */
4059
+ cutAtAnchors(refs = this.anchors) {
4060
+ const byNode = /* @__PURE__ */ new Map();
4061
+ for (const r of refs) if (getAnchor(this.doc, r)) byNode.set(r.nodeId, [..._nullishCoalesce(byNode.get(r.nodeId), () => ( [])), r]);
4062
+ if (!byNode.size) return { ok: false, message: "Select points to cut at." };
4063
+ const created = [];
4064
+ this.change("Cut Path", () => {
4065
+ for (const [id, list] of byNode) {
4066
+ const node = this.getNode(id);
4067
+ if (node.locked) continue;
4068
+ const ids = this.applyCuts(node, (sp, i) => {
4069
+ const at = list.filter((r) => r.subpath === i).map((r) => r.index);
4070
+ const pieces = at.length ? cutSubpathAt(sp, at) : null;
4071
+ return pieces && !(pieces.length === 1 && pieces[0].closed === sp.closed) ? pieces : null;
4072
+ });
4073
+ if (ids) created.push(...ids);
4074
+ }
4075
+ if (created.length) this.selection = created;
4076
+ this.anchors = [];
4077
+ this.dropStaleRefs();
4078
+ });
4079
+ return created.length ? { ok: true } : { ok: false, message: "Points at the ends of open paths do not cut." };
4080
+ }
4081
+ /** Visible, unlocked paths a stroke over `points` can reach: in `ids`, the selection, or (with neither) any whose bounds it crosses. */
4082
+ strokeTargets(points, ids, pad) {
4083
+ const blocked = /* @__PURE__ */ new Set();
4084
+ walkVectorNodes(this.doc.nodes, (n, parent) => {
4085
+ if (n.visible === false || n.locked || parent && blocked.has(parent.id)) blocked.add(n.id);
4086
+ });
4087
+ const visible = (p) => !blocked.has(p.id);
4088
+ if (_optionalChain([ids, 'optionalAccess', _85 => _85.length]) || this.selection.length) return this.pathTargets(_nullishCoalesce(ids, () => ( this.selection))).filter(visible);
4089
+ const box = polylineBounds(points.map(toPoint2), pad);
4090
+ if (!box) return [];
4091
+ const out = [];
4092
+ walkVectorNodes(this.doc.nodes, (n) => {
4093
+ if (n.type !== "path" || !visible(n)) return;
4094
+ const b = vectorNodeBounds(n, _nullishCoalesce(vectorParentMatrix(this.doc.nodes, n.id), () => ( IDENTITY_MATRIX)));
4095
+ if (b && boundsIntersect(b, box)) out.push(n);
4096
+ });
4097
+ return out;
4098
+ }
4099
+ /** A stroke's points in node `id`'s own space. */
4100
+ localPoints(id, points) {
4101
+ const inv = invertMatrix(this.worldOf(id));
4102
+ return points.map((p) => toPoint2(applyMatrix(inv, p)));
4103
+ }
4104
+ /**
4105
+ * Knife: cuts closed paths along the polyline `points` (document space) wherever it crosses
4106
+ * them from edge to edge, with no gap between the pieces. It cuts the given paths, the
4107
+ * selection, or (with neither) every path it crosses. Returns the ids of the pieces (empty
4108
+ * when nothing was cut).
4109
+ */
4110
+ knife(points, options = {}) {
4111
+ const pts = simplifyPolyline(points.map(toPoint2), 0.25 / this.viewport.zoom).map(([x, y]) => ({ x, y }));
4112
+ if (pts.length < 2) return [];
4113
+ const plan = [];
4114
+ for (const node of this.strokeTargets(pts, options.ids, 0)) {
4115
+ const closed = node.subpaths.filter((sp) => sp.anchors.length > 1);
4116
+ if (!closed.length || closed.some((sp) => !sp.closed)) continue;
4117
+ const result = knifePath(subpathsToBezier(closed), _nullishCoalesce(node.fillRule, () => ( "nonzero")), this.localPoints(node.id, pts));
4118
+ if (result) plan.push({ node, pieces: result.map((path) => bezierToSubpaths(path)) });
4119
+ }
4120
+ if (!plan.length) return [];
4121
+ const created = [];
4122
+ this.change("Knife", () => {
4123
+ for (const { node, pieces } of plan) created.push(...this.replaceWithPieces(node, pieces));
4124
+ this.selection = created;
4125
+ this.anchors = [];
4126
+ this.dropStaleRefs();
4127
+ });
4128
+ return created;
4129
+ }
4130
+ /**
4131
+ * Eraser: rubs out a round stroke of diameter `size` (default: the `eraserSize` setting)
4132
+ * along `points` (document space). Filled closed paths lose that area (pieces that come
4133
+ * apart become separate paths); outlines are cut where they pass under it. It erases from
4134
+ * the given paths, the selection, or (with neither) every path it reaches. Returns the ids
4135
+ * of the paths it changed or created.
4136
+ */
4137
+ erase(points, options = {}) {
4138
+ const width = _nullishCoalesce(options.size, () => ( this.editing.eraserSize));
4139
+ if (!(width > 0) || !points.length) return [];
4140
+ const pts = simplifyPolyline(points.map(toPoint2), Math.min(width * 0.05, 0.25 / this.viewport.zoom));
4141
+ const region = eraserRegion(pts, width);
4142
+ if (!region.length) return [];
4143
+ const plan = [];
4144
+ const rb = _chunkBGNPT5PEcjs.bezierPathBounds.call(void 0, region);
4145
+ const reach = rb ? [{ x: rb.minX, y: rb.minY }, { x: rb.maxX, y: rb.maxY }] : points;
4146
+ for (const node of this.strokeTargets(reach, options.ids, 0)) {
4147
+ const local = _chunkBGNPT5PEcjs.transformBezierPath.call(void 0, region, invertMatrix(this.worldOf(node.id)));
4148
+ const live = node.subpaths.filter((sp) => sp.anchors.length > 1);
4149
+ if (!live.length) continue;
4150
+ if (hasPaint(node.fill) && live.every((sp) => sp.closed)) {
4151
+ const faces = eraseFromFill(subpathsToBezier(live), _nullishCoalesce(node.fillRule, () => ( "nonzero")), local);
4152
+ if (faces) plan.push({ node, pieces: faces.map((f) => bezierToSubpaths(f)) });
4153
+ continue;
4154
+ }
4155
+ const results = live.map((sp) => eraseFromOutline(sp, local));
4156
+ if (results.every((r) => !r)) continue;
4157
+ const subpaths = live.flatMap((sp, i) => _nullishCoalesce(results[i], () => ( [sp])));
4158
+ plan.push({ node, pieces: live.length === 1 ? subpaths.map((sp) => [sp]) : [subpaths] });
4159
+ }
4160
+ if (!plan.length) return [];
4161
+ const changed = [];
4162
+ this.change("Erase", () => {
4163
+ for (const { node, pieces } of plan) changed.push(...this.replaceWithPieces(node, pieces));
4164
+ this.anchors = [];
4165
+ this.dropStaleRefs();
4166
+ if (this.selection.length || options.ids) this.selection = [.../* @__PURE__ */ new Set([...this.selection, ...changed.filter((id) => this.getNode(id))])];
4167
+ });
4168
+ return changed;
4169
+ }
2850
4170
  /** Ends the pen path being drawn (a lone anchor is removed). */
2851
4171
  finishPath() {
2852
4172
  if (!this.pen) return;
2853
4173
  const node = this.getNode(this.pen.nodeId);
2854
- const sp = _optionalChain([node, 'optionalAccess', _81 => _81.subpaths, 'access', _82 => _82[this.pen.subpath]]);
4174
+ const sp = _optionalChain([node, 'optionalAccess', _86 => _86.subpaths, 'access', _87 => _87[this.pen.subpath]]);
2855
4175
  const pen = this.pen;
2856
4176
  this.pen = null;
2857
4177
  this.pointer = null;
@@ -2895,7 +4215,7 @@ var VectorEditorController = (_class2 = class {
2895
4215
  if (trimmed.startsWith("{")) {
2896
4216
  try {
2897
4217
  const data = JSON.parse(trimmed);
2898
- if (_optionalChain([data, 'optionalAccess', _83 => _83.type]) === VECTOR_CLIPBOARD_TYPE && Array.isArray(data.nodes)) nodes = data.nodes;
4218
+ if (_optionalChain([data, 'optionalAccess', _88 => _88.type]) === VECTOR_CLIPBOARD_TYPE && Array.isArray(data.nodes)) nodes = data.nodes;
2899
4219
  } catch (e2) {
2900
4220
  nodes = null;
2901
4221
  }
@@ -2907,7 +4227,7 @@ var VectorEditorController = (_class2 = class {
2907
4227
  }
2908
4228
  }
2909
4229
  } else nodes = this.clipboard;
2910
- if (!_optionalChain([nodes, 'optionalAccess', _84 => _84.length])) return [];
4230
+ if (!_optionalChain([nodes, 'optionalAccess', _89 => _89.length])) return [];
2911
4231
  const offset = _nullishCoalesce(options.offset, () => ( (text && !text.trim().startsWith("{") ? { x: 0, y: 0 } : { x: 10, y: 10 })));
2912
4232
  const copies = nodes.map((n) => transformVectorNode(this.freshIds(n), translateMatrix(offset.x, offset.y)));
2913
4233
  this.change("Paste", () => {
@@ -2958,7 +4278,7 @@ var VectorEditorController = (_class2 = class {
2958
4278
  hitHandle(screen) {
2959
4279
  for (const ref of getVisibleHandles(this.view()).reverse()) {
2960
4280
  const a = getAnchorWorld(this.doc, ref);
2961
- const h = _optionalChain([a, 'optionalAccess', _85 => _85[ref.handle]]);
4281
+ const h = _optionalChain([a, 'optionalAccess', _90 => _90[ref.handle]]);
2962
4282
  if (!h) continue;
2963
4283
  const s = this.documentToScreen(h);
2964
4284
  if (Math.hypot(s.x - screen.x, s.y - screen.y) <= this.hitRadius) return ref;
@@ -3039,10 +4359,38 @@ var VectorEditorController = (_class2 = class {
3039
4359
  const anchor = this.hitAnchor(screen, refs);
3040
4360
  return anchor ? { nodeId: anchor.nodeId, anchor } : null;
3041
4361
  }
4362
+ case "scissors": {
4363
+ const seg = this.nearestOutline(p);
4364
+ return seg ? { nodeId: seg.nodeId, segment: seg } : null;
4365
+ }
3042
4366
  default:
3043
4367
  return null;
3044
4368
  }
3045
4369
  }
4370
+ /** The nearest outline point of any visible, unlocked path within the pick distance of document point `p`. */
4371
+ nearestOutline(p) {
4372
+ let best = null;
4373
+ const tol = this.tolerance();
4374
+ const visit = (list, parent) => {
4375
+ for (let i = list.length - 1; i >= 0; i--) {
4376
+ const n = list[i];
4377
+ if (n.visible === false || n.locked) continue;
4378
+ const m = multiplyMatrices(nodeMatrix(n), parent);
4379
+ if (n.type === "group") {
4380
+ visit(n.children, m);
4381
+ continue;
4382
+ }
4383
+ const hit = nearestSegment(n, applyMatrix(invertMatrix(m), p));
4384
+ if (hit && hit.distance <= tol && (!best || hit.distance < best.distance - 1e-9)) {
4385
+ best = { nodeId: n.id, subpath: hit.subpath, segment: hit.segment, t: hit.t, point: applyMatrix(m, hit.point), distance: hit.distance };
4386
+ }
4387
+ }
4388
+ };
4389
+ visit(this.doc.nodes, IDENTITY_MATRIX);
4390
+ if (!best) return null;
4391
+ const { distance: _d, ...seg } = best;
4392
+ return seg;
4393
+ }
3046
4394
  sameHover(a, b) {
3047
4395
  return JSON.stringify(a) === JSON.stringify(b);
3048
4396
  }
@@ -3067,6 +4415,17 @@ var VectorEditorController = (_class2 = class {
3067
4415
  return this.downPen(screen, p, input);
3068
4416
  case "pencil":
3069
4417
  return this.downPencil(p);
4418
+ case "scissors": {
4419
+ const seg = this.nearestOutline(p);
4420
+ if (seg) this.cutPathAt(seg.nodeId, seg.point);
4421
+ return;
4422
+ }
4423
+ case "knife":
4424
+ case "eraser":
4425
+ this.drag = { kind: "stroke", tool: this.tool, points: [p], before: this.snapshot() };
4426
+ this.interaction = { type: "stroke", tool: this.tool, points: [p], width: this.tool === "eraser" ? this.editing.eraserSize : 0 };
4427
+ this.notify();
4428
+ return;
3070
4429
  default:
3071
4430
  return this.downShape(p, input);
3072
4431
  }
@@ -3161,7 +4520,7 @@ var VectorEditorController = (_class2 = class {
3161
4520
  const b = { nodeId: id, subpath: seg.subpath, index: (seg.segment + 1) % sp.anchors.length };
3162
4521
  this.anchors = input.shift ? [...this.anchors.filter((r) => !sameRef(r, a) && !sameRef(r, b)), a, b] : [a, b];
3163
4522
  this.editingId = id;
3164
- this.drag = { kind: "anchors", start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: null, lead: null };
4523
+ this.drag = { kind: "bend", ref: { nodeId: id, subpath: seg.subpath, segment: seg.segment }, t: seg.t, start: p, before: this.snapshot(), base: this.doc, moved: false };
3165
4524
  this.notify();
3166
4525
  return;
3167
4526
  }
@@ -3184,7 +4543,7 @@ var VectorEditorController = (_class2 = class {
3184
4543
  }
3185
4544
  downPen(screen, p, input) {
3186
4545
  const before = this.snapshot();
3187
- const sp = this.pen ? _optionalChain([this, 'access', _86 => _86.getNode, 'call', _87 => _87(this.pen.nodeId), 'optionalAccess', _88 => _88.subpaths, 'access', _89 => _89[this.pen.subpath]]) : void 0;
4546
+ const sp = this.pen ? _optionalChain([this, 'access', _91 => _91.getNode, 'call', _92 => _92(this.pen.nodeId), 'optionalAccess', _93 => _93.subpaths, 'access', _94 => _94[this.pen.subpath]]) : void 0;
3188
4547
  if (this.pen && sp) {
3189
4548
  const pen = this.pen;
3190
4549
  const first = { nodeId: pen.nodeId, subpath: pen.subpath, index: 0 };
@@ -3253,7 +4612,9 @@ var VectorEditorController = (_class2 = class {
3253
4612
  subpaths: [{ anchors: [{ x: p.x, y: p.y }], closed: false }]
3254
4613
  };
3255
4614
  this.setDoc([...this.doc.nodes, node]);
3256
- this.drag = { kind: "pencil", nodeId: node.id, points: [[p.x, p.y]], before };
4615
+ const lazy = new LazyMouse(this.editing.pencilSmoothing * VECTOR_PENCIL_STRING_PX / this.viewport.zoom);
4616
+ lazy.push([p.x, p.y]);
4617
+ this.drag = { kind: "pencil", nodeId: node.id, points: [[p.x, p.y]], before, lazy };
3257
4618
  this.interaction = { type: "draw", tool: "pencil", nodeId: node.id, bounds: { x: p.x, y: p.y, width: 0, height: 0 } };
3258
4619
  this.notify(true);
3259
4620
  }
@@ -3365,7 +4726,10 @@ var VectorEditorController = (_class2 = class {
3365
4726
  dx = q.x - lead.x;
3366
4727
  dy = q.y - lead.y;
3367
4728
  } else if (input.shift) Math.abs(dx) > Math.abs(dy) ? dy = 0 : dx = 0;
3368
- this.doc = this.movedAnchors(drag.base, this.anchors, dx, dy);
4729
+ const single = this.anchors.length === 1 ? this.anchors[0] : null;
4730
+ const from = single ? getAnchorWorld(drag.base, single) : void 0;
4731
+ if (single && from && this.editing.curvatureMoves !== !!input.alt) this.doc = this.curvatureMoved(drag.base, single, { x: from.x + dx, y: from.y + dy });
4732
+ else this.doc = this.movedAnchors(drag.base, this.anchors, dx, dy);
3369
4733
  this.interaction = { type: "anchors", dx, dy };
3370
4734
  this.notify(true);
3371
4735
  return;
@@ -3380,6 +4744,22 @@ var VectorEditorController = (_class2 = class {
3380
4744
  this.notify(true);
3381
4745
  return;
3382
4746
  }
4747
+ case "bend": {
4748
+ if (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 2) return;
4749
+ drag.moved = true;
4750
+ this.doc = this.bent(drag.base, drag.ref, drag.t, p);
4751
+ this.interaction = { type: "bend", ref: drag.ref };
4752
+ this.notify(true);
4753
+ return;
4754
+ }
4755
+ case "stroke": {
4756
+ const last = drag.points[drag.points.length - 1];
4757
+ if (Math.hypot(p.x - last.x, p.y - last.y) * this.viewport.zoom < 1.5) return;
4758
+ drag.points.push(p);
4759
+ this.interaction = { type: "stroke", tool: drag.tool, points: drag.points.slice(), width: drag.tool === "eraser" ? this.editing.eraserSize : 0 };
4760
+ this.notify();
4761
+ return;
4762
+ }
3383
4763
  case "pull": {
3384
4764
  const anchor = getAnchorWorld(drag.base, drag.ref);
3385
4765
  if (!anchor) return;
@@ -3402,7 +4782,7 @@ var VectorEditorController = (_class2 = class {
3402
4782
  }
3403
4783
  case "shape": {
3404
4784
  const node = this.getNode(drag.nodeId);
3405
- if (_optionalChain([node, 'optionalAccess', _90 => _90.type]) !== "path") return;
4785
+ if (_optionalChain([node, 'optionalAccess', _95 => _95.type]) !== "path") return;
3406
4786
  const leave = { at: drag.start, constrain: input.shift ? drag.tool === "line" ? "angle" : "square" : null, curves: false };
3407
4787
  let end = this.snapDrawn(p, leave, input, () => drag.targets ??= this.drawingTargets(null));
3408
4788
  if (!this.snappedToObject && drag.tool !== "line" && !input.shift && !input.mod && this.snap.objects && drag.targets) end = this.matchShapeSize(drag.targets, drag.start, end, input.alt);
@@ -3433,10 +4813,22 @@ var VectorEditorController = (_class2 = class {
3433
4813
  }
3434
4814
  case "pencil": {
3435
4815
  const last = drag.points[drag.points.length - 1];
3436
- if (Math.hypot(p.x - last[0], p.y - last[1]) * this.viewport.zoom < 1.5) return;
3437
- drag.points.push([p.x, p.y]);
4816
+ let moved = false;
4817
+ for (const q of drag.lazy.push([p.x, p.y])) {
4818
+ if (Math.hypot(q[0] - last[0], q[1] - last[1]) * this.viewport.zoom < 1.5) continue;
4819
+ drag.points.push(q);
4820
+ moved = true;
4821
+ }
4822
+ const string = drag.lazy.string();
3438
4823
  const node = this.getNode(drag.nodeId);
3439
- if (_optionalChain([node, 'optionalAccess', _91 => _91.type]) !== "path") return;
4824
+ if (_optionalChain([node, 'optionalAccess', _96 => _96.type]) !== "path") return;
4825
+ if (!moved) {
4826
+ if (string && _optionalChain([this, 'access', _97 => _97.interaction, 'optionalAccess', _98 => _98.type]) === "draw") {
4827
+ this.interaction = { ...this.interaction, string: [{ x: string[0][0], y: string[0][1] }, { x: string[1][0], y: string[1][1] }] };
4828
+ this.notify();
4829
+ }
4830
+ return;
4831
+ }
3440
4832
  this.replaceNode(drag.nodeId, { ...node, subpaths: [{ anchors: drag.points.map(([x, y]) => ({ x, y })), closed: false }] });
3441
4833
  const xs = drag.points.map((q) => q[0]);
3442
4834
  const ys = drag.points.map((q) => q[1]);
@@ -3444,7 +4836,8 @@ var VectorEditorController = (_class2 = class {
3444
4836
  type: "draw",
3445
4837
  tool: "pencil",
3446
4838
  nodeId: drag.nodeId,
3447
- bounds: { x: Math.min(...xs), y: Math.min(...ys), width: Math.max(...xs) - Math.min(...xs), height: Math.max(...ys) - Math.min(...ys) }
4839
+ bounds: { x: Math.min(...xs), y: Math.min(...ys), width: Math.max(...xs) - Math.min(...xs), height: Math.max(...ys) - Math.min(...ys) },
4840
+ ...string ? { string: [{ x: string[0][0], y: string[0][1] }, { x: string[1][0], y: string[1][1] }] } : {}
3448
4841
  };
3449
4842
  this.notify(true);
3450
4843
  return;
@@ -3466,8 +4859,8 @@ var VectorEditorController = (_class2 = class {
3466
4859
  }
3467
4860
  this.setGuides(res);
3468
4861
  return {
3469
- dx: _optionalChain([res, 'optionalAccess', _92 => _92.x]) ? dx + res.dx : axes.x ? this.gridValue(origin.x + dx) - origin.x : dx,
3470
- dy: _optionalChain([res, 'optionalAccess', _93 => _93.y]) ? dy + res.dy : axes.y ? this.gridValue(origin.y + dy) - origin.y : dy
4862
+ dx: _optionalChain([res, 'optionalAccess', _99 => _99.x]) ? dx + res.dx : axes.x ? this.gridValue(origin.x + dx) - origin.x : dx,
4863
+ dy: _optionalChain([res, 'optionalAccess', _100 => _100.y]) ? dy + res.dy : axes.y ? this.gridValue(origin.y + dy) - origin.y : dy
3471
4864
  };
3472
4865
  }
3473
4866
  /** The anchor a point drag snaps by, where it started: the grabbed one, else the moving one nearest the press. */
@@ -3536,6 +4929,18 @@ var VectorEditorController = (_class2 = class {
3536
4929
  return this.commit(drag.before.doc !== drag.base ? "Add Point" : "Move Points", drag.before);
3537
4930
  case "handle":
3538
4931
  return this.commit("Move Handle", drag.before);
4932
+ case "bend":
4933
+ if (!drag.moved) {
4934
+ this.notify();
4935
+ return;
4936
+ }
4937
+ return this.commit("Bend Segment", drag.before);
4938
+ case "stroke": {
4939
+ this.notify();
4940
+ if (drag.tool === "knife") this.knife(drag.points);
4941
+ else this.erase(drag.points);
4942
+ return;
4943
+ }
3539
4944
  case "pull":
3540
4945
  if (drag.closing) this.pen = null;
3541
4946
  return this.commit(drag.label, drag.before);
@@ -3555,12 +4960,19 @@ var VectorEditorController = (_class2 = class {
3555
4960
  }
3556
4961
  case "pencil": {
3557
4962
  const node = this.getNode(drag.nodeId);
3558
- if (_optionalChain([node, 'optionalAccess', _94 => _94.type]) !== "path" || drag.points.length < 2) {
4963
+ if (_optionalChain([node, 'optionalAccess', _101 => _101.type]) !== "path" || drag.points.length < 2) {
3559
4964
  this.doc = drag.before.doc;
3560
4965
  this.notify(true);
3561
4966
  return;
3562
4967
  }
3563
- const fitted = _chunkUG5DVXJKcjs.fitCubics.call(void 0, drag.points, 1.5 / this.viewport.zoom, { cornerAngleDeg: 60 });
4968
+ for (const q of drag.lazy.finish()) drag.points.push(q);
4969
+ const shape = this.editing.recognizeShapes ? recognizeInkShape(drag.points, { minSize: 6 / this.viewport.zoom }) : null;
4970
+ if (shape) {
4971
+ this.replaceNode(drag.nodeId, { ...node, name: inkShapeLabel(shape), subpaths: [inkShapeSubpath(shape)] });
4972
+ this.selection = [drag.nodeId];
4973
+ return this.commit(`Draw ${inkShapeLabel(shape)}`, drag.before);
4974
+ }
4975
+ const fitted = _chunkBGNPT5PEcjs.fitCubics.call(void 0, drag.points, 1.5 / this.viewport.zoom, { cornerAngleDeg: 60 });
3564
4976
  const anchors = [];
3565
4977
  for (const c of fitted) {
3566
4978
  if (!anchors.length) anchors.push({ x: c.p0[0], y: c.p0[1] });
@@ -3622,7 +5034,7 @@ var VectorEditorController = (_class2 = class {
3622
5034
  this.convertAnchors(a.in || a.out ? "corner" : "smooth", [anchor]);
3623
5035
  return;
3624
5036
  }
3625
- const id = _nullishCoalesce(_optionalChain([this, 'access', _95 => _95.hover, 'optionalAccess', _96 => _96.segment, 'optionalAccess', _97 => _97.nodeId]), () => ( this.editingId));
5037
+ const id = _nullishCoalesce(_optionalChain([this, 'access', _102 => _102.hover, 'optionalAccess', _103 => _103.segment, 'optionalAccess', _104 => _104.nodeId]), () => ( this.editingId));
3626
5038
  if (id) {
3627
5039
  const node = this.getNode(id);
3628
5040
  const seg = node ? nearestSegment(node, this.toLocal(id, p)) : null;
@@ -3666,6 +5078,8 @@ var VectorEditorController = (_class2 = class {
3666
5078
  case "g":
3667
5079
  input.shift ? this.ungroup() : this.group();
3668
5080
  return true;
5081
+ case "j":
5082
+ return input.alt ? this.averageAnchors("both").ok : this.joinPaths().ok;
3669
5083
  case "]":
3670
5084
  this.arrange(input.alt ? "front" : "forward");
3671
5085
  return true;
@@ -3700,7 +5114,7 @@ var VectorEditorController = (_class2 = class {
3700
5114
  this.finishPath();
3701
5115
  return true;
3702
5116
  }
3703
- if (this.tool === "select" && this.selection.length === 1 && _optionalChain([this, 'access', _98 => _98.getNode, 'call', _99 => _99(this.selection[0]), 'optionalAccess', _100 => _100.type]) === "path") {
5117
+ if (this.tool === "select" && this.selection.length === 1 && _optionalChain([this, 'access', _105 => _105.getNode, 'call', _106 => _106(this.selection[0]), 'optionalAccess', _107 => _107.type]) === "path") {
3704
5118
  this.tool = "direct";
3705
5119
  this.editingId = this.selection[0];
3706
5120
  this.notify();
@@ -3745,7 +5159,23 @@ var VectorEditorController = (_class2 = class {
3745
5159
  this.notify();
3746
5160
  }
3747
5161
  }
3748
- }, _class2);
5162
+ }, _class3);
5163
+ function inkShapeLabel(shape) {
5164
+ const name = vectorInkShapeName(shape);
5165
+ return name[0].toUpperCase() + name.slice(1);
5166
+ }
5167
+ function inkShapeSubpath(shape) {
5168
+ switch (shape.kind) {
5169
+ case "line":
5170
+ return lineSubpath({ x: shape.a[0], y: shape.a[1] }, { x: shape.b[0], y: shape.b[1] });
5171
+ case "rect":
5172
+ return rectSubpath(shape);
5173
+ case "ellipse":
5174
+ return ellipseSubpath(shape);
5175
+ case "polygon":
5176
+ return { anchors: shape.points.map(([x, y]) => ({ x, y })), closed: true };
5177
+ }
5178
+ }
3749
5179
  function constrain45(origin, p) {
3750
5180
  const dx = p.x - origin.x;
3751
5181
  const dy = p.y - origin.y;
@@ -3798,7 +5228,7 @@ function renderNode(node, defs, idPrefix) {
3798
5228
  strokeWidth: _nullishCoalesce(node.strokeWidth, () => ( 1)),
3799
5229
  ...node.strokeLinecap ? { strokeLinecap: node.strokeLinecap } : {},
3800
5230
  ...node.strokeLinejoin ? { strokeLinejoin: node.strokeLinejoin } : {},
3801
- ..._optionalChain([node, 'access', _101 => _101.strokeDasharray, 'optionalAccess', _102 => _102.length]) ? { strokeDasharray: node.strokeDasharray.join(" ") } : {},
5231
+ ..._optionalChain([node, 'access', _108 => _108.strokeDasharray, 'optionalAccess', _109 => _109.length]) ? { strokeDasharray: node.strokeDasharray.join(" ") } : {},
3802
5232
  fillRule: _nullishCoalesce(node.fillRule, () => ( "nonzero")),
3803
5233
  opacity: _nullishCoalesce(node.opacity, () => ( 1))
3804
5234
  };
@@ -3822,7 +5252,7 @@ function screenOutline(state, node) {
3822
5252
  const v = state.viewport;
3823
5253
  const view = [v.zoom, 0, 0, v.zoom, v.x, v.y];
3824
5254
  const parent = _nullishCoalesce(vectorParentMatrix(state.document.nodes, node.id), () => ( IDENTITY_MATRIX));
3825
- return collectWithMatrix(node, parent).map(({ path, parent: m }) => _chunkUG5DVXJKcjs.bezierPathToSvg.call(void 0, _chunkUG5DVXJKcjs.transformBezierPath.call(void 0, vectorPathWorldBezier(path, m), view), { precision: 2 })).join(" ");
5255
+ return collectWithMatrix(node, parent).map(({ path, parent: m }) => _chunkBGNPT5PEcjs.bezierPathToSvg.call(void 0, _chunkBGNPT5PEcjs.transformBezierPath.call(void 0, vectorPathWorldBezier(path, m), view), { precision: 2 })).join(" ");
3826
5256
  }
3827
5257
  function collectWithMatrix(node, parent) {
3828
5258
  if (node.type === "path") return [{ path: node, parent }];
@@ -3847,9 +5277,9 @@ function getVectorOverlayRenderData(state) {
3847
5277
  };
3848
5278
  for (const id of state.selection) outline(id, true);
3849
5279
  if (state.editingId) outline(state.editingId, true);
3850
- if (_optionalChain([state, 'access', _103 => _103.hover, 'optionalAccess', _104 => _104.nodeId]) && !state.interaction) outline(state.hover.nodeId, false);
5280
+ if (_optionalChain([state, 'access', _110 => _110.hover, 'optionalAccess', _111 => _111.nodeId]) && !state.interaction) outline(state.hover.nodeId, false);
3851
5281
  let selectionBox = null;
3852
- if (state.tool === "select" && state.selectionFrame && _optionalChain([state, 'access', _105 => _105.interaction, 'optionalAccess', _106 => _106.type]) !== "marquee") {
5282
+ if (state.tool === "select" && state.selectionFrame && _optionalChain([state, 'access', _112 => _112.interaction, 'optionalAccess', _113 => _113.type]) !== "marquee") {
3853
5283
  const g = getVectorSelectionBoxGeometry(state.selectionFrame, state.viewport);
3854
5284
  selectionBox = { ...g.box, angle: g.angle, transform: g.transform, corners: g.corners, handles: g.handles };
3855
5285
  }
@@ -3859,9 +5289,9 @@ function getVectorOverlayRenderData(state) {
3859
5289
  const a = getAnchorWorld(doc, ref);
3860
5290
  const s = toScreen(state, a);
3861
5291
  const closes = !!state.pen && state.pen.nodeId === ref.nodeId && state.pen.subpath === ref.subpath && ref.index === 0;
3862
- return { ref, x: s.x, y: s.y, type: _nullishCoalesce(a.type, () => ( inferAnchorType(a))), selected: selected(ref), hovered: sameAnchor(_optionalChain([state, 'access', _107 => _107.hover, 'optionalAccess', _108 => _108.anchor]), ref), closes };
5292
+ return { ref, x: s.x, y: s.y, type: _nullishCoalesce(a.type, () => ( inferAnchorType(a))), selected: selected(ref), hovered: sameAnchor(_optionalChain([state, 'access', _114 => _114.hover, 'optionalAccess', _115 => _115.anchor]), ref), closes };
3863
5293
  });
3864
- if (state.tool === "pen" && !state.pen && _optionalChain([state, 'access', _109 => _109.hover, 'optionalAccess', _110 => _110.anchor])) {
5294
+ if (state.tool === "pen" && !state.pen && _optionalChain([state, 'access', _116 => _116.hover, 'optionalAccess', _117 => _117.anchor])) {
3865
5295
  const a = getAnchorWorld(doc, state.hover.anchor);
3866
5296
  if (a) {
3867
5297
  const s = toScreen(state, a);
@@ -3870,15 +5300,15 @@ function getVectorOverlayRenderData(state) {
3870
5300
  }
3871
5301
  const handles = getVisibleHandles(state).flatMap((ref) => {
3872
5302
  const a = getAnchorWorld(doc, ref);
3873
- const h = _optionalChain([a, 'optionalAccess', _111 => _111[ref.handle]]);
5303
+ const h = _optionalChain([a, 'optionalAccess', _118 => _118[ref.handle]]);
3874
5304
  if (!a || !h) return [];
3875
5305
  const s = toScreen(state, h);
3876
5306
  const sa = toScreen(state, a);
3877
- const hovered = !!_optionalChain([state, 'access', _112 => _112.hover, 'optionalAccess', _113 => _113.handle]) && sameAnchor(state.hover.handle, ref) && state.hover.handle.handle === ref.handle;
5307
+ const hovered = !!_optionalChain([state, 'access', _119 => _119.hover, 'optionalAccess', _120 => _120.handle]) && sameAnchor(state.hover.handle, ref) && state.hover.handle.handle === ref.handle;
3878
5308
  return [{ ref, x: s.x, y: s.y, anchorX: sa.x, anchorY: sa.y, hovered }];
3879
5309
  });
3880
5310
  let marquee = null;
3881
- if (_optionalChain([state, 'access', _114 => _114.interaction, 'optionalAccess', _115 => _115.type]) === "marquee") {
5311
+ if (_optionalChain([state, 'access', _121 => _121.interaction, 'optionalAccess', _122 => _122.type]) === "marquee") {
3882
5312
  const b = state.interaction.bounds;
3883
5313
  const tl = toScreen(state, b);
3884
5314
  marquee = { x: tl.x, y: tl.y, width: b.width * z, height: b.height * z };
@@ -3886,7 +5316,7 @@ function getVectorOverlayRenderData(state) {
3886
5316
  let penPreview = null;
3887
5317
  if (state.tool === "pen" && state.pen && state.pointer && !state.interaction) {
3888
5318
  const node = findVectorNode(doc.nodes, state.pen.nodeId);
3889
- const sp = _optionalChain([node, 'optionalAccess', _116 => _116.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
5319
+ const sp = _optionalChain([node, 'optionalAccess', _123 => _123.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
3890
5320
  const last = sp ? getAnchorWorld(doc, { nodeId: state.pen.nodeId, subpath: state.pen.subpath, index: sp.anchors.length - 1 }) : void 0;
3891
5321
  if (last) {
3892
5322
  const a = toScreen(state, last);
@@ -3899,9 +5329,9 @@ function getVectorOverlayRenderData(state) {
3899
5329
  const artboard = { x: origin.x, y: origin.y, width: doc.width * z, height: doc.height * z };
3900
5330
  let status = null;
3901
5331
  const it = state.interaction;
3902
- if (_optionalChain([it, 'optionalAccess', _117 => _117.type]) === "rotate") status = `${Math.round(it.angle)}\xB0`;
3903
- else if (_optionalChain([it, 'optionalAccess', _118 => _118.type]) === "move" || _optionalChain([it, 'optionalAccess', _119 => _119.type]) === "anchors") status = `\u0394 ${fmt(it.dx)}, ${fmt(it.dy)}`;
3904
- else if (_optionalChain([it, 'optionalAccess', _120 => _120.type]) === "scale" || _optionalChain([it, 'optionalAccess', _121 => _121.type]) === "draw") {
5332
+ if (_optionalChain([it, 'optionalAccess', _124 => _124.type]) === "rotate") status = `${Math.round(it.angle)}\xB0`;
5333
+ else if (_optionalChain([it, 'optionalAccess', _125 => _125.type]) === "move" || _optionalChain([it, 'optionalAccess', _126 => _126.type]) === "anchors") status = `\u0394 ${fmt(it.dx)}, ${fmt(it.dy)}`;
5334
+ else if (_optionalChain([it, 'optionalAccess', _127 => _127.type]) === "scale" || _optionalChain([it, 'optionalAccess', _128 => _128.type]) === "draw") {
3905
5335
  const b = it.type === "draw" ? findVectorNode(doc.nodes, it.nodeId) ? vectorNodeBounds(findVectorNode(doc.nodes, it.nodeId)) : null : state.selectionFrame;
3906
5336
  if (b) status = `W ${fmt(b.width)} \xD7 H ${fmt(b.height)}`;
3907
5337
  }
@@ -3917,7 +5347,20 @@ function getVectorOverlayRenderData(state) {
3917
5347
  guides.labels.push({ kind: l.kind, x: fmt(p.x), y: fmt(p.y), text: l.text });
3918
5348
  }
3919
5349
  }
3920
- return { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, guides, status };
5350
+ let toolStroke = null;
5351
+ if (_optionalChain([it, 'optionalAccess', _129 => _129.type]) === "stroke") {
5352
+ const pts = it.points.map((q) => toScreen(state, q));
5353
+ const d = pts.map((q, i) => `${i ? "L" : "M"}${fmt(q.x)} ${fmt(q.y)}`).join("") + (pts.length === 1 ? `L${fmt(pts[0].x)} ${fmt(pts[0].y)}` : "");
5354
+ toolStroke = { tool: it.tool, d, width: fmt(it.width * z) };
5355
+ }
5356
+ let pencilString = null;
5357
+ if (_optionalChain([it, 'optionalAccess', _130 => _130.type]) === "draw" && it.string) {
5358
+ const a = toScreen(state, it.string[0]);
5359
+ const b = toScreen(state, it.string[1]);
5360
+ pencilString = { x1: fmt(a.x), y1: fmt(a.y), x2: fmt(b.x), y2: fmt(b.y) };
5361
+ }
5362
+ const cutPoint = state.tool === "scissors" && _optionalChain([state, 'access', _131 => _131.hover, 'optionalAccess', _132 => _132.segment]) && !it ? (({ x, y }) => ({ x: fmt(x), y: fmt(y) }))(toScreen(state, state.hover.segment.point)) : null;
5363
+ return { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint };
3921
5364
  }
3922
5365
  var VECTOR_OVERLAY_THEME = {
3923
5366
  accent: "#0ea5e9",
@@ -3925,6 +5368,8 @@ var VECTOR_OVERLAY_THEME = {
3925
5368
  guide: "#ff3dfc",
3926
5369
  handleFill: "#ffffff",
3927
5370
  marqueeFill: "rgba(14, 165, 233, 0.08)",
5371
+ /** The eraser's swath while it is dragged. */
5372
+ eraserFill: "rgba(239, 68, 68, 0.25)",
3928
5373
  transformHandleSize: 8,
3929
5374
  anchorSize: 7,
3930
5375
  handleRadius: 3.5
@@ -3943,7 +5388,7 @@ function isVectorTextTarget(target) {
3943
5388
  if (!el || typeof el !== "object") return false;
3944
5389
  const tag = (_nullishCoalesce(el.tagName, () => ( ""))).toUpperCase();
3945
5390
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
3946
- return !!el.isContentEditable || _optionalChain([el, 'access', _122 => _122.getAttribute, 'optionalCall', _123 => _123("contenteditable")]) === "true";
5391
+ return !!el.isContentEditable || _optionalChain([el, 'access', _133 => _133.getAttribute, 'optionalCall', _134 => _134("contenteditable")]) === "true";
3947
5392
  }
3948
5393
  function getVectorSelectionStyle(state) {
3949
5394
  const paths = [];
@@ -3970,12 +5415,39 @@ function vectorPaintToHex(paint) {
3970
5415
  const hex = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
3971
5416
  return `#${hex.toLowerCase()}`;
3972
5417
  }
5418
+ var VECTOR_PATH_ACTIONS = [
5419
+ { id: "join", label: "\u22C8", title: "Join", shortcut: "J", enabled: (s, p) => s + p > 0, run: (c) => c.joinPaths() },
5420
+ { id: "average", label: "\u22B9", title: "Average Points", shortcut: "Alt+J", enabled: (_s, p) => p > 1, run: (c) => c.averageAnchors("both") },
5421
+ { id: "cut", label: "\u2702\xB7", title: "Cut at Points", enabled: (_s, p) => p > 0, run: (c) => c.cutAtAnchors() },
5422
+ { id: "clean", label: "\u2301", title: "Remove Redundant Points", enabled: (s, p) => s + p > 0, run: (c) => c.removeRedundantPoints() },
5423
+ { id: "smooth", label: "\u223F", title: "Smooth", enabled: (s, p) => s + p > 0, run: (c) => c.smoothPaths() }
5424
+ ];
5425
+ var VECTOR_EDIT_TOGGLES = [
5426
+ { key: "keepShapeOnDelete", label: "Keep", title: "Keep shape when deleting points" },
5427
+ { key: "curvatureMoves", label: "Curv", title: "Curvature point moves (Alt flips while dragging)" },
5428
+ { key: "recognizeShapes", label: "Shapes", title: "Turn pencil scribbles into shapes" }
5429
+ ];
3973
5430
 
3974
5431
  // src/vector/commands.ts
3975
5432
  var fail = (message) => {
3976
5433
  throw new Error(message);
3977
5434
  };
3978
5435
  var IDS = { type: "array", items: { type: "string" }, description: "Node ids (default: the selection)." };
5436
+ var ANCHORS = {
5437
+ type: "array",
5438
+ items: {
5439
+ type: "object",
5440
+ properties: { nodeId: { type: "string" }, subpath: { type: "integer", minimum: 0 }, index: { type: "integer", minimum: 0 } },
5441
+ required: ["nodeId", "subpath", "index"]
5442
+ },
5443
+ description: "Anchors as { nodeId, subpath, index } (default: the selected points)."
5444
+ };
5445
+ var POINTS = {
5446
+ type: "array",
5447
+ items: { type: "array", items: { type: "number" }, minItems: 2, maxItems: 2 },
5448
+ minItems: 1,
5449
+ description: "Polyline [[x, y], \u2026] in document units."
5450
+ };
3979
5451
  var PAINT = {
3980
5452
  description: 'A CSS colour, `null` for none, or a gradient `{ type: "linear", angle, stops: [{ offset, color }] }`.'
3981
5453
  };
@@ -4002,12 +5474,20 @@ function createVectorCommands(controller, options = {}) {
4002
5474
  const base = { group, ...options.scope ? { scope: options.scope } : {} };
4003
5475
  const hasSelection = () => controller.getState().selection.length ? true : "Nothing is selected.";
4004
5476
  const ids = (params) => {
4005
- const list2 = _nullishCoalesce(_optionalChain([params, 'optionalAccess', _124 => _124.ids]), () => ( controller.getState().selection));
5477
+ const list2 = _nullishCoalesce(_optionalChain([params, 'optionalAccess', _135 => _135.ids]), () => ( controller.getState().selection));
4006
5478
  for (const id of list2) if (!controller.getNode(id)) fail(`There is no node "${id}".`);
4007
5479
  if (!list2.length) fail("Nothing is selected; pass ids.");
4008
5480
  return list2;
4009
5481
  };
4010
- const enabledFor = (_, params) => _optionalChain([params, 'optionalAccess', _125 => _125.ids, 'optionalAccess', _126 => _126.length]) ? true : hasSelection();
5482
+ const enabledFor = (_, params) => _optionalChain([params, 'optionalAccess', _136 => _136.ids, 'optionalAccess', _137 => _137.length]) ? true : hasSelection();
5483
+ const anchorsOf = (refs) => {
5484
+ if (!refs) return [...controller.getState().selectedAnchors];
5485
+ for (const r of refs) {
5486
+ const node = controller.getNode(r.nodeId);
5487
+ if (_optionalChain([node, 'optionalAccess', _138 => _138.type]) !== "path" || !_optionalChain([node, 'access', _139 => _139.subpaths, 'access', _140 => _140[r.subpath], 'optionalAccess', _141 => _141.anchors, 'access', _142 => _142[r.index]])) fail(`There is no anchor ${r.index} on subpath ${r.subpath} of "${r.nodeId}".`);
5488
+ }
5489
+ return refs;
5490
+ };
4011
5491
  const list = () => {
4012
5492
  const state = controller.getState();
4013
5493
  const out = [];
@@ -4094,7 +5574,7 @@ function createVectorCommands(controller, options = {}) {
4094
5574
  run: (_ctx, params) => {
4095
5575
  const id = controller.addPath(params.d, { ...pickStyle(params), ...params.name ? { name: params.name } : {} });
4096
5576
  const node = controller.getNode(id);
4097
- if (_optionalChain([node, 'optionalAccess', _127 => _127.type]) === "path" && !node.subpaths.length) {
5577
+ if (_optionalChain([node, 'optionalAccess', _143 => _143.type]) === "path" && !node.subpaths.length) {
4098
5578
  controller.undo();
4099
5579
  fail("The path data has no points.");
4100
5580
  }
@@ -4227,7 +5707,7 @@ function createVectorCommands(controller, options = {}) {
4227
5707
  enabled: enabledFor,
4228
5708
  undoable: true,
4229
5709
  run: (_ctx, params) => {
4230
- const id = controller.group({ ids: ids(params), name: _optionalChain([params, 'optionalAccess', _128 => _128.name]) });
5710
+ const id = controller.group({ ids: ids(params), name: _optionalChain([params, 'optionalAccess', _144 => _144.name]) });
4231
5711
  if (!id) fail("Only nodes that share a parent can be grouped.");
4232
5712
  return { id };
4233
5713
  }
@@ -4246,6 +5726,186 @@ function createVectorCommands(controller, options = {}) {
4246
5726
  return { selection: controller.getState().selection };
4247
5727
  }
4248
5728
  },
5729
+ {
5730
+ ...base,
5731
+ id: `${prefix}deleteAnchors`,
5732
+ label: "Delete Points",
5733
+ keywords: ["remove", "anchor", "point", "node"],
5734
+ description: "Deletes anchors. With `keepShape` (default: the editor setting, on) the neighbouring handles are refitted so the outline keeps its shape; off, the neighbours are joined as they are.",
5735
+ params: { type: "object", properties: { anchors: ANCHORS, keepShape: { type: "boolean" } } },
5736
+ undoable: true,
5737
+ run: (_ctx, params) => {
5738
+ check(controller.deleteAnchors(anchorsOf(_optionalChain([params, 'optionalAccess', _145 => _145.anchors])), { keepShape: _optionalChain([params, 'optionalAccess', _146 => _146.keepShape]) }));
5739
+ return { ok: true };
5740
+ }
5741
+ },
5742
+ {
5743
+ ...base,
5744
+ id: `${prefix}moveAnchor`,
5745
+ label: "Move Point\u2026",
5746
+ keywords: ["anchor", "drag", "curvature"],
5747
+ description: "Moves one anchor to (x, y) in document units. `curvature: true` bends its two segments like the Curvature tool instead of carrying its handles along.",
5748
+ params: {
5749
+ type: "object",
5750
+ properties: { nodeId: { type: "string" }, subpath: { type: "integer", minimum: 0 }, index: { type: "integer", minimum: 0 }, x: { type: "number" }, y: { type: "number" }, curvature: { type: "boolean" } },
5751
+ required: ["nodeId", "subpath", "index", "x", "y"]
5752
+ },
5753
+ undoable: true,
5754
+ run: (_ctx, params) => {
5755
+ check(controller.moveAnchor({ nodeId: params.nodeId, subpath: params.subpath, index: params.index }, { x: params.x, y: params.y }, { curvature: params.curvature }));
5756
+ return { ok: true };
5757
+ }
5758
+ },
5759
+ {
5760
+ ...base,
5761
+ id: `${prefix}bendSegment`,
5762
+ label: "Bend Segment\u2026",
5763
+ keywords: ["curve", "segment", "drag"],
5764
+ description: "Bends segment `segment` (from anchor `segment` to the next) so the curve passes through (x, y) at parameter `t` (default 0.5).",
5765
+ params: {
5766
+ type: "object",
5767
+ properties: {
5768
+ nodeId: { type: "string" },
5769
+ subpath: { type: "integer", minimum: 0 },
5770
+ segment: { type: "integer", minimum: 0 },
5771
+ x: { type: "number" },
5772
+ y: { type: "number" },
5773
+ t: { type: "number", minimum: 0, maximum: 1 }
5774
+ },
5775
+ required: ["nodeId", "subpath", "segment", "x", "y"]
5776
+ },
5777
+ undoable: true,
5778
+ run: (_ctx, params) => {
5779
+ check(controller.bendSegment({ nodeId: params.nodeId, subpath: params.subpath, segment: params.segment }, { x: params.x, y: params.y }, params.t));
5780
+ return { ok: true };
5781
+ }
5782
+ },
5783
+ {
5784
+ ...base,
5785
+ id: `${prefix}join`,
5786
+ label: "Join",
5787
+ shortcut: "Mod+J",
5788
+ keywords: ["connect", "close", "merge", "endpoints"],
5789
+ description: "Joins the open subpaths of paths nearest endpoints first (ends within `tolerance` merge); a single open path is closed.",
5790
+ params: { type: "object", properties: { ids: IDS, tolerance: { type: "number", minimum: 0 } } },
5791
+ undoable: true,
5792
+ run: (_ctx, params) => {
5793
+ check(controller.joinPaths({ ids: _optionalChain([params, 'optionalAccess', _147 => _147.ids]) ? ids(params) : void 0, tolerance: _optionalChain([params, 'optionalAccess', _148 => _148.tolerance]) }));
5794
+ return { selection: controller.getState().selection };
5795
+ }
5796
+ },
5797
+ {
5798
+ ...base,
5799
+ id: `${prefix}average`,
5800
+ label: "Average Points\u2026",
5801
+ shortcut: "Mod+Alt+J",
5802
+ keywords: ["align", "points", "anchors", "collapse"],
5803
+ description: "Moves anchors onto their average position: the same y (`horizontal`), the same x (`vertical`) or the same point (`both`, default).",
5804
+ params: { type: "object", properties: { axis: { type: "string", enum: ["horizontal", "vertical", "both"] }, anchors: ANCHORS } },
5805
+ undoable: true,
5806
+ run: (_ctx, params) => {
5807
+ check(controller.averageAnchors(_nullishCoalesce(_optionalChain([params, 'optionalAccess', _149 => _149.axis]), () => ( "both")), anchorsOf(_optionalChain([params, 'optionalAccess', _150 => _150.anchors]))));
5808
+ return { ok: true };
5809
+ }
5810
+ },
5811
+ {
5812
+ ...base,
5813
+ id: `${prefix}removeRedundantPoints`,
5814
+ label: "Remove Redundant Points",
5815
+ keywords: ["simplify", "clean up", "anchors", "collinear"],
5816
+ description: "Drops anchors that can go without moving the outline more than `tolerance` document units (default 0.5).",
5817
+ params: { type: "object", properties: { ids: IDS, tolerance: { type: "number", minimum: 0 } } },
5818
+ enabled: enabledFor,
5819
+ undoable: true,
5820
+ run: (_ctx, params) => {
5821
+ check(controller.removeRedundantPoints({ ids: _optionalChain([params, 'optionalAccess', _151 => _151.ids]) ? ids(params) : void 0, tolerance: _optionalChain([params, 'optionalAccess', _152 => _152.tolerance]) }));
5822
+ return { ok: true };
5823
+ }
5824
+ },
5825
+ {
5826
+ ...base,
5827
+ id: `${prefix}smooth`,
5828
+ label: "Smooth Path",
5829
+ keywords: ["round", "soften", "handles", "curves"],
5830
+ description: "Pulls the handles of paths (or of the selected points) towards smooth tangents by `amount` (0\u20131, default 1).",
5831
+ params: { type: "object", properties: { ids: IDS, amount: { type: "number", minimum: 0, maximum: 1 }, anchors: ANCHORS } },
5832
+ undoable: true,
5833
+ run: (_ctx, params) => {
5834
+ check(controller.smoothPaths({ ids: _optionalChain([params, 'optionalAccess', _153 => _153.ids]) ? ids(params) : void 0, amount: _optionalChain([params, 'optionalAccess', _154 => _154.amount]), refs: _optionalChain([params, 'optionalAccess', _155 => _155.anchors]) }));
5835
+ return { ok: true };
5836
+ }
5837
+ },
5838
+ {
5839
+ ...base,
5840
+ id: `${prefix}cut`,
5841
+ label: "Cut Path (Scissors)\u2026",
5842
+ keywords: ["scissors", "split", "open", "break"],
5843
+ description: "Scissors: cuts path `nodeId` at the outline point nearest (x, y), or cuts at `anchors`. A closed path opens; an open one splits in two.",
5844
+ params: { type: "object", properties: { nodeId: { type: "string" }, x: { type: "number" }, y: { type: "number" }, anchors: ANCHORS } },
5845
+ undoable: true,
5846
+ run: (_ctx, params) => {
5847
+ if (_optionalChain([params, 'optionalAccess', _156 => _156.nodeId]) !== void 0) {
5848
+ if (!controller.getNode(params.nodeId)) fail(`There is no node "${params.nodeId}".`);
5849
+ if (params.x === void 0 || params.y === void 0) fail("Pass x and y with nodeId.");
5850
+ const created = controller.cutPathAt(params.nodeId, { x: params.x, y: params.y });
5851
+ if (!created) fail("Nothing to cut there (the end of an open path, or a locked path).");
5852
+ return { ids: created };
5853
+ }
5854
+ check(controller.cutAtAnchors(anchorsOf(_optionalChain([params, 'optionalAccess', _157 => _157.anchors]))));
5855
+ return { ids: controller.getState().selection };
5856
+ }
5857
+ },
5858
+ {
5859
+ ...base,
5860
+ id: `${prefix}knife`,
5861
+ label: "Knife\u2026",
5862
+ keywords: ["slice", "cut", "split", "divide"],
5863
+ description: "Cuts closed paths along a polyline wherever it crosses them edge to edge (no gap between pieces). Cuts `ids`, else the selection, else every path the line crosses.",
5864
+ params: { type: "object", properties: { points: { ...POINTS, minItems: 2 }, ids: IDS }, required: ["points"] },
5865
+ undoable: true,
5866
+ run: (_ctx, params) => {
5867
+ const created = controller.knife(params.points.map(([x, y]) => ({ x, y })), { ids: params.ids ? ids(params) : void 0 });
5868
+ if (!created.length) fail("The knife did not cross any closed path from edge to edge.");
5869
+ return { ids: created };
5870
+ }
5871
+ },
5872
+ {
5873
+ ...base,
5874
+ id: `${prefix}erase`,
5875
+ label: "Erase\u2026",
5876
+ keywords: ["eraser", "rub out", "remove"],
5877
+ description: "Erases a round stroke of diameter `size` (default: the eraser size setting) along a polyline: filled shapes lose the area, outlines are cut under it. Erases from `ids`, else the selection, else every path it reaches.",
5878
+ params: { type: "object", properties: { points: POINTS, size: { type: "number", exclusiveMinimum: 0 }, ids: IDS }, required: ["points"] },
5879
+ undoable: true,
5880
+ run: (_ctx, params) => {
5881
+ const changed = controller.erase(params.points.map(([x, y]) => ({ x, y })), { size: params.size, ids: params.ids ? ids(params) : void 0 });
5882
+ if (!changed.length) fail("The eraser did not touch any path.");
5883
+ return { ids: changed.filter((id) => controller.getNode(id)) };
5884
+ }
5885
+ },
5886
+ {
5887
+ ...base,
5888
+ id: `${prefix}setEditing`,
5889
+ label: "Path Editing Options\u2026",
5890
+ keywords: ["settings", "curvature", "pencil", "smoothing", "lazy mouse", "shapes", "eraser"],
5891
+ description: "Sets how editing behaves: `keepShapeOnDelete`, `curvatureMoves` (anchor drags bend like the Curvature tool), `pencilSmoothing` (lazy mouse, 0\u20131), `recognizeShapes` (pencil scribbles become lines, rectangles, ellipses, polygons) and `eraserSize`.",
5892
+ params: {
5893
+ type: "object",
5894
+ properties: {
5895
+ keepShapeOnDelete: { type: "boolean" },
5896
+ curvatureMoves: { type: "boolean" },
5897
+ pencilSmoothing: { type: "number", minimum: 0, maximum: 1 },
5898
+ recognizeShapes: { type: "boolean" },
5899
+ eraserSize: { type: "number", exclusiveMinimum: 0 }
5900
+ }
5901
+ },
5902
+ run: (_ctx, params) => {
5903
+ const patch = {};
5904
+ for (const key of ["keepShapeOnDelete", "curvatureMoves", "pencilSmoothing", "recognizeShapes", "eraserSize"]) if (_optionalChain([params, 'optionalAccess', _158 => _158[key]]) !== void 0) patch[key] = params[key];
5905
+ controller.setEditing(patch);
5906
+ return { editing: controller.getState().editing };
5907
+ }
5908
+ },
4249
5909
  {
4250
5910
  ...base,
4251
5911
  id: `${prefix}setTool`,
@@ -4266,7 +5926,7 @@ function createVectorCommands(controller, options = {}) {
4266
5926
  keywords: ["download", "save", "markup"],
4267
5927
  description: "Returns the document as SVG markup.",
4268
5928
  params: { type: "object", properties: { precision: { type: "integer", minimum: 0, maximum: 8 } } },
4269
- run: (_ctx, params) => ({ svg: controller.toSvg({ precision: _optionalChain([params, 'optionalAccess', _129 => _129.precision]) }) })
5929
+ run: (_ctx, params) => ({ svg: controller.toSvg({ precision: _optionalChain([params, 'optionalAccess', _159 => _159.precision]) }) })
4270
5930
  },
4271
5931
  {
4272
5932
  ...base,
@@ -4373,5 +6033,30 @@ function createVectorCommands(controller, options = {}) {
4373
6033
 
4374
6034
 
4375
6035
 
4376
- exports.DEFAULT_VECTOR_SNAP = DEFAULT_VECTOR_SNAP; exports.IDENTITY_MATRIX = IDENTITY_MATRIX; exports.KAPPA = KAPPA; exports.VECTOR_CLIPBOARD_TYPE = VECTOR_CLIPBOARD_TYPE; exports.VECTOR_CONSTRUCTION_ANGLES = VECTOR_CONSTRUCTION_ANGLES; exports.VECTOR_OVERLAY_THEME = VECTOR_OVERLAY_THEME; exports.VECTOR_ROTATE_HANDLE_OFFSET = VECTOR_ROTATE_HANDLE_OFFSET; exports.VECTOR_TOOLS = VECTOR_TOOLS; exports.VECTOR_TOOL_GLYPHS = VECTOR_TOOL_GLYPHS; exports.VECTOR_TOOL_SHORTCUTS = VECTOR_TOOL_SHORTCUTS; exports.VECTOR_TRANSFORM_HANDLES = VECTOR_TRANSFORM_HANDLES; exports.VectorEditorController = VectorEditorController; exports.VectorSnapTargets = VectorSnapTargets; exports.applyMatrix = applyMatrix; exports.applyMatrixLinear = applyMatrixLinear; exports.bezierToSubpaths = bezierToSubpaths; exports.boundsFromPoints = boundsFromPoints; exports.boundsIntersect = boundsIntersect; exports.cloneAnchor = cloneAnchor; exports.cloneSubpath = cloneSubpath; exports.cloneVectorDocument = cloneVectorDocument; exports.cloneVectorNode = cloneVectorNode; exports.collectPathNodes = collectPathNodes; exports.constrainToLeave = constrainToLeave; exports.convertAnchor = convertAnchor; exports.createVectorCommands = createVectorCommands; exports.decomposeRigid = decomposeRigid; exports.ellipseSubpath = ellipseSubpath; exports.findVectorNode = findVectorNode; exports.getAnchor = getAnchor; exports.getAnchorWorld = getAnchorWorld; exports.getEditablePathIds = getEditablePathIds; exports.getTransformHandlePositions = getTransformHandlePositions; exports.getVectorOverlayRenderData = getVectorOverlayRenderData; exports.getVectorSceneRenderData = getVectorSceneRenderData; exports.getVectorSelectionBoxGeometry = getVectorSelectionBoxGeometry; exports.getVectorSelectionFrame = getVectorSelectionFrame; exports.getVectorSelectionStyle = getVectorSelectionStyle; exports.getVisibleAnchors = getVisibleAnchors; exports.getVisibleHandles = getVisibleHandles; exports.hasPaint = hasPaint; exports.hitTestVectorNode = hitTestVectorNode; exports.hitTestVectorNodes = hitTestVectorNodes; exports.inferAnchorType = inferAnchorType; exports.insertAnchor = insertAnchor; exports.invertMatrix = invertMatrix; exports.isIdentityMatrix = isIdentityMatrix; exports.isVectorTextTarget = isVectorTextTarget; exports.lineSubpath = lineSubpath; exports.locateVectorNode = locateVectorNode; exports.matrixRotation = matrixRotation; exports.moveAnchorHandle = moveAnchorHandle; exports.multiplyMatrices = multiplyMatrices; exports.nearestSegment = nearestSegment; exports.nodeMatrix = nodeMatrix; exports.parseSvgTransform = parseSvgTransform; exports.parseVectorSvg = parseVectorSvg; exports.polygonSubpath = polygonSubpath; exports.rectSubpath = rectSubpath; exports.removeAnchors = removeAnchors; exports.reverseSubpath = reverseSubpath; exports.rotateMatrix = rotateMatrix; exports.roundSubpaths = roundSubpaths; exports.scaleFor = scaleFor; exports.scaleMatrix = scaleMatrix; exports.starSubpath = starSubpath; exports.subpathSegment = subpathSegment; exports.subpathSegmentCount = subpathSegmentCount; exports.subpathsToBezier = subpathsToBezier; exports.toVectorKeyInput = toVectorKeyInput; exports.toVectorModifiers = toVectorModifiers; exports.toVectorPointerInput = toVectorPointerInput; exports.transformVectorNode = transformVectorNode; exports.translateAnchor = translateAnchor; exports.translateMatrix = translateMatrix; exports.unionBounds = unionBounds; exports.vectorDocumentToSvg = vectorDocumentToSvg; exports.vectorNodeBounds = vectorNodeBounds; exports.vectorNodeRotation = vectorNodeRotation; exports.vectorNodeToBezier = vectorNodeToBezier; exports.vectorPaintToHex = vectorPaintToHex; exports.vectorPaintToSvg = vectorPaintToSvg; exports.vectorParentMatrix = vectorParentMatrix; exports.vectorPathData = vectorPathData; exports.vectorPathWorldBezier = vectorPathWorldBezier; exports.vectorResizeCursor = vectorResizeCursor; exports.vectorToolLabel = vectorToolLabel; exports.vectorWorldMatrix = vectorWorldMatrix; exports.walkVectorNodes = walkVectorNodes;
6036
+
6037
+
6038
+
6039
+
6040
+
6041
+
6042
+
6043
+
6044
+
6045
+
6046
+
6047
+
6048
+
6049
+
6050
+
6051
+
6052
+
6053
+
6054
+
6055
+
6056
+
6057
+
6058
+
6059
+
6060
+
6061
+ exports.DEFAULT_VECTOR_PATH_EDIT = DEFAULT_VECTOR_PATH_EDIT; exports.DEFAULT_VECTOR_SNAP = DEFAULT_VECTOR_SNAP; exports.IDENTITY_MATRIX = IDENTITY_MATRIX; exports.KAPPA = KAPPA; exports.LazyMouse = LazyMouse; exports.VECTOR_CLIPBOARD_TYPE = VECTOR_CLIPBOARD_TYPE; exports.VECTOR_CONSTRUCTION_ANGLES = VECTOR_CONSTRUCTION_ANGLES; exports.VECTOR_EDIT_TOGGLES = VECTOR_EDIT_TOGGLES; exports.VECTOR_INK_MAX_CORNERS = VECTOR_INK_MAX_CORNERS; exports.VECTOR_OVERLAY_THEME = VECTOR_OVERLAY_THEME; exports.VECTOR_PATH_ACTIONS = VECTOR_PATH_ACTIONS; exports.VECTOR_PENCIL_STRING_PX = VECTOR_PENCIL_STRING_PX; exports.VECTOR_ROTATE_HANDLE_OFFSET = VECTOR_ROTATE_HANDLE_OFFSET; exports.VECTOR_TOOLS = VECTOR_TOOLS; exports.VECTOR_TOOL_GLYPHS = VECTOR_TOOL_GLYPHS; exports.VECTOR_TOOL_SHORTCUTS = VECTOR_TOOL_SHORTCUTS; exports.VECTOR_TRANSFORM_HANDLES = VECTOR_TRANSFORM_HANDLES; exports.VectorEditorController = VectorEditorController; exports.VectorSnapTargets = VectorSnapTargets; exports.applyMatrix = applyMatrix; exports.applyMatrixLinear = applyMatrixLinear; exports.bendSegment = bendSegment; exports.bezierToSubpaths = bezierToSubpaths; exports.boundsFromPoints = boundsFromPoints; exports.boundsIntersect = boundsIntersect; exports.cloneAnchor = cloneAnchor; exports.cloneSubpath = cloneSubpath; exports.cloneVectorDocument = cloneVectorDocument; exports.cloneVectorNode = cloneVectorNode; exports.collectPathNodes = collectPathNodes; exports.constrainToLeave = constrainToLeave; exports.convertAnchor = convertAnchor; exports.createVectorCommands = createVectorCommands; exports.curvatureMoveAnchor = curvatureMoveAnchor; exports.cutSubpathAt = cutSubpathAt; exports.decomposeRigid = decomposeRigid; exports.ellipseSubpath = ellipseSubpath; exports.eraseFromFill = eraseFromFill; exports.eraseFromOutline = eraseFromOutline; exports.eraserRegion = eraserRegion; exports.findVectorNode = findVectorNode; exports.getAnchor = getAnchor; exports.getAnchorWorld = getAnchorWorld; exports.getEditablePathIds = getEditablePathIds; exports.getTransformHandlePositions = getTransformHandlePositions; exports.getVectorOverlayRenderData = getVectorOverlayRenderData; exports.getVectorSceneRenderData = getVectorSceneRenderData; exports.getVectorSelectionBoxGeometry = getVectorSelectionBoxGeometry; exports.getVectorSelectionFrame = getVectorSelectionFrame; exports.getVectorSelectionStyle = getVectorSelectionStyle; exports.getVisibleAnchors = getVisibleAnchors; exports.getVisibleHandles = getVisibleHandles; exports.hasPaint = hasPaint; exports.hitTestVectorNode = hitTestVectorNode; exports.hitTestVectorNodes = hitTestVectorNodes; exports.inferAnchorType = inferAnchorType; exports.insertAnchor = insertAnchor; exports.invertMatrix = invertMatrix; exports.isIdentityMatrix = isIdentityMatrix; exports.isVectorTextTarget = isVectorTextTarget; exports.joinSubpaths = joinSubpaths; exports.knifePath = knifePath; exports.lineSubpath = lineSubpath; exports.locateVectorNode = locateVectorNode; exports.matrixRotation = matrixRotation; exports.moveAnchorHandle = moveAnchorHandle; exports.multiplyMatrices = multiplyMatrices; exports.nearestSegment = nearestSegment; exports.nodeMatrix = nodeMatrix; exports.parseSvgTransform = parseSvgTransform; exports.parseVectorSvg = parseVectorSvg; exports.polygonSubpath = polygonSubpath; exports.polylineBounds = polylineBounds; exports.recognizeInkShape = recognizeInkShape; exports.rectSubpath = rectSubpath; exports.removeAnchorKeepingShape = removeAnchorKeepingShape; exports.removeAnchors = removeAnchors; exports.removeAnchorsKeepingShape = removeAnchorsKeepingShape; exports.removeRedundantPoints = removeRedundantPoints; exports.reverseSubpath = reverseSubpath; exports.rotateMatrix = rotateMatrix; exports.roundSubpaths = roundSubpaths; exports.scaleFor = scaleFor; exports.scaleMatrix = scaleMatrix; exports.simplifyPolyline = simplifyPolyline; exports.smoothSubpath = smoothSubpath; exports.splitIntoFaces = splitIntoFaces; exports.splitSubpathAt = splitSubpathAt; exports.starSubpath = starSubpath; exports.subpathPointAt = subpathPointAt; exports.subpathSegment = subpathSegment; exports.subpathSegmentCount = subpathSegmentCount; exports.subpathsToBezier = subpathsToBezier; exports.toVectorKeyInput = toVectorKeyInput; exports.toVectorModifiers = toVectorModifiers; exports.toVectorPointerInput = toVectorPointerInput; exports.transformVectorNode = transformVectorNode; exports.translateAnchor = translateAnchor; exports.translateMatrix = translateMatrix; exports.unionBounds = unionBounds; exports.vectorDocumentToSvg = vectorDocumentToSvg; exports.vectorInkShapeName = vectorInkShapeName; exports.vectorNodeBounds = vectorNodeBounds; exports.vectorNodeRotation = vectorNodeRotation; exports.vectorNodeToBezier = vectorNodeToBezier; exports.vectorPaintToHex = vectorPaintToHex; exports.vectorPaintToSvg = vectorPaintToSvg; exports.vectorParentMatrix = vectorParentMatrix; exports.vectorPathData = vectorPathData; exports.vectorPathWorldBezier = vectorPathWorldBezier; exports.vectorResizeCursor = vectorResizeCursor; exports.vectorToolLabel = vectorToolLabel; exports.vectorWorldMatrix = vectorWorldMatrix; exports.walkVectorNodes = walkVectorNodes;
4377
6062
  //# sourceMappingURL=index.cjs.map