@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.
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/dist/{bezier-DJPYiVbA.d.ts → bezier-BvqVmWMg.d.ts} +1 -1
- package/dist/{bezier-6KPx3w7Z.d.cts → bezier-D8bsO-L0.d.cts} +1 -1
- package/dist/canvas/index.cjs +93 -91
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +51 -3
- package/dist/canvas/index.d.ts +51 -3
- package/dist/canvas/index.js +3 -1
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-GQTXV2EQ.js → chunk-RK6HUKDI.js} +20 -1
- package/dist/{chunk-GQTXV2EQ.js.map → chunk-RK6HUKDI.js.map} +1 -1
- package/dist/{chunk-M4XE4UMT.cjs → chunk-UG5DVXJK.cjs} +21 -2
- package/dist/chunk-UG5DVXJK.cjs.map +1 -0
- package/dist/vector/index.cjs +1029 -123
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +266 -4
- package/dist/vector/index.d.ts +266 -4
- package/dist/vector/index.js +939 -33
- package/dist/vector/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-M4XE4UMT.cjs.map +0 -1
package/dist/vector/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 =
|
|
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 :
|
|
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 =
|
|
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) &&
|
|
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
|
|
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 =
|
|
259
|
+
const hit = _chunkUG5DVXJKcjs.segmentNearest.call(void 0, seg, [p.x, p.y]);
|
|
258
260
|
if (!best || hit.distance < best.distance) {
|
|
259
|
-
const pt =
|
|
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] =
|
|
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, "&").replace(/"/g, """)
|
|
|
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:
|
|
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) =>
|
|
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) =>
|
|
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',
|
|
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',
|
|
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',
|
|
906
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
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',
|
|
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 = (
|
|
1803
|
+
var VectorEditorController = (_class2 = class {
|
|
1050
1804
|
|
|
1051
1805
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
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
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
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
|
-
|
|
1074
|
-
|
|
1832
|
+
__init23() {this.committing = false}
|
|
1833
|
+
__init24() {this.revision = 0}
|
|
1075
1834
|
|
|
1076
|
-
constructor(options = {}) {;
|
|
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 = {
|
|
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
|
-
|
|
1863
|
+
__init25() {this.getState = () => this.state}
|
|
1105
1864
|
/** Calls `listener` after every change. Returns an unsubscribe function. */
|
|
1106
|
-
|
|
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',
|
|
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',
|
|
1178
|
-
if (_optionalChain([this, 'access',
|
|
1179
|
-
if (_optionalChain([this, 'access',
|
|
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',
|
|
1184
|
-
return _optionalChain([h, 'optionalAccess',
|
|
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',
|
|
1187
|
-
if (_optionalChain([h, 'optionalAccess',
|
|
1188
|
-
return _optionalChain([h, 'optionalAccess',
|
|
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',
|
|
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',
|
|
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
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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
|
-
|
|
2095
|
+
gridValue(v) {
|
|
1335
2096
|
const g = this.snap.grid;
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
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',
|
|
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',
|
|
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',
|
|
1756
|
-
if (locs.some((l) => (_nullishCoalesce(_optionalChain([l, 'access',
|
|
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',
|
|
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 =
|
|
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 :
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
3278
|
+
const guides = this.guides;
|
|
2461
3279
|
this.hover = hover;
|
|
2462
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
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
|
-
|
|
2545
|
-
|
|
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',
|
|
2574
|
-
|
|
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',
|
|
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',
|
|
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 =
|
|
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',
|
|
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',
|
|
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
|
-
},
|
|
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',
|
|
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 }) =>
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
3015
|
-
else if (_optionalChain([it, 'optionalAccess',
|
|
3016
|
-
else if (_optionalChain([it, 'optionalAccess',
|
|
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
|
-
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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
|
-
|
|
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
|