@pixerate/editor 0.32.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 +1339 -205
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +408 -27
- package/dist/vector/index.d.ts +408 -27
- package/dist/vector/index.js +1256 -122
- 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];
|
|
@@ -189,9 +191,20 @@ function collectPathNodes(node) {
|
|
|
189
191
|
});
|
|
190
192
|
return out;
|
|
191
193
|
}
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
function nodeMatrix(node) {
|
|
195
|
+
return _nullishCoalesce(node.transform, () => ( IDENTITY_MATRIX));
|
|
196
|
+
}
|
|
197
|
+
function vectorPathWorldBezier(node, parent = IDENTITY_MATRIX) {
|
|
198
|
+
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
199
|
+
const path = subpathsToBezier(node.subpaths);
|
|
200
|
+
return isIdentityMatrix(m) ? path : _chunkUG5DVXJKcjs.transformBezierPath.call(void 0, path, m);
|
|
201
|
+
}
|
|
202
|
+
function vectorNodeBounds(node, parent = IDENTITY_MATRIX) {
|
|
203
|
+
if (node.type === "group") {
|
|
204
|
+
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
205
|
+
return unionBounds(node.children.map((c) => vectorNodeBounds(c, m)));
|
|
206
|
+
}
|
|
207
|
+
const b = _chunkUG5DVXJKcjs.bezierPathBounds.call(void 0, vectorPathWorldBezier(node, parent));
|
|
195
208
|
if (!b) return null;
|
|
196
209
|
return { x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY };
|
|
197
210
|
}
|
|
@@ -216,15 +229,18 @@ function boundsIntersect(a, b) {
|
|
|
216
229
|
function boundsFromPoints(a, b) {
|
|
217
230
|
return { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), width: Math.abs(b.x - a.x), height: Math.abs(b.y - a.y) };
|
|
218
231
|
}
|
|
219
|
-
function hitTestVectorNode(node, p, tolerance = 0) {
|
|
232
|
+
function hitTestVectorNode(node, p, tolerance = 0, parent = IDENTITY_MATRIX) {
|
|
220
233
|
if (node.visible === false) return false;
|
|
221
|
-
if (node.type === "group")
|
|
222
|
-
|
|
234
|
+
if (node.type === "group") {
|
|
235
|
+
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
236
|
+
return node.children.some((c) => hitTestVectorNode(c, p, tolerance, m));
|
|
237
|
+
}
|
|
238
|
+
const path = vectorPathWorldBezier(node, parent);
|
|
223
239
|
if (path.length === 0) return false;
|
|
224
240
|
const point = [p.x, p.y];
|
|
225
|
-
if (hasPaint(node.fill) &&
|
|
241
|
+
if (hasPaint(node.fill) && _chunkUG5DVXJKcjs.pointInBezierPath.call(void 0, path, point, _nullishCoalesce(node.fillRule, () => ( "nonzero")))) return true;
|
|
226
242
|
const reach = (hasPaint(node.stroke) ? (_nullishCoalesce(node.strokeWidth, () => ( 1))) / 2 : 0) + tolerance;
|
|
227
|
-
return
|
|
243
|
+
return _chunkUG5DVXJKcjs.distanceToBezierPath.call(void 0, path, point) <= reach;
|
|
228
244
|
}
|
|
229
245
|
function hitTestVectorNodes(nodes, p, tolerance = 0) {
|
|
230
246
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
@@ -240,9 +256,9 @@ function nearestSegment(node, p) {
|
|
|
240
256
|
const count = subpathSegmentCount(sp);
|
|
241
257
|
for (let i = 0; i < count; i++) {
|
|
242
258
|
const seg = subpathSegment(sp, i);
|
|
243
|
-
const hit =
|
|
259
|
+
const hit = _chunkUG5DVXJKcjs.segmentNearest.call(void 0, seg, [p.x, p.y]);
|
|
244
260
|
if (!best || hit.distance < best.distance) {
|
|
245
|
-
const pt =
|
|
261
|
+
const pt = _chunkUG5DVXJKcjs.segmentPoint.call(void 0, seg, hit.t);
|
|
246
262
|
best = { subpath: s, segment: i, t: hit.t, point: { x: pt[0], y: pt[1] }, distance: hit.distance };
|
|
247
263
|
}
|
|
248
264
|
}
|
|
@@ -268,7 +284,38 @@ function rotateMatrix(degrees, origin = { x: 0, y: 0 }) {
|
|
|
268
284
|
function applyMatrix(m, p) {
|
|
269
285
|
return { x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] };
|
|
270
286
|
}
|
|
271
|
-
function
|
|
287
|
+
function isIdentityMatrix(m, eps = 1e-12) {
|
|
288
|
+
return Math.abs(m[0] - 1) < eps && Math.abs(m[1]) < eps && Math.abs(m[2]) < eps && Math.abs(m[3] - 1) < eps && Math.abs(m[4]) < eps && Math.abs(m[5]) < eps;
|
|
289
|
+
}
|
|
290
|
+
function invertMatrix(m) {
|
|
291
|
+
const [a, b, c, d, e, f] = m;
|
|
292
|
+
const det = a * d - b * c;
|
|
293
|
+
if (!det || !Number.isFinite(det)) return IDENTITY_MATRIX;
|
|
294
|
+
return [d / det, -b / det, -c / det, a / det, (c * f - d * e) / det, (b * e - a * f) / det];
|
|
295
|
+
}
|
|
296
|
+
function applyMatrixLinear(m, v) {
|
|
297
|
+
return { x: m[0] * v.x + m[2] * v.y, y: m[1] * v.x + m[3] * v.y };
|
|
298
|
+
}
|
|
299
|
+
function matrixRotation(m) {
|
|
300
|
+
const [a, b, c, d] = m;
|
|
301
|
+
const mirrored = a * d - b * c < 0;
|
|
302
|
+
const deg = (mirrored ? Math.atan2(-b - c, d - a) : Math.atan2(b - c, a + d)) * 180 / Math.PI;
|
|
303
|
+
return Math.abs(deg) < 1e-9 ? 0 : deg;
|
|
304
|
+
}
|
|
305
|
+
function decomposeRigid(m) {
|
|
306
|
+
const [a, b, c, d, e, f] = m;
|
|
307
|
+
const deg = matrixRotation(m);
|
|
308
|
+
if (deg === 0) return { rigid: IDENTITY_MATRIX, residual: m };
|
|
309
|
+
const r = deg * Math.PI / 180;
|
|
310
|
+
const cos = Math.cos(r);
|
|
311
|
+
const sin = Math.sin(r);
|
|
312
|
+
return {
|
|
313
|
+
rigid: [cos, sin, -sin, cos, e, f],
|
|
314
|
+
residual: [cos * a + sin * b, -sin * a + cos * b, cos * c + sin * d, -sin * c + cos * d, 0, 0]
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function bakeIntoGeometry(node, m) {
|
|
318
|
+
if (isIdentityMatrix(m)) return node;
|
|
272
319
|
if (node.type === "group") return { ...node, children: node.children.map((c) => transformVectorNode(c, m)) };
|
|
273
320
|
const path = node;
|
|
274
321
|
return {
|
|
@@ -286,6 +333,36 @@ function transformVectorNode(node, m) {
|
|
|
286
333
|
}))
|
|
287
334
|
};
|
|
288
335
|
}
|
|
336
|
+
function transformVectorNode(node, m) {
|
|
337
|
+
const total = multiplyMatrices(nodeMatrix(node), m);
|
|
338
|
+
const { rigid, residual } = decomposeRigid(total);
|
|
339
|
+
const { transform: _old, ...rest } = node;
|
|
340
|
+
const baked = bakeIntoGeometry(rest, residual);
|
|
341
|
+
return isIdentityMatrix(rigid) ? baked : { ...baked, transform: rigid };
|
|
342
|
+
}
|
|
343
|
+
function vectorNodeRotation(node, parent = IDENTITY_MATRIX) {
|
|
344
|
+
return matrixRotation(multiplyMatrices(nodeMatrix(node), parent));
|
|
345
|
+
}
|
|
346
|
+
function vectorWorldMatrix(nodes, id) {
|
|
347
|
+
const search = (list, parent) => {
|
|
348
|
+
for (const n of list) {
|
|
349
|
+
const m = multiplyMatrices(nodeMatrix(n), parent);
|
|
350
|
+
if (n.id === id) return m;
|
|
351
|
+
if (n.type === "group") {
|
|
352
|
+
const found = search(n.children, m);
|
|
353
|
+
if (found) return found;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
};
|
|
358
|
+
return search(nodes, IDENTITY_MATRIX);
|
|
359
|
+
}
|
|
360
|
+
function vectorParentMatrix(nodes, id) {
|
|
361
|
+
const node = findVectorNode(nodes, id);
|
|
362
|
+
const world = vectorWorldMatrix(nodes, id);
|
|
363
|
+
if (!node || !world) return null;
|
|
364
|
+
return multiplyMatrices(invertMatrix(nodeMatrix(node)), world);
|
|
365
|
+
}
|
|
289
366
|
function translateAnchor(a, dx, dy) {
|
|
290
367
|
const next = cloneAnchor(a);
|
|
291
368
|
next.x += dx;
|
|
@@ -352,7 +429,7 @@ function insertAnchor(sp, segment, t) {
|
|
|
352
429
|
const ia = segment;
|
|
353
430
|
const ib = (segment + 1) % n;
|
|
354
431
|
const seg = subpathSegment(sp, segment);
|
|
355
|
-
const [left, right] =
|
|
432
|
+
const [left, right] = _chunkUG5DVXJKcjs.splitSegment.call(void 0, seg, t);
|
|
356
433
|
const mid = left[left.length - 1];
|
|
357
434
|
const anchor = { x: mid[0], y: mid[1] };
|
|
358
435
|
if (seg.length === 4) {
|
|
@@ -460,7 +537,7 @@ var escapeAttr = (value) => value.replace(/&/g, "&").replace(/"/g, """)
|
|
|
460
537
|
function vectorPaintToSvg(paint, gradientId) {
|
|
461
538
|
if (!hasPaint(paint)) return { value: "none", gradient: null };
|
|
462
539
|
if (typeof paint === "string") return { value: paint, gradient: null };
|
|
463
|
-
return { value: `url(#${gradientId})`, gradient:
|
|
540
|
+
return { value: `url(#${gradientId})`, gradient: _chunkUG5DVXJKcjs.gradientToSvgDef.call(void 0, paint, gradientId) };
|
|
464
541
|
}
|
|
465
542
|
function gradientMarkup(def) {
|
|
466
543
|
const tag = def.type === "radial" ? "radialGradient" : "linearGradient";
|
|
@@ -474,10 +551,11 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
474
551
|
const pretty = options.pretty !== false;
|
|
475
552
|
const defs = [];
|
|
476
553
|
const lines = [];
|
|
477
|
-
const num = (n) =>
|
|
554
|
+
const num = (n) => _chunkUG5DVXJKcjs.formatNumber.call(void 0, n, precision);
|
|
478
555
|
const indent = (depth) => pretty ? " ".repeat(depth) : "";
|
|
479
556
|
const common = (node) => {
|
|
480
557
|
let out2 = ` id="${escapeAttr(node.id)}"`;
|
|
558
|
+
if (node.transform) out2 += ` transform="matrix(${node.transform.map((n) => _chunkUG5DVXJKcjs.formatNumber.call(void 0, n, 6)).join(" ")})"`;
|
|
481
559
|
if (node.opacity !== void 0 && node.opacity < 1) out2 += ` opacity="${num(node.opacity)}"`;
|
|
482
560
|
if (node.visible === false) out2 += ` display="none"`;
|
|
483
561
|
return out2;
|
|
@@ -774,7 +852,759 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
774
852
|
return { width, height, nodes };
|
|
775
853
|
}
|
|
776
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
|
+
|
|
777
1606
|
// src/vector/controller.ts
|
|
1607
|
+
var DEFAULT_VECTOR_SNAP = { grid: 0, objects: true, guides: true, threshold: 5, angles: VECTOR_CONSTRUCTION_ANGLES };
|
|
778
1608
|
var VECTOR_CLIPBOARD_TYPE = "pixerate/vector";
|
|
779
1609
|
var DEFAULT_DEFAULTS = {
|
|
780
1610
|
fill: "#d4d4d8",
|
|
@@ -802,7 +1632,7 @@ function getEditablePathIds(state) {
|
|
|
802
1632
|
}
|
|
803
1633
|
}
|
|
804
1634
|
}
|
|
805
|
-
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");
|
|
806
1636
|
}
|
|
807
1637
|
function getVisibleAnchors(state) {
|
|
808
1638
|
const out = [];
|
|
@@ -817,7 +1647,7 @@ function getVisibleHandles(state) {
|
|
|
817
1647
|
const seen = /* @__PURE__ */ new Set();
|
|
818
1648
|
const push = (node, s, i, handle) => {
|
|
819
1649
|
const sp = node.subpaths[s];
|
|
820
|
-
const a = _optionalChain([sp, 'optionalAccess',
|
|
1650
|
+
const a = _optionalChain([sp, 'optionalAccess', _29 => _29.anchors, 'access', _30 => _30[i]]);
|
|
821
1651
|
if (!a || !a[handle]) return;
|
|
822
1652
|
const key = `${node.id}:${s}:${i}:${handle}`;
|
|
823
1653
|
if (seen.has(key)) return;
|
|
@@ -826,8 +1656,8 @@ function getVisibleHandles(state) {
|
|
|
826
1656
|
};
|
|
827
1657
|
if (state.tool === "pen" && state.pen) {
|
|
828
1658
|
const node = findVectorNode(state.document.nodes, state.pen.nodeId);
|
|
829
|
-
const sp = _optionalChain([node, 'optionalAccess',
|
|
830
|
-
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) {
|
|
831
1661
|
const last = sp.anchors.length - 1;
|
|
832
1662
|
push(node, state.pen.subpath, last, "in");
|
|
833
1663
|
push(node, state.pen.subpath, last, "out");
|
|
@@ -853,8 +1683,26 @@ function getVisibleHandles(state) {
|
|
|
853
1683
|
}
|
|
854
1684
|
function getAnchor(doc, ref) {
|
|
855
1685
|
const node = findVectorNode(doc.nodes, ref.nodeId);
|
|
856
|
-
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;
|
|
1687
|
+
}
|
|
1688
|
+
function getAnchorWorld(doc, ref) {
|
|
1689
|
+
const a = getAnchor(doc, ref);
|
|
1690
|
+
if (!a) return void 0;
|
|
1691
|
+
const m = _nullishCoalesce(vectorWorldMatrix(doc.nodes, ref.nodeId), () => ( IDENTITY_MATRIX));
|
|
1692
|
+
const p = applyMatrix(m, a);
|
|
1693
|
+
return { x: p.x, y: p.y, ...a.in ? { in: applyMatrix(m, a.in) } : {}, ...a.out ? { out: applyMatrix(m, a.out) } : {}, ...a.type ? { type: a.type } : {} };
|
|
857
1694
|
}
|
|
1695
|
+
function getVectorSelectionFrame(doc, ids) {
|
|
1696
|
+
const nodes = ids.map((id) => ({ node: findVectorNode(doc.nodes, id), parent: _nullishCoalesce(vectorParentMatrix(doc.nodes, id), () => ( IDENTITY_MATRIX)) })).filter((n) => n.node);
|
|
1697
|
+
if (!nodes.length) return null;
|
|
1698
|
+
const angles = nodes.map((n) => matrixRotation(multiplyMatrices(nodeMatrix(n.node), n.parent)));
|
|
1699
|
+
const same = angles.every((a) => Math.abs(normalizeAngle(a - angles[0])) < 1e-6);
|
|
1700
|
+
const angle = same ? angles[0] : 0;
|
|
1701
|
+
const unrotate = rotateMatrix(-angle);
|
|
1702
|
+
const b = unionBounds(nodes.map((n) => vectorNodeBounds(n.node, multiplyMatrices(n.parent, unrotate))));
|
|
1703
|
+
return b ? { ...b, angle } : null;
|
|
1704
|
+
}
|
|
1705
|
+
var normalizeAngle = (a) => ((a + 180) % 360 + 360) % 360 - 180;
|
|
858
1706
|
function getTransformHandlePositions(box) {
|
|
859
1707
|
const { x, y, width: w, height: h } = box;
|
|
860
1708
|
return [
|
|
@@ -869,17 +1717,28 @@ function getTransformHandlePositions(box) {
|
|
|
869
1717
|
{ handle: "rotate", x: x + w / 2, y: y - VECTOR_ROTATE_HANDLE_OFFSET }
|
|
870
1718
|
];
|
|
871
1719
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
}
|
|
1720
|
+
function getVectorSelectionBoxGeometry(frame, viewport) {
|
|
1721
|
+
const z = viewport.zoom;
|
|
1722
|
+
const box = { x: frame.x * z + viewport.x, y: frame.y * z + viewport.y, width: frame.width * z, height: frame.height * z };
|
|
1723
|
+
const rot = rotateMatrix(frame.angle, { x: viewport.x, y: viewport.y });
|
|
1724
|
+
const toScreen2 = (p) => frame.angle ? applyMatrix(rot, p) : { x: p.x, y: p.y };
|
|
1725
|
+
const handles = getTransformHandlePositions(box).map((h) => ({ ...h, screen: toScreen2(h) }));
|
|
1726
|
+
const corners = [
|
|
1727
|
+
{ x: box.x, y: box.y },
|
|
1728
|
+
{ x: box.x + box.width, y: box.y },
|
|
1729
|
+
{ x: box.x + box.width, y: box.y + box.height },
|
|
1730
|
+
{ x: box.x, y: box.y + box.height }
|
|
1731
|
+
].map(toScreen2);
|
|
1732
|
+
const r = (n) => Math.round(n * 1e3) / 1e3;
|
|
1733
|
+
return { box, angle: frame.angle, transform: frame.angle ? `rotate(${r(frame.angle)} ${r(viewport.x)} ${r(viewport.y)})` : "", handles, corners };
|
|
1734
|
+
}
|
|
1735
|
+
var RESIZE_CURSOR_ORDER = ["ew-resize", "nwse-resize", "ns-resize", "nesw-resize"];
|
|
1736
|
+
var HANDLE_ANGLES = { e: 0, se: 45, s: 90, sw: 135, w: 180, nw: 225, n: 270, ne: 315 };
|
|
1737
|
+
function vectorResizeCursor(handle, angle = 0) {
|
|
1738
|
+
if (handle === "rotate") return "grab";
|
|
1739
|
+
const a = ((HANDLE_ANGLES[handle] + angle) % 180 + 180) % 180;
|
|
1740
|
+
return RESIZE_CURSOR_ORDER[Math.round(a / 45) % 4];
|
|
1741
|
+
}
|
|
883
1742
|
var TOOL_LABELS = {
|
|
884
1743
|
select: "Select",
|
|
885
1744
|
direct: "Direct Select",
|
|
@@ -938,42 +1797,48 @@ function mapTree(nodes, fn) {
|
|
|
938
1797
|
}
|
|
939
1798
|
return changed ? out : nodes;
|
|
940
1799
|
}
|
|
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;
|
|
941
1801
|
var idCounter = 0;
|
|
942
1802
|
var defaultCreateId = () => `vn-${Date.now().toString(36)}${(++idCounter).toString(36)}`;
|
|
943
|
-
var VectorEditorController = (
|
|
1803
|
+
var VectorEditorController = (_class2 = class {
|
|
944
1804
|
|
|
945
1805
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1806
|
+
__init7() {this.selection = []}
|
|
1807
|
+
__init8() {this.anchors = []}
|
|
1808
|
+
__init9() {this.editingId = null}
|
|
1809
|
+
__init10() {this.pen = null}
|
|
950
1810
|
|
|
951
1811
|
|
|
952
1812
|
|
|
953
1813
|
|
|
954
1814
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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}
|
|
961
1826
|
|
|
962
1827
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1828
|
+
__init20() {this.listeners = /* @__PURE__ */ new Set()}
|
|
1829
|
+
__init21() {this.cleanups = []}
|
|
1830
|
+
__init22() {this.applying = false}
|
|
966
1831
|
/** Set while a commit records a change that was already applied, so `restore` still reports it. */
|
|
967
|
-
|
|
968
|
-
|
|
1832
|
+
__init23() {this.committing = false}
|
|
1833
|
+
__init24() {this.revision = 0}
|
|
969
1834
|
|
|
970
|
-
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);
|
|
971
1836
|
this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkQKLRAA4Tcjs.HistoryManager)()));
|
|
972
1837
|
this.doc = options.document ? cloneVectorDocument(options.document) : { width: 800, height: 600, nodes: [] };
|
|
973
1838
|
this.tool = _nullishCoalesce(options.tool, () => ( "select"));
|
|
974
1839
|
this.defaults = { ...DEFAULT_DEFAULTS, ..._nullishCoalesce(options.defaults, () => ( {})) };
|
|
975
1840
|
this.viewport = { x: 0, y: 0, zoom: 1, ..._nullishCoalesce(options.viewport, () => ( {})) };
|
|
976
|
-
this.snap = {
|
|
1841
|
+
this.snap = { ...DEFAULT_VECTOR_SNAP, ..._nullishCoalesce(options.snap, () => ( {})) };
|
|
977
1842
|
this.hitRadius = _nullishCoalesce(options.hitRadius, () => ( 6));
|
|
978
1843
|
this.createId = _nullishCoalesce(options.createId, () => ( defaultCreateId));
|
|
979
1844
|
this.onChange = options.onChange;
|
|
@@ -995,9 +1860,9 @@ var VectorEditorController = (_class = class {
|
|
|
995
1860
|
// State
|
|
996
1861
|
// -------------------------------------------------------------------------
|
|
997
1862
|
/** The current snapshot (stable until the next change). */
|
|
998
|
-
|
|
1863
|
+
__init25() {this.getState = () => this.state}
|
|
999
1864
|
/** Calls `listener` after every change. Returns an unsubscribe function. */
|
|
1000
|
-
|
|
1865
|
+
__init26() {this.subscribe = (listener) => {
|
|
1001
1866
|
this.listeners.add(listener);
|
|
1002
1867
|
return () => {
|
|
1003
1868
|
this.listeners.delete(listener);
|
|
@@ -1018,9 +1883,11 @@ var VectorEditorController = (_class = class {
|
|
|
1018
1883
|
selectedAnchors: this.anchors,
|
|
1019
1884
|
editingId: this.editingId,
|
|
1020
1885
|
selectionBounds: this.boundsOf(this.selection),
|
|
1886
|
+
selectionFrame: this.frameOf(this.selectedRoots()),
|
|
1021
1887
|
viewport: this.viewport,
|
|
1022
1888
|
defaults: this.defaults,
|
|
1023
1889
|
snap: this.snap,
|
|
1890
|
+
guides: this.guides,
|
|
1024
1891
|
hitRadius: this.hitRadius,
|
|
1025
1892
|
hover: this.hover,
|
|
1026
1893
|
cursor: this.cursorFor(),
|
|
@@ -1037,7 +1904,7 @@ var VectorEditorController = (_class = class {
|
|
|
1037
1904
|
notify(docChanged = false) {
|
|
1038
1905
|
this.revision++;
|
|
1039
1906
|
this.state = this.computeState();
|
|
1040
|
-
if (docChanged) _optionalChain([this, 'access',
|
|
1907
|
+
if (docChanged) _optionalChain([this, 'access', _38 => _38.onChange, 'optionalCall', _39 => _39(this.doc)]);
|
|
1041
1908
|
const state = this.state;
|
|
1042
1909
|
this.listeners.forEach((listener) => {
|
|
1043
1910
|
try {
|
|
@@ -1048,23 +1915,39 @@ var VectorEditorController = (_class = class {
|
|
|
1048
1915
|
});
|
|
1049
1916
|
}
|
|
1050
1917
|
boundsOf(ids) {
|
|
1051
|
-
return unionBounds(
|
|
1918
|
+
return unionBounds(
|
|
1919
|
+
ids.map((id) => {
|
|
1920
|
+
const n = this.getNode(id);
|
|
1921
|
+
return n ? vectorNodeBounds(n, _nullishCoalesce(vectorParentMatrix(this.doc.nodes, id), () => ( IDENTITY_MATRIX))) : null;
|
|
1922
|
+
})
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
frameOf(ids) {
|
|
1926
|
+
return getVectorSelectionFrame(this.doc, ids);
|
|
1927
|
+
}
|
|
1928
|
+
/** Local → document matrix of a node in `doc` (default: the current document). */
|
|
1929
|
+
worldOf(id, doc = this.doc) {
|
|
1930
|
+
return _nullishCoalesce(vectorWorldMatrix(doc.nodes, id), () => ( IDENTITY_MATRIX));
|
|
1931
|
+
}
|
|
1932
|
+
/** A document point in node `id`'s own space. */
|
|
1933
|
+
toLocal(id, p, doc = this.doc) {
|
|
1934
|
+
return applyMatrix(invertMatrix(this.worldOf(id, doc)), p);
|
|
1052
1935
|
}
|
|
1053
1936
|
cursorFor() {
|
|
1054
|
-
if (_optionalChain([this, 'access',
|
|
1055
|
-
if (_optionalChain([this, 'access',
|
|
1056
|
-
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";
|
|
1057
1940
|
const h = this.hover;
|
|
1058
1941
|
switch (this.tool) {
|
|
1059
1942
|
case "select":
|
|
1060
|
-
if (_optionalChain([h, 'optionalAccess',
|
|
1061
|
-
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";
|
|
1062
1945
|
case "direct":
|
|
1063
|
-
if (_optionalChain([h, 'optionalAccess',
|
|
1064
|
-
if (_optionalChain([h, 'optionalAccess',
|
|
1065
|
-
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";
|
|
1066
1949
|
case "pen":
|
|
1067
|
-
return _optionalChain([h, 'optionalAccess',
|
|
1950
|
+
return _optionalChain([h, 'optionalAccess', _55 => _55.anchor]) ? "pointer" : "crosshair";
|
|
1068
1951
|
default:
|
|
1069
1952
|
return "crosshair";
|
|
1070
1953
|
}
|
|
@@ -1084,7 +1967,7 @@ var VectorEditorController = (_class = class {
|
|
|
1084
1967
|
this.selection = s.selection.filter((id) => this.getNode(id));
|
|
1085
1968
|
this.anchors = s.anchors.filter((r) => getAnchor(this.doc, r));
|
|
1086
1969
|
this.editingId = s.editingId && this.getNode(s.editingId) ? s.editingId : null;
|
|
1087
|
-
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;
|
|
1088
1971
|
} finally {
|
|
1089
1972
|
this.applying = false;
|
|
1090
1973
|
}
|
|
@@ -1189,6 +2072,7 @@ var VectorEditorController = (_class = class {
|
|
|
1189
2072
|
this.cancelDrag();
|
|
1190
2073
|
this.tool = tool;
|
|
1191
2074
|
this.hover = null;
|
|
2075
|
+
this.guides = null;
|
|
1192
2076
|
if (tool !== "direct") {
|
|
1193
2077
|
this.anchors = [];
|
|
1194
2078
|
this.editingId = null;
|
|
@@ -1201,18 +2085,74 @@ var VectorEditorController = (_class = class {
|
|
|
1201
2085
|
}
|
|
1202
2086
|
setSnap(snap) {
|
|
1203
2087
|
this.snap = { ...this.snap, ...snap };
|
|
2088
|
+
if (!this.snap.guides || !this.snap.objects) this.guides = null;
|
|
1204
2089
|
this.notify();
|
|
1205
2090
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
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 });
|
|
1210
2094
|
}
|
|
1211
|
-
|
|
2095
|
+
gridValue(v) {
|
|
1212
2096
|
const g = this.snap.grid;
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
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;
|
|
1216
2156
|
}
|
|
1217
2157
|
// -------------------------------------------------------------------------
|
|
1218
2158
|
// Document
|
|
@@ -1443,10 +2383,10 @@ var VectorEditorController = (_class = class {
|
|
|
1443
2383
|
return this.selection.filter((id) => {
|
|
1444
2384
|
let covered = false;
|
|
1445
2385
|
const loc = locateVectorNode(this.doc.nodes, id);
|
|
1446
|
-
let parent = _nullishCoalesce(_optionalChain([loc, 'optionalAccess',
|
|
2386
|
+
let parent = _nullishCoalesce(_optionalChain([loc, 'optionalAccess', _69 => _69.parent]), () => ( null));
|
|
1447
2387
|
while (parent) {
|
|
1448
2388
|
if (this.selection.includes(parent.id)) covered = true;
|
|
1449
|
-
parent = _nullishCoalesce(_optionalChain([locateVectorNode, 'call',
|
|
2389
|
+
parent = _nullishCoalesce(_optionalChain([locateVectorNode, 'call', _70 => _70(this.doc.nodes, parent.id), 'optionalAccess', _71 => _71.parent]), () => ( null));
|
|
1450
2390
|
}
|
|
1451
2391
|
return !covered;
|
|
1452
2392
|
});
|
|
@@ -1454,6 +2394,11 @@ var VectorEditorController = (_class = class {
|
|
|
1454
2394
|
// -------------------------------------------------------------------------
|
|
1455
2395
|
// Transforms
|
|
1456
2396
|
// -------------------------------------------------------------------------
|
|
2397
|
+
/** `m`, given in a frame rotated by `angle`, as a document-space matrix. */
|
|
2398
|
+
inFrame(angle, m) {
|
|
2399
|
+
if (!angle) return m;
|
|
2400
|
+
return multiplyMatrices(multiplyMatrices(rotateMatrix(-angle), m), rotateMatrix(angle));
|
|
2401
|
+
}
|
|
1457
2402
|
/** Applies a matrix to the selected (or given) nodes as one undo step. */
|
|
1458
2403
|
transform(matrix, options = {}) {
|
|
1459
2404
|
const ids = new Set(_nullishCoalesce(options.ids, () => ( this.selectedRoots())));
|
|
@@ -1463,8 +2408,25 @@ var VectorEditorController = (_class = class {
|
|
|
1463
2408
|
});
|
|
1464
2409
|
return { ok: true };
|
|
1465
2410
|
}
|
|
2411
|
+
/** `doc` with nodes `ids` moved by `m`, given in document space. */
|
|
1466
2412
|
transformed(doc, ids, m) {
|
|
1467
|
-
const
|
|
2413
|
+
const visit = (list, parent) => {
|
|
2414
|
+
let changed = false;
|
|
2415
|
+
const out = list.map((n) => {
|
|
2416
|
+
let next = n;
|
|
2417
|
+
if (ids.has(n.id) && !n.locked) {
|
|
2418
|
+
const local = isRoot(parent) ? m : multiplyMatrices(multiplyMatrices(parent, m), invertMatrix(parent));
|
|
2419
|
+
next = transformVectorNode(n, local);
|
|
2420
|
+
} else if (n.type === "group") {
|
|
2421
|
+
const children = visit(n.children, multiplyMatrices(nodeMatrix(n), parent));
|
|
2422
|
+
if (children !== n.children) next = { ...n, children };
|
|
2423
|
+
}
|
|
2424
|
+
if (next !== n) changed = true;
|
|
2425
|
+
return next;
|
|
2426
|
+
});
|
|
2427
|
+
return changed ? out : list;
|
|
2428
|
+
};
|
|
2429
|
+
const nodes = visit(doc.nodes, IDENTITY_MATRIX);
|
|
1468
2430
|
return nodes === doc.nodes ? doc : { ...doc, nodes };
|
|
1469
2431
|
}
|
|
1470
2432
|
translate(dx, dy, options = {}) {
|
|
@@ -1476,26 +2438,48 @@ var VectorEditorController = (_class = class {
|
|
|
1476
2438
|
}
|
|
1477
2439
|
return this.transform(translateMatrix(dx, dy), { ids: options.ids, label: "Move" });
|
|
1478
2440
|
}
|
|
1479
|
-
/**
|
|
2441
|
+
/** The selection frame's centre in document space. */
|
|
2442
|
+
frameCenter(frame) {
|
|
2443
|
+
return applyMatrix(rotateMatrix(frame.angle), { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 });
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Scales along the selection box's own axes (rotated with the selection when its nodes
|
|
2447
|
+
* share a rotation) around `origin` (default: the box centre, in document space).
|
|
2448
|
+
*/
|
|
1480
2449
|
scale(sx, sy = sx, options = {}) {
|
|
1481
|
-
const
|
|
1482
|
-
if (!
|
|
1483
|
-
const origin = _nullishCoalesce(options.origin, () => (
|
|
1484
|
-
return this.transform(scaleMatrix(sx, sy, origin), { ids: options.ids, label: "Scale" });
|
|
2450
|
+
const frame = this.frameOf(_nullishCoalesce(options.ids, () => ( this.selectedRoots())));
|
|
2451
|
+
if (!frame) return { ok: false, message: "Nothing is selected." };
|
|
2452
|
+
const origin = applyMatrix(rotateMatrix(-frame.angle), _nullishCoalesce(options.origin, () => ( this.frameCenter(frame))));
|
|
2453
|
+
return this.transform(this.inFrame(frame.angle, scaleMatrix(sx, sy, origin)), { ids: options.ids, label: "Scale" });
|
|
1485
2454
|
}
|
|
1486
2455
|
/** Rotates clockwise by `degrees` around `origin` (default: the selection centre). */
|
|
1487
2456
|
rotate(degrees, options = {}) {
|
|
1488
|
-
const
|
|
1489
|
-
if (!
|
|
1490
|
-
const origin = _nullishCoalesce(options.origin, () => (
|
|
2457
|
+
const frame = this.frameOf(_nullishCoalesce(options.ids, () => ( this.selectedRoots())));
|
|
2458
|
+
if (!frame) return { ok: false, message: "Nothing is selected." };
|
|
2459
|
+
const origin = _nullishCoalesce(options.origin, () => ( this.frameCenter(frame)));
|
|
1491
2460
|
return this.transform(rotateMatrix(degrees, origin), { ids: options.ids, label: "Rotate" });
|
|
1492
2461
|
}
|
|
2462
|
+
/** Sets each selected (or given) node's rotation to `degrees`, turning it around its own centre. */
|
|
2463
|
+
setRotation(degrees, options = {}) {
|
|
2464
|
+
const ids = _nullishCoalesce(options.ids, () => ( this.selectedRoots()));
|
|
2465
|
+
if (!ids.length) return { ok: false, message: "Nothing is selected." };
|
|
2466
|
+
this.change("Set Rotation", () => {
|
|
2467
|
+
for (const id of ids) {
|
|
2468
|
+
const frame = this.frameOf([id]);
|
|
2469
|
+
if (!frame) continue;
|
|
2470
|
+
const delta = normalizeAngle(degrees - frame.angle);
|
|
2471
|
+
if (Math.abs(delta) > 1e-9) this.doc = this.transformed(this.doc, /* @__PURE__ */ new Set([id]), rotateMatrix(delta, this.frameCenter(frame)));
|
|
2472
|
+
}
|
|
2473
|
+
});
|
|
2474
|
+
return { ok: true };
|
|
2475
|
+
}
|
|
2476
|
+
/** Mirrors along the selection box's own axes. */
|
|
1493
2477
|
flip(axis, options = {}) {
|
|
1494
|
-
const
|
|
1495
|
-
if (!
|
|
1496
|
-
const c = { x:
|
|
2478
|
+
const frame = this.frameOf(_nullishCoalesce(options.ids, () => ( this.selectedRoots())));
|
|
2479
|
+
if (!frame) return { ok: false, message: "Nothing is selected." };
|
|
2480
|
+
const c = { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 };
|
|
1497
2481
|
const m = axis === "horizontal" ? scaleMatrix(-1, 1, c) : scaleMatrix(1, -1, c);
|
|
1498
|
-
return this.transform(m, { ids: options.ids, label: axis === "horizontal" ? "Flip Horizontal" : "Flip Vertical" });
|
|
2482
|
+
return this.transform(this.inFrame(frame.angle, m), { ids: options.ids, label: axis === "horizontal" ? "Flip Horizontal" : "Flip Vertical" });
|
|
1499
2483
|
}
|
|
1500
2484
|
/**
|
|
1501
2485
|
* Aligns the selected nodes to their combined bounds, or to the artboard when one node is
|
|
@@ -1585,8 +2569,8 @@ var VectorEditorController = (_class = class {
|
|
|
1585
2569
|
if (!ids.length) return null;
|
|
1586
2570
|
const locs = ids.map((id2) => locateVectorNode(this.doc.nodes, id2));
|
|
1587
2571
|
if (locs.some((l) => !l)) return null;
|
|
1588
|
-
const parent = _nullishCoalesce(_optionalChain([locs, 'access',
|
|
1589
|
-
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;
|
|
1590
2574
|
const id = this.newId();
|
|
1591
2575
|
this.change("Group", () => {
|
|
1592
2576
|
const set = new Set(ids);
|
|
@@ -1609,7 +2593,7 @@ var VectorEditorController = (_class = class {
|
|
|
1609
2593
|
}
|
|
1610
2594
|
/** Replaces selected groups with their children. */
|
|
1611
2595
|
ungroup(options = {}) {
|
|
1612
|
-
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");
|
|
1613
2597
|
if (!groups.length) return { ok: false, message: "Select a group to ungroup." };
|
|
1614
2598
|
const set = new Set(groups.map((g) => g.id));
|
|
1615
2599
|
this.change("Ungroup", () => {
|
|
@@ -1620,7 +2604,10 @@ var VectorEditorController = (_class = class {
|
|
|
1620
2604
|
if (set.has(n.id) && n.type === "group") {
|
|
1621
2605
|
changed = true;
|
|
1622
2606
|
const opacity = n.opacity;
|
|
1623
|
-
|
|
2607
|
+
const m = nodeMatrix(n);
|
|
2608
|
+
out.push(
|
|
2609
|
+
...n.children.map((c) => n.transform ? transformVectorNode(c, m) : c).map((c) => opacity !== void 0 && opacity < 1 ? { ...c, opacity: (_nullishCoalesce(c.opacity, () => ( 1))) * opacity } : c)
|
|
2610
|
+
);
|
|
1624
2611
|
} else if (n.type === "group") {
|
|
1625
2612
|
const children = flatten(n.children);
|
|
1626
2613
|
if (children !== n.children) changed = true;
|
|
@@ -1666,17 +2653,24 @@ var VectorEditorController = (_class = class {
|
|
|
1666
2653
|
});
|
|
1667
2654
|
const paths = [...new Set(order)].map((id2) => this.getNode(id2));
|
|
1668
2655
|
if (paths.length < 2) return null;
|
|
1669
|
-
const result =
|
|
2656
|
+
const result = _chunkUG5DVXJKcjs.pathBoolean.call(void 0,
|
|
1670
2657
|
BOOLEAN_OPS[op],
|
|
1671
|
-
paths.map((p) => ({
|
|
2658
|
+
paths.map((p) => ({
|
|
2659
|
+
path: vectorPathWorldBezier(
|
|
2660
|
+
{ ...p, subpaths: p.subpaths.filter((s) => s.anchors.length > 1).map((s) => ({ ...s, closed: true })) },
|
|
2661
|
+
_nullishCoalesce(vectorParentMatrix(this.doc.nodes, p.id), () => ( IDENTITY_MATRIX))
|
|
2662
|
+
),
|
|
2663
|
+
fillRule: _nullishCoalesce(p.fillRule, () => ( "nonzero"))
|
|
2664
|
+
}))
|
|
1672
2665
|
);
|
|
1673
|
-
const subpaths = bezierToSubpaths(result.path);
|
|
1674
|
-
if (result.error || !subpaths.length) return null;
|
|
1675
2666
|
const base = paths[0];
|
|
2667
|
+
const anchorRoot = _nullishCoalesce(roots.find((r) => findVectorNode([this.getNode(r)], base.id)), () => ( base.id));
|
|
2668
|
+
const subpaths = bezierToSubpaths(this.intoParentSpace(anchorRoot, result.path));
|
|
2669
|
+
if (result.error || !subpaths.length) return null;
|
|
1676
2670
|
const id = this.newId();
|
|
1677
|
-
const
|
|
2671
|
+
const { transform: _t, ...baseRest } = cloneVectorNode(base);
|
|
2672
|
+
const node = { ...baseRest, id, name: BOOLEAN_LABELS[op], subpaths };
|
|
1678
2673
|
const drop = /* @__PURE__ */ new Set([...paths.map((p) => p.id), ...roots]);
|
|
1679
|
-
const anchorRoot = _nullishCoalesce(roots.find((r) => findVectorNode([this.getNode(r)], base.id)), () => ( base.id));
|
|
1680
2674
|
this.change(BOOLEAN_LABELS[op], () => {
|
|
1681
2675
|
this.setDoc(mapTree(this.doc.nodes, (n) => n.id === anchorRoot ? node : drop.has(n.id) ? null : n));
|
|
1682
2676
|
this.selection = [id];
|
|
@@ -1694,9 +2688,11 @@ var VectorEditorController = (_class = class {
|
|
|
1694
2688
|
});
|
|
1695
2689
|
if (paths.length < 2) return null;
|
|
1696
2690
|
const id = this.newId();
|
|
1697
|
-
const node = { ...cloneVectorNode(paths[0]), id, name: "Compound Path", subpaths: paths.flatMap((p) => cloneVectorNode(p).subpaths) };
|
|
1698
|
-
const drop = /* @__PURE__ */ new Set([...paths.map((p) => p.id), ...roots]);
|
|
1699
2691
|
const first = _nullishCoalesce(roots.find((r) => findVectorNode([this.getNode(r)], paths[0].id)), () => ( paths[0].id));
|
|
2692
|
+
const subpaths = paths.flatMap((p) => bezierToSubpaths(this.intoParentSpace(first, vectorPathWorldBezier(p, _nullishCoalesce(vectorParentMatrix(this.doc.nodes, p.id), () => ( IDENTITY_MATRIX))))));
|
|
2693
|
+
const { transform: _t, ...baseRest } = cloneVectorNode(paths[0]);
|
|
2694
|
+
const node = { ...baseRest, id, name: "Compound Path", subpaths };
|
|
2695
|
+
const drop = /* @__PURE__ */ new Set([...paths.map((p) => p.id), ...roots]);
|
|
1700
2696
|
this.change("Combine", () => {
|
|
1701
2697
|
this.setDoc(mapTree(this.doc.nodes, (n) => n.id === first ? node : drop.has(n.id) ? null : n));
|
|
1702
2698
|
this.selection = [id];
|
|
@@ -1704,9 +2700,14 @@ var VectorEditorController = (_class = class {
|
|
|
1704
2700
|
});
|
|
1705
2701
|
return id;
|
|
1706
2702
|
}
|
|
2703
|
+
/** A document-space path mapped into the parent space of node `id`. */
|
|
2704
|
+
intoParentSpace(id, path) {
|
|
2705
|
+
const parent = _nullishCoalesce(vectorParentMatrix(this.doc.nodes, id), () => ( IDENTITY_MATRIX));
|
|
2706
|
+
return isRoot(parent) ? path : _chunkUG5DVXJKcjs.transformBezierPath.call(void 0, path, invertMatrix(parent));
|
|
2707
|
+
}
|
|
1707
2708
|
/** Splits selected compound paths into one path per subpath. */
|
|
1708
2709
|
breakApart(options = {}) {
|
|
1709
|
-
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);
|
|
1710
2711
|
if (!paths.length) return { ok: false, message: "Select a compound path to break apart." };
|
|
1711
2712
|
const created = [];
|
|
1712
2713
|
const set = new Set(paths.map((p) => p.id));
|
|
@@ -1743,23 +2744,30 @@ var VectorEditorController = (_class = class {
|
|
|
1743
2744
|
});
|
|
1744
2745
|
return nodes === doc.nodes ? doc : { ...doc, nodes };
|
|
1745
2746
|
}
|
|
2747
|
+
/** Moves anchors by a document-space offset (converted into each node's own space). */
|
|
1746
2748
|
movedAnchors(doc, refs, dx, dy) {
|
|
1747
|
-
|
|
2749
|
+
const local = /* @__PURE__ */ new Map();
|
|
2750
|
+
for (const r of refs) if (!local.has(r.nodeId)) local.set(r.nodeId, applyMatrixLinear(invertMatrix(this.worldOf(r.nodeId, doc)), { x: dx, y: dy }));
|
|
2751
|
+
return this.updateAnchors(doc, refs, (a, ref) => {
|
|
2752
|
+
const d = local.get(ref.nodeId);
|
|
2753
|
+
return translateAnchor(a, d.x, d.y);
|
|
2754
|
+
});
|
|
1748
2755
|
}
|
|
1749
|
-
/** Moves one anchor to a position (handles follow). */
|
|
2756
|
+
/** Moves one anchor to a document position (handles follow). */
|
|
1750
2757
|
moveAnchor(ref, to) {
|
|
1751
|
-
const a =
|
|
2758
|
+
const a = getAnchorWorld(this.doc, ref);
|
|
1752
2759
|
if (!a) return { ok: false, message: "There is no such anchor." };
|
|
1753
2760
|
this.change("Move Point", () => {
|
|
1754
2761
|
this.doc = this.movedAnchors(this.doc, [ref], to.x - a.x, to.y - a.y);
|
|
1755
2762
|
});
|
|
1756
2763
|
return { ok: true };
|
|
1757
2764
|
}
|
|
1758
|
-
/** Moves one handle (see `moveAnchorHandle` for how the opposite handle follows). */
|
|
2765
|
+
/** Moves one handle to a document position (see `moveAnchorHandle` for how the opposite handle follows). */
|
|
1759
2766
|
moveHandle(ref, to, options = {}) {
|
|
1760
2767
|
if (!getAnchor(this.doc, ref)) return { ok: false, message: "There is no such anchor." };
|
|
2768
|
+
const local = this.toLocal(ref.nodeId, to);
|
|
1761
2769
|
this.change("Move Handle", () => {
|
|
1762
|
-
this.doc = this.updateAnchors(this.doc, [ref], (a) => moveAnchorHandle(a, ref.handle,
|
|
2770
|
+
this.doc = this.updateAnchors(this.doc, [ref], (a) => moveAnchorHandle(a, ref.handle, local, options.breakHandles));
|
|
1763
2771
|
});
|
|
1764
2772
|
return { ok: true };
|
|
1765
2773
|
}
|
|
@@ -1778,11 +2786,11 @@ var VectorEditorController = (_class = class {
|
|
|
1778
2786
|
});
|
|
1779
2787
|
return { ok: true };
|
|
1780
2788
|
}
|
|
1781
|
-
/** Adds an anchor on the nearest segment of `nodeId` to `point` (shape unchanged). */
|
|
2789
|
+
/** Adds an anchor on the nearest segment of `nodeId` to document point `point` (shape unchanged). */
|
|
1782
2790
|
insertAnchorAt(nodeId, point) {
|
|
1783
2791
|
const node = this.getNode(nodeId);
|
|
1784
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
1785
|
-
const hit = nearestSegment(node, point);
|
|
2792
|
+
if (_optionalChain([node, 'optionalAccess', _79 => _79.type]) !== "path") return null;
|
|
2793
|
+
const hit = nearestSegment(node, this.toLocal(nodeId, point));
|
|
1786
2794
|
if (!hit) return null;
|
|
1787
2795
|
const { subpath, index } = insertAnchor(node.subpaths[hit.subpath], hit.segment, hit.t);
|
|
1788
2796
|
const ref = { nodeId, subpath: hit.subpath, index };
|
|
@@ -1816,7 +2824,7 @@ var VectorEditorController = (_class = class {
|
|
|
1816
2824
|
setClosed(closed, nodeId) {
|
|
1817
2825
|
const targets = nodeId ? (() => {
|
|
1818
2826
|
const n = this.getNode(nodeId);
|
|
1819
|
-
return _optionalChain([n, 'optionalAccess',
|
|
2827
|
+
return _optionalChain([n, 'optionalAccess', _80 => _80.type]) === "path" ? n.subpaths.map((_, s) => ({ nodeId, subpath: s })) : [];
|
|
1820
2828
|
})() : this.anchors.map((r) => ({ nodeId: r.nodeId, subpath: r.subpath }));
|
|
1821
2829
|
if (!targets.length) return { ok: false, message: "Select a path or points." };
|
|
1822
2830
|
this.change(closed ? "Close Path" : "Open Path", () => {
|
|
@@ -1843,7 +2851,7 @@ var VectorEditorController = (_class = class {
|
|
|
1843
2851
|
finishPath() {
|
|
1844
2852
|
if (!this.pen) return;
|
|
1845
2853
|
const node = this.getNode(this.pen.nodeId);
|
|
1846
|
-
const sp = _optionalChain([node, 'optionalAccess',
|
|
2854
|
+
const sp = _optionalChain([node, 'optionalAccess', _81 => _81.subpaths, 'access', _82 => _82[this.pen.subpath]]);
|
|
1847
2855
|
const pen = this.pen;
|
|
1848
2856
|
this.pen = null;
|
|
1849
2857
|
this.pointer = null;
|
|
@@ -1887,7 +2895,7 @@ var VectorEditorController = (_class = class {
|
|
|
1887
2895
|
if (trimmed.startsWith("{")) {
|
|
1888
2896
|
try {
|
|
1889
2897
|
const data = JSON.parse(trimmed);
|
|
1890
|
-
if (_optionalChain([data, 'optionalAccess',
|
|
2898
|
+
if (_optionalChain([data, 'optionalAccess', _83 => _83.type]) === VECTOR_CLIPBOARD_TYPE && Array.isArray(data.nodes)) nodes = data.nodes;
|
|
1891
2899
|
} catch (e2) {
|
|
1892
2900
|
nodes = null;
|
|
1893
2901
|
}
|
|
@@ -1899,7 +2907,7 @@ var VectorEditorController = (_class = class {
|
|
|
1899
2907
|
}
|
|
1900
2908
|
}
|
|
1901
2909
|
} else nodes = this.clipboard;
|
|
1902
|
-
if (!_optionalChain([nodes, 'optionalAccess',
|
|
2910
|
+
if (!_optionalChain([nodes, 'optionalAccess', _84 => _84.length])) return [];
|
|
1903
2911
|
const offset = _nullishCoalesce(options.offset, () => ( (text && !text.trim().startsWith("{") ? { x: 0, y: 0 } : { x: 10, y: 10 })));
|
|
1904
2912
|
const copies = nodes.map((n) => transformVectorNode(this.freshIds(n), translateMatrix(offset.x, offset.y)));
|
|
1905
2913
|
this.change("Paste", () => {
|
|
@@ -1939,20 +2947,18 @@ var VectorEditorController = (_class = class {
|
|
|
1939
2947
|
}
|
|
1940
2948
|
hitTransformHandle(screen) {
|
|
1941
2949
|
if (this.tool !== "select" || !this.selection.length) return null;
|
|
1942
|
-
const
|
|
1943
|
-
if (!
|
|
1944
|
-
const
|
|
1945
|
-
const box = { x: tl.x, y: tl.y, width: b.width * this.viewport.zoom, height: b.height * this.viewport.zoom };
|
|
2950
|
+
const frame = this.frameOf(this.selectedRoots());
|
|
2951
|
+
if (!frame) return null;
|
|
2952
|
+
const { box, handles } = getVectorSelectionBoxGeometry(frame, this.viewport);
|
|
1946
2953
|
const r = this.hitRadius + 2;
|
|
1947
|
-
const handles = getTransformHandlePositions(box);
|
|
1948
2954
|
const usable = box.width < 2 * r || box.height < 2 * r ? handles.filter((h) => h.handle.length === 2 || h.handle === "rotate") : handles;
|
|
1949
|
-
for (const h of usable) if (Math.
|
|
2955
|
+
for (const h of usable) if (Math.hypot(h.screen.x - screen.x, h.screen.y - screen.y) <= r * Math.SQRT2) return h.handle;
|
|
1950
2956
|
return null;
|
|
1951
2957
|
}
|
|
1952
2958
|
hitHandle(screen) {
|
|
1953
2959
|
for (const ref of getVisibleHandles(this.view()).reverse()) {
|
|
1954
|
-
const a =
|
|
1955
|
-
const h = _optionalChain([a, 'optionalAccess',
|
|
2960
|
+
const a = getAnchorWorld(this.doc, ref);
|
|
2961
|
+
const h = _optionalChain([a, 'optionalAccess', _85 => _85[ref.handle]]);
|
|
1956
2962
|
if (!h) continue;
|
|
1957
2963
|
const s = this.documentToScreen(h);
|
|
1958
2964
|
if (Math.hypot(s.x - screen.x, s.y - screen.y) <= this.hitRadius) return ref;
|
|
@@ -1963,7 +2969,7 @@ var VectorEditorController = (_class = class {
|
|
|
1963
2969
|
let best = null;
|
|
1964
2970
|
let bestD = Infinity;
|
|
1965
2971
|
for (const ref of refs) {
|
|
1966
|
-
const a =
|
|
2972
|
+
const a = getAnchorWorld(this.doc, ref);
|
|
1967
2973
|
if (!a) continue;
|
|
1968
2974
|
const s = this.documentToScreen(a);
|
|
1969
2975
|
const d = Math.hypot(s.x - screen.x, s.y - screen.y);
|
|
@@ -1977,18 +2983,18 @@ var VectorEditorController = (_class = class {
|
|
|
1977
2983
|
/** Deepest unlocked visible path under `p`. */
|
|
1978
2984
|
hitPath(p) {
|
|
1979
2985
|
const tol = this.tolerance();
|
|
1980
|
-
const search = (list) => {
|
|
2986
|
+
const search = (list, parent) => {
|
|
1981
2987
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
1982
2988
|
const n = list[i];
|
|
1983
2989
|
if (n.visible === false || n.locked) continue;
|
|
1984
2990
|
if (n.type === "group") {
|
|
1985
|
-
const found = search(n.children);
|
|
2991
|
+
const found = search(n.children, multiplyMatrices(nodeMatrix(n), parent));
|
|
1986
2992
|
if (found) return found;
|
|
1987
|
-
} else if (hitTestVectorNode(n, p, tol)) return n;
|
|
2993
|
+
} else if (hitTestVectorNode(n, p, tol, parent)) return n;
|
|
1988
2994
|
}
|
|
1989
2995
|
return null;
|
|
1990
2996
|
};
|
|
1991
|
-
return search(this.doc.nodes);
|
|
2997
|
+
return search(this.doc.nodes, IDENTITY_MATRIX);
|
|
1992
2998
|
}
|
|
1993
2999
|
/** Endpoints of open subpaths of unlocked visible paths (pen tool resumes from them). */
|
|
1994
3000
|
openEndpoints() {
|
|
@@ -2019,8 +3025,11 @@ var VectorEditorController = (_class = class {
|
|
|
2019
3025
|
if (anchor) return { nodeId: anchor.nodeId, anchor };
|
|
2020
3026
|
for (const id of getEditablePathIds(this.view())) {
|
|
2021
3027
|
const node = this.getNode(id);
|
|
2022
|
-
const seg = nearestSegment(node, p);
|
|
2023
|
-
if (seg && seg.distance <= this.tolerance())
|
|
3028
|
+
const seg = nearestSegment(node, this.toLocal(id, p));
|
|
3029
|
+
if (seg && seg.distance <= this.tolerance()) {
|
|
3030
|
+
const point = applyMatrix(this.worldOf(id), seg.point);
|
|
3031
|
+
return { nodeId: id, segment: { nodeId: id, subpath: seg.subpath, segment: seg.segment, t: seg.t, point } };
|
|
3032
|
+
}
|
|
2024
3033
|
}
|
|
2025
3034
|
const path = this.hitPath(p);
|
|
2026
3035
|
return path ? { nodeId: path.id } : null;
|
|
@@ -2059,15 +3068,16 @@ var VectorEditorController = (_class = class {
|
|
|
2059
3068
|
case "pencil":
|
|
2060
3069
|
return this.downPencil(p);
|
|
2061
3070
|
default:
|
|
2062
|
-
return this.downShape(p);
|
|
3071
|
+
return this.downShape(p, input);
|
|
2063
3072
|
}
|
|
2064
3073
|
}
|
|
2065
3074
|
downSelect(screen, p, input) {
|
|
2066
3075
|
const handle = this.hitTransformHandle(screen);
|
|
2067
3076
|
if (handle) {
|
|
2068
|
-
const
|
|
3077
|
+
const frame = this.frameOf(this.selectedRoots());
|
|
2069
3078
|
const before = this.snapshot();
|
|
2070
|
-
|
|
3079
|
+
const center = applyMatrix(rotateMatrix(frame.angle), { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 });
|
|
3080
|
+
this.drag = handle === "rotate" ? { kind: "rotate", start: p, center, before, base: this.doc } : { kind: "scale", handle, start: p, frame, before, base: this.doc };
|
|
2071
3081
|
this.interaction = handle === "rotate" ? { type: "rotate", angle: 0 } : { type: "scale", handle, sx: 1, sy: 1 };
|
|
2072
3082
|
this.notify();
|
|
2073
3083
|
return;
|
|
@@ -2127,13 +3137,13 @@ var VectorEditorController = (_class = class {
|
|
|
2127
3137
|
return;
|
|
2128
3138
|
}
|
|
2129
3139
|
} else if (!selected) this.anchors = [anchor];
|
|
2130
|
-
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 };
|
|
2131
3141
|
this.notify();
|
|
2132
3142
|
return;
|
|
2133
3143
|
}
|
|
2134
3144
|
for (const id of getEditablePathIds(this.view())) {
|
|
2135
3145
|
const node = this.getNode(id);
|
|
2136
|
-
const seg = nearestSegment(node, p);
|
|
3146
|
+
const seg = nearestSegment(node, this.toLocal(id, p));
|
|
2137
3147
|
if (!seg || seg.distance > this.tolerance()) continue;
|
|
2138
3148
|
const sp = node.subpaths[seg.subpath];
|
|
2139
3149
|
if (input.alt) {
|
|
@@ -2143,7 +3153,7 @@ var VectorEditorController = (_class = class {
|
|
|
2143
3153
|
const ref = { nodeId: id, subpath: seg.subpath, index };
|
|
2144
3154
|
this.anchors = [ref];
|
|
2145
3155
|
this.editingId = id;
|
|
2146
|
-
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 };
|
|
2147
3157
|
this.notify(true);
|
|
2148
3158
|
return;
|
|
2149
3159
|
}
|
|
@@ -2151,7 +3161,7 @@ var VectorEditorController = (_class = class {
|
|
|
2151
3161
|
const b = { nodeId: id, subpath: seg.subpath, index: (seg.segment + 1) % sp.anchors.length };
|
|
2152
3162
|
this.anchors = input.shift ? [...this.anchors.filter((r) => !sameRef(r, a) && !sameRef(r, b)), a, b] : [a, b];
|
|
2153
3163
|
this.editingId = id;
|
|
2154
|
-
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 };
|
|
2155
3165
|
this.notify();
|
|
2156
3166
|
return;
|
|
2157
3167
|
}
|
|
@@ -2161,7 +3171,7 @@ var VectorEditorController = (_class = class {
|
|
|
2161
3171
|
if (!input.shift) this.selection = [path.id];
|
|
2162
3172
|
else if (!this.selection.includes(path.id)) this.selection = [...this.selection, path.id];
|
|
2163
3173
|
this.anchors = path.subpaths.flatMap((sp, s) => sp.anchors.map((_, i) => ({ nodeId: path.id, subpath: s, index: i })));
|
|
2164
|
-
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 };
|
|
2165
3175
|
this.notify();
|
|
2166
3176
|
return;
|
|
2167
3177
|
}
|
|
@@ -2174,7 +3184,7 @@ var VectorEditorController = (_class = class {
|
|
|
2174
3184
|
}
|
|
2175
3185
|
downPen(screen, p, input) {
|
|
2176
3186
|
const before = this.snapshot();
|
|
2177
|
-
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;
|
|
2178
3188
|
if (this.pen && sp) {
|
|
2179
3189
|
const pen = this.pen;
|
|
2180
3190
|
const first = { nodeId: pen.nodeId, subpath: pen.subpath, index: 0 };
|
|
@@ -2186,9 +3196,10 @@ var VectorEditorController = (_class = class {
|
|
|
2186
3196
|
this.notify(true);
|
|
2187
3197
|
return;
|
|
2188
3198
|
}
|
|
2189
|
-
|
|
2190
|
-
const
|
|
2191
|
-
|
|
3199
|
+
const last = getAnchorWorld(this.doc, { nodeId: pen.nodeId, subpath: pen.subpath, index: sp.anchors.length - 1 });
|
|
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));
|
|
3202
|
+
pt2 = this.toLocal(pen.nodeId, pt2);
|
|
2192
3203
|
const node2 = this.getNode(pen.nodeId);
|
|
2193
3204
|
const anchors = [...sp.anchors, { x: pt2.x, y: pt2.y }];
|
|
2194
3205
|
this.replaceNode(pen.nodeId, { ...node2, subpaths: node2.subpaths.map((s, i) => i === pen.subpath ? { ...s, anchors } : s) });
|
|
@@ -2218,7 +3229,7 @@ var VectorEditorController = (_class = class {
|
|
|
2218
3229
|
this.notify(this.doc !== before.doc);
|
|
2219
3230
|
return;
|
|
2220
3231
|
}
|
|
2221
|
-
const pt = this.
|
|
3232
|
+
const pt = this.snapDrawn(p, null, input, () => this.drawingTargets(null));
|
|
2222
3233
|
const node = { id: this.newId(), type: "path", name: "Path", ...this.styleFor(true), subpaths: [{ anchors: [{ x: pt.x, y: pt.y }], closed: false }] };
|
|
2223
3234
|
this.setDoc([...this.doc.nodes, node]);
|
|
2224
3235
|
this.pen = { nodeId: node.id, subpath: 0 };
|
|
@@ -2246,13 +3257,14 @@ var VectorEditorController = (_class = class {
|
|
|
2246
3257
|
this.interaction = { type: "draw", tool: "pencil", nodeId: node.id, bounds: { x: p.x, y: p.y, width: 0, height: 0 } };
|
|
2247
3258
|
this.notify(true);
|
|
2248
3259
|
}
|
|
2249
|
-
downShape(p) {
|
|
3260
|
+
downShape(p, input) {
|
|
2250
3261
|
const before = this.snapshot();
|
|
2251
|
-
const
|
|
3262
|
+
const targets = this.drawingTargets(null);
|
|
3263
|
+
const start = this.snapDrawn(p, null, input, () => targets);
|
|
2252
3264
|
const kind = this.tool;
|
|
2253
3265
|
const node = this.createShapeNode({ kind, x: start.x, y: start.y, width: 0, height: 0 });
|
|
2254
3266
|
this.setDoc([...this.doc.nodes, node]);
|
|
2255
|
-
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 };
|
|
2256
3268
|
this.interaction = { type: "draw", tool: this.tool, nodeId: node.id, bounds: { x: start.x, y: start.y, width: 0, height: 0 } };
|
|
2257
3269
|
this.notify(true);
|
|
2258
3270
|
}
|
|
@@ -2263,9 +3275,11 @@ var VectorEditorController = (_class = class {
|
|
|
2263
3275
|
if (!drag) {
|
|
2264
3276
|
const hover = this.computeHover(screen);
|
|
2265
3277
|
const pointerMatters = this.tool === "pen" && !!this.pen;
|
|
2266
|
-
|
|
3278
|
+
const guides = this.guides;
|
|
2267
3279
|
this.hover = hover;
|
|
2268
|
-
|
|
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;
|
|
2269
3283
|
this.notify();
|
|
2270
3284
|
return;
|
|
2271
3285
|
}
|
|
@@ -2286,17 +3300,26 @@ var VectorEditorController = (_class = class {
|
|
|
2286
3300
|
const created = [];
|
|
2287
3301
|
drag.base = this.withDuplicates(drag.base, this.selectedRoots(), { x: 0, y: 0 }, created);
|
|
2288
3302
|
this.selection = created;
|
|
3303
|
+
drag.targets = void 0;
|
|
2289
3304
|
}
|
|
2290
|
-
({ dx, dy } = this.
|
|
3305
|
+
({ dx, dy } = this.snapMove(drag, dx, dy, input));
|
|
2291
3306
|
this.doc = this.transformed(drag.base, new Set(this.selectedRoots()), translateMatrix(dx, dy));
|
|
2292
3307
|
this.interaction = { type: "move", dx, dy };
|
|
2293
3308
|
this.notify(true);
|
|
2294
3309
|
return;
|
|
2295
3310
|
}
|
|
2296
3311
|
case "scale": {
|
|
2297
|
-
const
|
|
3312
|
+
const angle = drag.frame.angle;
|
|
3313
|
+
const q = angle ? applyMatrix(rotateMatrix(-angle), p) : p;
|
|
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);
|
|
2298
3321
|
const ids = new Set(this.selectedRoots());
|
|
2299
|
-
this.doc = this.transformed(drag.base, ids, scaleMatrix(sx, sy, origin));
|
|
3322
|
+
this.doc = this.transformed(drag.base, ids, this.inFrame(angle, scaleMatrix(sx, sy, origin)));
|
|
2300
3323
|
this.interaction = { type: "scale", handle: drag.handle, sx, sy };
|
|
2301
3324
|
this.notify(true);
|
|
2302
3325
|
return;
|
|
@@ -2316,7 +3339,7 @@ var VectorEditorController = (_class = class {
|
|
|
2316
3339
|
this.interaction = { type: "marquee", bounds };
|
|
2317
3340
|
if (drag.anchorsMode) {
|
|
2318
3341
|
const inside = getVisibleAnchors(this.view()).filter((r) => {
|
|
2319
|
-
const a =
|
|
3342
|
+
const a = getAnchorWorld(this.doc, r);
|
|
2320
3343
|
return a.x >= bounds.x && a.x <= bounds.x + bounds.width && a.y >= bounds.y && a.y <= bounds.y + bounds.height;
|
|
2321
3344
|
});
|
|
2322
3345
|
this.anchors = [...drag.baseAnchors, ...inside.filter((r) => !drag.baseAnchors.some((b) => sameRef(b, r)))];
|
|
@@ -2335,35 +3358,41 @@ var VectorEditorController = (_class = class {
|
|
|
2335
3358
|
let dy = p.y - drag.start.y;
|
|
2336
3359
|
if (!drag.moved && Math.hypot(dx, dy) * this.viewport.zoom < 2) return;
|
|
2337
3360
|
drag.moved = true;
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
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;
|
|
2341
3368
|
this.doc = this.movedAnchors(drag.base, this.anchors, dx, dy);
|
|
2342
3369
|
this.interaction = { type: "anchors", dx, dy };
|
|
2343
3370
|
this.notify(true);
|
|
2344
3371
|
return;
|
|
2345
3372
|
}
|
|
2346
3373
|
case "handle": {
|
|
2347
|
-
const anchor =
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
3374
|
+
const anchor = getAnchorWorld(drag.base, drag.ref);
|
|
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));
|
|
3377
|
+
const local = this.toLocal(drag.ref.nodeId, pt, drag.base);
|
|
3378
|
+
this.doc = this.updateAnchors(drag.base, [drag.ref], (a) => moveAnchorHandle(a, drag.ref.handle, local, input.alt));
|
|
2351
3379
|
this.interaction = { type: "handle", ref: drag.ref };
|
|
2352
3380
|
this.notify(true);
|
|
2353
3381
|
return;
|
|
2354
3382
|
}
|
|
2355
3383
|
case "pull": {
|
|
2356
|
-
const anchor =
|
|
3384
|
+
const anchor = getAnchorWorld(drag.base, drag.ref);
|
|
2357
3385
|
if (!anchor) return;
|
|
2358
3386
|
if (Math.hypot(p.x - anchor.x, p.y - anchor.y) * this.viewport.zoom < 2) {
|
|
2359
3387
|
this.doc = drag.base;
|
|
2360
3388
|
this.notify(true);
|
|
2361
3389
|
return;
|
|
2362
3390
|
}
|
|
3391
|
+
this.guides = null;
|
|
2363
3392
|
let pt = p;
|
|
2364
3393
|
if (input.shift) pt = constrain45(anchor, pt);
|
|
2365
3394
|
const side = drag.closing ? "in" : "out";
|
|
2366
|
-
const pos = drag.closing ? { x: 2 * anchor.x - pt.x, y: 2 * anchor.y - pt.y } : pt;
|
|
3395
|
+
const pos = this.toLocal(drag.ref.nodeId, drag.closing ? { x: 2 * anchor.x - pt.x, y: 2 * anchor.y - pt.y } : pt, drag.base);
|
|
2367
3396
|
this.doc = this.updateAnchors(drag.base, [drag.ref], (a) => {
|
|
2368
3397
|
if (drag.outOnly || drag.altBreaks && input.alt) return moveAnchorHandle(a, side, pos, true);
|
|
2369
3398
|
return moveAnchorHandle({ ...a, type: "symmetric" }, side, pos);
|
|
@@ -2373,8 +3402,10 @@ var VectorEditorController = (_class = class {
|
|
|
2373
3402
|
}
|
|
2374
3403
|
case "shape": {
|
|
2375
3404
|
const node = this.getNode(drag.nodeId);
|
|
2376
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
2377
|
-
|
|
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);
|
|
2378
3409
|
let start = drag.start;
|
|
2379
3410
|
let bounds;
|
|
2380
3411
|
if (drag.tool === "line") {
|
|
@@ -2405,7 +3436,7 @@ var VectorEditorController = (_class = class {
|
|
|
2405
3436
|
if (Math.hypot(p.x - last[0], p.y - last[1]) * this.viewport.zoom < 1.5) return;
|
|
2406
3437
|
drag.points.push([p.x, p.y]);
|
|
2407
3438
|
const node = this.getNode(drag.nodeId);
|
|
2408
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
3439
|
+
if (_optionalChain([node, 'optionalAccess', _91 => _91.type]) !== "path") return;
|
|
2409
3440
|
this.replaceNode(drag.nodeId, { ...node, subpaths: [{ anchors: drag.points.map(([x, y]) => ({ x, y })), closed: false }] });
|
|
2410
3441
|
const xs = drag.points.map((q) => q[0]);
|
|
2411
3442
|
const ys = drag.points.map((q) => q[1]);
|
|
@@ -2420,12 +3451,64 @@ var VectorEditorController = (_class = class {
|
|
|
2420
3451
|
}
|
|
2421
3452
|
}
|
|
2422
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
|
+
}
|
|
2423
3505
|
/** Primary button released. */
|
|
2424
3506
|
pointerUp(screen, input = {}) {
|
|
2425
3507
|
if (screen && this.drag && this.drag.kind !== "pan") this.pointerMove(screen, input);
|
|
2426
3508
|
const drag = this.drag;
|
|
2427
3509
|
this.drag = null;
|
|
2428
3510
|
this.interaction = null;
|
|
3511
|
+
this.guides = null;
|
|
2429
3512
|
if (!drag) return;
|
|
2430
3513
|
switch (drag.kind) {
|
|
2431
3514
|
case "pan":
|
|
@@ -2472,12 +3555,12 @@ var VectorEditorController = (_class = class {
|
|
|
2472
3555
|
}
|
|
2473
3556
|
case "pencil": {
|
|
2474
3557
|
const node = this.getNode(drag.nodeId);
|
|
2475
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
3558
|
+
if (_optionalChain([node, 'optionalAccess', _94 => _94.type]) !== "path" || drag.points.length < 2) {
|
|
2476
3559
|
this.doc = drag.before.doc;
|
|
2477
3560
|
this.notify(true);
|
|
2478
3561
|
return;
|
|
2479
3562
|
}
|
|
2480
|
-
const fitted =
|
|
3563
|
+
const fitted = _chunkUG5DVXJKcjs.fitCubics.call(void 0, drag.points, 1.5 / this.viewport.zoom, { cornerAngleDeg: 60 });
|
|
2481
3564
|
const anchors = [];
|
|
2482
3565
|
for (const c of fitted) {
|
|
2483
3566
|
if (!anchors.length) anchors.push({ x: c.p0[0], y: c.p0[1] });
|
|
@@ -2494,9 +3577,10 @@ var VectorEditorController = (_class = class {
|
|
|
2494
3577
|
/** The pointer left the canvas. */
|
|
2495
3578
|
pointerLeave() {
|
|
2496
3579
|
if (this.drag) return;
|
|
2497
|
-
if (!this.hover && !this.pointer) return;
|
|
3580
|
+
if (!this.hover && !this.pointer && !this.guides) return;
|
|
2498
3581
|
this.hover = null;
|
|
2499
3582
|
this.pointer = null;
|
|
3583
|
+
this.guides = null;
|
|
2500
3584
|
this.notify();
|
|
2501
3585
|
}
|
|
2502
3586
|
/** Abandons the drag in progress (Escape, lost capture). */
|
|
@@ -2505,6 +3589,7 @@ var VectorEditorController = (_class = class {
|
|
|
2505
3589
|
if (!drag) return;
|
|
2506
3590
|
this.drag = null;
|
|
2507
3591
|
this.interaction = null;
|
|
3592
|
+
this.guides = null;
|
|
2508
3593
|
if (drag.kind === "pan") {
|
|
2509
3594
|
this.notify();
|
|
2510
3595
|
return;
|
|
@@ -2537,10 +3622,10 @@ var VectorEditorController = (_class = class {
|
|
|
2537
3622
|
this.convertAnchors(a.in || a.out ? "corner" : "smooth", [anchor]);
|
|
2538
3623
|
return;
|
|
2539
3624
|
}
|
|
2540
|
-
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));
|
|
2541
3626
|
if (id) {
|
|
2542
3627
|
const node = this.getNode(id);
|
|
2543
|
-
const seg = node ? nearestSegment(node, p) : null;
|
|
3628
|
+
const seg = node ? nearestSegment(node, this.toLocal(id, p)) : null;
|
|
2544
3629
|
if (seg && seg.distance <= this.tolerance()) this.insertAnchorAt(id, p);
|
|
2545
3630
|
}
|
|
2546
3631
|
return;
|
|
@@ -2615,7 +3700,7 @@ var VectorEditorController = (_class = class {
|
|
|
2615
3700
|
this.finishPath();
|
|
2616
3701
|
return true;
|
|
2617
3702
|
}
|
|
2618
|
-
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") {
|
|
2619
3704
|
this.tool = "direct";
|
|
2620
3705
|
this.editingId = this.selection[0];
|
|
2621
3706
|
this.notify();
|
|
@@ -2660,7 +3745,7 @@ var VectorEditorController = (_class = class {
|
|
|
2660
3745
|
this.notify();
|
|
2661
3746
|
}
|
|
2662
3747
|
}
|
|
2663
|
-
},
|
|
3748
|
+
}, _class2);
|
|
2664
3749
|
function constrain45(origin, p) {
|
|
2665
3750
|
const dx = p.x - origin.x;
|
|
2666
3751
|
const dy = p.y - origin.y;
|
|
@@ -2692,11 +3777,12 @@ function scaleFor(handle, b, p, mods = {}) {
|
|
|
2692
3777
|
|
|
2693
3778
|
// src/vector/render.ts
|
|
2694
3779
|
var fmt = (n) => Math.round(n * 100) / 100;
|
|
3780
|
+
var fmtMatrix = (m) => `matrix(${m.map((n) => Math.round(n * 1e6) / 1e6).join(" ")})`;
|
|
2695
3781
|
function renderNode(node, defs, idPrefix) {
|
|
2696
3782
|
if (node.visible === false) return null;
|
|
2697
3783
|
if (node.type === "group") {
|
|
2698
3784
|
const children = node.children.map((c) => renderNode(c, defs, idPrefix)).filter((c) => !!c);
|
|
2699
|
-
return { kind: "group", id: node.id, opacity: _nullishCoalesce(node.opacity, () => ( 1)), children };
|
|
3785
|
+
return { kind: "group", id: node.id, ...node.transform ? { transform: fmtMatrix(node.transform) } : {}, opacity: _nullishCoalesce(node.opacity, () => ( 1)), children };
|
|
2700
3786
|
}
|
|
2701
3787
|
const fill = vectorPaintToSvg(node.fill, `${idPrefix}fill-${node.id}`);
|
|
2702
3788
|
const stroke = vectorPaintToSvg(node.stroke, `${idPrefix}stroke-${node.id}`);
|
|
@@ -2705,13 +3791,14 @@ function renderNode(node, defs, idPrefix) {
|
|
|
2705
3791
|
return {
|
|
2706
3792
|
kind: "path",
|
|
2707
3793
|
id: node.id,
|
|
3794
|
+
...node.transform ? { transform: fmtMatrix(node.transform) } : {},
|
|
2708
3795
|
d: vectorPathData(node),
|
|
2709
3796
|
fill: fill.value,
|
|
2710
3797
|
stroke: stroke.value,
|
|
2711
3798
|
strokeWidth: _nullishCoalesce(node.strokeWidth, () => ( 1)),
|
|
2712
3799
|
...node.strokeLinecap ? { strokeLinecap: node.strokeLinecap } : {},
|
|
2713
3800
|
...node.strokeLinejoin ? { strokeLinejoin: node.strokeLinejoin } : {},
|
|
2714
|
-
..._optionalChain([node, 'access',
|
|
3801
|
+
..._optionalChain([node, 'access', _101 => _101.strokeDasharray, 'optionalAccess', _102 => _102.length]) ? { strokeDasharray: node.strokeDasharray.join(" ") } : {},
|
|
2715
3802
|
fillRule: _nullishCoalesce(node.fillRule, () => ( "nonzero")),
|
|
2716
3803
|
opacity: _nullishCoalesce(node.opacity, () => ( 1))
|
|
2717
3804
|
};
|
|
@@ -2733,8 +3820,14 @@ function getVectorSceneRenderData(state, options = {}) {
|
|
|
2733
3820
|
var toScreen = (state, p) => ({ x: p.x * state.viewport.zoom + state.viewport.x, y: p.y * state.viewport.zoom + state.viewport.y });
|
|
2734
3821
|
function screenOutline(state, node) {
|
|
2735
3822
|
const v = state.viewport;
|
|
2736
|
-
const
|
|
2737
|
-
|
|
3823
|
+
const view = [v.zoom, 0, 0, v.zoom, v.x, v.y];
|
|
3824
|
+
const parent = _nullishCoalesce(vectorParentMatrix(state.document.nodes, node.id), () => ( IDENTITY_MATRIX));
|
|
3825
|
+
return collectWithMatrix(node, parent).map(({ path, parent: m }) => _chunkUG5DVXJKcjs.bezierPathToSvg.call(void 0, _chunkUG5DVXJKcjs.transformBezierPath.call(void 0, vectorPathWorldBezier(path, m), view), { precision: 2 })).join(" ");
|
|
3826
|
+
}
|
|
3827
|
+
function collectWithMatrix(node, parent) {
|
|
3828
|
+
if (node.type === "path") return [{ path: node, parent }];
|
|
3829
|
+
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
3830
|
+
return node.children.flatMap((c) => collectWithMatrix(c, m));
|
|
2738
3831
|
}
|
|
2739
3832
|
function collect(node) {
|
|
2740
3833
|
if (node.type === "path") return [node];
|
|
@@ -2754,40 +3847,38 @@ function getVectorOverlayRenderData(state) {
|
|
|
2754
3847
|
};
|
|
2755
3848
|
for (const id of state.selection) outline(id, true);
|
|
2756
3849
|
if (state.editingId) outline(state.editingId, true);
|
|
2757
|
-
if (_optionalChain([state, 'access',
|
|
3850
|
+
if (_optionalChain([state, 'access', _103 => _103.hover, 'optionalAccess', _104 => _104.nodeId]) && !state.interaction) outline(state.hover.nodeId, false);
|
|
2758
3851
|
let selectionBox = null;
|
|
2759
|
-
if (state.tool === "select" && state.
|
|
2760
|
-
const
|
|
2761
|
-
|
|
2762
|
-
const box = { x: tl.x, y: tl.y, width: b.width * z, height: b.height * z };
|
|
2763
|
-
selectionBox = { ...box, handles: getTransformHandlePositions(box) };
|
|
3852
|
+
if (state.tool === "select" && state.selectionFrame && _optionalChain([state, 'access', _105 => _105.interaction, 'optionalAccess', _106 => _106.type]) !== "marquee") {
|
|
3853
|
+
const g = getVectorSelectionBoxGeometry(state.selectionFrame, state.viewport);
|
|
3854
|
+
selectionBox = { ...g.box, angle: g.angle, transform: g.transform, corners: g.corners, handles: g.handles };
|
|
2764
3855
|
}
|
|
2765
3856
|
const selected = (r) => state.selectedAnchors.some((a) => a.nodeId === r.nodeId && a.subpath === r.subpath && a.index === r.index);
|
|
2766
3857
|
const sameAnchor = (a, b) => !!a && a.nodeId === b.nodeId && a.subpath === b.subpath && a.index === b.index;
|
|
2767
3858
|
const anchors = getVisibleAnchors(state).map((ref) => {
|
|
2768
|
-
const a =
|
|
3859
|
+
const a = getAnchorWorld(doc, ref);
|
|
2769
3860
|
const s = toScreen(state, a);
|
|
2770
3861
|
const closes = !!state.pen && state.pen.nodeId === ref.nodeId && state.pen.subpath === ref.subpath && ref.index === 0;
|
|
2771
|
-
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 };
|
|
2772
3863
|
});
|
|
2773
|
-
if (state.tool === "pen" && !state.pen && _optionalChain([state, 'access',
|
|
2774
|
-
const a =
|
|
3864
|
+
if (state.tool === "pen" && !state.pen && _optionalChain([state, 'access', _109 => _109.hover, 'optionalAccess', _110 => _110.anchor])) {
|
|
3865
|
+
const a = getAnchorWorld(doc, state.hover.anchor);
|
|
2775
3866
|
if (a) {
|
|
2776
3867
|
const s = toScreen(state, a);
|
|
2777
3868
|
anchors.push({ ref: state.hover.anchor, x: s.x, y: s.y, type: _nullishCoalesce(a.type, () => ( "corner")), selected: false, hovered: true, closes: false });
|
|
2778
3869
|
}
|
|
2779
3870
|
}
|
|
2780
3871
|
const handles = getVisibleHandles(state).flatMap((ref) => {
|
|
2781
|
-
const a =
|
|
2782
|
-
const h = _optionalChain([a, 'optionalAccess',
|
|
3872
|
+
const a = getAnchorWorld(doc, ref);
|
|
3873
|
+
const h = _optionalChain([a, 'optionalAccess', _111 => _111[ref.handle]]);
|
|
2783
3874
|
if (!a || !h) return [];
|
|
2784
3875
|
const s = toScreen(state, h);
|
|
2785
3876
|
const sa = toScreen(state, a);
|
|
2786
|
-
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;
|
|
2787
3878
|
return [{ ref, x: s.x, y: s.y, anchorX: sa.x, anchorY: sa.y, hovered }];
|
|
2788
3879
|
});
|
|
2789
3880
|
let marquee = null;
|
|
2790
|
-
if (_optionalChain([state, 'access',
|
|
3881
|
+
if (_optionalChain([state, 'access', _114 => _114.interaction, 'optionalAccess', _115 => _115.type]) === "marquee") {
|
|
2791
3882
|
const b = state.interaction.bounds;
|
|
2792
3883
|
const tl = toScreen(state, b);
|
|
2793
3884
|
marquee = { x: tl.x, y: tl.y, width: b.width * z, height: b.height * z };
|
|
@@ -2795,8 +3886,8 @@ function getVectorOverlayRenderData(state) {
|
|
|
2795
3886
|
let penPreview = null;
|
|
2796
3887
|
if (state.tool === "pen" && state.pen && state.pointer && !state.interaction) {
|
|
2797
3888
|
const node = findVectorNode(doc.nodes, state.pen.nodeId);
|
|
2798
|
-
const sp = _optionalChain([node, 'optionalAccess',
|
|
2799
|
-
const last =
|
|
3889
|
+
const sp = _optionalChain([node, 'optionalAccess', _116 => _116.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
|
|
3890
|
+
const last = sp ? getAnchorWorld(doc, { nodeId: state.pen.nodeId, subpath: state.pen.subpath, index: sp.anchors.length - 1 }) : void 0;
|
|
2800
3891
|
if (last) {
|
|
2801
3892
|
const a = toScreen(state, last);
|
|
2802
3893
|
const p = toScreen(state, state.pointer);
|
|
@@ -2808,16 +3899,30 @@ function getVectorOverlayRenderData(state) {
|
|
|
2808
3899
|
const artboard = { x: origin.x, y: origin.y, width: doc.width * z, height: doc.height * z };
|
|
2809
3900
|
let status = null;
|
|
2810
3901
|
const it = state.interaction;
|
|
2811
|
-
if (_optionalChain([it, 'optionalAccess',
|
|
2812
|
-
else if (_optionalChain([it, 'optionalAccess',
|
|
2813
|
-
else if (_optionalChain([it, 'optionalAccess',
|
|
2814
|
-
const b = it.type === "draw" ? findVectorNode(doc.nodes, it.nodeId) ? vectorNodeBounds(findVectorNode(doc.nodes, it.nodeId)) : null : state.
|
|
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") {
|
|
3905
|
+
const b = it.type === "draw" ? findVectorNode(doc.nodes, it.nodeId) ? vectorNodeBounds(findVectorNode(doc.nodes, it.nodeId)) : null : state.selectionFrame;
|
|
2815
3906
|
if (b) status = `W ${fmt(b.width)} \xD7 H ${fmt(b.height)}`;
|
|
2816
3907
|
}
|
|
2817
|
-
|
|
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 };
|
|
2818
3921
|
}
|
|
2819
3922
|
var VECTOR_OVERLAY_THEME = {
|
|
2820
3923
|
accent: "#0ea5e9",
|
|
3924
|
+
/** Smart guide lines and labels. */
|
|
3925
|
+
guide: "#ff3dfc",
|
|
2821
3926
|
handleFill: "#ffffff",
|
|
2822
3927
|
marqueeFill: "rgba(14, 165, 233, 0.08)",
|
|
2823
3928
|
transformHandleSize: 8,
|
|
@@ -2838,7 +3943,7 @@ function isVectorTextTarget(target) {
|
|
|
2838
3943
|
if (!el || typeof el !== "object") return false;
|
|
2839
3944
|
const tag = (_nullishCoalesce(el.tagName, () => ( ""))).toUpperCase();
|
|
2840
3945
|
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
2841
|
-
return !!el.isContentEditable || _optionalChain([el, 'access',
|
|
3946
|
+
return !!el.isContentEditable || _optionalChain([el, 'access', _122 => _122.getAttribute, 'optionalCall', _123 => _123("contenteditable")]) === "true";
|
|
2842
3947
|
}
|
|
2843
3948
|
function getVectorSelectionStyle(state) {
|
|
2844
3949
|
const paths = [];
|
|
@@ -2897,23 +4002,25 @@ function createVectorCommands(controller, options = {}) {
|
|
|
2897
4002
|
const base = { group, ...options.scope ? { scope: options.scope } : {} };
|
|
2898
4003
|
const hasSelection = () => controller.getState().selection.length ? true : "Nothing is selected.";
|
|
2899
4004
|
const ids = (params) => {
|
|
2900
|
-
const list2 = _nullishCoalesce(_optionalChain([params, 'optionalAccess',
|
|
4005
|
+
const list2 = _nullishCoalesce(_optionalChain([params, 'optionalAccess', _124 => _124.ids]), () => ( controller.getState().selection));
|
|
2901
4006
|
for (const id of list2) if (!controller.getNode(id)) fail(`There is no node "${id}".`);
|
|
2902
4007
|
if (!list2.length) fail("Nothing is selected; pass ids.");
|
|
2903
4008
|
return list2;
|
|
2904
4009
|
};
|
|
2905
|
-
const enabledFor = (_, params) => _optionalChain([params, 'optionalAccess',
|
|
4010
|
+
const enabledFor = (_, params) => _optionalChain([params, 'optionalAccess', _125 => _125.ids, 'optionalAccess', _126 => _126.length]) ? true : hasSelection();
|
|
2906
4011
|
const list = () => {
|
|
2907
4012
|
const state = controller.getState();
|
|
2908
4013
|
const out = [];
|
|
2909
4014
|
const visit = (nodes, depth) => {
|
|
2910
4015
|
for (const n of nodes) {
|
|
2911
|
-
const
|
|
4016
|
+
const parent = _nullishCoalesce(vectorParentMatrix(state.document.nodes, n.id), () => ( IDENTITY_MATRIX));
|
|
4017
|
+
const b = vectorNodeBounds(n, parent);
|
|
2912
4018
|
out.push({
|
|
2913
4019
|
id: n.id,
|
|
2914
4020
|
type: n.type,
|
|
2915
4021
|
...n.name ? { name: n.name } : {},
|
|
2916
4022
|
depth,
|
|
4023
|
+
rotation: Math.round(vectorNodeRotation(n, parent) * 100) / 100,
|
|
2917
4024
|
bounds: b && { x: Math.round(b.x * 100) / 100, y: Math.round(b.y * 100) / 100, width: Math.round(b.width * 100) / 100, height: Math.round(b.height * 100) / 100 },
|
|
2918
4025
|
visible: n.visible !== false,
|
|
2919
4026
|
locked: !!n.locked,
|
|
@@ -2987,7 +4094,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
2987
4094
|
run: (_ctx, params) => {
|
|
2988
4095
|
const id = controller.addPath(params.d, { ...pickStyle(params), ...params.name ? { name: params.name } : {} });
|
|
2989
4096
|
const node = controller.getNode(id);
|
|
2990
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
4097
|
+
if (_optionalChain([node, 'optionalAccess', _127 => _127.type]) === "path" && !node.subpaths.length) {
|
|
2991
4098
|
controller.undo();
|
|
2992
4099
|
fail("The path data has no points.");
|
|
2993
4100
|
}
|
|
@@ -3015,10 +4122,18 @@ function createVectorCommands(controller, options = {}) {
|
|
|
3015
4122
|
id: `${prefix}transform`,
|
|
3016
4123
|
label: "Transform\u2026",
|
|
3017
4124
|
keywords: ["move", "translate", "scale", "resize", "rotate", "flip"],
|
|
3018
|
-
description: "Moves (dx, dy), scales (sx, sy) and/or rotates (degrees clockwise
|
|
4125
|
+
description: "Moves (dx, dy), scales (sx, sy, along the selection box axes) and/or rotates (`rotate`: by degrees clockwise around the combined centre; `angle`: to an absolute rotation, each node around its own centre), in that order.",
|
|
3019
4126
|
params: {
|
|
3020
4127
|
type: "object",
|
|
3021
|
-
properties: {
|
|
4128
|
+
properties: {
|
|
4129
|
+
ids: IDS,
|
|
4130
|
+
dx: { type: "number" },
|
|
4131
|
+
dy: { type: "number" },
|
|
4132
|
+
sx: { type: "number" },
|
|
4133
|
+
sy: { type: "number" },
|
|
4134
|
+
rotate: { type: "number" },
|
|
4135
|
+
angle: { type: "number" }
|
|
4136
|
+
}
|
|
3022
4137
|
},
|
|
3023
4138
|
enabled: enabledFor,
|
|
3024
4139
|
undoable: true,
|
|
@@ -3027,6 +4142,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
3027
4142
|
if (params.dx || params.dy) check(controller.translate(_nullishCoalesce(params.dx, () => ( 0)), _nullishCoalesce(params.dy, () => ( 0)), { ids: target }));
|
|
3028
4143
|
if (params.sx !== void 0 || params.sy !== void 0) check(controller.scale(_nullishCoalesce(params.sx, () => ( 1)), _nullishCoalesce(_nullishCoalesce(params.sy, () => ( params.sx)), () => ( 1)), { ids: target }));
|
|
3029
4144
|
if (params.rotate) check(controller.rotate(params.rotate, { ids: target }));
|
|
4145
|
+
if (params.angle !== void 0) check(controller.setRotation(params.angle, { ids: target }));
|
|
3030
4146
|
return { ok: true };
|
|
3031
4147
|
}
|
|
3032
4148
|
},
|
|
@@ -3111,7 +4227,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
3111
4227
|
enabled: enabledFor,
|
|
3112
4228
|
undoable: true,
|
|
3113
4229
|
run: (_ctx, params) => {
|
|
3114
|
-
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]) });
|
|
3115
4231
|
if (!id) fail("Only nodes that share a parent can be grouped.");
|
|
3116
4232
|
return { id };
|
|
3117
4233
|
}
|
|
@@ -3150,7 +4266,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
3150
4266
|
keywords: ["download", "save", "markup"],
|
|
3151
4267
|
description: "Returns the document as SVG markup.",
|
|
3152
4268
|
params: { type: "object", properties: { precision: { type: "integer", minimum: 0, maximum: 8 } } },
|
|
3153
|
-
run: (_ctx, params) => ({ svg: controller.toSvg({ precision: _optionalChain([params, 'optionalAccess',
|
|
4269
|
+
run: (_ctx, params) => ({ svg: controller.toSvg({ precision: _optionalChain([params, 'optionalAccess', _129 => _129.precision]) }) })
|
|
3154
4270
|
},
|
|
3155
4271
|
{
|
|
3156
4272
|
...base,
|
|
@@ -3239,5 +4355,23 @@ function createVectorCommands(controller, options = {}) {
|
|
|
3239
4355
|
|
|
3240
4356
|
|
|
3241
4357
|
|
|
3242
|
-
|
|
4358
|
+
|
|
4359
|
+
|
|
4360
|
+
|
|
4361
|
+
|
|
4362
|
+
|
|
4363
|
+
|
|
4364
|
+
|
|
4365
|
+
|
|
4366
|
+
|
|
4367
|
+
|
|
4368
|
+
|
|
4369
|
+
|
|
4370
|
+
|
|
4371
|
+
|
|
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;
|
|
3243
4377
|
//# sourceMappingURL=index.cjs.map
|