@pixerate/editor 0.35.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.
@@ -20,27 +20,444 @@ import {
20
20
  segmentBounds,
21
21
  segmentDerivative,
22
22
  segmentIntersections,
23
+ segmentLength,
23
24
  segmentNearest,
25
+ segmentParamAtLength,
24
26
  segmentPoint,
27
+ segmentTangent,
25
28
  splitSegment,
26
29
  subSegment,
27
30
  toBezierPath,
28
31
  transformBezierPath,
32
+ trimSegments,
29
33
  windingNumber
30
34
  } from "../chunk-EGPIVUEF.js";
31
35
 
32
36
  // src/vector/types.ts
37
+ var VECTOR_CORNER_KINDS = ["round", "inverted", "chamfer"];
38
+ var VECTOR_ARROWHEADS = ["triangle", "arrow", "open", "circle", "square", "bar"];
33
39
  var VECTOR_TOOLS = ["select", "direct", "pen", "pencil", "rect", "ellipse", "polygon", "star", "line", "scissors", "knife", "eraser"];
34
40
  var VECTOR_TRANSFORM_HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w", "rotate"];
35
41
 
36
- // src/vector/geometry.ts
42
+ // src/vector/corners.ts
37
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
+ curve;
56
+ straight;
57
+ length;
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(h1 ?? a), pt(h2 ?? b), pt(b)];
61
+ this.length = this.straight ? Math.hypot(b.x - a.x, b.y - a.y) : segmentLength(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 = segmentParamAtLength(this.curve, s, this.length);
80
+ const d = vec(segmentDerivative(this.curve, t));
81
+ const l = len(d);
82
+ return { t, p: vec(segmentPoint(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 = a.corner?.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, a.corner?.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(a.out ?? a), pt(b.in ?? b), pt(b)];
183
+ const part = subSegment(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 : a.in ?? null;
192
+ };
193
+ const handleOut = (i, a) => {
194
+ const t = trimmed[i];
195
+ return t ? t.out : 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 = kind ?? corner?.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 = pathToContours(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 = style.strokeAlign ?? "center";
257
+ return closedShape ? a : "center";
258
+ }
259
+ function hasStrokeExtras(style) {
260
+ return (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, (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) => segmentLength(s);
338
+ function endOf(segs, atStart) {
339
+ const long = (s2) => segLen(s2) > 1e-9;
340
+ if (atStart) {
341
+ const s2 = segs.find(long) ?? segs[0];
342
+ const t2 = segmentTangent(s2, 0);
343
+ return { p: toVec(segs[0][0]), out: v(-t2[0], -t2[1]) };
344
+ }
345
+ const s = [...segs].reverse().find(long) ?? segs[segs.length - 1];
346
+ const t = segmentTangent(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 = trimSegments([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 = style.strokeWidth ?? 1;
367
+ const contours = pathToContours(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 ? trimSegments(c.segments, s0, total - s1, lengths) : [] };
408
+ });
409
+ return { align, line: contoursToPath(trimmed), drawWidth, heads };
410
+ }
411
+ function vectorStrokeOutlinePath(path, style) {
412
+ const parts = vectorStrokeParts(path, style);
413
+ const stroke = outlineStroke(parts.line, {
414
+ width: parts.drawWidth,
415
+ join: style.strokeLinejoin ?? "miter",
416
+ cap: style.strokeLinecap ?? "butt",
417
+ ...style.strokeDasharray?.length ? { dash: style.strokeDasharray } : {}
418
+ });
419
+ const fill = { path, fillRule: style.fillRule ?? "nonzero" };
420
+ let area = stroke;
421
+ if (parts.align === "inside") area = pathBoolean("intersection", [stroke, fill]).path;
422
+ else if (parts.align === "outside") area = pathBoolean("difference", [stroke, fill]).path;
423
+ if (!parts.heads.length) return area;
424
+ return pathBoolean("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 = style.strokeWidth ?? 1;
430
+ for (const h of parts.heads) {
431
+ if (pointInBezierPath(h.path, point) || tolerance > 0 && distanceToBezierPath(h.path, point) <= tolerance) return true;
432
+ }
433
+ if (!parts.line.length) return false;
434
+ const d = distanceToBezierPath(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 = pointInBezierPath(path, point, style.fillRule ?? "nonzero");
439
+ return parts.align === "inside" ? inside : !inside;
440
+ }
441
+ function vectorStrokeReach(style) {
442
+ const width = style.strokeWidth ?? 1;
443
+ const align = 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;
38
454
  var cloneAnchor = (a) => ({
39
455
  x: a.x,
40
456
  y: a.y,
41
457
  ...a.in ? { in: { x: a.in.x, y: a.in.y } } : {},
42
458
  ...a.out ? { out: { x: a.out.x, y: a.out.y } } : {},
43
- ...a.type && a.type !== "corner" ? { type: a.type } : {}
459
+ ...a.type && a.type !== "corner" ? { type: a.type } : {},
460
+ ...a.corner ? { corner: { ...a.corner } } : {}
44
461
  });
45
462
  var cloneSubpath = (s) => ({ anchors: s.anchors.map(cloneAnchor), closed: s.closed });
46
463
  function cloneVectorNode(node) {
@@ -58,7 +475,7 @@ function hasPaint(paint) {
58
475
  return true;
59
476
  }
60
477
  var handleOf = (a, side) => a[side] ?? { x: a.x, y: a.y };
61
- var isOnAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS && Math.abs(h.y - a.y) < EPS;
478
+ var isOnAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS2 && Math.abs(h.y - a.y) < EPS2;
62
479
  function subpathSegmentCount(sp) {
63
480
  const n = sp.anchors.length;
64
481
  if (n < 2) return 0;
@@ -72,9 +489,9 @@ function subpathSegment(sp, i) {
72
489
  const c2 = handleOf(b, "in");
73
490
  return [[a.x, a.y], [c1.x, c1.y], [c2.x, c2.y], [b.x, b.y]];
74
491
  }
75
- function subpathsToBezier(subpaths) {
492
+ function subpathsToBezier(subpaths, options = {}) {
76
493
  const out = [];
77
- for (const sp of subpaths) {
494
+ for (const sp of options.corners === false ? subpaths : expandVectorCorners(subpaths)) {
78
495
  if (sp.anchors.length === 0) continue;
79
496
  const first = sp.anchors[0];
80
497
  out.push({ type: "M", x: first.x, y: first.y });
@@ -104,9 +521,9 @@ function inferAnchorType(a) {
104
521
  const oy = a.out.y - a.y;
105
522
  const li = Math.hypot(ix, iy);
106
523
  const lo = Math.hypot(ox, oy);
107
- const cross = (ix * oy - iy * ox) / (li * lo);
108
- const dot = (ix * ox + iy * oy) / (li * lo);
109
- if (Math.abs(cross) > 1e-3 || dot > 0) return "corner";
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";
110
527
  return Math.abs(li - lo) <= 1e-3 * Math.max(li, lo) ? "symmetric" : "smooth";
111
528
  }
112
529
  function bezierToSubpaths(input) {
@@ -249,8 +666,12 @@ function hitTestVectorNode(node, p, tolerance = 0, parent = IDENTITY_MATRIX) {
249
666
  if (path.length === 0) return false;
250
667
  const point = [p.x, p.y];
251
668
  if (hasPaint(node.fill) && pointInBezierPath(path, point, node.fillRule ?? "nonzero")) return true;
252
- const reach = (hasPaint(node.stroke) ? (node.strokeWidth ?? 1) / 2 : 0) + tolerance;
253
- return distanceToBezierPath(path, point) <= reach;
669
+ if (hasPaint(node.stroke)) return hitTestVectorStroke(path, p, node, tolerance);
670
+ return distanceToBezierPath(path, point) <= tolerance;
671
+ }
672
+ function vectorStrokeOutline(node, parent = IDENTITY_MATRIX) {
673
+ if (!hasPaint(node.stroke) || !((node.strokeWidth ?? 1) > 0)) return [];
674
+ return vectorStrokeOutlinePath(vectorPathWorldBezier(node, parent), node);
254
675
  }
255
676
  function hitTestVectorNodes(nodes, p, tolerance = 0) {
256
677
  for (let i = nodes.length - 1; i >= 0; i--) {
@@ -268,8 +689,8 @@ function nearestSegment(node, p) {
268
689
  const seg = subpathSegment(sp, i);
269
690
  const hit = segmentNearest(seg, [p.x, p.y]);
270
691
  if (!best || hit.distance < best.distance) {
271
- const pt = segmentPoint(seg, hit.t);
272
- best = { subpath: s, segment: i, t: hit.t, point: { x: pt[0], y: pt[1] }, distance: hit.distance };
692
+ const pt2 = segmentPoint(seg, hit.t);
693
+ best = { subpath: s, segment: i, t: hit.t, point: { x: pt2[0], y: pt2[1] }, distance: hit.distance };
273
694
  }
274
695
  }
275
696
  });
@@ -303,8 +724,8 @@ function invertMatrix(m) {
303
724
  if (!det || !Number.isFinite(det)) return IDENTITY_MATRIX;
304
725
  return [d / det, -b / det, -c / det, a / det, (c * f - d * e) / det, (b * e - a * f) / det];
305
726
  }
306
- function applyMatrixLinear(m, v) {
307
- return { x: m[0] * v.x + m[2] * v.y, y: m[1] * v.x + m[3] * v.y };
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 };
308
729
  }
309
730
  function matrixRotation(m) {
310
731
  const [a, b, c, d] = m;
@@ -338,6 +759,7 @@ function bakeIntoGeometry(node, m) {
338
759
  if (a.in) next.in = applyMatrix(m, a.in);
339
760
  if (a.out) next.out = applyMatrix(m, a.out);
340
761
  if (a.type) next.type = a.type;
762
+ if (a.corner) next.corner = { ...a.corner };
341
763
  return next;
342
764
  })
343
765
  }))
@@ -389,12 +811,12 @@ function moveAnchorHandle(a, side, pos, breakHandles = false) {
389
811
  if (breakHandles) delete next.type;
390
812
  const dx = pos.x - a.x;
391
813
  const dy = pos.y - a.y;
392
- const len = Math.hypot(dx, dy);
814
+ const len2 = Math.hypot(dx, dy);
393
815
  if (type === "symmetric") next[other] = { x: a.x - dx, y: a.y - dy };
394
- else if (type === "smooth" && len > EPS) {
816
+ else if (type === "smooth" && len2 > EPS2) {
395
817
  const o = a[other];
396
- const olen = o ? Math.hypot(o.x - a.x, o.y - a.y) : len;
397
- next[other] = { x: a.x - dx / len * olen, y: a.y - dy / len * olen };
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 };
398
820
  }
399
821
  if (isOnAnchor(next, next.in)) delete next.in;
400
822
  if (isOnAnchor(next, next.out)) delete next.out;
@@ -421,15 +843,15 @@ function convertAnchor(sp, index, type) {
421
843
  else if (prev && next) dir = { x: next.x - prev.x, y: next.y - prev.y };
422
844
  else if (next) dir = { x: next.x - a.x, y: next.y - a.y };
423
845
  else if (prev) dir = { x: a.x - prev.x, y: a.y - prev.y };
424
- if (!dir || Math.hypot(dir.x, dir.y) < EPS) return { anchors, closed: sp.closed };
846
+ if (!dir || Math.hypot(dir.x, dir.y) < EPS2) return { anchors, closed: sp.closed };
425
847
  const dl = Math.hypot(dir.x, dir.y);
426
848
  const ux = dir.x / dl;
427
849
  const uy = dir.y / dl;
428
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;
429
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;
430
852
  if (type === "symmetric") lenIn = lenOut = (lenIn + lenOut) / 2 || Math.max(lenIn, lenOut);
431
- if (lenIn > EPS) a.in = { x: a.x - ux * lenIn, y: a.y - uy * lenIn };
432
- if (lenOut > EPS) a.out = { x: a.x + ux * lenOut, y: a.y + uy * 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 };
433
855
  a.type = type;
434
856
  return { anchors, closed: sp.closed };
435
857
  }
@@ -465,37 +887,24 @@ function reverseSubpath(sp) {
465
887
  if (a.out) next.in = { ...a.out };
466
888
  if (a.in) next.out = { ...a.in };
467
889
  if (a.type) next.type = a.type;
890
+ if (a.corner) next.corner = { ...a.corner };
468
891
  return next;
469
892
  });
470
893
  return { anchors, closed: sp.closed };
471
894
  }
472
- var KAPPA = 0.5522847498307936;
473
- function rectSubpath(b, radius = 0) {
895
+ var KAPPA2 = 0.5522847498307936;
896
+ function rectSubpath(b, radius = 0, kind) {
474
897
  const { x, y, width: w, height: h } = b;
475
- const r = Math.max(0, Math.min(radius, w / 2, h / 2));
476
- if (r <= EPS) {
477
- return { anchors: [{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }], closed: true };
478
- }
479
- const k = r * KAPPA;
480
- const anchors = [
481
- { x: x + r, y, in: { x: x + r - k, y } },
482
- { x: x + w - r, y, out: { x: x + w - r + k, y } },
483
- { x: x + w, y: y + r, in: { x: x + w, y: y + r - k } },
484
- { x: x + w, y: y + h - r, out: { x: x + w, y: y + h - r + k } },
485
- { x: x + w - r, y: y + h, in: { x: x + w - r + k, y: y + h } },
486
- { x: x + r, y: y + h, out: { x: x + r - k, y: y + h } },
487
- { x, y: y + h - r, in: { x, y: y + h - r + k } },
488
- { x, y: y + r, out: { x, y: y + r - k } }
489
- ];
490
- 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;
491
900
  }
492
901
  function ellipseSubpath(b) {
493
902
  const rx = b.width / 2;
494
903
  const ry = b.height / 2;
495
904
  const cx = b.x + rx;
496
905
  const cy = b.y + ry;
497
- const kx = rx * KAPPA;
498
- const ky = ry * KAPPA;
906
+ const kx = rx * KAPPA2;
907
+ const ky = ry * KAPPA2;
499
908
  return {
500
909
  closed: true,
501
910
  anchors: [
@@ -543,15 +952,52 @@ function roundSubpaths(subpaths, precision = 3) {
543
952
  }
544
953
 
545
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}:${formatNumber(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 = subpaths[+m[1]]?.anchors[+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
+ }
546
977
  var escapeAttr = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
547
978
  function vectorPaintToSvg(paint, gradientId) {
548
979
  if (!hasPaint(paint)) return { value: "none", gradient: null };
549
980
  if (typeof paint === "string") return { value: paint, gradient: null };
550
981
  return { value: `url(#${gradientId})`, gradient: gradientToSvgDef(paint, gradientId) };
551
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 = bezierPathBounds(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: bezierPathToSvg(parts.line, { precision }), width: parts.drawWidth, clip, heads: parts.heads.map((h) => bezierPathToSvg(h.path, { precision })) };
996
+ }
997
+ var round2 = (n) => Math.round(n * 100) / 100;
552
998
  function gradientMarkup(def) {
553
999
  const tag = def.type === "radial" ? "radialGradient" : "linearGradient";
554
- const attrs = Object.entries(def.attrs).map(([k, v]) => ` ${k}="${escapeAttr(String(v))}"`).join("");
1000
+ const attrs = Object.entries(def.attrs).map(([k, v2]) => ` ${k}="${escapeAttr(String(v2))}"`).join("");
555
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("");
556
1002
  return `<${tag} id="${escapeAttr(def.id)}"${attrs}>${stops}</${tag}>`;
557
1003
  }
@@ -582,15 +1028,53 @@ function vectorDocumentToSvg(doc, options = {}) {
582
1028
  const stroke = vectorPaintToSvg(node.stroke, `${prefix}stroke-${node.id}`);
583
1029
  if (fill.gradient) defs.push(gradientMarkup(fill.gradient));
584
1030
  if (stroke.gradient) defs.push(gradientMarkup(stroke.gradient));
585
- let attrs = `${common(node)} d="${vectorPathData(node, precision)}" fill="${escapeAttr(fill.value)}"`;
586
- if (node.fillRule === "evenodd") attrs += ` fill-rule="evenodd"`;
587
- if (stroke.value !== "none") {
588
- attrs += ` stroke="${escapeAttr(stroke.value)}" stroke-width="${num(node.strokeWidth ?? 1)}"`;
589
- if (node.strokeLinecap && node.strokeLinecap !== "butt") attrs += ` stroke-linecap="${node.strokeLinecap}"`;
590
- if (node.strokeLinejoin && node.strokeLinejoin !== "miter") attrs += ` stroke-linejoin="${node.strokeLinejoin}"`;
591
- if (node.strokeDasharray?.length) attrs += ` stroke-dasharray="${node.strokeDasharray.map(num).join(" ")}"`;
592
- }
593
- lines.push(`${indent(depth)}<path${attrs}/>`);
1031
+ const d = vectorPathData(node, precision);
1032
+ const corners2 = vectorCornersAttribute(node.subpaths, precision);
1033
+ const live = corners2 ? ` data-vector-d="${bezierPathToSvg(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 (node.strokeDasharray?.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(node.strokeWidth ?? 1) : ""}${live}/>`);
1045
+ return;
1046
+ }
1047
+ let data = ` data-vector-path="" data-vector-stroke-width="${num(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 = bezierPathToSvg(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>`);
594
1078
  };
595
1079
  for (const node of doc.nodes) emit(node, 1);
596
1080
  const w = num(doc.width);
@@ -765,11 +1249,11 @@ function parseVectorSvg(svg, options = {}) {
765
1249
  const rawGradients = /* @__PURE__ */ new Map();
766
1250
  collectGradients(root, rawGradients);
767
1251
  const paint = (value) => {
768
- const v = value.trim();
769
- const url = /^url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/.exec(v);
1252
+ const v2 = value.trim();
1253
+ const url = /^url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/.exec(v2);
770
1254
  if (url) return resolveGradient(url[1], rawGradients) ?? null;
771
- if (v === "none" || v === "") return null;
772
- return v;
1255
+ if (v2 === "none" || v2 === "") return null;
1256
+ return v2;
773
1257
  };
774
1258
  const viewBox = numberList(root.attrs.viewBox);
775
1259
  const width = viewBox.length === 4 ? viewBox[2] : length(root.attrs.width, 300);
@@ -779,8 +1263,13 @@ function parseVectorSvg(svg, options = {}) {
779
1263
  const shapeOf = (el) => {
780
1264
  const a = el.attrs;
781
1265
  switch (el.name) {
782
- 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
+ }
783
1271
  return a.d ? bezierToSubpaths(a.d) : null;
1272
+ }
784
1273
  case "rect": {
785
1274
  const w = length(a.width);
786
1275
  const h = length(a.height);
@@ -814,6 +1303,56 @@ function parseVectorSvg(svg, options = {}) {
814
1303
  }
815
1304
  return null;
816
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 = (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 || !subpaths?.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
+ };
817
1356
  const visit = (el, style, matrix) => {
818
1357
  const out = [];
819
1358
  for (const child of el.children) {
@@ -827,6 +1366,11 @@ function parseVectorSvg(svg, options = {}) {
827
1366
  ...opacity !== void 0 && Number.isFinite(opacity) && opacity < 1 ? { opacity } : {},
828
1367
  ...hidden ? { visible: false } : {}
829
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
+ }
830
1374
  if (child.name === "g" || child.name === "svg" || child.name === "a" || child.name === "switch") {
831
1375
  const children = visit(child, s, m);
832
1376
  if (!children.length) continue;
@@ -836,24 +1380,18 @@ function parseVectorSvg(svg, options = {}) {
836
1380
  }
837
1381
  const subpaths = shapeOf(child);
838
1382
  if (!subpaths || !subpaths.length) continue;
839
- let node = {
840
- id: idFor(child),
841
- type: "path",
842
- ...meta,
843
- subpaths,
844
- fill: child.name === "line" ? null : paint(s.fill),
845
- stroke: paint(s.stroke),
846
- ...s.strokeWidth ? { strokeWidth: length(s.strokeWidth, 1) } : {},
847
- ...s.strokeLinecap ? { strokeLinecap: s.strokeLinecap } : {},
848
- ...s.strokeLinejoin ? { strokeLinejoin: s.strokeLinejoin } : {},
849
- ...s.strokeDasharray && s.strokeDasharray !== "none" ? { strokeDasharray: numberList(s.strokeDasharray) } : {},
850
- ...s.fillRule === "evenodd" ? { fillRule: "evenodd" } : {}
851
- };
852
- if (m !== IDENTITY_MATRIX) {
853
- node = transformVectorNode(node, m);
854
- const strokeScale = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
855
- if (hasPaint(node.stroke) && strokeScale > 0 && Math.abs(strokeScale - 1) > 1e-9) node.strokeWidth = (node.strokeWidth ?? 1) * strokeScale;
856
- }
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
+ );
857
1395
  out.push(node);
858
1396
  }
859
1397
  return out;
@@ -876,9 +1414,9 @@ function constrainToLeave(leave, p) {
876
1414
  return { x: leave.at.x + Math.sign(dx || 1) * s, y: leave.at.y + Math.sign(dy || 1) * s };
877
1415
  }
878
1416
  default: {
879
- const len = Math.hypot(dx, dy);
1417
+ const len2 = Math.hypot(dx, dy);
880
1418
  const a = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);
881
- return { x: leave.at.x + Math.cos(a) * len, y: leave.at.y + Math.sin(a) * len };
1419
+ return { x: leave.at.x + Math.cos(a) * len2, y: leave.at.y + Math.sin(a) * len2 };
882
1420
  }
883
1421
  }
884
1422
  }
@@ -887,24 +1425,24 @@ var CROSSING_PAIRS = 32;
887
1425
  var CURVE_SAMPLES = 48;
888
1426
  var BISECT = 50;
889
1427
  var EQUAL = 1e-6;
890
- function bound(sorted, key, v, strict) {
1428
+ function bound(sorted, key, v2, strict) {
891
1429
  let lo = 0;
892
1430
  let hi = sorted.length;
893
1431
  while (lo < hi) {
894
1432
  const mid = lo + hi >>> 1;
895
1433
  const k = key(sorted[mid]);
896
- if (strict ? k <= v : k < v) lo = mid + 1;
1434
+ if (strict ? k <= v2 : k < v2) lo = mid + 1;
897
1435
  else hi = mid;
898
1436
  }
899
1437
  return lo;
900
1438
  }
901
- function within(sorted, key, v, tol) {
902
- return sorted.slice(bound(sorted, key, v - tol, false), bound(sorted, key, v + tol, true));
1439
+ function within(sorted, key, v2, tol) {
1440
+ return sorted.slice(bound(sorted, key, v2 - tol, false), bound(sorted, key, v2 + tol, true));
903
1441
  }
904
- var linesNear = (lines, v, tol) => within(lines, (l) => l.v, v, tol);
1442
+ var linesNear = (lines, v2, tol) => within(lines, (l) => l.v, v2, tol);
905
1443
  var distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
906
1444
  var toPoint = (p) => [p.x, p.y];
907
- var toVec = (p) => ({ x: p[0], y: p[1] });
1445
+ var toVec2 = (p) => ({ x: p[0], y: p[1] });
908
1446
  var LABELS = {
909
1447
  anchor: "anchor",
910
1448
  centre: "centre",
@@ -1022,7 +1560,7 @@ var VectorSnapTargets = class _VectorSnapTargets {
1022
1560
  const count = subpathSegmentCount(sp);
1023
1561
  for (let i = 0; i < count && budget > 0; i++) {
1024
1562
  if (moving(i) || moving((i + 1) % n)) continue;
1025
- const seg = subpathSegment(sp, i).map((q) => toPoint(applyMatrix(m, toVec(q))));
1563
+ const seg = subpathSegment(sp, i).map((q) => toPoint(applyMatrix(m, toVec2(q))));
1026
1564
  let minX = Infinity;
1027
1565
  let minY = Infinity;
1028
1566
  let maxX = -Infinity;
@@ -1166,9 +1704,9 @@ var VectorSnapTargets = class _VectorSnapTargets {
1166
1704
  return { point: c.point, kind: "construction", x: true, y: true, lines: [line("construction", leave.at, c.point)], labels: [] };
1167
1705
  }
1168
1706
  }
1169
- const nearest = (lines2, v) => {
1707
+ const nearest = (lines2, v2) => {
1170
1708
  let best = null;
1171
- for (const l of linesNear(lines2, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.v - v)) best = l;
1709
+ for (const l of linesNear(lines2, v2, tol)) if (!best || Math.abs(l.v - v2) < Math.abs(best.v - v2)) best = l;
1172
1710
  return best;
1173
1711
  };
1174
1712
  const bx = nearest(this.xs, p.x);
@@ -1228,16 +1766,16 @@ var VectorSnapTargets = class _VectorSnapTargets {
1228
1766
  /** Target segments whose control box reaches `p` and which pass within `tol` of it, nearest first. */
1229
1767
  segmentsNear(p, tol) {
1230
1768
  const out = [];
1231
- const pt = toPoint(p);
1769
+ const pt2 = toPoint(p);
1232
1770
  for (const s of this.segments) {
1233
1771
  if (p.x < s.minX - tol || p.x > s.maxX + tol || p.y < s.minY - tol || p.y > s.maxY + tol) continue;
1234
- const hit = segmentNearest(s.seg, pt);
1772
+ const hit = segmentNearest(s.seg, pt2);
1235
1773
  if (hit.distance <= tol) out.push({ seg: s.seg, t: hit.t, distance: hit.distance });
1236
1774
  }
1237
1775
  return out.sort((a, b) => a.distance - b.distance);
1238
1776
  }
1239
1777
  onSegment(near2) {
1240
- return near2.length ? toVec(segmentPoint(near2[0].seg, near2[0].t)) : null;
1778
+ return near2.length ? toVec2(segmentPoint(near2[0].seg, near2[0].t)) : null;
1241
1779
  }
1242
1780
  /** Where two of the target segments near `p` cross, the crossing nearest `p` within `tol`. */
1243
1781
  crossing(near2, p, tol) {
@@ -1247,7 +1785,7 @@ var VectorSnapTargets = class _VectorSnapTargets {
1247
1785
  for (let i = 0; i < list.length; i++) {
1248
1786
  for (let j = i + 1; j < list.length; j++) {
1249
1787
  for (const hit of segmentIntersections(list[i].seg, list[j].seg)) {
1250
- const q = toVec(hit.point);
1788
+ const q = toVec2(hit.point);
1251
1789
  const d = distance(q, p);
1252
1790
  if (d <= tol && d < bestD) {
1253
1791
  best = q;
@@ -1288,7 +1826,7 @@ var VectorSnapTargets = class _VectorSnapTargets {
1288
1826
  const axes = { x: options.axes?.x !== false, y: options.axes?.y !== false };
1289
1827
  const nearest = (lines, values) => {
1290
1828
  let best = null;
1291
- for (const v of values) for (const l of linesNear(lines, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.d)) best = { d: l.v - v, from: l.from };
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 };
1292
1830
  return best;
1293
1831
  };
1294
1832
  const bx = axes.x ? nearest(this.xs, [r.x, r.x + r.width / 2, r.x + r.width]) : null;
@@ -1331,7 +1869,7 @@ var VectorSnapTargets = class _VectorSnapTargets {
1331
1869
  evenSpacing(r, horizontal, tol) {
1332
1870
  const span = (b, x) => x ? [b.x, b.x + b.width] : [b.y, b.y + b.height];
1333
1871
  const [r0, r1] = span(r, horizontal);
1334
- const len = r1 - r0;
1872
+ const len2 = r1 - r0;
1335
1873
  const [c0, c1] = span(r, !horizontal);
1336
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);
1337
1875
  if (!row.length) return null;
@@ -1351,17 +1889,17 @@ var VectorSnapTargets = class _VectorSnapTargets {
1351
1889
  if (e.a0 >= r1 - tol && (!after || e.a0 < after.a0)) after = e;
1352
1890
  }
1353
1891
  const starts = [];
1354
- if (before && after && after.a0 - before.a1 > len) starts.push({ start: (before.a1 + after.a0 - len) / 2, repeats: null });
1892
+ if (before && after && after.a0 - before.a1 > len2) starts.push({ start: (before.a1 + after.a0 - len2) / 2, repeats: null });
1355
1893
  for (const { g } of pairs) {
1356
1894
  if (before) starts.push({ start: before.a1 + g, repeats: g });
1357
- if (after) starts.push({ start: after.a0 - g - len, repeats: g });
1895
+ if (after) starts.push({ start: after.a0 - g - len2, repeats: g });
1358
1896
  }
1359
1897
  let pick = null;
1360
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;
1361
1899
  if (!pick) return null;
1362
1900
  const { start, repeats } = pick;
1363
1901
  const at = horizontal ? { ...r, x: start } : { ...r, y: start };
1364
- const end = start + len;
1902
+ const end = start + len2;
1365
1903
  const gaps = [];
1366
1904
  if (before && (repeats === null || Math.abs(start - before.a1 - repeats) <= EQUAL)) gaps.push([before.a1, start, mid(before.b, at)]);
1367
1905
  if (after && (repeats === null || Math.abs(after.a0 - end - repeats) <= EQUAL)) gaps.push([end, after.a0, mid(after.b, at)]);
@@ -1372,9 +1910,9 @@ var VectorSnapTargets = class _VectorSnapTargets {
1372
1910
  }
1373
1911
  /** A gap mark: a line across the gap with a tick at each end, and its length as a label. */
1374
1912
  gapMarks([u0, u1, c], horizontal, tick, out) {
1375
- const pt = (u, v) => horizontal ? { x: u, y: v } : { x: v, y: u };
1376
- out.lines.push(line("spacing", pt(u0, c), pt(u1, c)), line("spacing", pt(u0, c - tick), pt(u0, c + tick)), line("spacing", pt(u1, c - tick), pt(u1, c + tick)));
1377
- out.labels.push(this.label(pt((u0 + u1) / 2, c), "spacing", fmtLength(u1 - u0)));
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)));
1378
1916
  }
1379
1917
  /** The other objects' side length nearest `length`, within `tol`, or null. */
1380
1918
  matchSize(length2, tol) {
@@ -1401,9 +1939,9 @@ var VectorSnapTargets = class _VectorSnapTargets {
1401
1939
  const hp = handlePos(handle, frame);
1402
1940
  const d0 = { x: hp.x - origin.x, y: hp.y - origin.y };
1403
1941
  const valid = (n, s) => Number.isFinite(n) && n * s > 0;
1404
- const nearest = (v, lines) => {
1942
+ const nearest = (v2, lines) => {
1405
1943
  let best = null;
1406
- for (const l of linesNear(lines, v, tol)) if (!best || Math.abs(l.v - v) < Math.abs(best.v - v)) best = l;
1944
+ for (const l of linesNear(lines, v2, tol)) if (!best || Math.abs(l.v - v2) < Math.abs(best.v - v2)) best = l;
1407
1945
  return best;
1408
1946
  };
1409
1947
  let nx = sx;
@@ -1454,9 +1992,9 @@ var VectorSnapTargets = class _VectorSnapTargets {
1454
1992
  };
1455
1993
  for (const h of [handle, ...ends]) {
1456
1994
  const hv = handlePos(h, frame);
1457
- const v = { x: hv.x - origin.x, y: hv.y - origin.y };
1458
- const base = applyMatrix(toDoc, { x: origin.x + c.x * v.x, y: origin.y + c.y * v.y });
1459
- const dir = applyMatrix(toDoc, { x: e.x * v.x, y: e.y * v.y });
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 });
1460
1998
  const p = { x: base.x + dir.x * s0, y: base.y + dir.y * s0 };
1461
1999
  const l2 = dir.x * dir.x + dir.y * dir.y;
1462
2000
  const on = l2 > 1e-18 ? this.nearestPoint(p, tol) : null;
@@ -1489,16 +2027,16 @@ var VectorSnapTargets = class _VectorSnapTargets {
1489
2027
  const sized = [];
1490
2028
  if (!proportional) {
1491
2029
  if (movesX && !snap.hx && !(frame.angle && snap.hy) && frame.width > 1e-9) {
1492
- const len = this.matchSize(Math.abs(out.sx) * frame.width, tol);
1493
- if (len !== null && len > 0) {
1494
- out.sx = Math.sign(out.sx || 1) * (len / frame.width);
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);
1495
2033
  sized.push("x");
1496
2034
  }
1497
2035
  }
1498
2036
  if (movesY && !snap.hy && !(frame.angle && snap.hx) && frame.height > 1e-9) {
1499
- const len = this.matchSize(Math.abs(out.sy) * frame.height, tol);
1500
- if (len !== null && len > 0) {
1501
- out.sy = Math.sign(out.sy || 1) * (len / frame.height);
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);
1502
2040
  sized.push("y");
1503
2041
  }
1504
2042
  }
@@ -1549,14 +2087,14 @@ function line(kind, a, b) {
1549
2087
  return { kind, x1: a.x, y1: a.y, x2: b.x, y2: b.y };
1550
2088
  }
1551
2089
  function construction(leave, p, tol) {
1552
- const v = { x: p.x - leave.at.x, y: p.y - leave.at.y };
1553
- if (Math.hypot(v.x, v.y) <= tol) return null;
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;
1554
2092
  let best = null;
1555
2093
  for (const a of leave.angles ?? []) {
1556
2094
  const r = a * Math.PI / 180;
1557
2095
  const exact = (n) => Math.abs(n) < 1e-12 ? 0 : n;
1558
2096
  const dir = { x: exact(Math.cos(r)), y: exact(-Math.sin(r)) };
1559
- const k = v.x * dir.x + v.y * dir.y;
2097
+ const k = v2.x * dir.x + v2.y * dir.y;
1560
2098
  const point = { x: leave.at.x + dir.x * k, y: leave.at.y + dir.y * k };
1561
2099
  const d = distance(point, p);
1562
2100
  if (d <= tol && (!best || d < best.d)) best = { dir, point, d };
@@ -1581,7 +2119,7 @@ function solveOnSegment(seg, base, cond) {
1581
2119
  vals.push(cond(r, d));
1582
2120
  scale = Math.max(scale, Math.hypot(r[0], r[1]) * Math.hypot(d[0], d[1]));
1583
2121
  }
1584
- if (!(scale > 0) || vals.every((v) => Math.abs(v) <= scale * 1e-12)) return [];
2122
+ if (!(scale > 0) || vals.every((v2) => Math.abs(v2) <= scale * 1e-12)) return [];
1585
2123
  const out = [];
1586
2124
  for (let i = 0; i < CURVE_SAMPLES; i++) {
1587
2125
  const ga = vals[i];
@@ -1614,8 +2152,8 @@ function solveOnSegment(seg, base, cond) {
1614
2152
  }
1615
2153
 
1616
2154
  // src/vector/pathEdit.ts
1617
- var EPS2 = 1e-9;
1618
- var unit = (x, y) => {
2155
+ var EPS3 = 1e-9;
2156
+ var unit2 = (x, y) => {
1619
2157
  const l = Math.hypot(x, y);
1620
2158
  return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
1621
2159
  };
@@ -1628,14 +2166,14 @@ function cubicOf(sp, i) {
1628
2166
  var isLine = (sp, i) => subpathSegment(sp, i).length === 2;
1629
2167
  function startTangent(c) {
1630
2168
  for (let k = 1; k < 4; k++) {
1631
- const u = unit(c[k][0] - c[0][0], c[k][1] - c[0][1]);
2169
+ const u = unit2(c[k][0] - c[0][0], c[k][1] - c[0][1]);
1632
2170
  if (u) return u;
1633
2171
  }
1634
2172
  return [1, 0];
1635
2173
  }
1636
2174
  function endTangent(c) {
1637
2175
  for (let k = 2; k >= 0; k--) {
1638
- const u = unit(c[3][0] - c[k][0], c[3][1] - c[k][1]);
2176
+ const u = unit2(c[3][0] - c[k][0], c[3][1] - c[k][1]);
1639
2177
  if (u) return u;
1640
2178
  }
1641
2179
  return [1, 0];
@@ -1643,11 +2181,11 @@ function endTangent(c) {
1643
2181
  function sample(c, n, out, withStart) {
1644
2182
  for (let i = withStart ? 0 : 1; i <= n; i++) out.push(segmentPoint(c, i / n));
1645
2183
  }
1646
- var onAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS2 && Math.abs(h.y - a.y) < EPS2;
2184
+ var onAnchor2 = (a, h) => !h || Math.abs(h.x - a.x) < EPS3 && Math.abs(h.y - a.y) < EPS3;
1647
2185
  function tidy(a, keepType = true) {
1648
2186
  const next = cloneAnchor(a);
1649
- if (onAnchor(next, next.in)) delete next.in;
1650
- if (onAnchor(next, next.out)) delete next.out;
2187
+ if (onAnchor2(next, next.in)) delete next.in;
2188
+ if (onAnchor2(next, next.out)) delete next.out;
1651
2189
  const shown = inferAnchorType(next);
1652
2190
  const type = keepType && a.type ? a.type === "symmetric" && shown !== "symmetric" ? shown === "corner" ? "corner" : "smooth" : a.type === "smooth" && shown === "corner" ? "corner" : a.type : shown;
1653
2191
  if (type === "corner") delete next.type;
@@ -1755,8 +2293,8 @@ function curvatureMoveAnchor(sp, index, q) {
1755
2293
  const a = sp.anchors[index];
1756
2294
  if (!a) return { anchors, closed: sp.closed };
1757
2295
  const own = [
1758
- a.in && !onAnchor(a, a.in) ? { x: a.in.x - a.x, y: a.in.y - a.y } : null,
1759
- a.out && !onAnchor(a, a.out) ? { x: a.out.x - a.x, y: a.out.y - a.y } : null
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
1760
2298
  ];
1761
2299
  const ends = [null, null];
1762
2300
  neighbours(sp, index).forEach((j, side) => {
@@ -1775,15 +2313,16 @@ function curvatureMoveAnchor(sp, index, q) {
1775
2313
  if (own[0]) moved.in = { x: q.x + own[0].x, y: q.y + own[0].y };
1776
2314
  if (own[1]) moved.out = { x: q.x + own[1].x, y: q.y + own[1].y };
1777
2315
  if (a.type) moved.type = a.type;
2316
+ if (a.corner) moved.corner = { ...a.corner };
1778
2317
  const [prev, next] = ends;
1779
2318
  if (a.type && a.type !== "corner" && prev && next) {
1780
2319
  const dx = next.x - prev.x;
1781
2320
  const dy = next.y - prev.y;
1782
2321
  const l = Math.hypot(dx, dy);
1783
2322
  if (l > 1e-9) {
1784
- const len = (h, to) => h ? Math.hypot(h.x, h.y) : Math.hypot(q.x - to.x, q.y - to.y) / 3;
1785
- let li = len(own[0], prev);
1786
- let lo = len(own[1], next);
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);
1787
2326
  if (a.type === "symmetric") li = lo = (li + lo) / 2;
1788
2327
  moved.in = { x: q.x - dx * li / l, y: q.y - dy * li / l };
1789
2328
  moved.out = { x: q.x + dx * lo / l, y: q.y + dy * lo / l };
@@ -1824,7 +2363,7 @@ function splitSubpathAt(sp, segment, t, endTolerance = 1e-6) {
1824
2363
  const { subpath, index } = insertAnchor(sp, segment, t);
1825
2364
  return cutSubpathAt(subpath, [index]);
1826
2365
  }
1827
- var endOf = (sp, last) => sp.anchors[last ? sp.anchors.length - 1 : 0];
2366
+ var endOf2 = (sp, last) => sp.anchors[last ? sp.anchors.length - 1 : 0];
1828
2367
  var dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
1829
2368
  function reverse(sp) {
1830
2369
  return {
@@ -1834,6 +2373,7 @@ function reverse(sp) {
1834
2373
  if (a.out) next.in = { ...a.out };
1835
2374
  if (a.in) next.out = { ...a.in };
1836
2375
  if (a.type) next.type = a.type;
2376
+ if (a.corner) next.corner = { ...a.corner };
1837
2377
  return next;
1838
2378
  })
1839
2379
  };
@@ -1867,7 +2407,7 @@ function joinSubpaths(subpaths, tolerance) {
1867
2407
  for (let j = i + 1; j < open.length; j++) {
1868
2408
  for (const ei of [false, true]) {
1869
2409
  for (const ej of [false, true]) {
1870
- const d = dist(endOf(open[i], ei), endOf(open[j], ej));
2410
+ const d = dist(endOf2(open[i], ei), endOf2(open[j], ej));
1871
2411
  if (d < best.d) best = { d, i, j, ei, ej };
1872
2412
  }
1873
2413
  }
@@ -1905,7 +2445,7 @@ function smoothSubpath(sp, amount = 1, only) {
1905
2445
  const prev = orig[(i + n - 1) % n];
1906
2446
  const next = orig[(i + 1) % n];
1907
2447
  const p = orig[i];
1908
- const dir = unit(next.x - prev.x, next.y - prev.y);
2448
+ const dir = unit2(next.x - prev.x, next.y - prev.y);
1909
2449
  if (!dir) continue;
1910
2450
  const li = dist(p, prev) / 3;
1911
2451
  const lo = dist(p, next) / 3;
@@ -1962,12 +2502,12 @@ function mergePair(a, b, tol) {
1962
2502
  if (a.line && b.line) {
1963
2503
  const cx = b3[0] - a0[0];
1964
2504
  const cy = b3[1] - a0[1];
1965
- const len = Math.hypot(cx, cy);
1966
- if (len < 1e-12) return null;
2505
+ const len2 = Math.hypot(cx, cy);
2506
+ if (len2 < 1e-12) return null;
1967
2507
  const mx = a3[0] - a0[0];
1968
2508
  const my = a3[1] - a0[1];
1969
- const along = (mx * cx + my * cy) / len;
1970
- return Math.abs((mx * cy - my * cx) / len) <= tol && along >= 0 && along <= len ? { c: [a0, b3], line: true } : null;
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;
1971
2511
  }
1972
2512
  const ca = a.line ? [a0, a0, a3, a3] : a.c;
1973
2513
  const cb = b.line ? [a3, a3, b3, b3] : b.c;
@@ -2091,7 +2631,7 @@ function lineHits(p, q, seg) {
2091
2631
  const dy = q[1] - p[1];
2092
2632
  const len2 = dx * dx + dy * dy;
2093
2633
  if (len2 < 1e-24) return [];
2094
- const h = (pt) => (pt[0] - p[0]) * -dy + (pt[1] - p[1]) * dx;
2634
+ const h = (pt2) => (pt2[0] - p[0]) * -dy + (pt2[1] - p[1]) * dx;
2095
2635
  let coeffs;
2096
2636
  if (seg.length === 2) coeffs = [0, 0, h(seg[1]) - h(seg[0]), h(seg[0])];
2097
2637
  else {
@@ -2099,7 +2639,7 @@ function lineHits(p, q, seg) {
2099
2639
  coeffs = [-h0 + 3 * h1 - 3 * h2 + h3, 3 * h0 - 6 * h1 + 3 * h2, -3 * h0 + 3 * h1, h0];
2100
2640
  }
2101
2641
  if (coeffs.every((c) => Math.abs(c) < 1e-12 * Math.max(1, len2))) return [];
2102
- const scale = Math.sqrt(len2) * Math.max(1, ...seg.map((pt) => Math.abs(pt[0]) + Math.abs(pt[1])));
2642
+ const scale = Math.sqrt(len2) * Math.max(1, ...seg.map((pt2) => Math.abs(pt2[0]) + Math.abs(pt2[1])));
2103
2643
  const out = [];
2104
2644
  for (const t of rootsInUnit(...coeffs, scale)) {
2105
2645
  const point = segmentPoint(seg, t);
@@ -2326,12 +2866,12 @@ function simplifyPolyline(points, tolerance) {
2326
2866
  const [ax, ay] = points[a];
2327
2867
  const dx = points[b][0] - ax;
2328
2868
  const dy = points[b][1] - ay;
2329
- const len = Math.hypot(dx, dy);
2869
+ const len2 = Math.hypot(dx, dy);
2330
2870
  let far = -1;
2331
2871
  let farD = 0;
2332
2872
  for (let i = a + 1; i < b; i++) {
2333
2873
  const [px, py] = points[i];
2334
- const d = len < 1e-12 ? Math.hypot(px - ax, py - ay) : Math.abs((px - ax) * dy - (py - ay) * dx) / len;
2874
+ const d = len2 < 1e-12 ? Math.hypot(px - ax, py - ay) : Math.abs((px - ax) * dy - (py - ay) * dx) / len2;
2335
2875
  if (d > farD) {
2336
2876
  farD = d;
2337
2877
  far = i;
@@ -2443,11 +2983,11 @@ function snapSquare(x, y, w, h) {
2443
2983
  }
2444
2984
  function turning(a, b, c) {
2445
2985
  const u = [b[0] - a[0], b[1] - a[1]];
2446
- const v = [c[0] - b[0], c[1] - b[1]];
2986
+ const v2 = [c[0] - b[0], c[1] - b[1]];
2447
2987
  const lu = Math.hypot(u[0], u[1]);
2448
- const lv = Math.hypot(v[0], v[1]);
2988
+ const lv = Math.hypot(v2[0], v2[1]);
2449
2989
  if (lu <= 0 || lv <= 0) return 0;
2450
- return Math.acos(Math.min(1, Math.max(-1, (u[0] * v[0] + u[1] * v[1]) / (lu * lv)))) * 180 / Math.PI;
2990
+ return Math.acos(Math.min(1, Math.max(-1, (u[0] * v2[0] + u[1] * v2[1]) / (lu * lv)))) * 180 / Math.PI;
2451
2991
  }
2452
2992
  function rdp(p, eps) {
2453
2993
  const n = p.length;
@@ -2575,6 +3115,21 @@ function recognizeInkShape(points, options = {}) {
2575
3115
  var DEFAULT_VECTOR_SNAP = { grid: 0, objects: true, guides: true, threshold: 5, angles: VECTOR_CONSTRUCTION_ANGLES };
2576
3116
  var DEFAULT_VECTOR_PATH_EDIT = { keepShapeOnDelete: true, curvatureMoves: false, pencilSmoothing: 0, recognizeShapes: false, eraserSize: 10 };
2577
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
+ ];
2578
3133
  var VECTOR_CLIPBOARD_TYPE = "pixerate/vector";
2579
3134
  var DEFAULT_DEFAULTS = {
2580
3135
  fill: "#d4d4d8",
@@ -2651,6 +3206,29 @@ function getVisibleHandles(state) {
2651
3206
  }
2652
3207
  return out;
2653
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 = 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 = a.corner?.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: a.corner?.kind ?? "round", corner: c });
3228
+ }
3229
+ }
3230
+ return out;
3231
+ }
2654
3232
  function getAnchor(doc, ref) {
2655
3233
  const node = findVectorNode(doc.nodes, ref.nodeId);
2656
3234
  return node?.type === "path" ? node.subpaths[ref.subpath]?.anchors[ref.index] : void 0;
@@ -2948,7 +3526,7 @@ var VectorEditorController = class {
2948
3526
  if (h?.transformHandle) return vectorResizeCursor(h.transformHandle, this.frameOf(this.selectedRoots())?.angle ?? 0);
2949
3527
  return h?.nodeId ? "move" : "default";
2950
3528
  case "direct":
2951
- if (h?.anchor || h?.handle) return "pointer";
3529
+ if (h?.anchor || h?.handle || h?.corner) return "pointer";
2952
3530
  if (h?.segment) return "copy";
2953
3531
  return h?.nodeId ? "move" : "default";
2954
3532
  case "pen":
@@ -3033,12 +3611,12 @@ var VectorEditorController = class {
3033
3611
  // Viewport
3034
3612
  // -------------------------------------------------------------------------
3035
3613
  screenToDocument(p) {
3036
- const v = this.viewport;
3037
- return { x: (p.x - v.x) / v.zoom, y: (p.y - v.y) / v.zoom };
3614
+ const v2 = this.viewport;
3615
+ return { x: (p.x - v2.x) / v2.zoom, y: (p.y - v2.y) / v2.zoom };
3038
3616
  }
3039
3617
  documentToScreen(p) {
3040
- const v = this.viewport;
3041
- return { x: p.x * v.zoom + v.x, y: p.y * v.zoom + v.y };
3618
+ const v2 = this.viewport;
3619
+ return { x: p.x * v2.zoom + v2.x, y: p.y * v2.zoom + v2.y };
3042
3620
  }
3043
3621
  setViewport(viewport) {
3044
3622
  const next = { ...this.viewport, ...viewport };
@@ -3105,9 +3683,9 @@ var VectorEditorController = class {
3105
3683
  toggleSnapping(on = !this.snap.objects) {
3106
3684
  this.setSnap({ objects: on });
3107
3685
  }
3108
- gridValue(v) {
3686
+ gridValue(v2) {
3109
3687
  const g = this.snap.grid;
3110
- return g > 0 ? Math.round(v / g) * g : v;
3688
+ return g > 0 ? Math.round(v2 / g) * g : v2;
3111
3689
  }
3112
3690
  snapPoint(p) {
3113
3691
  return { x: this.gridValue(p.x), y: this.gridValue(p.y) };
@@ -3263,7 +3841,7 @@ var VectorEditorController = class {
3263
3841
  let subpath;
3264
3842
  switch (input.kind) {
3265
3843
  case "rect":
3266
- subpath = rectSubpath(b, input.cornerRadius ?? d.cornerRadius);
3844
+ subpath = rectSubpath(b);
3267
3845
  break;
3268
3846
  case "ellipse":
3269
3847
  subpath = ellipseSubpath(b);
@@ -3278,10 +3856,13 @@ var VectorEditorController = class {
3278
3856
  subpath = lineSubpath({ x: b.x, y: b.y }, { x: b.x + b.width, y: b.y + b.height });
3279
3857
  break;
3280
3858
  }
3859
+ const radius = input.cornerRadius ?? d.cornerRadius;
3860
+ const kind = input.cornerKind ?? d.cornerKind;
3861
+ if ((input.kind === "rect" || input.kind === "polygon" || input.kind === "star") && radius > 0) subpath = setSubpathCorners(subpath, radius, kind);
3281
3862
  const names = { rect: "Rectangle", ellipse: "Ellipse", polygon: "Polygon", star: "Star", line: "Line" };
3282
3863
  const style = this.styleFor(input.kind === "line");
3283
3864
  const own = {};
3284
- for (const key of ["fill", "stroke", "strokeWidth", "strokeLinecap", "strokeLinejoin", "strokeDasharray", "fillRule"]) {
3865
+ for (const key of VECTOR_STYLE_KEYS) {
3285
3866
  if (input[key] !== void 0) own[key] = input[key];
3286
3867
  }
3287
3868
  return { id: this.newId(), type: "path", name: input.name ?? names[input.kind], ...style, ...own, subpaths: [subpath] };
@@ -3304,6 +3885,8 @@ var VectorEditorController = class {
3304
3885
  if (d.strokeLinejoin) out.strokeLinejoin = d.strokeLinejoin;
3305
3886
  if (d.strokeDasharray) out.strokeDasharray = d.strokeDasharray;
3306
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];
3307
3890
  return out;
3308
3891
  }
3309
3892
  /** Merges `patch` into a node (paths take style and geometry; groups name / visibility / opacity). */
@@ -3328,6 +3911,95 @@ var VectorEditorController = class {
3328
3911
  });
3329
3912
  return { ok: true };
3330
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, a.corner?.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 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 = 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) && (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: `${p.name ?? "Path"} Stroke`, subpaths: outline, fill: 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
+ }
3331
4003
  rename(id, name) {
3332
4004
  return this.updateNode(id, { name }, { label: "Rename" });
3333
4005
  }
@@ -3930,7 +4602,7 @@ var VectorEditorController = class {
3930
4602
  const map = (p) => applyMatrix(m, p);
3931
4603
  return from.subpaths.map((sp) => ({
3932
4604
  closed: sp.closed,
3933
- anchors: sp.anchors.map((a) => ({ ...map(a), ...a.in ? { in: map(a.in) } : {}, ...a.out ? { out: map(a.out) } : {}, ...a.type ? { type: a.type } : {} }))
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 } } : {} }))
3934
4606
  }));
3935
4607
  }
3936
4608
  /**
@@ -4285,6 +4957,22 @@ var VectorEditorController = class {
4285
4957
  }
4286
4958
  return null;
4287
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
+ }
4288
4976
  hitAnchor(screen, refs) {
4289
4977
  let best = null;
4290
4978
  let bestD = Infinity;
@@ -4343,6 +5031,8 @@ var VectorEditorController = class {
4343
5031
  if (handle) return { nodeId: handle.nodeId, handle };
4344
5032
  const anchor = this.hitAnchor(screen, getVisibleAnchors(this.view()));
4345
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 };
4346
5036
  for (const id of getEditablePathIds(this.view())) {
4347
5037
  const node = this.getNode(id);
4348
5038
  const seg = nearestSegment(node, this.toLocal(id, p));
@@ -4500,6 +5190,13 @@ var VectorEditorController = class {
4500
5190
  this.notify();
4501
5191
  return;
4502
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
+ }
4503
5200
  for (const id of getEditablePathIds(this.view())) {
4504
5201
  const node = this.getNode(id);
4505
5202
  const seg = nearestSegment(node, this.toLocal(id, p));
@@ -4557,10 +5254,10 @@ var VectorEditorController = class {
4557
5254
  }
4558
5255
  const last = getAnchorWorld(this.doc, { nodeId: pen.nodeId, subpath: pen.subpath, index: sp.anchors.length - 1 });
4559
5256
  const leave = { at: { x: last.x, y: last.y }, constrain: input.shift ? "angle" : null };
4560
- let pt2 = this.snapDrawn(p, leave, input, () => this.drawingTargets(pen.nodeId));
4561
- pt2 = this.toLocal(pen.nodeId, pt2);
5257
+ let pt3 = this.snapDrawn(p, leave, input, () => this.drawingTargets(pen.nodeId));
5258
+ pt3 = this.toLocal(pen.nodeId, pt3);
4562
5259
  const node2 = this.getNode(pen.nodeId);
4563
- const anchors = [...sp.anchors, { x: pt2.x, y: pt2.y }];
5260
+ const anchors = [...sp.anchors, { x: pt3.x, y: pt3.y }];
4564
5261
  this.replaceNode(pen.nodeId, { ...node2, subpaths: node2.subpaths.map((s, i) => i === pen.subpath ? { ...s, anchors } : s) });
4565
5262
  const ref2 = { nodeId: pen.nodeId, subpath: pen.subpath, index: anchors.length - 1 };
4566
5263
  this.anchors = [ref2];
@@ -4588,8 +5285,8 @@ var VectorEditorController = class {
4588
5285
  this.notify(this.doc !== before.doc);
4589
5286
  return;
4590
5287
  }
4591
- const pt = this.snapDrawn(p, null, input, () => this.drawingTargets(null));
4592
- const node = { id: this.newId(), type: "path", name: "Path", ...this.styleFor(true), subpaths: [{ anchors: [{ x: pt.x, y: pt.y }], closed: false }] };
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 }] };
4593
5290
  this.setDoc([...this.doc.nodes, node]);
4594
5291
  this.pen = { nodeId: node.id, subpath: 0 };
4595
5292
  this.selection = [node.id];
@@ -4737,13 +5434,25 @@ var VectorEditorController = class {
4737
5434
  case "handle": {
4738
5435
  const anchor = getAnchorWorld(drag.base, drag.ref);
4739
5436
  const leave = anchor ? { at: { x: anchor.x, y: anchor.y }, constrain: input.shift ? "angle" : null } : null;
4740
- const pt = this.snapDrawn(p, leave, input, () => drag.targets ??= VectorSnapTargets.forHandle(drag.base, drag.ref.nodeId));
4741
- const local = this.toLocal(drag.ref.nodeId, pt, drag.base);
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);
4742
5439
  this.doc = this.updateAnchors(drag.base, [drag.ref], (a) => moveAnchorHandle(a, drag.ref.handle, local, input.alt));
4743
5440
  this.interaction = { type: "handle", ref: drag.ref };
4744
5441
  this.notify(true);
4745
5442
  return;
4746
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
+ }
4747
5456
  case "bend": {
4748
5457
  if (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 2) return;
4749
5458
  drag.moved = true;
@@ -4769,10 +5478,10 @@ var VectorEditorController = class {
4769
5478
  return;
4770
5479
  }
4771
5480
  this.guides = null;
4772
- let pt = p;
4773
- if (input.shift) pt = constrain45(anchor, pt);
5481
+ let pt2 = p;
5482
+ if (input.shift) pt2 = constrain45(anchor, pt2);
4774
5483
  const side = drag.closing ? "in" : "out";
4775
- const pos = this.toLocal(drag.ref.nodeId, drag.closing ? { x: 2 * anchor.x - pt.x, y: 2 * anchor.y - pt.y } : pt, drag.base);
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);
4776
5485
  this.doc = this.updateAnchors(drag.base, [drag.ref], (a) => {
4777
5486
  if (drag.outOnly || drag.altBreaks && input.alt) return moveAnchorHandle(a, side, pos, true);
4778
5487
  return moveAnchorHandle({ ...a, type: "symmetric" }, side, pos);
@@ -4885,12 +5594,12 @@ var VectorEditorController = class {
4885
5594
  const guides = { lines: [], labels: [] };
4886
5595
  for (const axis of ["x", "y"]) {
4887
5596
  const d = end[axis] - start[axis];
4888
- const len = Math.abs(d) > 1e-9 ? targets.matchSize(Math.abs(d) * k, tol) : null;
4889
- if (len === null || len <= 0) continue;
4890
- out[axis] = start[axis] + Math.sign(d) * len / k;
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;
4891
5600
  const from = { ...out, [axis]: fromCentre ? 2 * start[axis] - out[axis] : start[axis] };
4892
5601
  guides.lines.push({ kind: "size", x1: from.x, y1: from.y, x2: out.x, y2: out.y });
4893
- guides.labels.push({ kind: "size", x: (from.x + out.x) / 2, y: (from.y + out.y) / 2, text: `${axis === "x" ? "W" : "H"} ${Math.round(len * 100) / 100}` });
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}` });
4894
5603
  }
4895
5604
  this.setGuides(guides);
4896
5605
  return out;
@@ -4935,6 +5644,20 @@ var VectorEditorController = class {
4935
5644
  return;
4936
5645
  }
4937
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, a.corner?.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
+ }
4938
5661
  case "stroke": {
4939
5662
  this.notify();
4940
5663
  if (drag.tool === "knife") this.knife(drag.points);
@@ -5179,9 +5902,9 @@ function inkShapeSubpath(shape) {
5179
5902
  function constrain45(origin, p) {
5180
5903
  const dx = p.x - origin.x;
5181
5904
  const dy = p.y - origin.y;
5182
- const len = Math.hypot(dx, dy);
5905
+ const len2 = Math.hypot(dx, dy);
5183
5906
  const a = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);
5184
- return { x: origin.x + Math.cos(a) * len, y: origin.y + Math.sin(a) * len };
5907
+ return { x: origin.x + Math.cos(a) * len2, y: origin.y + Math.sin(a) * len2 };
5185
5908
  }
5186
5909
  function scaleFor(handle, b, p, mods = {}) {
5187
5910
  const west = handle.includes("w");
@@ -5218,10 +5941,12 @@ function renderNode(node, defs, idPrefix) {
5218
5941
  const stroke = vectorPaintToSvg(node.stroke, `${idPrefix}stroke-${node.id}`);
5219
5942
  if (fill.gradient) defs.push(fill.gradient);
5220
5943
  if (stroke.gradient) defs.push(stroke.gradient);
5944
+ const strokeParts = stroke.value !== "none" ? vectorStrokeSvgParts(node, `${idPrefix}clip-${node.id}`) : void 0;
5221
5945
  return {
5222
5946
  kind: "path",
5223
5947
  id: node.id,
5224
5948
  ...node.transform ? { transform: fmtMatrix(node.transform) } : {},
5949
+ ...strokeParts ? { strokeParts } : {},
5225
5950
  d: vectorPathData(node),
5226
5951
  fill: fill.value,
5227
5952
  stroke: stroke.value,
@@ -5237,9 +5962,9 @@ function getVectorSceneRenderData(state, options = {}) {
5237
5962
  const defs = [];
5238
5963
  const prefix = options.idPrefix ?? "ve-";
5239
5964
  const nodes = state.document.nodes.map((n) => renderNode(n, defs, prefix)).filter((n) => !!n);
5240
- const v = state.viewport;
5965
+ const v2 = state.viewport;
5241
5966
  return {
5242
- transform: `translate(${fmt(v.x)} ${fmt(v.y)}) scale(${v.zoom})`,
5967
+ transform: `translate(${fmt(v2.x)} ${fmt(v2.y)}) scale(${v2.zoom})`,
5243
5968
  width: state.document.width,
5244
5969
  height: state.document.height,
5245
5970
  ...state.document.background ? { background: state.document.background } : {},
@@ -5249,8 +5974,8 @@ function getVectorSceneRenderData(state, options = {}) {
5249
5974
  }
5250
5975
  var toScreen = (state, p) => ({ x: p.x * state.viewport.zoom + state.viewport.x, y: p.y * state.viewport.zoom + state.viewport.y });
5251
5976
  function screenOutline(state, node) {
5252
- const v = state.viewport;
5253
- const view = [v.zoom, 0, 0, v.zoom, v.x, v.y];
5977
+ const v2 = state.viewport;
5978
+ const view = [v2.zoom, 0, 0, v2.zoom, v2.x, v2.y];
5254
5979
  const parent = vectorParentMatrix(state.document.nodes, node.id) ?? IDENTITY_MATRIX;
5255
5980
  return collectWithMatrix(node, parent).map(({ path, parent: m }) => bezierPathToSvg(transformBezierPath(vectorPathWorldBezier(path, m), view), { precision: 2 })).join(" ");
5256
5981
  }
@@ -5359,8 +6084,14 @@ function getVectorOverlayRenderData(state) {
5359
6084
  const b = toScreen(state, it.string[1]);
5360
6085
  pencilString = { x1: fmt(a.x), y1: fmt(a.y), x2: fmt(b.x), y2: fmt(b.y) };
5361
6086
  }
6087
+ const dragged = it?.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(state.hover?.corner, w.ref), active: !!dragged };
6091
+ });
6092
+ if (it?.type === "corner") status = `R ${fmt(it.radius)}`;
5362
6093
  const cutPoint = state.tool === "scissors" && state.hover?.segment && !it ? (({ x, y }) => ({ x: fmt(x), y: fmt(y) }))(toScreen(state, state.hover.segment.point)) : null;
5363
- return { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint };
6094
+ return { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint, cornerWidgets };
5364
6095
  }
5365
6096
  var VECTOR_OVERLAY_THEME = {
5366
6097
  accent: "#0ea5e9",
@@ -5372,7 +6103,9 @@ var VECTOR_OVERLAY_THEME = {
5372
6103
  eraserFill: "rgba(239, 68, 68, 0.25)",
5373
6104
  transformHandleSize: 8,
5374
6105
  anchorSize: 7,
5375
- handleRadius: 3.5
6106
+ handleRadius: 3.5,
6107
+ /** Radius of a live corner widget (a ring with a dot). */
6108
+ cornerWidgetRadius: 4
5376
6109
  };
5377
6110
  function toVectorModifiers(event) {
5378
6111
  return { shift: !!event.shiftKey, alt: !!event.altKey, mod: !!(event.metaKey || event.ctrlKey) };
@@ -5396,18 +6129,68 @@ function getVectorSelectionStyle(state) {
5396
6129
  const node = findVectorNode(state.document.nodes, id);
5397
6130
  if (node) paths.push(...collect(node));
5398
6131
  }
6132
+ const anchorMode = state.tool === "direct" && !!state.selectedAnchors?.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 (node?.type === "path") paths.push(node);
6138
+ }
6139
+ }
5399
6140
  if (!paths.length) return null;
5400
6141
  const first = paths[0];
5401
6142
  const same = (get) => paths.every((p) => JSON.stringify(get(p) ?? null) === JSON.stringify(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: corner?.radius ?? 0, kind: corner?.kind ?? "round" });
6149
+ }
6150
+ }
6151
+ const c0 = corners2[0] ?? { radius: 0, kind: "round" };
5402
6152
  return {
5403
6153
  fill: first.fill ?? null,
5404
6154
  stroke: first.stroke ?? null,
5405
6155
  strokeWidth: first.strokeWidth ?? 1,
5406
6156
  opacity: first.opacity ?? 1,
5407
- mixed: { fill: !same((p) => p.fill), stroke: !same((p) => p.stroke), strokeWidth: !same((p) => p.strokeWidth ?? 1), opacity: !same((p) => p.opacity ?? 1) },
6157
+ strokeAlign: first.strokeAlign ?? "center",
6158
+ startArrow: first.startArrow ?? null,
6159
+ endArrow: first.endArrow ?? null,
6160
+ arrowAlign: first.arrowAlign ?? "extend",
6161
+ startArrowScale: first.startArrowScale ?? 1,
6162
+ endArrowScale: 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) => p.strokeWidth ?? 1),
6171
+ opacity: !same((p) => p.opacity ?? 1),
6172
+ strokeAlign: !same((p) => p.strokeAlign ?? "center"),
6173
+ startArrow: !same((p) => p.startArrow ?? null),
6174
+ endArrow: !same((p) => p.endArrow ?? null),
6175
+ arrowAlign: !same((p) => p.arrowAlign ?? "extend"),
6176
+ startArrowScale: !same((p) => p.startArrowScale ?? 1),
6177
+ endArrowScale: !same((p) => p.endArrowScale ?? 1),
6178
+ cornerRadius: corners2.some((c) => c.radius !== c0.radius),
6179
+ cornerKind: corners2.some((c) => c.kind !== c0.kind)
6180
+ },
5408
6181
  pathIds: paths.map((p) => p.id)
5409
6182
  };
5410
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
+ };
5411
6194
  function vectorPaintToHex(paint) {
5412
6195
  if (typeof paint !== "string") return null;
5413
6196
  const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(paint.trim());
@@ -5420,7 +6203,8 @@ var VECTOR_PATH_ACTIONS = [
5420
6203
  { id: "average", label: "\u22B9", title: "Average Points", shortcut: "Alt+J", enabled: (_s, p) => p > 1, run: (c) => c.averageAnchors("both") },
5421
6204
  { id: "cut", label: "\u2702\xB7", title: "Cut at Points", enabled: (_s, p) => p > 0, run: (c) => c.cutAtAnchors() },
5422
6205
  { id: "clean", label: "\u2301", title: "Remove Redundant Points", enabled: (s, p) => s + p > 0, run: (c) => c.removeRedundantPoints() },
5423
- { id: "smooth", label: "\u223F", title: "Smooth", enabled: (s, p) => s + p > 0, run: (c) => c.smoothPaths() }
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() }
5424
6208
  ];
5425
6209
  var VECTOR_EDIT_TOGGLES = [
5426
6210
  { key: "keepShapeOnDelete", label: "Keep", title: "Keep shape when deleting points" },
@@ -5458,7 +6242,13 @@ var STYLE_PROPS = {
5458
6242
  strokeLinecap: { type: "string", enum: ["butt", "round", "square"] },
5459
6243
  strokeLinejoin: { type: "string", enum: ["miter", "round", "bevel"] },
5460
6244
  strokeDasharray: { type: "array", items: { type: "number" } },
5461
- 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)." }
5462
6252
  };
5463
6253
  var pickStyle = (params) => {
5464
6254
  const out = {};
@@ -5551,7 +6341,8 @@ function createVectorCommands(controller, options = {}) {
5551
6341
  y: { type: "number" },
5552
6342
  width: { type: "number", description: "For a line: the x offset of its end." },
5553
6343
  height: { type: "number", description: "For a line: the y offset of its end." },
5554
- 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 },
5555
6346
  sides: { type: "integer", minimum: 3 },
5556
6347
  points: { type: "integer", minimum: 3 },
5557
6348
  innerRatio: { type: "number", minimum: 0, maximum: 1 },
@@ -5597,6 +6388,38 @@ function createVectorCommands(controller, options = {}) {
5597
6388
  return { ok: true };
5598
6389
  }
5599
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 = params?.anchors ? anchorsOf(params.anchors) : void 0;
6401
+ const target = { refs, ids: params?.ids ? ids(params) : void 0 };
6402
+ if (params?.radius !== void 0) check(controller.setCornerRadius(params.radius, { ...target, kind: params.kind }));
6403
+ else if (params?.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: params?.ids ? ids(params) : void 0 });
6419
+ if (!created.length) fail("No selected path has a stroke.");
6420
+ return { ids: created };
6421
+ }
6422
+ },
5600
6423
  {
5601
6424
  ...base,
5602
6425
  id: `${prefix}transform`,
@@ -5947,16 +6770,23 @@ export {
5947
6770
  DEFAULT_VECTOR_PATH_EDIT,
5948
6771
  DEFAULT_VECTOR_SNAP,
5949
6772
  IDENTITY_MATRIX,
5950
- KAPPA,
6773
+ KAPPA2 as KAPPA,
5951
6774
  LazyMouse,
6775
+ VECTOR_ARROWHEADS,
6776
+ VECTOR_ARROWHEAD_LABELS,
5952
6777
  VECTOR_CLIPBOARD_TYPE,
5953
6778
  VECTOR_CONSTRUCTION_ANGLES,
6779
+ VECTOR_CORNER_KINDS,
6780
+ VECTOR_CORNER_KIND_LABELS,
6781
+ VECTOR_CORNER_WIDGET_INSET,
5954
6782
  VECTOR_EDIT_TOGGLES,
5955
6783
  VECTOR_INK_MAX_CORNERS,
5956
6784
  VECTOR_OVERLAY_THEME,
5957
6785
  VECTOR_PATH_ACTIONS,
5958
6786
  VECTOR_PENCIL_STRING_PX,
5959
6787
  VECTOR_ROTATE_HANDLE_OFFSET,
6788
+ VECTOR_STROKE_ALIGN_LABELS,
6789
+ VECTOR_STYLE_KEYS,
5960
6790
  VECTOR_TOOLS,
5961
6791
  VECTOR_TOOL_GLYPHS,
5962
6792
  VECTOR_TOOL_SHORTCUTS,
@@ -5965,6 +6795,10 @@ export {
5965
6795
  VectorSnapTargets,
5966
6796
  applyMatrix,
5967
6797
  applyMatrixLinear,
6798
+ applyVectorCornersAttribute,
6799
+ arrowheadInset,
6800
+ arrowheadPath,
6801
+ arrowheadWeight,
5968
6802
  bendSegment,
5969
6803
  bezierToSubpaths,
5970
6804
  boundsFromPoints,
@@ -5976,19 +6810,27 @@ export {
5976
6810
  collectPathNodes,
5977
6811
  constrainToLeave,
5978
6812
  convertAnchor,
6813
+ cornerBisectorPoint,
6814
+ cornerRadiusChange,
6815
+ cornerSetback,
5979
6816
  createVectorCommands,
5980
6817
  curvatureMoveAnchor,
5981
6818
  cutSubpathAt,
5982
6819
  decomposeRigid,
6820
+ effectiveStrokeAlign,
5983
6821
  ellipseSubpath,
5984
6822
  eraseFromFill,
5985
6823
  eraseFromOutline,
5986
6824
  eraserRegion,
6825
+ expandVectorCorners,
6826
+ expandVectorCornersOf,
5987
6827
  findVectorNode,
6828
+ fitCornerRadius,
5988
6829
  getAnchor,
5989
6830
  getAnchorWorld,
5990
6831
  getEditablePathIds,
5991
6832
  getTransformHandlePositions,
6833
+ getVectorCornerWidgets,
5992
6834
  getVectorOverlayRenderData,
5993
6835
  getVectorSceneRenderData,
5994
6836
  getVectorSelectionBoxGeometry,
@@ -5997,11 +6839,15 @@ export {
5997
6839
  getVisibleAnchors,
5998
6840
  getVisibleHandles,
5999
6841
  hasPaint,
6842
+ hasStrokeExtras,
6843
+ hasVectorCorners,
6000
6844
  hitTestVectorNode,
6001
6845
  hitTestVectorNodes,
6846
+ hitTestVectorStroke,
6002
6847
  inferAnchorType,
6003
6848
  insertAnchor,
6004
6849
  invertMatrix,
6850
+ isClosedBezierShape,
6005
6851
  isIdentityMatrix,
6006
6852
  isVectorTextTarget,
6007
6853
  joinSubpaths,
@@ -6012,6 +6858,7 @@ export {
6012
6858
  moveAnchorHandle,
6013
6859
  multiplyMatrices,
6014
6860
  nearestSegment,
6861
+ nextVectorCornerKind,
6015
6862
  nodeMatrix,
6016
6863
  parseSvgTransform,
6017
6864
  parseVectorSvg,
@@ -6028,6 +6875,8 @@ export {
6028
6875
  roundSubpaths,
6029
6876
  scaleFor,
6030
6877
  scaleMatrix,
6878
+ setAnchorCorner,
6879
+ setSubpathCorners,
6031
6880
  simplifyPolyline,
6032
6881
  smoothSubpath,
6033
6882
  splitIntoFaces,
@@ -6044,6 +6893,9 @@ export {
6044
6893
  translateAnchor,
6045
6894
  translateMatrix,
6046
6895
  unionBounds,
6896
+ vectorCornerAt,
6897
+ vectorCorners,
6898
+ vectorCornersAttribute,
6047
6899
  vectorDocumentToSvg,
6048
6900
  vectorInkShapeName,
6049
6901
  vectorNodeBounds,
@@ -6055,6 +6907,11 @@ export {
6055
6907
  vectorPathData,
6056
6908
  vectorPathWorldBezier,
6057
6909
  vectorResizeCursor,
6910
+ vectorStrokeOutline,
6911
+ vectorStrokeOutlinePath,
6912
+ vectorStrokeParts,
6913
+ vectorStrokeReach,
6914
+ vectorStrokeSvgParts,
6058
6915
  vectorToolLabel,
6059
6916
  vectorWorldMatrix,
6060
6917
  walkVectorNodes