@pixerate/editor 0.33.0 → 0.34.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;
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;
2
2
 
3
3
  var _chunkQKLRAA4Tcjs = require('../chunk-QKLRAA4T.cjs');
4
4
 
@@ -15,7 +15,9 @@ var _chunkQKLRAA4Tcjs = require('../chunk-QKLRAA4T.cjs');
15
15
 
16
16
 
17
17
 
18
- var _chunkM4XE4UMTcjs = require('../chunk-M4XE4UMT.cjs');
18
+
19
+
20
+ var _chunkUG5DVXJKcjs = require('../chunk-UG5DVXJK.cjs');
19
21
 
20
22
  // src/vector/types.ts
21
23
  var VECTOR_TOOLS = ["select", "direct", "pen", "pencil", "rect", "ellipse", "polygon", "star", "line"];
@@ -82,7 +84,7 @@ function vectorNodeToBezier(node) {
82
84
  }
83
85
  function vectorPathData(input, precision = 3) {
84
86
  const subpaths = Array.isArray(input) ? input : input.subpaths;
85
- return _chunkM4XE4UMTcjs.bezierPathToSvg.call(void 0, subpathsToBezier(subpaths), { precision });
87
+ return _chunkUG5DVXJKcjs.bezierPathToSvg.call(void 0, subpathsToBezier(subpaths), { precision });
86
88
  }
