@pixerate/editor 0.34.0 → 0.36.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 +14 -0
- package/dist/canvas/index.cjs +178 -623
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +10 -1
- package/dist/canvas/index.d.ts +10 -1
- package/dist/canvas/index.js +35 -480
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-UG5DVXJK.cjs → chunk-BGNPT5PE.cjs} +446 -6
- package/dist/chunk-BGNPT5PE.cjs.map +1 -0
- package/dist/{chunk-RK6HUKDI.js → chunk-EGPIVUEF.js} +451 -11
- package/dist/chunk-EGPIVUEF.js.map +1 -0
- package/dist/vector/index.cjs +2943 -401
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +696 -24
- package/dist/vector/index.d.ts +696 -24
- package/dist/vector/index.js +2880 -338
- package/dist/vector/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-RK6HUKDI.js.map +0 -1
- package/dist/chunk-UG5DVXJK.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; var _class2;
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;
|
|
2
2
|
|
|
3
3
|
var _chunkQKLRAA4Tcjs = require('../chunk-QKLRAA4T.cjs');
|
|
4
4
|
|
|
@@ -17,20 +17,447 @@ var _chunkQKLRAA4Tcjs = require('../chunk-QKLRAA4T.cjs');
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
var _chunkBGNPT5PEcjs = require('../chunk-BGNPT5PE.cjs');
|
|
21
35
|
|
|
22
36
|
// src/vector/types.ts
|
|
23
|
-
var
|
|
37
|
+
var VECTOR_CORNER_KINDS = ["round", "inverted", "chamfer"];
|
|
38
|
+
var VECTOR_ARROWHEADS = ["triangle", "arrow", "open", "circle", "square", "bar"];
|
|
39
|
+
var VECTOR_TOOLS = ["select", "direct", "pen", "pencil", "rect", "ellipse", "polygon", "star", "line", "scissors", "knife", "eraser"];
|
|
24
40
|
var VECTOR_TRANSFORM_HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w", "rotate"];
|
|
25
41
|
|
|
26
|
-
// src/vector/
|
|
42
|
+
// src/vector/corners.ts
|
|
27
43
|
var EPS = 1e-9;
|
|
44
|
+
var MIN_SINE = 1e-6;
|
|
45
|
+
var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
46
|
+
var add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
47
|
+
var mul = (a, k) => ({ x: a.x * k, y: a.y * k });
|
|
48
|
+
var dot = (a, b) => a.x * b.x + a.y * b.y;
|
|
49
|
+
var cross = (a, b) => a.x * b.y - a.y * b.x;
|
|
50
|
+
var len = (a) => Math.hypot(a.x, a.y);
|
|
51
|
+
var pt = (p) => [p.x, p.y];
|
|
52
|
+
var vec = (p) => ({ x: p[0], y: p[1] });
|
|
53
|
+
var onAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS && Math.abs(h.y - a.y) < EPS;
|
|
54
|
+
var Side = class {
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
constructor(a, h1, h2, b) {
|
|
59
|
+
this.straight = onAnchor(a, h1) && onAnchor(b, h2);
|
|
60
|
+
this.curve = this.straight ? [pt(a), pt(b)] : [pt(a), pt(_nullishCoalesce(h1, () => ( a))), pt(_nullishCoalesce(h2, () => ( b))), pt(b)];
|
|
61
|
+
this.length = this.straight ? Math.hypot(b.x - a.x, b.y - a.y) : _chunkBGNPT5PEcjs.segmentLength.call(void 0, this.curve);
|
|
62
|
+
}
|
|
63
|
+
/** Unit direction the side leaves the corner in (towards its first control point off the corner). */
|
|
64
|
+
direction() {
|
|
65
|
+
const p0 = vec(this.curve[0]);
|
|
66
|
+
for (const q of this.curve.slice(1)) {
|
|
67
|
+
const d = sub(vec(q), p0);
|
|
68
|
+
const l = len(d);
|
|
69
|
+
if (l > EPS) return mul(d, 1 / l);
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/** The point `s` along the side from the corner: its parameter, position and the direction the side runs on in there. */
|
|
74
|
+
at(s, u) {
|
|
75
|
+
if (this.straight) {
|
|
76
|
+
const t2 = this.length > EPS ? Math.min(1, Math.max(0, s / this.length)) : 0;
|
|
77
|
+
return { t: t2, p: add(vec(this.curve[0]), mul(u, s)), dir: u };
|
|
78
|
+
}
|
|
79
|
+
const t = _chunkBGNPT5PEcjs.segmentParamAtLength.call(void 0, this.curve, s, this.length);
|
|
80
|
+
const d = vec(_chunkBGNPT5PEcjs.segmentDerivative.call(void 0, this.curve, t));
|
|
81
|
+
const l = len(d);
|
|
82
|
+
return { t, p: vec(_chunkBGNPT5PEcjs.segmentPoint.call(void 0, this.curve, t)), dir: l > EPS ? mul(d, 1 / l) : u };
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
function cornerOf(sp, s, i) {
|
|
86
|
+
const n = sp.anchors.length;
|
|
87
|
+
if (n < 2 || !sp.closed && (i === 0 || i === n - 1)) return null;
|
|
88
|
+
if (n === 2 && sp.closed) return null;
|
|
89
|
+
const a = sp.anchors[i];
|
|
90
|
+
if (a.type === "smooth" || a.type === "symmetric") return null;
|
|
91
|
+
const prev = sp.anchors[(i - 1 + n) % n];
|
|
92
|
+
const next = sp.anchors[(i + 1) % n];
|
|
93
|
+
const sideU = new Side(a, a.in, prev.out, prev);
|
|
94
|
+
const sideV = new Side(a, a.out, next.in, next);
|
|
95
|
+
const u = sideU.direction();
|
|
96
|
+
const v2 = sideV.direction();
|
|
97
|
+
if (!u || !v2) return null;
|
|
98
|
+
const lu = sideU.length;
|
|
99
|
+
const lv = sideV.length;
|
|
100
|
+
if (!(lu > EPS && lv > EPS) || !Number.isFinite(lu) || !Number.isFinite(lv)) return null;
|
|
101
|
+
const cos = Math.max(-1, Math.min(1, dot(u, v2)));
|
|
102
|
+
const sin = Math.abs(cross(u, v2));
|
|
103
|
+
if (sin <= MIN_SINE) return null;
|
|
104
|
+
return { subpath: s, index: i, at: { x: a.x, y: a.y }, u, v: v2, lu, lv, cos, sin, angle: Math.atan2(sin, cos) * 180 / Math.PI, maxRadius: Math.min(lu, lv) / 2 * sin / (1 + cos), sideU, sideV };
|
|
105
|
+
}
|
|
106
|
+
var strip = ({ sideU: _u, sideV: _v, ...info }) => info;
|
|
107
|
+
function vectorCornerAt(sp, index, subpath = 0) {
|
|
108
|
+
const c = cornerOf(sp, subpath, index);
|
|
109
|
+
return c ? strip(c) : null;
|
|
110
|
+
}
|
|
111
|
+
function vectorCorners(subpaths) {
|
|
112
|
+
const out = [];
|
|
113
|
+
subpaths.forEach((sp, s) => {
|
|
114
|
+
for (let i = 0; i < sp.anchors.length; i++) {
|
|
115
|
+
const c = cornerOf(sp, s, i);
|
|
116
|
+
if (c) out.push(strip(c));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
var cornerSetback = (c, r) => r * (1 + c.cos) / c.sin;
|
|
122
|
+
var fitCornerRadius = (c, radius) => Number.isFinite(radius) ? Math.min(Math.max(0, radius), c.maxRadius) : 0;
|
|
123
|
+
var cornerBisectorPoint = (c, k) => add(c.at, mul(add(c.u, c.v), k));
|
|
124
|
+
var cornerRadiusChange = (c, d) => dot(d, add(c.u, c.v)) * c.sin / (2 + 2 * c.cos);
|
|
125
|
+
function arcHandle(cos) {
|
|
126
|
+
const c = Math.max(-1, Math.min(1, cos));
|
|
127
|
+
const sinHalf = Math.sqrt(Math.max(0, (1 - c) / 2));
|
|
128
|
+
const cosHalf = Math.sqrt(Math.max(0, (1 + c) / 2));
|
|
129
|
+
return 4 / 3 * sinHalf / (1 + cosHalf);
|
|
130
|
+
}
|
|
131
|
+
function squareTo(a, b) {
|
|
132
|
+
const w = sub(b, mul(a, dot(a, b)));
|
|
133
|
+
const l = len(w);
|
|
134
|
+
return l > EPS ? mul(w, 1 / l) : { x: 0, y: 0 };
|
|
135
|
+
}
|
|
136
|
+
function cutCorner(c, radius, kind) {
|
|
137
|
+
const s = Math.min(cornerSetback(c, radius), Math.min(c.lu, c.lv) / 2);
|
|
138
|
+
if (!(s > EPS)) return null;
|
|
139
|
+
const a = c.sideU.at(s, c.u);
|
|
140
|
+
const b = c.sideV.at(s, c.v);
|
|
141
|
+
if (kind === "chamfer") return { tu: a.t, tv: b.t, start: a.p, out: null, in: null, end: b.p };
|
|
142
|
+
if (kind === "inverted") {
|
|
143
|
+
const h = s * arcHandle(dot(a.dir, b.dir));
|
|
144
|
+
return { tu: a.t, tv: b.t, start: a.p, out: add(a.p, mul(squareTo(a.dir, b.dir), h)), in: add(b.p, mul(squareTo(b.dir, a.dir), h)), end: b.p };
|
|
145
|
+
}
|
|
146
|
+
const d1 = mul(a.dir, -1);
|
|
147
|
+
const d2 = mul(b.dir, -1);
|
|
148
|
+
const turn = Math.acos(Math.max(-1, Math.min(1, -dot(d1, d2))));
|
|
149
|
+
const chord = len(sub(b.p, a.p));
|
|
150
|
+
const k = turn < 1e-9 ? chord / 3 : 4 / 3 * Math.tan(turn / 4) * chord / (2 * Math.sin(turn / 2));
|
|
151
|
+
if (!Number.isFinite(k)) return null;
|
|
152
|
+
return { tu: a.t, tv: b.t, start: a.p, out: add(a.p, mul(d1, k)), in: add(b.p, mul(d2, k)), end: b.p };
|
|
153
|
+
}
|
|
154
|
+
var radiusOf = (a) => {
|
|
155
|
+
const r = _optionalChain([a, 'access', _2 => _2.corner, 'optionalAccess', _3 => _3.radius]);
|
|
156
|
+
return typeof r === "number" && r > 0 && Number.isFinite(r) ? r : 0;
|
|
157
|
+
};
|
|
158
|
+
function hasVectorCorners(subpaths) {
|
|
159
|
+
return subpaths.some((sp) => sp.anchors.some((a) => radiusOf(a) > 0));
|
|
160
|
+
}
|
|
161
|
+
function expandVectorCornersOf(sp, subpath = 0) {
|
|
162
|
+
const n = sp.anchors.length;
|
|
163
|
+
const cuts = sp.anchors.map((a, i) => {
|
|
164
|
+
const r = radiusOf(a);
|
|
165
|
+
if (!r) return null;
|
|
166
|
+
const c = cornerOf(sp, subpath, i);
|
|
167
|
+
if (!c) return null;
|
|
168
|
+
const fitted = fitCornerRadius(c, r);
|
|
169
|
+
return fitted > EPS ? cutCorner(c, fitted, _nullishCoalesce(_optionalChain([a, 'access', _4 => _4.corner, 'optionalAccess', _5 => _5.kind]), () => ( "round"))) : null;
|
|
170
|
+
});
|
|
171
|
+
if (!cuts.some(Boolean)) return { subpath: { closed: sp.closed, anchors: sp.anchors.map(plain) }, sources: sp.anchors.map((_, i) => i) };
|
|
172
|
+
const trimmed = sp.anchors.map((a, j) => {
|
|
173
|
+
const k = (j + 1) % n;
|
|
174
|
+
if (k === 0 && !sp.closed) return void 0;
|
|
175
|
+
const b = sp.anchors[k];
|
|
176
|
+
const from = cuts[j];
|
|
177
|
+
const to = cuts[k];
|
|
178
|
+
if (!from && !to) return void 0;
|
|
179
|
+
if (onAnchor(a, a.out) && onAnchor(b, b.in)) return { out: null, in: null };
|
|
180
|
+
const s0 = from ? from.tv : 0;
|
|
181
|
+
const s1 = to ? 1 - to.tu : 1;
|
|
182
|
+
const seg = [pt(a), pt(_nullishCoalesce(a.out, () => ( a))), pt(_nullishCoalesce(b.in, () => ( b))), pt(b)];
|
|
183
|
+
const part = _chunkBGNPT5PEcjs.subSegment.call(void 0, seg, s0, Math.max(s0, s1));
|
|
184
|
+
return { out: vec(part[1]), in: vec(part[2]) };
|
|
185
|
+
});
|
|
186
|
+
const anchors = [];
|
|
187
|
+
const sources = [];
|
|
188
|
+
let closing = null;
|
|
189
|
+
const handleIn = (i, a) => {
|
|
190
|
+
const t = trimmed[(i - 1 + n) % n];
|
|
191
|
+
return t ? t.in : _nullishCoalesce(a.in, () => ( null));
|
|
192
|
+
};
|
|
193
|
+
const handleOut = (i, a) => {
|
|
194
|
+
const t = trimmed[i];
|
|
195
|
+
return t ? t.out : _nullishCoalesce(a.out, () => ( null));
|
|
196
|
+
};
|
|
197
|
+
sp.anchors.forEach((a, i) => {
|
|
198
|
+
const c = cuts[i];
|
|
199
|
+
if (!c) {
|
|
200
|
+
anchors.push(withHandles({ x: a.x, y: a.y, ...a.type ? { type: a.type } : {} }, handleIn(i, a), handleOut(i, a)));
|
|
201
|
+
sources.push(i);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const start = withHandles({ x: c.start.x, y: c.start.y }, handleIn(i, a), c.out);
|
|
205
|
+
const end = withHandles({ x: c.end.x, y: c.end.y }, c.in, handleOut(i, a));
|
|
206
|
+
if (i === 0 && sp.closed) closing = start;
|
|
207
|
+
else {
|
|
208
|
+
anchors.push(start);
|
|
209
|
+
sources.push(i);
|
|
210
|
+
}
|
|
211
|
+
anchors.push(end);
|
|
212
|
+
sources.push(i);
|
|
213
|
+
});
|
|
214
|
+
if (closing) {
|
|
215
|
+
anchors.push(closing);
|
|
216
|
+
sources.push(0);
|
|
217
|
+
}
|
|
218
|
+
return { subpath: { closed: sp.closed, anchors }, sources };
|
|
219
|
+
}
|
|
220
|
+
function withHandles(a, hin, hout) {
|
|
221
|
+
if (hin && !onAnchor(a, hin)) a.in = { x: hin.x, y: hin.y };
|
|
222
|
+
if (hout && !onAnchor(a, hout)) a.out = { x: hout.x, y: hout.y };
|
|
223
|
+
return a;
|
|
224
|
+
}
|
|
225
|
+
function plain(a) {
|
|
226
|
+
if (!("corner" in a)) return a;
|
|
227
|
+
const { corner: _c, ...rest } = a;
|
|
228
|
+
return rest;
|
|
229
|
+
}
|
|
230
|
+
function expandVectorCorners(subpaths) {
|
|
231
|
+
if (!hasVectorCorners(subpaths)) return subpaths;
|
|
232
|
+
return subpaths.map((sp, s) => sp.anchors.some((a) => radiusOf(a) > 0) ? expandVectorCornersOf(sp, s).subpath : sp);
|
|
233
|
+
}
|
|
234
|
+
function setAnchorCorner(a, radius, kind) {
|
|
235
|
+
const { corner, ...rest } = a;
|
|
236
|
+
const r = Number.isFinite(radius) ? Math.max(0, radius) : 0;
|
|
237
|
+
const k = _nullishCoalesce(kind, () => ( _optionalChain([corner, 'optionalAccess', _6 => _6.kind])));
|
|
238
|
+
if (r <= 0 && !kind) return rest;
|
|
239
|
+
return { ...rest, corner: { radius: r, ...k && k !== "round" ? { kind: k } : {} } };
|
|
240
|
+
}
|
|
241
|
+
function nextVectorCornerKind(kind) {
|
|
242
|
+
return kind === "inverted" ? "chamfer" : kind === "chamfer" ? "round" : "inverted";
|
|
243
|
+
}
|
|
244
|
+
function setSubpathCorners(sp, radius, kind) {
|
|
245
|
+
return { closed: sp.closed, anchors: sp.anchors.map((a) => setAnchorCorner(a, radius, kind)) };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/vector/stroke.ts
|
|
249
|
+
var KAPPA = 0.5522847498307936;
|
|
250
|
+
var NOTCH = 0.7;
|
|
251
|
+
function isClosedBezierShape(path) {
|
|
252
|
+
const contours = _chunkBGNPT5PEcjs.pathToContours.call(void 0, path).filter((c) => c.segments.length > 0);
|
|
253
|
+
return contours.length > 0 && contours.every((c) => c.closed);
|
|
254
|
+
}
|
|
255
|
+
function effectiveStrokeAlign(style, closedShape) {
|
|
256
|
+
const a = _nullishCoalesce(style.strokeAlign, () => ( "center"));
|
|
257
|
+
return closedShape ? a : "center";
|
|
258
|
+
}
|
|
259
|
+
function hasStrokeExtras(style) {
|
|
260
|
+
return (_nullishCoalesce(style.strokeAlign, () => ( "center"))) !== "center" || !!style.startArrow || !!style.endArrow;
|
|
261
|
+
}
|
|
262
|
+
var capExtent = (style, width) => style.strokeLinecap === "round" || style.strokeLinecap === "square" ? width / 2 : 0;
|
|
263
|
+
function arrowheadWeight(style, end) {
|
|
264
|
+
const scale = end === "start" ? style.startArrowScale : style.endArrowScale;
|
|
265
|
+
const s = typeof scale === "number" && scale > 0 && Number.isFinite(scale) ? scale : 1;
|
|
266
|
+
return Math.max(0.25, (_nullishCoalesce(style.strokeWidth, () => ( 1))) * s);
|
|
267
|
+
}
|
|
268
|
+
function arrowheadInset(kind, hw, cap) {
|
|
269
|
+
const l = 4 * hw;
|
|
270
|
+
const solid = (back) => Math.max(back / 2, cap);
|
|
271
|
+
switch (kind) {
|
|
272
|
+
case "triangle":
|
|
273
|
+
case "circle":
|
|
274
|
+
case "square":
|
|
275
|
+
return solid(l);
|
|
276
|
+
case "arrow":
|
|
277
|
+
return solid(NOTCH * l);
|
|
278
|
+
case "bar":
|
|
279
|
+
return solid(0.75 * hw);
|
|
280
|
+
case "open":
|
|
281
|
+
return Math.max(hw * Math.sqrt(l * l + l * l / 4) / (l / 2), cap);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
var v = (x, y) => ({ x, y });
|
|
285
|
+
var plus = (a, b, k = 1) => ({ x: a.x + b.x * k, y: a.y + b.y * k });
|
|
286
|
+
var unit = (a) => {
|
|
287
|
+
const l = Math.hypot(a.x, a.y);
|
|
288
|
+
return l > 0 ? { x: a.x / l, y: a.y / l } : { x: 1, y: 0 };
|
|
289
|
+
};
|
|
290
|
+
function polygon(pts) {
|
|
291
|
+
return [{ type: "M", x: pts[0].x, y: pts[0].y }, ...pts.slice(1).map((p) => ({ type: "L", x: p.x, y: p.y })), { type: "Z" }];
|
|
292
|
+
}
|
|
293
|
+
function circle(c, r) {
|
|
294
|
+
const k = r * KAPPA;
|
|
295
|
+
const { x, y } = c;
|
|
296
|
+
return [
|
|
297
|
+
{ type: "M", x: x + r, y },
|
|
298
|
+
{ type: "C", x1: x + r, y1: y + k, x2: x + k, y2: y + r, x, y: y + r },
|
|
299
|
+
{ type: "C", x1: x - k, y1: y + r, x2: x - r, y2: y + k, x: x - r, y },
|
|
300
|
+
{ type: "C", x1: x - r, y1: y - k, x2: x - k, y2: y - r, x, y: y - r },
|
|
301
|
+
{ type: "C", x1: x + k, y1: y - r, x2: x + r, y2: y - k, x: x + r, y },
|
|
302
|
+
{ type: "Z" }
|
|
303
|
+
];
|
|
304
|
+
}
|
|
305
|
+
function arrowheadPath(kind, tip, dir, hw) {
|
|
306
|
+
const n = v(-dir.y, dir.x);
|
|
307
|
+
const l = 4 * hw;
|
|
308
|
+
const half = l / 2;
|
|
309
|
+
const base = plus(tip, dir, -l);
|
|
310
|
+
switch (kind) {
|
|
311
|
+
case "triangle":
|
|
312
|
+
return polygon([tip, plus(base, n, half), plus(base, n, -half)]);
|
|
313
|
+
case "arrow":
|
|
314
|
+
return polygon([tip, plus(base, n, half), plus(tip, dir, -NOTCH * l), plus(base, n, -half)]);
|
|
315
|
+
case "open": {
|
|
316
|
+
const t = hw;
|
|
317
|
+
const a = plus(base, n, half);
|
|
318
|
+
const b = plus(base, n, -half);
|
|
319
|
+
const ua = unit(plus(a, tip, -1));
|
|
320
|
+
const ub = unit(plus(b, tip, -1));
|
|
321
|
+
const na = v(-ua.y, ua.x);
|
|
322
|
+
const nb = v(ub.y, -ub.x);
|
|
323
|
+
const inner = plus(tip, dir, -(t * Math.hypot(a.x - tip.x, a.y - tip.y)) / half);
|
|
324
|
+
return polygon([tip, a, plus(a, na, t), inner, plus(b, nb, t), b]);
|
|
325
|
+
}
|
|
326
|
+
case "circle":
|
|
327
|
+
return circle(plus(tip, dir, -half), half);
|
|
328
|
+
case "square":
|
|
329
|
+
return polygon([plus(tip, n, half), plus(base, n, half), plus(base, n, -half), plus(tip, n, -half)]);
|
|
330
|
+
case "bar": {
|
|
331
|
+
const t = 0.75 * hw;
|
|
332
|
+
return polygon([plus(tip, n, half), plus(plus(tip, n, half), dir, -t), plus(plus(tip, n, -half), dir, -t), plus(tip, n, -half)]);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
var toVec = (p) => ({ x: p[0], y: p[1] });
|
|
337
|
+
var segLen = (s) => _chunkBGNPT5PEcjs.segmentLength.call(void 0, s);
|
|
338
|
+
function endOf(segs, atStart) {
|
|
339
|
+
const long = (s2) => segLen(s2) > 1e-9;
|
|
340
|
+
if (atStart) {
|
|
341
|
+
const s2 = _nullishCoalesce(segs.find(long), () => ( segs[0]));
|
|
342
|
+
const t2 = _chunkBGNPT5PEcjs.segmentTangent.call(void 0, s2, 0);
|
|
343
|
+
return { p: toVec(segs[0][0]), out: v(-t2[0], -t2[1]) };
|
|
344
|
+
}
|
|
345
|
+
const s = _nullishCoalesce([...segs].reverse().find(long), () => ( segs[segs.length - 1]));
|
|
346
|
+
const t = _chunkBGNPT5PEcjs.segmentTangent.call(void 0, s, 1);
|
|
347
|
+
const last = segs[segs.length - 1];
|
|
348
|
+
return { p: toVec(last[last.length - 1]), out: v(t[0], t[1]) };
|
|
349
|
+
}
|
|
350
|
+
function pointAlong(segs, fromStart, dist3) {
|
|
351
|
+
const order = fromStart ? segs : [...segs].reverse();
|
|
352
|
+
let acc = 0;
|
|
353
|
+
for (const s of order) {
|
|
354
|
+
const l = segLen(s);
|
|
355
|
+
if (acc + l >= dist3) {
|
|
356
|
+
const want = fromStart ? dist3 - acc : l - (dist3 - acc);
|
|
357
|
+
const pts = _chunkBGNPT5PEcjs.trimSegments.call(void 0, [s], 0, want);
|
|
358
|
+
const p = pts.length ? pts[0][pts[0].length - 1] : s[0];
|
|
359
|
+
return toVec(p);
|
|
360
|
+
}
|
|
361
|
+
acc += l;
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
function vectorStrokeParts(path, style) {
|
|
366
|
+
const width = _nullishCoalesce(style.strokeWidth, () => ( 1));
|
|
367
|
+
const contours = _chunkBGNPT5PEcjs.pathToContours.call(void 0, path).filter((c) => c.segments.length > 0);
|
|
368
|
+
const closedShape = contours.length > 0 && contours.every((c) => c.closed);
|
|
369
|
+
const align = effectiveStrokeAlign(style, closedShape);
|
|
370
|
+
const drawWidth = align === "center" ? width : width * 2;
|
|
371
|
+
const heads = [];
|
|
372
|
+
if (!contours.length || !style.startArrow && !style.endArrow) return { align, line: path, drawWidth, heads };
|
|
373
|
+
const first = contours[0];
|
|
374
|
+
const last = contours[contours.length - 1];
|
|
375
|
+
const cap = capExtent(style, width);
|
|
376
|
+
const tipMode = style.arrowAlign === "tip";
|
|
377
|
+
const trims = contours.map(() => [0, 0]);
|
|
378
|
+
const place = (end, contour, ci) => {
|
|
379
|
+
const kind = end === "start" ? style.startArrow : style.endArrow;
|
|
380
|
+
if (!kind || contour.closed) return;
|
|
381
|
+
const hw = arrowheadWeight(style, end);
|
|
382
|
+
const inset = arrowheadInset(kind, hw, cap);
|
|
383
|
+
const atStart = end === "start";
|
|
384
|
+
const { p, out } = endOf(contour.segments, atStart);
|
|
385
|
+
let tip;
|
|
386
|
+
let dir;
|
|
387
|
+
if (tipMode) {
|
|
388
|
+
const q = pointAlong(contour.segments, atStart, inset);
|
|
389
|
+
const d = q ? v(p.x - q.x, p.y - q.y) : null;
|
|
390
|
+
dir = d && Math.hypot(d.x, d.y) > 1e-9 ? unit(d) : out;
|
|
391
|
+
tip = p;
|
|
392
|
+
trims[ci][atStart ? 0 : 1] = inset;
|
|
393
|
+
} else {
|
|
394
|
+
tip = plus(p, out, inset);
|
|
395
|
+
dir = out;
|
|
396
|
+
}
|
|
397
|
+
heads.push({ end, kind, tip, dir, inset, path: arrowheadPath(kind, tip, dir, hw) });
|
|
398
|
+
};
|
|
399
|
+
place("start", first, 0);
|
|
400
|
+
place("end", last, contours.length - 1);
|
|
401
|
+
if (!tipMode || !heads.length) return { align, line: path, drawWidth, heads };
|
|
402
|
+
const trimmed = contours.map((c, i) => {
|
|
403
|
+
const [s0, s1] = trims[i];
|
|
404
|
+
if (!s0 && !s1) return c;
|
|
405
|
+
const lengths = c.segments.map(segLen);
|
|
406
|
+
const total = lengths.reduce((a, b) => a + b, 0);
|
|
407
|
+
return { closed: false, segments: total - s1 > s0 ? _chunkBGNPT5PEcjs.trimSegments.call(void 0, c.segments, s0, total - s1, lengths) : [] };
|
|
408
|
+
});
|
|
409
|
+
return { align, line: _chunkBGNPT5PEcjs.contoursToPath.call(void 0, trimmed), drawWidth, heads };
|
|
410
|
+
}
|
|
411
|
+
function vectorStrokeOutlinePath(path, style) {
|
|
412
|
+
const parts = vectorStrokeParts(path, style);
|
|
413
|
+
const stroke = _chunkBGNPT5PEcjs.outlineStroke.call(void 0, parts.line, {
|
|
414
|
+
width: parts.drawWidth,
|
|
415
|
+
join: _nullishCoalesce(style.strokeLinejoin, () => ( "miter")),
|
|
416
|
+
cap: _nullishCoalesce(style.strokeLinecap, () => ( "butt")),
|
|
417
|
+
..._optionalChain([style, 'access', _7 => _7.strokeDasharray, 'optionalAccess', _8 => _8.length]) ? { dash: style.strokeDasharray } : {}
|
|
418
|
+
});
|
|
419
|
+
const fill = { path, fillRule: _nullishCoalesce(style.fillRule, () => ( "nonzero")) };
|
|
420
|
+
let area = stroke;
|
|
421
|
+
if (parts.align === "inside") area = _chunkBGNPT5PEcjs.pathBoolean.call(void 0, "intersection", [stroke, fill]).path;
|
|
422
|
+
else if (parts.align === "outside") area = _chunkBGNPT5PEcjs.pathBoolean.call(void 0, "difference", [stroke, fill]).path;
|
|
423
|
+
if (!parts.heads.length) return area;
|
|
424
|
+
return _chunkBGNPT5PEcjs.pathBoolean.call(void 0, "union", [area, ...parts.heads.map((h) => h.path)]).path;
|
|
425
|
+
}
|
|
426
|
+
function hitTestVectorStroke(path, p, style, tolerance = 0) {
|
|
427
|
+
const parts = vectorStrokeParts(path, style);
|
|
428
|
+
const point = [p.x, p.y];
|
|
429
|
+
const width = _nullishCoalesce(style.strokeWidth, () => ( 1));
|
|
430
|
+
for (const h of parts.heads) {
|
|
431
|
+
if (_chunkBGNPT5PEcjs.pointInBezierPath.call(void 0, h.path, point) || tolerance > 0 && _chunkBGNPT5PEcjs.distanceToBezierPath.call(void 0, h.path, point) <= tolerance) return true;
|
|
432
|
+
}
|
|
433
|
+
if (!parts.line.length) return false;
|
|
434
|
+
const d = _chunkBGNPT5PEcjs.distanceToBezierPath.call(void 0, parts.line, point);
|
|
435
|
+
if (parts.align === "center") return d <= width / 2 + tolerance;
|
|
436
|
+
if (d > width + tolerance) return false;
|
|
437
|
+
if (d <= tolerance) return true;
|
|
438
|
+
const inside = _chunkBGNPT5PEcjs.pointInBezierPath.call(void 0, path, point, _nullishCoalesce(style.fillRule, () => ( "nonzero")));
|
|
439
|
+
return parts.align === "inside" ? inside : !inside;
|
|
440
|
+
}
|
|
441
|
+
function vectorStrokeReach(style) {
|
|
442
|
+
const width = _nullishCoalesce(style.strokeWidth, () => ( 1));
|
|
443
|
+
const align = _nullishCoalesce(style.strokeAlign, () => ( "center"));
|
|
444
|
+
let reach = align === "center" ? width / 2 : align === "outside" ? width : 0;
|
|
445
|
+
if (style.strokeLinejoin !== "round" && style.strokeLinejoin !== "bevel") reach *= 4;
|
|
446
|
+
for (const end of ["start", "end"]) {
|
|
447
|
+
if (end === "start" ? style.startArrow : style.endArrow) reach = Math.max(reach, 8 * arrowheadWeight(style, end) + width);
|
|
448
|
+
}
|
|
449
|
+
return reach;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/vector/geometry.ts
|
|
453
|
+
var EPS2 = 1e-9;
|
|
28
454
|
var cloneAnchor = (a) => ({
|
|
29
455
|
x: a.x,
|
|
30
456
|
y: a.y,
|
|
31
457
|
...a.in ? { in: { x: a.in.x, y: a.in.y } } : {},
|
|
32
458
|
...a.out ? { out: { x: a.out.x, y: a.out.y } } : {},
|
|
33
|
-
...a.type && a.type !== "corner" ? { type: a.type } : {}
|
|
459
|
+
...a.type && a.type !== "corner" ? { type: a.type } : {},
|
|
460
|
+
...a.corner ? { corner: { ...a.corner } } : {}
|
|
34
461
|
});
|
|
35
462
|
var cloneSubpath = (s) => ({ anchors: s.anchors.map(cloneAnchor), closed: s.closed });
|
|
36
463
|
function cloneVectorNode(node) {
|
|
@@ -48,7 +475,7 @@ function hasPaint(paint) {
|
|
|
48
475
|
return true;
|
|
49
476
|
}
|
|
50
477
|
var handleOf = (a, side) => _nullishCoalesce(a[side], () => ( { x: a.x, y: a.y }));
|
|
51
|
-
var isOnAnchor = (a, h) => !h || Math.abs(h.x - a.x) <
|
|
478
|
+
var isOnAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS2 && Math.abs(h.y - a.y) < EPS2;
|
|
52
479
|
function subpathSegmentCount(sp) {
|
|
53
480
|
const n = sp.anchors.length;
|
|
54
481
|
if (n < 2) return 0;
|
|
@@ -62,9 +489,9 @@ function subpathSegment(sp, i) {
|
|
|
62
489
|
const c2 = handleOf(b, "in");
|
|
63
490
|
return [[a.x, a.y], [c1.x, c1.y], [c2.x, c2.y], [b.x, b.y]];
|
|
64
491
|
}
|
|
65
|
-
function subpathsToBezier(subpaths) {
|
|
492
|
+
function subpathsToBezier(subpaths, options = {}) {
|
|
66
493
|
const out = [];
|
|
67
|
-
for (const sp of subpaths) {
|
|
494
|
+
for (const sp of options.corners === false ? subpaths : expandVectorCorners(subpaths)) {
|
|
68
495
|
if (sp.anchors.length === 0) continue;
|
|
69
496
|
const first = sp.anchors[0];
|
|
70
497
|
out.push({ type: "M", x: first.x, y: first.y });
|
|
@@ -84,7 +511,7 @@ function vectorNodeToBezier(node) {
|
|
|
84
511
|
}
|
|
85
512
|
function vectorPathData(input, precision = 3) {
|
|
86
513
|
const subpaths = Array.isArray(input) ? input : input.subpaths;
|
|
87
|
-
return
|
|
514
|
+
return _chunkBGNPT5PEcjs.bezierPathToSvg.call(void 0, subpathsToBezier(subpaths), { precision });
|
|
88
515
|
}
|
|
89
516
|
function inferAnchorType(a) {
|
|
90
517
|
if (isOnAnchor(a, a.in) || isOnAnchor(a, a.out)) return "corner";
|
|
@@ -94,13 +521,13 @@ function inferAnchorType(a) {
|
|
|
94
521
|
const oy = a.out.y - a.y;
|
|
95
522
|
const li = Math.hypot(ix, iy);
|
|
96
523
|
const lo = Math.hypot(ox, oy);
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
if (Math.abs(
|
|
524
|
+
const cross2 = (ix * oy - iy * ox) / (li * lo);
|
|
525
|
+
const dot2 = (ix * ox + iy * oy) / (li * lo);
|
|
526
|
+
if (Math.abs(cross2) > 1e-3 || dot2 > 0) return "corner";
|
|
100
527
|
return Math.abs(li - lo) <= 1e-3 * Math.max(li, lo) ? "symmetric" : "smooth";
|
|
101
528
|
}
|
|
102
529
|
function bezierToSubpaths(input) {
|
|
103
|
-
const path =
|
|
530
|
+
const path = _chunkBGNPT5PEcjs.toBezierPath.call(void 0, input);
|
|
104
531
|
const out = [];
|
|
105
532
|
let current = null;
|
|
106
533
|
const last = () => current.anchors[current.anchors.length - 1];
|
|
@@ -197,14 +624,14 @@ function nodeMatrix(node) {
|
|
|
197
624
|
function vectorPathWorldBezier(node, parent = IDENTITY_MATRIX) {
|
|
198
625
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
199
626
|
const path = subpathsToBezier(node.subpaths);
|
|
200
|
-
return isIdentityMatrix(m) ? path :
|
|
627
|
+
return isIdentityMatrix(m) ? path : _chunkBGNPT5PEcjs.transformBezierPath.call(void 0, path, m);
|
|
201
628
|
}
|
|
202
629
|
function vectorNodeBounds(node, parent = IDENTITY_MATRIX) {
|
|
203
630
|
if (node.type === "group") {
|
|
204
631
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
205
632
|
return unionBounds(node.children.map((c) => vectorNodeBounds(c, m)));
|
|
206
633
|
}
|
|
207
|
-
const b =
|
|
634
|
+
const b = _chunkBGNPT5PEcjs.bezierPathBounds.call(void 0, vectorPathWorldBezier(node, parent));
|
|
208
635
|
if (!b) return null;
|
|
209
636
|
return { x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY };
|
|
210
637
|
}
|
|
@@ -238,9 +665,13 @@ function hitTestVectorNode(node, p, tolerance = 0, parent = IDENTITY_MATRIX) {
|
|
|
238
665
|
const path = vectorPathWorldBezier(node, parent);
|
|
239
666
|
if (path.length === 0) return false;
|
|
240
667
|
const point = [p.x, p.y];
|
|
241
|
-
if (hasPaint(node.fill) &&
|
|
242
|
-
|
|
243
|
-
return
|
|
668
|
+
if (hasPaint(node.fill) && _chunkBGNPT5PEcjs.pointInBezierPath.call(void 0, path, point, _nullishCoalesce(node.fillRule, () => ( "nonzero")))) return true;
|
|
669
|
+
if (hasPaint(node.stroke)) return hitTestVectorStroke(path, p, node, tolerance);
|
|
670
|
+
return _chunkBGNPT5PEcjs.distanceToBezierPath.call(void 0, path, point) <= tolerance;
|
|
671
|
+
}
|
|
672
|
+
function vectorStrokeOutline(node, parent = IDENTITY_MATRIX) {
|
|
673
|
+
if (!hasPaint(node.stroke) || !((_nullishCoalesce(node.strokeWidth, () => ( 1))) > 0)) return [];
|
|
674
|
+
return vectorStrokeOutlinePath(vectorPathWorldBezier(node, parent), node);
|
|
244
675
|
}
|
|
245
676
|
function hitTestVectorNodes(nodes, p, tolerance = 0) {
|
|
246
677
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
@@ -256,10 +687,10 @@ function nearestSegment(node, p) {
|
|
|
256
687
|
const count = subpathSegmentCount(sp);
|
|
257
688
|
for (let i = 0; i < count; i++) {
|
|
258
689
|
const seg = subpathSegment(sp, i);
|
|
259
|
-
const hit =
|
|
690
|
+
const hit = _chunkBGNPT5PEcjs.segmentNearest.call(void 0, seg, [p.x, p.y]);
|
|
260
691
|
if (!best || hit.distance < best.distance) {
|
|
261
|
-
const
|
|
262
|
-
best = { subpath: s, segment: i, t: hit.t, point: { x:
|
|
692
|
+
const pt2 = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, hit.t);
|
|
693
|
+
best = { subpath: s, segment: i, t: hit.t, point: { x: pt2[0], y: pt2[1] }, distance: hit.distance };
|
|
263
694
|
}
|
|
264
695
|
}
|
|
265
696
|
});
|
|
@@ -293,8 +724,8 @@ function invertMatrix(m) {
|
|
|
293
724
|
if (!det || !Number.isFinite(det)) return IDENTITY_MATRIX;
|
|
294
725
|
return [d / det, -b / det, -c / det, a / det, (c * f - d * e) / det, (b * e - a * f) / det];
|
|
295
726
|
}
|
|
296
|
-
function applyMatrixLinear(m,
|
|
297
|
-
return { x: m[0] *
|
|
727
|
+
function applyMatrixLinear(m, v2) {
|
|
728
|
+
return { x: m[0] * v2.x + m[2] * v2.y, y: m[1] * v2.x + m[3] * v2.y };
|
|
298
729
|
}
|
|
299
730
|
function matrixRotation(m) {
|
|
300
731
|
const [a, b, c, d] = m;
|
|
@@ -328,6 +759,7 @@ function bakeIntoGeometry(node, m) {
|
|
|
328
759
|
if (a.in) next.in = applyMatrix(m, a.in);
|
|
329
760
|
if (a.out) next.out = applyMatrix(m, a.out);
|
|
330
761
|
if (a.type) next.type = a.type;
|
|
762
|
+
if (a.corner) next.corner = { ...a.corner };
|
|
331
763
|
return next;
|
|
332
764
|
})
|
|
333
765
|
}))
|
|
@@ -379,12 +811,12 @@ function moveAnchorHandle(a, side, pos, breakHandles = false) {
|
|
|
379
811
|
if (breakHandles) delete next.type;
|
|
380
812
|
const dx = pos.x - a.x;
|
|
381
813
|
const dy = pos.y - a.y;
|
|
382
|
-
const
|
|
814
|
+
const len2 = Math.hypot(dx, dy);
|
|
383
815
|
if (type === "symmetric") next[other] = { x: a.x - dx, y: a.y - dy };
|
|
384
|
-
else if (type === "smooth" &&
|
|
816
|
+
else if (type === "smooth" && len2 > EPS2) {
|
|
385
817
|
const o = a[other];
|
|
386
|
-
const olen = o ? Math.hypot(o.x - a.x, o.y - a.y) :
|
|
387
|
-
next[other] = { x: a.x - dx /
|
|
818
|
+
const olen = o ? Math.hypot(o.x - a.x, o.y - a.y) : len2;
|
|
819
|
+
next[other] = { x: a.x - dx / len2 * olen, y: a.y - dy / len2 * olen };
|
|
388
820
|
}
|
|
389
821
|
if (isOnAnchor(next, next.in)) delete next.in;
|
|
390
822
|
if (isOnAnchor(next, next.out)) delete next.out;
|
|
@@ -411,15 +843,15 @@ function convertAnchor(sp, index, type) {
|
|
|
411
843
|
else if (prev && next) dir = { x: next.x - prev.x, y: next.y - prev.y };
|
|
412
844
|
else if (next) dir = { x: next.x - a.x, y: next.y - a.y };
|
|
413
845
|
else if (prev) dir = { x: a.x - prev.x, y: a.y - prev.y };
|
|
414
|
-
if (!dir || Math.hypot(dir.x, dir.y) <
|
|
846
|
+
if (!dir || Math.hypot(dir.x, dir.y) < EPS2) return { anchors, closed: sp.closed };
|
|
415
847
|
const dl = Math.hypot(dir.x, dir.y);
|
|
416
848
|
const ux = dir.x / dl;
|
|
417
849
|
const uy = dir.y / dl;
|
|
418
850
|
lenIn = a.in && !isOnAnchor(a, a.in) ? Math.hypot(a.in.x - a.x, a.in.y - a.y) : prev ? Math.hypot(prev.x - a.x, prev.y - a.y) / 3 : 0;
|
|
419
851
|
lenOut = a.out && !isOnAnchor(a, a.out) ? Math.hypot(a.out.x - a.x, a.out.y - a.y) : next ? Math.hypot(next.x - a.x, next.y - a.y) / 3 : 0;
|
|
420
852
|
if (type === "symmetric") lenIn = lenOut = (lenIn + lenOut) / 2 || Math.max(lenIn, lenOut);
|
|
421
|
-
if (lenIn >
|
|
422
|
-
if (lenOut >
|
|
853
|
+
if (lenIn > EPS2) a.in = { x: a.x - ux * lenIn, y: a.y - uy * lenIn };
|
|
854
|
+
if (lenOut > EPS2) a.out = { x: a.x + ux * lenOut, y: a.y + uy * lenOut };
|
|
423
855
|
a.type = type;
|
|
424
856
|
return { anchors, closed: sp.closed };
|
|
425
857
|
}
|
|
@@ -429,7 +861,7 @@ function insertAnchor(sp, segment, t) {
|
|
|
429
861
|
const ia = segment;
|
|
430
862
|
const ib = (segment + 1) % n;
|
|
431
863
|
const seg = subpathSegment(sp, segment);
|
|
432
|
-
const [left, right] =
|
|
864
|
+
const [left, right] = _chunkBGNPT5PEcjs.splitSegment.call(void 0, seg, t);
|
|
433
865
|
const mid = left[left.length - 1];
|
|
434
866
|
const anchor = { x: mid[0], y: mid[1] };
|
|
435
867
|
if (seg.length === 4) {
|
|
@@ -455,37 +887,24 @@ function reverseSubpath(sp) {
|
|
|
455
887
|
if (a.out) next.in = { ...a.out };
|
|
456
888
|
if (a.in) next.out = { ...a.in };
|
|
457
889
|
if (a.type) next.type = a.type;
|
|
890
|
+
if (a.corner) next.corner = { ...a.corner };
|
|
458
891
|
return next;
|
|
459
892
|
});
|
|
460
893
|
return { anchors, closed: sp.closed };
|
|
461
894
|
}
|
|
462
|
-
var
|
|
463
|
-
function rectSubpath(b, radius = 0) {
|
|
895
|
+
var KAPPA2 = 0.5522847498307936;
|
|
896
|
+
function rectSubpath(b, radius = 0, kind) {
|
|
464
897
|
const { x, y, width: w, height: h } = b;
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
return { anchors: [{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }], closed: true };
|
|
468
|
-
}
|
|
469
|
-
const k = r * KAPPA;
|
|
470
|
-
const anchors = [
|
|
471
|
-
{ x: x + r, y, in: { x: x + r - k, y } },
|
|
472
|
-
{ x: x + w - r, y, out: { x: x + w - r + k, y } },
|
|
473
|
-
{ x: x + w, y: y + r, in: { x: x + w, y: y + r - k } },
|
|
474
|
-
{ x: x + w, y: y + h - r, out: { x: x + w, y: y + h - r + k } },
|
|
475
|
-
{ x: x + w - r, y: y + h, in: { x: x + w - r + k, y: y + h } },
|
|
476
|
-
{ x: x + r, y: y + h, out: { x: x + r - k, y: y + h } },
|
|
477
|
-
{ x, y: y + h - r, in: { x, y: y + h - r + k } },
|
|
478
|
-
{ x, y: y + r, out: { x, y: y + r - k } }
|
|
479
|
-
];
|
|
480
|
-
return { anchors, closed: true };
|
|
898
|
+
const sp = { anchors: [{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }], closed: true };
|
|
899
|
+
return radius > 0 ? setSubpathCorners(sp, radius, kind) : sp;
|
|
481
900
|
}
|
|
482
901
|
function ellipseSubpath(b) {
|
|
483
902
|
const rx = b.width / 2;
|
|
484
903
|
const ry = b.height / 2;
|
|
485
904
|
const cx = b.x + rx;
|
|
486
905
|
const cy = b.y + ry;
|
|
487
|
-
const kx = rx *
|
|
488
|
-
const ky = ry *
|
|
906
|
+
const kx = rx * KAPPA2;
|
|
907
|
+
const ky = ry * KAPPA2;
|
|
489
908
|
return {
|
|
490
909
|
closed: true,
|
|
491
910
|
anchors: [
|
|
@@ -533,15 +952,52 @@ function roundSubpaths(subpaths, precision = 3) {
|
|
|
533
952
|
}
|
|
534
953
|
|
|
535
954
|
// src/vector/svg.ts
|
|
955
|
+
function vectorCornersAttribute(subpaths, precision = 3) {
|
|
956
|
+
const out = [];
|
|
957
|
+
subpaths.forEach(
|
|
958
|
+
(sp, s) => sp.anchors.forEach((a, i) => {
|
|
959
|
+
if (!a.corner || !(a.corner.radius > 0)) return;
|
|
960
|
+
out.push(`${s}.${i}:${_chunkBGNPT5PEcjs.formatNumber.call(void 0, a.corner.radius, precision)}${a.corner.kind && a.corner.kind !== "round" ? `:${a.corner.kind}` : ""}`);
|
|
961
|
+
})
|
|
962
|
+
);
|
|
963
|
+
return out.join(" ");
|
|
964
|
+
}
|
|
965
|
+
function applyVectorCornersAttribute(subpaths, value) {
|
|
966
|
+
for (const item of value.trim().split(/\s+/)) {
|
|
967
|
+
const m = /^(\d+)\.(\d+):([\d.eE+-]+)(?::([a-z]+))?$/.exec(item);
|
|
968
|
+
if (!m) continue;
|
|
969
|
+
const a = _optionalChain([subpaths, 'access', _9 => _9[+m[1]], 'optionalAccess', _10 => _10.anchors, 'access', _11 => _11[+m[2]]]);
|
|
970
|
+
const radius = parseFloat(m[3]);
|
|
971
|
+
if (!a || !(radius > 0)) continue;
|
|
972
|
+
const kind = VECTOR_CORNER_KINDS.includes(m[4]) ? m[4] : void 0;
|
|
973
|
+
a.corner = { radius, ...kind && kind !== "round" ? { kind } : {} };
|
|
974
|
+
}
|
|
975
|
+
return subpaths;
|
|
976
|
+
}
|
|
536
977
|
var escapeAttr = (value) => value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
537
978
|
function vectorPaintToSvg(paint, gradientId) {
|
|
538
979
|
if (!hasPaint(paint)) return { value: "none", gradient: null };
|
|
539
980
|
if (typeof paint === "string") return { value: paint, gradient: null };
|
|
540
|
-
return { value: `url(#${gradientId})`, gradient:
|
|
981
|
+
return { value: `url(#${gradientId})`, gradient: _chunkBGNPT5PEcjs.gradientToSvgDef.call(void 0, paint, gradientId) };
|
|
982
|
+
}
|
|
983
|
+
function vectorStrokeSvgParts(node, clipId, precision = 3) {
|
|
984
|
+
if (!hasPaint(node.stroke) || !hasStrokeExtras(node)) return void 0;
|
|
985
|
+
const path = subpathsToBezier(node.subpaths);
|
|
986
|
+
const parts = vectorStrokeParts(path, node);
|
|
987
|
+
if (parts.align === "center" && !parts.heads.length) return void 0;
|
|
988
|
+
let clip = null;
|
|
989
|
+
if (parts.align !== "center") {
|
|
990
|
+
const b = _chunkBGNPT5PEcjs.bezierPathBounds.call(void 0, path);
|
|
991
|
+
const pad = vectorStrokeReach(node) + 1;
|
|
992
|
+
const bounds = b ? { x: round2(b.minX - pad), y: round2(b.minY - pad), width: round2(b.maxX - b.minX + 2 * pad), height: round2(b.maxY - b.minY + 2 * pad) } : { x: 0, y: 0, width: 0, height: 0 };
|
|
993
|
+
clip = { kind: parts.align === "inside" ? "clip" : "mask", id: clipId, bounds };
|
|
994
|
+
}
|
|
995
|
+
return { align: parts.align, d: _chunkBGNPT5PEcjs.bezierPathToSvg.call(void 0, parts.line, { precision }), width: parts.drawWidth, clip, heads: parts.heads.map((h) => _chunkBGNPT5PEcjs.bezierPathToSvg.call(void 0, h.path, { precision })) };
|
|
541
996
|
}
|
|
997
|
+
var round2 = (n) => Math.round(n * 100) / 100;
|
|
542
998
|
function gradientMarkup(def) {
|
|
543
999
|
const tag = def.type === "radial" ? "radialGradient" : "linearGradient";
|
|
544
|
-
const attrs = Object.entries(def.attrs).map(([k,
|
|
1000
|
+
const attrs = Object.entries(def.attrs).map(([k, v2]) => ` ${k}="${escapeAttr(String(v2))}"`).join("");
|
|
545
1001
|
const stops = def.stops.map((s) => `<stop offset="${s.offset}" stop-color="${escapeAttr(s.stopColor)}"${s.stopOpacity !== void 0 ? ` stop-opacity="${s.stopOpacity}"` : ""}/>`).join("");
|
|
546
1002
|
return `<${tag} id="${escapeAttr(def.id)}"${attrs}>${stops}</${tag}>`;
|
|
547
1003
|
}
|
|
@@ -551,11 +1007,11 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
551
1007
|
const pretty = options.pretty !== false;
|
|
552
1008
|
const defs = [];
|
|
553
1009
|
const lines = [];
|
|
554
|
-
const num = (n) =>
|
|
1010
|
+
const num = (n) => _chunkBGNPT5PEcjs.formatNumber.call(void 0, n, precision);
|
|
555
1011
|
const indent = (depth) => pretty ? " ".repeat(depth) : "";
|
|
556
1012
|
const common = (node) => {
|
|
557
1013
|
let out2 = ` id="${escapeAttr(node.id)}"`;
|
|
558
|
-
if (node.transform) out2 += ` transform="matrix(${node.transform.map((n) =>
|
|
1014
|
+
if (node.transform) out2 += ` transform="matrix(${node.transform.map((n) => _chunkBGNPT5PEcjs.formatNumber.call(void 0, n, 6)).join(" ")})"`;
|
|
559
1015
|
if (node.opacity !== void 0 && node.opacity < 1) out2 += ` opacity="${num(node.opacity)}"`;
|
|
560
1016
|
if (node.visible === false) out2 += ` display="none"`;
|
|
561
1017
|
return out2;
|
|
@@ -572,15 +1028,53 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
572
1028
|
const stroke = vectorPaintToSvg(node.stroke, `${prefix}stroke-${node.id}`);
|
|
573
1029
|
if (fill.gradient) defs.push(gradientMarkup(fill.gradient));
|
|
574
1030
|
if (stroke.gradient) defs.push(gradientMarkup(stroke.gradient));
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if (
|
|
582
|
-
|
|
583
|
-
|
|
1031
|
+
const d = vectorPathData(node, precision);
|
|
1032
|
+
const corners2 = vectorCornersAttribute(node.subpaths, precision);
|
|
1033
|
+
const live = corners2 ? ` data-vector-d="${_chunkBGNPT5PEcjs.bezierPathToSvg.call(void 0, subpathsToBezier(node.subpaths, { corners: false }), { precision })}" data-vector-corners="${corners2}"` : "";
|
|
1034
|
+
const fillAttrs = ` fill="${escapeAttr(fill.value)}"${node.fillRule === "evenodd" ? ` fill-rule="evenodd"` : ""}`;
|
|
1035
|
+
const lineAttrs = (width) => {
|
|
1036
|
+
let out2 = ` stroke="${escapeAttr(stroke.value)}" stroke-width="${num(width)}"`;
|
|
1037
|
+
if (node.strokeLinecap && node.strokeLinecap !== "butt") out2 += ` stroke-linecap="${node.strokeLinecap}"`;
|
|
1038
|
+
if (node.strokeLinejoin && node.strokeLinejoin !== "miter") out2 += ` stroke-linejoin="${node.strokeLinejoin}"`;
|
|
1039
|
+
if (_optionalChain([node, 'access', _12 => _12.strokeDasharray, 'optionalAccess', _13 => _13.length])) out2 += ` stroke-dasharray="${node.strokeDasharray.map(num).join(" ")}"`;
|
|
1040
|
+
return out2;
|
|
1041
|
+
};
|
|
1042
|
+
const parts = stroke.value !== "none" ? vectorStrokeSvgParts(node, `${prefix}clip-${node.id}`, precision) : void 0;
|
|
1043
|
+
if (!parts) {
|
|
1044
|
+
lines.push(`${indent(depth)}<path${common(node)} d="${d}"${fillAttrs}${stroke.value !== "none" ? lineAttrs(_nullishCoalesce(node.strokeWidth, () => ( 1))) : ""}${live}/>`);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
let data = ` data-vector-path="" data-vector-stroke-width="${num(_nullishCoalesce(node.strokeWidth, () => ( 1)))}"`;
|
|
1048
|
+
if (parts.align !== "center") data += ` data-vector-stroke-align="${parts.align}"`;
|
|
1049
|
+
if (node.startArrow) data += ` data-vector-start-arrow="${node.startArrow}"`;
|
|
1050
|
+
if (node.endArrow) data += ` data-vector-end-arrow="${node.endArrow}"`;
|
|
1051
|
+
if (node.arrowAlign && node.arrowAlign !== "extend") data += ` data-vector-arrow-align="${node.arrowAlign}"`;
|
|
1052
|
+
if (node.startArrowScale !== void 0 && node.startArrowScale !== 1) data += ` data-vector-start-arrow-scale="${num(node.startArrowScale)}"`;
|
|
1053
|
+
if (node.endArrowScale !== void 0 && node.endArrowScale !== 1) data += ` data-vector-end-arrow-scale="${num(node.endArrowScale)}"`;
|
|
1054
|
+
lines.push(`${indent(depth)}<g${common(node)}${data}>`);
|
|
1055
|
+
lines.push(`${indent(depth + 1)}<path data-vector-part="fill" d="${d}"${fillAttrs}${live}/>`);
|
|
1056
|
+
if (options.strokes === "outline") {
|
|
1057
|
+
const area = _chunkBGNPT5PEcjs.bezierPathToSvg.call(void 0, vectorStrokeOutlinePath(subpathsToBezier(node.subpaths), node), { precision });
|
|
1058
|
+
lines.push(`${indent(depth + 1)}<path data-vector-part="stroke-outline" d="${area}" fill="${escapeAttr(stroke.value)}"/>`);
|
|
1059
|
+
} else {
|
|
1060
|
+
let clip = "";
|
|
1061
|
+
if (parts.clip) {
|
|
1062
|
+
const rule = node.fillRule === "evenodd" ? "evenodd" : "nonzero";
|
|
1063
|
+
const id = escapeAttr(parts.clip.id);
|
|
1064
|
+
if (parts.clip.kind === "clip") {
|
|
1065
|
+
defs.push(`<clipPath id="${id}"><path d="${d}" clip-rule="${rule}"/></clipPath>`);
|
|
1066
|
+
clip = ` clip-path="url(#${id})"`;
|
|
1067
|
+
} else {
|
|
1068
|
+
const b = parts.clip.bounds;
|
|
1069
|
+
const box = `x="${num(b.x)}" y="${num(b.y)}" width="${num(b.width)}" height="${num(b.height)}"`;
|
|
1070
|
+
defs.push(`<mask id="${id}" maskUnits="userSpaceOnUse" ${box}><rect ${box} fill="#fff"/><path d="${d}" fill="#000" fill-rule="${rule}"/></mask>`);
|
|
1071
|
+
clip = ` mask="url(#${id})"`;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
lines.push(`${indent(depth + 1)}<path data-vector-part="stroke" d="${parts.d}" fill="none"${lineAttrs(parts.width)}${clip}/>`);
|
|
1075
|
+
for (const h2 of parts.heads) lines.push(`${indent(depth + 1)}<path data-vector-part="head" d="${h2}" fill="${escapeAttr(stroke.value)}"/>`);
|
|
1076
|
+
}
|
|
1077
|
+
lines.push(`${indent(depth)}</g>`);
|
|
584
1078
|
};
|
|
585
1079
|
for (const node of doc.nodes) emit(node, 1);
|
|
586
1080
|
const w = num(doc.width);
|
|
@@ -723,7 +1217,7 @@ function resolveGradient(id, raw, seen = /* @__PURE__ */ new Set()) {
|
|
|
723
1217
|
seen.add(id);
|
|
724
1218
|
let stops = readStops(el);
|
|
725
1219
|
const href = _nullishCoalesce(el.attrs.href, () => ( el.attrs["xlink:href"]));
|
|
726
|
-
if (!stops.length && _optionalChain([href, 'optionalAccess',
|
|
1220
|
+
if (!stops.length && _optionalChain([href, 'optionalAccess', _14 => _14.startsWith, 'call', _15 => _15("#")])) stops = _nullishCoalesce(_optionalChain([resolveGradient, 'call', _16 => _16(href.slice(1), raw, seen), 'optionalAccess', _17 => _17.stops]), () => ( []));
|
|
727
1221
|
if (!stops.length) return null;
|
|
728
1222
|
if (el.name === "radialGradient") {
|
|
729
1223
|
return { type: "radial", cx: fraction(el.attrs.cx, 0.5), cy: fraction(el.attrs.cy, 0.5), r: fraction(el.attrs.r, 0.5), stops };
|
|
@@ -755,11 +1249,11 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
755
1249
|
const rawGradients = /* @__PURE__ */ new Map();
|
|
756
1250
|
collectGradients(root, rawGradients);
|
|
757
1251
|
const paint = (value) => {
|
|
758
|
-
const
|
|
759
|
-
const url = /^url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/.exec(
|
|
1252
|
+
const v2 = value.trim();
|
|
1253
|
+
const url = /^url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/.exec(v2);
|
|
760
1254
|
if (url) return _nullishCoalesce(resolveGradient(url[1], rawGradients), () => ( null));
|
|
761
|
-
if (
|
|
762
|
-
return
|
|
1255
|
+
if (v2 === "none" || v2 === "") return null;
|
|
1256
|
+
return v2;
|
|
763
1257
|
};
|
|
764
1258
|
const viewBox = numberList(root.attrs.viewBox);
|
|
765
1259
|
const width = viewBox.length === 4 ? viewBox[2] : length(root.attrs.width, 300);
|
|
@@ -769,8 +1263,13 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
769
1263
|
const shapeOf = (el) => {
|
|
770
1264
|
const a = el.attrs;
|
|
771
1265
|
switch (el.name) {
|
|
772
|
-
case "path":
|
|
1266
|
+
case "path": {
|
|
1267
|
+
if (a["data-vector-d"] && a["data-vector-corners"]) {
|
|
1268
|
+
const sharp = bezierToSubpaths(a["data-vector-d"]);
|
|
1269
|
+
if (sharp.length) return applyVectorCornersAttribute(sharp, a["data-vector-corners"]);
|
|
1270
|
+
}
|
|
773
1271
|
return a.d ? bezierToSubpaths(a.d) : null;
|
|
1272
|
+
}
|
|
774
1273
|
case "rect": {
|
|
775
1274
|
const w = length(a.width);
|
|
776
1275
|
const h = length(a.height);
|
|
@@ -804,6 +1303,56 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
804
1303
|
}
|
|
805
1304
|
return null;
|
|
806
1305
|
};
|
|
1306
|
+
const strokeStyle = (s) => ({
|
|
1307
|
+
...s.strokeWidth ? { strokeWidth: length(s.strokeWidth, 1) } : {},
|
|
1308
|
+
...s.strokeLinecap ? { strokeLinecap: s.strokeLinecap } : {},
|
|
1309
|
+
...s.strokeLinejoin ? { strokeLinejoin: s.strokeLinejoin } : {},
|
|
1310
|
+
...s.strokeDasharray && s.strokeDasharray !== "none" ? { strokeDasharray: numberList(s.strokeDasharray) } : {},
|
|
1311
|
+
...s.fillRule === "evenodd" ? { fillRule: "evenodd" } : {}
|
|
1312
|
+
});
|
|
1313
|
+
const placed = (node, m) => {
|
|
1314
|
+
if (m === IDENTITY_MATRIX) return node;
|
|
1315
|
+
const next = transformVectorNode(node, m);
|
|
1316
|
+
const strokeScale = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
|
|
1317
|
+
if (hasPaint(next.stroke) && strokeScale > 0 && Math.abs(strokeScale - 1) > 1e-9) next.strokeWidth = (_nullishCoalesce(next.strokeWidth, () => ( 1))) * strokeScale;
|
|
1318
|
+
return next;
|
|
1319
|
+
};
|
|
1320
|
+
const mergedPath = (g, style, m, meta) => {
|
|
1321
|
+
const part = (name) => g.children.find((c) => c.name === "path" && c.attrs["data-vector-part"] === name);
|
|
1322
|
+
const fillEl = part("fill");
|
|
1323
|
+
const subpaths = fillEl ? shapeOf(fillEl) : null;
|
|
1324
|
+
if (!fillEl || !_optionalChain([subpaths, 'optionalAccess', _18 => _18.length])) return null;
|
|
1325
|
+
const a = g.attrs;
|
|
1326
|
+
const fs = readStyle(fillEl, style);
|
|
1327
|
+
const strokeEl = part("stroke");
|
|
1328
|
+
const outlineEl = part("stroke-outline");
|
|
1329
|
+
const ss = strokeEl ? readStyle(strokeEl, style) : null;
|
|
1330
|
+
const width2 = length(a["data-vector-stroke-width"], ss ? length(ss.strokeWidth, 1) : 1);
|
|
1331
|
+
const arrow = (v2) => VECTOR_ARROWHEADS.includes(v2) ? v2 : void 0;
|
|
1332
|
+
const scale = (v2) => {
|
|
1333
|
+
const n = v2 === void 0 ? NaN : parseFloat(v2);
|
|
1334
|
+
return n > 0 ? n : void 0;
|
|
1335
|
+
};
|
|
1336
|
+
const align = a["data-vector-stroke-align"];
|
|
1337
|
+
const node = {
|
|
1338
|
+
id: idFor(g),
|
|
1339
|
+
type: "path",
|
|
1340
|
+
...meta,
|
|
1341
|
+
subpaths,
|
|
1342
|
+
fill: paint(fs.fill),
|
|
1343
|
+
stroke: ss ? paint(ss.stroke) : outlineEl ? paint(readStyle(outlineEl, style).fill) : null,
|
|
1344
|
+
...ss ? strokeStyle(ss) : {},
|
|
1345
|
+
...fs.fillRule === "evenodd" ? { fillRule: "evenodd" } : {},
|
|
1346
|
+
strokeWidth: width2,
|
|
1347
|
+
...align === "inside" || align === "outside" ? { strokeAlign: align } : {},
|
|
1348
|
+
...arrow(a["data-vector-start-arrow"]) ? { startArrow: arrow(a["data-vector-start-arrow"]) } : {},
|
|
1349
|
+
...arrow(a["data-vector-end-arrow"]) ? { endArrow: arrow(a["data-vector-end-arrow"]) } : {},
|
|
1350
|
+
...a["data-vector-arrow-align"] === "tip" ? { arrowAlign: "tip" } : {},
|
|
1351
|
+
...scale(a["data-vector-start-arrow-scale"]) ? { startArrowScale: scale(a["data-vector-start-arrow-scale"]) } : {},
|
|
1352
|
+
...scale(a["data-vector-end-arrow-scale"]) ? { endArrowScale: scale(a["data-vector-end-arrow-scale"]) } : {}
|
|
1353
|
+
};
|
|
1354
|
+
return placed(node, m);
|
|
1355
|
+
};
|
|
807
1356
|
const visit = (el, style, matrix) => {
|
|
808
1357
|
const out = [];
|
|
809
1358
|
for (const child of el.children) {
|
|
@@ -817,6 +1366,11 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
817
1366
|
...opacity !== void 0 && Number.isFinite(opacity) && opacity < 1 ? { opacity } : {},
|
|
818
1367
|
...hidden ? { visible: false } : {}
|
|
819
1368
|
};
|
|
1369
|
+
if (child.name === "g" && child.attrs["data-vector-path"] !== void 0) {
|
|
1370
|
+
const node2 = mergedPath(child, s, m, meta);
|
|
1371
|
+
if (node2) out.push(node2);
|
|
1372
|
+
continue;
|
|
1373
|
+
}
|
|
820
1374
|
if (child.name === "g" || child.name === "svg" || child.name === "a" || child.name === "switch") {
|
|
821
1375
|
const children = visit(child, s, m);
|
|
822
1376
|
if (!children.length) continue;
|
|
@@ -826,24 +1380,18 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
826
1380
|
}
|
|
827
1381
|
const subpaths = shapeOf(child);
|
|
828
1382
|
if (!subpaths || !subpaths.length) continue;
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
};
|
|
842
|
-
if (m !== IDENTITY_MATRIX) {
|
|
843
|
-
node = transformVectorNode(node, m);
|
|
844
|
-
const strokeScale = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
|
|
845
|
-
if (hasPaint(node.stroke) && strokeScale > 0 && Math.abs(strokeScale - 1) > 1e-9) node.strokeWidth = (_nullishCoalesce(node.strokeWidth, () => ( 1))) * strokeScale;
|
|
846
|
-
}
|
|
1383
|
+
const node = placed(
|
|
1384
|
+
{
|
|
1385
|
+
id: idFor(child),
|
|
1386
|
+
type: "path",
|
|
1387
|
+
...meta,
|
|
1388
|
+
subpaths,
|
|
1389
|
+
fill: child.name === "line" ? null : paint(s.fill),
|
|
1390
|
+
stroke: paint(s.stroke),
|
|
1391
|
+
...strokeStyle(s)
|
|
1392
|
+
},
|
|
1393
|
+
m
|
|
1394
|
+
);
|
|
847
1395
|
out.push(node);
|
|
848
1396
|
}
|
|
849
1397
|
return out;
|
|
@@ -855,7 +1403,7 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
855
1403
|
// src/vector/snap.ts
|
|
856
1404
|
var VECTOR_CONSTRUCTION_ANGLES = [0, 45, 90, 135];
|
|
857
1405
|
function constrainToLeave(leave, p) {
|
|
858
|
-
if (!_optionalChain([leave, 'optionalAccess',
|
|
1406
|
+
if (!_optionalChain([leave, 'optionalAccess', _19 => _19.constrain])) return p;
|
|
859
1407
|
const dx = p.x - leave.at.x;
|
|
860
1408
|
const dy = p.y - leave.at.y;
|
|
861
1409
|
switch (leave.constrain) {
|
|
@@ -866,9 +1414,9 @@ function constrainToLeave(leave, p) {
|
|
|
866
1414
|
return { x: leave.at.x + Math.sign(dx || 1) * s, y: leave.at.y + Math.sign(dy || 1) * s };
|
|
867
1415
|
}
|
|
868
1416
|
default: {
|
|
869
|
-
const
|
|
1417
|
+
const len2 = Math.hypot(dx, dy);
|
|
870
1418
|
const a = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);
|
|
871
|
-
return { x: leave.at.x + Math.cos(a) *
|
|
1419
|
+
return { x: leave.at.x + Math.cos(a) * len2, y: leave.at.y + Math.sin(a) * len2 };
|
|
872
1420
|
}
|
|
873
1421
|
}
|
|
874
1422
|
}
|
|
@@ -877,24 +1425,24 @@ var CROSSING_PAIRS = 32;
|
|
|
877
1425
|
var CURVE_SAMPLES = 48;
|
|
878
1426
|
var BISECT = 50;
|
|
879
1427
|
var EQUAL = 1e-6;
|
|
880
|
-
function bound(sorted, key,
|
|
1428
|
+
function bound(sorted, key, v2, strict) {
|
|
881
1429
|
let lo = 0;
|
|
882
1430
|
let hi = sorted.length;
|
|
883
1431
|
while (lo < hi) {
|
|
884
1432
|
const mid = lo + hi >>> 1;
|
|
885
1433
|
const k = key(sorted[mid]);
|
|
886
|
-
if (strict ? k <=
|
|
1434
|
+
if (strict ? k <= v2 : k < v2) lo = mid + 1;
|
|
887
1435
|
else hi = mid;
|
|
888
1436
|
}
|
|
889
1437
|
return lo;
|
|
890
1438
|
}
|
|
891
|
-
function within(sorted, key,
|
|
892
|
-
return sorted.slice(bound(sorted, key,
|
|
1439
|
+
function within(sorted, key, v2, tol) {
|
|
1440
|
+
return sorted.slice(bound(sorted, key, v2 - tol, false), bound(sorted, key, v2 + tol, true));
|
|
893
1441
|
}
|
|
894
|
-
var linesNear = (lines,
|
|
1442
|
+
var linesNear = (lines, v2, tol) => within(lines, (l) => l.v, v2, tol);
|
|
895
1443
|
var distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
896
1444
|
var toPoint = (p) => [p.x, p.y];
|
|
897
|
-
var
|
|
1445
|
+
var toVec2 = (p) => ({ x: p[0], y: p[1] });
|
|
898
1446
|
var LABELS = {
|
|
899
1447
|
anchor: "anchor",
|
|
900
1448
|
centre: "centre",
|
|
@@ -966,7 +1514,7 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
966
1514
|
moving.set(r.nodeId, set);
|
|
967
1515
|
}
|
|
968
1516
|
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',
|
|
1517
|
+
t.pushPaths(doc, new Set(moving.keys()), (id, s, i) => !!_optionalChain([moving, 'access', _20 => _20.get, 'call', _21 => _21(id), 'optionalAccess', _22 => _22.has, 'call', _23 => _23(`${s}:${i}`)]), true);
|
|
970
1518
|
return t.index();
|
|
971
1519
|
}
|
|
972
1520
|
/**
|
|
@@ -1012,7 +1560,7 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1012
1560
|
const count = subpathSegmentCount(sp);
|
|
1013
1561
|
for (let i = 0; i < count && budget > 0; i++) {
|
|
1014
1562
|
if (moving(i) || moving((i + 1) % n)) continue;
|
|
1015
|
-
const seg = subpathSegment(sp, i).map((q) => toPoint(applyMatrix(m,
|
|
1563
|
+
const seg = subpathSegment(sp, i).map((q) => toPoint(applyMatrix(m, toVec2(q))));
|
|
1016
1564
|
let minX = Infinity;
|
|
1017
1565
|
let minY = Infinity;
|
|
1018
1566
|
let maxX = -Infinity;
|
|
@@ -1029,13 +1577,13 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1029
1577
|
return budget;
|
|
1030
1578
|
}
|
|
1031
1579
|
pushRect(r, kind, centre) {
|
|
1032
|
-
const
|
|
1580
|
+
const corners2 = [
|
|
1033
1581
|
{ x: r.x, y: r.y },
|
|
1034
1582
|
{ x: r.x + r.width, y: r.y },
|
|
1035
1583
|
{ x: r.x + r.width, y: r.y + r.height },
|
|
1036
1584
|
{ x: r.x, y: r.y + r.height }
|
|
1037
1585
|
];
|
|
1038
|
-
for (const p of
|
|
1586
|
+
for (const p of corners2) {
|
|
1039
1587
|
this.xs.push({ v: p.x, from: p, kind });
|
|
1040
1588
|
this.ys.push({ v: p.y, from: p, kind });
|
|
1041
1589
|
}
|
|
@@ -1084,7 +1632,7 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1084
1632
|
}
|
|
1085
1633
|
const b = vectorNodeBounds(n, parent);
|
|
1086
1634
|
const f = nodeFrame(n, parent, b);
|
|
1087
|
-
if (b) self.pushRect(b, "edge", _nullishCoalesce(_optionalChain([f, 'optionalAccess',
|
|
1635
|
+
if (b) self.pushRect(b, "edge", _nullishCoalesce(_optionalChain([f, 'optionalAccess', _24 => _24.centre]), () => ( null)));
|
|
1088
1636
|
}
|
|
1089
1637
|
};
|
|
1090
1638
|
visit(doc.nodes, IDENTITY_MATRIX);
|
|
@@ -1129,23 +1677,23 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1129
1677
|
lines: lines2,
|
|
1130
1678
|
labels: [this.label(q, kind)]
|
|
1131
1679
|
});
|
|
1132
|
-
if (_optionalChain([leave, 'optionalAccess',
|
|
1680
|
+
if (_optionalChain([leave, 'optionalAccess', _25 => _25.constrain])) {
|
|
1133
1681
|
const q = constrainToLeave(leave, p);
|
|
1134
1682
|
const hit = this.slide(leave.at, { x: q.x - leave.at.x, y: q.y - leave.at.y }, q, tol, true);
|
|
1135
1683
|
return hit ? { point: hit.point, x: true, y: true, ...this.linedUp(hit.point, hit.from) } : none(q);
|
|
1136
1684
|
}
|
|
1137
1685
|
const point = this.nearestPoint(p, tol);
|
|
1138
1686
|
if (point) return at(point.p, point.kind);
|
|
1139
|
-
const
|
|
1140
|
-
const crossing = this.crossing(
|
|
1687
|
+
const near2 = this.segmentsNear(p, tol);
|
|
1688
|
+
const crossing = this.crossing(near2, p, tol);
|
|
1141
1689
|
if (crossing) return at(crossing, "intersect");
|
|
1142
1690
|
if (leave && leave.curves !== false) {
|
|
1143
|
-
const foot = this.curvePoint(
|
|
1691
|
+
const foot = this.curvePoint(near2, leave.at, p, tol);
|
|
1144
1692
|
if (foot) return at(foot.point, foot.kind, [line("construction", leave.at, foot.point)]);
|
|
1145
1693
|
}
|
|
1146
|
-
const on = this.onSegment(
|
|
1694
|
+
const on = this.onSegment(near2);
|
|
1147
1695
|
if (on) return at(on, "path");
|
|
1148
|
-
if (_optionalChain([leave, 'optionalAccess',
|
|
1696
|
+
if (_optionalChain([leave, 'optionalAccess', _26 => _26.angles, 'optionalAccess', _27 => _27.length])) {
|
|
1149
1697
|
const c = construction(leave, p, tol);
|
|
1150
1698
|
if (c) {
|
|
1151
1699
|
const hit = this.slide(leave.at, c.dir, c.point, tol, false);
|
|
@@ -1156,9 +1704,9 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1156
1704
|
return { point: c.point, kind: "construction", x: true, y: true, lines: [line("construction", leave.at, c.point)], labels: [] };
|
|
1157
1705
|
}
|
|
1158
1706
|
}
|
|
1159
|
-
const nearest = (lines2,
|
|
1707
|
+
const nearest = (lines2, v2) => {
|
|
1160
1708
|
let best = null;
|
|
1161
|
-
for (const l of linesNear(lines2,
|
|
1709
|
+
for (const l of linesNear(lines2, v2, tol)) if (!best || Math.abs(l.v - v2) < Math.abs(best.v - v2)) best = l;
|
|
1162
1710
|
return best;
|
|
1163
1711
|
};
|
|
1164
1712
|
const bx = nearest(this.xs, p.x);
|
|
@@ -1218,26 +1766,26 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1218
1766
|
/** Target segments whose control box reaches `p` and which pass within `tol` of it, nearest first. */
|
|
1219
1767
|
segmentsNear(p, tol) {
|
|
1220
1768
|
const out = [];
|
|
1221
|
-
const
|
|
1769
|
+
const pt2 = toPoint(p);
|
|
1222
1770
|
for (const s of this.segments) {
|
|
1223
1771
|
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 =
|
|
1772
|
+
const hit = _chunkBGNPT5PEcjs.segmentNearest.call(void 0, s.seg, pt2);
|
|
1225
1773
|
if (hit.distance <= tol) out.push({ seg: s.seg, t: hit.t, distance: hit.distance });
|
|
1226
1774
|
}
|
|
1227
1775
|
return out.sort((a, b) => a.distance - b.distance);
|
|
1228
1776
|
}
|
|
1229
|
-
onSegment(
|
|
1230
|
-
return
|
|
1777
|
+
onSegment(near2) {
|
|
1778
|
+
return near2.length ? toVec2(_chunkBGNPT5PEcjs.segmentPoint.call(void 0, near2[0].seg, near2[0].t)) : null;
|
|
1231
1779
|
}
|
|
1232
1780
|
/** Where two of the target segments near `p` cross, the crossing nearest `p` within `tol`. */
|
|
1233
|
-
crossing(
|
|
1234
|
-
const list =
|
|
1781
|
+
crossing(near2, p, tol) {
|
|
1782
|
+
const list = near2.slice(0, CROSSING_PAIRS);
|
|
1235
1783
|
let best = null;
|
|
1236
1784
|
let bestD = Infinity;
|
|
1237
1785
|
for (let i = 0; i < list.length; i++) {
|
|
1238
1786
|
for (let j = i + 1; j < list.length; j++) {
|
|
1239
|
-
for (const hit of
|
|
1240
|
-
const q =
|
|
1787
|
+
for (const hit of _chunkBGNPT5PEcjs.segmentIntersections.call(void 0, list[i].seg, list[j].seg)) {
|
|
1788
|
+
const q = toVec2(hit.point);
|
|
1241
1789
|
const d = distance(q, p);
|
|
1242
1790
|
if (d <= tol && d < bestD) {
|
|
1243
1791
|
best = q;
|
|
@@ -1253,9 +1801,9 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1253
1801
|
* (within `tol` of it): roots of `(B(t) − base) · B′(t)` and `(B(t) − base) × B′(t)`
|
|
1254
1802
|
* bracketed by sign changes of samples and narrowed by bisection.
|
|
1255
1803
|
*/
|
|
1256
|
-
curvePoint(
|
|
1804
|
+
curvePoint(near2, base, p, tol) {
|
|
1257
1805
|
let best = null;
|
|
1258
|
-
for (const { seg } of
|
|
1806
|
+
for (const { seg } of near2) {
|
|
1259
1807
|
for (const kind of ["perpendicular", "tangent"]) {
|
|
1260
1808
|
if (kind === "tangent" && seg.length === 2) continue;
|
|
1261
1809
|
for (const q of solveOnSegment(seg, base, kind === "perpendicular" ? dotCondition : crossCondition)) {
|
|
@@ -1275,15 +1823,15 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1275
1823
|
* with nothing spaces the box evenly in its row or column. Returns the correction and guides.
|
|
1276
1824
|
*/
|
|
1277
1825
|
snapRect(r, tol, options = {}) {
|
|
1278
|
-
const axes = { x: _optionalChain([options, 'access',
|
|
1826
|
+
const axes = { x: _optionalChain([options, 'access', _28 => _28.axes, 'optionalAccess', _29 => _29.x]) !== false, y: _optionalChain([options, 'access', _30 => _30.axes, 'optionalAccess', _31 => _31.y]) !== false };
|
|
1279
1827
|
const nearest = (lines, values) => {
|
|
1280
1828
|
let best = null;
|
|
1281
|
-
for (const
|
|
1829
|
+
for (const v2 of values) for (const l of linesNear(lines, v2, tol)) if (!best || Math.abs(l.v - v2) < Math.abs(best.d)) best = { d: l.v - v2, from: l.from };
|
|
1282
1830
|
return best;
|
|
1283
1831
|
};
|
|
1284
1832
|
const bx = axes.x ? nearest(this.xs, [r.x, r.x + r.width / 2, r.x + r.width]) : null;
|
|
1285
1833
|
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',
|
|
1834
|
+
const out = { dx: _nullishCoalesce(_optionalChain([bx, 'optionalAccess', _32 => _32.d]), () => ( 0)), dy: _nullishCoalesce(_optionalChain([by, 'optionalAccess', _33 => _33.d]), () => ( 0)), x: !!bx, y: !!by, lines: [], labels: [] };
|
|
1287
1835
|
const done = { ...r, x: r.x + out.dx, y: r.y + out.dy };
|
|
1288
1836
|
if (bx) {
|
|
1289
1837
|
const x = bx.from.x;
|
|
@@ -1321,7 +1869,7 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1321
1869
|
evenSpacing(r, horizontal, tol) {
|
|
1322
1870
|
const span = (b, x) => x ? [b.x, b.x + b.width] : [b.y, b.y + b.height];
|
|
1323
1871
|
const [r0, r1] = span(r, horizontal);
|
|
1324
|
-
const
|
|
1872
|
+
const len2 = r1 - r0;
|
|
1325
1873
|
const [c0, c1] = span(r, !horizontal);
|
|
1326
1874
|
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
1875
|
if (!row.length) return null;
|
|
@@ -1341,17 +1889,17 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1341
1889
|
if (e.a0 >= r1 - tol && (!after || e.a0 < after.a0)) after = e;
|
|
1342
1890
|
}
|
|
1343
1891
|
const starts = [];
|
|
1344
|
-
if (before && after && after.a0 - before.a1 >
|
|
1892
|
+
if (before && after && after.a0 - before.a1 > len2) starts.push({ start: (before.a1 + after.a0 - len2) / 2, repeats: null });
|
|
1345
1893
|
for (const { g } of pairs) {
|
|
1346
1894
|
if (before) starts.push({ start: before.a1 + g, repeats: g });
|
|
1347
|
-
if (after) starts.push({ start: after.a0 - g -
|
|
1895
|
+
if (after) starts.push({ start: after.a0 - g - len2, repeats: g });
|
|
1348
1896
|
}
|
|
1349
1897
|
let pick = null;
|
|
1350
1898
|
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
1899
|
if (!pick) return null;
|
|
1352
1900
|
const { start, repeats } = pick;
|
|
1353
1901
|
const at = horizontal ? { ...r, x: start } : { ...r, y: start };
|
|
1354
|
-
const end = start +
|
|
1902
|
+
const end = start + len2;
|
|
1355
1903
|
const gaps = [];
|
|
1356
1904
|
if (before && (repeats === null || Math.abs(start - before.a1 - repeats) <= EQUAL)) gaps.push([before.a1, start, mid(before.b, at)]);
|
|
1357
1905
|
if (after && (repeats === null || Math.abs(after.a0 - end - repeats) <= EQUAL)) gaps.push([end, after.a0, mid(after.b, at)]);
|
|
@@ -1362,9 +1910,9 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1362
1910
|
}
|
|
1363
1911
|
/** A gap mark: a line across the gap with a tick at each end, and its length as a label. */
|
|
1364
1912
|
gapMarks([u0, u1, c], horizontal, tick, out) {
|
|
1365
|
-
const
|
|
1366
|
-
out.lines.push(line("spacing",
|
|
1367
|
-
out.labels.push(this.label(
|
|
1913
|
+
const pt2 = (u, v2) => horizontal ? { x: u, y: v2 } : { x: v2, y: u };
|
|
1914
|
+
out.lines.push(line("spacing", pt2(u0, c), pt2(u1, c)), line("spacing", pt2(u0, c - tick), pt2(u0, c + tick)), line("spacing", pt2(u1, c - tick), pt2(u1, c + tick)));
|
|
1915
|
+
out.labels.push(this.label(pt2((u0 + u1) / 2, c), "spacing", fmtLength(u1 - u0)));
|
|
1368
1916
|
}
|
|
1369
1917
|
/** The other objects' side length nearest `length`, within `tol`, or null. */
|
|
1370
1918
|
matchSize(length2, tol) {
|
|
@@ -1391,9 +1939,9 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1391
1939
|
const hp = handlePos(handle, frame);
|
|
1392
1940
|
const d0 = { x: hp.x - origin.x, y: hp.y - origin.y };
|
|
1393
1941
|
const valid = (n, s) => Number.isFinite(n) && n * s > 0;
|
|
1394
|
-
const nearest = (
|
|
1942
|
+
const nearest = (v2, lines) => {
|
|
1395
1943
|
let best = null;
|
|
1396
|
-
for (const l of linesNear(lines,
|
|
1944
|
+
for (const l of linesNear(lines, v2, tol)) if (!best || Math.abs(l.v - v2) < Math.abs(best.v - v2)) best = l;
|
|
1397
1945
|
return best;
|
|
1398
1946
|
};
|
|
1399
1947
|
let nx = sx;
|
|
@@ -1406,9 +1954,9 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1406
1954
|
const on = this.nearestPoint(p, tol);
|
|
1407
1955
|
const tries = [];
|
|
1408
1956
|
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',
|
|
1410
|
-
tries.push({ tx: _nullishCoalesce(_optionalChain([hx, 'optionalAccess',
|
|
1411
|
-
tries.push({ tx: null, ty: _nullishCoalesce(_optionalChain([hy, 'optionalAccess',
|
|
1957
|
+
tries.push({ tx: _nullishCoalesce(_optionalChain([hx, 'optionalAccess', _34 => _34.v]), () => ( null)), ty: _nullishCoalesce(_optionalChain([hy, 'optionalAccess', _35 => _35.v]), () => ( null)), s: { hx, hy, on: null } });
|
|
1958
|
+
tries.push({ tx: _nullishCoalesce(_optionalChain([hx, 'optionalAccess', _36 => _36.v]), () => ( null)), ty: null, s: { hx, hy: null, on: null } });
|
|
1959
|
+
tries.push({ tx: null, ty: _nullishCoalesce(_optionalChain([hy, 'optionalAccess', _37 => _37.v]), () => ( null)), s: { hx: null, hy, on: null } });
|
|
1412
1960
|
for (const { tx, ty, s } of tries) {
|
|
1413
1961
|
if (tx === null && ty === null) continue;
|
|
1414
1962
|
const q = applyMatrix(toLocal, { x: _nullishCoalesce(tx, () => ( p.x)), y: _nullishCoalesce(ty, () => ( p.y)) });
|
|
@@ -1444,9 +1992,9 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1444
1992
|
};
|
|
1445
1993
|
for (const h of [handle, ...ends]) {
|
|
1446
1994
|
const hv = handlePos(h, frame);
|
|
1447
|
-
const
|
|
1448
|
-
const base = applyMatrix(toDoc, { x: origin.x + c.x *
|
|
1449
|
-
const dir = applyMatrix(toDoc, { x: e.x *
|
|
1995
|
+
const v2 = { x: hv.x - origin.x, y: hv.y - origin.y };
|
|
1996
|
+
const base = applyMatrix(toDoc, { x: origin.x + c.x * v2.x, y: origin.y + c.y * v2.y });
|
|
1997
|
+
const dir = applyMatrix(toDoc, { x: e.x * v2.x, y: e.y * v2.y });
|
|
1450
1998
|
const p = { x: base.x + dir.x * s0, y: base.y + dir.y * s0 };
|
|
1451
1999
|
const l2 = dir.x * dir.x + dir.y * dir.y;
|
|
1452
2000
|
const on = l2 > 1e-18 ? this.nearestPoint(p, tol) : null;
|
|
@@ -1479,16 +2027,16 @@ var VectorSnapTargets = (_class = class _VectorSnapTargets {
|
|
|
1479
2027
|
const sized = [];
|
|
1480
2028
|
if (!proportional) {
|
|
1481
2029
|
if (movesX && !snap.hx && !(frame.angle && snap.hy) && frame.width > 1e-9) {
|
|
1482
|
-
const
|
|
1483
|
-
if (
|
|
1484
|
-
out.sx = Math.sign(out.sx || 1) * (
|
|
2030
|
+
const len2 = this.matchSize(Math.abs(out.sx) * frame.width, tol);
|
|
2031
|
+
if (len2 !== null && len2 > 0) {
|
|
2032
|
+
out.sx = Math.sign(out.sx || 1) * (len2 / frame.width);
|
|
1485
2033
|
sized.push("x");
|
|
1486
2034
|
}
|
|
1487
2035
|
}
|
|
1488
2036
|
if (movesY && !snap.hy && !(frame.angle && snap.hx) && frame.height > 1e-9) {
|
|
1489
|
-
const
|
|
1490
|
-
if (
|
|
1491
|
-
out.sy = Math.sign(out.sy || 1) * (
|
|
2037
|
+
const len2 = this.matchSize(Math.abs(out.sy) * frame.height, tol);
|
|
2038
|
+
if (len2 !== null && len2 > 0) {
|
|
2039
|
+
out.sy = Math.sign(out.sy || 1) * (len2 / frame.height);
|
|
1492
2040
|
sized.push("y");
|
|
1493
2041
|
}
|
|
1494
2042
|
}
|
|
@@ -1539,14 +2087,14 @@ function line(kind, a, b) {
|
|
|
1539
2087
|
return { kind, x1: a.x, y1: a.y, x2: b.x, y2: b.y };
|
|
1540
2088
|
}
|
|
1541
2089
|
function construction(leave, p, tol) {
|
|
1542
|
-
const
|
|
1543
|
-
if (Math.hypot(
|
|
2090
|
+
const v2 = { x: p.x - leave.at.x, y: p.y - leave.at.y };
|
|
2091
|
+
if (Math.hypot(v2.x, v2.y) <= tol) return null;
|
|
1544
2092
|
let best = null;
|
|
1545
2093
|
for (const a of _nullishCoalesce(leave.angles, () => ( []))) {
|
|
1546
2094
|
const r = a * Math.PI / 180;
|
|
1547
2095
|
const exact = (n) => Math.abs(n) < 1e-12 ? 0 : n;
|
|
1548
2096
|
const dir = { x: exact(Math.cos(r)), y: exact(-Math.sin(r)) };
|
|
1549
|
-
const k =
|
|
2097
|
+
const k = v2.x * dir.x + v2.y * dir.y;
|
|
1550
2098
|
const point = { x: leave.at.x + dir.x * k, y: leave.at.y + dir.y * k };
|
|
1551
2099
|
const d = distance(point, p);
|
|
1552
2100
|
if (d <= tol && (!best || d < best.d)) best = { dir, point, d };
|
|
@@ -1558,20 +2106,20 @@ var crossCondition = (r, d) => r[0] * d[1] - r[1] * d[0];
|
|
|
1558
2106
|
function solveOnSegment(seg, base, cond) {
|
|
1559
2107
|
const b = [base.x, base.y];
|
|
1560
2108
|
const g = (t) => {
|
|
1561
|
-
const p =
|
|
1562
|
-
return cond([p[0] - b[0], p[1] - b[1]],
|
|
2109
|
+
const p = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, t);
|
|
2110
|
+
return cond([p[0] - b[0], p[1] - b[1]], _chunkBGNPT5PEcjs.segmentDerivative.call(void 0, seg, t));
|
|
1563
2111
|
};
|
|
1564
2112
|
const vals = [];
|
|
1565
2113
|
let scale = 0;
|
|
1566
2114
|
for (let i = 0; i <= CURVE_SAMPLES; i++) {
|
|
1567
2115
|
const t = i / CURVE_SAMPLES;
|
|
1568
|
-
const p =
|
|
1569
|
-
const d =
|
|
2116
|
+
const p = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, t);
|
|
2117
|
+
const d = _chunkBGNPT5PEcjs.segmentDerivative.call(void 0, seg, t);
|
|
1570
2118
|
const r = [p[0] - b[0], p[1] - b[1]];
|
|
1571
2119
|
vals.push(cond(r, d));
|
|
1572
2120
|
scale = Math.max(scale, Math.hypot(r[0], r[1]) * Math.hypot(d[0], d[1]));
|
|
1573
2121
|
}
|
|
1574
|
-
if (!(scale > 0) || vals.every((
|
|
2122
|
+
if (!(scale > 0) || vals.every((v2) => Math.abs(v2) <= scale * 1e-12)) return [];
|
|
1575
2123
|
const out = [];
|
|
1576
2124
|
for (let i = 0; i < CURVE_SAMPLES; i++) {
|
|
1577
2125
|
const ga = vals[i];
|
|
@@ -1593,8 +2141,8 @@ function solveOnSegment(seg, base, cond) {
|
|
|
1593
2141
|
} else hi = m;
|
|
1594
2142
|
}
|
|
1595
2143
|
const t = (lo + hi) / 2;
|
|
1596
|
-
const p =
|
|
1597
|
-
const dv =
|
|
2144
|
+
const p = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, t);
|
|
2145
|
+
const dv = _chunkBGNPT5PEcjs.segmentDerivative.call(void 0, seg, t);
|
|
1598
2146
|
const r = Math.hypot(p[0] - b[0], p[1] - b[1]);
|
|
1599
2147
|
const dl = Math.hypot(dv[0], dv[1]);
|
|
1600
2148
|
if (!(r > 1e-9 && dl > 0) || Math.abs(cond([p[0] - b[0], p[1] - b[1]], dv)) > 1e-6 * r * dl) continue;
|
|
@@ -1603,134 +2151,1134 @@ function solveOnSegment(seg, base, cond) {
|
|
|
1603
2151
|
return out;
|
|
1604
2152
|
}
|
|
1605
2153
|
|
|
1606
|
-
// src/vector/
|
|
1607
|
-
var
|
|
1608
|
-
var
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
stroke: "#18181b",
|
|
1612
|
-
strokeWidth: 2,
|
|
1613
|
-
polygonSides: 6,
|
|
1614
|
-
starPoints: 5,
|
|
1615
|
-
starInnerRatio: 0.5,
|
|
1616
|
-
cornerRadius: 0
|
|
2154
|
+
// src/vector/pathEdit.ts
|
|
2155
|
+
var EPS3 = 1e-9;
|
|
2156
|
+
var unit2 = (x, y) => {
|
|
2157
|
+
const l = Math.hypot(x, y);
|
|
2158
|
+
return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
|
|
1617
2159
|
};
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
const node = findVectorNode(state.document.nodes, id);
|
|
1630
|
-
if (node && !node.locked) {
|
|
1631
|
-
for (const p of collectPathNodes(node)) if (!p.locked) ids.add(p.id);
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
2160
|
+
function cubicOf(sp, i) {
|
|
2161
|
+
const s = subpathSegment(sp, i);
|
|
2162
|
+
if (s.length === 4) return s;
|
|
2163
|
+
const [a, b] = s;
|
|
2164
|
+
return [a, [a[0] + (b[0] - a[0]) / 3, a[1] + (b[1] - a[1]) / 3], [a[0] + 2 * (b[0] - a[0]) / 3, a[1] + 2 * (b[1] - a[1]) / 3], b];
|
|
2165
|
+
}
|
|
2166
|
+
var isLine = (sp, i) => subpathSegment(sp, i).length === 2;
|
|
2167
|
+
function startTangent(c) {
|
|
2168
|
+
for (let k = 1; k < 4; k++) {
|
|
2169
|
+
const u = unit2(c[k][0] - c[0][0], c[k][1] - c[0][1]);
|
|
2170
|
+
if (u) return u;
|
|
1634
2171
|
}
|
|
1635
|
-
return [
|
|
2172
|
+
return [1, 0];
|
|
1636
2173
|
}
|
|
1637
|
-
function
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
node.subpaths.forEach((sp, s) => sp.anchors.forEach((_, i) => out.push({ nodeId: id, subpath: s, index: i })));
|
|
2174
|
+
function endTangent(c) {
|
|
2175
|
+
for (let k = 2; k >= 0; k--) {
|
|
2176
|
+
const u = unit2(c[3][0] - c[k][0], c[3][1] - c[k][1]);
|
|
2177
|
+
if (u) return u;
|
|
1642
2178
|
}
|
|
1643
|
-
return
|
|
2179
|
+
return [1, 0];
|
|
1644
2180
|
}
|
|
1645
|
-
function
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
2181
|
+
function sample(c, n, out, withStart) {
|
|
2182
|
+
for (let i = withStart ? 0 : 1; i <= n; i++) out.push(_chunkBGNPT5PEcjs.segmentPoint.call(void 0, c, i / n));
|
|
2183
|
+
}
|
|
2184
|
+
var onAnchor2 = (a, h) => !h || Math.abs(h.x - a.x) < EPS3 && Math.abs(h.y - a.y) < EPS3;
|
|
2185
|
+
function tidy(a, keepType = true) {
|
|
2186
|
+
const next = cloneAnchor(a);
|
|
2187
|
+
if (onAnchor2(next, next.in)) delete next.in;
|
|
2188
|
+
if (onAnchor2(next, next.out)) delete next.out;
|
|
2189
|
+
const shown = inferAnchorType(next);
|
|
2190
|
+
const type = keepType && a.type ? a.type === "symmetric" && shown !== "symmetric" ? shown === "corner" ? "corner" : "smooth" : a.type === "smooth" && shown === "corner" ? "corner" : a.type : shown;
|
|
2191
|
+
if (type === "corner") delete next.type;
|
|
2192
|
+
else next.type = type;
|
|
2193
|
+
return next;
|
|
2194
|
+
}
|
|
2195
|
+
function neighbours(sp, i) {
|
|
2196
|
+
const n = sp.anchors.length;
|
|
2197
|
+
const wrap = sp.closed && n > 1;
|
|
2198
|
+
return [i > 0 ? i - 1 : wrap ? n - 1 : -1, i < n - 1 ? i + 1 : wrap ? 0 : -1];
|
|
2199
|
+
}
|
|
2200
|
+
function goldenMin(f, a, b) {
|
|
2201
|
+
const R = 0.618033988749895;
|
|
2202
|
+
let x1 = b - R * (b - a);
|
|
2203
|
+
let x2 = a + R * (b - a);
|
|
2204
|
+
let f1 = f(x1);
|
|
2205
|
+
let f2 = f(x2);
|
|
2206
|
+
for (let i = 0; i < 40; i++) {
|
|
2207
|
+
if (f1.error < f2.error || Number.isNaN(f2.error)) {
|
|
2208
|
+
b = x2;
|
|
2209
|
+
x2 = x1;
|
|
2210
|
+
f2 = f1;
|
|
2211
|
+
x1 = b - R * (b - a);
|
|
2212
|
+
f1 = f(x1);
|
|
2213
|
+
} else {
|
|
2214
|
+
a = x1;
|
|
2215
|
+
x1 = x2;
|
|
2216
|
+
f1 = f2;
|
|
2217
|
+
x2 = a + R * (b - a);
|
|
2218
|
+
f2 = f(x2);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
return f1.error <= f2.error ? f1 : f2;
|
|
2222
|
+
}
|
|
2223
|
+
function fitAcross(pts, N, t0, t1) {
|
|
2224
|
+
const fit = (k) => {
|
|
2225
|
+
const u = [];
|
|
2226
|
+
for (let i = 0; i <= N; i++) u.push(k * i / N);
|
|
2227
|
+
for (let i = 1; i <= N; i++) u.push(k + (1 - k) * i / N);
|
|
2228
|
+
return _chunkBGNPT5PEcjs.fitCubicWithTangents.call(void 0, pts, u, t0, t1);
|
|
1656
2229
|
};
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
2230
|
+
return goldenMin(fit, 0, 1);
|
|
2231
|
+
}
|
|
2232
|
+
function removeAnchorKeepingShape(sp, index) {
|
|
2233
|
+
const anchors = sp.anchors.map(cloneAnchor);
|
|
2234
|
+
const n = anchors.length;
|
|
2235
|
+
if (index < 0 || index >= n) return { anchors, closed: sp.closed };
|
|
2236
|
+
if ((sp.closed || index > 0 && index < n - 1) && n >= 3) {
|
|
2237
|
+
const pi = (index + n - 1) % n;
|
|
2238
|
+
const ni = (index + 1) % n;
|
|
2239
|
+
if (!(isLine(sp, pi) && isLine(sp, index))) {
|
|
2240
|
+
const left = cubicOf(sp, pi);
|
|
2241
|
+
const right = cubicOf(sp, index);
|
|
2242
|
+
const N = 16;
|
|
2243
|
+
const pts = [];
|
|
2244
|
+
sample(left, N, pts, true);
|
|
2245
|
+
sample(right, N, pts, false);
|
|
2246
|
+
const t0 = startTangent(left);
|
|
2247
|
+
const t1 = endTangent(right);
|
|
2248
|
+
const { curve } = fitAcross(pts, N, t0, t1);
|
|
2249
|
+
if ([curve.p1, curve.p2].every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]))) {
|
|
2250
|
+
anchors[pi] = tidy({ ...anchors[pi], out: { x: curve.p1[0], y: curve.p1[1] } });
|
|
2251
|
+
anchors[ni] = tidy({ ...anchors[ni], in: { x: curve.p2[0], y: curve.p2[1] } });
|
|
2252
|
+
}
|
|
1664
2253
|
}
|
|
1665
|
-
return out;
|
|
1666
2254
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
if (!sp) continue;
|
|
1674
|
-
const n = sp.anchors.length;
|
|
1675
|
-
push(node, ref.subpath, ref.index, "in");
|
|
1676
|
-
push(node, ref.subpath, ref.index, "out");
|
|
1677
|
-
const prev = ref.index > 0 ? ref.index - 1 : sp.closed ? n - 1 : -1;
|
|
1678
|
-
const next = ref.index < n - 1 ? ref.index + 1 : sp.closed ? 0 : -1;
|
|
1679
|
-
if (prev >= 0) push(node, ref.subpath, prev, "out");
|
|
1680
|
-
if (next >= 0) push(node, ref.subpath, next, "in");
|
|
2255
|
+
anchors.splice(index, 1);
|
|
2256
|
+
let closed = sp.closed;
|
|
2257
|
+
if (!closed && anchors.length) {
|
|
2258
|
+
anchors[0] = tidy({ ...anchors[0], in: null });
|
|
2259
|
+
const last = anchors.length - 1;
|
|
2260
|
+
anchors[last] = tidy({ ...anchors[last], out: null });
|
|
1681
2261
|
}
|
|
1682
|
-
|
|
2262
|
+
if (anchors.length < 3) closed = closed && anchors.length === 2 && anchors.some((a) => a.in || a.out);
|
|
2263
|
+
return { anchors, closed };
|
|
1683
2264
|
}
|
|
1684
|
-
function
|
|
1685
|
-
const
|
|
1686
|
-
return
|
|
2265
|
+
function removeAnchorsKeepingShape(sp, indices) {
|
|
2266
|
+
const order = [...new Set(indices)].filter((i) => i >= 0 && i < sp.anchors.length).sort((a, b) => b - a);
|
|
2267
|
+
return order.reduce((acc, i) => removeAnchorKeepingShape(acc, i), { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed });
|
|
1687
2268
|
}
|
|
1688
|
-
function
|
|
1689
|
-
const
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
const
|
|
1693
|
-
|
|
2269
|
+
function bendSegment(sp, segment, t, p) {
|
|
2270
|
+
const anchors = sp.anchors.map(cloneAnchor);
|
|
2271
|
+
const n = anchors.length;
|
|
2272
|
+
if (segment < 0 || segment >= subpathSegmentCount(sp) || !Number.isFinite(t) || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return { anchors, closed: sp.closed };
|
|
2273
|
+
const tc = Math.min(0.95, Math.max(0.05, t));
|
|
2274
|
+
const ia = segment;
|
|
2275
|
+
const ib = (segment + 1) % n;
|
|
2276
|
+
const a = anchors[ia];
|
|
2277
|
+
const b = anchors[ib];
|
|
2278
|
+
const k = 3 * tc * (1 - tc);
|
|
2279
|
+
const ox = (p.x - (a.x + (b.x - a.x) * tc)) / k;
|
|
2280
|
+
const oy = (p.y - (a.y + (b.y - a.y) * tc)) / k;
|
|
2281
|
+
const c1 = { x: a.x + (b.x - a.x) / 3 + ox, y: a.y + (b.y - a.y) / 3 + oy };
|
|
2282
|
+
const c2 = { x: b.x + (a.x - b.x) / 3 + ox, y: b.y + (a.y - b.y) / 3 + oy };
|
|
2283
|
+
anchors[ia] = moveAnchorHandle(a, "out", c1);
|
|
2284
|
+
anchors[ib] = moveAnchorHandle(b, "in", c2);
|
|
2285
|
+
return { anchors, closed: sp.closed };
|
|
1694
2286
|
}
|
|
1695
|
-
function
|
|
1696
|
-
const
|
|
1697
|
-
|
|
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;
|
|
2287
|
+
function subpathPointAt(sp, segment, t) {
|
|
2288
|
+
const [x, y] = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, subpathSegment(sp, segment), t);
|
|
2289
|
+
return { x, y };
|
|
1704
2290
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
const
|
|
1708
|
-
return
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
{ handle: "e", x: x + w, y: y + h / 2 },
|
|
1713
|
-
{ handle: "se", x: x + w, y: y + h },
|
|
1714
|
-
{ handle: "s", x: x + w / 2, y: y + h },
|
|
1715
|
-
{ handle: "sw", x, y: y + h },
|
|
1716
|
-
{ handle: "w", x, y: y + h / 2 },
|
|
1717
|
-
{ handle: "rotate", x: x + w / 2, y: y - VECTOR_ROTATE_HANDLE_OFFSET }
|
|
2291
|
+
function curvatureMoveAnchor(sp, index, q) {
|
|
2292
|
+
const anchors = sp.anchors.map(cloneAnchor);
|
|
2293
|
+
const a = sp.anchors[index];
|
|
2294
|
+
if (!a) return { anchors, closed: sp.closed };
|
|
2295
|
+
const own = [
|
|
2296
|
+
a.in && !onAnchor2(a, a.in) ? { x: a.in.x - a.x, y: a.in.y - a.y } : null,
|
|
2297
|
+
a.out && !onAnchor2(a, a.out) ? { x: a.out.x - a.x, y: a.out.y - a.y } : null
|
|
1718
2298
|
];
|
|
2299
|
+
const ends = [null, null];
|
|
2300
|
+
neighbours(sp, index).forEach((j, side) => {
|
|
2301
|
+
if (j < 0 || j === index) return;
|
|
2302
|
+
const b = anchors[j];
|
|
2303
|
+
const old = Math.hypot(a.x - b.x, a.y - b.y);
|
|
2304
|
+
const s = old > 1e-9 ? Math.hypot(q.x - b.x, q.y - b.y) / old : 1;
|
|
2305
|
+
const key = side === 0 ? "out" : "in";
|
|
2306
|
+
const h = b[key];
|
|
2307
|
+
if (h) b[key] = { x: b.x + (h.x - b.x) * s, y: b.y + (h.y - b.y) * s };
|
|
2308
|
+
const o = own[side];
|
|
2309
|
+
if (o) own[side] = { x: o.x * s, y: o.y * s };
|
|
2310
|
+
ends[side] = { x: b.x, y: b.y };
|
|
2311
|
+
});
|
|
2312
|
+
const moved = { x: q.x, y: q.y };
|
|
2313
|
+
if (own[0]) moved.in = { x: q.x + own[0].x, y: q.y + own[0].y };
|
|
2314
|
+
if (own[1]) moved.out = { x: q.x + own[1].x, y: q.y + own[1].y };
|
|
2315
|
+
if (a.type) moved.type = a.type;
|
|
2316
|
+
if (a.corner) moved.corner = { ...a.corner };
|
|
2317
|
+
const [prev, next] = ends;
|
|
2318
|
+
if (a.type && a.type !== "corner" && prev && next) {
|
|
2319
|
+
const dx = next.x - prev.x;
|
|
2320
|
+
const dy = next.y - prev.y;
|
|
2321
|
+
const l = Math.hypot(dx, dy);
|
|
2322
|
+
if (l > 1e-9) {
|
|
2323
|
+
const len2 = (h, to) => h ? Math.hypot(h.x, h.y) : Math.hypot(q.x - to.x, q.y - to.y) / 3;
|
|
2324
|
+
let li = len2(own[0], prev);
|
|
2325
|
+
let lo = len2(own[1], next);
|
|
2326
|
+
if (a.type === "symmetric") li = lo = (li + lo) / 2;
|
|
2327
|
+
moved.in = { x: q.x - dx * li / l, y: q.y - dy * li / l };
|
|
2328
|
+
moved.out = { x: q.x + dx * lo / l, y: q.y + dy * lo / l };
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
anchors[index] = moved;
|
|
2332
|
+
return { anchors, closed: sp.closed };
|
|
1719
2333
|
}
|
|
1720
|
-
function
|
|
1721
|
-
const
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1724
|
-
const
|
|
1725
|
-
const
|
|
1726
|
-
const
|
|
2334
|
+
function cutSubpathAt(sp, at) {
|
|
2335
|
+
const n = sp.anchors.length;
|
|
2336
|
+
const cuts = [...new Set(at)].filter((i) => Number.isInteger(i) && i >= 0 && i < n && (sp.closed || i > 0 && i < n - 1)).sort((a, b) => a - b);
|
|
2337
|
+
if (!cuts.length || n < 2) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];
|
|
2338
|
+
const first = cuts[0];
|
|
2339
|
+
const run = sp.closed ? Array.from({ length: n + 1 }, (_, i) => sp.anchors[(first + i) % n]) : sp.anchors;
|
|
2340
|
+
const cutSet = new Set(sp.closed ? [...cuts.map((c) => (c - first + n) % n), n] : cuts);
|
|
2341
|
+
const pieces = [];
|
|
2342
|
+
let cur = [];
|
|
2343
|
+
run.forEach((a, i) => {
|
|
2344
|
+
if (!cutSet.has(i)) {
|
|
2345
|
+
cur.push(cloneAnchor(a));
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
if (cur.length) {
|
|
2349
|
+
cur.push(tidy({ ...a, out: null }, false));
|
|
2350
|
+
pieces.push({ anchors: cur, closed: false });
|
|
2351
|
+
cur = [];
|
|
2352
|
+
}
|
|
2353
|
+
if (i < n) cur.push(tidy({ ...a, in: null }, false));
|
|
2354
|
+
});
|
|
2355
|
+
if (cur.length > 1) pieces.push({ anchors: cur, closed: false });
|
|
2356
|
+
return pieces;
|
|
2357
|
+
}
|
|
2358
|
+
function splitSubpathAt(sp, segment, t, endTolerance = 1e-6) {
|
|
2359
|
+
const n = sp.anchors.length;
|
|
2360
|
+
if (segment < 0 || segment >= subpathSegmentCount(sp)) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];
|
|
2361
|
+
if (t <= endTolerance) return cutSubpathAt(sp, [segment]);
|
|
2362
|
+
if (t >= 1 - endTolerance) return cutSubpathAt(sp, [(segment + 1) % n]);
|
|
2363
|
+
const { subpath, index } = insertAnchor(sp, segment, t);
|
|
2364
|
+
return cutSubpathAt(subpath, [index]);
|
|
2365
|
+
}
|
|
2366
|
+
var endOf2 = (sp, last) => sp.anchors[last ? sp.anchors.length - 1 : 0];
|
|
2367
|
+
var dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
2368
|
+
function reverse(sp) {
|
|
2369
|
+
return {
|
|
2370
|
+
closed: sp.closed,
|
|
2371
|
+
anchors: sp.anchors.slice().reverse().map((a) => {
|
|
2372
|
+
const next = { x: a.x, y: a.y };
|
|
2373
|
+
if (a.out) next.in = { ...a.out };
|
|
2374
|
+
if (a.in) next.out = { ...a.in };
|
|
2375
|
+
if (a.type) next.type = a.type;
|
|
2376
|
+
if (a.corner) next.corner = { ...a.corner };
|
|
2377
|
+
return next;
|
|
2378
|
+
})
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
function joinSubpaths(subpaths, tolerance) {
|
|
2382
|
+
const closed = [];
|
|
2383
|
+
let open = [];
|
|
2384
|
+
for (const sp of subpaths) {
|
|
2385
|
+
const copy = { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
|
|
2386
|
+
if (sp.closed || sp.anchors.length < 2) {
|
|
2387
|
+
if (sp.anchors.length) closed.push(copy);
|
|
2388
|
+
} else open.push(copy);
|
|
2389
|
+
}
|
|
2390
|
+
if (open.length === 1) {
|
|
2391
|
+
const sp = open[0];
|
|
2392
|
+
const first = sp.anchors[0];
|
|
2393
|
+
const last = sp.anchors[sp.anchors.length - 1];
|
|
2394
|
+
if (sp.anchors.length > 2 && dist(first, last) <= tolerance) {
|
|
2395
|
+
sp.anchors.pop();
|
|
2396
|
+
sp.anchors[0] = tidy({ ...first, in: _nullishCoalesce(last.in, () => ( null)) }, false);
|
|
2397
|
+
} else {
|
|
2398
|
+
sp.anchors[0] = tidy({ ...first, in: null }, false);
|
|
2399
|
+
sp.anchors[sp.anchors.length - 1] = tidy({ ...sp.anchors[sp.anchors.length - 1], out: null }, false);
|
|
2400
|
+
}
|
|
2401
|
+
sp.closed = true;
|
|
2402
|
+
return [...closed, sp];
|
|
2403
|
+
}
|
|
2404
|
+
while (open.length > 1) {
|
|
2405
|
+
let best = { d: Infinity, i: 0, j: 1, ei: true, ej: false };
|
|
2406
|
+
for (let i = 0; i < open.length; i++) {
|
|
2407
|
+
for (let j = i + 1; j < open.length; j++) {
|
|
2408
|
+
for (const ei of [false, true]) {
|
|
2409
|
+
for (const ej of [false, true]) {
|
|
2410
|
+
const d = dist(endOf2(open[i], ei), endOf2(open[j], ej));
|
|
2411
|
+
if (d < best.d) best = { d, i, j, ei, ej };
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
let a = open[best.i];
|
|
2417
|
+
let b = open[best.j];
|
|
2418
|
+
open = open.filter((_, k) => k !== best.j);
|
|
2419
|
+
if (!best.ei) a = reverse(a);
|
|
2420
|
+
if (best.ej) b = reverse(b);
|
|
2421
|
+
const anchors = a.anchors;
|
|
2422
|
+
if (best.d <= tolerance) {
|
|
2423
|
+
const [head, ...rest] = b.anchors;
|
|
2424
|
+
const l = anchors[anchors.length - 1];
|
|
2425
|
+
anchors[anchors.length - 1] = tidy({ ...l, out: _nullishCoalesce(head.out, () => ( null)) }, false);
|
|
2426
|
+
anchors.push(...rest);
|
|
2427
|
+
} else {
|
|
2428
|
+
const l = anchors[anchors.length - 1];
|
|
2429
|
+
anchors[anchors.length - 1] = tidy({ ...l, out: null }, false);
|
|
2430
|
+
anchors.push(tidy({ ...b.anchors[0], in: null }, false), ...b.anchors.slice(1));
|
|
2431
|
+
}
|
|
2432
|
+
open[best.i > best.j ? best.i - 1 : best.i] = { anchors, closed: false };
|
|
2433
|
+
}
|
|
2434
|
+
return [...closed, ...open];
|
|
2435
|
+
}
|
|
2436
|
+
function smoothSubpath(sp, amount = 1, only) {
|
|
2437
|
+
const t = Math.min(1, Math.max(0, amount));
|
|
2438
|
+
const orig = sp.anchors;
|
|
2439
|
+
const n = orig.length;
|
|
2440
|
+
const anchors = orig.map(cloneAnchor);
|
|
2441
|
+
if (n < 3 || t === 0) return { anchors, closed: sp.closed };
|
|
2442
|
+
for (let i = 0; i < n; i++) {
|
|
2443
|
+
if (!sp.closed && (i === 0 || i === n - 1)) continue;
|
|
2444
|
+
if (only && !only.has(i)) continue;
|
|
2445
|
+
const prev = orig[(i + n - 1) % n];
|
|
2446
|
+
const next = orig[(i + 1) % n];
|
|
2447
|
+
const p = orig[i];
|
|
2448
|
+
const dir = unit2(next.x - prev.x, next.y - prev.y);
|
|
2449
|
+
if (!dir) continue;
|
|
2450
|
+
const li = dist(p, prev) / 3;
|
|
2451
|
+
const lo = dist(p, next) / 3;
|
|
2452
|
+
const hin = _nullishCoalesce(p.in, () => ( p));
|
|
2453
|
+
const hout = _nullishCoalesce(p.out, () => ( p));
|
|
2454
|
+
const target = (h, x, y) => ({ x: h.x + (x - h.x) * t, y: h.y + (y - h.y) * t });
|
|
2455
|
+
anchors[i] = tidy(
|
|
2456
|
+
{
|
|
2457
|
+
x: p.x,
|
|
2458
|
+
y: p.y,
|
|
2459
|
+
in: target(hin, p.x - dir[0] * li, p.y - dir[1] * li),
|
|
2460
|
+
out: target(hout, p.x + dir[0] * lo, p.y + dir[1] * lo)
|
|
2461
|
+
},
|
|
2462
|
+
false
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
return { anchors, closed: sp.closed };
|
|
2466
|
+
}
|
|
2467
|
+
function segsOf(sp) {
|
|
2468
|
+
const out = [];
|
|
2469
|
+
for (let i = 0; i < subpathSegmentCount(sp); i++) {
|
|
2470
|
+
const s = subpathSegment(sp, i);
|
|
2471
|
+
out.push(s.length === 2 ? { c: s, line: true } : { c: s, line: false });
|
|
2472
|
+
}
|
|
2473
|
+
return out;
|
|
2474
|
+
}
|
|
2475
|
+
function segsToSubpath(segs, closed, types) {
|
|
2476
|
+
if (!segs.length) return null;
|
|
2477
|
+
const anchors = [];
|
|
2478
|
+
const at = (p) => ({ x: p[0], y: p[1] });
|
|
2479
|
+
anchors.push(at(segs[0].c[0]));
|
|
2480
|
+
for (const s of segs) {
|
|
2481
|
+
const end = s.c[s.c.length - 1];
|
|
2482
|
+
if (!s.line) anchors[anchors.length - 1].out = { x: s.c[1][0], y: s.c[1][1] };
|
|
2483
|
+
const next = at(end);
|
|
2484
|
+
if (!s.line) next.in = { x: s.c[2][0], y: s.c[2][1] };
|
|
2485
|
+
anchors.push(next);
|
|
2486
|
+
}
|
|
2487
|
+
if (closed) {
|
|
2488
|
+
const last = anchors.pop();
|
|
2489
|
+
if (last.in) anchors[0].in = last.in;
|
|
2490
|
+
}
|
|
2491
|
+
const typed = anchors.map((a) => {
|
|
2492
|
+
const type = types.get(`${a.x},${a.y}`);
|
|
2493
|
+
return tidy(type ? { ...a, type } : a, !!type);
|
|
2494
|
+
});
|
|
2495
|
+
return { anchors: typed, closed };
|
|
2496
|
+
}
|
|
2497
|
+
var isZeroSeg = (s) => s.c.every((p) => Math.abs(p[0] - s.c[0][0]) < 1e-9 && Math.abs(p[1] - s.c[0][1]) < 1e-9);
|
|
2498
|
+
function mergePair(a, b, tol) {
|
|
2499
|
+
const a0 = a.c[0];
|
|
2500
|
+
const a3 = a.c[a.c.length - 1];
|
|
2501
|
+
const b3 = b.c[b.c.length - 1];
|
|
2502
|
+
if (a.line && b.line) {
|
|
2503
|
+
const cx = b3[0] - a0[0];
|
|
2504
|
+
const cy = b3[1] - a0[1];
|
|
2505
|
+
const len2 = Math.hypot(cx, cy);
|
|
2506
|
+
if (len2 < 1e-12) return null;
|
|
2507
|
+
const mx = a3[0] - a0[0];
|
|
2508
|
+
const my = a3[1] - a0[1];
|
|
2509
|
+
const along = (mx * cx + my * cy) / len2;
|
|
2510
|
+
return Math.abs((mx * cy - my * cx) / len2) <= tol && along >= 0 && along <= len2 ? { c: [a0, b3], line: true } : null;
|
|
2511
|
+
}
|
|
2512
|
+
const ca = a.line ? [a0, a0, a3, a3] : a.c;
|
|
2513
|
+
const cb = b.line ? [a3, a3, b3, b3] : b.c;
|
|
2514
|
+
const ta = endTangent(ca);
|
|
2515
|
+
const tb = startTangent(cb);
|
|
2516
|
+
if (ta[0] * tb[0] + ta[1] * tb[1] < 0.99) return null;
|
|
2517
|
+
const pts = [];
|
|
2518
|
+
sample(ca, 16, pts, true);
|
|
2519
|
+
sample(cb, 16, pts, false);
|
|
2520
|
+
const { curve, error } = fitAcross(pts, 16, startTangent(ca), endTangent(cb));
|
|
2521
|
+
return error <= tol ? { c: [curve.p0, curve.p1, curve.p2, curve.p3], line: false } : null;
|
|
2522
|
+
}
|
|
2523
|
+
function removeRedundantPoints(sp, tolerance) {
|
|
2524
|
+
const tol = Math.max(tolerance, 1e-9);
|
|
2525
|
+
const types = new Map(sp.anchors.filter((a) => a.type).map((a) => [`${a.x},${a.y}`, a.type]));
|
|
2526
|
+
const segs = segsOf(sp).filter((s) => !isZeroSeg(s));
|
|
2527
|
+
if (!segs.length) return { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
|
|
2528
|
+
const minSegs = sp.closed ? 2 : 1;
|
|
2529
|
+
let changed = true;
|
|
2530
|
+
while (changed && segs.length > minSegs) {
|
|
2531
|
+
changed = false;
|
|
2532
|
+
const n = segs.length;
|
|
2533
|
+
const joints = sp.closed ? n : n - 1;
|
|
2534
|
+
for (let j = 0; j < joints; j++) {
|
|
2535
|
+
const [i0, i1] = sp.closed ? [(j + n - 1) % n, j] : [j, j + 1];
|
|
2536
|
+
const m = mergePair(segs[i0], segs[i1], tol);
|
|
2537
|
+
if (!m) continue;
|
|
2538
|
+
segs[i0] = m;
|
|
2539
|
+
segs.splice(i1, 1);
|
|
2540
|
+
if (sp.closed && i1 === 0) segs.unshift(segs.pop());
|
|
2541
|
+
changed = true;
|
|
2542
|
+
break;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
return _nullishCoalesce(segsToSubpath(segs, sp.closed, types), () => ( { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }));
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// src/vector/pathCut.ts
|
|
2549
|
+
var loopArea = (l) => _chunkBGNPT5PEcjs.contourSignedArea.call(void 0, l.segs);
|
|
2550
|
+
function loopSample(l) {
|
|
2551
|
+
const k = l.chord.findIndex((c) => !c);
|
|
2552
|
+
return _chunkBGNPT5PEcjs.segmentPoint.call(void 0, l.segs[k >= 0 ? k : 0], 0.5);
|
|
2553
|
+
}
|
|
2554
|
+
var contains2 = (l, p) => _chunkBGNPT5PEcjs.windingNumber.call(void 0, [l.segs], p) !== 0;
|
|
2555
|
+
function groupFaces(loops) {
|
|
2556
|
+
const usable = loops.filter((l) => l.segs.length && Math.abs(loopArea(l)) > 1e-12);
|
|
2557
|
+
if (!usable.length) return [];
|
|
2558
|
+
const largest = usable.reduce((a, b) => Math.abs(loopArea(b)) > Math.abs(loopArea(a)) ? b : a);
|
|
2559
|
+
const sign = Math.sign(loopArea(largest));
|
|
2560
|
+
const outers = usable.filter((l) => Math.sign(loopArea(l)) === sign).sort((a, b) => Math.abs(loopArea(a)) - Math.abs(loopArea(b)));
|
|
2561
|
+
const faces = new Map(outers.map((o) => [o, { loops: [o] }]));
|
|
2562
|
+
for (const hole of usable.filter((l) => Math.sign(loopArea(l)) !== sign)) {
|
|
2563
|
+
const p = loopSample(hole);
|
|
2564
|
+
const parent = outers.find((o) => Math.abs(loopArea(o)) > Math.abs(loopArea(hole)) && contains2(o, p));
|
|
2565
|
+
if (parent) faces.get(parent).loops.push(hole);
|
|
2566
|
+
}
|
|
2567
|
+
return outers.reverse().map((o) => faces.get(o));
|
|
2568
|
+
}
|
|
2569
|
+
function faceToPath(face) {
|
|
2570
|
+
return _chunkBGNPT5PEcjs.contoursToPath.call(void 0, face.loops.map((l) => ({ segments: l.segs, closed: true })));
|
|
2571
|
+
}
|
|
2572
|
+
function facesOf(path, fillRule) {
|
|
2573
|
+
const normalized = _chunkBGNPT5PEcjs.normalizeBezierPath.call(void 0, path, fillRule);
|
|
2574
|
+
const loops = _chunkBGNPT5PEcjs.pathToContours.call(void 0, normalized).map((c) => {
|
|
2575
|
+
const segs = _chunkBGNPT5PEcjs.closeContour.call(void 0, c, true).map((s) => s.slice());
|
|
2576
|
+
return { segs, chord: segs.map(() => false) };
|
|
2577
|
+
});
|
|
2578
|
+
return groupFaces(loops);
|
|
2579
|
+
}
|
|
2580
|
+
function splitIntoFaces(path, fillRule = "nonzero") {
|
|
2581
|
+
return facesOf(path, fillRule).map(faceToPath);
|
|
2582
|
+
}
|
|
2583
|
+
function rootsInUnit(a, b, c, d, scale) {
|
|
2584
|
+
const f = (t) => ((a * t + b) * t + c) * t + d;
|
|
2585
|
+
const tol = 1e-12 * Math.max(1, scale);
|
|
2586
|
+
const cuts = [0];
|
|
2587
|
+
const qa = 3 * a;
|
|
2588
|
+
const qb = 2 * b;
|
|
2589
|
+
if (Math.abs(qa) > 1e-18) {
|
|
2590
|
+
const disc = qb * qb - 4 * qa * c;
|
|
2591
|
+
if (disc >= 0) {
|
|
2592
|
+
const r = Math.sqrt(disc);
|
|
2593
|
+
for (const t of [(-qb - r) / (2 * qa), (-qb + r) / (2 * qa)].sort((x, y) => x - y)) if (t > 0 && t < 1) cuts.push(t);
|
|
2594
|
+
}
|
|
2595
|
+
} else if (Math.abs(qb) > 1e-18) {
|
|
2596
|
+
const t = -c / qb;
|
|
2597
|
+
if (t > 0 && t < 1) cuts.push(t);
|
|
2598
|
+
}
|
|
2599
|
+
cuts.push(1);
|
|
2600
|
+
const roots = [];
|
|
2601
|
+
const push = (t) => {
|
|
2602
|
+
if (!roots.some((r) => Math.abs(r - t) < 1e-9)) roots.push(t);
|
|
2603
|
+
};
|
|
2604
|
+
for (let i = 0; i < cuts.length - 1; i++) {
|
|
2605
|
+
let lo = cuts[i];
|
|
2606
|
+
let hi = cuts[i + 1];
|
|
2607
|
+
let flo = f(lo);
|
|
2608
|
+
const fhi = f(hi);
|
|
2609
|
+
if (Math.abs(flo) <= tol) push(lo);
|
|
2610
|
+
if (Math.abs(fhi) <= tol) push(hi);
|
|
2611
|
+
if (flo * fhi >= 0) continue;
|
|
2612
|
+
for (let k = 0; k < 80; k++) {
|
|
2613
|
+
const m = (lo + hi) / 2;
|
|
2614
|
+
const fm = f(m);
|
|
2615
|
+
if (fm === 0) {
|
|
2616
|
+
lo = hi = m;
|
|
2617
|
+
break;
|
|
2618
|
+
}
|
|
2619
|
+
if (fm * flo < 0) hi = m;
|
|
2620
|
+
else {
|
|
2621
|
+
lo = m;
|
|
2622
|
+
flo = fm;
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
push((lo + hi) / 2);
|
|
2626
|
+
}
|
|
2627
|
+
return roots.sort((x, y) => x - y);
|
|
2628
|
+
}
|
|
2629
|
+
function lineHits(p, q, seg) {
|
|
2630
|
+
const dx = q[0] - p[0];
|
|
2631
|
+
const dy = q[1] - p[1];
|
|
2632
|
+
const len2 = dx * dx + dy * dy;
|
|
2633
|
+
if (len2 < 1e-24) return [];
|
|
2634
|
+
const h = (pt2) => (pt2[0] - p[0]) * -dy + (pt2[1] - p[1]) * dx;
|
|
2635
|
+
let coeffs;
|
|
2636
|
+
if (seg.length === 2) coeffs = [0, 0, h(seg[1]) - h(seg[0]), h(seg[0])];
|
|
2637
|
+
else {
|
|
2638
|
+
const [h0, h1, h2, h3] = seg.map(h);
|
|
2639
|
+
coeffs = [-h0 + 3 * h1 - 3 * h2 + h3, 3 * h0 - 6 * h1 + 3 * h2, -3 * h0 + 3 * h1, h0];
|
|
2640
|
+
}
|
|
2641
|
+
if (coeffs.every((c) => Math.abs(c) < 1e-12 * Math.max(1, len2))) return [];
|
|
2642
|
+
const scale = Math.sqrt(len2) * Math.max(1, ...seg.map((pt2) => Math.abs(pt2[0]) + Math.abs(pt2[1])));
|
|
2643
|
+
const out = [];
|
|
2644
|
+
for (const t of rootsInUnit(...coeffs, scale)) {
|
|
2645
|
+
const point = _chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, t);
|
|
2646
|
+
const s = ((point[0] - p[0]) * dx + (point[1] - p[1]) * dy) / len2;
|
|
2647
|
+
if (s >= -1e-9 && s <= 1 + 1e-9) out.push({ s: Math.min(1, Math.max(0, s)), t, point });
|
|
2648
|
+
}
|
|
2649
|
+
return out;
|
|
2650
|
+
}
|
|
2651
|
+
var near = (a, b, eps) => Math.abs(a[0] - b[0]) <= eps && Math.abs(a[1] - b[1]) <= eps;
|
|
2652
|
+
var degenerate = (seg, eps) => seg.every((p) => near(p, seg[0], eps));
|
|
2653
|
+
function arc(l, a, b, eps) {
|
|
2654
|
+
const n = l.segs.length;
|
|
2655
|
+
const segs = [];
|
|
2656
|
+
const chord = [];
|
|
2657
|
+
const push = (k, t0, t1) => {
|
|
2658
|
+
const piece = t0 <= 0 && t1 >= 1 ? l.segs[k].slice() : _chunkBGNPT5PEcjs.subSegment.call(void 0, l.segs[k], t0, t1);
|
|
2659
|
+
if (!degenerate(piece, eps)) {
|
|
2660
|
+
segs.push(piece);
|
|
2661
|
+
chord.push(l.chord[k]);
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
if (a.seg === b.seg && b.t >= a.t + 1e-12) {
|
|
2665
|
+
push(a.seg, a.t, b.t);
|
|
2666
|
+
return { segs, chord };
|
|
2667
|
+
}
|
|
2668
|
+
push(a.seg, a.t, 1);
|
|
2669
|
+
for (let k = (a.seg + 1) % n; k !== b.seg; k = (k + 1) % n) push(k, 0, 1);
|
|
2670
|
+
push(b.seg, 0, b.t);
|
|
2671
|
+
return { segs, chord };
|
|
2672
|
+
}
|
|
2673
|
+
function polySegs(points, eps) {
|
|
2674
|
+
const out = [];
|
|
2675
|
+
for (let i = 1; i < points.length; i++) if (!near(points[i - 1], points[i], eps)) out.push([points[i - 1], points[i]]);
|
|
2676
|
+
return out;
|
|
2677
|
+
}
|
|
2678
|
+
function knifePath(path, fillRule, knife) {
|
|
2679
|
+
const pts = knife.filter((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]));
|
|
2680
|
+
if (pts.length < 2) return null;
|
|
2681
|
+
let faces = facesOf(path, fillRule);
|
|
2682
|
+
if (!faces.length) return null;
|
|
2683
|
+
const b = _chunkBGNPT5PEcjs.bezierPathBounds.call(void 0, path);
|
|
2684
|
+
const size = b ? Math.max(b.maxX - b.minX, b.maxY - b.minY, 1) : 1;
|
|
2685
|
+
const eps = size * 1e-9;
|
|
2686
|
+
let cursor = -Infinity;
|
|
2687
|
+
let cuts = 0;
|
|
2688
|
+
for (let guard = 0; guard < 512; guard++) {
|
|
2689
|
+
let best = null;
|
|
2690
|
+
faces.forEach((face2, fi2) => {
|
|
2691
|
+
const hits = [];
|
|
2692
|
+
for (let m = 0; m < pts.length - 1; m++) {
|
|
2693
|
+
if (m + 1 < cursor - 1e-9) continue;
|
|
2694
|
+
const kb = { minX: Math.min(pts[m][0], pts[m + 1][0]), maxX: Math.max(pts[m][0], pts[m + 1][0]), minY: Math.min(pts[m][1], pts[m + 1][1]), maxY: Math.max(pts[m][1], pts[m + 1][1]) };
|
|
2695
|
+
face2.loops.forEach(
|
|
2696
|
+
(l, li) => l.segs.forEach((seg, si) => {
|
|
2697
|
+
const sb = _chunkBGNPT5PEcjs.segmentBounds.call(void 0, seg);
|
|
2698
|
+
if (sb.maxX < kb.minX - eps || sb.minX > kb.maxX + eps || sb.maxY < kb.minY - eps || sb.minY > kb.maxY + eps) return;
|
|
2699
|
+
for (const h of lineHits(pts[m], pts[m + 1], seg)) {
|
|
2700
|
+
const s = m + h.s;
|
|
2701
|
+
if (s >= cursor - 1e-9) hits.push({ s, loop: li, seg: si, t: h.t, point: h.point });
|
|
2702
|
+
}
|
|
2703
|
+
})
|
|
2704
|
+
);
|
|
2705
|
+
}
|
|
2706
|
+
hits.sort((x, y) => x.s - y.s);
|
|
2707
|
+
const unique = hits.filter((h, i) => i === 0 || h.s - hits[i - 1].s > 1e-9 || !near(h.point, hits[i - 1].point, eps * 10));
|
|
2708
|
+
const inside = (s) => {
|
|
2709
|
+
const m = Math.min(pts.length - 2, Math.floor(s));
|
|
2710
|
+
const u = s - m;
|
|
2711
|
+
const q = [pts[m][0] + (pts[m + 1][0] - pts[m][0]) * u, pts[m][1] + (pts[m + 1][1] - pts[m][1]) * u];
|
|
2712
|
+
return _chunkBGNPT5PEcjs.windingNumber.call(void 0, face2.loops.map((l) => l.segs), q) !== 0;
|
|
2713
|
+
};
|
|
2714
|
+
for (let i = 0; i < unique.length - 1; i++) {
|
|
2715
|
+
const from2 = unique[i];
|
|
2716
|
+
const to2 = unique[i + 1];
|
|
2717
|
+
if (near(from2.point, to2.point, eps * 10) || !inside((from2.s + to2.s) / 2)) continue;
|
|
2718
|
+
if (!best || from2.s < best.from.s) best = { face: fi2, from: from2, to: to2 };
|
|
2719
|
+
break;
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2722
|
+
if (!best) break;
|
|
2723
|
+
const { face: fi, from, to } = best;
|
|
2724
|
+
const face = faces[fi];
|
|
2725
|
+
const corners2 = [from.point];
|
|
2726
|
+
for (let m = Math.floor(from.s) + 1; m <= to.s - 1e-12 && m < pts.length; m++) if (m > from.s) corners2.push(pts[m]);
|
|
2727
|
+
corners2.push(to.point);
|
|
2728
|
+
const forward = polySegs(corners2, eps);
|
|
2729
|
+
const back = polySegs(corners2.slice().reverse(), eps);
|
|
2730
|
+
const la = face.loops[from.loop];
|
|
2731
|
+
const loops = face.loops.filter((_, i) => i !== from.loop && i !== to.loop);
|
|
2732
|
+
if (from.loop === to.loop) {
|
|
2733
|
+
const ab = arc(la, from, to, eps);
|
|
2734
|
+
const ba = arc(la, to, from, eps);
|
|
2735
|
+
loops.push(
|
|
2736
|
+
{ segs: [...ab.segs, ...back], chord: [...ab.chord, ...back.map(() => true)] },
|
|
2737
|
+
{ segs: [...ba.segs, ...forward], chord: [...ba.chord, ...forward.map(() => true)] }
|
|
2738
|
+
);
|
|
2739
|
+
} else {
|
|
2740
|
+
const lb = face.loops[to.loop];
|
|
2741
|
+
const roundA = arc(la, from, from, eps);
|
|
2742
|
+
const roundB = arc(lb, to, to, eps);
|
|
2743
|
+
loops.push({
|
|
2744
|
+
segs: [...roundA.segs, ...forward, ...roundB.segs, ...back],
|
|
2745
|
+
chord: [...roundA.chord, ...forward.map(() => true), ...roundB.chord, ...back.map(() => true)]
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
faces = [...faces.slice(0, fi), ...groupFaces(loops), ...faces.slice(fi + 1)];
|
|
2749
|
+
cursor = to.s;
|
|
2750
|
+
cuts++;
|
|
2751
|
+
}
|
|
2752
|
+
return cuts ? faces.map(faceToPath) : null;
|
|
2753
|
+
}
|
|
2754
|
+
function eraserRegion(points, width) {
|
|
2755
|
+
const pts = points.filter((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]));
|
|
2756
|
+
const r = width / 2;
|
|
2757
|
+
if (!pts.length || !(r > 0)) return [];
|
|
2758
|
+
const moved = pts.some((p) => !near(p, pts[0], 1e-9));
|
|
2759
|
+
if (!moved) {
|
|
2760
|
+
const [cx, cy] = pts[0];
|
|
2761
|
+
const k = r * 0.5522847498307936;
|
|
2762
|
+
return [
|
|
2763
|
+
{ type: "M", x: cx, y: cy - r },
|
|
2764
|
+
{ type: "C", x1: cx + k, y1: cy - r, x2: cx + r, y2: cy - k, x: cx + r, y: cy },
|
|
2765
|
+
{ type: "C", x1: cx + r, y1: cy + k, x2: cx + k, y2: cy + r, x: cx, y: cy + r },
|
|
2766
|
+
{ type: "C", x1: cx - k, y1: cy + r, x2: cx - r, y2: cy + k, x: cx - r, y: cy },
|
|
2767
|
+
{ type: "C", x1: cx - r, y1: cy - k, x2: cx - k, y2: cy - r, x: cx, y: cy - r },
|
|
2768
|
+
{ type: "Z" }
|
|
2769
|
+
];
|
|
2770
|
+
}
|
|
2771
|
+
const line2 = [{ type: "M", x: pts[0][0], y: pts[0][1] }, ...pts.slice(1).map((p) => ({ type: "L", x: p[0], y: p[1] }))];
|
|
2772
|
+
return _chunkBGNPT5PEcjs.outlineStroke.call(void 0, line2, { width, cap: "round", join: "round" });
|
|
2773
|
+
}
|
|
2774
|
+
function eraseFromFill(path, fillRule, region) {
|
|
2775
|
+
const overlap = _chunkBGNPT5PEcjs.pathBoolean.call(void 0, "intersection", [{ path, fillRule }, { path: region }]).path;
|
|
2776
|
+
if (!_chunkBGNPT5PEcjs.pathToContours.call(void 0, overlap).some((c) => Math.abs(_chunkBGNPT5PEcjs.contourSignedArea.call(void 0, _chunkBGNPT5PEcjs.closeContour.call(void 0, c, true))) > 1e-9)) return null;
|
|
2777
|
+
const rest = _chunkBGNPT5PEcjs.pathBoolean.call(void 0, "difference", [{ path, fillRule }, { path: region }]).path;
|
|
2778
|
+
return splitIntoFaces(rest);
|
|
2779
|
+
}
|
|
2780
|
+
function eraseFromOutline(sp, region) {
|
|
2781
|
+
const regionSegs = _chunkBGNPT5PEcjs.pathToContours.call(void 0, region).flatMap((c) => _chunkBGNPT5PEcjs.closeContour.call(void 0, c, true));
|
|
2782
|
+
if (!regionSegs.length) return null;
|
|
2783
|
+
const rb = _chunkBGNPT5PEcjs.bezierPathBounds.call(void 0, region);
|
|
2784
|
+
const count = subpathSegmentCount(sp);
|
|
2785
|
+
if (!rb || count === 0) return null;
|
|
2786
|
+
const removed = (p) => _chunkBGNPT5PEcjs.pointInBezierPath.call(void 0, region, p, "nonzero");
|
|
2787
|
+
const runs = [];
|
|
2788
|
+
let cur = [];
|
|
2789
|
+
let any = false;
|
|
2790
|
+
for (let i = 0; i < count; i++) {
|
|
2791
|
+
const seg = subpathSegment(sp, i);
|
|
2792
|
+
const sb = _chunkBGNPT5PEcjs.segmentBounds.call(void 0, seg);
|
|
2793
|
+
const ts = [0, 1];
|
|
2794
|
+
if (!(sb.maxX < rb.minX || sb.minX > rb.maxX || sb.maxY < rb.minY || sb.minY > rb.maxY)) {
|
|
2795
|
+
for (const r of regionSegs) {
|
|
2796
|
+
const b = _chunkBGNPT5PEcjs.segmentBounds.call(void 0, r);
|
|
2797
|
+
if (b.maxX < sb.minX || b.minX > sb.maxX || b.maxY < sb.minY || b.minY > sb.maxY) continue;
|
|
2798
|
+
for (const hit of _chunkBGNPT5PEcjs.segmentIntersections.call(void 0, seg, r)) if (hit.ta > 1e-9 && hit.ta < 1 - 1e-9) ts.push(hit.ta);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
ts.sort((a, b) => a - b);
|
|
2802
|
+
for (let k = 0; k < ts.length - 1; k++) {
|
|
2803
|
+
const t0 = ts[k];
|
|
2804
|
+
const t1 = ts[k + 1];
|
|
2805
|
+
if (t1 - t0 < 1e-9) continue;
|
|
2806
|
+
if (removed(_chunkBGNPT5PEcjs.segmentPoint.call(void 0, seg, (t0 + t1) / 2))) {
|
|
2807
|
+
any = true;
|
|
2808
|
+
if (cur.length) runs.push(cur);
|
|
2809
|
+
cur = [];
|
|
2810
|
+
} else cur.push({ seg: i, t0, t1 });
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
if (cur.length) runs.push(cur);
|
|
2814
|
+
if (!any) return null;
|
|
2815
|
+
if (sp.closed && runs.length >= 2) {
|
|
2816
|
+
const first = runs[0][0];
|
|
2817
|
+
const lastRun = runs[runs.length - 1];
|
|
2818
|
+
const last = lastRun[lastRun.length - 1];
|
|
2819
|
+
if (first.seg === 0 && first.t0 === 0 && last.seg === count - 1 && last.t1 === 1) lastRun.push(...runs.shift());
|
|
2820
|
+
}
|
|
2821
|
+
const out = [];
|
|
2822
|
+
for (const run of runs) {
|
|
2823
|
+
const anchors = [];
|
|
2824
|
+
for (const { seg, t0, t1 } of run) {
|
|
2825
|
+
const c = _chunkBGNPT5PEcjs.subSegment.call(void 0, subpathSegment(sp, seg), t0, t1);
|
|
2826
|
+
const line2 = c.length === 2;
|
|
2827
|
+
const start = c[0];
|
|
2828
|
+
const end = c[c.length - 1];
|
|
2829
|
+
if (!anchors.length) anchors.push({ x: start[0], y: start[1] });
|
|
2830
|
+
if (!line2) anchors[anchors.length - 1].out = { x: c[1][0], y: c[1][1] };
|
|
2831
|
+
const next = { x: end[0], y: end[1] };
|
|
2832
|
+
if (!line2) next.in = { x: c[2][0], y: c[2][1] };
|
|
2833
|
+
anchors.push(next);
|
|
2834
|
+
}
|
|
2835
|
+
if (anchors.length < 2) continue;
|
|
2836
|
+
for (const a of anchors) {
|
|
2837
|
+
const type = inferAnchorType(a);
|
|
2838
|
+
if (type !== "corner") a.type = type;
|
|
2839
|
+
}
|
|
2840
|
+
out.push({ anchors, closed: false });
|
|
2841
|
+
}
|
|
2842
|
+
return out;
|
|
2843
|
+
}
|
|
2844
|
+
function polylineBounds(points, pad = 0) {
|
|
2845
|
+
if (!points.length) return null;
|
|
2846
|
+
let minX = Infinity;
|
|
2847
|
+
let minY = Infinity;
|
|
2848
|
+
let maxX = -Infinity;
|
|
2849
|
+
let maxY = -Infinity;
|
|
2850
|
+
for (const [x, y] of points) {
|
|
2851
|
+
minX = Math.min(minX, x);
|
|
2852
|
+
minY = Math.min(minY, y);
|
|
2853
|
+
maxX = Math.max(maxX, x);
|
|
2854
|
+
maxY = Math.max(maxY, y);
|
|
2855
|
+
}
|
|
2856
|
+
return { x: minX - pad, y: minY - pad, width: maxX - minX + 2 * pad, height: maxY - minY + 2 * pad };
|
|
2857
|
+
}
|
|
2858
|
+
function simplifyPolyline(points, tolerance) {
|
|
2859
|
+
const n = points.length;
|
|
2860
|
+
if (n < 3) return points.slice();
|
|
2861
|
+
const keep = new Array(n).fill(false);
|
|
2862
|
+
keep[0] = keep[n - 1] = true;
|
|
2863
|
+
const stack = [[0, n - 1]];
|
|
2864
|
+
while (stack.length) {
|
|
2865
|
+
const [a, b] = stack.pop();
|
|
2866
|
+
const [ax, ay] = points[a];
|
|
2867
|
+
const dx = points[b][0] - ax;
|
|
2868
|
+
const dy = points[b][1] - ay;
|
|
2869
|
+
const len2 = Math.hypot(dx, dy);
|
|
2870
|
+
let far = -1;
|
|
2871
|
+
let farD = 0;
|
|
2872
|
+
for (let i = a + 1; i < b; i++) {
|
|
2873
|
+
const [px, py] = points[i];
|
|
2874
|
+
const d = len2 < 1e-12 ? Math.hypot(px - ax, py - ay) : Math.abs((px - ax) * dy - (py - ay) * dx) / len2;
|
|
2875
|
+
if (d > farD) {
|
|
2876
|
+
farD = d;
|
|
2877
|
+
far = i;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
if (far >= 0 && farD > tolerance) {
|
|
2881
|
+
keep[far] = true;
|
|
2882
|
+
stack.push([a, far], [far, b]);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
return points.filter((_, i) => keep[i]);
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
// src/vector/ink.ts
|
|
2889
|
+
var LazyMouse = (_class2 = class {
|
|
2890
|
+
/** `length` is the string length in the units of the points (0 turns smoothing off). */
|
|
2891
|
+
constructor(length2) {;_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);
|
|
2892
|
+
this.length = length2;
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
__init7() {this.pos = null}
|
|
2896
|
+
__init8() {this.last = null}
|
|
2897
|
+
push(p) {
|
|
2898
|
+
this.last = p;
|
|
2899
|
+
if (!(this.length > 0)) return [p];
|
|
2900
|
+
const pos = this.pos;
|
|
2901
|
+
if (!pos) {
|
|
2902
|
+
this.pos = p;
|
|
2903
|
+
return [p];
|
|
2904
|
+
}
|
|
2905
|
+
const dx = p[0] - pos[0];
|
|
2906
|
+
const dy = p[1] - pos[1];
|
|
2907
|
+
const d = Math.hypot(dx, dy);
|
|
2908
|
+
if (d <= this.length) return [];
|
|
2909
|
+
const k = (d - this.length) / d;
|
|
2910
|
+
const next = [pos[0] + dx * k, pos[1] + dy * k];
|
|
2911
|
+
this.pos = next;
|
|
2912
|
+
return [next];
|
|
2913
|
+
}
|
|
2914
|
+
/** The string from the pen to the pointer while they are apart, else null. */
|
|
2915
|
+
string() {
|
|
2916
|
+
const { pos, last } = this;
|
|
2917
|
+
if (!(this.length > 0) || !pos || !last || pos[0] === last[0] && pos[1] === last[1]) return null;
|
|
2918
|
+
return [pos, last];
|
|
2919
|
+
}
|
|
2920
|
+
/** End of stroke: the pen catches up with the pointer (the point to add, if it lagged). */
|
|
2921
|
+
finish() {
|
|
2922
|
+
const { pos, last } = this;
|
|
2923
|
+
if (!(this.length > 0) || !pos || !last || pos[0] === last[0] && pos[1] === last[1]) return [];
|
|
2924
|
+
this.pos = last;
|
|
2925
|
+
return [last];
|
|
2926
|
+
}
|
|
2927
|
+
}, _class2);
|
|
2928
|
+
var VECTOR_INK_MAX_CORNERS = 8;
|
|
2929
|
+
var MAX_POINTS = 1e5;
|
|
2930
|
+
function vectorInkShapeName(shape) {
|
|
2931
|
+
switch (shape.kind) {
|
|
2932
|
+
case "line":
|
|
2933
|
+
return "line";
|
|
2934
|
+
case "rect":
|
|
2935
|
+
return shape.width === shape.height ? "square" : "rectangle";
|
|
2936
|
+
case "ellipse":
|
|
2937
|
+
return shape.width === shape.height ? "circle" : "ellipse";
|
|
2938
|
+
case "polygon":
|
|
2939
|
+
return shape.points.length === 3 ? "triangle" : "polygon";
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
var dist2 = (a, b) => Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
2943
|
+
function bbox(pts) {
|
|
2944
|
+
if (!pts.length) return [0, 0, 0, 0];
|
|
2945
|
+
let [x0, y0] = pts[0];
|
|
2946
|
+
let [x1, y1] = pts[0];
|
|
2947
|
+
for (const [x, y] of pts) {
|
|
2948
|
+
x0 = Math.min(x0, x);
|
|
2949
|
+
y0 = Math.min(y0, y);
|
|
2950
|
+
x1 = Math.max(x1, x);
|
|
2951
|
+
y1 = Math.max(y1, y);
|
|
2952
|
+
}
|
|
2953
|
+
return [x0, y0, x1 - x0, y1 - y0];
|
|
2954
|
+
}
|
|
2955
|
+
function segDistance(a, b, p) {
|
|
2956
|
+
const dx = b[0] - a[0];
|
|
2957
|
+
const dy = b[1] - a[1];
|
|
2958
|
+
const len2 = dx * dx + dy * dy;
|
|
2959
|
+
const t = len2 > 0 ? Math.min(1, Math.max(0, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2)) : 0;
|
|
2960
|
+
return dist2([a[0] + t * dx, a[1] + t * dy], p);
|
|
2961
|
+
}
|
|
2962
|
+
function polygonDistance(c, p) {
|
|
2963
|
+
let best = Infinity;
|
|
2964
|
+
for (let i = 0; i < c.length; i++) best = Math.min(best, segDistance(c[i], c[(i + 1) % c.length], p));
|
|
2965
|
+
return best;
|
|
2966
|
+
}
|
|
2967
|
+
function ellipseRms(p, x0, y0, w, h) {
|
|
2968
|
+
const rx = w / 2;
|
|
2969
|
+
const ry = h / 2;
|
|
2970
|
+
if (rx <= 0 || ry <= 0 || !p.length) return Infinity;
|
|
2971
|
+
const cx = x0 + rx;
|
|
2972
|
+
const cy = y0 + ry;
|
|
2973
|
+
let sum = 0;
|
|
2974
|
+
for (const [x, y] of p) sum += (Math.hypot((x - cx) / rx, (y - cy) / ry) - 1) ** 2;
|
|
2975
|
+
return Math.sqrt(sum / p.length);
|
|
2976
|
+
}
|
|
2977
|
+
function snapSquare(x, y, w, h) {
|
|
2978
|
+
if (Math.abs(w - h) <= 0.12 * Math.max(w, h)) {
|
|
2979
|
+
const s = (w + h) / 2;
|
|
2980
|
+
return [x + (w - s) / 2, y + (h - s) / 2, s, s];
|
|
2981
|
+
}
|
|
2982
|
+
return [x, y, w, h];
|
|
2983
|
+
}
|
|
2984
|
+
function turning(a, b, c) {
|
|
2985
|
+
const u = [b[0] - a[0], b[1] - a[1]];
|
|
2986
|
+
const v2 = [c[0] - b[0], c[1] - b[1]];
|
|
2987
|
+
const lu = Math.hypot(u[0], u[1]);
|
|
2988
|
+
const lv = Math.hypot(v2[0], v2[1]);
|
|
2989
|
+
if (lu <= 0 || lv <= 0) return 0;
|
|
2990
|
+
return Math.acos(Math.min(1, Math.max(-1, (u[0] * v2[0] + u[1] * v2[1]) / (lu * lv)))) * 180 / Math.PI;
|
|
2991
|
+
}
|
|
2992
|
+
function rdp(p, eps) {
|
|
2993
|
+
const n = p.length;
|
|
2994
|
+
if (n < 3) return p.slice();
|
|
2995
|
+
const keep = new Array(n).fill(false);
|
|
2996
|
+
keep[0] = keep[n - 1] = true;
|
|
2997
|
+
const stack = [[0, n - 1]];
|
|
2998
|
+
while (stack.length) {
|
|
2999
|
+
const [a, b] = stack.pop();
|
|
3000
|
+
let far = a;
|
|
3001
|
+
let farD = 0;
|
|
3002
|
+
for (let i = a + 1; i < b; i++) {
|
|
3003
|
+
const d = segDistance(p[a], p[b], p[i]);
|
|
3004
|
+
if (d > farD) {
|
|
3005
|
+
farD = d;
|
|
3006
|
+
far = i;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
if (farD > eps) {
|
|
3010
|
+
keep[far] = true;
|
|
3011
|
+
stack.push([a, far], [far, b]);
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
return p.filter((_, i) => keep[i]);
|
|
3015
|
+
}
|
|
3016
|
+
function corners(p, diag) {
|
|
3017
|
+
const eps = 0.05 * diag;
|
|
3018
|
+
const start = p[0];
|
|
3019
|
+
let far = 0;
|
|
3020
|
+
let farD = 0;
|
|
3021
|
+
p.forEach((q, i) => {
|
|
3022
|
+
const d = dist2(start, q);
|
|
3023
|
+
if (d > farD) {
|
|
3024
|
+
farD = d;
|
|
3025
|
+
far = i;
|
|
3026
|
+
}
|
|
3027
|
+
});
|
|
3028
|
+
const a = p.slice(0, far);
|
|
3029
|
+
const b = p.slice(far);
|
|
3030
|
+
const ring = rdp([...a, ...b.slice(0, 1)], eps);
|
|
3031
|
+
const back = rdp([...b, start], eps);
|
|
3032
|
+
ring.pop();
|
|
3033
|
+
back.pop();
|
|
3034
|
+
ring.push(...back);
|
|
3035
|
+
while (ring.length > 3) {
|
|
3036
|
+
const n = ring.length;
|
|
3037
|
+
const turn = (i) => turning(ring[(i + n - 1) % n], ring[i], ring[(i + 1) % n]);
|
|
3038
|
+
let flat = 0;
|
|
3039
|
+
let flatT = Infinity;
|
|
3040
|
+
for (let i = 0; i < n; i++) {
|
|
3041
|
+
const t = turn(i);
|
|
3042
|
+
if (t < flatT) {
|
|
3043
|
+
flatT = t;
|
|
3044
|
+
flat = i;
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
let short = -1;
|
|
3048
|
+
for (let j = 0; j < n; j++) {
|
|
3049
|
+
if (dist2(ring[j], ring[(j + 1) % n]) < 0.1 * diag) {
|
|
3050
|
+
short = j;
|
|
3051
|
+
break;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
if (flatT < 30) ring.splice(flat, 1);
|
|
3055
|
+
else if (short >= 0) {
|
|
3056
|
+
const k = turn(short) < turn((short + 1) % n) ? short : (short + 1) % n;
|
|
3057
|
+
ring.splice(k, 1);
|
|
3058
|
+
} else break;
|
|
3059
|
+
}
|
|
3060
|
+
return ring;
|
|
3061
|
+
}
|
|
3062
|
+
function upright(c) {
|
|
3063
|
+
const n = c.length;
|
|
3064
|
+
const dirs = c.map((a, i) => {
|
|
3065
|
+
const b = c[(i + 1) % n];
|
|
3066
|
+
const deg = (Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI % 180 + 180) % 180;
|
|
3067
|
+
if (deg <= 15 || deg >= 165) return "across";
|
|
3068
|
+
if (deg >= 75 && deg <= 105) return "down";
|
|
3069
|
+
return null;
|
|
3070
|
+
});
|
|
3071
|
+
return dirs.every((d) => d !== null) && dirs.every((d, i) => d !== dirs[(i + 1) % n]);
|
|
3072
|
+
}
|
|
3073
|
+
function recognizeInkShape(points, options = {}) {
|
|
3074
|
+
const minSize = _nullishCoalesce(options.minSize, () => ( 6));
|
|
3075
|
+
const p = [];
|
|
3076
|
+
for (const q of points.slice(0, MAX_POINTS)) {
|
|
3077
|
+
if (!Number.isFinite(q[0]) || !Number.isFinite(q[1]) || Math.abs(q[0]) >= 1e9 || Math.abs(q[1]) >= 1e9) continue;
|
|
3078
|
+
const last2 = p[p.length - 1];
|
|
3079
|
+
if (!last2 || last2[0] !== q[0] || last2[1] !== q[1]) p.push([q[0], q[1]]);
|
|
3080
|
+
}
|
|
3081
|
+
if (p.length < 2) return null;
|
|
3082
|
+
const first = p[0];
|
|
3083
|
+
const last = p[p.length - 1];
|
|
3084
|
+
const [x0, y0, w, h] = bbox(p);
|
|
3085
|
+
const size = Math.max(w, h);
|
|
3086
|
+
const diag = Math.hypot(w, h);
|
|
3087
|
+
if (size < minSize) return null;
|
|
3088
|
+
let length2 = 0;
|
|
3089
|
+
for (let i = 1; i < p.length; i++) length2 += dist2(p[i - 1], p[i]);
|
|
3090
|
+
const gap = dist2(first, last);
|
|
3091
|
+
if (gap > 0.85 * length2 && gap >= minSize) {
|
|
3092
|
+
const off = Math.max(...p.map((q) => segDistance(first, last, q)));
|
|
3093
|
+
if (off <= Math.max(0.06 * gap, 1.5 * minSize / 6)) return { kind: "line", a: first, b: last };
|
|
3094
|
+
}
|
|
3095
|
+
if (gap > 0.2 * size || length2 < 2 * size) return null;
|
|
3096
|
+
const rms = ellipseRms(p, x0, y0, w, h);
|
|
3097
|
+
const roundish = w > 0.2 * size && h > 0.2 * size;
|
|
3098
|
+
const ellipse = () => {
|
|
3099
|
+
const [x, y, sw, sh] = snapSquare(x0, y0, w, h);
|
|
3100
|
+
return { kind: "ellipse", x, y, width: sw, height: sh };
|
|
3101
|
+
};
|
|
3102
|
+
if (roundish && rms < 0.06) return ellipse();
|
|
3103
|
+
const c = corners(p, diag);
|
|
3104
|
+
const fits = c.length >= 3 && p.every((q) => polygonDistance(c, q) <= 0.08 * diag);
|
|
3105
|
+
if (fits && c.length === 4 && upright(c)) {
|
|
3106
|
+
const [bx, by, bw, bh] = bbox(c);
|
|
3107
|
+
const [x, y, sw, sh] = snapSquare(bx, by, bw, bh);
|
|
3108
|
+
return { kind: "rect", x, y, width: sw, height: sh };
|
|
3109
|
+
}
|
|
3110
|
+
if (fits && c.length <= VECTOR_INK_MAX_CORNERS) return { kind: "polygon", points: c };
|
|
3111
|
+
return roundish && rms < 0.1 ? ellipse() : null;
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
// src/vector/controller.ts
|
|
3115
|
+
var DEFAULT_VECTOR_SNAP = { grid: 0, objects: true, guides: true, threshold: 5, angles: VECTOR_CONSTRUCTION_ANGLES };
|
|
3116
|
+
var DEFAULT_VECTOR_PATH_EDIT = { keepShapeOnDelete: true, curvatureMoves: false, pencilSmoothing: 0, recognizeShapes: false, eraserSize: 10 };
|
|
3117
|
+
var VECTOR_PENCIL_STRING_PX = 100;
|
|
3118
|
+
var VECTOR_STYLE_KEYS = [
|
|
3119
|
+
"fill",
|
|
3120
|
+
"stroke",
|
|
3121
|
+
"strokeWidth",
|
|
3122
|
+
"strokeLinecap",
|
|
3123
|
+
"strokeLinejoin",
|
|
3124
|
+
"strokeDasharray",
|
|
3125
|
+
"fillRule",
|
|
3126
|
+
"strokeAlign",
|
|
3127
|
+
"startArrow",
|
|
3128
|
+
"endArrow",
|
|
3129
|
+
"arrowAlign",
|
|
3130
|
+
"startArrowScale",
|
|
3131
|
+
"endArrowScale"
|
|
3132
|
+
];
|
|
3133
|
+
var VECTOR_CLIPBOARD_TYPE = "pixerate/vector";
|
|
3134
|
+
var DEFAULT_DEFAULTS = {
|
|
3135
|
+
fill: "#d4d4d8",
|
|
3136
|
+
stroke: "#18181b",
|
|
3137
|
+
strokeWidth: 2,
|
|
3138
|
+
polygonSides: 6,
|
|
3139
|
+
starPoints: 5,
|
|
3140
|
+
starInnerRatio: 0.5,
|
|
3141
|
+
cornerRadius: 0
|
|
3142
|
+
};
|
|
3143
|
+
var MIN_ZOOM = 0.05;
|
|
3144
|
+
var MAX_ZOOM = 64;
|
|
3145
|
+
var VECTOR_ROTATE_HANDLE_OFFSET = 24;
|
|
3146
|
+
var sameRef = (a, b) => a.nodeId === b.nodeId && a.subpath === b.subpath && a.index === b.index;
|
|
3147
|
+
function getEditablePathIds(state) {
|
|
3148
|
+
if (state.tool !== "direct" && state.tool !== "pen") return [];
|
|
3149
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3150
|
+
if (state.pen) ids.add(state.pen.nodeId);
|
|
3151
|
+
if (state.tool === "direct") {
|
|
3152
|
+
if (state.editingId) ids.add(state.editingId);
|
|
3153
|
+
for (const id of state.selection) {
|
|
3154
|
+
const node = findVectorNode(state.document.nodes, id);
|
|
3155
|
+
if (node && !node.locked) {
|
|
3156
|
+
for (const p of collectPathNodes(node)) if (!p.locked) ids.add(p.id);
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
return [...ids].filter((id) => _optionalChain([findVectorNode, 'call', _38 => _38(state.document.nodes, id), 'optionalAccess', _39 => _39.type]) === "path");
|
|
3161
|
+
}
|
|
3162
|
+
function getVisibleAnchors(state) {
|
|
3163
|
+
const out = [];
|
|
3164
|
+
for (const id of getEditablePathIds(state)) {
|
|
3165
|
+
const node = findVectorNode(state.document.nodes, id);
|
|
3166
|
+
node.subpaths.forEach((sp, s) => sp.anchors.forEach((_, i) => out.push({ nodeId: id, subpath: s, index: i })));
|
|
3167
|
+
}
|
|
3168
|
+
return out;
|
|
3169
|
+
}
|
|
3170
|
+
function getVisibleHandles(state) {
|
|
3171
|
+
const out = [];
|
|
3172
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3173
|
+
const push = (node, s, i, handle) => {
|
|
3174
|
+
const sp = node.subpaths[s];
|
|
3175
|
+
const a = _optionalChain([sp, 'optionalAccess', _40 => _40.anchors, 'access', _41 => _41[i]]);
|
|
3176
|
+
if (!a || !a[handle]) return;
|
|
3177
|
+
const key = `${node.id}:${s}:${i}:${handle}`;
|
|
3178
|
+
if (seen.has(key)) return;
|
|
3179
|
+
seen.add(key);
|
|
3180
|
+
out.push({ nodeId: node.id, subpath: s, index: i, handle });
|
|
3181
|
+
};
|
|
3182
|
+
if (state.tool === "pen" && state.pen) {
|
|
3183
|
+
const node = findVectorNode(state.document.nodes, state.pen.nodeId);
|
|
3184
|
+
const sp = _optionalChain([node, 'optionalAccess', _42 => _42.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
|
|
3185
|
+
if (_optionalChain([node, 'optionalAccess', _43 => _43.type]) === "path" && sp) {
|
|
3186
|
+
const last = sp.anchors.length - 1;
|
|
3187
|
+
push(node, state.pen.subpath, last, "in");
|
|
3188
|
+
push(node, state.pen.subpath, last, "out");
|
|
3189
|
+
}
|
|
3190
|
+
return out;
|
|
3191
|
+
}
|
|
3192
|
+
if (state.tool !== "direct") return out;
|
|
3193
|
+
const editable = new Set(getEditablePathIds(state));
|
|
3194
|
+
for (const ref of state.selectedAnchors) {
|
|
3195
|
+
if (!editable.has(ref.nodeId)) continue;
|
|
3196
|
+
const node = findVectorNode(state.document.nodes, ref.nodeId);
|
|
3197
|
+
const sp = node.subpaths[ref.subpath];
|
|
3198
|
+
if (!sp) continue;
|
|
3199
|
+
const n = sp.anchors.length;
|
|
3200
|
+
push(node, ref.subpath, ref.index, "in");
|
|
3201
|
+
push(node, ref.subpath, ref.index, "out");
|
|
3202
|
+
const prev = ref.index > 0 ? ref.index - 1 : sp.closed ? n - 1 : -1;
|
|
3203
|
+
const next = ref.index < n - 1 ? ref.index + 1 : sp.closed ? 0 : -1;
|
|
3204
|
+
if (prev >= 0) push(node, ref.subpath, prev, "out");
|
|
3205
|
+
if (next >= 0) push(node, ref.subpath, next, "in");
|
|
3206
|
+
}
|
|
3207
|
+
return out;
|
|
3208
|
+
}
|
|
3209
|
+
var VECTOR_CORNER_WIDGET_INSET = 10;
|
|
3210
|
+
function getVectorCornerWidgets(state) {
|
|
3211
|
+
if (state.tool !== "direct" || state.pen) return [];
|
|
3212
|
+
const zoom = state.viewport.zoom;
|
|
3213
|
+
const out = [];
|
|
3214
|
+
for (const id of getEditablePathIds(state)) {
|
|
3215
|
+
const node = findVectorNode(state.document.nodes, id);
|
|
3216
|
+
const picked = state.selectedAnchors.filter((r) => r.nodeId === id);
|
|
3217
|
+
const m = _nullishCoalesce(vectorWorldMatrix(state.document.nodes, id), () => ( IDENTITY_MATRIX));
|
|
3218
|
+
for (const c of vectorCorners(node.subpaths)) {
|
|
3219
|
+
if (picked.length && !picked.some((r) => r.subpath === c.subpath && r.index === c.index)) continue;
|
|
3220
|
+
const side = Math.min(c.lu, c.lv);
|
|
3221
|
+
if (side * zoom < 3 * VECTOR_CORNER_WIDGET_INSET) continue;
|
|
3222
|
+
const a = node.subpaths[c.subpath].anchors[c.index];
|
|
3223
|
+
const radius = _nullishCoalesce(_optionalChain([a, 'access', _44 => _44.corner, 'optionalAccess', _45 => _45.radius]), () => ( 0));
|
|
3224
|
+
const drawn = fitCornerRadius(c, radius);
|
|
3225
|
+
const k = Math.min(Math.max(drawn / c.sin, VECTOR_CORNER_WIDGET_INSET / (zoom * Math.sqrt(1 + c.cos))), side / 2);
|
|
3226
|
+
const p = applyMatrix(m, cornerBisectorPoint(c, k));
|
|
3227
|
+
out.push({ ref: { nodeId: id, subpath: c.subpath, index: c.index }, x: p.x, y: p.y, radius, drawnRadius: drawn, kind: _nullishCoalesce(_optionalChain([a, 'access', _46 => _46.corner, 'optionalAccess', _47 => _47.kind]), () => ( "round")), corner: c });
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
return out;
|
|
3231
|
+
}
|
|
3232
|
+
function getAnchor(doc, ref) {
|
|
3233
|
+
const node = findVectorNode(doc.nodes, ref.nodeId);
|
|
3234
|
+
return _optionalChain([node, 'optionalAccess', _48 => _48.type]) === "path" ? _optionalChain([node, 'access', _49 => _49.subpaths, 'access', _50 => _50[ref.subpath], 'optionalAccess', _51 => _51.anchors, 'access', _52 => _52[ref.index]]) : void 0;
|
|
3235
|
+
}
|
|
3236
|
+
function getAnchorWorld(doc, ref) {
|
|
3237
|
+
const a = getAnchor(doc, ref);
|
|
3238
|
+
if (!a) return void 0;
|
|
3239
|
+
const m = _nullishCoalesce(vectorWorldMatrix(doc.nodes, ref.nodeId), () => ( IDENTITY_MATRIX));
|
|
3240
|
+
const p = applyMatrix(m, a);
|
|
3241
|
+
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 } : {} };
|
|
3242
|
+
}
|
|
3243
|
+
function getVectorSelectionFrame(doc, ids) {
|
|
3244
|
+
const nodes = ids.map((id) => ({ node: findVectorNode(doc.nodes, id), parent: _nullishCoalesce(vectorParentMatrix(doc.nodes, id), () => ( IDENTITY_MATRIX)) })).filter((n) => n.node);
|
|
3245
|
+
if (!nodes.length) return null;
|
|
3246
|
+
const angles = nodes.map((n) => matrixRotation(multiplyMatrices(nodeMatrix(n.node), n.parent)));
|
|
3247
|
+
const same = angles.every((a) => Math.abs(normalizeAngle(a - angles[0])) < 1e-6);
|
|
3248
|
+
const angle = same ? angles[0] : 0;
|
|
3249
|
+
const unrotate = rotateMatrix(-angle);
|
|
3250
|
+
const b = unionBounds(nodes.map((n) => vectorNodeBounds(n.node, multiplyMatrices(n.parent, unrotate))));
|
|
3251
|
+
return b ? { ...b, angle } : null;
|
|
3252
|
+
}
|
|
3253
|
+
var normalizeAngle = (a) => ((a + 180) % 360 + 360) % 360 - 180;
|
|
3254
|
+
function getTransformHandlePositions(box) {
|
|
3255
|
+
const { x, y, width: w, height: h } = box;
|
|
3256
|
+
return [
|
|
3257
|
+
{ handle: "nw", x, y },
|
|
3258
|
+
{ handle: "n", x: x + w / 2, y },
|
|
3259
|
+
{ handle: "ne", x: x + w, y },
|
|
3260
|
+
{ handle: "e", x: x + w, y: y + h / 2 },
|
|
3261
|
+
{ handle: "se", x: x + w, y: y + h },
|
|
3262
|
+
{ handle: "s", x: x + w / 2, y: y + h },
|
|
3263
|
+
{ handle: "sw", x, y: y + h },
|
|
3264
|
+
{ handle: "w", x, y: y + h / 2 },
|
|
3265
|
+
{ handle: "rotate", x: x + w / 2, y: y - VECTOR_ROTATE_HANDLE_OFFSET }
|
|
3266
|
+
];
|
|
3267
|
+
}
|
|
3268
|
+
function getVectorSelectionBoxGeometry(frame, viewport) {
|
|
3269
|
+
const z = viewport.zoom;
|
|
3270
|
+
const box = { x: frame.x * z + viewport.x, y: frame.y * z + viewport.y, width: frame.width * z, height: frame.height * z };
|
|
3271
|
+
const rot = rotateMatrix(frame.angle, { x: viewport.x, y: viewport.y });
|
|
3272
|
+
const toScreen2 = (p) => frame.angle ? applyMatrix(rot, p) : { x: p.x, y: p.y };
|
|
3273
|
+
const handles = getTransformHandlePositions(box).map((h) => ({ ...h, screen: toScreen2(h) }));
|
|
3274
|
+
const corners2 = [
|
|
1727
3275
|
{ x: box.x, y: box.y },
|
|
1728
3276
|
{ x: box.x + box.width, y: box.y },
|
|
1729
3277
|
{ x: box.x + box.width, y: box.y + box.height },
|
|
1730
3278
|
{ x: box.x, y: box.y + box.height }
|
|
1731
3279
|
].map(toScreen2);
|
|
1732
3280
|
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 };
|
|
3281
|
+
return { box, angle: frame.angle, transform: frame.angle ? `rotate(${r(frame.angle)} ${r(viewport.x)} ${r(viewport.y)})` : "", handles, corners: corners2 };
|
|
1734
3282
|
}
|
|
1735
3283
|
var RESIZE_CURSOR_ORDER = ["ew-resize", "nwse-resize", "ns-resize", "nesw-resize"];
|
|
1736
3284
|
var HANDLE_ANGLES = { e: 0, se: 45, s: 90, sw: 135, w: 180, nw: 225, n: 270, ne: 315 };
|
|
@@ -1748,9 +3296,12 @@ var TOOL_LABELS = {
|
|
|
1748
3296
|
ellipse: "Ellipse",
|
|
1749
3297
|
polygon: "Polygon",
|
|
1750
3298
|
star: "Star",
|
|
1751
|
-
line: "Line"
|
|
3299
|
+
line: "Line",
|
|
3300
|
+
scissors: "Scissors",
|
|
3301
|
+
knife: "Knife",
|
|
3302
|
+
eraser: "Eraser"
|
|
1752
3303
|
};
|
|
1753
|
-
var TOOL_KEYS = { v: "select", a: "direct", p: "pen", n: "pencil", r: "rect", o: "ellipse",
|
|
3304
|
+
var TOOL_KEYS = { v: "select", a: "direct", p: "pen", n: "pencil", r: "rect", o: "ellipse", y: "polygon", s: "star", l: "line", c: "scissors", k: "knife", e: "eraser" };
|
|
1754
3305
|
function vectorToolLabel(tool) {
|
|
1755
3306
|
return TOOL_LABELS[tool];
|
|
1756
3307
|
}
|
|
@@ -1763,7 +3314,10 @@ var VECTOR_TOOL_SHORTCUTS = {
|
|
|
1763
3314
|
ellipse: "O",
|
|
1764
3315
|
polygon: "Y",
|
|
1765
3316
|
star: "S",
|
|
1766
|
-
line: "L"
|
|
3317
|
+
line: "L",
|
|
3318
|
+
scissors: "C",
|
|
3319
|
+
knife: "K",
|
|
3320
|
+
eraser: "E"
|
|
1767
3321
|
};
|
|
1768
3322
|
var VECTOR_TOOL_GLYPHS = {
|
|
1769
3323
|
select: "\u2196",
|
|
@@ -1774,8 +3328,12 @@ var VECTOR_TOOL_GLYPHS = {
|
|
|
1774
3328
|
ellipse: "\u25EF",
|
|
1775
3329
|
polygon: "\u2B21",
|
|
1776
3330
|
star: "\u2606",
|
|
1777
|
-
line: "\u2571"
|
|
3331
|
+
line: "\u2571",
|
|
3332
|
+
scissors: "\u2702",
|
|
3333
|
+
knife: "\u27CB",
|
|
3334
|
+
eraser: "\u232B"
|
|
1778
3335
|
};
|
|
3336
|
+
var SHAPE_TOOLS = /* @__PURE__ */ new Set(["rect", "ellipse", "polygon", "star", "line"]);
|
|
1779
3337
|
var BOOLEAN_OPS = {
|
|
1780
3338
|
union: "union",
|
|
1781
3339
|
subtract: "difference",
|
|
@@ -1797,48 +3355,72 @@ function mapTree(nodes, fn) {
|
|
|
1797
3355
|
}
|
|
1798
3356
|
return changed ? out : nodes;
|
|
1799
3357
|
}
|
|
3358
|
+
function spliceTree(nodes, id, replacements) {
|
|
3359
|
+
let changed = false;
|
|
3360
|
+
const out = [];
|
|
3361
|
+
for (const node of nodes) {
|
|
3362
|
+
if (node.id === id) {
|
|
3363
|
+
changed = true;
|
|
3364
|
+
out.push(...replacements);
|
|
3365
|
+
continue;
|
|
3366
|
+
}
|
|
3367
|
+
if (node.type === "group") {
|
|
3368
|
+
const children = spliceTree(node.children, id, replacements);
|
|
3369
|
+
if (children !== node.children) {
|
|
3370
|
+
changed = true;
|
|
3371
|
+
out.push({ ...node, children });
|
|
3372
|
+
continue;
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
out.push(node);
|
|
3376
|
+
}
|
|
3377
|
+
return changed ? out : nodes;
|
|
3378
|
+
}
|
|
3379
|
+
var toPoint2 = (p) => [p.x, p.y];
|
|
1800
3380
|
var isRoot = (m) => m === IDENTITY_MATRIX || m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
|
|
1801
3381
|
var idCounter = 0;
|
|
1802
3382
|
var defaultCreateId = () => `vn-${Date.now().toString(36)}${(++idCounter).toString(36)}`;
|
|
1803
|
-
var VectorEditorController = (
|
|
3383
|
+
var VectorEditorController = (_class3 = class {
|
|
1804
3384
|
|
|
1805
3385
|
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
3386
|
+
__init9() {this.selection = []}
|
|
3387
|
+
__init10() {this.anchors = []}
|
|
3388
|
+
__init11() {this.editingId = null}
|
|
3389
|
+
__init12() {this.pen = null}
|
|
1810
3390
|
|
|
1811
3391
|
|
|
1812
3392
|
|
|
1813
3393
|
|
|
1814
3394
|
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
3395
|
+
|
|
3396
|
+
__init13() {this.hover = null}
|
|
3397
|
+
__init14() {this.pointer = null}
|
|
3398
|
+
__init15() {this.drag = null}
|
|
3399
|
+
__init16() {this.interaction = null}
|
|
3400
|
+
__init17() {this.guides = null}
|
|
1820
3401
|
/** Whether the last `snapDrawn` landed on an object (guides may be hidden). */
|
|
1821
|
-
|
|
3402
|
+
__init18() {this.snappedToObject = false}
|
|
1822
3403
|
/** Drawing targets for hovering the pen and shape tools, kept while the document and the excluded path stay the same. */
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
3404
|
+
__init19() {this.hoverTargets = null}
|
|
3405
|
+
__init20() {this.spaceHeld = false}
|
|
3406
|
+
__init21() {this.clipboard = null}
|
|
1826
3407
|
|
|
1827
3408
|
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
3409
|
+
__init22() {this.listeners = /* @__PURE__ */ new Set()}
|
|
3410
|
+
__init23() {this.cleanups = []}
|
|
3411
|
+
__init24() {this.applying = false}
|
|
1831
3412
|
/** Set while a commit records a change that was already applied, so `restore` still reports it. */
|
|
1832
|
-
|
|
1833
|
-
|
|
3413
|
+
__init25() {this.committing = false}
|
|
3414
|
+
__init26() {this.revision = 0}
|
|
1834
3415
|
|
|
1835
|
-
constructor(options = {}) {;
|
|
3416
|
+
constructor(options = {}) {;_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);_class3.prototype.__init17.call(this);_class3.prototype.__init18.call(this);_class3.prototype.__init19.call(this);_class3.prototype.__init20.call(this);_class3.prototype.__init21.call(this);_class3.prototype.__init22.call(this);_class3.prototype.__init23.call(this);_class3.prototype.__init24.call(this);_class3.prototype.__init25.call(this);_class3.prototype.__init26.call(this);_class3.prototype.__init27.call(this);_class3.prototype.__init28.call(this);
|
|
1836
3417
|
this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkQKLRAA4Tcjs.HistoryManager)()));
|
|
1837
3418
|
this.doc = options.document ? cloneVectorDocument(options.document) : { width: 800, height: 600, nodes: [] };
|
|
1838
3419
|
this.tool = _nullishCoalesce(options.tool, () => ( "select"));
|
|
1839
3420
|
this.defaults = { ...DEFAULT_DEFAULTS, ..._nullishCoalesce(options.defaults, () => ( {})) };
|
|
1840
3421
|
this.viewport = { x: 0, y: 0, zoom: 1, ..._nullishCoalesce(options.viewport, () => ( {})) };
|
|
1841
3422
|
this.snap = { ...DEFAULT_VECTOR_SNAP, ..._nullishCoalesce(options.snap, () => ( {})) };
|
|
3423
|
+
this.editing = { ...DEFAULT_VECTOR_PATH_EDIT, ..._nullishCoalesce(options.editing, () => ( {})) };
|
|
1842
3424
|
this.hitRadius = _nullishCoalesce(options.hitRadius, () => ( 6));
|
|
1843
3425
|
this.createId = _nullishCoalesce(options.createId, () => ( defaultCreateId));
|
|
1844
3426
|
this.onChange = options.onChange;
|
|
@@ -1860,9 +3442,9 @@ var VectorEditorController = (_class2 = class {
|
|
|
1860
3442
|
// State
|
|
1861
3443
|
// -------------------------------------------------------------------------
|
|
1862
3444
|
/** The current snapshot (stable until the next change). */
|
|
1863
|
-
|
|
3445
|
+
__init27() {this.getState = () => this.state}
|
|
1864
3446
|
/** Calls `listener` after every change. Returns an unsubscribe function. */
|
|
1865
|
-
|
|
3447
|
+
__init28() {this.subscribe = (listener) => {
|
|
1866
3448
|
this.listeners.add(listener);
|
|
1867
3449
|
return () => {
|
|
1868
3450
|
this.listeners.delete(listener);
|
|
@@ -1887,6 +3469,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
1887
3469
|
viewport: this.viewport,
|
|
1888
3470
|
defaults: this.defaults,
|
|
1889
3471
|
snap: this.snap,
|
|
3472
|
+
editing: this.editing,
|
|
1890
3473
|
guides: this.guides,
|
|
1891
3474
|
hitRadius: this.hitRadius,
|
|
1892
3475
|
hover: this.hover,
|
|
@@ -1904,7 +3487,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
1904
3487
|
notify(docChanged = false) {
|
|
1905
3488
|
this.revision++;
|
|
1906
3489
|
this.state = this.computeState();
|
|
1907
|
-
if (docChanged) _optionalChain([this, 'access',
|
|
3490
|
+
if (docChanged) _optionalChain([this, 'access', _53 => _53.onChange, 'optionalCall', _54 => _54(this.doc)]);
|
|
1908
3491
|
const state = this.state;
|
|
1909
3492
|
this.listeners.forEach((listener) => {
|
|
1910
3493
|
try {
|
|
@@ -1934,20 +3517,20 @@ var VectorEditorController = (_class2 = class {
|
|
|
1934
3517
|
return applyMatrix(invertMatrix(this.worldOf(id, doc)), p);
|
|
1935
3518
|
}
|
|
1936
3519
|
cursorFor() {
|
|
1937
|
-
if (_optionalChain([this, 'access',
|
|
1938
|
-
if (_optionalChain([this, 'access',
|
|
1939
|
-
if (_optionalChain([this, 'access',
|
|
3520
|
+
if (_optionalChain([this, 'access', _55 => _55.drag, 'optionalAccess', _56 => _56.kind]) === "pan" || this.spaceHeld) return this.drag ? "grabbing" : "grab";
|
|
3521
|
+
if (_optionalChain([this, 'access', _57 => _57.drag, 'optionalAccess', _58 => _58.kind]) === "rotate") return "grabbing";
|
|
3522
|
+
if (_optionalChain([this, 'access', _59 => _59.drag, 'optionalAccess', _60 => _60.kind]) === "move") return "move";
|
|
1940
3523
|
const h = this.hover;
|
|
1941
3524
|
switch (this.tool) {
|
|
1942
3525
|
case "select":
|
|
1943
|
-
if (_optionalChain([h, 'optionalAccess',
|
|
1944
|
-
return _optionalChain([h, 'optionalAccess',
|
|
3526
|
+
if (_optionalChain([h, 'optionalAccess', _61 => _61.transformHandle])) return vectorResizeCursor(h.transformHandle, _nullishCoalesce(_optionalChain([this, 'access', _62 => _62.frameOf, 'call', _63 => _63(this.selectedRoots()), 'optionalAccess', _64 => _64.angle]), () => ( 0)));
|
|
3527
|
+
return _optionalChain([h, 'optionalAccess', _65 => _65.nodeId]) ? "move" : "default";
|
|
1945
3528
|
case "direct":
|
|
1946
|
-
if (_optionalChain([h, 'optionalAccess',
|
|
1947
|
-
if (_optionalChain([h, 'optionalAccess',
|
|
1948
|
-
return _optionalChain([h, 'optionalAccess',
|
|
3529
|
+
if (_optionalChain([h, 'optionalAccess', _66 => _66.anchor]) || _optionalChain([h, 'optionalAccess', _67 => _67.handle]) || _optionalChain([h, 'optionalAccess', _68 => _68.corner])) return "pointer";
|
|
3530
|
+
if (_optionalChain([h, 'optionalAccess', _69 => _69.segment])) return "copy";
|
|
3531
|
+
return _optionalChain([h, 'optionalAccess', _70 => _70.nodeId]) ? "move" : "default";
|
|
1949
3532
|
case "pen":
|
|
1950
|
-
return _optionalChain([h, 'optionalAccess',
|
|
3533
|
+
return _optionalChain([h, 'optionalAccess', _71 => _71.anchor]) ? "pointer" : "crosshair";
|
|
1951
3534
|
default:
|
|
1952
3535
|
return "crosshair";
|
|
1953
3536
|
}
|
|
@@ -1967,7 +3550,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
1967
3550
|
this.selection = s.selection.filter((id) => this.getNode(id));
|
|
1968
3551
|
this.anchors = s.anchors.filter((r) => getAnchor(this.doc, r));
|
|
1969
3552
|
this.editingId = s.editingId && this.getNode(s.editingId) ? s.editingId : null;
|
|
1970
|
-
this.pen = s.pen && _optionalChain([this, 'access',
|
|
3553
|
+
this.pen = s.pen && _optionalChain([this, 'access', _72 => _72.getNode, 'call', _73 => _73(s.pen.nodeId), 'optionalAccess', _74 => _74.subpaths, 'optionalAccess', _75 => _75[s.pen.subpath]]) ? s.pen : null;
|
|
1971
3554
|
} finally {
|
|
1972
3555
|
this.applying = false;
|
|
1973
3556
|
}
|
|
@@ -2028,12 +3611,12 @@ var VectorEditorController = (_class2 = class {
|
|
|
2028
3611
|
// Viewport
|
|
2029
3612
|
// -------------------------------------------------------------------------
|
|
2030
3613
|
screenToDocument(p) {
|
|
2031
|
-
const
|
|
2032
|
-
return { x: (p.x -
|
|
3614
|
+
const v2 = this.viewport;
|
|
3615
|
+
return { x: (p.x - v2.x) / v2.zoom, y: (p.y - v2.y) / v2.zoom };
|
|
2033
3616
|
}
|
|
2034
3617
|
documentToScreen(p) {
|
|
2035
|
-
const
|
|
2036
|
-
return { x: p.x *
|
|
3618
|
+
const v2 = this.viewport;
|
|
3619
|
+
return { x: p.x * v2.zoom + v2.x, y: p.y * v2.zoom + v2.y };
|
|
2037
3620
|
}
|
|
2038
3621
|
setViewport(viewport) {
|
|
2039
3622
|
const next = { ...this.viewport, ...viewport };
|
|
@@ -2088,13 +3671,21 @@ var VectorEditorController = (_class2 = class {
|
|
|
2088
3671
|
if (!this.snap.guides || !this.snap.objects) this.guides = null;
|
|
2089
3672
|
this.notify();
|
|
2090
3673
|
}
|
|
3674
|
+
/** Changes how deleting, dragging anchors, the pencil and the eraser behave. */
|
|
3675
|
+
setEditing(editing) {
|
|
3676
|
+
const next = { ...this.editing, ...editing };
|
|
3677
|
+
next.pencilSmoothing = Math.min(1, Math.max(0, Number.isFinite(next.pencilSmoothing) ? next.pencilSmoothing : 0));
|
|
3678
|
+
if (!(next.eraserSize > 0)) next.eraserSize = this.editing.eraserSize;
|
|
3679
|
+
this.editing = next;
|
|
3680
|
+
this.notify();
|
|
3681
|
+
}
|
|
2091
3682
|
/** Turns object snapping on or off (the toolbar's snapping toggle). */
|
|
2092
3683
|
toggleSnapping(on = !this.snap.objects) {
|
|
2093
3684
|
this.setSnap({ objects: on });
|
|
2094
3685
|
}
|
|
2095
|
-
gridValue(
|
|
3686
|
+
gridValue(v2) {
|
|
2096
3687
|
const g = this.snap.grid;
|
|
2097
|
-
return g > 0 ? Math.round(
|
|
3688
|
+
return g > 0 ? Math.round(v2 / g) * g : v2;
|
|
2098
3689
|
}
|
|
2099
3690
|
snapPoint(p) {
|
|
2100
3691
|
return { x: this.gridValue(p.x), y: this.gridValue(p.y) };
|
|
@@ -2123,7 +3714,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
2123
3714
|
if (res.kind) {
|
|
2124
3715
|
this.snappedToObject = true;
|
|
2125
3716
|
this.setGuides(res);
|
|
2126
|
-
return _optionalChain([leave, 'optionalAccess',
|
|
3717
|
+
return _optionalChain([leave, 'optionalAccess', _76 => _76.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
3718
|
}
|
|
2128
3719
|
}
|
|
2129
3720
|
this.setGuides(null);
|
|
@@ -2141,16 +3732,16 @@ var VectorEditorController = (_class2 = class {
|
|
|
2141
3732
|
/** Where the pointer at `p` would place a point with the pen or a shape tool (and the guides showing why). */
|
|
2142
3733
|
hoverSnap(p, input) {
|
|
2143
3734
|
if (this.tool === "pen") {
|
|
2144
|
-
const sp = this.pen ? _optionalChain([this, 'access',
|
|
2145
|
-
if (this.pen && sp && _optionalChain([this, 'access',
|
|
3735
|
+
const sp = this.pen ? _optionalChain([this, 'access', _77 => _77.getNode, 'call', _78 => _78(this.pen.nodeId), 'optionalAccess', _79 => _79.subpaths, 'access', _80 => _80[this.pen.subpath]]) : void 0;
|
|
3736
|
+
if (this.pen && sp && _optionalChain([this, 'access', _81 => _81.hover, 'optionalAccess', _82 => _82.anchor])) {
|
|
2146
3737
|
this.setGuides(null);
|
|
2147
3738
|
return p;
|
|
2148
3739
|
}
|
|
2149
3740
|
const last = this.pen && sp ? getAnchorWorld(this.doc, { nodeId: this.pen.nodeId, subpath: this.pen.subpath, index: sp.anchors.length - 1 }) : void 0;
|
|
2150
3741
|
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',
|
|
3742
|
+
return this.snapDrawn(p, leave, input, () => this.drawingTargets(_nullishCoalesce(_optionalChain([this, 'access', _83 => _83.pen, 'optionalAccess', _84 => _84.nodeId]), () => ( null))));
|
|
2152
3743
|
}
|
|
2153
|
-
if (
|
|
3744
|
+
if (SHAPE_TOOLS.has(this.tool)) return this.snapDrawn(p, null, input, () => this.drawingTargets(null));
|
|
2154
3745
|
this.setGuides(null);
|
|
2155
3746
|
return p;
|
|
2156
3747
|
}
|
|
@@ -2250,7 +3841,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
2250
3841
|
let subpath;
|
|
2251
3842
|
switch (input.kind) {
|
|
2252
3843
|
case "rect":
|
|
2253
|
-
subpath = rectSubpath(b
|
|
3844
|
+
subpath = rectSubpath(b);
|
|
2254
3845
|
break;
|
|
2255
3846
|
case "ellipse":
|
|
2256
3847
|
subpath = ellipseSubpath(b);
|
|
@@ -2265,10 +3856,13 @@ var VectorEditorController = (_class2 = class {
|
|
|
2265
3856
|
subpath = lineSubpath({ x: b.x, y: b.y }, { x: b.x + b.width, y: b.y + b.height });
|
|
2266
3857
|
break;
|
|
2267
3858
|
}
|
|
3859
|
+
const radius = _nullishCoalesce(input.cornerRadius, () => ( d.cornerRadius));
|
|
3860
|
+
const kind = _nullishCoalesce(input.cornerKind, () => ( d.cornerKind));
|
|
3861
|
+
if ((input.kind === "rect" || input.kind === "polygon" || input.kind === "star") && radius > 0) subpath = setSubpathCorners(subpath, radius, kind);
|
|
2268
3862
|
const names = { rect: "Rectangle", ellipse: "Ellipse", polygon: "Polygon", star: "Star", line: "Line" };
|
|
2269
3863
|
const style = this.styleFor(input.kind === "line");
|
|
2270
3864
|
const own = {};
|
|
2271
|
-
for (const key of
|
|
3865
|
+
for (const key of VECTOR_STYLE_KEYS) {
|
|
2272
3866
|
if (input[key] !== void 0) own[key] = input[key];
|
|
2273
3867
|
}
|
|
2274
3868
|
return { id: this.newId(), type: "path", name: _nullishCoalesce(input.name, () => ( names[input.kind])), ...style, ...own, subpaths: [subpath] };
|
|
@@ -2291,6 +3885,8 @@ var VectorEditorController = (_class2 = class {
|
|
|
2291
3885
|
if (d.strokeLinejoin) out.strokeLinejoin = d.strokeLinejoin;
|
|
2292
3886
|
if (d.strokeDasharray) out.strokeDasharray = d.strokeDasharray;
|
|
2293
3887
|
if (d.fillRule) out.fillRule = d.fillRule;
|
|
3888
|
+
if (d.strokeAlign && d.strokeAlign !== "center") out.strokeAlign = d.strokeAlign;
|
|
3889
|
+
for (const key of ["startArrow", "endArrow", "arrowAlign", "startArrowScale", "endArrowScale"]) if (d[key]) out[key] = d[key];
|
|
2294
3890
|
return out;
|
|
2295
3891
|
}
|
|
2296
3892
|
/** Merges `patch` into a node (paths take style and geometry; groups name / visibility / opacity). */
|
|
@@ -2315,6 +3911,95 @@ var VectorEditorController = (_class2 = class {
|
|
|
2315
3911
|
});
|
|
2316
3912
|
return { ok: true };
|
|
2317
3913
|
}
|
|
3914
|
+
/**
|
|
3915
|
+
* Sets the live corner radius (and optionally the kind) of anchors: `refs`, else the selected
|
|
3916
|
+
* anchors (direct tool), else every anchor of the `ids` / selected paths. Radius 0 makes the
|
|
3917
|
+
* corners sharp again. Corners shrink to fit when drawn, so any radius is accepted.
|
|
3918
|
+
*/
|
|
3919
|
+
setCornerRadius(radius, options = {}) {
|
|
3920
|
+
if (!Number.isFinite(radius) || radius < 0) return { ok: false, message: "The radius must be a number of at least 0." };
|
|
3921
|
+
const refs = this.cornerTargets(options);
|
|
3922
|
+
if (!refs.length) return { ok: false, message: "Nothing is selected." };
|
|
3923
|
+
this.change(radius > 0 ? "Corner Radius" : "Sharpen Corners", () => {
|
|
3924
|
+
this.doc = this.updateAnchors(this.doc, refs, (a) => setAnchorCorner(a, radius, options.kind));
|
|
3925
|
+
});
|
|
3926
|
+
return { ok: true };
|
|
3927
|
+
}
|
|
3928
|
+
/** Sets the live corner kind of the same anchors as `setCornerRadius`, keeping their radii. */
|
|
3929
|
+
setCornerKind(kind, options = {}) {
|
|
3930
|
+
const refs = this.cornerTargets(options);
|
|
3931
|
+
if (!refs.length) return { ok: false, message: "Nothing is selected." };
|
|
3932
|
+
this.change("Corner Kind", () => {
|
|
3933
|
+
this.doc = this.updateAnchors(this.doc, refs, (a) => setAnchorCorner(a, _nullishCoalesce(_optionalChain([a, 'access', _85 => _85.corner, 'optionalAccess', _86 => _86.radius]), () => ( 0)), kind));
|
|
3934
|
+
});
|
|
3935
|
+
return { ok: true };
|
|
3936
|
+
}
|
|
3937
|
+
/** Anchors a corner change applies to (see `setCornerRadius`). */
|
|
3938
|
+
cornerTargets(options) {
|
|
3939
|
+
if (options.refs) return options.refs.filter((r) => getAnchor(this.doc, r));
|
|
3940
|
+
if (!options.ids && this.tool === "direct" && this.anchors.length) return this.anchors.slice();
|
|
3941
|
+
const out = [];
|
|
3942
|
+
for (const id of _nullishCoalesce(options.ids, () => ( this.selection))) {
|
|
3943
|
+
const node = this.getNode(id);
|
|
3944
|
+
if (!node) continue;
|
|
3945
|
+
for (const p of collectPathNodes(node)) {
|
|
3946
|
+
if (p.locked) continue;
|
|
3947
|
+
p.subpaths.forEach((sp, s) => sp.anchors.forEach((_, i) => out.push({ nodeId: p.id, subpath: s, index: i })));
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
return out;
|
|
3951
|
+
}
|
|
3952
|
+
/**
|
|
3953
|
+
* Outline Stroke: replaces each selected (or given) path's stroke with a filled path of the
|
|
3954
|
+
* area it paints (alignment, joins, caps, dashes and arrowheads included), placed above a copy
|
|
3955
|
+
* of the path that keeps the fill (left out when there is none). Returns the new paths' ids.
|
|
3956
|
+
*/
|
|
3957
|
+
outlineStroke(options = {}) {
|
|
3958
|
+
const roots = _nullishCoalesce(options.ids, () => ( this.selectedRoots()));
|
|
3959
|
+
const paths = [];
|
|
3960
|
+
for (const id of roots) {
|
|
3961
|
+
const node = this.getNode(id);
|
|
3962
|
+
if (node) {
|
|
3963
|
+
for (const p of collectPathNodes(node)) if (!p.locked && hasPaint(p.stroke) && (_nullishCoalesce(p.strokeWidth, () => ( 1))) > 0) paths.push(p);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
if (!paths.length) return [];
|
|
3967
|
+
const created = [];
|
|
3968
|
+
const replace = /* @__PURE__ */ new Map();
|
|
3969
|
+
for (const p of paths) {
|
|
3970
|
+
const outline = bezierToSubpaths(vectorStrokeOutlinePath(subpathsToBezier(p.subpaths), p));
|
|
3971
|
+
if (!outline.length) continue;
|
|
3972
|
+
const {
|
|
3973
|
+
stroke,
|
|
3974
|
+
strokeWidth: _w,
|
|
3975
|
+
strokeLinecap: _c,
|
|
3976
|
+
strokeLinejoin: _j,
|
|
3977
|
+
strokeDasharray: _d,
|
|
3978
|
+
strokeAlign: _a,
|
|
3979
|
+
startArrow: _s,
|
|
3980
|
+
endArrow: _e,
|
|
3981
|
+
arrowAlign: _al,
|
|
3982
|
+
startArrowScale: _ss,
|
|
3983
|
+
endArrowScale: _es,
|
|
3984
|
+
...rest
|
|
3985
|
+
} = cloneVectorNode(p);
|
|
3986
|
+
const id = this.newId();
|
|
3987
|
+
const strokeNode = { ...rest, id, name: `${_nullishCoalesce(p.name, () => ( "Path"))} Stroke`, subpaths: outline, fill: _nullishCoalesce(stroke, () => ( null)), stroke: null, fillRule: "nonzero" };
|
|
3988
|
+
const list = hasPaint(p.fill) ? [{ ...rest, stroke: null }, strokeNode] : [strokeNode];
|
|
3989
|
+
created.push(id);
|
|
3990
|
+
replace.set(p.id, list);
|
|
3991
|
+
}
|
|
3992
|
+
if (!created.length) return [];
|
|
3993
|
+
this.change("Outline Stroke", () => {
|
|
3994
|
+
let nodes = this.doc.nodes;
|
|
3995
|
+
for (const [id, list] of replace) nodes = spliceTree(nodes, id, list);
|
|
3996
|
+
this.setDoc(nodes);
|
|
3997
|
+
this.selection = created;
|
|
3998
|
+
this.anchors = [];
|
|
3999
|
+
this.editingId = null;
|
|
4000
|
+
});
|
|
4001
|
+
return created;
|
|
4002
|
+
}
|
|
2318
4003
|
rename(id, name) {
|
|
2319
4004
|
return this.updateNode(id, { name }, { label: "Rename" });
|
|
2320
4005
|
}
|
|
@@ -2383,10 +4068,10 @@ var VectorEditorController = (_class2 = class {
|
|
|
2383
4068
|
return this.selection.filter((id) => {
|
|
2384
4069
|
let covered = false;
|
|
2385
4070
|
const loc = locateVectorNode(this.doc.nodes, id);
|
|
2386
|
-
let parent = _nullishCoalesce(_optionalChain([loc, 'optionalAccess',
|
|
4071
|
+
let parent = _nullishCoalesce(_optionalChain([loc, 'optionalAccess', _87 => _87.parent]), () => ( null));
|
|
2387
4072
|
while (parent) {
|
|
2388
4073
|
if (this.selection.includes(parent.id)) covered = true;
|
|
2389
|
-
parent = _nullishCoalesce(_optionalChain([locateVectorNode, 'call',
|
|
4074
|
+
parent = _nullishCoalesce(_optionalChain([locateVectorNode, 'call', _88 => _88(this.doc.nodes, parent.id), 'optionalAccess', _89 => _89.parent]), () => ( null));
|
|
2390
4075
|
}
|
|
2391
4076
|
return !covered;
|
|
2392
4077
|
});
|
|
@@ -2569,8 +4254,8 @@ var VectorEditorController = (_class2 = class {
|
|
|
2569
4254
|
if (!ids.length) return null;
|
|
2570
4255
|
const locs = ids.map((id2) => locateVectorNode(this.doc.nodes, id2));
|
|
2571
4256
|
if (locs.some((l) => !l)) return null;
|
|
2572
|
-
const parent = _nullishCoalesce(_optionalChain([locs, 'access',
|
|
2573
|
-
if (locs.some((l) => (_nullishCoalesce(_optionalChain([l, 'access',
|
|
4257
|
+
const parent = _nullishCoalesce(_optionalChain([locs, 'access', _90 => _90[0], 'access', _91 => _91.parent, 'optionalAccess', _92 => _92.id]), () => ( null));
|
|
4258
|
+
if (locs.some((l) => (_nullishCoalesce(_optionalChain([l, 'access', _93 => _93.parent, 'optionalAccess', _94 => _94.id]), () => ( null))) !== parent)) return null;
|
|
2574
4259
|
const id = this.newId();
|
|
2575
4260
|
this.change("Group", () => {
|
|
2576
4261
|
const set = new Set(ids);
|
|
@@ -2593,7 +4278,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
2593
4278
|
}
|
|
2594
4279
|
/** Replaces selected groups with their children. */
|
|
2595
4280
|
ungroup(options = {}) {
|
|
2596
|
-
const groups = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess',
|
|
4281
|
+
const groups = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess', _95 => _95.type]) === "group");
|
|
2597
4282
|
if (!groups.length) return { ok: false, message: "Select a group to ungroup." };
|
|
2598
4283
|
const set = new Set(groups.map((g) => g.id));
|
|
2599
4284
|
this.change("Ungroup", () => {
|
|
@@ -2653,7 +4338,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
2653
4338
|
});
|
|
2654
4339
|
const paths = [...new Set(order)].map((id2) => this.getNode(id2));
|
|
2655
4340
|
if (paths.length < 2) return null;
|
|
2656
|
-
const result =
|
|
4341
|
+
const result = _chunkBGNPT5PEcjs.pathBoolean.call(void 0,
|
|
2657
4342
|
BOOLEAN_OPS[op],
|
|
2658
4343
|
paths.map((p) => ({
|
|
2659
4344
|
path: vectorPathWorldBezier(
|
|
@@ -2703,11 +4388,11 @@ var VectorEditorController = (_class2 = class {
|
|
|
2703
4388
|
/** A document-space path mapped into the parent space of node `id`. */
|
|
2704
4389
|
intoParentSpace(id, path) {
|
|
2705
4390
|
const parent = _nullishCoalesce(vectorParentMatrix(this.doc.nodes, id), () => ( IDENTITY_MATRIX));
|
|
2706
|
-
return isRoot(parent) ? path :
|
|
4391
|
+
return isRoot(parent) ? path : _chunkBGNPT5PEcjs.transformBezierPath.call(void 0, path, invertMatrix(parent));
|
|
2707
4392
|
}
|
|
2708
4393
|
/** Splits selected compound paths into one path per subpath. */
|
|
2709
4394
|
breakApart(options = {}) {
|
|
2710
|
-
const paths = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess',
|
|
4395
|
+
const paths = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess', _96 => _96.type]) === "path" && n.subpaths.length > 1);
|
|
2711
4396
|
if (!paths.length) return { ok: false, message: "Select a compound path to break apart." };
|
|
2712
4397
|
const created = [];
|
|
2713
4398
|
const set = new Set(paths.map((p) => p.id));
|
|
@@ -2753,12 +4438,47 @@ var VectorEditorController = (_class2 = class {
|
|
|
2753
4438
|
return translateAnchor(a, d.x, d.y);
|
|
2754
4439
|
});
|
|
2755
4440
|
}
|
|
2756
|
-
/**
|
|
2757
|
-
|
|
4441
|
+
/**
|
|
4442
|
+
* Moves one anchor to a document position. Its handles follow; with `curvature` (default:
|
|
4443
|
+
* the `curvatureMoves` setting) its two segments bend instead, as the Curvature tool does.
|
|
4444
|
+
*/
|
|
4445
|
+
moveAnchor(ref, to, options = {}) {
|
|
2758
4446
|
const a = getAnchorWorld(this.doc, ref);
|
|
2759
4447
|
if (!a) return { ok: false, message: "There is no such anchor." };
|
|
4448
|
+
const curvature = _nullishCoalesce(options.curvature, () => ( this.editing.curvatureMoves));
|
|
2760
4449
|
this.change("Move Point", () => {
|
|
2761
|
-
this.doc = this.movedAnchors(this.doc, [ref], to.x - a.x, to.y - a.y);
|
|
4450
|
+
this.doc = curvature ? this.curvatureMoved(this.doc, ref, to) : this.movedAnchors(this.doc, [ref], to.x - a.x, to.y - a.y);
|
|
4451
|
+
});
|
|
4452
|
+
return { ok: true };
|
|
4453
|
+
}
|
|
4454
|
+
/** `doc` with anchor `ref` moved to document point `to` by a Curvature-tool move. */
|
|
4455
|
+
curvatureMoved(doc, ref, to) {
|
|
4456
|
+
const node = findVectorNode(doc.nodes, ref.nodeId);
|
|
4457
|
+
if (_optionalChain([node, 'optionalAccess', _97 => _97.type]) !== "path" || node.locked || !node.subpaths[ref.subpath]) return doc;
|
|
4458
|
+
const q = this.toLocal(ref.nodeId, to, doc);
|
|
4459
|
+
const subpaths = node.subpaths.map((sp, i) => i === ref.subpath ? curvatureMoveAnchor(sp, ref.index, q) : sp);
|
|
4460
|
+
const nodes = mapTree(doc.nodes, (n) => n.id === ref.nodeId ? { ...node, subpaths } : n);
|
|
4461
|
+
return { ...doc, nodes };
|
|
4462
|
+
}
|
|
4463
|
+
/** `doc` with segment `ref` bent through document point `p` at parameter `t`. */
|
|
4464
|
+
bent(doc, ref, t, p) {
|
|
4465
|
+
const node = findVectorNode(doc.nodes, ref.nodeId);
|
|
4466
|
+
if (_optionalChain([node, 'optionalAccess', _98 => _98.type]) !== "path" || node.locked || !node.subpaths[ref.subpath]) return doc;
|
|
4467
|
+
const q = this.toLocal(ref.nodeId, p, doc);
|
|
4468
|
+
const subpaths = node.subpaths.map((sp, i) => i === ref.subpath ? bendSegment(sp, ref.segment, t, q) : sp);
|
|
4469
|
+
return { ...doc, nodes: mapTree(doc.nodes, (n) => n.id === ref.nodeId ? { ...node, subpaths } : n) };
|
|
4470
|
+
}
|
|
4471
|
+
/**
|
|
4472
|
+
* Bends a segment so the curve passes through document point `to` at parameter `t`
|
|
4473
|
+
* (default 0.5, clamped to 0.05–0.95); see `bendSegment` in `pathEdit`.
|
|
4474
|
+
*/
|
|
4475
|
+
bendSegment(ref, to, t = 0.5) {
|
|
4476
|
+
const node = this.getNode(ref.nodeId);
|
|
4477
|
+
const sp = _optionalChain([node, 'optionalAccess', _99 => _99.type]) === "path" ? node.subpaths[ref.subpath] : void 0;
|
|
4478
|
+
if (!sp || ref.segment < 0 || ref.segment >= (sp.closed ? sp.anchors.length : sp.anchors.length - 1) || sp.anchors.length < 2) return { ok: false, message: "There is no such segment." };
|
|
4479
|
+
if (node.locked) return { ok: false, message: "The path is locked." };
|
|
4480
|
+
this.change("Bend Segment", () => {
|
|
4481
|
+
this.doc = this.bent(this.doc, ref, t, to);
|
|
2762
4482
|
});
|
|
2763
4483
|
return { ok: true };
|
|
2764
4484
|
}
|
|
@@ -2789,7 +4509,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
2789
4509
|
/** Adds an anchor on the nearest segment of `nodeId` to document point `point` (shape unchanged). */
|
|
2790
4510
|
insertAnchorAt(nodeId, point) {
|
|
2791
4511
|
const node = this.getNode(nodeId);
|
|
2792
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
4512
|
+
if (_optionalChain([node, 'optionalAccess', _100 => _100.type]) !== "path") return null;
|
|
2793
4513
|
const hit = nearestSegment(node, this.toLocal(nodeId, point));
|
|
2794
4514
|
if (!hit) return null;
|
|
2795
4515
|
const { subpath, index } = insertAnchor(node.subpaths[hit.subpath], hit.segment, hit.t);
|
|
@@ -2801,15 +4521,24 @@ var VectorEditorController = (_class2 = class {
|
|
|
2801
4521
|
});
|
|
2802
4522
|
return ref;
|
|
2803
4523
|
}
|
|
2804
|
-
/**
|
|
2805
|
-
|
|
4524
|
+
/**
|
|
4525
|
+
* Deletes the selected (or given) anchors; subpaths left with one anchor and empty paths go
|
|
4526
|
+
* too. With `keepShape` (default: the `keepShapeOnDelete` setting, on) the neighbours'
|
|
4527
|
+
* handles are refitted so the path keeps its shape; off, the neighbours are joined as they are.
|
|
4528
|
+
*/
|
|
4529
|
+
deleteAnchors(refs = this.anchors, options = {}) {
|
|
2806
4530
|
if (!refs.length) return { ok: false, message: "Select points to delete." };
|
|
4531
|
+
const keepShape = _nullishCoalesce(options.keepShape, () => ( this.editing.keepShapeOnDelete));
|
|
2807
4532
|
this.change("Delete Points", () => {
|
|
2808
4533
|
const nodes = mapTree(this.doc.nodes, (n) => {
|
|
2809
|
-
if (n.type !== "path") return n;
|
|
4534
|
+
if (n.type !== "path" || n.locked) return n;
|
|
2810
4535
|
const mine = refs.filter((r) => r.nodeId === n.id);
|
|
2811
4536
|
if (!mine.length) return n;
|
|
2812
|
-
const subpaths = n.subpaths.map((sp, s) =>
|
|
4537
|
+
const subpaths = n.subpaths.map((sp, s) => {
|
|
4538
|
+
const indices = mine.filter((r) => r.subpath === s).map((r) => r.index);
|
|
4539
|
+
if (!indices.length) return sp;
|
|
4540
|
+
return keepShape ? removeAnchorsKeepingShape(sp, indices) : removeAnchors(sp, indices);
|
|
4541
|
+
}).filter((sp) => sp.anchors.length > 1);
|
|
2813
4542
|
return subpaths.length ? { ...n, subpaths } : null;
|
|
2814
4543
|
});
|
|
2815
4544
|
this.setDoc(nodes);
|
|
@@ -2824,7 +4553,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
2824
4553
|
setClosed(closed, nodeId) {
|
|
2825
4554
|
const targets = nodeId ? (() => {
|
|
2826
4555
|
const n = this.getNode(nodeId);
|
|
2827
|
-
return _optionalChain([n, 'optionalAccess',
|
|
4556
|
+
return _optionalChain([n, 'optionalAccess', _101 => _101.type]) === "path" ? n.subpaths.map((_, s) => ({ nodeId, subpath: s })) : [];
|
|
2828
4557
|
})() : this.anchors.map((r) => ({ nodeId: r.nodeId, subpath: r.subpath }));
|
|
2829
4558
|
if (!targets.length) return { ok: false, message: "Select a path or points." };
|
|
2830
4559
|
this.change(closed ? "Close Path" : "Open Path", () => {
|
|
@@ -2835,23 +4564,286 @@ var VectorEditorController = (_class2 = class {
|
|
|
2835
4564
|
)
|
|
2836
4565
|
);
|
|
2837
4566
|
});
|
|
2838
|
-
return { ok: true };
|
|
4567
|
+
return { ok: true };
|
|
4568
|
+
}
|
|
4569
|
+
/** Reverses the direction of every subpath of the selected paths. */
|
|
4570
|
+
reversePaths(ids = this.selection) {
|
|
4571
|
+
const set = new Set(ids.flatMap((id) => this.getNode(id) ? collectPathNodes(this.getNode(id)) : []).map((p) => p.id));
|
|
4572
|
+
if (!set.size) return { ok: false, message: "Select a path." };
|
|
4573
|
+
this.change("Reverse Path", () => {
|
|
4574
|
+
this.setDoc(mapTree(this.doc.nodes, (n) => n.type === "path" && set.has(n.id) ? { ...n, subpaths: n.subpaths.map(reverseSubpath) } : n));
|
|
4575
|
+
this.anchors = [];
|
|
4576
|
+
});
|
|
4577
|
+
return { ok: true };
|
|
4578
|
+
}
|
|
4579
|
+
// -------------------------------------------------------------------------
|
|
4580
|
+
// Path clean-up, Scissors, Knife and Eraser
|
|
4581
|
+
// -------------------------------------------------------------------------
|
|
4582
|
+
/** Unlocked path nodes in the given roots (default: the paths being edited, else the selection). */
|
|
4583
|
+
pathTargets(ids) {
|
|
4584
|
+
const roots = _nullishCoalesce(ids, () => ( (this.tool === "direct" && this.anchors.length ? [...new Set(this.anchors.map((r) => r.nodeId))] : this.selection)));
|
|
4585
|
+
const out = /* @__PURE__ */ new Map();
|
|
4586
|
+
for (const id of roots) {
|
|
4587
|
+
const node = this.getNode(id);
|
|
4588
|
+
if (node && !node.locked) {
|
|
4589
|
+
for (const p of collectPathNodes(node)) if (!p.locked) out.set(p.id, p);
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
const order = [];
|
|
4593
|
+
walkVectorNodes(this.doc.nodes, (n) => {
|
|
4594
|
+
if (out.has(n.id)) order.push(n.id);
|
|
4595
|
+
});
|
|
4596
|
+
return order.map((id) => out.get(id));
|
|
4597
|
+
}
|
|
4598
|
+
/** Subpaths of node `from` mapped into node `to`'s own space. */
|
|
4599
|
+
subpathsIn(from, to) {
|
|
4600
|
+
const m = multiplyMatrices(this.worldOf(from.id), invertMatrix(this.worldOf(to)));
|
|
4601
|
+
if (isRoot(m)) return from.subpaths;
|
|
4602
|
+
const map = (p) => applyMatrix(m, p);
|
|
4603
|
+
return from.subpaths.map((sp) => ({
|
|
4604
|
+
closed: sp.closed,
|
|
4605
|
+
anchors: sp.anchors.map((a) => ({ ...map(a), ...a.in ? { in: map(a.in) } : {}, ...a.out ? { out: map(a.out) } : {}, ...a.type ? { type: a.type } : {}, ...a.corner ? { corner: { ...a.corner } } : {} }))
|
|
4606
|
+
}));
|
|
4607
|
+
}
|
|
4608
|
+
/**
|
|
4609
|
+
* Replaces a path with pieces: each entry becomes one path (the first keeps the node's id and
|
|
4610
|
+
* place, the others go just above it with the same style). No pieces removes the path.
|
|
4611
|
+
* Returns the ids of the paths now standing in for it.
|
|
4612
|
+
*/
|
|
4613
|
+
replaceWithPieces(node, pieces) {
|
|
4614
|
+
const nodes = pieces.filter((p) => p.length).map((subpaths, i) => ({ ...node, id: i === 0 ? node.id : this.newId(), subpaths }));
|
|
4615
|
+
this.setDoc(spliceTree(this.doc.nodes, node.id, nodes));
|
|
4616
|
+
return nodes.map((n) => n.id);
|
|
4617
|
+
}
|
|
4618
|
+
/** Clears anchor selections, editing and pen state that no longer point at anything. */
|
|
4619
|
+
dropStaleRefs() {
|
|
4620
|
+
this.selection = this.selection.filter((id) => this.getNode(id));
|
|
4621
|
+
this.anchors = this.anchors.filter((r) => getAnchor(this.doc, r));
|
|
4622
|
+
if (this.editingId && !this.getNode(this.editingId)) this.editingId = null;
|
|
4623
|
+
if (this.pen && !this.getNode(this.pen.nodeId)) this.pen = null;
|
|
4624
|
+
}
|
|
4625
|
+
/**
|
|
4626
|
+
* Join: the open subpaths of the selected (or given) paths become one open path, joined
|
|
4627
|
+
* nearest endpoints first (ends within `tolerance` document units merge into one anchor,
|
|
4628
|
+
* others get a straight segment); a single open subpath is closed. The result replaces the
|
|
4629
|
+
* lowest path, in its space and style. Default tolerance: the pick distance.
|
|
4630
|
+
*/
|
|
4631
|
+
joinPaths(options = {}) {
|
|
4632
|
+
const paths = this.pathTargets(options.ids);
|
|
4633
|
+
if (!paths.length) return { ok: false, message: "Select paths to join." };
|
|
4634
|
+
const base = paths[0];
|
|
4635
|
+
const all = paths.flatMap((p) => this.subpathsIn(p, base.id));
|
|
4636
|
+
if (!all.some((sp) => !sp.closed && sp.anchors.length > 1)) return { ok: false, message: "There are no open paths to join." };
|
|
4637
|
+
const subpaths = joinSubpaths(all, _nullishCoalesce(options.tolerance, () => ( this.tolerance())));
|
|
4638
|
+
const drop = new Set(paths.slice(1).map((p) => p.id));
|
|
4639
|
+
this.change("Join", () => {
|
|
4640
|
+
this.setDoc(mapTree(this.doc.nodes, (n) => n.id === base.id ? { ...base, subpaths } : drop.has(n.id) ? null : n));
|
|
4641
|
+
this.selection = [base.id];
|
|
4642
|
+
this.anchors = [];
|
|
4643
|
+
if (this.editingId && drop.has(this.editingId)) this.editingId = base.id;
|
|
4644
|
+
this.dropStaleRefs();
|
|
4645
|
+
});
|
|
4646
|
+
return { ok: true };
|
|
4647
|
+
}
|
|
4648
|
+
/** Average: moves the selected (or given) anchors onto their average position along `axis`, in document space. */
|
|
4649
|
+
averageAnchors(axis = "both", refs = this.anchors) {
|
|
4650
|
+
const points = refs.map((r) => ({ ref: r, at: getAnchorWorld(this.doc, r) })).filter((p) => !!p.at);
|
|
4651
|
+
if (points.length < 2) return { ok: false, message: "Select at least two points to average." };
|
|
4652
|
+
const cx = points.reduce((s, p) => s + p.at.x, 0) / points.length;
|
|
4653
|
+
const cy = points.reduce((s, p) => s + p.at.y, 0) / points.length;
|
|
4654
|
+
this.change("Average", () => {
|
|
4655
|
+
for (const { ref, at } of points) {
|
|
4656
|
+
const dx = axis === "horizontal" ? 0 : cx - at.x;
|
|
4657
|
+
const dy = axis === "vertical" ? 0 : cy - at.y;
|
|
4658
|
+
if (dx || dy) this.doc = this.movedAnchors(this.doc, [ref], dx, dy);
|
|
4659
|
+
}
|
|
4660
|
+
});
|
|
4661
|
+
return { ok: true };
|
|
4662
|
+
}
|
|
4663
|
+
/** Applies `fn` to every subpath of the target paths as one undo step. */
|
|
4664
|
+
editSubpaths(label, ids, fn) {
|
|
4665
|
+
const paths = this.pathTargets(ids);
|
|
4666
|
+
if (!paths.length) return { ok: false, message: "Select a path." };
|
|
4667
|
+
const set = new Set(paths.map((p) => p.id));
|
|
4668
|
+
this.change(label, () => {
|
|
4669
|
+
this.setDoc(mapTree(this.doc.nodes, (n) => n.type === "path" && set.has(n.id) ? { ...n, subpaths: n.subpaths.map((sp, i) => fn(sp, n, i)) } : n));
|
|
4670
|
+
this.dropStaleRefs();
|
|
4671
|
+
});
|
|
4672
|
+
return { ok: true };
|
|
4673
|
+
}
|
|
4674
|
+
/**
|
|
4675
|
+
* Remove Redundant Points: drops anchors of the selected (or given) paths that can go
|
|
4676
|
+
* without moving the outline more than `tolerance` document units (default 0.5).
|
|
4677
|
+
*/
|
|
4678
|
+
removeRedundantPoints(options = {}) {
|
|
4679
|
+
const tolerance = _nullishCoalesce(options.tolerance, () => ( 0.5));
|
|
4680
|
+
return this.editSubpaths("Remove Redundant Points", options.ids, (sp) => removeRedundantPoints(sp, tolerance));
|
|
4681
|
+
}
|
|
4682
|
+
/**
|
|
4683
|
+
* Smooth: pulls handles towards smooth tangents by `amount` (0–1, default 1). With the direct
|
|
4684
|
+
* tool and points selected, only those points change.
|
|
4685
|
+
*/
|
|
4686
|
+
smoothPaths(options = {}) {
|
|
4687
|
+
const refs = _nullishCoalesce(options.refs, () => ( (!options.ids && this.tool === "direct" && this.anchors.length ? this.anchors : null)));
|
|
4688
|
+
const ids = _nullishCoalesce(options.ids, () => ( (refs ? [...new Set(refs.map((r) => r.nodeId))] : void 0)));
|
|
4689
|
+
return this.editSubpaths("Smooth", ids, (sp, node, i) => {
|
|
4690
|
+
const only = refs ? new Set(refs.filter((r) => r.nodeId === node.id && r.subpath === i).map((r) => r.index)) : void 0;
|
|
4691
|
+
return only && !only.size ? sp : smoothSubpath(sp, _nullishCoalesce(options.amount, () => ( 1)), only);
|
|
4692
|
+
});
|
|
4693
|
+
}
|
|
4694
|
+
/** Splits a cut subpath into pieces: separate paths for a one-subpath node, subpaths in place for a compound one. */
|
|
4695
|
+
applyCuts(node, cut) {
|
|
4696
|
+
const results = node.subpaths.map((sp, i) => cut(sp, i));
|
|
4697
|
+
if (results.every((r) => !r)) return null;
|
|
4698
|
+
const subpaths = node.subpaths.flatMap((sp, i) => _nullishCoalesce(results[i], () => ( [sp])));
|
|
4699
|
+
if (node.subpaths.length === 1) return this.replaceWithPieces(node, subpaths.map((sp) => [sp]));
|
|
4700
|
+
this.replaceNode(node.id, { ...node, subpaths });
|
|
4701
|
+
return [node.id];
|
|
4702
|
+
}
|
|
4703
|
+
/**
|
|
4704
|
+
* Scissors: cuts path `nodeId` at the outline point nearest document point `point` (at an
|
|
4705
|
+
* anchor when within the pick distance of one). A closed subpath opens there; an open one
|
|
4706
|
+
* splits in two (a one-subpath path becomes two paths). Returns the ids of the paths.
|
|
4707
|
+
*/
|
|
4708
|
+
cutPathAt(nodeId, point) {
|
|
4709
|
+
const node = this.getNode(nodeId);
|
|
4710
|
+
if (_optionalChain([node, 'optionalAccess', _102 => _102.type]) !== "path" || node.locked) return null;
|
|
4711
|
+
const local = this.toLocal(nodeId, point);
|
|
4712
|
+
const hit = nearestSegment(node, local);
|
|
4713
|
+
if (!hit) return null;
|
|
4714
|
+
const sp = node.subpaths[hit.subpath];
|
|
4715
|
+
const n = sp.anchors.length;
|
|
4716
|
+
const near2 = [hit.segment, (hit.segment + 1) % n].find((i) => Math.hypot(sp.anchors[i].x - hit.point.x, sp.anchors[i].y - hit.point.y) <= this.tolerance());
|
|
4717
|
+
if (!sp.closed && n > 1 && (near2 === 0 || near2 === n - 1) && !(hit.t > 1e-6 && hit.t < 1 - 1e-6)) return null;
|
|
4718
|
+
let ids = null;
|
|
4719
|
+
this.change("Cut Path", () => {
|
|
4720
|
+
ids = this.applyCuts(node, (s, i) => i !== hit.subpath ? null : near2 !== void 0 ? cutSubpathAt(s, [near2]) : splitSubpathAt(s, hit.segment, hit.t));
|
|
4721
|
+
if (ids) {
|
|
4722
|
+
this.selection = ids;
|
|
4723
|
+
this.anchors = [];
|
|
4724
|
+
if (this.editingId === nodeId) this.editingId = null;
|
|
4725
|
+
}
|
|
4726
|
+
this.dropStaleRefs();
|
|
4727
|
+
});
|
|
4728
|
+
return ids;
|
|
4729
|
+
}
|
|
4730
|
+
/** Scissors on the selected (or given) anchors: each cuts its subpath there (see `cutPathAt`). */
|
|
4731
|
+
cutAtAnchors(refs = this.anchors) {
|
|
4732
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
4733
|
+
for (const r of refs) if (getAnchor(this.doc, r)) byNode.set(r.nodeId, [..._nullishCoalesce(byNode.get(r.nodeId), () => ( [])), r]);
|
|
4734
|
+
if (!byNode.size) return { ok: false, message: "Select points to cut at." };
|
|
4735
|
+
const created = [];
|
|
4736
|
+
this.change("Cut Path", () => {
|
|
4737
|
+
for (const [id, list] of byNode) {
|
|
4738
|
+
const node = this.getNode(id);
|
|
4739
|
+
if (node.locked) continue;
|
|
4740
|
+
const ids = this.applyCuts(node, (sp, i) => {
|
|
4741
|
+
const at = list.filter((r) => r.subpath === i).map((r) => r.index);
|
|
4742
|
+
const pieces = at.length ? cutSubpathAt(sp, at) : null;
|
|
4743
|
+
return pieces && !(pieces.length === 1 && pieces[0].closed === sp.closed) ? pieces : null;
|
|
4744
|
+
});
|
|
4745
|
+
if (ids) created.push(...ids);
|
|
4746
|
+
}
|
|
4747
|
+
if (created.length) this.selection = created;
|
|
4748
|
+
this.anchors = [];
|
|
4749
|
+
this.dropStaleRefs();
|
|
4750
|
+
});
|
|
4751
|
+
return created.length ? { ok: true } : { ok: false, message: "Points at the ends of open paths do not cut." };
|
|
4752
|
+
}
|
|
4753
|
+
/** Visible, unlocked paths a stroke over `points` can reach: in `ids`, the selection, or (with neither) any whose bounds it crosses. */
|
|
4754
|
+
strokeTargets(points, ids, pad) {
|
|
4755
|
+
const blocked = /* @__PURE__ */ new Set();
|
|
4756
|
+
walkVectorNodes(this.doc.nodes, (n, parent) => {
|
|
4757
|
+
if (n.visible === false || n.locked || parent && blocked.has(parent.id)) blocked.add(n.id);
|
|
4758
|
+
});
|
|
4759
|
+
const visible = (p) => !blocked.has(p.id);
|
|
4760
|
+
if (_optionalChain([ids, 'optionalAccess', _103 => _103.length]) || this.selection.length) return this.pathTargets(_nullishCoalesce(ids, () => ( this.selection))).filter(visible);
|
|
4761
|
+
const box = polylineBounds(points.map(toPoint2), pad);
|
|
4762
|
+
if (!box) return [];
|
|
4763
|
+
const out = [];
|
|
4764
|
+
walkVectorNodes(this.doc.nodes, (n) => {
|
|
4765
|
+
if (n.type !== "path" || !visible(n)) return;
|
|
4766
|
+
const b = vectorNodeBounds(n, _nullishCoalesce(vectorParentMatrix(this.doc.nodes, n.id), () => ( IDENTITY_MATRIX)));
|
|
4767
|
+
if (b && boundsIntersect(b, box)) out.push(n);
|
|
4768
|
+
});
|
|
4769
|
+
return out;
|
|
4770
|
+
}
|
|
4771
|
+
/** A stroke's points in node `id`'s own space. */
|
|
4772
|
+
localPoints(id, points) {
|
|
4773
|
+
const inv = invertMatrix(this.worldOf(id));
|
|
4774
|
+
return points.map((p) => toPoint2(applyMatrix(inv, p)));
|
|
4775
|
+
}
|
|
4776
|
+
/**
|
|
4777
|
+
* Knife: cuts closed paths along the polyline `points` (document space) wherever it crosses
|
|
4778
|
+
* them from edge to edge, with no gap between the pieces. It cuts the given paths, the
|
|
4779
|
+
* selection, or (with neither) every path it crosses. Returns the ids of the pieces (empty
|
|
4780
|
+
* when nothing was cut).
|
|
4781
|
+
*/
|
|
4782
|
+
knife(points, options = {}) {
|
|
4783
|
+
const pts = simplifyPolyline(points.map(toPoint2), 0.25 / this.viewport.zoom).map(([x, y]) => ({ x, y }));
|
|
4784
|
+
if (pts.length < 2) return [];
|
|
4785
|
+
const plan = [];
|
|
4786
|
+
for (const node of this.strokeTargets(pts, options.ids, 0)) {
|
|
4787
|
+
const closed = node.subpaths.filter((sp) => sp.anchors.length > 1);
|
|
4788
|
+
if (!closed.length || closed.some((sp) => !sp.closed)) continue;
|
|
4789
|
+
const result = knifePath(subpathsToBezier(closed), _nullishCoalesce(node.fillRule, () => ( "nonzero")), this.localPoints(node.id, pts));
|
|
4790
|
+
if (result) plan.push({ node, pieces: result.map((path) => bezierToSubpaths(path)) });
|
|
4791
|
+
}
|
|
4792
|
+
if (!plan.length) return [];
|
|
4793
|
+
const created = [];
|
|
4794
|
+
this.change("Knife", () => {
|
|
4795
|
+
for (const { node, pieces } of plan) created.push(...this.replaceWithPieces(node, pieces));
|
|
4796
|
+
this.selection = created;
|
|
4797
|
+
this.anchors = [];
|
|
4798
|
+
this.dropStaleRefs();
|
|
4799
|
+
});
|
|
4800
|
+
return created;
|
|
2839
4801
|
}
|
|
2840
|
-
/**
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
4802
|
+
/**
|
|
4803
|
+
* Eraser: rubs out a round stroke of diameter `size` (default: the `eraserSize` setting)
|
|
4804
|
+
* along `points` (document space). Filled closed paths lose that area (pieces that come
|
|
4805
|
+
* apart become separate paths); outlines are cut where they pass under it. It erases from
|
|
4806
|
+
* the given paths, the selection, or (with neither) every path it reaches. Returns the ids
|
|
4807
|
+
* of the paths it changed or created.
|
|
4808
|
+
*/
|
|
4809
|
+
erase(points, options = {}) {
|
|
4810
|
+
const width = _nullishCoalesce(options.size, () => ( this.editing.eraserSize));
|
|
4811
|
+
if (!(width > 0) || !points.length) return [];
|
|
4812
|
+
const pts = simplifyPolyline(points.map(toPoint2), Math.min(width * 0.05, 0.25 / this.viewport.zoom));
|
|
4813
|
+
const region = eraserRegion(pts, width);
|
|
4814
|
+
if (!region.length) return [];
|
|
4815
|
+
const plan = [];
|
|
4816
|
+
const rb = _chunkBGNPT5PEcjs.bezierPathBounds.call(void 0, region);
|
|
4817
|
+
const reach = rb ? [{ x: rb.minX, y: rb.minY }, { x: rb.maxX, y: rb.maxY }] : points;
|
|
4818
|
+
for (const node of this.strokeTargets(reach, options.ids, 0)) {
|
|
4819
|
+
const local = _chunkBGNPT5PEcjs.transformBezierPath.call(void 0, region, invertMatrix(this.worldOf(node.id)));
|
|
4820
|
+
const live = node.subpaths.filter((sp) => sp.anchors.length > 1);
|
|
4821
|
+
if (!live.length) continue;
|
|
4822
|
+
if (hasPaint(node.fill) && live.every((sp) => sp.closed)) {
|
|
4823
|
+
const faces = eraseFromFill(subpathsToBezier(live), _nullishCoalesce(node.fillRule, () => ( "nonzero")), local);
|
|
4824
|
+
if (faces) plan.push({ node, pieces: faces.map((f) => bezierToSubpaths(f)) });
|
|
4825
|
+
continue;
|
|
4826
|
+
}
|
|
4827
|
+
const results = live.map((sp) => eraseFromOutline(sp, local));
|
|
4828
|
+
if (results.every((r) => !r)) continue;
|
|
4829
|
+
const subpaths = live.flatMap((sp, i) => _nullishCoalesce(results[i], () => ( [sp])));
|
|
4830
|
+
plan.push({ node, pieces: live.length === 1 ? subpaths.map((sp) => [sp]) : [subpaths] });
|
|
4831
|
+
}
|
|
4832
|
+
if (!plan.length) return [];
|
|
4833
|
+
const changed = [];
|
|
4834
|
+
this.change("Erase", () => {
|
|
4835
|
+
for (const { node, pieces } of plan) changed.push(...this.replaceWithPieces(node, pieces));
|
|
2846
4836
|
this.anchors = [];
|
|
4837
|
+
this.dropStaleRefs();
|
|
4838
|
+
if (this.selection.length || options.ids) this.selection = [.../* @__PURE__ */ new Set([...this.selection, ...changed.filter((id) => this.getNode(id))])];
|
|
2847
4839
|
});
|
|
2848
|
-
return
|
|
4840
|
+
return changed;
|
|
2849
4841
|
}
|
|
2850
4842
|
/** Ends the pen path being drawn (a lone anchor is removed). */
|
|
2851
4843
|
finishPath() {
|
|
2852
4844
|
if (!this.pen) return;
|
|
2853
4845
|
const node = this.getNode(this.pen.nodeId);
|
|
2854
|
-
const sp = _optionalChain([node, 'optionalAccess',
|
|
4846
|
+
const sp = _optionalChain([node, 'optionalAccess', _104 => _104.subpaths, 'access', _105 => _105[this.pen.subpath]]);
|
|
2855
4847
|
const pen = this.pen;
|
|
2856
4848
|
this.pen = null;
|
|
2857
4849
|
this.pointer = null;
|
|
@@ -2895,7 +4887,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
2895
4887
|
if (trimmed.startsWith("{")) {
|
|
2896
4888
|
try {
|
|
2897
4889
|
const data = JSON.parse(trimmed);
|
|
2898
|
-
if (_optionalChain([data, 'optionalAccess',
|
|
4890
|
+
if (_optionalChain([data, 'optionalAccess', _106 => _106.type]) === VECTOR_CLIPBOARD_TYPE && Array.isArray(data.nodes)) nodes = data.nodes;
|
|
2899
4891
|
} catch (e2) {
|
|
2900
4892
|
nodes = null;
|
|
2901
4893
|
}
|
|
@@ -2907,7 +4899,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
2907
4899
|
}
|
|
2908
4900
|
}
|
|
2909
4901
|
} else nodes = this.clipboard;
|
|
2910
|
-
if (!_optionalChain([nodes, 'optionalAccess',
|
|
4902
|
+
if (!_optionalChain([nodes, 'optionalAccess', _107 => _107.length])) return [];
|
|
2911
4903
|
const offset = _nullishCoalesce(options.offset, () => ( (text && !text.trim().startsWith("{") ? { x: 0, y: 0 } : { x: 10, y: 10 })));
|
|
2912
4904
|
const copies = nodes.map((n) => transformVectorNode(this.freshIds(n), translateMatrix(offset.x, offset.y)));
|
|
2913
4905
|
this.change("Paste", () => {
|
|
@@ -2958,13 +4950,29 @@ var VectorEditorController = (_class2 = class {
|
|
|
2958
4950
|
hitHandle(screen) {
|
|
2959
4951
|
for (const ref of getVisibleHandles(this.view()).reverse()) {
|
|
2960
4952
|
const a = getAnchorWorld(this.doc, ref);
|
|
2961
|
-
const h = _optionalChain([a, 'optionalAccess',
|
|
4953
|
+
const h = _optionalChain([a, 'optionalAccess', _108 => _108[ref.handle]]);
|
|
2962
4954
|
if (!h) continue;
|
|
2963
4955
|
const s = this.documentToScreen(h);
|
|
2964
4956
|
if (Math.hypot(s.x - screen.x, s.y - screen.y) <= this.hitRadius) return ref;
|
|
2965
4957
|
}
|
|
2966
4958
|
return null;
|
|
2967
4959
|
}
|
|
4960
|
+
cornerView() {
|
|
4961
|
+
return { ...this.view(), viewport: this.viewport };
|
|
4962
|
+
}
|
|
4963
|
+
hitCornerWidget(screen) {
|
|
4964
|
+
let best = null;
|
|
4965
|
+
let bestD = Infinity;
|
|
4966
|
+
for (const w of getVectorCornerWidgets(this.cornerView())) {
|
|
4967
|
+
const s = this.documentToScreen(w);
|
|
4968
|
+
const d = Math.hypot(s.x - screen.x, s.y - screen.y);
|
|
4969
|
+
if (d <= this.hitRadius && d < bestD) {
|
|
4970
|
+
best = w;
|
|
4971
|
+
bestD = d;
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
return best;
|
|
4975
|
+
}
|
|
2968
4976
|
hitAnchor(screen, refs) {
|
|
2969
4977
|
let best = null;
|
|
2970
4978
|
let bestD = Infinity;
|
|
@@ -3023,6 +5031,8 @@ var VectorEditorController = (_class2 = class {
|
|
|
3023
5031
|
if (handle) return { nodeId: handle.nodeId, handle };
|
|
3024
5032
|
const anchor = this.hitAnchor(screen, getVisibleAnchors(this.view()));
|
|
3025
5033
|
if (anchor) return { nodeId: anchor.nodeId, anchor };
|
|
5034
|
+
const widget = this.hitCornerWidget(screen);
|
|
5035
|
+
if (widget) return { nodeId: widget.ref.nodeId, corner: widget.ref };
|
|
3026
5036
|
for (const id of getEditablePathIds(this.view())) {
|
|
3027
5037
|
const node = this.getNode(id);
|
|
3028
5038
|
const seg = nearestSegment(node, this.toLocal(id, p));
|
|
@@ -3039,10 +5049,38 @@ var VectorEditorController = (_class2 = class {
|
|
|
3039
5049
|
const anchor = this.hitAnchor(screen, refs);
|
|
3040
5050
|
return anchor ? { nodeId: anchor.nodeId, anchor } : null;
|
|
3041
5051
|
}
|
|
5052
|
+
case "scissors": {
|
|
5053
|
+
const seg = this.nearestOutline(p);
|
|
5054
|
+
return seg ? { nodeId: seg.nodeId, segment: seg } : null;
|
|
5055
|
+
}
|
|
3042
5056
|
default:
|
|
3043
5057
|
return null;
|
|
3044
5058
|
}
|
|
3045
5059
|
}
|
|
5060
|
+
/** The nearest outline point of any visible, unlocked path within the pick distance of document point `p`. */
|
|
5061
|
+
nearestOutline(p) {
|
|
5062
|
+
let best = null;
|
|
5063
|
+
const tol = this.tolerance();
|
|
5064
|
+
const visit = (list, parent) => {
|
|
5065
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
5066
|
+
const n = list[i];
|
|
5067
|
+
if (n.visible === false || n.locked) continue;
|
|
5068
|
+
const m = multiplyMatrices(nodeMatrix(n), parent);
|
|
5069
|
+
if (n.type === "group") {
|
|
5070
|
+
visit(n.children, m);
|
|
5071
|
+
continue;
|
|
5072
|
+
}
|
|
5073
|
+
const hit = nearestSegment(n, applyMatrix(invertMatrix(m), p));
|
|
5074
|
+
if (hit && hit.distance <= tol && (!best || hit.distance < best.distance - 1e-9)) {
|
|
5075
|
+
best = { nodeId: n.id, subpath: hit.subpath, segment: hit.segment, t: hit.t, point: applyMatrix(m, hit.point), distance: hit.distance };
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
};
|
|
5079
|
+
visit(this.doc.nodes, IDENTITY_MATRIX);
|
|
5080
|
+
if (!best) return null;
|
|
5081
|
+
const { distance: _d, ...seg } = best;
|
|
5082
|
+
return seg;
|
|
5083
|
+
}
|
|
3046
5084
|
sameHover(a, b) {
|
|
3047
5085
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
3048
5086
|
}
|
|
@@ -3067,6 +5105,17 @@ var VectorEditorController = (_class2 = class {
|
|
|
3067
5105
|
return this.downPen(screen, p, input);
|
|
3068
5106
|
case "pencil":
|
|
3069
5107
|
return this.downPencil(p);
|
|
5108
|
+
case "scissors": {
|
|
5109
|
+
const seg = this.nearestOutline(p);
|
|
5110
|
+
if (seg) this.cutPathAt(seg.nodeId, seg.point);
|
|
5111
|
+
return;
|
|
5112
|
+
}
|
|
5113
|
+
case "knife":
|
|
5114
|
+
case "eraser":
|
|
5115
|
+
this.drag = { kind: "stroke", tool: this.tool, points: [p], before: this.snapshot() };
|
|
5116
|
+
this.interaction = { type: "stroke", tool: this.tool, points: [p], width: this.tool === "eraser" ? this.editing.eraserSize : 0 };
|
|
5117
|
+
this.notify();
|
|
5118
|
+
return;
|
|
3070
5119
|
default:
|
|
3071
5120
|
return this.downShape(p, input);
|
|
3072
5121
|
}
|
|
@@ -3141,6 +5190,13 @@ var VectorEditorController = (_class2 = class {
|
|
|
3141
5190
|
this.notify();
|
|
3142
5191
|
return;
|
|
3143
5192
|
}
|
|
5193
|
+
const widget = this.hitCornerWidget(screen);
|
|
5194
|
+
if (widget) {
|
|
5195
|
+
const refs = getVectorCornerWidgets(this.cornerView()).map((w) => w.ref);
|
|
5196
|
+
this.drag = { kind: "corner", widget, refs, start: p, before: this.snapshot(), base: this.doc, moved: false, alt: !!input.alt, shift: !!input.shift };
|
|
5197
|
+
this.notify();
|
|
5198
|
+
return;
|
|
5199
|
+
}
|
|
3144
5200
|
for (const id of getEditablePathIds(this.view())) {
|
|
3145
5201
|
const node = this.getNode(id);
|
|
3146
5202
|
const seg = nearestSegment(node, this.toLocal(id, p));
|
|
@@ -3161,7 +5217,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
3161
5217
|
const b = { nodeId: id, subpath: seg.subpath, index: (seg.segment + 1) % sp.anchors.length };
|
|
3162
5218
|
this.anchors = input.shift ? [...this.anchors.filter((r) => !sameRef(r, a) && !sameRef(r, b)), a, b] : [a, b];
|
|
3163
5219
|
this.editingId = id;
|
|
3164
|
-
this.drag = { kind: "
|
|
5220
|
+
this.drag = { kind: "bend", ref: { nodeId: id, subpath: seg.subpath, segment: seg.segment }, t: seg.t, start: p, before: this.snapshot(), base: this.doc, moved: false };
|
|
3165
5221
|
this.notify();
|
|
3166
5222
|
return;
|
|
3167
5223
|
}
|
|
@@ -3184,7 +5240,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
3184
5240
|
}
|
|
3185
5241
|
downPen(screen, p, input) {
|
|
3186
5242
|
const before = this.snapshot();
|
|
3187
|
-
const sp = this.pen ? _optionalChain([this, 'access',
|
|
5243
|
+
const sp = this.pen ? _optionalChain([this, 'access', _109 => _109.getNode, 'call', _110 => _110(this.pen.nodeId), 'optionalAccess', _111 => _111.subpaths, 'access', _112 => _112[this.pen.subpath]]) : void 0;
|
|
3188
5244
|
if (this.pen && sp) {
|
|
3189
5245
|
const pen = this.pen;
|
|
3190
5246
|
const first = { nodeId: pen.nodeId, subpath: pen.subpath, index: 0 };
|
|
@@ -3198,10 +5254,10 @@ var VectorEditorController = (_class2 = class {
|
|
|
3198
5254
|
}
|
|
3199
5255
|
const last = getAnchorWorld(this.doc, { nodeId: pen.nodeId, subpath: pen.subpath, index: sp.anchors.length - 1 });
|
|
3200
5256
|
const leave = { at: { x: last.x, y: last.y }, constrain: input.shift ? "angle" : null };
|
|
3201
|
-
let
|
|
3202
|
-
|
|
5257
|
+
let pt3 = this.snapDrawn(p, leave, input, () => this.drawingTargets(pen.nodeId));
|
|
5258
|
+
pt3 = this.toLocal(pen.nodeId, pt3);
|
|
3203
5259
|
const node2 = this.getNode(pen.nodeId);
|
|
3204
|
-
const anchors = [...sp.anchors, { x:
|
|
5260
|
+
const anchors = [...sp.anchors, { x: pt3.x, y: pt3.y }];
|
|
3205
5261
|
this.replaceNode(pen.nodeId, { ...node2, subpaths: node2.subpaths.map((s, i) => i === pen.subpath ? { ...s, anchors } : s) });
|
|
3206
5262
|
const ref2 = { nodeId: pen.nodeId, subpath: pen.subpath, index: anchors.length - 1 };
|
|
3207
5263
|
this.anchors = [ref2];
|
|
@@ -3229,8 +5285,8 @@ var VectorEditorController = (_class2 = class {
|
|
|
3229
5285
|
this.notify(this.doc !== before.doc);
|
|
3230
5286
|
return;
|
|
3231
5287
|
}
|
|
3232
|
-
const
|
|
3233
|
-
const node = { id: this.newId(), type: "path", name: "Path", ...this.styleFor(true), subpaths: [{ anchors: [{ x:
|
|
5288
|
+
const pt2 = this.snapDrawn(p, null, input, () => this.drawingTargets(null));
|
|
5289
|
+
const node = { id: this.newId(), type: "path", name: "Path", ...this.styleFor(true), subpaths: [{ anchors: [{ x: pt2.x, y: pt2.y }], closed: false }] };
|
|
3234
5290
|
this.setDoc([...this.doc.nodes, node]);
|
|
3235
5291
|
this.pen = { nodeId: node.id, subpath: 0 };
|
|
3236
5292
|
this.selection = [node.id];
|
|
@@ -3253,7 +5309,9 @@ var VectorEditorController = (_class2 = class {
|
|
|
3253
5309
|
subpaths: [{ anchors: [{ x: p.x, y: p.y }], closed: false }]
|
|
3254
5310
|
};
|
|
3255
5311
|
this.setDoc([...this.doc.nodes, node]);
|
|
3256
|
-
|
|
5312
|
+
const lazy = new LazyMouse(this.editing.pencilSmoothing * VECTOR_PENCIL_STRING_PX / this.viewport.zoom);
|
|
5313
|
+
lazy.push([p.x, p.y]);
|
|
5314
|
+
this.drag = { kind: "pencil", nodeId: node.id, points: [[p.x, p.y]], before, lazy };
|
|
3257
5315
|
this.interaction = { type: "draw", tool: "pencil", nodeId: node.id, bounds: { x: p.x, y: p.y, width: 0, height: 0 } };
|
|
3258
5316
|
this.notify(true);
|
|
3259
5317
|
}
|
|
@@ -3365,7 +5423,10 @@ var VectorEditorController = (_class2 = class {
|
|
|
3365
5423
|
dx = q.x - lead.x;
|
|
3366
5424
|
dy = q.y - lead.y;
|
|
3367
5425
|
} else if (input.shift) Math.abs(dx) > Math.abs(dy) ? dy = 0 : dx = 0;
|
|
3368
|
-
|
|
5426
|
+
const single = this.anchors.length === 1 ? this.anchors[0] : null;
|
|
5427
|
+
const from = single ? getAnchorWorld(drag.base, single) : void 0;
|
|
5428
|
+
if (single && from && this.editing.curvatureMoves !== !!input.alt) this.doc = this.curvatureMoved(drag.base, single, { x: from.x + dx, y: from.y + dy });
|
|
5429
|
+
else this.doc = this.movedAnchors(drag.base, this.anchors, dx, dy);
|
|
3369
5430
|
this.interaction = { type: "anchors", dx, dy };
|
|
3370
5431
|
this.notify(true);
|
|
3371
5432
|
return;
|
|
@@ -3373,13 +5434,41 @@ var VectorEditorController = (_class2 = class {
|
|
|
3373
5434
|
case "handle": {
|
|
3374
5435
|
const anchor = getAnchorWorld(drag.base, drag.ref);
|
|
3375
5436
|
const leave = anchor ? { at: { x: anchor.x, y: anchor.y }, constrain: input.shift ? "angle" : null } : null;
|
|
3376
|
-
const
|
|
3377
|
-
const local = this.toLocal(drag.ref.nodeId,
|
|
5437
|
+
const pt2 = this.snapDrawn(p, leave, input, () => drag.targets ??= VectorSnapTargets.forHandle(drag.base, drag.ref.nodeId));
|
|
5438
|
+
const local = this.toLocal(drag.ref.nodeId, pt2, drag.base);
|
|
3378
5439
|
this.doc = this.updateAnchors(drag.base, [drag.ref], (a) => moveAnchorHandle(a, drag.ref.handle, local, input.alt));
|
|
3379
5440
|
this.interaction = { type: "handle", ref: drag.ref };
|
|
3380
5441
|
this.notify(true);
|
|
3381
5442
|
return;
|
|
3382
5443
|
}
|
|
5444
|
+
case "corner": {
|
|
5445
|
+
if (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 3) return;
|
|
5446
|
+
drag.moved = true;
|
|
5447
|
+
const c = drag.widget.corner;
|
|
5448
|
+
const inv = invertMatrix(this.worldOf(drag.widget.ref.nodeId, drag.base));
|
|
5449
|
+
const d = applyMatrixLinear(inv, { x: p.x - drag.start.x, y: p.y - drag.start.y });
|
|
5450
|
+
const radius = Math.max(0, Math.min(c.maxRadius, drag.widget.drawnRadius + cornerRadiusChange(c, d)));
|
|
5451
|
+
this.doc = this.updateAnchors(drag.base, drag.refs, (a) => setAnchorCorner(a, radius));
|
|
5452
|
+
this.interaction = { type: "corner", ref: drag.widget.ref, radius };
|
|
5453
|
+
this.notify(true);
|
|
5454
|
+
return;
|
|
5455
|
+
}
|
|
5456
|
+
case "bend": {
|
|
5457
|
+
if (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 2) return;
|
|
5458
|
+
drag.moved = true;
|
|
5459
|
+
this.doc = this.bent(drag.base, drag.ref, drag.t, p);
|
|
5460
|
+
this.interaction = { type: "bend", ref: drag.ref };
|
|
5461
|
+
this.notify(true);
|
|
5462
|
+
return;
|
|
5463
|
+
}
|
|
5464
|
+
case "stroke": {
|
|
5465
|
+
const last = drag.points[drag.points.length - 1];
|
|
5466
|
+
if (Math.hypot(p.x - last.x, p.y - last.y) * this.viewport.zoom < 1.5) return;
|
|
5467
|
+
drag.points.push(p);
|
|
5468
|
+
this.interaction = { type: "stroke", tool: drag.tool, points: drag.points.slice(), width: drag.tool === "eraser" ? this.editing.eraserSize : 0 };
|
|
5469
|
+
this.notify();
|
|
5470
|
+
return;
|
|
5471
|
+
}
|
|
3383
5472
|
case "pull": {
|
|
3384
5473
|
const anchor = getAnchorWorld(drag.base, drag.ref);
|
|
3385
5474
|
if (!anchor) return;
|
|
@@ -3389,10 +5478,10 @@ var VectorEditorController = (_class2 = class {
|
|
|
3389
5478
|
return;
|
|
3390
5479
|
}
|
|
3391
5480
|
this.guides = null;
|
|
3392
|
-
let
|
|
3393
|
-
if (input.shift)
|
|
5481
|
+
let pt2 = p;
|
|
5482
|
+
if (input.shift) pt2 = constrain45(anchor, pt2);
|
|
3394
5483
|
const side = drag.closing ? "in" : "out";
|
|
3395
|
-
const pos = this.toLocal(drag.ref.nodeId, drag.closing ? { x: 2 * anchor.x -
|
|
5484
|
+
const pos = this.toLocal(drag.ref.nodeId, drag.closing ? { x: 2 * anchor.x - pt2.x, y: 2 * anchor.y - pt2.y } : pt2, drag.base);
|
|
3396
5485
|
this.doc = this.updateAnchors(drag.base, [drag.ref], (a) => {
|
|
3397
5486
|
if (drag.outOnly || drag.altBreaks && input.alt) return moveAnchorHandle(a, side, pos, true);
|
|
3398
5487
|
return moveAnchorHandle({ ...a, type: "symmetric" }, side, pos);
|
|
@@ -3402,7 +5491,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
3402
5491
|
}
|
|
3403
5492
|
case "shape": {
|
|
3404
5493
|
const node = this.getNode(drag.nodeId);
|
|
3405
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
5494
|
+
if (_optionalChain([node, 'optionalAccess', _113 => _113.type]) !== "path") return;
|
|
3406
5495
|
const leave = { at: drag.start, constrain: input.shift ? drag.tool === "line" ? "angle" : "square" : null, curves: false };
|
|
3407
5496
|
let end = this.snapDrawn(p, leave, input, () => drag.targets ??= this.drawingTargets(null));
|
|
3408
5497
|
if (!this.snappedToObject && drag.tool !== "line" && !input.shift && !input.mod && this.snap.objects && drag.targets) end = this.matchShapeSize(drag.targets, drag.start, end, input.alt);
|
|
@@ -3433,10 +5522,22 @@ var VectorEditorController = (_class2 = class {
|
|
|
3433
5522
|
}
|
|
3434
5523
|
case "pencil": {
|
|
3435
5524
|
const last = drag.points[drag.points.length - 1];
|
|
3436
|
-
|
|
3437
|
-
drag.
|
|
5525
|
+
let moved = false;
|
|
5526
|
+
for (const q of drag.lazy.push([p.x, p.y])) {
|
|
5527
|
+
if (Math.hypot(q[0] - last[0], q[1] - last[1]) * this.viewport.zoom < 1.5) continue;
|
|
5528
|
+
drag.points.push(q);
|
|
5529
|
+
moved = true;
|
|
5530
|
+
}
|
|
5531
|
+
const string = drag.lazy.string();
|
|
3438
5532
|
const node = this.getNode(drag.nodeId);
|
|
3439
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
5533
|
+
if (_optionalChain([node, 'optionalAccess', _114 => _114.type]) !== "path") return;
|
|
5534
|
+
if (!moved) {
|
|
5535
|
+
if (string && _optionalChain([this, 'access', _115 => _115.interaction, 'optionalAccess', _116 => _116.type]) === "draw") {
|
|
5536
|
+
this.interaction = { ...this.interaction, string: [{ x: string[0][0], y: string[0][1] }, { x: string[1][0], y: string[1][1] }] };
|
|
5537
|
+
this.notify();
|
|
5538
|
+
}
|
|
5539
|
+
return;
|
|
5540
|
+
}
|
|
3440
5541
|
this.replaceNode(drag.nodeId, { ...node, subpaths: [{ anchors: drag.points.map(([x, y]) => ({ x, y })), closed: false }] });
|
|
3441
5542
|
const xs = drag.points.map((q) => q[0]);
|
|
3442
5543
|
const ys = drag.points.map((q) => q[1]);
|
|
@@ -3444,7 +5545,8 @@ var VectorEditorController = (_class2 = class {
|
|
|
3444
5545
|
type: "draw",
|
|
3445
5546
|
tool: "pencil",
|
|
3446
5547
|
nodeId: drag.nodeId,
|
|
3447
|
-
bounds: { x: Math.min(...xs), y: Math.min(...ys), width: Math.max(...xs) - Math.min(...xs), height: Math.max(...ys) - Math.min(...ys) }
|
|
5548
|
+
bounds: { x: Math.min(...xs), y: Math.min(...ys), width: Math.max(...xs) - Math.min(...xs), height: Math.max(...ys) - Math.min(...ys) },
|
|
5549
|
+
...string ? { string: [{ x: string[0][0], y: string[0][1] }, { x: string[1][0], y: string[1][1] }] } : {}
|
|
3448
5550
|
};
|
|
3449
5551
|
this.notify(true);
|
|
3450
5552
|
return;
|
|
@@ -3466,8 +5568,8 @@ var VectorEditorController = (_class2 = class {
|
|
|
3466
5568
|
}
|
|
3467
5569
|
this.setGuides(res);
|
|
3468
5570
|
return {
|
|
3469
|
-
dx: _optionalChain([res, 'optionalAccess',
|
|
3470
|
-
dy: _optionalChain([res, 'optionalAccess',
|
|
5571
|
+
dx: _optionalChain([res, 'optionalAccess', _117 => _117.x]) ? dx + res.dx : axes.x ? this.gridValue(origin.x + dx) - origin.x : dx,
|
|
5572
|
+
dy: _optionalChain([res, 'optionalAccess', _118 => _118.y]) ? dy + res.dy : axes.y ? this.gridValue(origin.y + dy) - origin.y : dy
|
|
3471
5573
|
};
|
|
3472
5574
|
}
|
|
3473
5575
|
/** The anchor a point drag snaps by, where it started: the grabbed one, else the moving one nearest the press. */
|
|
@@ -3492,12 +5594,12 @@ var VectorEditorController = (_class2 = class {
|
|
|
3492
5594
|
const guides = { lines: [], labels: [] };
|
|
3493
5595
|
for (const axis of ["x", "y"]) {
|
|
3494
5596
|
const d = end[axis] - start[axis];
|
|
3495
|
-
const
|
|
3496
|
-
if (
|
|
3497
|
-
out[axis] = start[axis] + Math.sign(d) *
|
|
5597
|
+
const len2 = Math.abs(d) > 1e-9 ? targets.matchSize(Math.abs(d) * k, tol) : null;
|
|
5598
|
+
if (len2 === null || len2 <= 0) continue;
|
|
5599
|
+
out[axis] = start[axis] + Math.sign(d) * len2 / k;
|
|
3498
5600
|
const from = { ...out, [axis]: fromCentre ? 2 * start[axis] - out[axis] : start[axis] };
|
|
3499
5601
|
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(
|
|
5602
|
+
guides.labels.push({ kind: "size", x: (from.x + out.x) / 2, y: (from.y + out.y) / 2, text: `${axis === "x" ? "W" : "H"} ${Math.round(len2 * 100) / 100}` });
|
|
3501
5603
|
}
|
|
3502
5604
|
this.setGuides(guides);
|
|
3503
5605
|
return out;
|
|
@@ -3536,6 +5638,32 @@ var VectorEditorController = (_class2 = class {
|
|
|
3536
5638
|
return this.commit(drag.before.doc !== drag.base ? "Add Point" : "Move Points", drag.before);
|
|
3537
5639
|
case "handle":
|
|
3538
5640
|
return this.commit("Move Handle", drag.before);
|
|
5641
|
+
case "bend":
|
|
5642
|
+
if (!drag.moved) {
|
|
5643
|
+
this.notify();
|
|
5644
|
+
return;
|
|
5645
|
+
}
|
|
5646
|
+
return this.commit("Bend Segment", drag.before);
|
|
5647
|
+
case "corner": {
|
|
5648
|
+
if (drag.moved) return this.commit("Corner Radius", drag.before);
|
|
5649
|
+
if (drag.alt) {
|
|
5650
|
+
const kind = nextVectorCornerKind(drag.widget.kind);
|
|
5651
|
+
this.doc = this.updateAnchors(drag.base, drag.refs, (a) => setAnchorCorner(a, _nullishCoalesce(_optionalChain([a, 'access', _119 => _119.corner, 'optionalAccess', _120 => _120.radius]), () => ( 0)), kind));
|
|
5652
|
+
return this.commit("Corner Kind", drag.before);
|
|
5653
|
+
}
|
|
5654
|
+
const ref = drag.widget.ref;
|
|
5655
|
+
const has = this.anchors.some((a) => sameRef(a, ref));
|
|
5656
|
+
this.editingId = ref.nodeId;
|
|
5657
|
+
this.anchors = drag.shift ? has ? this.anchors.filter((a) => !sameRef(a, ref)) : [...this.anchors, ref] : [ref];
|
|
5658
|
+
this.notify();
|
|
5659
|
+
return;
|
|
5660
|
+
}
|
|
5661
|
+
case "stroke": {
|
|
5662
|
+
this.notify();
|
|
5663
|
+
if (drag.tool === "knife") this.knife(drag.points);
|
|
5664
|
+
else this.erase(drag.points);
|
|
5665
|
+
return;
|
|
5666
|
+
}
|
|
3539
5667
|
case "pull":
|
|
3540
5668
|
if (drag.closing) this.pen = null;
|
|
3541
5669
|
return this.commit(drag.label, drag.before);
|
|
@@ -3555,12 +5683,19 @@ var VectorEditorController = (_class2 = class {
|
|
|
3555
5683
|
}
|
|
3556
5684
|
case "pencil": {
|
|
3557
5685
|
const node = this.getNode(drag.nodeId);
|
|
3558
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
5686
|
+
if (_optionalChain([node, 'optionalAccess', _121 => _121.type]) !== "path" || drag.points.length < 2) {
|
|
3559
5687
|
this.doc = drag.before.doc;
|
|
3560
5688
|
this.notify(true);
|
|
3561
5689
|
return;
|
|
3562
5690
|
}
|
|
3563
|
-
const
|
|
5691
|
+
for (const q of drag.lazy.finish()) drag.points.push(q);
|
|
5692
|
+
const shape = this.editing.recognizeShapes ? recognizeInkShape(drag.points, { minSize: 6 / this.viewport.zoom }) : null;
|
|
5693
|
+
if (shape) {
|
|
5694
|
+
this.replaceNode(drag.nodeId, { ...node, name: inkShapeLabel(shape), subpaths: [inkShapeSubpath(shape)] });
|
|
5695
|
+
this.selection = [drag.nodeId];
|
|
5696
|
+
return this.commit(`Draw ${inkShapeLabel(shape)}`, drag.before);
|
|
5697
|
+
}
|
|
5698
|
+
const fitted = _chunkBGNPT5PEcjs.fitCubics.call(void 0, drag.points, 1.5 / this.viewport.zoom, { cornerAngleDeg: 60 });
|
|
3564
5699
|
const anchors = [];
|
|
3565
5700
|
for (const c of fitted) {
|
|
3566
5701
|
if (!anchors.length) anchors.push({ x: c.p0[0], y: c.p0[1] });
|
|
@@ -3622,7 +5757,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
3622
5757
|
this.convertAnchors(a.in || a.out ? "corner" : "smooth", [anchor]);
|
|
3623
5758
|
return;
|
|
3624
5759
|
}
|
|
3625
|
-
const id = _nullishCoalesce(_optionalChain([this, 'access',
|
|
5760
|
+
const id = _nullishCoalesce(_optionalChain([this, 'access', _122 => _122.hover, 'optionalAccess', _123 => _123.segment, 'optionalAccess', _124 => _124.nodeId]), () => ( this.editingId));
|
|
3626
5761
|
if (id) {
|
|
3627
5762
|
const node = this.getNode(id);
|
|
3628
5763
|
const seg = node ? nearestSegment(node, this.toLocal(id, p)) : null;
|
|
@@ -3666,6 +5801,8 @@ var VectorEditorController = (_class2 = class {
|
|
|
3666
5801
|
case "g":
|
|
3667
5802
|
input.shift ? this.ungroup() : this.group();
|
|
3668
5803
|
return true;
|
|
5804
|
+
case "j":
|
|
5805
|
+
return input.alt ? this.averageAnchors("both").ok : this.joinPaths().ok;
|
|
3669
5806
|
case "]":
|
|
3670
5807
|
this.arrange(input.alt ? "front" : "forward");
|
|
3671
5808
|
return true;
|
|
@@ -3700,7 +5837,7 @@ var VectorEditorController = (_class2 = class {
|
|
|
3700
5837
|
this.finishPath();
|
|
3701
5838
|
return true;
|
|
3702
5839
|
}
|
|
3703
|
-
if (this.tool === "select" && this.selection.length === 1 && _optionalChain([this, 'access',
|
|
5840
|
+
if (this.tool === "select" && this.selection.length === 1 && _optionalChain([this, 'access', _125 => _125.getNode, 'call', _126 => _126(this.selection[0]), 'optionalAccess', _127 => _127.type]) === "path") {
|
|
3704
5841
|
this.tool = "direct";
|
|
3705
5842
|
this.editingId = this.selection[0];
|
|
3706
5843
|
this.notify();
|
|
@@ -3745,13 +5882,29 @@ var VectorEditorController = (_class2 = class {
|
|
|
3745
5882
|
this.notify();
|
|
3746
5883
|
}
|
|
3747
5884
|
}
|
|
3748
|
-
},
|
|
5885
|
+
}, _class3);
|
|
5886
|
+
function inkShapeLabel(shape) {
|
|
5887
|
+
const name = vectorInkShapeName(shape);
|
|
5888
|
+
return name[0].toUpperCase() + name.slice(1);
|
|
5889
|
+
}
|
|
5890
|
+
function inkShapeSubpath(shape) {
|
|
5891
|
+
switch (shape.kind) {
|
|
5892
|
+
case "line":
|
|
5893
|
+
return lineSubpath({ x: shape.a[0], y: shape.a[1] }, { x: shape.b[0], y: shape.b[1] });
|
|
5894
|
+
case "rect":
|
|
5895
|
+
return rectSubpath(shape);
|
|
5896
|
+
case "ellipse":
|
|
5897
|
+
return ellipseSubpath(shape);
|
|
5898
|
+
case "polygon":
|
|
5899
|
+
return { anchors: shape.points.map(([x, y]) => ({ x, y })), closed: true };
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
3749
5902
|
function constrain45(origin, p) {
|
|
3750
5903
|
const dx = p.x - origin.x;
|
|
3751
5904
|
const dy = p.y - origin.y;
|
|
3752
|
-
const
|
|
5905
|
+
const len2 = Math.hypot(dx, dy);
|
|
3753
5906
|
const a = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);
|
|
3754
|
-
return { x: origin.x + Math.cos(a) *
|
|
5907
|
+
return { x: origin.x + Math.cos(a) * len2, y: origin.y + Math.sin(a) * len2 };
|
|
3755
5908
|
}
|
|
3756
5909
|
function scaleFor(handle, b, p, mods = {}) {
|
|
3757
5910
|
const west = handle.includes("w");
|
|
@@ -3788,17 +5941,19 @@ function renderNode(node, defs, idPrefix) {
|
|
|
3788
5941
|
const stroke = vectorPaintToSvg(node.stroke, `${idPrefix}stroke-${node.id}`);
|
|
3789
5942
|
if (fill.gradient) defs.push(fill.gradient);
|
|
3790
5943
|
if (stroke.gradient) defs.push(stroke.gradient);
|
|
5944
|
+
const strokeParts = stroke.value !== "none" ? vectorStrokeSvgParts(node, `${idPrefix}clip-${node.id}`) : void 0;
|
|
3791
5945
|
return {
|
|
3792
5946
|
kind: "path",
|
|
3793
5947
|
id: node.id,
|
|
3794
5948
|
...node.transform ? { transform: fmtMatrix(node.transform) } : {},
|
|
5949
|
+
...strokeParts ? { strokeParts } : {},
|
|
3795
5950
|
d: vectorPathData(node),
|
|
3796
5951
|
fill: fill.value,
|
|
3797
5952
|
stroke: stroke.value,
|
|
3798
5953
|
strokeWidth: _nullishCoalesce(node.strokeWidth, () => ( 1)),
|
|
3799
5954
|
...node.strokeLinecap ? { strokeLinecap: node.strokeLinecap } : {},
|
|
3800
5955
|
...node.strokeLinejoin ? { strokeLinejoin: node.strokeLinejoin } : {},
|
|
3801
|
-
..._optionalChain([node, 'access',
|
|
5956
|
+
..._optionalChain([node, 'access', _128 => _128.strokeDasharray, 'optionalAccess', _129 => _129.length]) ? { strokeDasharray: node.strokeDasharray.join(" ") } : {},
|
|
3802
5957
|
fillRule: _nullishCoalesce(node.fillRule, () => ( "nonzero")),
|
|
3803
5958
|
opacity: _nullishCoalesce(node.opacity, () => ( 1))
|
|
3804
5959
|
};
|
|
@@ -3807,9 +5962,9 @@ function getVectorSceneRenderData(state, options = {}) {
|
|
|
3807
5962
|
const defs = [];
|
|
3808
5963
|
const prefix = _nullishCoalesce(options.idPrefix, () => ( "ve-"));
|
|
3809
5964
|
const nodes = state.document.nodes.map((n) => renderNode(n, defs, prefix)).filter((n) => !!n);
|
|
3810
|
-
const
|
|
5965
|
+
const v2 = state.viewport;
|
|
3811
5966
|
return {
|
|
3812
|
-
transform: `translate(${fmt(
|
|
5967
|
+
transform: `translate(${fmt(v2.x)} ${fmt(v2.y)}) scale(${v2.zoom})`,
|
|
3813
5968
|
width: state.document.width,
|
|
3814
5969
|
height: state.document.height,
|
|
3815
5970
|
...state.document.background ? { background: state.document.background } : {},
|
|
@@ -3819,10 +5974,10 @@ function getVectorSceneRenderData(state, options = {}) {
|
|
|
3819
5974
|
}
|
|
3820
5975
|
var toScreen = (state, p) => ({ x: p.x * state.viewport.zoom + state.viewport.x, y: p.y * state.viewport.zoom + state.viewport.y });
|
|
3821
5976
|
function screenOutline(state, node) {
|
|
3822
|
-
const
|
|
3823
|
-
const view = [
|
|
5977
|
+
const v2 = state.viewport;
|
|
5978
|
+
const view = [v2.zoom, 0, 0, v2.zoom, v2.x, v2.y];
|
|
3824
5979
|
const parent = _nullishCoalesce(vectorParentMatrix(state.document.nodes, node.id), () => ( IDENTITY_MATRIX));
|
|
3825
|
-
return collectWithMatrix(node, parent).map(({ path, parent: m }) =>
|
|
5980
|
+
return collectWithMatrix(node, parent).map(({ path, parent: m }) => _chunkBGNPT5PEcjs.bezierPathToSvg.call(void 0, _chunkBGNPT5PEcjs.transformBezierPath.call(void 0, vectorPathWorldBezier(path, m), view), { precision: 2 })).join(" ");
|
|
3826
5981
|
}
|
|
3827
5982
|
function collectWithMatrix(node, parent) {
|
|
3828
5983
|
if (node.type === "path") return [{ path: node, parent }];
|
|
@@ -3847,9 +6002,9 @@ function getVectorOverlayRenderData(state) {
|
|
|
3847
6002
|
};
|
|
3848
6003
|
for (const id of state.selection) outline(id, true);
|
|
3849
6004
|
if (state.editingId) outline(state.editingId, true);
|
|
3850
|
-
if (_optionalChain([state, 'access',
|
|
6005
|
+
if (_optionalChain([state, 'access', _130 => _130.hover, 'optionalAccess', _131 => _131.nodeId]) && !state.interaction) outline(state.hover.nodeId, false);
|
|
3851
6006
|
let selectionBox = null;
|
|
3852
|
-
if (state.tool === "select" && state.selectionFrame && _optionalChain([state, 'access',
|
|
6007
|
+
if (state.tool === "select" && state.selectionFrame && _optionalChain([state, 'access', _132 => _132.interaction, 'optionalAccess', _133 => _133.type]) !== "marquee") {
|
|
3853
6008
|
const g = getVectorSelectionBoxGeometry(state.selectionFrame, state.viewport);
|
|
3854
6009
|
selectionBox = { ...g.box, angle: g.angle, transform: g.transform, corners: g.corners, handles: g.handles };
|
|
3855
6010
|
}
|
|
@@ -3859,9 +6014,9 @@ function getVectorOverlayRenderData(state) {
|
|
|
3859
6014
|
const a = getAnchorWorld(doc, ref);
|
|
3860
6015
|
const s = toScreen(state, a);
|
|
3861
6016
|
const closes = !!state.pen && state.pen.nodeId === ref.nodeId && state.pen.subpath === ref.subpath && ref.index === 0;
|
|
3862
|
-
return { ref, x: s.x, y: s.y, type: _nullishCoalesce(a.type, () => ( inferAnchorType(a))), selected: selected(ref), hovered: sameAnchor(_optionalChain([state, 'access',
|
|
6017
|
+
return { ref, x: s.x, y: s.y, type: _nullishCoalesce(a.type, () => ( inferAnchorType(a))), selected: selected(ref), hovered: sameAnchor(_optionalChain([state, 'access', _134 => _134.hover, 'optionalAccess', _135 => _135.anchor]), ref), closes };
|
|
3863
6018
|
});
|
|
3864
|
-
if (state.tool === "pen" && !state.pen && _optionalChain([state, 'access',
|
|
6019
|
+
if (state.tool === "pen" && !state.pen && _optionalChain([state, 'access', _136 => _136.hover, 'optionalAccess', _137 => _137.anchor])) {
|
|
3865
6020
|
const a = getAnchorWorld(doc, state.hover.anchor);
|
|
3866
6021
|
if (a) {
|
|
3867
6022
|
const s = toScreen(state, a);
|
|
@@ -3870,15 +6025,15 @@ function getVectorOverlayRenderData(state) {
|
|
|
3870
6025
|
}
|
|
3871
6026
|
const handles = getVisibleHandles(state).flatMap((ref) => {
|
|
3872
6027
|
const a = getAnchorWorld(doc, ref);
|
|
3873
|
-
const h = _optionalChain([a, 'optionalAccess',
|
|
6028
|
+
const h = _optionalChain([a, 'optionalAccess', _138 => _138[ref.handle]]);
|
|
3874
6029
|
if (!a || !h) return [];
|
|
3875
6030
|
const s = toScreen(state, h);
|
|
3876
6031
|
const sa = toScreen(state, a);
|
|
3877
|
-
const hovered = !!_optionalChain([state, 'access',
|
|
6032
|
+
const hovered = !!_optionalChain([state, 'access', _139 => _139.hover, 'optionalAccess', _140 => _140.handle]) && sameAnchor(state.hover.handle, ref) && state.hover.handle.handle === ref.handle;
|
|
3878
6033
|
return [{ ref, x: s.x, y: s.y, anchorX: sa.x, anchorY: sa.y, hovered }];
|
|
3879
6034
|
});
|
|
3880
6035
|
let marquee = null;
|
|
3881
|
-
if (_optionalChain([state, 'access',
|
|
6036
|
+
if (_optionalChain([state, 'access', _141 => _141.interaction, 'optionalAccess', _142 => _142.type]) === "marquee") {
|
|
3882
6037
|
const b = state.interaction.bounds;
|
|
3883
6038
|
const tl = toScreen(state, b);
|
|
3884
6039
|
marquee = { x: tl.x, y: tl.y, width: b.width * z, height: b.height * z };
|
|
@@ -3886,7 +6041,7 @@ function getVectorOverlayRenderData(state) {
|
|
|
3886
6041
|
let penPreview = null;
|
|
3887
6042
|
if (state.tool === "pen" && state.pen && state.pointer && !state.interaction) {
|
|
3888
6043
|
const node = findVectorNode(doc.nodes, state.pen.nodeId);
|
|
3889
|
-
const sp = _optionalChain([node, 'optionalAccess',
|
|
6044
|
+
const sp = _optionalChain([node, 'optionalAccess', _143 => _143.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
|
|
3890
6045
|
const last = sp ? getAnchorWorld(doc, { nodeId: state.pen.nodeId, subpath: state.pen.subpath, index: sp.anchors.length - 1 }) : void 0;
|
|
3891
6046
|
if (last) {
|
|
3892
6047
|
const a = toScreen(state, last);
|
|
@@ -3899,9 +6054,9 @@ function getVectorOverlayRenderData(state) {
|
|
|
3899
6054
|
const artboard = { x: origin.x, y: origin.y, width: doc.width * z, height: doc.height * z };
|
|
3900
6055
|
let status = null;
|
|
3901
6056
|
const it = state.interaction;
|
|
3902
|
-
if (_optionalChain([it, 'optionalAccess',
|
|
3903
|
-
else if (_optionalChain([it, 'optionalAccess',
|
|
3904
|
-
else if (_optionalChain([it, 'optionalAccess',
|
|
6057
|
+
if (_optionalChain([it, 'optionalAccess', _144 => _144.type]) === "rotate") status = `${Math.round(it.angle)}\xB0`;
|
|
6058
|
+
else if (_optionalChain([it, 'optionalAccess', _145 => _145.type]) === "move" || _optionalChain([it, 'optionalAccess', _146 => _146.type]) === "anchors") status = `\u0394 ${fmt(it.dx)}, ${fmt(it.dy)}`;
|
|
6059
|
+
else if (_optionalChain([it, 'optionalAccess', _147 => _147.type]) === "scale" || _optionalChain([it, 'optionalAccess', _148 => _148.type]) === "draw") {
|
|
3905
6060
|
const b = it.type === "draw" ? findVectorNode(doc.nodes, it.nodeId) ? vectorNodeBounds(findVectorNode(doc.nodes, it.nodeId)) : null : state.selectionFrame;
|
|
3906
6061
|
if (b) status = `W ${fmt(b.width)} \xD7 H ${fmt(b.height)}`;
|
|
3907
6062
|
}
|
|
@@ -3917,7 +6072,26 @@ function getVectorOverlayRenderData(state) {
|
|
|
3917
6072
|
guides.labels.push({ kind: l.kind, x: fmt(p.x), y: fmt(p.y), text: l.text });
|
|
3918
6073
|
}
|
|
3919
6074
|
}
|
|
3920
|
-
|
|
6075
|
+
let toolStroke = null;
|
|
6076
|
+
if (_optionalChain([it, 'optionalAccess', _149 => _149.type]) === "stroke") {
|
|
6077
|
+
const pts = it.points.map((q) => toScreen(state, q));
|
|
6078
|
+
const d = pts.map((q, i) => `${i ? "L" : "M"}${fmt(q.x)} ${fmt(q.y)}`).join("") + (pts.length === 1 ? `L${fmt(pts[0].x)} ${fmt(pts[0].y)}` : "");
|
|
6079
|
+
toolStroke = { tool: it.tool, d, width: fmt(it.width * z) };
|
|
6080
|
+
}
|
|
6081
|
+
let pencilString = null;
|
|
6082
|
+
if (_optionalChain([it, 'optionalAccess', _150 => _150.type]) === "draw" && it.string) {
|
|
6083
|
+
const a = toScreen(state, it.string[0]);
|
|
6084
|
+
const b = toScreen(state, it.string[1]);
|
|
6085
|
+
pencilString = { x1: fmt(a.x), y1: fmt(a.y), x2: fmt(b.x), y2: fmt(b.y) };
|
|
6086
|
+
}
|
|
6087
|
+
const dragged = _optionalChain([it, 'optionalAccess', _151 => _151.type]) === "corner" ? it.ref : null;
|
|
6088
|
+
const cornerWidgets = getVectorCornerWidgets(state).map((w) => {
|
|
6089
|
+
const s = toScreen(state, w);
|
|
6090
|
+
return { ref: w.ref, x: fmt(s.x), y: fmt(s.y), radius: w.radius, kind: w.kind, hovered: sameAnchor(_optionalChain([state, 'access', _152 => _152.hover, 'optionalAccess', _153 => _153.corner]), w.ref), active: !!dragged };
|
|
6091
|
+
});
|
|
6092
|
+
if (_optionalChain([it, 'optionalAccess', _154 => _154.type]) === "corner") status = `R ${fmt(it.radius)}`;
|
|
6093
|
+
const cutPoint = state.tool === "scissors" && _optionalChain([state, 'access', _155 => _155.hover, 'optionalAccess', _156 => _156.segment]) && !it ? (({ x, y }) => ({ x: fmt(x), y: fmt(y) }))(toScreen(state, state.hover.segment.point)) : null;
|
|
6094
|
+
return { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint, cornerWidgets };
|
|
3921
6095
|
}
|
|
3922
6096
|
var VECTOR_OVERLAY_THEME = {
|
|
3923
6097
|
accent: "#0ea5e9",
|
|
@@ -3925,9 +6099,13 @@ var VECTOR_OVERLAY_THEME = {
|
|
|
3925
6099
|
guide: "#ff3dfc",
|
|
3926
6100
|
handleFill: "#ffffff",
|
|
3927
6101
|
marqueeFill: "rgba(14, 165, 233, 0.08)",
|
|
6102
|
+
/** The eraser's swath while it is dragged. */
|
|
6103
|
+
eraserFill: "rgba(239, 68, 68, 0.25)",
|
|
3928
6104
|
transformHandleSize: 8,
|
|
3929
6105
|
anchorSize: 7,
|
|
3930
|
-
handleRadius: 3.5
|
|
6106
|
+
handleRadius: 3.5,
|
|
6107
|
+
/** Radius of a live corner widget (a ring with a dot). */
|
|
6108
|
+
cornerWidgetRadius: 4
|
|
3931
6109
|
};
|
|
3932
6110
|
function toVectorModifiers(event) {
|
|
3933
6111
|
return { shift: !!event.shiftKey, alt: !!event.altKey, mod: !!(event.metaKey || event.ctrlKey) };
|
|
@@ -3943,7 +6121,7 @@ function isVectorTextTarget(target) {
|
|
|
3943
6121
|
if (!el || typeof el !== "object") return false;
|
|
3944
6122
|
const tag = (_nullishCoalesce(el.tagName, () => ( ""))).toUpperCase();
|
|
3945
6123
|
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
3946
|
-
return !!el.isContentEditable || _optionalChain([el, 'access',
|
|
6124
|
+
return !!el.isContentEditable || _optionalChain([el, 'access', _157 => _157.getAttribute, 'optionalCall', _158 => _158("contenteditable")]) === "true";
|
|
3947
6125
|
}
|
|
3948
6126
|
function getVectorSelectionStyle(state) {
|
|
3949
6127
|
const paths = [];
|
|
@@ -3951,18 +6129,68 @@ function getVectorSelectionStyle(state) {
|
|
|
3951
6129
|
const node = findVectorNode(state.document.nodes, id);
|
|
3952
6130
|
if (node) paths.push(...collect(node));
|
|
3953
6131
|
}
|
|
6132
|
+
const anchorMode = state.tool === "direct" && !!_optionalChain([state, 'access', _159 => _159.selectedAnchors, 'optionalAccess', _160 => _160.length]);
|
|
6133
|
+
if (anchorMode) {
|
|
6134
|
+
for (const r of state.selectedAnchors) {
|
|
6135
|
+
if (paths.some((p) => p.id === r.nodeId)) continue;
|
|
6136
|
+
const node = findVectorNode(state.document.nodes, r.nodeId);
|
|
6137
|
+
if (_optionalChain([node, 'optionalAccess', _161 => _161.type]) === "path") paths.push(node);
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
3954
6140
|
if (!paths.length) return null;
|
|
3955
6141
|
const first = paths[0];
|
|
3956
6142
|
const same = (get) => paths.every((p) => JSON.stringify(_nullishCoalesce(get(p), () => ( null))) === JSON.stringify(_nullishCoalesce(get(first), () => ( null))));
|
|
6143
|
+
const corners2 = [];
|
|
6144
|
+
for (const p of paths) {
|
|
6145
|
+
for (const c of vectorCorners(p.subpaths)) {
|
|
6146
|
+
if (anchorMode && !state.selectedAnchors.some((r) => r.nodeId === p.id && r.subpath === c.subpath && r.index === c.index)) continue;
|
|
6147
|
+
const corner = p.subpaths[c.subpath].anchors[c.index].corner;
|
|
6148
|
+
corners2.push({ radius: _nullishCoalesce(_optionalChain([corner, 'optionalAccess', _162 => _162.radius]), () => ( 0)), kind: _nullishCoalesce(_optionalChain([corner, 'optionalAccess', _163 => _163.kind]), () => ( "round")) });
|
|
6149
|
+
}
|
|
6150
|
+
}
|
|
6151
|
+
const c0 = _nullishCoalesce(corners2[0], () => ( { radius: 0, kind: "round" }));
|
|
3957
6152
|
return {
|
|
3958
6153
|
fill: _nullishCoalesce(first.fill, () => ( null)),
|
|
3959
6154
|
stroke: _nullishCoalesce(first.stroke, () => ( null)),
|
|
3960
6155
|
strokeWidth: _nullishCoalesce(first.strokeWidth, () => ( 1)),
|
|
3961
6156
|
opacity: _nullishCoalesce(first.opacity, () => ( 1)),
|
|
3962
|
-
|
|
6157
|
+
strokeAlign: _nullishCoalesce(first.strokeAlign, () => ( "center")),
|
|
6158
|
+
startArrow: _nullishCoalesce(first.startArrow, () => ( null)),
|
|
6159
|
+
endArrow: _nullishCoalesce(first.endArrow, () => ( null)),
|
|
6160
|
+
arrowAlign: _nullishCoalesce(first.arrowAlign, () => ( "extend")),
|
|
6161
|
+
startArrowScale: _nullishCoalesce(first.startArrowScale, () => ( 1)),
|
|
6162
|
+
endArrowScale: _nullishCoalesce(first.endArrowScale, () => ( 1)),
|
|
6163
|
+
cornerRadius: c0.radius,
|
|
6164
|
+
cornerKind: c0.kind,
|
|
6165
|
+
corners: corners2.length,
|
|
6166
|
+
hasOpenPath: paths.some((p) => p.subpaths.some((sp) => !sp.closed && sp.anchors.length > 1)),
|
|
6167
|
+
mixed: {
|
|
6168
|
+
fill: !same((p) => p.fill),
|
|
6169
|
+
stroke: !same((p) => p.stroke),
|
|
6170
|
+
strokeWidth: !same((p) => _nullishCoalesce(p.strokeWidth, () => ( 1))),
|
|
6171
|
+
opacity: !same((p) => _nullishCoalesce(p.opacity, () => ( 1))),
|
|
6172
|
+
strokeAlign: !same((p) => _nullishCoalesce(p.strokeAlign, () => ( "center"))),
|
|
6173
|
+
startArrow: !same((p) => _nullishCoalesce(p.startArrow, () => ( null))),
|
|
6174
|
+
endArrow: !same((p) => _nullishCoalesce(p.endArrow, () => ( null))),
|
|
6175
|
+
arrowAlign: !same((p) => _nullishCoalesce(p.arrowAlign, () => ( "extend"))),
|
|
6176
|
+
startArrowScale: !same((p) => _nullishCoalesce(p.startArrowScale, () => ( 1))),
|
|
6177
|
+
endArrowScale: !same((p) => _nullishCoalesce(p.endArrowScale, () => ( 1))),
|
|
6178
|
+
cornerRadius: corners2.some((c) => c.radius !== c0.radius),
|
|
6179
|
+
cornerKind: corners2.some((c) => c.kind !== c0.kind)
|
|
6180
|
+
},
|
|
3963
6181
|
pathIds: paths.map((p) => p.id)
|
|
3964
6182
|
};
|
|
3965
6183
|
}
|
|
6184
|
+
var VECTOR_STROKE_ALIGN_LABELS = { center: "Center", inside: "Inside", outside: "Outside" };
|
|
6185
|
+
var VECTOR_CORNER_KIND_LABELS = { round: "Round", inverted: "Inverted", chamfer: "Chamfer" };
|
|
6186
|
+
var VECTOR_ARROWHEAD_LABELS = {
|
|
6187
|
+
triangle: "Triangle",
|
|
6188
|
+
arrow: "Arrow",
|
|
6189
|
+
open: "Open arrow",
|
|
6190
|
+
circle: "Circle",
|
|
6191
|
+
square: "Square",
|
|
6192
|
+
bar: "Bar"
|
|
6193
|
+
};
|
|
3966
6194
|
function vectorPaintToHex(paint) {
|
|
3967
6195
|
if (typeof paint !== "string") return null;
|
|
3968
6196
|
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(paint.trim());
|
|
@@ -3970,12 +6198,40 @@ function vectorPaintToHex(paint) {
|
|
|
3970
6198
|
const hex = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
|
|
3971
6199
|
return `#${hex.toLowerCase()}`;
|
|
3972
6200
|
}
|
|
6201
|
+
var VECTOR_PATH_ACTIONS = [
|
|
6202
|
+
{ id: "join", label: "\u22C8", title: "Join", shortcut: "J", enabled: (s, p) => s + p > 0, run: (c) => c.joinPaths() },
|
|
6203
|
+
{ id: "average", label: "\u22B9", title: "Average Points", shortcut: "Alt+J", enabled: (_s, p) => p > 1, run: (c) => c.averageAnchors("both") },
|
|
6204
|
+
{ id: "cut", label: "\u2702\xB7", title: "Cut at Points", enabled: (_s, p) => p > 0, run: (c) => c.cutAtAnchors() },
|
|
6205
|
+
{ id: "clean", label: "\u2301", title: "Remove Redundant Points", enabled: (s, p) => s + p > 0, run: (c) => c.removeRedundantPoints() },
|
|
6206
|
+
{ id: "smooth", label: "\u223F", title: "Smooth", enabled: (s, p) => s + p > 0, run: (c) => c.smoothPaths() },
|
|
6207
|
+
{ id: "outline", label: "\u22A1", title: "Outline Stroke", enabled: (s) => s > 0, run: (c) => c.outlineStroke() }
|
|
6208
|
+
];
|
|
6209
|
+
var VECTOR_EDIT_TOGGLES = [
|
|
6210
|
+
{ key: "keepShapeOnDelete", label: "Keep", title: "Keep shape when deleting points" },
|
|
6211
|
+
{ key: "curvatureMoves", label: "Curv", title: "Curvature point moves (Alt flips while dragging)" },
|
|
6212
|
+
{ key: "recognizeShapes", label: "Shapes", title: "Turn pencil scribbles into shapes" }
|
|
6213
|
+
];
|
|
3973
6214
|
|
|
3974
6215
|
// src/vector/commands.ts
|
|
3975
6216
|
var fail = (message) => {
|
|
3976
6217
|
throw new Error(message);
|
|
3977
6218
|
};
|
|
3978
6219
|
var IDS = { type: "array", items: { type: "string" }, description: "Node ids (default: the selection)." };
|
|
6220
|
+
var ANCHORS = {
|
|
6221
|
+
type: "array",
|
|
6222
|
+
items: {
|
|
6223
|
+
type: "object",
|
|
6224
|
+
properties: { nodeId: { type: "string" }, subpath: { type: "integer", minimum: 0 }, index: { type: "integer", minimum: 0 } },
|
|
6225
|
+
required: ["nodeId", "subpath", "index"]
|
|
6226
|
+
},
|
|
6227
|
+
description: "Anchors as { nodeId, subpath, index } (default: the selected points)."
|
|
6228
|
+
};
|
|
6229
|
+
var POINTS = {
|
|
6230
|
+
type: "array",
|
|
6231
|
+
items: { type: "array", items: { type: "number" }, minItems: 2, maxItems: 2 },
|
|
6232
|
+
minItems: 1,
|
|
6233
|
+
description: "Polyline [[x, y], \u2026] in document units."
|
|
6234
|
+
};
|
|
3979
6235
|
var PAINT = {
|
|
3980
6236
|
description: 'A CSS colour, `null` for none, or a gradient `{ type: "linear", angle, stops: [{ offset, color }] }`.'
|
|
3981
6237
|
};
|
|
@@ -3986,7 +6242,13 @@ var STYLE_PROPS = {
|
|
|
3986
6242
|
strokeLinecap: { type: "string", enum: ["butt", "round", "square"] },
|
|
3987
6243
|
strokeLinejoin: { type: "string", enum: ["miter", "round", "bevel"] },
|
|
3988
6244
|
strokeDasharray: { type: "array", items: { type: "number" } },
|
|
3989
|
-
fillRule: { type: "string", enum: ["nonzero", "evenodd"] }
|
|
6245
|
+
fillRule: { type: "string", enum: ["nonzero", "evenodd"] },
|
|
6246
|
+
strokeAlign: { type: "string", enum: ["center", "inside", "outside"], description: "Where the stroke sits on the outline (closed shapes only; open paths stroke centred)." },
|
|
6247
|
+
startArrow: { type: ["string", "null"], enum: [...VECTOR_ARROWHEADS, null], description: "Head at the start of an open path, or null for none." },
|
|
6248
|
+
endArrow: { type: ["string", "null"], enum: [...VECTOR_ARROWHEADS, null], description: "Head at the end of an open path, or null for none." },
|
|
6249
|
+
arrowAlign: { type: "string", enum: ["extend", "tip"], description: "`extend`: the tip sits past the end point; `tip`: on it, the stroke shortened under the head." },
|
|
6250
|
+
startArrowScale: { type: "number", exclusiveMinimum: 0, description: "Start head size as a multiple of the stroke width (default 1)." },
|
|
6251
|
+
endArrowScale: { type: "number", exclusiveMinimum: 0, description: "End head size as a multiple of the stroke width (default 1)." }
|
|
3990
6252
|
};
|
|
3991
6253
|
var pickStyle = (params) => {
|
|
3992
6254
|
const out = {};
|
|
@@ -4002,12 +6264,20 @@ function createVectorCommands(controller, options = {}) {
|
|
|
4002
6264
|
const base = { group, ...options.scope ? { scope: options.scope } : {} };
|
|
4003
6265
|
const hasSelection = () => controller.getState().selection.length ? true : "Nothing is selected.";
|
|
4004
6266
|
const ids = (params) => {
|
|
4005
|
-
const list2 = _nullishCoalesce(_optionalChain([params, 'optionalAccess',
|
|
6267
|
+
const list2 = _nullishCoalesce(_optionalChain([params, 'optionalAccess', _164 => _164.ids]), () => ( controller.getState().selection));
|
|
4006
6268
|
for (const id of list2) if (!controller.getNode(id)) fail(`There is no node "${id}".`);
|
|
4007
6269
|
if (!list2.length) fail("Nothing is selected; pass ids.");
|
|
4008
6270
|
return list2;
|
|
4009
6271
|
};
|
|
4010
|
-
const enabledFor = (_, params) => _optionalChain([params, 'optionalAccess',
|
|
6272
|
+
const enabledFor = (_, params) => _optionalChain([params, 'optionalAccess', _165 => _165.ids, 'optionalAccess', _166 => _166.length]) ? true : hasSelection();
|
|
6273
|
+
const anchorsOf = (refs) => {
|
|
6274
|
+
if (!refs) return [...controller.getState().selectedAnchors];
|
|
6275
|
+
for (const r of refs) {
|
|
6276
|
+
const node = controller.getNode(r.nodeId);
|
|
6277
|
+
if (_optionalChain([node, 'optionalAccess', _167 => _167.type]) !== "path" || !_optionalChain([node, 'access', _168 => _168.subpaths, 'access', _169 => _169[r.subpath], 'optionalAccess', _170 => _170.anchors, 'access', _171 => _171[r.index]])) fail(`There is no anchor ${r.index} on subpath ${r.subpath} of "${r.nodeId}".`);
|
|
6278
|
+
}
|
|
6279
|
+
return refs;
|
|
6280
|
+
};
|
|
4011
6281
|
const list = () => {
|
|
4012
6282
|
const state = controller.getState();
|
|
4013
6283
|
const out = [];
|
|
@@ -4071,7 +6341,8 @@ function createVectorCommands(controller, options = {}) {
|
|
|
4071
6341
|
y: { type: "number" },
|
|
4072
6342
|
width: { type: "number", description: "For a line: the x offset of its end." },
|
|
4073
6343
|
height: { type: "number", description: "For a line: the y offset of its end." },
|
|
4074
|
-
cornerRadius: { type: "number", minimum: 0 },
|
|
6344
|
+
cornerRadius: { type: "number", minimum: 0, description: "Live corner radius of a rect, polygon or star." },
|
|
6345
|
+
cornerKind: { type: "string", enum: VECTOR_CORNER_KINDS },
|
|
4075
6346
|
sides: { type: "integer", minimum: 3 },
|
|
4076
6347
|
points: { type: "integer", minimum: 3 },
|
|
4077
6348
|
innerRatio: { type: "number", minimum: 0, maximum: 1 },
|
|
@@ -4094,7 +6365,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
4094
6365
|
run: (_ctx, params) => {
|
|
4095
6366
|
const id = controller.addPath(params.d, { ...pickStyle(params), ...params.name ? { name: params.name } : {} });
|
|
4096
6367
|
const node = controller.getNode(id);
|
|
4097
|
-
if (_optionalChain([node, 'optionalAccess',
|
|
6368
|
+
if (_optionalChain([node, 'optionalAccess', _172 => _172.type]) === "path" && !node.subpaths.length) {
|
|
4098
6369
|
controller.undo();
|
|
4099
6370
|
fail("The path data has no points.");
|
|
4100
6371
|
}
|
|
@@ -4117,6 +6388,38 @@ function createVectorCommands(controller, options = {}) {
|
|
|
4117
6388
|
return { ok: true };
|
|
4118
6389
|
}
|
|
4119
6390
|
},
|
|
6391
|
+
{
|
|
6392
|
+
...base,
|
|
6393
|
+
id: `${prefix}setCorners`,
|
|
6394
|
+
label: "Corner Radius\u2026",
|
|
6395
|
+
keywords: ["round", "rounded", "corners", "radius", "fillet", "chamfer", "live corners"],
|
|
6396
|
+
description: "Sets the live corner radius (and optionally `kind`: round, inverted or chamfer) of `anchors`, else the selected points (direct tool), else every corner of `ids` / the selection. Radius 0 makes corners sharp; radii shrink to fit short sides. The anchors stay editable.",
|
|
6397
|
+
params: { type: "object", properties: { radius: { type: "number", minimum: 0 }, kind: { type: "string", enum: VECTOR_CORNER_KINDS }, ids: IDS, anchors: ANCHORS } },
|
|
6398
|
+
undoable: true,
|
|
6399
|
+
run: (_ctx, params) => {
|
|
6400
|
+
const refs = _optionalChain([params, 'optionalAccess', _173 => _173.anchors]) ? anchorsOf(params.anchors) : void 0;
|
|
6401
|
+
const target = { refs, ids: _optionalChain([params, 'optionalAccess', _174 => _174.ids]) ? ids(params) : void 0 };
|
|
6402
|
+
if (_optionalChain([params, 'optionalAccess', _175 => _175.radius]) !== void 0) check(controller.setCornerRadius(params.radius, { ...target, kind: params.kind }));
|
|
6403
|
+
else if (_optionalChain([params, 'optionalAccess', _176 => _176.kind])) check(controller.setCornerKind(params.kind, target));
|
|
6404
|
+
else fail("Pass `radius` and / or `kind`.");
|
|
6405
|
+
return { ok: true };
|
|
6406
|
+
}
|
|
6407
|
+
},
|
|
6408
|
+
{
|
|
6409
|
+
...base,
|
|
6410
|
+
id: `${prefix}outlineStroke`,
|
|
6411
|
+
label: "Outline Stroke",
|
|
6412
|
+
keywords: ["expand", "stroke to path", "outline", "convert stroke"],
|
|
6413
|
+
description: "Turns the stroke of paths into filled shapes of the area it paints (alignment, caps, joins, dashes and arrowheads included). Returns the new ids.",
|
|
6414
|
+
params: { type: "object", properties: { ids: IDS } },
|
|
6415
|
+
enabled: enabledFor,
|
|
6416
|
+
undoable: true,
|
|
6417
|
+
run: (_ctx, params) => {
|
|
6418
|
+
const created = controller.outlineStroke({ ids: _optionalChain([params, 'optionalAccess', _177 => _177.ids]) ? ids(params) : void 0 });
|
|
6419
|
+
if (!created.length) fail("No selected path has a stroke.");
|
|
6420
|
+
return { ids: created };
|
|
6421
|
+
}
|
|
6422
|
+
},
|
|
4120
6423
|
{
|
|
4121
6424
|
...base,
|
|
4122
6425
|
id: `${prefix}transform`,
|
|
@@ -4227,7 +6530,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
4227
6530
|
enabled: enabledFor,
|
|
4228
6531
|
undoable: true,
|
|
4229
6532
|
run: (_ctx, params) => {
|
|
4230
|
-
const id = controller.group({ ids: ids(params), name: _optionalChain([params, 'optionalAccess',
|
|
6533
|
+
const id = controller.group({ ids: ids(params), name: _optionalChain([params, 'optionalAccess', _178 => _178.name]) });
|
|
4231
6534
|
if (!id) fail("Only nodes that share a parent can be grouped.");
|
|
4232
6535
|
return { id };
|
|
4233
6536
|
}
|
|
@@ -4246,6 +6549,186 @@ function createVectorCommands(controller, options = {}) {
|
|
|
4246
6549
|
return { selection: controller.getState().selection };
|
|
4247
6550
|
}
|
|
4248
6551
|
},
|
|
6552
|
+
{
|
|
6553
|
+
...base,
|
|
6554
|
+
id: `${prefix}deleteAnchors`,
|
|
6555
|
+
label: "Delete Points",
|
|
6556
|
+
keywords: ["remove", "anchor", "point", "node"],
|
|
6557
|
+
description: "Deletes anchors. With `keepShape` (default: the editor setting, on) the neighbouring handles are refitted so the outline keeps its shape; off, the neighbours are joined as they are.",
|
|
6558
|
+
params: { type: "object", properties: { anchors: ANCHORS, keepShape: { type: "boolean" } } },
|
|
6559
|
+
undoable: true,
|
|
6560
|
+
run: (_ctx, params) => {
|
|
6561
|
+
check(controller.deleteAnchors(anchorsOf(_optionalChain([params, 'optionalAccess', _179 => _179.anchors])), { keepShape: _optionalChain([params, 'optionalAccess', _180 => _180.keepShape]) }));
|
|
6562
|
+
return { ok: true };
|
|
6563
|
+
}
|
|
6564
|
+
},
|
|
6565
|
+
{
|
|
6566
|
+
...base,
|
|
6567
|
+
id: `${prefix}moveAnchor`,
|
|
6568
|
+
label: "Move Point\u2026",
|
|
6569
|
+
keywords: ["anchor", "drag", "curvature"],
|
|
6570
|
+
description: "Moves one anchor to (x, y) in document units. `curvature: true` bends its two segments like the Curvature tool instead of carrying its handles along.",
|
|
6571
|
+
params: {
|
|
6572
|
+
type: "object",
|
|
6573
|
+
properties: { nodeId: { type: "string" }, subpath: { type: "integer", minimum: 0 }, index: { type: "integer", minimum: 0 }, x: { type: "number" }, y: { type: "number" }, curvature: { type: "boolean" } },
|
|
6574
|
+
required: ["nodeId", "subpath", "index", "x", "y"]
|
|
6575
|
+
},
|
|
6576
|
+
undoable: true,
|
|
6577
|
+
run: (_ctx, params) => {
|
|
6578
|
+
check(controller.moveAnchor({ nodeId: params.nodeId, subpath: params.subpath, index: params.index }, { x: params.x, y: params.y }, { curvature: params.curvature }));
|
|
6579
|
+
return { ok: true };
|
|
6580
|
+
}
|
|
6581
|
+
},
|
|
6582
|
+
{
|
|
6583
|
+
...base,
|
|
6584
|
+
id: `${prefix}bendSegment`,
|
|
6585
|
+
label: "Bend Segment\u2026",
|
|
6586
|
+
keywords: ["curve", "segment", "drag"],
|
|
6587
|
+
description: "Bends segment `segment` (from anchor `segment` to the next) so the curve passes through (x, y) at parameter `t` (default 0.5).",
|
|
6588
|
+
params: {
|
|
6589
|
+
type: "object",
|
|
6590
|
+
properties: {
|
|
6591
|
+
nodeId: { type: "string" },
|
|
6592
|
+
subpath: { type: "integer", minimum: 0 },
|
|
6593
|
+
segment: { type: "integer", minimum: 0 },
|
|
6594
|
+
x: { type: "number" },
|
|
6595
|
+
y: { type: "number" },
|
|
6596
|
+
t: { type: "number", minimum: 0, maximum: 1 }
|
|
6597
|
+
},
|
|
6598
|
+
required: ["nodeId", "subpath", "segment", "x", "y"]
|
|
6599
|
+
},
|
|
6600
|
+
undoable: true,
|
|
6601
|
+
run: (_ctx, params) => {
|
|
6602
|
+
check(controller.bendSegment({ nodeId: params.nodeId, subpath: params.subpath, segment: params.segment }, { x: params.x, y: params.y }, params.t));
|
|
6603
|
+
return { ok: true };
|
|
6604
|
+
}
|
|
6605
|
+
},
|
|
6606
|
+
{
|
|
6607
|
+
...base,
|
|
6608
|
+
id: `${prefix}join`,
|
|
6609
|
+
label: "Join",
|
|
6610
|
+
shortcut: "Mod+J",
|
|
6611
|
+
keywords: ["connect", "close", "merge", "endpoints"],
|
|
6612
|
+
description: "Joins the open subpaths of paths nearest endpoints first (ends within `tolerance` merge); a single open path is closed.",
|
|
6613
|
+
params: { type: "object", properties: { ids: IDS, tolerance: { type: "number", minimum: 0 } } },
|
|
6614
|
+
undoable: true,
|
|
6615
|
+
run: (_ctx, params) => {
|
|
6616
|
+
check(controller.joinPaths({ ids: _optionalChain([params, 'optionalAccess', _181 => _181.ids]) ? ids(params) : void 0, tolerance: _optionalChain([params, 'optionalAccess', _182 => _182.tolerance]) }));
|
|
6617
|
+
return { selection: controller.getState().selection };
|
|
6618
|
+
}
|
|
6619
|
+
},
|
|
6620
|
+
{
|
|
6621
|
+
...base,
|
|
6622
|
+
id: `${prefix}average`,
|
|
6623
|
+
label: "Average Points\u2026",
|
|
6624
|
+
shortcut: "Mod+Alt+J",
|
|
6625
|
+
keywords: ["align", "points", "anchors", "collapse"],
|
|
6626
|
+
description: "Moves anchors onto their average position: the same y (`horizontal`), the same x (`vertical`) or the same point (`both`, default).",
|
|
6627
|
+
params: { type: "object", properties: { axis: { type: "string", enum: ["horizontal", "vertical", "both"] }, anchors: ANCHORS } },
|
|
6628
|
+
undoable: true,
|
|
6629
|
+
run: (_ctx, params) => {
|
|
6630
|
+
check(controller.averageAnchors(_nullishCoalesce(_optionalChain([params, 'optionalAccess', _183 => _183.axis]), () => ( "both")), anchorsOf(_optionalChain([params, 'optionalAccess', _184 => _184.anchors]))));
|
|
6631
|
+
return { ok: true };
|
|
6632
|
+
}
|
|
6633
|
+
},
|
|
6634
|
+
{
|
|
6635
|
+
...base,
|
|
6636
|
+
id: `${prefix}removeRedundantPoints`,
|
|
6637
|
+
label: "Remove Redundant Points",
|
|
6638
|
+
keywords: ["simplify", "clean up", "anchors", "collinear"],
|
|
6639
|
+
description: "Drops anchors that can go without moving the outline more than `tolerance` document units (default 0.5).",
|
|
6640
|
+
params: { type: "object", properties: { ids: IDS, tolerance: { type: "number", minimum: 0 } } },
|
|
6641
|
+
enabled: enabledFor,
|
|
6642
|
+
undoable: true,
|
|
6643
|
+
run: (_ctx, params) => {
|
|
6644
|
+
check(controller.removeRedundantPoints({ ids: _optionalChain([params, 'optionalAccess', _185 => _185.ids]) ? ids(params) : void 0, tolerance: _optionalChain([params, 'optionalAccess', _186 => _186.tolerance]) }));
|
|
6645
|
+
return { ok: true };
|
|
6646
|
+
}
|
|
6647
|
+
},
|
|
6648
|
+
{
|
|
6649
|
+
...base,
|
|
6650
|
+
id: `${prefix}smooth`,
|
|
6651
|
+
label: "Smooth Path",
|
|
6652
|
+
keywords: ["round", "soften", "handles", "curves"],
|
|
6653
|
+
description: "Pulls the handles of paths (or of the selected points) towards smooth tangents by `amount` (0\u20131, default 1).",
|
|
6654
|
+
params: { type: "object", properties: { ids: IDS, amount: { type: "number", minimum: 0, maximum: 1 }, anchors: ANCHORS } },
|
|
6655
|
+
undoable: true,
|
|
6656
|
+
run: (_ctx, params) => {
|
|
6657
|
+
check(controller.smoothPaths({ ids: _optionalChain([params, 'optionalAccess', _187 => _187.ids]) ? ids(params) : void 0, amount: _optionalChain([params, 'optionalAccess', _188 => _188.amount]), refs: _optionalChain([params, 'optionalAccess', _189 => _189.anchors]) }));
|
|
6658
|
+
return { ok: true };
|
|
6659
|
+
}
|
|
6660
|
+
},
|
|
6661
|
+
{
|
|
6662
|
+
...base,
|
|
6663
|
+
id: `${prefix}cut`,
|
|
6664
|
+
label: "Cut Path (Scissors)\u2026",
|
|
6665
|
+
keywords: ["scissors", "split", "open", "break"],
|
|
6666
|
+
description: "Scissors: cuts path `nodeId` at the outline point nearest (x, y), or cuts at `anchors`. A closed path opens; an open one splits in two.",
|
|
6667
|
+
params: { type: "object", properties: { nodeId: { type: "string" }, x: { type: "number" }, y: { type: "number" }, anchors: ANCHORS } },
|
|
6668
|
+
undoable: true,
|
|
6669
|
+
run: (_ctx, params) => {
|
|
6670
|
+
if (_optionalChain([params, 'optionalAccess', _190 => _190.nodeId]) !== void 0) {
|
|
6671
|
+
if (!controller.getNode(params.nodeId)) fail(`There is no node "${params.nodeId}".`);
|
|
6672
|
+
if (params.x === void 0 || params.y === void 0) fail("Pass x and y with nodeId.");
|
|
6673
|
+
const created = controller.cutPathAt(params.nodeId, { x: params.x, y: params.y });
|
|
6674
|
+
if (!created) fail("Nothing to cut there (the end of an open path, or a locked path).");
|
|
6675
|
+
return { ids: created };
|
|
6676
|
+
}
|
|
6677
|
+
check(controller.cutAtAnchors(anchorsOf(_optionalChain([params, 'optionalAccess', _191 => _191.anchors]))));
|
|
6678
|
+
return { ids: controller.getState().selection };
|
|
6679
|
+
}
|
|
6680
|
+
},
|
|
6681
|
+
{
|
|
6682
|
+
...base,
|
|
6683
|
+
id: `${prefix}knife`,
|
|
6684
|
+
label: "Knife\u2026",
|
|
6685
|
+
keywords: ["slice", "cut", "split", "divide"],
|
|
6686
|
+
description: "Cuts closed paths along a polyline wherever it crosses them edge to edge (no gap between pieces). Cuts `ids`, else the selection, else every path the line crosses.",
|
|
6687
|
+
params: { type: "object", properties: { points: { ...POINTS, minItems: 2 }, ids: IDS }, required: ["points"] },
|
|
6688
|
+
undoable: true,
|
|
6689
|
+
run: (_ctx, params) => {
|
|
6690
|
+
const created = controller.knife(params.points.map(([x, y]) => ({ x, y })), { ids: params.ids ? ids(params) : void 0 });
|
|
6691
|
+
if (!created.length) fail("The knife did not cross any closed path from edge to edge.");
|
|
6692
|
+
return { ids: created };
|
|
6693
|
+
}
|
|
6694
|
+
},
|
|
6695
|
+
{
|
|
6696
|
+
...base,
|
|
6697
|
+
id: `${prefix}erase`,
|
|
6698
|
+
label: "Erase\u2026",
|
|
6699
|
+
keywords: ["eraser", "rub out", "remove"],
|
|
6700
|
+
description: "Erases a round stroke of diameter `size` (default: the eraser size setting) along a polyline: filled shapes lose the area, outlines are cut under it. Erases from `ids`, else the selection, else every path it reaches.",
|
|
6701
|
+
params: { type: "object", properties: { points: POINTS, size: { type: "number", exclusiveMinimum: 0 }, ids: IDS }, required: ["points"] },
|
|
6702
|
+
undoable: true,
|
|
6703
|
+
run: (_ctx, params) => {
|
|
6704
|
+
const changed = controller.erase(params.points.map(([x, y]) => ({ x, y })), { size: params.size, ids: params.ids ? ids(params) : void 0 });
|
|
6705
|
+
if (!changed.length) fail("The eraser did not touch any path.");
|
|
6706
|
+
return { ids: changed.filter((id) => controller.getNode(id)) };
|
|
6707
|
+
}
|
|
6708
|
+
},
|
|
6709
|
+
{
|
|
6710
|
+
...base,
|
|
6711
|
+
id: `${prefix}setEditing`,
|
|
6712
|
+
label: "Path Editing Options\u2026",
|
|
6713
|
+
keywords: ["settings", "curvature", "pencil", "smoothing", "lazy mouse", "shapes", "eraser"],
|
|
6714
|
+
description: "Sets how editing behaves: `keepShapeOnDelete`, `curvatureMoves` (anchor drags bend like the Curvature tool), `pencilSmoothing` (lazy mouse, 0\u20131), `recognizeShapes` (pencil scribbles become lines, rectangles, ellipses, polygons) and `eraserSize`.",
|
|
6715
|
+
params: {
|
|
6716
|
+
type: "object",
|
|
6717
|
+
properties: {
|
|
6718
|
+
keepShapeOnDelete: { type: "boolean" },
|
|
6719
|
+
curvatureMoves: { type: "boolean" },
|
|
6720
|
+
pencilSmoothing: { type: "number", minimum: 0, maximum: 1 },
|
|
6721
|
+
recognizeShapes: { type: "boolean" },
|
|
6722
|
+
eraserSize: { type: "number", exclusiveMinimum: 0 }
|
|
6723
|
+
}
|
|
6724
|
+
},
|
|
6725
|
+
run: (_ctx, params) => {
|
|
6726
|
+
const patch = {};
|
|
6727
|
+
for (const key of ["keepShapeOnDelete", "curvatureMoves", "pencilSmoothing", "recognizeShapes", "eraserSize"]) if (_optionalChain([params, 'optionalAccess', _192 => _192[key]]) !== void 0) patch[key] = params[key];
|
|
6728
|
+
controller.setEditing(patch);
|
|
6729
|
+
return { editing: controller.getState().editing };
|
|
6730
|
+
}
|
|
6731
|
+
},
|
|
4249
6732
|
{
|
|
4250
6733
|
...base,
|
|
4251
6734
|
id: `${prefix}setTool`,
|
|
@@ -4266,7 +6749,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
4266
6749
|
keywords: ["download", "save", "markup"],
|
|
4267
6750
|
description: "Returns the document as SVG markup.",
|
|
4268
6751
|
params: { type: "object", properties: { precision: { type: "integer", minimum: 0, maximum: 8 } } },
|
|
4269
|
-
run: (_ctx, params) => ({ svg: controller.toSvg({ precision: _optionalChain([params, 'optionalAccess',
|
|
6752
|
+
run: (_ctx, params) => ({ svg: controller.toSvg({ precision: _optionalChain([params, 'optionalAccess', _193 => _193.precision]) }) })
|
|
4270
6753
|
},
|
|
4271
6754
|
{
|
|
4272
6755
|
...base,
|
|
@@ -4373,5 +6856,64 @@ function createVectorCommands(controller, options = {}) {
|
|
|
4373
6856
|
|
|
4374
6857
|
|
|
4375
6858
|
|
|
4376
|
-
|
|
6859
|
+
|
|
6860
|
+
|
|
6861
|
+
|
|
6862
|
+
|
|
6863
|
+
|
|
6864
|
+
|
|
6865
|
+
|
|
6866
|
+
|
|
6867
|
+
|
|
6868
|
+
|
|
6869
|
+
|
|
6870
|
+
|
|
6871
|
+
|
|
6872
|
+
|
|
6873
|
+
|
|
6874
|
+
|
|
6875
|
+
|
|
6876
|
+
|
|
6877
|
+
|
|
6878
|
+
|
|
6879
|
+
|
|
6880
|
+
|
|
6881
|
+
|
|
6882
|
+
|
|
6883
|
+
|
|
6884
|
+
|
|
6885
|
+
|
|
6886
|
+
|
|
6887
|
+
|
|
6888
|
+
|
|
6889
|
+
|
|
6890
|
+
|
|
6891
|
+
|
|
6892
|
+
|
|
6893
|
+
|
|
6894
|
+
|
|
6895
|
+
|
|
6896
|
+
|
|
6897
|
+
|
|
6898
|
+
|
|
6899
|
+
|
|
6900
|
+
|
|
6901
|
+
|
|
6902
|
+
|
|
6903
|
+
|
|
6904
|
+
|
|
6905
|
+
|
|
6906
|
+
|
|
6907
|
+
|
|
6908
|
+
|
|
6909
|
+
|
|
6910
|
+
|
|
6911
|
+
|
|
6912
|
+
|
|
6913
|
+
|
|
6914
|
+
|
|
6915
|
+
|
|
6916
|
+
|
|
6917
|
+
|
|
6918
|
+
exports.DEFAULT_VECTOR_PATH_EDIT = DEFAULT_VECTOR_PATH_EDIT; exports.DEFAULT_VECTOR_SNAP = DEFAULT_VECTOR_SNAP; exports.IDENTITY_MATRIX = IDENTITY_MATRIX; exports.KAPPA = KAPPA2; exports.LazyMouse = LazyMouse; exports.VECTOR_ARROWHEADS = VECTOR_ARROWHEADS; exports.VECTOR_ARROWHEAD_LABELS = VECTOR_ARROWHEAD_LABELS; exports.VECTOR_CLIPBOARD_TYPE = VECTOR_CLIPBOARD_TYPE; exports.VECTOR_CONSTRUCTION_ANGLES = VECTOR_CONSTRUCTION_ANGLES; exports.VECTOR_CORNER_KINDS = VECTOR_CORNER_KINDS; exports.VECTOR_CORNER_KIND_LABELS = VECTOR_CORNER_KIND_LABELS; exports.VECTOR_CORNER_WIDGET_INSET = VECTOR_CORNER_WIDGET_INSET; exports.VECTOR_EDIT_TOGGLES = VECTOR_EDIT_TOGGLES; exports.VECTOR_INK_MAX_CORNERS = VECTOR_INK_MAX_CORNERS; exports.VECTOR_OVERLAY_THEME = VECTOR_OVERLAY_THEME; exports.VECTOR_PATH_ACTIONS = VECTOR_PATH_ACTIONS; exports.VECTOR_PENCIL_STRING_PX = VECTOR_PENCIL_STRING_PX; exports.VECTOR_ROTATE_HANDLE_OFFSET = VECTOR_ROTATE_HANDLE_OFFSET; exports.VECTOR_STROKE_ALIGN_LABELS = VECTOR_STROKE_ALIGN_LABELS; exports.VECTOR_STYLE_KEYS = VECTOR_STYLE_KEYS; 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.applyVectorCornersAttribute = applyVectorCornersAttribute; exports.arrowheadInset = arrowheadInset; exports.arrowheadPath = arrowheadPath; exports.arrowheadWeight = arrowheadWeight; exports.bendSegment = bendSegment; exports.bezierToSubpaths = bezierToSubpaths; exports.boundsFromPoints = boundsFromPoints; exports.boundsIntersect = boundsIntersect; exports.cloneAnchor = cloneAnchor; exports.cloneSubpath = cloneSubpath; exports.cloneVectorDocument = cloneVectorDocument; exports.cloneVectorNode = cloneVectorNode; exports.collectPathNodes = collectPathNodes; exports.constrainToLeave = constrainToLeave; exports.convertAnchor = convertAnchor; exports.cornerBisectorPoint = cornerBisectorPoint; exports.cornerRadiusChange = cornerRadiusChange; exports.cornerSetback = cornerSetback; exports.createVectorCommands = createVectorCommands; exports.curvatureMoveAnchor = curvatureMoveAnchor; exports.cutSubpathAt = cutSubpathAt; exports.decomposeRigid = decomposeRigid; exports.effectiveStrokeAlign = effectiveStrokeAlign; exports.ellipseSubpath = ellipseSubpath; exports.eraseFromFill = eraseFromFill; exports.eraseFromOutline = eraseFromOutline; exports.eraserRegion = eraserRegion; exports.expandVectorCorners = expandVectorCorners; exports.expandVectorCornersOf = expandVectorCornersOf; exports.findVectorNode = findVectorNode; exports.fitCornerRadius = fitCornerRadius; exports.getAnchor = getAnchor; exports.getAnchorWorld = getAnchorWorld; exports.getEditablePathIds = getEditablePathIds; exports.getTransformHandlePositions = getTransformHandlePositions; exports.getVectorCornerWidgets = getVectorCornerWidgets; 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.hasStrokeExtras = hasStrokeExtras; exports.hasVectorCorners = hasVectorCorners; exports.hitTestVectorNode = hitTestVectorNode; exports.hitTestVectorNodes = hitTestVectorNodes; exports.hitTestVectorStroke = hitTestVectorStroke; exports.inferAnchorType = inferAnchorType; exports.insertAnchor = insertAnchor; exports.invertMatrix = invertMatrix; exports.isClosedBezierShape = isClosedBezierShape; exports.isIdentityMatrix = isIdentityMatrix; exports.isVectorTextTarget = isVectorTextTarget; exports.joinSubpaths = joinSubpaths; exports.knifePath = knifePath; exports.lineSubpath = lineSubpath; exports.locateVectorNode = locateVectorNode; exports.matrixRotation = matrixRotation; exports.moveAnchorHandle = moveAnchorHandle; exports.multiplyMatrices = multiplyMatrices; exports.nearestSegment = nearestSegment; exports.nextVectorCornerKind = nextVectorCornerKind; exports.nodeMatrix = nodeMatrix; exports.parseSvgTransform = parseSvgTransform; exports.parseVectorSvg = parseVectorSvg; exports.polygonSubpath = polygonSubpath; exports.polylineBounds = polylineBounds; exports.recognizeInkShape = recognizeInkShape; exports.rectSubpath = rectSubpath; exports.removeAnchorKeepingShape = removeAnchorKeepingShape; exports.removeAnchors = removeAnchors; exports.removeAnchorsKeepingShape = removeAnchorsKeepingShape; exports.removeRedundantPoints = removeRedundantPoints; exports.reverseSubpath = reverseSubpath; exports.rotateMatrix = rotateMatrix; exports.roundSubpaths = roundSubpaths; exports.scaleFor = scaleFor; exports.scaleMatrix = scaleMatrix; exports.setAnchorCorner = setAnchorCorner; exports.setSubpathCorners = setSubpathCorners; exports.simplifyPolyline = simplifyPolyline; exports.smoothSubpath = smoothSubpath; exports.splitIntoFaces = splitIntoFaces; exports.splitSubpathAt = splitSubpathAt; exports.starSubpath = starSubpath; exports.subpathPointAt = subpathPointAt; exports.subpathSegment = subpathSegment; exports.subpathSegmentCount = subpathSegmentCount; exports.subpathsToBezier = subpathsToBezier; exports.toVectorKeyInput = toVectorKeyInput; exports.toVectorModifiers = toVectorModifiers; exports.toVectorPointerInput = toVectorPointerInput; exports.transformVectorNode = transformVectorNode; exports.translateAnchor = translateAnchor; exports.translateMatrix = translateMatrix; exports.unionBounds = unionBounds; exports.vectorCornerAt = vectorCornerAt; exports.vectorCorners = vectorCorners; exports.vectorCornersAttribute = vectorCornersAttribute; exports.vectorDocumentToSvg = vectorDocumentToSvg; exports.vectorInkShapeName = vectorInkShapeName; exports.vectorNodeBounds = vectorNodeBounds; exports.vectorNodeRotation = vectorNodeRotation; exports.vectorNodeToBezier = vectorNodeToBezier; exports.vectorPaintToHex = vectorPaintToHex; exports.vectorPaintToSvg = vectorPaintToSvg; exports.vectorParentMatrix = vectorParentMatrix; exports.vectorPathData = vectorPathData; exports.vectorPathWorldBezier = vectorPathWorldBezier; exports.vectorResizeCursor = vectorResizeCursor; exports.vectorStrokeOutline = vectorStrokeOutline; exports.vectorStrokeOutlinePath = vectorStrokeOutlinePath; exports.vectorStrokeParts = vectorStrokeParts; exports.vectorStrokeReach = vectorStrokeReach; exports.vectorStrokeSvgParts = vectorStrokeSvgParts; exports.vectorToolLabel = vectorToolLabel; exports.vectorWorldMatrix = vectorWorldMatrix; exports.walkVectorNodes = walkVectorNodes;
|
|
4377
6919
|
//# sourceMappingURL=index.cjs.map
|