87
89
  function inferAnchorType(a) {
88
90
  if (isOnAnchor(a, a.in) || isOnAnchor(a, a.out)) return "corner";
@@ -98,7 +100,7 @@ function inferAnchorType(a) {
98
100
  return Math.abs(li - lo) <= 1e-3 * Math.max(li, lo) ? "symmetric" : "smooth";
99
101
  }
100
102
  function bezierToSubpaths(input) {
101
- const path = _chunkM4XE4UMTcjs.toBezierPath.call(void 0, input);
103
+ const path = _chunkUG5DVXJKcjs.toBezierPath.call(void 0, input);
102
104
  const out = [];
103
105
  let current = null;
104
106
  const last = () => current.anchors[current.anchors.length - 1];
@@ -195,14 +197,14 @@ function nodeMatrix(node) {
195
197
  function vectorPathWorldBezier(node, parent = IDENTITY_MATRIX) {
196
198
  const m = multiplyMatrices(nodeMatrix(node), parent);
197
199
  const path = subpathsToBezier(node.subpaths);
198
- return isIdentityMatrix(m) ? path : _chunkM4XE4UMTcjs.transformBezierPath.call(void 0, path, m);
200
+ return isIdentityMatrix(m) ? path : _chunkUG5DVXJKcjs.transformBezierPath.call(void 0, path, m);
199
201
  }
200
202
  function vectorNodeBounds(node, parent = IDENTITY_MATRIX) {
201
203
  if (node.type === "group") {
202
204
  const m = multiplyMatrices(nodeMatrix(node), parent);
203
205
  return unionBounds(node.children.map((c) => vectorNodeBounds(c, m)));
204
206
  }
205
- const b = _chunkM4XE4UMTcjs.bezierPathBounds.call(void 0, vectorPathWorldBezier(node, parent));
207
+ const b = _chunkUG5DVXJKcjs.bezierPathBounds.call(void 0, vectorPathWorldBezier(node, parent));
206
208
  if (!b) return null;
207
209
  return { x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY };
208
210
  }
@@ -236,9 +238,9 @@ function hitTestVectorNode(node, p, tolerance = 0, parent = IDENTITY_MATRIX) {
236
238
  const path = vectorPathWorldBezier(node, parent);
237
239
  if (path.length === 0) return false;
238
240
  const point = [p.x, p.y];
239
- if (hasPaint(node.fill) && _chunkM4XE4UMTcjs.pointInBezierPath.call(void 0, path, point, _nullishCoalesce(node.fillRule, () => ( "nonzero")))) return true;
241
+ if (hasPaint(node.fill) && _chunkUG5DVXJKcjs.pointInBezierPath.call(void 0, path, point, _nullishCoalesce(node.fillRule, () => ( "nonzero")))) return true;
240
242
  const reach = (hasPaint(node.stroke) ? (_nullishCoalesce(node.strokeWidth, () => ( 1))) / 2 : 0) + tolerance;
241
- return _chunkM4XE4UMTcjs.distanceToBezierPath.call(void 0, path, point) <= reach;
243
+ return _chunkUG5DVXJKcjs.distanceToBezierPath.call(void 0, path, point) <= reach;
242
244
  }
243
245
  function hitTestVectorNodes(nodes, p, tolerance = 0) {
244
246
  for (let i = nodes.length - 1; i >= 0; i--) {
@@ -254,9 +256,9 @@ function nearestSegment(node, p) {
254
256
  const count = subpathSegmentCount(sp);
255
257
  for (let i = 0; i < count; i++) {
256
258
  const seg = subpathSegment(sp, i);
257
- const hit = _chunkM4XE4UMTcjs.segmentNearest.call(void 0, seg, [p.x, p.y]);
259
+ const hit = _chunkUG5DVXJKcjs.segmentNearest.call(void 0, seg, [p.x, p.y]);
258
260
  if (!best || hit.distance < best.distance) {
259
- const pt = _chunkM4XE4UMTcjs.segmentPoint.call(void 0, seg, hit.t);
261
+ const pt = _chunkUG5DVXJKcjs.segmentPoint.call(void 0, seg, hit.t);
260
262
  best = { subpath: s, segment: i, t: hit.t, point: { x: pt[0], y: pt[1] }, distance: hit.distance };
261
263
  }
262
264
  }
@@ -427,7 +429,7 @@ function insertAnchor(sp, segment, t) {
427
429
  const ia = segment;
428
430
  const ib = (segment + 1) % n;
429
431
  const seg = subpathSegment(sp, segment);
430
- const [left, right] = _chunkM4XE4UMTcjs.splitSegment.call(void 0, seg, t);
432
+ const [left, right] = _chunkUG5DVXJKcjs.splitSegment.call(void 0, seg, t);
431
433
  const mid = left[left.length - 1];
432
434
  const anchor = { x: mid[0], y: mid[1] };
433
435
  if (seg.length === 4) {
@@ -535,7 +537,7 @@ var escapeAttr = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;")
535
537
  function vectorPaintToSvg(paint, gradientId) {
536
538
  if (!hasPaint(paint)) return { value: "none", gradient: null };
537
539
  if (typeof paint === "string") return { value: paint, gradient: null };
538
- return { value: `url(#${gradientId})`, gradient: _chunkM4XE4UMTcjs.gradientToSvgDef.call(void 0, paint, gradientId) };
540
+ return { value: `url(#${gradientId})`, gradient: _chunkUG5DVXJKcjs.gradientToSvgDef.call(void 0, paint, gradientId) };
539
541
  }
540
542
  function gradientMarkup(def) {
541
543
  const tag = def.type === "radial" ? "radialGradient" : "linearGradient";
@@ -549,11 +551,11 @@ function vectorDocumentToSvg(doc, options = {}) {
549
551
  const pretty = options.pretty !== false;
550
552
  const defs = [];
551
553
  const lines = [];
552
- const num = (n) => _chunkM4XE4UMTcjs.formatNumber.call(void 0, n, precision);
554
+ const num = (n) => _chunkUG5DVXJKcjs.formatNumber.call(void 0, n, precision);
553
555
  const indent = (depth) => pretty ? " ".repeat(depth) : "";
554
556
  const common = (node) => {
555
557
  let out2 = ` id="${escapeAttr(node.id)}"`;
556
- if (node.transform) out2 += ` transform="matrix(${node.transform.map((n) => _chunkM4XE4UMTcjs.formatNumber.call(void 0, n, 6)).join(" ")})"`;
558
+ if (node.transform) out2 += ` transform="matrix(${node.transform.map((n) => _chunkUG5DVXJKcjs.formatNumber.call(void 0, n, 6)).join(" ")})"`;
557
559
  if (node.opacity !== void 0 && node.opacity < 1) out2 += ` opacity="${num(node.opacity)}"`;
558
560
  if (node.visible === false) out2 += ` display="none"`;
559
561
  return out2;
@@ -850,7 +852,759 @@ function parseVectorSvg(svg, options = {}) {
850
852
  return { width, height, nodes };
851
853
  }
852
854
 
855
+ // src/vector/snap.ts
856
+ var VECTOR_CONSTRUCTION_ANGLES = [0, 45, 90, 135];
857
+ function constrainToLeave(leave, p) {
858
+ if (!_optionalChain([leave, 'optionalAccess', _8 => _8.constrain])) return p;
859
+ const dx = p.x - leave.at.x;
860
+ const dy = p.y - leave.at.y;
861
+ switch (leave.constrain) {
862
+ case "axis":
863
+ return Math.abs(dx) > Math.abs(dy) ? { x: p.x, y: leave.at.y } : { x: leave.at.x, y: p.y };
864
+ case "square": {
865
+ const s = Math.max(Math.abs(dx), Math.abs(dy));
866
+ return { x: leave.at.x + Math.sign(dx || 1) * s, y: leave.at.y + Math.sign(dy || 1) * s };
867
+ }
868
+ default: {
869
+ const len = Math.hypot(dx, dy);
870
+ const a = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);
871
+ return { x: leave.at.x + Math.cos(a) * len, y: leave.at.y + Math.sin(a) * len };
872
+ }
873
+ }
874
+ }
875
+ var BUDGET = 2e4;
876
+ var CROSSING_PAIRS = 32;
877
+ var CURVE_SAMPLES = 48;
878
+ var BISECT = 50;
879
+ var EQUAL = 1e-6;
880
+ function bound(sorted, key, v, strict) {
881
+ let lo = 0;
882
+ let hi = sorted.length;
883
+ while (lo < hi) {
884
+ const mid = lo + hi >>> 1;
885
+ const k = key(sorted[mid]);
886
+ if (strict ? k <= v : k < v) lo = mid + 1;
887
+ else hi = mid;
888
+ }
889
+ return lo;
890
+ }
891
+ function within(sorted, key, v, tol) {
892
+ return sorted.slice(bound(sorted, key, v - tol, false), bound(sorted, key, v + tol, true));
893
+ }
894
+ var linesNear = (lines, v, tol) => within(lines, (l) => l.v, v, tol);
895
+ var distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
896
+ var toPoint = (p) => [p.x, p.y];
897
+ var toVec = (p) => ({ x: p[0], y: p[1] });
898
+ var LABELS = {
899
+ anchor: "anchor",
900
+ centre: "centre",
901
+ intersect: "intersect",
902
+ perpendicular: "perpendicular",
903
+ tangent: "tangent",
904
+ path: "path",
905
+ construction: "angle",
906
+ align: "align",
907
+ spacing: "spacing",
908
+ size: "size"
909
+ };
910
+ var fmtLength = (n) => String(Math.round(n * 100) / 100);
911
+ function nodeFrame(node, parent, aabb) {
912
+ const angle = matrixRotation(multiplyMatrices(nodeMatrix(node), parent));
913
+ const b = angle ? vectorNodeBounds(node, multiplyMatrices(parent, rotateMatrix(-angle))) : aabb !== void 0 ? aabb : vectorNodeBounds(node, parent);
914
+ if (!b) return null;
915
+ const c = { x: b.x + b.width / 2, y: b.y + b.height / 2 };
916
+ return { width: b.width, height: b.height, centre: angle ? applyMatrix(rotateMatrix(angle), c) : c };
917
+ }
918
+ function contains(node, ids) {
919
+ if (ids.has(node.id)) return true;
920
+ return node.type === "group" && node.children.some((c) => contains(c, ids));
921
+ }
922
+ var VectorSnapTargets = (_class = class _VectorSnapTargets {
923
+ __init() {this.points = []}
924
+ __init2() {this.xs = []}
925
+ __init3() {this.ys = []}
926
+ __init4() {this.segments = []}
927
+ /** Bounds of the objects a moved box spaces itself evenly with. */
928
+ __init5() {this.boxes = []}
929
+ /** Side lengths of the other objects (in their own frames), which a resize may match. */
930
+ __init6() {this.sizes = []}
931
+ constructor() {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);
932
+ }
933
+ /** How many targets of each sort were gathered. */
934
+ get counts() {
935
+ return { points: this.points.length, lines: this.xs.length + this.ys.length, segments: this.segments.length, boxes: this.boxes.length };
936
+ }
937
+ /** Targets for moving nodes `ids`: the other art's anchors, bounds and centres, the artboard, and boxes to space evenly with. */
938
+ static forMove(doc, ids) {
939
+ return _VectorSnapTargets.collect(doc, new Set(ids), "bounds");
940
+ }
941
+ /** Targets for resizing nodes `ids`: as for a move, plus the other objects' sizes. */
942
+ static forResize(doc, ids) {
943
+ return _VectorSnapTargets.collect(doc, new Set(ids), "bounds");
944
+ }
945
+ /**
946
+ * Targets for a drawn point (pen, shape tools): the art but `exclude`, its anchors (as
947
+ * points and in line), centres, edges and segments. The anchors and segments of `exclude`
948
+ * (the path being drawn, whose bounds grow with it) stay targets.
949
+ */
950
+ static forDrawing(doc, exclude = []) {
951
+ const skip = new Set(exclude);
952
+ const t = _VectorSnapTargets.collect(doc, skip, "drawing", false);
953
+ t.pushPaths(doc, skip, () => false, true);
954
+ return t.index();
955
+ }
956
+ /**
957
+ * Targets for dragging anchors: the other art with its segments, and the anchors and
958
+ * segments that stay put on the paths being reshaped (their bounds move along, so they are
959
+ * no targets). The moving anchors are left out.
960
+ */
961
+ static forAnchorDrag(doc, anchors) {
962
+ const moving = /* @__PURE__ */ new Map();
963
+ for (const r of anchors) {
964
+ const set = _nullishCoalesce(moving.get(r.nodeId), () => ( /* @__PURE__ */ new Set()));
965
+ set.add(`${r.subpath}:${r.index}`);
966
+ moving.set(r.nodeId, set);
967
+ }
968
+ const t = _VectorSnapTargets.collect(doc, new Set(moving.keys()), "segments", false);
969
+ t.pushPaths(doc, new Set(moving.keys()), (id, s, i) => !!_optionalChain([moving, 'access', _9 => _9.get, 'call', _10 => _10(id), 'optionalAccess', _11 => _11.has, 'call', _12 => _12(`${s}:${i}`)]), true);
970
+ return t.index();
971
+ }
972
+ /**
973
+ * Targets for dragging a handle of path `nodeId`: the other art and that path's anchors
974
+ * (its bounds and the segments through the handle move with it, so they would chase it).
975
+ */
976
+ static forHandle(doc, nodeId) {
977
+ const skip = /* @__PURE__ */ new Set([nodeId]);
978
+ const t = _VectorSnapTargets.collect(doc, skip, "bounds", false);
979
+ t.pushPaths(doc, skip, () => false, false);
980
+ return t.index();
981
+ }
982
+ /** The anchors of the paths `ids` as targets (on them and in line), and their segments when `segments`. */
983
+ pushPaths(doc, ids, moving, segments) {
984
+ let anchors = BUDGET;
985
+ let segmentBudget = segments ? BUDGET : 0;
986
+ const visit = (list, parent) => {
987
+ for (const n of list) {
988
+ const m = multiplyMatrices(nodeMatrix(n), parent);
989
+ if (n.type === "group") visit(n.children, m);
990
+ else if (ids.has(n.id) && n.visible !== false) {
991
+ n.subpaths.forEach((sp, s) => {
992
+ sp.anchors.forEach((a, i) => {
993
+ if (anchors <= 0 || moving(n.id, s, i)) return;
994
+ this.pushAnchor(applyMatrix(m, a));
995
+ anchors--;
996
+ });
997
+ segmentBudget = this.pushSegments(sp, m, (i) => moving(n.id, s, i), segmentBudget);
998
+ });
999
+ }
1000
+ }
1001
+ };
1002
+ visit(doc.nodes, IDENTITY_MATRIX);
1003
+ }
1004
+ pushAnchor(p) {
1005
+ this.points.push({ p, kind: "anchor" });
1006
+ this.xs.push({ v: p.x, from: p, kind: "anchor" });
1007
+ this.ys.push({ v: p.y, from: p, kind: "anchor" });
1008
+ }
1009
+ /** The segments of `sp` (mapped by `m`) that stay put while the anchors `moving` accepts move. */
1010
+ pushSegments(sp, m, moving, budget) {
1011
+ const n = sp.anchors.length;
1012
+ const count = subpathSegmentCount(sp);
1013
+ for (let i = 0; i < count && budget > 0; i++) {
1014
+ if (moving(i) || moving((i + 1) % n)) continue;
1015
+ const seg = subpathSegment(sp, i).map((q) => toPoint(applyMatrix(m, toVec(q))));
1016
+ let minX = Infinity;
1017
+ let minY = Infinity;
1018
+ let maxX = -Infinity;
1019
+ let maxY = -Infinity;
1020
+ for (const [x, y] of seg) {
1021
+ minX = Math.min(minX, x);
1022
+ minY = Math.min(minY, y);
1023
+ maxX = Math.max(maxX, x);
1024
+ maxY = Math.max(maxY, y);
1025
+ }
1026
+ this.segments.push({ seg, minX, minY, maxX, maxY });
1027
+ budget--;
1028
+ }
1029
+ return budget;
1030
+ }
1031
+ pushRect(r, kind, centre) {
1032
+ const corners = [
1033
+ { x: r.x, y: r.y },
1034
+ { x: r.x + r.width, y: r.y },
1035
+ { x: r.x + r.width, y: r.y + r.height },
1036
+ { x: r.x, y: r.y + r.height }
1037
+ ];
1038
+ for (const p of corners) {
1039
+ this.xs.push({ v: p.x, from: p, kind });
1040
+ this.ys.push({ v: p.y, from: p, kind });
1041
+ }
1042
+ if (centre) {
1043
+ this.points.push({ p: centre, kind: "centre" });
1044
+ this.xs.push({ v: centre.x, from: centre, kind: "centre" });
1045
+ this.ys.push({ v: centre.y, from: centre, kind: "centre" });
1046
+ }
1047
+ }
1048
+ /** Sorts the targets for `within` (stable: of targets alike, the first gathered wins). */
1049
+ index() {
1050
+ this.points.sort((a, b) => a.p.x - b.p.x);
1051
+ this.xs.sort((a, b) => a.v - b.v);
1052
+ this.ys.sort((a, b) => a.v - b.v);
1053
+ return this;
1054
+ }
1055
+ /**
1056
+ * Gathers targets from the artboard and every visible node but `exclude` (and what is inside
1057
+ * those): each leaf's anchors (also as lines for `drawing`), its segments (`segments`,
1058
+ * `drawing`), its bounds' corners as lines and its centre; and the top-level objects' boxes and
1059
+ * sizes (inside a group holding an excluded node, that group's other members).
1060
+ */
1061
+ static collect(doc, exclude, gather, sort = true) {
1062
+ const self = new _VectorSnapTargets();
1063
+ const artboard = { x: 0, y: 0, width: doc.width, height: doc.height };
1064
+ self.pushRect(artboard, "artboard", { x: doc.width / 2, y: doc.height / 2 });
1065
+ let anchors = BUDGET;
1066
+ let segments = BUDGET;
1067
+ const visit = (list, parent) => {
1068
+ for (const n of list) {
1069
+ if (exclude.has(n.id) || n.visible === false) continue;
1070
+ const m = multiplyMatrices(nodeMatrix(n), parent);
1071
+ if (n.type === "group") {
1072
+ visit(n.children, m);
1073
+ continue;
1074
+ }
1075
+ if (anchors <= 0) continue;
1076
+ for (const sp of n.subpaths) {
1077
+ for (const a of sp.anchors) {
1078
+ if (anchors-- <= 0) break;
1079
+ const p = applyMatrix(m, a);
1080
+ if (gather === "drawing") self.pushAnchor(p);
1081
+ else self.points.push({ p, kind: "anchor" });
1082
+ }
1083
+ if (gather !== "bounds") segments = self.pushSegments(sp, m, () => false, segments);
1084
+ }
1085
+ const b = vectorNodeBounds(n, parent);
1086
+ const f = nodeFrame(n, parent, b);
1087
+ if (b) self.pushRect(b, "edge", _nullishCoalesce(_optionalChain([f, 'optionalAccess', _13 => _13.centre]), () => ( null)));
1088
+ }
1089
+ };
1090
+ visit(doc.nodes, IDENTITY_MATRIX);
1091
+ const objects = (list, parent) => {
1092
+ for (const n of list) {
1093
+ if (exclude.has(n.id) || n.visible === false || self.boxes.length >= BUDGET) continue;
1094
+ if (n.type === "group" && contains(n, exclude)) {
1095
+ objects(n.children, multiplyMatrices(nodeMatrix(n), parent));
1096
+ continue;
1097
+ }
1098
+ const b = vectorNodeBounds(n, parent);
1099
+ if (b) self.boxes.push(b);
1100
+ const f = nodeFrame(n, parent, b);
1101
+ if (f) self.sizes.push(f.width, f.height);
1102
+ }
1103
+ };
1104
+ objects(doc.nodes, IDENTITY_MATRIX);
1105
+ return sort ? self.index() : self;
1106
+ }
1107
+ // -------------------------------------------------------------------------
1108
+ // Points
1109
+ // -------------------------------------------------------------------------
1110
+ /** Snaps a single point (see `snapFrom`). */
1111
+ snapPoint(p, tol) {
1112
+ return this.snapFrom(p, null, tol);
1113
+ }
1114
+ /**
1115
+ * Snaps a point `p`, drawn or dragged from `leave` when given. With `leave.constrain` it keeps
1116
+ * to its angle round `leave.at`, sliding along that way into line with the nearest target.
1117
+ * Otherwise it lands, in order, on an anchor or centre, an intersection of two paths, the
1118
+ * perpendicular foot or a tangent point from `leave.at`, a path, a construction guide
1119
+ * through `leave.at` (where a target's line crosses it if one does nearby), or else lines up
1120
+ * with targets on x and y.
1121
+ */
1122
+ snapFrom(p, leave, tol) {
1123
+ const none = (q) => ({ point: q, kind: null, x: false, y: false, lines: [], labels: [] });
1124
+ const at = (q, kind, lines2 = []) => ({
1125
+ point: q,
1126
+ kind,
1127
+ x: true,
1128
+ y: true,
1129
+ lines: lines2,
1130
+ labels: [this.label(q, kind)]
1131
+ });
1132
+ if (_optionalChain([leave, 'optionalAccess', _14 => _14.constrain])) {
1133
+ const q = constrainToLeave(leave, p);
1134
+ const hit = this.slide(leave.at, { x: q.x - leave.at.x, y: q.y - leave.at.y }, q, tol, true);
1135
+ return hit ? { point: hit.point, x: true, y: true, ...this.linedUp(hit.point, hit.from) } : none(q);
1136
+ }
1137
+ const point = this.nearestPoint(p, tol);
1138
+ if (point) return at(point.p, point.kind);
1139
+ const near = this.segmentsNear(p, tol);
1140
+ const crossing = this.crossing(near, p, tol);
1141
+ if (crossing) return at(crossing, "intersect");
1142
+ if (leave && leave.curves !== false) {
1143
+ const foot = this.curvePoint(near, leave.at, p, tol);
1144
+ if (foot) return at(foot.point, foot.kind, [line("construction", leave.at, foot.point)]);
1145
+ }
1146
+ const on = this.onSegment(near);
1147
+ if (on) return at(on, "path");
1148
+ if (_optionalChain([leave, 'optionalAccess', _15 => _15.angles, 'optionalAccess', _16 => _16.length])) {
1149
+ const c = construction(leave, p, tol);
1150
+ if (c) {
1151
+ const hit = this.slide(leave.at, c.dir, c.point, tol, false);
1152
+ if (hit) {
1153
+ const lined = this.linedUp(hit.point, hit.from);
1154
+ return { point: hit.point, x: true, y: true, kind: lined.kind, lines: [line("construction", leave.at, hit.point), ...lined.lines], labels: lined.labels };
1155
+ }
1156
+ return { point: c.point, kind: "construction", x: true, y: true, lines: [line("construction", leave.at, c.point)], labels: [] };
1157
+ }
1158
+ }
1159
+ const nearest = (lines2, v) => {
1160
+ let best = null;
1161
+ for (const l of linesNear(lines2, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.v - v)) best = l;
1162
+ return best;
1163
+ };
1164
+ const bx = nearest(this.xs, p.x);
1165
+ const by = nearest(this.ys, p.y);
1166
+ if (!bx && !by) return none(p);
1167
+ const out = { x: bx ? bx.v : p.x, y: by ? by.v : p.y };
1168
+ const lines = [];
1169
+ if (bx) lines.push(line("align", bx.from, { x: out.x, y: out.y }));
1170
+ if (by) lines.push(line("align", by.from, { x: out.x, y: out.y }));
1171
+ return { point: out, kind: "align", x: !!bx, y: !!by, lines, labels: [this.label(out, "align")] };
1172
+ }
1173
+ label(p, kind, text = LABELS[kind]) {
1174
+ return { kind, x: p.x, y: p.y, text };
1175
+ }
1176
+ /** The anchor or centre nearest `p`, within `tol`. */
1177
+ nearestPoint(p, tol) {
1178
+ let best = null;
1179
+ let bestD = Infinity;
1180
+ for (const t of within(this.points, (e) => e.p.x, p.x, tol)) {
1181
+ const d = distance(t.p, p);
1182
+ if (d <= tol && d < bestD) {
1183
+ best = t;
1184
+ bestD = d;
1185
+ }
1186
+ }
1187
+ return best;
1188
+ }
1189
+ /**
1190
+ * Slides `q`, on the line through `at` along `dir` (beyond `at` only for a `ray`), to where
1191
+ * the nearest target line crosses it, at most twice `tol` away: where it lands and the
1192
+ * target point that line comes from.
1193
+ */
1194
+ slide(at, dir, q, tol, ray) {
1195
+ const len2 = dir.x * dir.x + dir.y * dir.y;
1196
+ if (!Number.isFinite(len2) || len2 <= 1e-18) return null;
1197
+ const reach = 2 * tol;
1198
+ let best = null;
1199
+ for (const vertical of [true, false]) {
1200
+ const d = vertical ? dir.x : dir.y;
1201
+ if (Math.abs(d) <= 1e-9) continue;
1202
+ const a = vertical ? at.x : at.y;
1203
+ for (const l of linesNear(vertical ? this.xs : this.ys, vertical ? q.x : q.y, reach)) {
1204
+ const s = (l.v - a) / d;
1205
+ const hit = vertical ? { x: l.v, y: at.y + dir.y * s } : { x: at.x + dir.x * s, y: l.v };
1206
+ const moved = distance(hit, q);
1207
+ if (moved <= reach && (!ray || s > 0) && (!best || moved < best.moved)) best = { moved, point: hit, from: l.from };
1208
+ }
1209
+ }
1210
+ return best && { point: best.point, from: best.from };
1211
+ }
1212
+ /** The guides of a point `q` slid into line with the target at `from`: the line and "align", or the target's own label when `q` sits on it. */
1213
+ linedUp(q, from) {
1214
+ const on = this.nearestPoint(q, 1e-9);
1215
+ if (on) return { kind: on.kind, lines: [], labels: [this.label(on.p, on.kind)] };
1216
+ return { kind: "align", lines: [line("align", from, q)], labels: [this.label(q, "align")] };
1217
+ }
1218
+ /** Target segments whose control box reaches `p` and which pass within `tol` of it, nearest first. */
1219
+ segmentsNear(p, tol) {
1220
+ const out = [];
1221
+ const pt = toPoint(p);
1222
+ for (const s of this.segments) {
1223
+ 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);
1225
+ if (hit.distance <= tol) out.push({ seg: s.seg, t: hit.t, distance: hit.distance });
1226
+ }
1227
+ return out.sort((a, b) => a.distance - b.distance);
1228
+ }
1229
+ onSegment(near) {
1230
+ return near.length ? toVec(_chunkUG5DVXJKcjs.segmentPoint.call(void 0, near[0].seg, near[0].t)) : null;
1231
+ }
1232
+ /** 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);
1235
+ let best = null;
1236
+ let bestD = Infinity;
1237
+ for (let i = 0; i < list.length; i++) {
1238
+ 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)) {
1240
+ const q = toVec(hit.point);
1241
+ const d = distance(q, p);
1242
+ if (d <= tol && d < bestD) {
1243
+ best = q;
1244
+ bestD = d;
1245
+ }
1246
+ }
1247
+ }
1248
+ }
1249
+ return best;
1250
+ }
1251
+ /**
1252
+ * The foot of a perpendicular, or a tangent point, from `base` on a target segment near `p`
1253
+ * (within `tol` of it): roots of `(B(t) − base) · B′(t)` and `(B(t) − base) × B′(t)`
1254
+ * bracketed by sign changes of samples and narrowed by bisection.
1255
+ */
1256
+ curvePoint(near, base, p, tol) {
1257
+ let best = null;
1258
+ for (const { seg } of near) {
1259
+ for (const kind of ["perpendicular", "tangent"]) {
1260
+ if (kind === "tangent" && seg.length === 2) continue;
1261
+ for (const q of solveOnSegment(seg, base, kind === "perpendicular" ? dotCondition : crossCondition)) {
1262
+ const d = distance(q, p);
1263
+ if (d <= tol && (!best || d < best.d)) best = { point: q, kind, d };
1264
+ }
1265
+ }
1266
+ }
1267
+ return best && { point: best.point, kind: best.kind };
1268
+ }
1269
+ // -------------------------------------------------------------------------
1270
+ // Boxes
1271
+ // -------------------------------------------------------------------------
1272
+ /**
1273
+ * Snaps a moving box `r` (the moved selection's bounds): its edge or centre nearest a target
1274
+ * line on each axis (`axes` limits which may move). With `spacing`, an axis that lines up
1275
+ * with nothing spaces the box evenly in its row or column. Returns the correction and guides.
1276
+ */
1277
+ snapRect(r, tol, options = {}) {
1278
+ const axes = { x: _optionalChain([options, 'access', _17 => _17.axes, 'optionalAccess', _18 => _18.x]) !== false, y: _optionalChain([options, 'access', _19 => _19.axes, 'optionalAccess', _20 => _20.y]) !== false };
1279
+ const nearest = (lines, values) => {
1280
+ let best = null;
1281
+ for (const v of values) for (const l of linesNear(lines, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.d)) best = { d: l.v - v, from: l.from };
1282
+ return best;
1283
+ };
1284
+ const bx = axes.x ? nearest(this.xs, [r.x, r.x + r.width / 2, r.x + r.width]) : null;
1285
+ const by = axes.y ? nearest(this.ys, [r.y, r.y + r.height / 2, r.y + r.height]) : null;
1286
+ const out = { dx: _nullishCoalesce(_optionalChain([bx, 'optionalAccess', _21 => _21.d]), () => ( 0)), dy: _nullishCoalesce(_optionalChain([by, 'optionalAccess', _22 => _22.d]), () => ( 0)), x: !!bx, y: !!by, lines: [], labels: [] };
1287
+ const done = { ...r, x: r.x + out.dx, y: r.y + out.dy };
1288
+ if (bx) {
1289
+ const x = bx.from.x;
1290
+ out.lines.push({ kind: "align", x1: x, y1: Math.min(bx.from.y, done.y), x2: x, y2: Math.max(bx.from.y, done.y + done.height) });
1291
+ }
1292
+ if (by) {
1293
+ const y = by.from.y;
1294
+ out.lines.push({ kind: "align", x1: Math.min(by.from.x, done.x), y1: y, x2: Math.max(by.from.x, done.x + done.width), y2: y });
1295
+ }
1296
+ if (options.spacing && this.boxes.length) {
1297
+ for (const horizontal of [true, false]) {
1298
+ if (horizontal ? out.x || !axes.x : out.y || !axes.y) continue;
1299
+ const moved = { ...r, x: r.x + out.dx, y: r.y + out.dy };
1300
+ const even = this.evenSpacing(moved, horizontal, tol);
1301
+ if (!even) continue;
1302
+ if (horizontal) {
1303
+ out.dx += even.shift;
1304
+ out.x = true;
1305
+ } else {
1306
+ out.dy += even.shift;
1307
+ out.y = true;
1308
+ }
1309
+ for (const gap of even.gaps) this.gapMarks(gap, horizontal, tol, out);
1310
+ }
1311
+ }
1312
+ if (bx || by) out.labels.unshift(this.label({ x: r.x + out.dx + r.width / 2, y: r.y + out.dy + r.height / 2 }, "align"));
1313
+ return out;
1314
+ }
1315
+ /**
1316
+ * Equal spacing along x (`horizontal`) or y for the moved box `r`, among the objects of its
1317
+ * row (column): the shift within `tol` that centres it between its neighbours, or leaves it
1318
+ * as far from the neighbour before or after it as two other objects of the row are apart;
1319
+ * and the gaps that are equal then, as (start, end, where across).
1320
+ */
1321
+ evenSpacing(r, horizontal, tol) {
1322
+ const span = (b, x) => x ? [b.x, b.x + b.width] : [b.y, b.y + b.height];
1323
+ const [r0, r1] = span(r, horizontal);
1324
+ const len = r1 - r0;
1325
+ const [c0, c1] = span(r, !horizontal);
1326
+ const row = this.boxes.map((b) => ({ b, a0: span(b, horizontal)[0], a1: span(b, horizontal)[1] })).filter((e) => span(e.b, !horizontal)[0] < c1 && span(e.b, !horizontal)[1] > c0 && (e.a1 <= r0 + tol || e.a0 >= r1 - tol)).sort((a, b) => a.a0 - b.a0);
1327
+ if (!row.length) return null;
1328
+ const mid = (a, b) => (Math.max(span(a, !horizontal)[0], span(b, !horizontal)[0]) + Math.min(span(a, !horizontal)[1], span(b, !horizontal)[1])) / 2;
1329
+ const pairs = [];
1330
+ for (const p of row) {
1331
+ const next = bound(row, (e) => e.a0, p.a1, false);
1332
+ const q = row[next];
1333
+ if (!q) continue;
1334
+ const straddles = p.a1 <= r0 + tol && q.a0 >= r1 - tol;
1335
+ if (q.a0 > p.a1 && !straddles) pairs.push({ g: q.a0 - p.a1, gap: [p.a1, q.a0, mid(p.b, q.b)] });
1336
+ }
1337
+ let before = null;
1338
+ let after = null;
1339
+ for (const e of row) {
1340
+ if (e.a1 <= r0 + tol && (!before || e.a1 > before.a1)) before = e;
1341
+ if (e.a0 >= r1 - tol && (!after || e.a0 < after.a0)) after = e;
1342
+ }
1343
+ const starts = [];
1344
+ if (before && after && after.a0 - before.a1 > len) starts.push({ start: (before.a1 + after.a0 - len) / 2, repeats: null });
1345
+ for (const { g } of pairs) {
1346
+ if (before) starts.push({ start: before.a1 + g, repeats: g });
1347
+ if (after) starts.push({ start: after.a0 - g - len, repeats: g });
1348
+ }
1349
+ let pick = null;
1350
+ for (const s of starts) if (Math.abs(s.start - r0) <= tol && (!pick || Math.abs(s.start - r0) < Math.abs(pick.start - r0))) pick = s;
1351
+ if (!pick) return null;
1352
+ const { start, repeats } = pick;
1353
+ const at = horizontal ? { ...r, x: start } : { ...r, y: start };
1354
+ const end = start + len;
1355
+ const gaps = [];
1356
+ if (before && (repeats === null || Math.abs(start - before.a1 - repeats) <= EQUAL)) gaps.push([before.a1, start, mid(before.b, at)]);
1357
+ if (after && (repeats === null || Math.abs(after.a0 - end - repeats) <= EQUAL)) gaps.push([end, after.a0, mid(after.b, at)]);
1358
+ if (repeats !== null) {
1359
+ for (const p of pairs) if (Math.abs(p.g - repeats) <= EQUAL) gaps.push(p.gap);
1360
+ }
1361
+ return { shift: start - r0, gaps };
1362
+ }
1363
+ /** A gap mark: a line across the gap with a tick at each end, and its length as a label. */
1364
+ gapMarks([u0, u1, c], horizontal, tick, out) {
1365
+ const pt = (u, v) => horizontal ? { x: u, y: v } : { x: v, y: u };
1366
+ out.lines.push(line("spacing", pt(u0, c), pt(u1, c)), line("spacing", pt(u0, c - tick), pt(u0, c + tick)), line("spacing", pt(u1, c - tick), pt(u1, c + tick)));
1367
+ out.labels.push(this.label(pt((u0 + u1) / 2, c), "spacing", fmtLength(u1 - u0)));
1368
+ }
1369
+ /** The other objects' side length nearest `length`, within `tol`, or null. */
1370
+ matchSize(length2, tol) {
1371
+ let best = null;
1372
+ for (const s of this.sizes) if (Math.abs(s - length2) <= tol && (best === null || Math.abs(s - length2) < Math.abs(best - length2))) best = s;
1373
+ return best;
1374
+ }
1375
+ /**
1376
+ * Snaps a selection-box resize. `sx / sy` are the scale `scaleFor` gave for dragging
1377
+ * `handle` of `frame` (in the box's own, possibly rotated frame), `mods.shift` keeps the
1378
+ * proportions and `mods.alt` scales about the centre. The handle lands on an anchor or
1379
+ * centre within `tol`, else the corner or the side it moves lines up with the nearest
1380
+ * target, whatever the box's angle; a handle with one way to go (a side, or a proportional
1381
+ * corner) slides along it until one of them is on a target. An axis that snaps to nothing
1382
+ * may match another object's width or height instead.
1383
+ */
1384
+ snapScale(frame, handle, sx, sy, mods, tol) {
1385
+ const toDoc = frame.angle ? rotateMatrix(frame.angle) : IDENTITY_MATRIX;
1386
+ const toLocal = frame.angle ? rotateMatrix(-frame.angle) : IDENTITY_MATRIX;
1387
+ const corner = handle.length === 2;
1388
+ const proportional = !!mods.shift;
1389
+ const centre = { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 };
1390
+ const origin = mods.alt ? centre : handlePos(opposite(handle), frame);
1391
+ const hp = handlePos(handle, frame);
1392
+ const d0 = { x: hp.x - origin.x, y: hp.y - origin.y };
1393
+ const valid = (n, s) => Number.isFinite(n) && n * s > 0;
1394
+ const nearest = (v, lines) => {
1395
+ let best = null;
1396
+ for (const l of linesNear(lines, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.v - v)) best = l;
1397
+ return best;
1398
+ };
1399
+ let nx = sx;
1400
+ let ny = sy;
1401
+ let snap = { hx: null, hy: null, on: null };
1402
+ if (corner && !proportional) {
1403
+ const p = applyMatrix(toDoc, { x: origin.x + d0.x * sx, y: origin.y + d0.y * sy });
1404
+ const hx = nearest(p.x, this.xs);
1405
+ const hy = nearest(p.y, this.ys);
1406
+ const on = this.nearestPoint(p, tol);
1407
+ const tries = [];
1408
+ if (on) tries.push({ tx: on.p.x, ty: on.p.y, s: { hx: null, hy: null, on } });
1409
+ tries.push({ tx: _nullishCoalesce(_optionalChain([hx, 'optionalAccess', _23 => _23.v]), () => ( null)), ty: _nullishCoalesce(_optionalChain([hy, 'optionalAccess', _24 => _24.v]), () => ( null)), s: { hx, hy, on: null } });
1410
+ tries.push({ tx: _nullishCoalesce(_optionalChain([hx, 'optionalAccess', _25 => _25.v]), () => ( null)), ty: null, s: { hx, hy: null, on: null } });
1411
+ tries.push({ tx: null, ty: _nullishCoalesce(_optionalChain([hy, 'optionalAccess', _26 => _26.v]), () => ( null)), s: { hx: null, hy, on: null } });
1412
+ for (const { tx, ty, s } of tries) {
1413
+ if (tx === null && ty === null) continue;
1414
+ const q = applyMatrix(toLocal, { x: _nullishCoalesce(tx, () => ( p.x)), y: _nullishCoalesce(ty, () => ( p.y)) });
1415
+ let cx = (q.x - origin.x) / d0.x;
1416
+ let cy = (q.y - origin.y) / d0.y;
1417
+ if (!frame.angle) {
1418
+ if (tx === null) cx = sx;
1419
+ if (ty === null) cy = sy;
1420
+ }
1421
+ if (valid(cx, sx) && valid(cy, sy)) {
1422
+ nx = cx;
1423
+ ny = cy;
1424
+ snap = s;
1425
+ break;
1426
+ }
1427
+ }
1428
+ } else {
1429
+ const movesX2 = handle === "e" || handle === "w";
1430
+ let e;
1431
+ let c;
1432
+ let s0;
1433
+ if (corner) [e, c, s0] = [{ x: Math.sign(sx), y: Math.sign(sy) }, { x: 0, y: 0 }, Math.abs(sx)];
1434
+ else if (movesX2) [e, c, s0] = proportional ? [{ x: 1, y: Math.sign(sx) }, { x: 0, y: 0 }, sx] : [{ x: 1, y: 0 }, { x: 0, y: 1 }, sx];
1435
+ else [e, c, s0] = proportional ? [{ x: Math.sign(sy), y: 1 }, { x: 0, y: 0 }, sy] : [{ x: 0, y: 1 }, { x: 1, y: 0 }, sy];
1436
+ const i = RING.indexOf(handle);
1437
+ const ends = corner ? [] : [RING[(i + 1) % 8], RING[(i + 7) % 8]];
1438
+ const travel = Math.hypot(e.x * d0.x, e.y * d0.y);
1439
+ let best = null;
1440
+ const consider = (s, inLine, found2) => {
1441
+ const moved = Math.abs(s - s0) * travel;
1442
+ if (!valid(s, s0) || moved > 2 * tol) return;
1443
+ if (!best || (inLine === best.inLine ? moved < best.moved : !inLine)) best = { s, inLine, moved, snap: found2 };
1444
+ };
1445
+ for (const h of [handle, ...ends]) {
1446
+ const hv = handlePos(h, frame);
1447
+ const v = { x: hv.x - origin.x, y: hv.y - origin.y };
1448
+ const base = applyMatrix(toDoc, { x: origin.x + c.x * v.x, y: origin.y + c.y * v.y });
1449
+ const dir = applyMatrix(toDoc, { x: e.x * v.x, y: e.y * v.y });
1450
+ const p = { x: base.x + dir.x * s0, y: base.y + dir.y * s0 };
1451
+ const l2 = dir.x * dir.x + dir.y * dir.y;
1452
+ const on = l2 > 1e-18 ? this.nearestPoint(p, tol) : null;
1453
+ if (on) consider(((on.p.x - base.x) * dir.x + (on.p.y - base.y) * dir.y) / l2, false, { hx: null, hy: null, on });
1454
+ if (Math.abs(dir.x) > 1e-9) {
1455
+ const hit = nearest(p.x, this.xs);
1456
+ if (hit) consider((hit.v - base.x) / dir.x, true, { hx: hit, hy: null, on: null });
1457
+ }
1458
+ if (Math.abs(dir.y) > 1e-9) {
1459
+ const hit = nearest(p.y, this.ys);
1460
+ if (hit) consider((hit.v - base.y) / dir.y, true, { hx: null, hy: hit, on: null });
1461
+ }
1462
+ }
1463
+ const found = best;
1464
+ if (found) {
1465
+ nx = c.x + found.s * e.x;
1466
+ ny = c.y + found.s * e.y;
1467
+ snap = found.snap;
1468
+ }
1469
+ }
1470
+ const out = { sx: nx, sy: ny, snapped: false, lines: [], labels: [] };
1471
+ const handleAt = () => applyMatrix(toDoc, { x: origin.x + d0.x * out.sx, y: origin.y + d0.y * out.sy });
1472
+ if (snap.on) {
1473
+ out.snapped = true;
1474
+ out.labels.push(this.label(snap.on.p, snap.on.kind));
1475
+ return out;
1476
+ }
1477
+ const movesX = handle !== "n" && handle !== "s";
1478
+ const movesY = handle !== "e" && handle !== "w";
1479
+ const sized = [];
1480
+ if (!proportional) {
1481
+ if (movesX && !snap.hx && !(frame.angle && snap.hy) && frame.width > 1e-9) {
1482
+ const len = this.matchSize(Math.abs(out.sx) * frame.width, tol);
1483
+ if (len !== null && len > 0) {
1484
+ out.sx = Math.sign(out.sx || 1) * (len / frame.width);
1485
+ sized.push("x");
1486
+ }
1487
+ }
1488
+ if (movesY && !snap.hy && !(frame.angle && snap.hx) && frame.height > 1e-9) {
1489
+ const len = this.matchSize(Math.abs(out.sy) * frame.height, tol);
1490
+ if (len !== null && len > 0) {
1491
+ out.sy = Math.sign(out.sy || 1) * (len / frame.height);
1492
+ sized.push("y");
1493
+ }
1494
+ }
1495
+ }
1496
+ const x0 = origin.x + (frame.x - origin.x) * out.sx;
1497
+ const x1 = origin.x + (frame.x + frame.width - origin.x) * out.sx;
1498
+ const y0 = origin.y + (frame.y - origin.y) * out.sy;
1499
+ const y1 = origin.y + (frame.y + frame.height - origin.y) * out.sy;
1500
+ const box = { x0: Math.min(x0, x1), x1: Math.max(x0, x1), y0: Math.min(y0, y1), y1: Math.max(y0, y1) };
1501
+ const page = [
1502
+ { x: box.x0, y: box.y0 },
1503
+ { x: box.x1, y: box.y0 },
1504
+ { x: box.x1, y: box.y1 },
1505
+ { x: box.x0, y: box.y1 }
1506
+ ].map((q) => applyMatrix(toDoc, q));
1507
+ const pminY = Math.min(...page.map((q) => q.y));
1508
+ const pmaxY = Math.max(...page.map((q) => q.y));
1509
+ const pminX = Math.min(...page.map((q) => q.x));
1510
+ const pmaxX = Math.max(...page.map((q) => q.x));
1511
+ if (snap.hx) out.lines.push({ kind: "align", x1: snap.hx.v, y1: Math.min(snap.hx.from.y, pminY), x2: snap.hx.v, y2: Math.max(snap.hx.from.y, pmaxY) });
1512
+ if (snap.hy) out.lines.push({ kind: "align", x1: Math.min(snap.hy.from.x, pminX), y1: snap.hy.v, x2: Math.max(snap.hy.from.x, pmaxX), y2: snap.hy.v });
1513
+ if (snap.hx || snap.hy) {
1514
+ out.snapped = true;
1515
+ out.labels.push(this.label(handleAt(), "align"));
1516
+ }
1517
+ for (const axis of sized) {
1518
+ out.snapped = true;
1519
+ const off = tol * 2;
1520
+ const [a, b] = axis === "x" ? [{ x: box.x0, y: box.y1 + off }, { x: box.x1, y: box.y1 + off }] : [{ x: box.x1 + off, y: box.y0 }, { x: box.x1 + off, y: box.y1 }];
1521
+ const pa = applyMatrix(toDoc, a);
1522
+ const pb = applyMatrix(toDoc, b);
1523
+ out.lines.push(line("size", pa, pb));
1524
+ out.labels.push(this.label({ x: (pa.x + pb.x) / 2, y: (pa.y + pb.y) / 2 }, "size", `${axis === "x" ? "W" : "H"} ${fmtLength(axis === "x" ? box.x1 - box.x0 : box.y1 - box.y0)}`));
1525
+ }
1526
+ return out;
1527
+ }
1528
+ }, _class);
1529
+ var RING = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
1530
+ function opposite(h) {
1531
+ return RING[(RING.indexOf(h) + 4) % 8];
1532
+ }
1533
+ function handlePos(h, r) {
1534
+ const x = h.includes("w") ? r.x : h.includes("e") ? r.x + r.width : r.x + r.width / 2;
1535
+ const y = h.startsWith("n") ? r.y : h.startsWith("s") ? r.y + r.height : r.y + r.height / 2;
1536
+ return { x, y };
1537
+ }
1538
+ function line(kind, a, b) {
1539
+ return { kind, x1: a.x, y1: a.y, x2: b.x, y2: b.y };
1540
+ }
1541
+ function construction(leave, p, tol) {
1542
+ const v = { x: p.x - leave.at.x, y: p.y - leave.at.y };
1543
+ if (Math.hypot(v.x, v.y) <= tol) return null;
1544
+ let best = null;
1545
+ for (const a of _nullishCoalesce(leave.angles, () => ( []))) {
1546
+ const r = a * Math.PI / 180;
1547
+ const exact = (n) => Math.abs(n) < 1e-12 ? 0 : n;
1548
+ const dir = { x: exact(Math.cos(r)), y: exact(-Math.sin(r)) };
1549
+ const k = v.x * dir.x + v.y * dir.y;
1550
+ const point = { x: leave.at.x + dir.x * k, y: leave.at.y + dir.y * k };
1551
+ const d = distance(point, p);
1552
+ if (d <= tol && (!best || d < best.d)) best = { dir, point, d };
1553
+ }
1554
+ return best && { dir: best.dir, point: best.point };
1555
+ }
1556
+ var dotCondition = (r, d) => r[0] * d[0] + r[1] * d[1];
1557
+ var crossCondition = (r, d) => r[0] * d[1] - r[1] * d[0];
1558
+ function solveOnSegment(seg, base, cond) {
1559
+ const b = [base.x, base.y];
1560
+ 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));
1563
+ };
1564
+ const vals = [];
1565
+ let scale = 0;
1566
+ for (let i = 0; i <= CURVE_SAMPLES; i++) {
1567
+ 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);
1570
+ const r = [p[0] - b[0], p[1] - b[1]];
1571
+ vals.push(cond(r, d));
1572
+ scale = Math.max(scale, Math.hypot(r[0], r[1]) * Math.hypot(d[0], d[1]));
1573
+ }
1574
+ if (!(scale > 0) || vals.every((v) => Math.abs(v) <= scale * 1e-12)) return [];
1575
+ const out = [];
1576
+ for (let i = 0; i < CURVE_SAMPLES; i++) {
1577
+ const ga = vals[i];
1578
+ const gb = vals[i + 1];
1579
+ if (!(Number.isFinite(ga) && Number.isFinite(gb)) || ga > 0 === gb > 0 && ga !== 0) continue;
1580
+ let lo = i / CURVE_SAMPLES;
1581
+ let hi = (i + 1) / CURVE_SAMPLES;
1582
+ let flo = ga;
1583
+ for (let k = 0; k < BISECT; k++) {
1584
+ const m = (lo + hi) / 2;
1585
+ const fm = g(m);
1586
+ if (fm === 0) {
1587
+ lo = hi = m;
1588
+ break;
1589
+ }
1590
+ if (fm > 0 === flo > 0) {
1591
+ lo = m;
1592
+ flo = fm;
1593
+ } else hi = m;
1594
+ }
1595
+ 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);
1598
+ const r = Math.hypot(p[0] - b[0], p[1] - b[1]);
1599
+ const dl = Math.hypot(dv[0], dv[1]);
1600
+ if (!(r > 1e-9 && dl > 0) || Math.abs(cond([p[0] - b[0], p[1] - b[1]], dv)) > 1e-6 * r * dl) continue;
1601
+ if (!out.some((q) => Math.hypot(q.x - p[0], q.y - p[1]) <= 1e-9 * (1 + r))) out.push({ x: p[0], y: p[1] });
1602
+ }
1603
+ return out;
1604
+ }
1605
+
853
1606
  // src/vector/controller.ts
1607
+ var DEFAULT_VECTOR_SNAP = { grid: 0, objects: true, guides: true, threshold: 5, angles: VECTOR_CONSTRUCTION_ANGLES };
854
1608
  var VECTOR_CLIPBOARD_TYPE = "pixerate/vector";
855
1609
  var DEFAULT_DEFAULTS = {
856
1610
  fill: "#d4d4d8",
@@ -878,7 +1632,7 @@ function getEditablePathIds(state) {
878
1632
  }
879
1633
  }
880
1634
  }
881
- return [...ids].filter((id) => _optionalChain([findVectorNode, 'call', _8 => _8(state.document.nodes, id), 'optionalAccess', _9 => _9.type]) === "path");
1635
+ return [...ids].filter((id) => _optionalChain([findVectorNode, 'call', _27 => _27(state.document.nodes, id), 'optionalAccess', _28 => _28.type]) === "path");
882
1636
  }
883
1637
  function getVisibleAnchors(state) {
884
1638
  const out = [];
@@ -893,7 +1647,7 @@ function getVisibleHandles(state) {
893
1647
  const seen = /* @__PURE__ */ new Set();
894
1648
  const push = (node, s, i, handle) => {
895
1649
  const sp = node.subpaths[s];
896
- const a = _optionalChain([sp, 'optionalAccess', _10 => _10.anchors, 'access', _11 => _11[i]]);
1650
+ const a = _optionalChain([sp, 'optionalAccess', _29 => _29.anchors, 'access', _30 => _30[i]]);
897
1651
  if (!a || !a[handle]) return;
898
1652
  const key = `${node.id}:${s}:${i}:${handle}`;
899
1653
  if (seen.has(key)) return;
@@ -902,8 +1656,8 @@ function getVisibleHandles(state) {
902
1656
  };
903
1657
  if (state.tool === "pen" && state.pen) {
904
1658
  const node = findVectorNode(state.document.nodes, state.pen.nodeId);
905
- const sp = _optionalChain([node, 'optionalAccess', _12 => _12.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
906
- if (_optionalChain([node, 'optionalAccess', _13 => _13.type]) === "path" && sp) {
1659
+ const sp = _optionalChain([node, 'optionalAccess', _31 => _31.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
1660
+ if (_optionalChain([node, 'optionalAccess', _32 => _32.type]) === "path" && sp) {
907
1661
  const last = sp.anchors.length - 1;
908
1662
  push(node, state.pen.subpath, last, "in");
909
1663
  push(node, state.pen.subpath, last, "out");
@@ -929,7 +1683,7 @@ function getVisibleHandles(state) {
929
1683
  }
930
1684
  function getAnchor(doc, ref) {
931
1685
  const node = findVectorNode(doc.nodes, ref.nodeId);
932
- return _optionalChain([node, 'optionalAccess', _14 => _14.type]) === "path" ? _optionalChain([node, 'access', _15 => _15.subpaths, 'access', _16 => _16[ref.subpath], 'optionalAccess', _17 => _17.anchors, 'access', _18 => _18[ref.index]]) : void 0;
1686
+ return _optionalChain([node, 'optionalAccess', _33 => _33.type]) === "path" ? _optionalChain([node, 'access', _34 => _34.subpaths, 'access', _35 => _35[ref.subpath], 'optionalAccess', _36 => _36.anchors, 'access', _37 => _37[ref.index]]) : void 0;
933
1687
  }
934
1688
  function getAnchorWorld(doc, ref) {
935
1689
  const a = getAnchor(doc, ref);
@@ -1046,40 +1800,45 @@ function mapTree(nodes, fn) {
1046
1800
  var isRoot = (m) => m === IDENTITY_MATRIX || m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
1047
1801
  var idCounter = 0;
1048
1802
  var defaultCreateId = () => `vn-${Date.now().toString(36)}${(++idCounter).toString(36)}`;
1049
- var VectorEditorController = (_class = class {
1803
+ var VectorEditorController = (_class2 = class {
1050
1804
 
1051
1805
 
1052
- __init() {this.selection = []}
1053
- __init2() {this.anchors = []}
1054
- __init3() {this.editingId = null}
1055
- __init4() {this.pen = null}
1806
+ __init7() {this.selection = []}
1807
+ __init8() {this.anchors = []}
1808
+ __init9() {this.editingId = null}
1809
+ __init10() {this.pen = null}
1056
1810
 
1057
1811
 
1058
1812
 
1059
1813
 
1060
1814
 
1061
- __init5() {this.hover = null}
1062
- __init6() {this.pointer = null}
1063
- __init7() {this.drag = null}
1064
- __init8() {this.interaction = null}
1065
- __init9() {this.spaceHeld = false}
1066
- __init10() {this.clipboard = null}
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}
1820
+ /** Whether the last `snapDrawn` landed on an object (guides may be hidden). */
1821
+ __init16() {this.snappedToObject = false}
1822
+ /** 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}
1067
1826
 
1068
1827
 
1069
- __init11() {this.listeners = /* @__PURE__ */ new Set()}
1070
- __init12() {this.cleanups = []}
1071
- __init13() {this.applying = false}
1828
+ __init20() {this.listeners = /* @__PURE__ */ new Set()}
1829
+ __init21() {this.cleanups = []}
1830
+ __init22() {this.applying = false}
1072
1831
  /** Set while a commit records a change that was already applied, so `restore` still reports it. */
1073
- __init14() {this.committing = false}
1074
- __init15() {this.revision = 0}
1832
+ __init23() {this.committing = false}
1833
+ __init24() {this.revision = 0}
1075
1834
 
1076
- constructor(options = {}) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);_class.prototype.__init15.call(this);_class.prototype.__init16.call(this);_class.prototype.__init17.call(this);
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);
1077
1836
  this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkQKLRAA4Tcjs.HistoryManager)()));
1078
1837
  this.doc = options.document ? cloneVectorDocument(options.document) : { width: 800, height: 600, nodes: [] };
1079
1838
  this.tool = _nullishCoalesce(options.tool, () => ( "select"));
1080
1839
  this.defaults = { ...DEFAULT_DEFAULTS, ..._nullishCoalesce(options.defaults, () => ( {})) };
1081
1840
  this.viewport = { x: 0, y: 0, zoom: 1, ..._nullishCoalesce(options.viewport, () => ( {})) };
1082
- this.snap = { grid: 0, ..._nullishCoalesce(options.snap, () => ( {})) };
1841
+ this.snap = { ...DEFAULT_VECTOR_SNAP, ..._nullishCoalesce(options.snap, () => ( {})) };
1083
1842
  this.hitRadius = _nullishCoalesce(options.hitRadius, () => ( 6));
1084
1843
  this.createId = _nullishCoalesce(options.createId, () => ( defaultCreateId));
1085
1844
  this.onChange = options.onChange;
@@ -1101,9 +1860,9 @@ var VectorEditorController = (_class = class {
1101
1860
  // State
1102
1861
  // -------------------------------------------------------------------------
1103
1862
  /** The current snapshot (stable until the next change). */
1104
- __init16() {this.getState = () => this.state}
1863
+ __init25() {this.getState = () => this.state}
1105
1864
  /** Calls `listener` after every change. Returns an unsubscribe function. */
1106
- __init17() {this.subscribe = (listener) => {
1865
+ __init26() {this.subscribe = (listener) => {
1107
1866
  this.listeners.add(listener);
1108
1867
  return () => {
1109
1868
  this.listeners.delete(listener);
@@ -1128,6 +1887,7 @@ var VectorEditorController = (_class = class {
1128
1887
  viewport: this.viewport,
1129
1888
  defaults: this.defaults,
1130
1889
  snap: this.snap,
1890
+ guides: this.guides,
1131
1891
  hitRadius: this.hitRadius,
1132
1892
  hover: this.hover,
1133
1893
  cursor: this.cursorFor(),
@@ -1144,7 +1904,7 @@ var VectorEditorController = (_class = class {
1144
1904
  notify(docChanged = false) {
1145
1905
  this.revision++;
1146
1906
  this.state = this.computeState();
1147
- if (docChanged) _optionalChain([this, 'access', _19 => _19.onChange, 'optionalCall', _20 => _20(this.doc)]);
1907
+ if (docChanged) _optionalChain([this, 'access', _38 => _38.onChange, 'optionalCall', _39 => _39(this.doc)]);
1148
1908
  const state = this.state;
1149
1909
  this.listeners.forEach((listener) => {
1150
1910
  try {
@@ -1174,20 +1934,20 @@ var VectorEditorController = (_class = class {
1174
1934
  return applyMatrix(invertMatrix(this.worldOf(id, doc)), p);
1175
1935
  }
1176
1936
  cursorFor() {
1177
- if (_optionalChain([this, 'access', _21 => _21.drag, 'optionalAccess', _22 => _22.kind]) === "pan" || this.spaceHeld) return this.drag ? "grabbing" : "grab";
1178
- if (_optionalChain([this, 'access', _23 => _23.drag, 'optionalAccess', _24 => _24.kind]) === "rotate") return "grabbing";
1179
- if (_optionalChain([this, 'access', _25 => _25.drag, 'optionalAccess', _26 => _26.kind]) === "move") return "move";
1937
+ if (_optionalChain([this, 'access', _40 => _40.drag, 'optionalAccess', _41 => _41.kind]) === "pan" || this.spaceHeld) return this.drag ? "grabbing" : "grab";
1938
+ if (_optionalChain([this, 'access', _42 => _42.drag, 'optionalAccess', _43 => _43.kind]) === "rotate") return "grabbing";
1939
+ if (_optionalChain([this, 'access', _44 => _44.drag, 'optionalAccess', _45 => _45.kind]) === "move") return "move";
1180
1940
  const h = this.hover;
1181
1941
  switch (this.tool) {
1182
1942
  case "select":
1183
- if (_optionalChain([h, 'optionalAccess', _27 => _27.transformHandle])) return vectorResizeCursor(h.transformHandle, _nullishCoalesce(_optionalChain([this, 'access', _28 => _28.frameOf, 'call', _29 => _29(this.selectedRoots()), 'optionalAccess', _30 => _30.angle]), () => ( 0)));
1184
- return _optionalChain([h, 'optionalAccess', _31 => _31.nodeId]) ? "move" : "default";
1943
+ if (_optionalChain([h, 'optionalAccess', _46 => _46.transformHandle])) return vectorResizeCursor(h.transformHandle, _nullishCoalesce(_optionalChain([this, 'access', _47 => _47.frameOf, 'call', _48 => _48(this.selectedRoots()), 'optionalAccess', _49 => _49.angle]), () => ( 0)));
1944
+ return _optionalChain([h, 'optionalAccess', _50 => _50.nodeId]) ? "move" : "default";
1185
1945
  case "direct":
1186
- if (_optionalChain([h, 'optionalAccess', _32 => _32.anchor]) || _optionalChain([h, 'optionalAccess', _33 => _33.handle])) return "pointer";
1187
- if (_optionalChain([h, 'optionalAccess', _34 => _34.segment])) return "copy";
1188
- return _optionalChain([h, 'optionalAccess', _35 => _35.nodeId]) ? "move" : "default";
1946
+ if (_optionalChain([h, 'optionalAccess', _51 => _51.anchor]) || _optionalChain([h, 'optionalAccess', _52 => _52.handle])) return "pointer";
1947
+ if (_optionalChain([h, 'optionalAccess', _53 => _53.segment])) return "copy";
1948
+ return _optionalChain([h, 'optionalAccess', _54 => _54.nodeId]) ? "move" : "default";
1189
1949
  case "pen":
1190
- return _optionalChain([h, 'optionalAccess', _36 => _36.anchor]) ? "pointer" : "crosshair";
1950
+ return _optionalChain([h, 'optionalAccess', _55 => _55.anchor]) ? "pointer" : "crosshair";
1191
1951
  default:
1192
1952
  return "crosshair";
1193
1953
  }
@@ -1207,7 +1967,7 @@ var VectorEditorController = (_class = class {
1207
1967
  this.selection = s.selection.filter((id) => this.getNode(id));
1208
1968
  this.anchors = s.anchors.filter((r) => getAnchor(this.doc, r));
1209
1969
  this.editingId = s.editingId && this.getNode(s.editingId) ? s.editingId : null;
1210
- this.pen = s.pen && _optionalChain([this, 'access', _37 => _37.getNode, 'call', _38 => _38(s.pen.nodeId), 'optionalAccess', _39 => _39.subpaths, 'optionalAccess', _40 => _40[s.pen.subpath]]) ? s.pen : null;
1970
+ this.pen = s.pen && _optionalChain([this, 'access', _56 => _56.getNode, 'call', _57 => _57(s.pen.nodeId), 'optionalAccess', _58 => _58.subpaths, 'optionalAccess', _59 => _59[s.pen.subpath]]) ? s.pen : null;
1211
1971
  } finally {
1212
1972
  this.applying = false;
1213
1973
  }
@@ -1312,6 +2072,7 @@ var VectorEditorController = (_class = class {
1312
2072
  this.cancelDrag();
1313
2073
  this.tool = tool;
1314
2074
  this.hover = null;
2075
+ this.guides = null;
1315
2076
  if (tool !== "direct") {
1316
2077
  this.anchors = [];
1317
2078
  this.editingId = null;
@@ -1324,18 +2085,74 @@ var VectorEditorController = (_class = class {
1324
2085
  }
1325
2086
  setSnap(snap) {
1326
2087
  this.snap = { ...this.snap, ...snap };
2088
+ if (!this.snap.guides || !this.snap.objects) this.guides = null;
1327
2089
  this.notify();
1328
2090
  }
1329
- snapPoint(p) {
1330
- const g = this.snap.grid;
1331
- if (!g || g <= 0) return p;
1332
- return { x: Math.round(p.x / g) * g, y: Math.round(p.y / g) * g };
2091
+ /** Turns object snapping on or off (the toolbar's snapping toggle). */
2092
+ toggleSnapping(on = !this.snap.objects) {
2093
+ this.setSnap({ objects: on });
1333
2094
  }
1334
- snapDelta(dx, dy, origin) {
2095
+ gridValue(v) {
1335
2096
  const g = this.snap.grid;
1336
- if (!g || g <= 0 || !origin) return { dx, dy };
1337
- const target = this.snapPoint({ x: origin.x + dx, y: origin.y + dy });
1338
- return { dx: target.x - origin.x, dy: target.y - origin.y };
2097
+ return g > 0 ? Math.round(v / g) * g : v;
2098
+ }
2099
+ snapPoint(p) {
2100
+ return { x: this.gridValue(p.x), y: this.gridValue(p.y) };
2101
+ }
2102
+ /** The pull distance in document units. */
2103
+ snapTolerance() {
2104
+ return this.snap.threshold / this.viewport.zoom;
2105
+ }
2106
+ setGuides(guides) {
2107
+ this.guides = guides && this.snap.guides && (guides.lines.length || guides.labels.length) ? guides : null;
2108
+ }
2109
+ /**
2110
+ * Where a drawn or dragged point `p` goes: onto the smart guide `targets` (gathered by
2111
+ * `gather` when missing), else onto the grid, kept to `leave`'s constraint. Cmd / Ctrl
2112
+ * bypasses snapping.
2113
+ */
2114
+ snapDrawn(p, leave, input, targets) {
2115
+ this.snappedToObject = false;
2116
+ if (input.mod) {
2117
+ this.setGuides(null);
2118
+ return constrainToLeave(leave, p);
2119
+ }
2120
+ if (this.snap.objects) {
2121
+ const withAngles = leave ? { ...leave, angles: this.snap.angles } : null;
2122
+ const res = targets().snapFrom(p, withAngles, this.snapTolerance());
2123
+ if (res.kind) {
2124
+ this.snappedToObject = true;
2125
+ this.setGuides(res);
2126
+ return _optionalChain([leave, 'optionalAccess', _60 => _60.constrain]) ? res.point : { x: res.x ? res.point.x : this.gridValue(res.point.x), y: res.y ? res.point.y : this.gridValue(res.point.y) };
2127
+ }
2128
+ }
2129
+ this.setGuides(null);
2130
+ return constrainToLeave(leave, this.snapPoint(p));
2131
+ }
2132
+ /** Drawing targets for the pen and shape tools, reused while the document stays the same. */
2133
+ drawingTargets(exclude) {
2134
+ const key = _nullishCoalesce(exclude, () => ( ""));
2135
+ const cached = this.hoverTargets;
2136
+ if (cached && cached.doc === this.doc && cached.exclude === key) return cached.targets;
2137
+ const targets = VectorSnapTargets.forDrawing(this.doc, exclude ? [exclude] : []);
2138
+ this.hoverTargets = { doc: this.doc, exclude: key, targets };
2139
+ return targets;
2140
+ }
2141
+ /** Where the pointer at `p` would place a point with the pen or a shape tool (and the guides showing why). */
2142
+ hoverSnap(p, input) {
2143
+ if (this.tool === "pen") {
2144
+ const sp = this.pen ? _optionalChain([this, 'access', _61 => _61.getNode, 'call', _62 => _62(this.pen.nodeId), 'optionalAccess', _63 => _63.subpaths, 'access', _64 => _64[this.pen.subpath]]) : void 0;
2145
+ if (this.pen && sp && _optionalChain([this, 'access', _65 => _65.hover, 'optionalAccess', _66 => _66.anchor])) {
2146
+ this.setGuides(null);
2147
+ return p;
2148
+ }
2149
+ const last = this.pen && sp ? getAnchorWorld(this.doc, { nodeId: this.pen.nodeId, subpath: this.pen.subpath, index: sp.anchors.length - 1 }) : void 0;
2150
+ const leave = last ? { at: { x: last.x, y: last.y }, constrain: input.shift ? "angle" : null } : null;
2151
+ return this.snapDrawn(p, leave, input, () => this.drawingTargets(_nullishCoalesce(_optionalChain([this, 'access', _67 => _67.pen, 'optionalAccess', _68 => _68.nodeId]), () => ( null))));
2152
+ }
2153
+ if (this.tool !== "select" && this.tool !== "direct" && this.tool !== "pencil") return this.snapDrawn(p, null, input, () => this.drawingTargets(null));
2154
+ this.setGuides(null);
2155
+ return p;
1339
2156
  }
1340
2157
  // -------------------------------------------------------------------------
1341
2158
  // Document
@@ -1566,10 +2383,10 @@ var VectorEditorController = (_class = class {
1566
2383
  return this.selection.filter((id) => {
1567
2384
  let covered = false;
1568
2385
  const loc = locateVectorNode(this.doc.nodes, id);
1569
- let parent = _nullishCoalesce(_optionalChain([loc, 'optionalAccess', _41 => _41.parent]), () => ( null));
2386
+ let parent = _nullishCoalesce(_optionalChain([loc, 'optionalAccess', _69 => _69.parent]), () => ( null));
1570
2387
  while (parent) {
1571
2388
  if (this.selection.includes(parent.id)) covered = true;
1572
- parent = _nullishCoalesce(_optionalChain([locateVectorNode, 'call', _42 => _42(this.doc.nodes, parent.id), 'optionalAccess', _43 => _43.parent]), () => ( null));
2389
+ parent = _nullishCoalesce(_optionalChain([locateVectorNode, 'call', _70 => _70(this.doc.nodes, parent.id), 'optionalAccess', _71 => _71.parent]), () => ( null));
1573
2390
  }
1574
2391
  return !covered;
1575
2392
  });
@@ -1752,8 +2569,8 @@ var VectorEditorController = (_class = class {
1752
2569
  if (!ids.length) return null;
1753
2570
  const locs = ids.map((id2) => locateVectorNode(this.doc.nodes, id2));
1754
2571
  if (locs.some((l) => !l)) return null;
1755
- const parent = _nullishCoalesce(_optionalChain([locs, 'access', _44 => _44[0], 'access', _45 => _45.parent, 'optionalAccess', _46 => _46.id]), () => ( null));
1756
- if (locs.some((l) => (_nullishCoalesce(_optionalChain([l, 'access', _47 => _47.parent, 'optionalAccess', _48 => _48.id]), () => ( null))) !== parent)) return null;
2572
+ const parent = _nullishCoalesce(_optionalChain([locs, 'access', _72 => _72[0], 'access', _73 => _73.parent, 'optionalAccess', _74 => _74.id]), () => ( null));
2573
+ if (locs.some((l) => (_nullishCoalesce(_optionalChain([l, 'access', _75 => _75.parent, 'optionalAccess', _76 => _76.id]), () => ( null))) !== parent)) return null;
1757
2574
  const id = this.newId();
1758
2575
  this.change("Group", () => {
1759
2576
  const set = new Set(ids);
@@ -1776,7 +2593,7 @@ var VectorEditorController = (_class = class {
1776
2593
  }
1777
2594
  /** Replaces selected groups with their children. */
1778
2595
  ungroup(options = {}) {
1779
- const groups = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess', _49 => _49.type]) === "group");
2596
+ const groups = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess', _77 => _77.type]) === "group");
1780
2597
  if (!groups.length) return { ok: false, message: "Select a group to ungroup." };
1781
2598
  const set = new Set(groups.map((g) => g.id));
1782
2599
  this.change("Ungroup", () => {
@@ -1836,7 +2653,7 @@ var VectorEditorController = (_class = class {
1836
2653
  });
1837
2654
  const paths = [...new Set(order)].map((id2) => this.getNode(id2));
1838
2655
  if (paths.length < 2) return null;
1839
- const result = _chunkM4XE4UMTcjs.pathBoolean.call(void 0,
2656
+ const result = _chunkUG5DVXJKcjs.pathBoolean.call(void 0,
1840
2657
  BOOLEAN_OPS[op],
1841
2658
  paths.map((p) => ({
1842
2659
  path: vectorPathWorldBezier(
@@ -1886,11 +2703,11 @@ var VectorEditorController = (_class = class {
1886
2703
  /** A document-space path mapped into the parent space of node `id`. */
1887
2704
  intoParentSpace(id, path) {
1888
2705
  const parent = _nullishCoalesce(vectorParentMatrix(this.doc.nodes, id), () => ( IDENTITY_MATRIX));
1889
- return isRoot(parent) ? path : _chunkM4XE4UMTcjs.transformBezierPath.call(void 0, path, invertMatrix(parent));
2706
+ return isRoot(parent) ? path : _chunkUG5DVXJKcjs.transformBezierPath.call(void 0, path, invertMatrix(parent));
1890
2707
  }
1891
2708
  /** Splits selected compound paths into one path per subpath. */
1892
2709
  breakApart(options = {}) {
1893
- const paths = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess', _50 => _50.type]) === "path" && n.subpaths.length > 1);
2710
+ const paths = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess', _78 => _78.type]) === "path" && n.subpaths.length > 1);
1894
2711
  if (!paths.length) return { ok: false, message: "Select a compound path to break apart." };
1895
2712
  const created = [];
1896
2713
  const set = new Set(paths.map((p) => p.id));
@@ -1972,7 +2789,7 @@ var VectorEditorController = (_class = class {
1972
2789
  /** Adds an anchor on the nearest segment of `nodeId` to document point `point` (shape unchanged). */
1973
2790
  insertAnchorAt(nodeId, point) {
1974
2791
  const node = this.getNode(nodeId);
1975
- if (_optionalChain([node, 'optionalAccess', _51 => _51.type]) !== "path") return null;
2792
+ if (_optionalChain([node, 'optionalAccess', _79 => _79.type]) !== "path") return null;
1976
2793
  const hit = nearestSegment(node, this.toLocal(nodeId, point));
1977
2794
  if (!hit) return null;
1978
2795
  const { subpath, index } = insertAnchor(node.subpaths[hit.subpath], hit.segment, hit.t);
@@ -2007,7 +2824,7 @@ var VectorEditorController = (_class = class {
2007
2824
  setClosed(closed, nodeId) {
2008
2825
  const targets = nodeId ? (() => {
2009
2826
  const n = this.getNode(nodeId);
2010
- return _optionalChain([n, 'optionalAccess', _52 => _52.type]) === "path" ? n.subpaths.map((_, s) => ({ nodeId, subpath: s })) : [];
2827
+ return _optionalChain([n, 'optionalAccess', _80 => _80.type]) === "path" ? n.subpaths.map((_, s) => ({ nodeId, subpath: s })) : [];
2011
2828
  })() : this.anchors.map((r) => ({ nodeId: r.nodeId, subpath: r.subpath }));
2012
2829
  if (!targets.length) return { ok: false, message: "Select a path or points." };
2013
2830
  this.change(closed ? "Close Path" : "Open Path", () => {
@@ -2034,7 +2851,7 @@ var VectorEditorController = (_class = class {
2034
2851
  finishPath() {
2035
2852
  if (!this.pen) return;
2036
2853
  const node = this.getNode(this.pen.nodeId);
2037
- const sp = _optionalChain([node, 'optionalAccess', _53 => _53.subpaths, 'access', _54 => _54[this.pen.subpath]]);
2854
+ const sp = _optionalChain([node, 'optionalAccess', _81 => _81.subpaths, 'access', _82 => _82[this.pen.subpath]]);
2038
2855
  const pen = this.pen;
2039
2856
  this.pen = null;
2040
2857
  this.pointer = null;
@@ -2078,7 +2895,7 @@ var VectorEditorController = (_class = class {
2078
2895
  if (trimmed.startsWith("{")) {
2079
2896
  try {
2080
2897
  const data = JSON.parse(trimmed);
2081
- if (_optionalChain([data, 'optionalAccess', _55 => _55.type]) === VECTOR_CLIPBOARD_TYPE && Array.isArray(data.nodes)) nodes = data.nodes;
2898
+ if (_optionalChain([data, 'optionalAccess', _83 => _83.type]) === VECTOR_CLIPBOARD_TYPE && Array.isArray(data.nodes)) nodes = data.nodes;
2082
2899
  } catch (e2) {
2083
2900
  nodes = null;
2084
2901
  }
@@ -2090,7 +2907,7 @@ var VectorEditorController = (_class = class {
2090
2907
  }
2091
2908
  }
2092
2909
  } else nodes = this.clipboard;
2093
- if (!_optionalChain([nodes, 'optionalAccess', _56 => _56.length])) return [];
2910
+ if (!_optionalChain([nodes, 'optionalAccess', _84 => _84.length])) return [];
2094
2911
  const offset = _nullishCoalesce(options.offset, () => ( (text && !text.trim().startsWith("{") ? { x: 0, y: 0 } : { x: 10, y: 10 })));
2095
2912
  const copies = nodes.map((n) => transformVectorNode(this.freshIds(n), translateMatrix(offset.x, offset.y)));
2096
2913
  this.change("Paste", () => {
@@ -2141,7 +2958,7 @@ var VectorEditorController = (_class = class {
2141
2958
  hitHandle(screen) {
2142
2959
  for (const ref of getVisibleHandles(this.view()).reverse()) {
2143
2960
  const a = getAnchorWorld(this.doc, ref);
2144
- const h = _optionalChain([a, 'optionalAccess', _57 => _57[ref.handle]]);
2961
+ const h = _optionalChain([a, 'optionalAccess', _85 => _85[ref.handle]]);
2145
2962
  if (!h) continue;
2146
2963
  const s = this.documentToScreen(h);
2147
2964
  if (Math.hypot(s.x - screen.x, s.y - screen.y) <= this.hitRadius) return ref;
@@ -2251,7 +3068,7 @@ var VectorEditorController = (_class = class {
2251
3068
  case "pencil":
2252
3069
  return this.downPencil(p);
2253
3070
  default:
2254
- return this.downShape(p);
3071
+ return this.downShape(p, input);
2255
3072
  }
2256
3073
  }
2257
3074
  downSelect(screen, p, input) {
@@ -2320,7 +3137,7 @@ var VectorEditorController = (_class = class {
2320
3137
  return;
2321
3138
  }
2322
3139
  } else if (!selected) this.anchors = [anchor];
2323
- this.drag = { kind: "anchors", start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: selected && !input.shift ? anchor : null };
3140
+ this.drag = { kind: "anchors", start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: selected && !input.shift ? anchor : null, lead: anchor };
2324
3141
  this.notify();
2325
3142
  return;
2326
3143
  }
@@ -2336,7 +3153,7 @@ var VectorEditorController = (_class = class {
2336
3153
  const ref = { nodeId: id, subpath: seg.subpath, index };
2337
3154
  this.anchors = [ref];
2338
3155
  this.editingId = id;
2339
- this.drag = { kind: "anchors", start: p, before, base: this.doc, moved: true, clickRef: null };
3156
+ this.drag = { kind: "anchors", start: p, before, base: this.doc, moved: true, clickRef: null, lead: ref };
2340
3157
  this.notify(true);
2341
3158
  return;
2342
3159
  }
@@ -2344,7 +3161,7 @@ var VectorEditorController = (_class = class {
2344
3161
  const b = { nodeId: id, subpath: seg.subpath, index: (seg.segment + 1) % sp.anchors.length };
2345
3162
  this.anchors = input.shift ? [...this.anchors.filter((r) => !sameRef(r, a) && !sameRef(r, b)), a, b] : [a, b];
2346
3163
  this.editingId = id;
2347
- this.drag = { kind: "anchors", start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: null };
3164
+ this.drag = { kind: "anchors", start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: null, lead: null };
2348
3165
  this.notify();
2349
3166
  return;
2350
3167
  }
@@ -2354,7 +3171,7 @@ var VectorEditorController = (_class = class {
2354
3171
  if (!input.shift) this.selection = [path.id];
2355
3172
  else if (!this.selection.includes(path.id)) this.selection = [...this.selection, path.id];
2356
3173
  this.anchors = path.subpaths.flatMap((sp, s) => sp.anchors.map((_, i) => ({ nodeId: path.id, subpath: s, index: i })));
2357
- this.drag = { kind: "anchors", start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: null };
3174
+ this.drag = { kind: "anchors", start: p, before: this.snapshot(), base: this.doc, moved: false, clickRef: null, lead: null };
2358
3175
  this.notify();
2359
3176
  return;
2360
3177
  }
@@ -2367,7 +3184,7 @@ var VectorEditorController = (_class = class {
2367
3184
  }
2368
3185
  downPen(screen, p, input) {
2369
3186
  const before = this.snapshot();
2370
- const sp = this.pen ? _optionalChain([this, 'access', _58 => _58.getNode, 'call', _59 => _59(this.pen.nodeId), 'optionalAccess', _60 => _60.subpaths, 'access', _61 => _61[this.pen.subpath]]) : void 0;
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;
2371
3188
  if (this.pen && sp) {
2372
3189
  const pen = this.pen;
2373
3190
  const first = { nodeId: pen.nodeId, subpath: pen.subpath, index: 0 };
@@ -2379,9 +3196,9 @@ var VectorEditorController = (_class = class {
2379
3196
  this.notify(true);
2380
3197
  return;
2381
3198
  }
2382
- let pt2 = this.snapPoint(p);
2383
3199
  const last = getAnchorWorld(this.doc, { nodeId: pen.nodeId, subpath: pen.subpath, index: sp.anchors.length - 1 });
2384
- if (input.shift) pt2 = constrain45(last, pt2);
3200
+ const leave = { at: { x: last.x, y: last.y }, constrain: input.shift ? "angle" : null };
3201
+ let pt2 = this.snapDrawn(p, leave, input, () => this.drawingTargets(pen.nodeId));
2385
3202
  pt2 = this.toLocal(pen.nodeId, pt2);
2386
3203
  const node2 = this.getNode(pen.nodeId);
2387
3204
  const anchors = [...sp.anchors, { x: pt2.x, y: pt2.y }];
@@ -2412,7 +3229,7 @@ var VectorEditorController = (_class = class {
2412
3229
  this.notify(this.doc !== before.doc);
2413
3230
  return;
2414
3231
  }
2415
- const pt = this.snapPoint(p);
3232
+ const pt = this.snapDrawn(p, null, input, () => this.drawingTargets(null));
2416
3233
  const node = { id: this.newId(), type: "path", name: "Path", ...this.styleFor(true), subpaths: [{ anchors: [{ x: pt.x, y: pt.y }], closed: false }] };
2417
3234
  this.setDoc([...this.doc.nodes, node]);
2418
3235
  this.pen = { nodeId: node.id, subpath: 0 };
@@ -2440,13 +3257,14 @@ var VectorEditorController = (_class = class {
2440
3257
  this.interaction = { type: "draw", tool: "pencil", nodeId: node.id, bounds: { x: p.x, y: p.y, width: 0, height: 0 } };
2441
3258
  this.notify(true);
2442
3259
  }
2443
- downShape(p) {
3260
+ downShape(p, input) {
2444
3261
  const before = this.snapshot();
2445
- const start = this.snapPoint(p);
3262
+ const targets = this.drawingTargets(null);
3263
+ const start = this.snapDrawn(p, null, input, () => targets);
2446
3264
  const kind = this.tool;
2447
3265
  const node = this.createShapeNode({ kind, x: start.x, y: start.y, width: 0, height: 0 });
2448
3266
  this.setDoc([...this.doc.nodes, node]);
2449
- this.drag = { kind: "shape", tool: this.tool, start, nodeId: node.id, before };
3267
+ this.drag = { kind: "shape", tool: this.tool, start, nodeId: node.id, before, targets };
2450
3268
  this.interaction = { type: "draw", tool: this.tool, nodeId: node.id, bounds: { x: start.x, y: start.y, width: 0, height: 0 } };
2451
3269
  this.notify(true);
2452
3270
  }
@@ -2457,9 +3275,11 @@ var VectorEditorController = (_class = class {
2457
3275
  if (!drag) {
2458
3276
  const hover = this.computeHover(screen);
2459
3277
  const pointerMatters = this.tool === "pen" && !!this.pen;
2460
- if (this.sameHover(hover, this.hover) && !pointerMatters) return;
3278
+ const guides = this.guides;
2461
3279
  this.hover = hover;
2462
- this.pointer = p;
3280
+ const pointer = this.hoverSnap(p, input);
3281
+ if (this.sameHover(hover, this.state.hover) && !pointerMatters && JSON.stringify(guides) === JSON.stringify(this.guides)) return;
3282
+ this.pointer = pointer;
2463
3283
  this.notify();
2464
3284
  return;
2465
3285
  }
@@ -2480,8 +3300,9 @@ var VectorEditorController = (_class = class {
2480
3300
  const created = [];
2481
3301
  drag.base = this.withDuplicates(drag.base, this.selectedRoots(), { x: 0, y: 0 }, created);
2482
3302
  this.selection = created;
3303
+ drag.targets = void 0;
2483
3304
  }
2484
- ({ dx, dy } = this.snapDelta(dx, dy, drag.origin));
3305
+ ({ dx, dy } = this.snapMove(drag, dx, dy, input));
2485
3306
  this.doc = this.transformed(drag.base, new Set(this.selectedRoots()), translateMatrix(dx, dy));
2486
3307
  this.interaction = { type: "move", dx, dy };
2487
3308
  this.notify(true);
@@ -2490,7 +3311,13 @@ var VectorEditorController = (_class = class {
2490
3311
  case "scale": {
2491
3312
  const angle = drag.frame.angle;
2492
3313
  const q = angle ? applyMatrix(rotateMatrix(-angle), p) : p;
2493
- const { sx, sy, origin } = scaleFor(drag.handle, drag.frame, q, input);
3314
+ let { sx, sy, origin } = scaleFor(drag.handle, drag.frame, q, input);
3315
+ if (this.snap.objects && !input.mod && drag.handle !== "rotate") {
3316
+ drag.targets ??= VectorSnapTargets.forResize(drag.base, this.selectedRoots());
3317
+ const res = drag.targets.snapScale(drag.frame, drag.handle, sx, sy, input, this.snapTolerance());
3318
+ ({ sx, sy } = res);
3319
+ this.setGuides(res);
3320
+ } else this.setGuides(null);
2494
3321
  const ids = new Set(this.selectedRoots());
2495
3322
  this.doc = this.transformed(drag.base, ids, this.inFrame(angle, scaleMatrix(sx, sy, origin)));
2496
3323
  this.interaction = { type: "scale", handle: drag.handle, sx, sy };
@@ -2531,9 +3358,13 @@ var VectorEditorController = (_class = class {
2531
3358
  let dy = p.y - drag.start.y;
2532
3359
  if (!drag.moved && Math.hypot(dx, dy) * this.viewport.zoom < 2) return;
2533
3360
  drag.moved = true;
2534
- if (input.shift) Math.abs(dx) > Math.abs(dy) ? dy = 0 : dx = 0;
2535
- const lead = this.anchors.length ? getAnchorWorld(drag.base, this.anchors[0]) : void 0;
2536
- ({ dx, dy } = this.snapDelta(dx, dy, _nullishCoalesce(lead, () => ( null))));
3361
+ const lead = this.leadAnchor(drag);
3362
+ if (lead) {
3363
+ const leave = input.shift ? { at: lead, constrain: "axis", curves: false } : null;
3364
+ const q = this.snapDrawn({ x: lead.x + dx, y: lead.y + dy }, leave, input, () => drag.targets ??= VectorSnapTargets.forAnchorDrag(drag.base, this.anchors));
3365
+ dx = q.x - lead.x;
3366
+ dy = q.y - lead.y;
3367
+ } else if (input.shift) Math.abs(dx) > Math.abs(dy) ? dy = 0 : dx = 0;
2537
3368
  this.doc = this.movedAnchors(drag.base, this.anchors, dx, dy);
2538
3369
  this.interaction = { type: "anchors", dx, dy };
2539
3370
  this.notify(true);
@@ -2541,8 +3372,8 @@ var VectorEditorController = (_class = class {
2541
3372
  }
2542
3373
  case "handle": {
2543
3374
  const anchor = getAnchorWorld(drag.base, drag.ref);
2544
- let pt = this.snapPoint(p);
2545
- if (input.shift && anchor) pt = constrain45(anchor, pt);
3375
+ const leave = anchor ? { at: { x: anchor.x, y: anchor.y }, constrain: input.shift ? "angle" : null } : null;
3376
+ const pt = this.snapDrawn(p, leave, input, () => drag.targets ??= VectorSnapTargets.forHandle(drag.base, drag.ref.nodeId));
2546
3377
  const local = this.toLocal(drag.ref.nodeId, pt, drag.base);
2547
3378
  this.doc = this.updateAnchors(drag.base, [drag.ref], (a) => moveAnchorHandle(a, drag.ref.handle, local, input.alt));
2548
3379
  this.interaction = { type: "handle", ref: drag.ref };
@@ -2557,6 +3388,7 @@ var VectorEditorController = (_class = class {
2557
3388
  this.notify(true);
2558
3389
  return;
2559
3390
  }
3391
+ this.guides = null;
2560
3392
  let pt = p;
2561
3393
  if (input.shift) pt = constrain45(anchor, pt);
2562
3394
  const side = drag.closing ? "in" : "out";
@@ -2570,8 +3402,10 @@ var VectorEditorController = (_class = class {
2570
3402
  }
2571
3403
  case "shape": {
2572
3404
  const node = this.getNode(drag.nodeId);
2573
- if (_optionalChain([node, 'optionalAccess', _62 => _62.type]) !== "path") return;
2574
- let end = this.snapPoint(p);
3405
+ if (_optionalChain([node, 'optionalAccess', _90 => _90.type]) !== "path") return;
3406
+ const leave = { at: drag.start, constrain: input.shift ? drag.tool === "line" ? "angle" : "square" : null, curves: false };
3407
+ let end = this.snapDrawn(p, leave, input, () => drag.targets ??= this.drawingTargets(null));
3408
+ 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);
2575
3409
  let start = drag.start;
2576
3410
  let bounds;
2577
3411
  if (drag.tool === "line") {
@@ -2602,7 +3436,7 @@ var VectorEditorController = (_class = class {
2602
3436
  if (Math.hypot(p.x - last[0], p.y - last[1]) * this.viewport.zoom < 1.5) return;
2603
3437
  drag.points.push([p.x, p.y]);
2604
3438
  const node = this.getNode(drag.nodeId);
2605
- if (_optionalChain([node, 'optionalAccess', _63 => _63.type]) !== "path") return;
3439
+ if (_optionalChain([node, 'optionalAccess', _91 => _91.type]) !== "path") return;
2606
3440
  this.replaceNode(drag.nodeId, { ...node, subpaths: [{ anchors: drag.points.map(([x, y]) => ({ x, y })), closed: false }] });
2607
3441
  const xs = drag.points.map((q) => q[0]);
2608
3442
  const ys = drag.points.map((q) => q[1]);
@@ -2617,12 +3451,64 @@ var VectorEditorController = (_class = class {
2617
3451
  }
2618
3452
  }
2619
3453
  }
3454
+ /** Snaps a move of the selection by (dx, dy): its bounds line up with targets or space evenly, else go to the grid. */
3455
+ snapMove(drag, dx, dy, input) {
3456
+ const origin = drag.origin;
3457
+ if (input.mod || !origin) {
3458
+ this.setGuides(null);
3459
+ return { dx, dy };
3460
+ }
3461
+ const axes = { x: !input.shift || dx !== 0, y: !input.shift || dy !== 0 };
3462
+ let res = null;
3463
+ if (this.snap.objects) {
3464
+ drag.targets ??= VectorSnapTargets.forMove(drag.base, this.selectedRoots());
3465
+ res = drag.targets.snapRect({ ...origin, x: origin.x + dx, y: origin.y + dy }, this.snapTolerance(), { spacing: true, axes });
3466
+ }
3467
+ this.setGuides(res);
3468
+ 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
3471
+ };
3472
+ }
3473
+ /** The anchor a point drag snaps by, where it started: the grabbed one, else the moving one nearest the press. */
3474
+ leadAnchor(drag) {
3475
+ const ref = drag.lead && this.anchors.some((a) => sameRef(a, drag.lead)) ? drag.lead : null;
3476
+ if (ref) {
3477
+ const a = getAnchorWorld(drag.base, ref);
3478
+ return a ? { x: a.x, y: a.y } : null;
3479
+ }
3480
+ let best = null;
3481
+ for (const r of this.anchors) {
3482
+ const a = getAnchorWorld(drag.base, r);
3483
+ if (a && (!best || Math.hypot(a.x - drag.start.x, a.y - drag.start.y) < Math.hypot(best.x - drag.start.x, best.y - drag.start.y))) best = { x: a.x, y: a.y };
3484
+ }
3485
+ return best;
3486
+ }
3487
+ /** A shape's dragged corner moved so its width or height matches another object's, when one is within reach. */
3488
+ matchShapeSize(targets, start, end, fromCentre = false) {
3489
+ const k = fromCentre ? 2 : 1;
3490
+ const tol = this.snapTolerance() * k;
3491
+ const out = { ...end };
3492
+ const guides = { lines: [], labels: [] };
3493
+ for (const axis of ["x", "y"]) {
3494
+ const d = end[axis] - start[axis];
3495
+ const len = Math.abs(d) > 1e-9 ? targets.matchSize(Math.abs(d) * k, tol) : null;
3496
+ if (len === null || len <= 0) continue;
3497
+ out[axis] = start[axis] + Math.sign(d) * len / k;
3498
+ const from = { ...out, [axis]: fromCentre ? 2 * start[axis] - out[axis] : start[axis] };
3499
+ guides.lines.push({ kind: "size", x1: from.x, y1: from.y, x2: out.x, y2: out.y });
3500
+ guides.labels.push({ kind: "size", x: (from.x + out.x) / 2, y: (from.y + out.y) / 2, text: `${axis === "x" ? "W" : "H"} ${Math.round(len * 100) / 100}` });
3501
+ }
3502
+ this.setGuides(guides);
3503
+ return out;
3504
+ }
2620
3505
  /** Primary button released. */
2621
3506
  pointerUp(screen, input = {}) {
2622
3507
  if (screen && this.drag && this.drag.kind !== "pan") this.pointerMove(screen, input);
2623
3508
  const drag = this.drag;
2624
3509
  this.drag = null;
2625
3510
  this.interaction = null;
3511
+ this.guides = null;
2626
3512
  if (!drag) return;
2627
3513
  switch (drag.kind) {
2628
3514
  case "pan":
@@ -2669,12 +3555,12 @@ var VectorEditorController = (_class = class {
2669
3555
  }
2670
3556
  case "pencil": {
2671
3557
  const node = this.getNode(drag.nodeId);
2672
- if (_optionalChain([node, 'optionalAccess', _64 => _64.type]) !== "path" || drag.points.length < 2) {
3558
+ if (_optionalChain([node, 'optionalAccess', _94 => _94.type]) !== "path" || drag.points.length < 2) {
2673
3559
  this.doc = drag.before.doc;
2674
3560
  this.notify(true);
2675
3561
  return;
2676
3562
  }
2677
- const fitted = _chunkM4XE4UMTcjs.fitCubics.call(void 0, drag.points, 1.5 / this.viewport.zoom, { cornerAngleDeg: 60 });
3563
+ const fitted = _chunkUG5DVXJKcjs.fitCubics.call(void 0, drag.points, 1.5 / this.viewport.zoom, { cornerAngleDeg: 60 });
2678
3564
  const anchors = [];
2679
3565
  for (const c of fitted) {
2680
3566
  if (!anchors.length) anchors.push({ x: c.p0[0], y: c.p0[1] });
@@ -2691,9 +3577,10 @@ var VectorEditorController = (_class = class {
2691
3577
  /** The pointer left the canvas. */
2692
3578
  pointerLeave() {
2693
3579
  if (this.drag) return;
2694
- if (!this.hover && !this.pointer) return;
3580
+ if (!this.hover && !this.pointer && !this.guides) return;
2695
3581
  this.hover = null;
2696
3582
  this.pointer = null;
3583
+ this.guides = null;
2697
3584
  this.notify();
2698
3585
  }
2699
3586
  /** Abandons the drag in progress (Escape, lost capture). */
@@ -2702,6 +3589,7 @@ var VectorEditorController = (_class = class {
2702
3589
  if (!drag) return;
2703
3590
  this.drag = null;
2704
3591
  this.interaction = null;
3592
+ this.guides = null;
2705
3593
  if (drag.kind === "pan") {
2706
3594
  this.notify();
2707
3595
  return;
@@ -2734,7 +3622,7 @@ var VectorEditorController = (_class = class {
2734
3622
  this.convertAnchors(a.in || a.out ? "corner" : "smooth", [anchor]);
2735
3623
  return;
2736
3624
  }
2737
- const id = _nullishCoalesce(_optionalChain([this, 'access', _65 => _65.hover, 'optionalAccess', _66 => _66.segment, 'optionalAccess', _67 => _67.nodeId]), () => ( this.editingId));
3625
+ const id = _nullishCoalesce(_optionalChain([this, 'access', _95 => _95.hover, 'optionalAccess', _96 => _96.segment, 'optionalAccess', _97 => _97.nodeId]), () => ( this.editingId));
2738
3626
  if (id) {
2739
3627
  const node = this.getNode(id);
2740
3628
  const seg = node ? nearestSegment(node, this.toLocal(id, p)) : null;
@@ -2812,7 +3700,7 @@ var VectorEditorController = (_class = class {
2812
3700
  this.finishPath();
2813
3701
  return true;
2814
3702
  }
2815
- if (this.tool === "select" && this.selection.length === 1 && _optionalChain([this, 'access', _68 => _68.getNode, 'call', _69 => _69(this.selection[0]), 'optionalAccess', _70 => _70.type]) === "path") {
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") {
2816
3704
  this.tool = "direct";
2817
3705
  this.editingId = this.selection[0];
2818
3706
  this.notify();
@@ -2857,7 +3745,7 @@ var VectorEditorController = (_class = class {
2857
3745
  this.notify();
2858
3746
  }
2859
3747
  }
2860
- }, _class);
3748
+ }, _class2);
2861
3749
  function constrain45(origin, p) {
2862
3750
  const dx = p.x - origin.x;
2863
3751
  const dy = p.y - origin.y;
@@ -2910,7 +3798,7 @@ function renderNode(node, defs, idPrefix) {
2910
3798
  strokeWidth: _nullishCoalesce(node.strokeWidth, () => ( 1)),
2911
3799
  ...node.strokeLinecap ? { strokeLinecap: node.strokeLinecap } : {},
2912
3800
  ...node.strokeLinejoin ? { strokeLinejoin: node.strokeLinejoin } : {},
2913
- ..._optionalChain([node, 'access', _71 => _71.strokeDasharray, 'optionalAccess', _72 => _72.length]) ? { strokeDasharray: node.strokeDasharray.join(" ") } : {},
3801
+ ..._optionalChain([node, 'access', _101 => _101.strokeDasharray, 'optionalAccess', _102 => _102.length]) ? { strokeDasharray: node.strokeDasharray.join(" ") } : {},
2914
3802
  fillRule: _nullishCoalesce(node.fillRule, () => ( "nonzero")),
2915
3803
  opacity: _nullishCoalesce(node.opacity, () => ( 1))
2916
3804
  };
@@ -2934,7 +3822,7 @@ function screenOutline(state, node) {
2934
3822
  const v = state.viewport;
2935
3823
  const view = [v.zoom, 0, 0, v.zoom, v.x, v.y];
2936
3824
  const parent = _nullishCoalesce(vectorParentMatrix(state.document.nodes, node.id), () => ( IDENTITY_MATRIX));
2937
- return collectWithMatrix(node, parent).map(({ path, parent: m }) => _chunkM4XE4UMTcjs.bezierPathToSvg.call(void 0, _chunkM4XE4UMTcjs.transformBezierPath.call(void 0, vectorPathWorldBezier(path, m), view), { precision: 2 })).join(" ");
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(" ");
2938
3826
  }
2939
3827
  function collectWithMatrix(node, parent) {
2940
3828
  if (node.type === "path") return [{ path: node, parent }];
@@ -2959,9 +3847,9 @@ function getVectorOverlayRenderData(state) {
2959
3847
  };
2960
3848
  for (const id of state.selection) outline(id, true);
2961
3849
  if (state.editingId) outline(state.editingId, true);
2962
- if (_optionalChain([state, 'access', _73 => _73.hover, 'optionalAccess', _74 => _74.nodeId]) && !state.interaction) outline(state.hover.nodeId, false);
3850
+ if (_optionalChain([state, 'access', _103 => _103.hover, 'optionalAccess', _104 => _104.nodeId]) && !state.interaction) outline(state.hover.nodeId, false);
2963
3851
  let selectionBox = null;
2964
- if (state.tool === "select" && state.selectionFrame && _optionalChain([state, 'access', _75 => _75.interaction, 'optionalAccess', _76 => _76.type]) !== "marquee") {
3852
+ if (state.tool === "select" && state.selectionFrame && _optionalChain([state, 'access', _105 => _105.interaction, 'optionalAccess', _106 => _106.type]) !== "marquee") {
2965
3853
  const g = getVectorSelectionBoxGeometry(state.selectionFrame, state.viewport);
2966
3854
  selectionBox = { ...g.box, angle: g.angle, transform: g.transform, corners: g.corners, handles: g.handles };
2967
3855
  }
@@ -2971,9 +3859,9 @@ function getVectorOverlayRenderData(state) {
2971
3859
  const a = getAnchorWorld(doc, ref);
2972
3860
  const s = toScreen(state, a);
2973
3861
  const closes = !!state.pen && state.pen.nodeId === ref.nodeId && state.pen.subpath === ref.subpath && ref.index === 0;
2974
- return { ref, x: s.x, y: s.y, type: _nullishCoalesce(a.type, () => ( inferAnchorType(a))), selected: selected(ref), hovered: sameAnchor(_optionalChain([state, 'access', _77 => _77.hover, 'optionalAccess', _78 => _78.anchor]), ref), closes };
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 };
2975
3863
  });
2976
- if (state.tool === "pen" && !state.pen && _optionalChain([state, 'access', _79 => _79.hover, 'optionalAccess', _80 => _80.anchor])) {
3864
+ if (state.tool === "pen" && !state.pen && _optionalChain([state, 'access', _109 => _109.hover, 'optionalAccess', _110 => _110.anchor])) {
2977
3865
  const a = getAnchorWorld(doc, state.hover.anchor);
2978
3866
  if (a) {
2979
3867
  const s = toScreen(state, a);
@@ -2982,15 +3870,15 @@ function getVectorOverlayRenderData(state) {
2982
3870
  }
2983
3871
  const handles = getVisibleHandles(state).flatMap((ref) => {
2984
3872
  const a = getAnchorWorld(doc, ref);
2985
- const h = _optionalChain([a, 'optionalAccess', _81 => _81[ref.handle]]);
3873
+ const h = _optionalChain([a, 'optionalAccess', _111 => _111[ref.handle]]);
2986
3874
  if (!a || !h) return [];
2987
3875
  const s = toScreen(state, h);
2988
3876
  const sa = toScreen(state, a);
2989
- const hovered = !!_optionalChain([state, 'access', _82 => _82.hover, 'optionalAccess', _83 => _83.handle]) && sameAnchor(state.hover.handle, ref) && state.hover.handle.handle === ref.handle;
3877
+ const hovered = !!_optionalChain([state, 'access', _112 => _112.hover, 'optionalAccess', _113 => _113.handle]) && sameAnchor(state.hover.handle, ref) && state.hover.handle.handle === ref.handle;
2990
3878
  return [{ ref, x: s.x, y: s.y, anchorX: sa.x, anchorY: sa.y, hovered }];
2991
3879
  });
2992
3880
  let marquee = null;
2993
- if (_optionalChain([state, 'access', _84 => _84.interaction, 'optionalAccess', _85 => _85.type]) === "marquee") {
3881
+ if (_optionalChain([state, 'access', _114 => _114.interaction, 'optionalAccess', _115 => _115.type]) === "marquee") {
2994
3882
  const b = state.interaction.bounds;
2995
3883
  const tl = toScreen(state, b);
2996
3884
  marquee = { x: tl.x, y: tl.y, width: b.width * z, height: b.height * z };
@@ -2998,7 +3886,7 @@ function getVectorOverlayRenderData(state) {
2998
3886
  let penPreview = null;
2999
3887
  if (state.tool === "pen" && state.pen && state.pointer && !state.interaction) {
3000
3888
  const node = findVectorNode(doc.nodes, state.pen.nodeId);
3001
- const sp = _optionalChain([node, 'optionalAccess', _86 => _86.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
3889
+ const sp = _optionalChain([node, 'optionalAccess', _116 => _116.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
3002
3890
  const last = sp ? getAnchorWorld(doc, { nodeId: state.pen.nodeId, subpath: state.pen.subpath, index: sp.anchors.length - 1 }) : void 0;
3003
3891
  if (last) {
3004
3892
  const a = toScreen(state, last);
@@ -3011,16 +3899,30 @@ function getVectorOverlayRenderData(state) {
3011
3899
  const artboard = { x: origin.x, y: origin.y, width: doc.width * z, height: doc.height * z };
3012
3900
  let status = null;
3013
3901
  const it = state.interaction;
3014
- if (_optionalChain([it, 'optionalAccess', _87 => _87.type]) === "rotate") status = `${Math.round(it.angle)}\xB0`;
3015
- else if (_optionalChain([it, 'optionalAccess', _88 => _88.type]) === "move" || _optionalChain([it, 'optionalAccess', _89 => _89.type]) === "anchors") status = `\u0394 ${fmt(it.dx)}, ${fmt(it.dy)}`;
3016
- else if (_optionalChain([it, 'optionalAccess', _90 => _90.type]) === "scale" || _optionalChain([it, 'optionalAccess', _91 => _91.type]) === "draw") {
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") {
3017
3905
  const b = it.type === "draw" ? findVectorNode(doc.nodes, it.nodeId) ? vectorNodeBounds(findVectorNode(doc.nodes, it.nodeId)) : null : state.selectionFrame;
3018
3906
  if (b) status = `W ${fmt(b.width)} \xD7 H ${fmt(b.height)}`;
3019
3907
  }
3020
- return { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, status };
3908
+ const guides = { lines: [], labels: [] };
3909
+ if (state.guides) {
3910
+ for (const l of state.guides.lines) {
3911
+ const a = toScreen(state, { x: l.x1, y: l.y1 });
3912
+ const b = toScreen(state, { x: l.x2, y: l.y2 });
3913
+ guides.lines.push({ kind: l.kind, x1: fmt(a.x), y1: fmt(a.y), x2: fmt(b.x), y2: fmt(b.y) });
3914
+ }
3915
+ for (const l of state.guides.labels) {
3916
+ const p = toScreen(state, l);
3917
+ guides.labels.push({ kind: l.kind, x: fmt(p.x), y: fmt(p.y), text: l.text });
3918
+ }
3919
+ }
3920
+ return { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, guides, status };
3021
3921
  }
3022
3922
  var VECTOR_OVERLAY_THEME = {
3023
3923
  accent: "#0ea5e9",
3924
+ /** Smart guide lines and labels. */
3925
+ guide: "#ff3dfc",
3024
3926
  handleFill: "#ffffff",
3025
3927
  marqueeFill: "rgba(14, 165, 233, 0.08)",
3026
3928
  transformHandleSize: 8,
@@ -3041,7 +3943,7 @@ function isVectorTextTarget(target) {
3041
3943
  if (!el || typeof el !== "object") return false;
3042
3944
  const tag = (_nullishCoalesce(el.tagName, () => ( ""))).toUpperCase();
3043
3945
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
3044
- return !!el.isContentEditable || _optionalChain([el, 'access', _92 => _92.getAttribute, 'optionalCall', _93 => _93("contenteditable")]) === "true";
3946
+ return !!el.isContentEditable || _optionalChain([el, 'access', _122 => _122.getAttribute, 'optionalCall', _123 => _123("contenteditable")]) === "true";
3045
3947
  }
3046
3948
  function getVectorSelectionStyle(state) {
3047
3949
  const paths = [];
@@ -3100,12 +4002,12 @@ function createVectorCommands(controller, options = {}) {
3100
4002
  const base = { group, ...options.scope ? { scope: options.scope } : {} };
3101
4003
  const hasSelection = () => controller.getState().selection.length ? true : "Nothing is selected.";
3102
4004
  const ids = (params) => {
3103
- const list2 = _nullishCoalesce(_optionalChain([params, 'optionalAccess', _94 => _94.ids]), () => ( controller.getState().selection));
4005
+ const list2 = _nullishCoalesce(_optionalChain([params, 'optionalAccess', _124 => _124.ids]), () => ( controller.getState().selection));
3104
4006
  for (const id of list2) if (!controller.getNode(id)) fail(`There is no node "${id}".`);
3105
4007
  if (!list2.length) fail("Nothing is selected; pass ids.");
3106
4008
  return list2;
3107
4009
  };
3108
- const enabledFor = (_, params) => _optionalChain([params, 'optionalAccess', _95 => _95.ids, 'optionalAccess', _96 => _96.length]) ? true : hasSelection();
4010
+ const enabledFor = (_, params) => _optionalChain([params, 'optionalAccess', _125 => _125.ids, 'optionalAccess', _126 => _126.length]) ? true : hasSelection();
3109
4011
  const list = () => {
3110
4012
  const state = controller.getState();
3111
4013
  const out = [];
@@ -3192,7 +4094,7 @@ function createVectorCommands(controller, options = {}) {
3192
4094
  run: (_ctx, params) => {
3193
4095
  const id = controller.addPath(params.d, { ...pickStyle(params), ...params.name ? { name: params.name } : {} });
3194
4096
  const node = controller.getNode(id);
3195
- if (_optionalChain([node, 'optionalAccess', _97 => _97.type]) === "path" && !node.subpaths.length) {
4097
+ if (_optionalChain([node, 'optionalAccess', _127 => _127.type]) === "path" && !node.subpaths.length) {
3196
4098
  controller.undo();
3197
4099
  fail("The path data has no points.");
3198
4100
  }
@@ -3325,7 +4227,7 @@ function createVectorCommands(controller, options = {}) {
3325
4227
  enabled: enabledFor,
3326
4228
  undoable: true,
3327
4229
  run: (_ctx, params) => {
3328
- const id = controller.group({ ids: ids(params), name: _optionalChain([params, 'optionalAccess', _98 => _98.name]) });
4230
+ const id = controller.group({ ids: ids(params), name: _optionalChain([params, 'optionalAccess', _128 => _128.name]) });
3329
4231
  if (!id) fail("Only nodes that share a parent can be grouped.");
3330
4232
  return { id };
3331
4233
  }
@@ -3364,7 +4266,7 @@ function createVectorCommands(controller, options = {}) {
3364
4266
  keywords: ["download", "save", "markup"],
3365
4267
  description: "Returns the document as SVG markup.",
3366
4268
  params: { type: "object", properties: { precision: { type: "integer", minimum: 0, maximum: 8 } } },
3367
- run: (_ctx, params) => ({ svg: controller.toSvg({ precision: _optionalChain([params, 'optionalAccess', _99 => _99.precision]) }) })
4269
+ run: (_ctx, params) => ({ svg: controller.toSvg({ precision: _optionalChain([params, 'optionalAccess', _129 => _129.precision]) }) })
3368
4270
  },
3369
4271
  {
3370
4272
  ...base,
@@ -3467,5 +4369,9 @@ function createVectorCommands(controller, options = {}) {
3467
4369
 
3468
4370
 
3469
4371
 
3470
- exports.IDENTITY_MATRIX = IDENTITY_MATRIX; exports.KAPPA = KAPPA; exports.VECTOR_CLIPBOARD_TYPE = VECTOR_CLIPBOARD_TYPE; 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.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.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;
4372
+
4373
+
4374
+
4375
+
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;
3471
4377
  //# sourceMappingURL=index.cjs.map