@pixerate/editor 0.43.0 → 0.44.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.
@@ -11,6 +11,7 @@ import {
11
11
  contoursToPath,
12
12
  cutOutPiece,
13
13
  distanceToBezierPath,
14
+ fitCubicRun,
14
15
  fitCubicWithTangents,
15
16
  fitCubics,
16
17
  flattenChain,
@@ -79,11 +80,12 @@ var VECTOR_TOOLS = [
79
80
  "shapeBuilder",
80
81
  "paintBucket",
81
82
  "blend",
82
- "gradient"
83
+ "gradient",
84
+ "envelope"
83
85
  ];
84
86
  var VECTOR_TRANSFORM_HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w", "rotate"];
85
87
  var VECTOR_PATHFINDER_OPERATIONS = ["divide", "trim", "merge", "crop", "outline", "minusBack"];
86
- var VECTOR_EFFECT_TYPES = ["trim", "roundCorners", "puckerBloat", "twist", "zigzag", "wiggle", "offset", "merge", "repeater"];
88
+ var VECTOR_EFFECT_TYPES = ["trim", "roundCorners", "puckerBloat", "twist", "zigzag", "wiggle", "offset", "merge", "repeater", "envelope"];
87
89
  var VECTOR_MERGE_MODES = ["merge", "add", "subtract", "intersect", "exclude"];
88
90
 
89
91
  // src/vector/gradient.ts
@@ -482,6 +484,14 @@ function transformVectorGradient(g, m) {
482
484
  const p = vectorGradientPoints(g);
483
485
  return { ...g, start: apply(m, p.start), end: apply(m, p.end), width: apply(m, p.width), ...p.focal ? { focal: apply(m, p.focal) } : {} };
484
486
  }
487
+ function mapVectorGradient(g, f) {
488
+ if (!hasExplicitGradientGeometry(g) || g.units !== "userSpaceOnUse") return g;
489
+ const p = vectorGradientPoints(g);
490
+ return { ...g, start: f(p.start), end: f(p.end), width: f(p.width), ...p.focal ? { focal: f(p.focal) } : {} };
491
+ }
492
+ function mapVectorPaint(paint, f) {
493
+ return isVectorGradient(paint) ? mapVectorGradient(paint, f) : paint;
494
+ }
485
495
  function transformVectorPaint(paint, m) {
486
496
  return isVectorGradient(paint) ? transformVectorGradient(paint, m) : paint;
487
497
  }
@@ -1179,8 +1189,8 @@ function widthProfilePreset(id) {
1179
1189
  }
1180
1190
  function widthProfilePresetId(points) {
1181
1191
  if (isUniformWidthProfile(points)) return "uniform";
1182
- const same = (a, b) => a.length === b.length && a.every((p, i) => Math.abs(p.t - b[i].t) < 1e-9 && Math.abs(p.left - b[i].left) < 1e-9 && Math.abs(p.right - b[i].right) < 1e-9);
1183
- return VECTOR_WIDTH_PRESETS.find((p) => p.points.length && same(points, p.points))?.id ?? "custom";
1192
+ const same2 = (a, b) => a.length === b.length && a.every((p, i) => Math.abs(p.t - b[i].t) < 1e-9 && Math.abs(p.left - b[i].left) < 1e-9 && Math.abs(p.right - b[i].right) < 1e-9);
1193
+ return VECTOR_WIDTH_PRESETS.find((p) => p.points.length && same2(points, p.points))?.id ?? "custom";
1184
1194
  }
1185
1195
  function reverseWidthProfile(points) {
1186
1196
  return points.map((p) => ({ t: 1 - p.t, left: p.right, right: p.left })).reverse();
@@ -1200,10 +1210,10 @@ function insertWidthPoint(points, t, sides2 = {}, options = {}) {
1200
1210
  left: clean(sides2.left, VECTOR_WIDTH_PROFILE_MAX_SCALE) ?? here.left,
1201
1211
  right: clean(sides2.right, VECTOR_WIDTH_PROFILE_MAX_SCALE) ?? here.right
1202
1212
  };
1203
- const same = pts.findIndex((q) => Math.abs(q.t - tt) <= VECTOR_WIDTH_SAME_T);
1204
- if (same >= 0 && !options.step) {
1205
- pts[same] = { ...p, t: pts[same].t };
1206
- return { points: pts, index: same };
1213
+ const same2 = pts.findIndex((q) => Math.abs(q.t - tt) <= VECTOR_WIDTH_SAME_T);
1214
+ if (same2 >= 0 && !options.step) {
1215
+ pts[same2] = { ...p, t: pts[same2].t };
1216
+ return { points: pts, index: same2 };
1207
1217
  }
1208
1218
  let i = 0;
1209
1219
  while (i < pts.length && pts[i].t <= tt) i++;
@@ -1743,13 +1753,13 @@ function endOf(segs, atStart) {
1743
1753
  const last = segs[segs.length - 1];
1744
1754
  return { p: toVec(last[last.length - 1]), out: v(t[0], t[1]) };
1745
1755
  }
1746
- function pointAlong(segs, fromStart, dist4) {
1756
+ function pointAlong(segs, fromStart, dist5) {
1747
1757
  const order = fromStart ? segs : [...segs].reverse();
1748
1758
  let acc = 0;
1749
1759
  for (const s of order) {
1750
1760
  const l = segLen(s);
1751
- if (acc + l >= dist4) {
1752
- const want = fromStart ? dist4 - acc : l - (dist4 - acc);
1761
+ if (acc + l >= dist5) {
1762
+ const want = fromStart ? dist5 - acc : l - (dist5 - acc);
1753
1763
  const pts = trimSegments([s], 0, want);
1754
1764
  const p = pts.length ? pts[0][pts[0].length - 1] : s[0];
1755
1765
  return toVec(p);
@@ -2231,6 +2241,846 @@ function pathsCenter(paths) {
2231
2241
  return b ? { x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 } : { x: 0, y: 0 };
2232
2242
  }
2233
2243
 
2244
+ // src/vector/pathMap.ts
2245
+ var MAX_PIECES = 1 << 20;
2246
+ var MAX_DEPTH = 6;
2247
+ var PROBES = [0.25, 0.5, 0.75];
2248
+ var lerp2 = (a, b, t) => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });
2249
+ var dist2 = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
2250
+ var same = (a, b) => Math.abs(a.x - b.x) < 1e-9 && Math.abs(a.y - b.y) < 1e-9;
2251
+ function evalCubic(s, t) {
2252
+ const ab = lerp2(s[0], s[1], t);
2253
+ const bc = lerp2(s[1], s[2], t);
2254
+ const cd = lerp2(s[2], s[3], t);
2255
+ return lerp2(lerp2(ab, bc, t), lerp2(bc, cd, t), t);
2256
+ }
2257
+ function halves(s) {
2258
+ const ab = lerp2(s[0], s[1], 0.5);
2259
+ const bc = lerp2(s[1], s[2], 0.5);
2260
+ const cd = lerp2(s[2], s[3], 0.5);
2261
+ const abc = lerp2(ab, bc, 0.5);
2262
+ const bcd = lerp2(bc, cd, 0.5);
2263
+ const m = lerp2(abc, bcd, 0.5);
2264
+ return [
2265
+ [s[0], ab, abc, m],
2266
+ [m, bcd, cd, s[3]]
2267
+ ];
2268
+ }
2269
+ function offLine(p, a, b) {
2270
+ const l = dist2(a, b);
2271
+ return l < 1e-12 ? dist2(p, a) : Math.abs((b.x - a.x) * (a.y - p.y) - (a.x - p.x) * (b.y - a.y)) / l;
2272
+ }
2273
+ var mapAnchorPoints = (a, f) => {
2274
+ const p = f(a);
2275
+ return {
2276
+ x: p.x,
2277
+ y: p.y,
2278
+ ...a.in ? { in: f(a.in) } : {},
2279
+ ...a.out ? { out: f(a.out) } : {},
2280
+ ...a.type && a.type !== "corner" ? { type: a.type } : {},
2281
+ ...a.corner ? { corner: { ...a.corner } } : {}
2282
+ };
2283
+ };
2284
+ function mapVectorPoints(subpaths, f) {
2285
+ return subpaths.map((sp) => ({ closed: sp.closed, anchors: sp.anchors.map((a) => mapAnchorPoints(a, f)) }));
2286
+ }
2287
+ function splitMapped(seg, f, tol, depth, out) {
2288
+ const mapped = seg.map(f);
2289
+ const fits = PROBES.every((t) => dist2(f(evalCubic(seg, t)), evalCubic(mapped, t)) <= tol);
2290
+ if (fits || depth === 0) {
2291
+ out.push(mapped);
2292
+ return;
2293
+ }
2294
+ const [a, b] = halves(seg);
2295
+ splitMapped(a, f, tol, depth - 1, out);
2296
+ splitMapped(b, f, tol, depth - 1, out);
2297
+ }
2298
+ var toPoint = (p) => [p.x, p.y];
2299
+ var toVec2 = (p) => ({ x: p[0], y: p[1] });
2300
+ function refit(seg, pieces, f, tol) {
2301
+ if (pieces.length < 3) return pieces;
2302
+ const n = pieces.length * 4;
2303
+ const samples = [];
2304
+ for (let i = 0; i <= n; i++) samples.push(toPoint(f(evalCubic(seg, i / n))));
2305
+ const first = pieces[0];
2306
+ const last = pieces[pieces.length - 1];
2307
+ const t0 = !same(first[1], first[0]) ? first[1] : first[2];
2308
+ const t1 = !same(last[2], last[3]) ? last[2] : last[1];
2309
+ const fitted = fitCubicRun(samples, tol / 2, [t0.x - first[0].x, t0.y - first[0].y], [last[3].x - t1.x, last[3].y - t1.y]);
2310
+ if (!fitted.length || fitted.length >= pieces.length) return pieces;
2311
+ const chain = fitted.map((c) => [c.p0, c.p1, c.p2, c.p3]);
2312
+ for (let i = 0; i < n; i++) {
2313
+ const q = toPoint(f(evalCubic(seg, (i + 0.5) / n)));
2314
+ let best = Infinity;
2315
+ for (const c of chain) best = Math.min(best, segmentNearest(c, q).distance);
2316
+ if (best > tol) return pieces;
2317
+ }
2318
+ return fitted.map((c) => [toVec2(c.p0), toVec2(c.p1), toVec2(c.p2), toVec2(c.p3)]);
2319
+ }
2320
+ function mapSegment(seg, f, tol, depth, fit) {
2321
+ if (same(seg[1], seg[0]) && same(seg[2], seg[3])) {
2322
+ const a = f(seg[0]);
2323
+ const b = f(seg[3]);
2324
+ if (PROBES.every((t) => offLine(f(lerp2(seg[0], seg[3], t)), a, b) <= tol)) return [[a, a, b, b]];
2325
+ const line2 = [seg[0], lerp2(seg[0], seg[3], 1 / 3), lerp2(seg[0], seg[3], 2 / 3), seg[3]];
2326
+ const out2 = [];
2327
+ splitMapped(line2, f, tol, depth, out2);
2328
+ return fit ? refit(line2, out2, f, tol) : out2;
2329
+ }
2330
+ const out = [];
2331
+ splitMapped(seg, f, tol, depth, out);
2332
+ return fit ? refit(seg, out, f, tol) : out;
2333
+ }
2334
+ function realign(a) {
2335
+ if (!a.in || !a.out || same(a.in, a) || same(a.out, a)) return a;
2336
+ const li = dist2(a.in, a);
2337
+ const lo = dist2(a.out, a);
2338
+ const dx = (a.x - a.in.x) / li + (a.out.x - a.x) / lo;
2339
+ const dy = (a.y - a.in.y) / li + (a.out.y - a.y) / lo;
2340
+ const l = Math.hypot(dx, dy);
2341
+ if (l < 1e-12) return a;
2342
+ const u = { x: dx / l, y: dy / l };
2343
+ const type = a.type === "symmetric" && Math.abs(li - lo) > 1e-6 * Math.max(li, lo) ? "smooth" : a.type;
2344
+ return { ...a, in: { x: a.x - u.x * li, y: a.y - u.y * li }, out: { x: a.x + u.x * lo, y: a.y + u.y * lo }, ...type ? { type } : {} };
2345
+ }
2346
+ var segmentOf = (sp, i) => {
2347
+ const a = sp.anchors[i];
2348
+ const b = sp.anchors[(i + 1) % sp.anchors.length];
2349
+ return [{ x: a.x, y: a.y }, a.out ?? { x: a.x, y: a.y }, b.in ?? { x: b.x, y: b.y }, { x: b.x, y: b.y }];
2350
+ };
2351
+ function mapSubpath(sp, f, tol, depth, fit) {
2352
+ const n = sp.anchors.length;
2353
+ const count = n < 2 ? 0 : sp.closed ? n : n - 1;
2354
+ if (!count) return mapVectorPoints([sp], f)[0];
2355
+ const pieces = [];
2356
+ for (let i = 0; i < count; i++) pieces.push(mapSegment(segmentOf(sp, i), f, tol, depth, fit));
2357
+ const anchors = [];
2358
+ const put = (a) => {
2359
+ if (a.in && same(a.in, a)) delete a.in;
2360
+ if (a.out && same(a.out, a)) delete a.out;
2361
+ anchors.push(a.type === "smooth" || a.type === "symmetric" ? realign(a) : a);
2362
+ };
2363
+ sp.anchors.forEach((k, i) => {
2364
+ const incoming = i > 0 ? pieces[i - 1] : sp.closed ? pieces[n - 1] : void 0;
2365
+ const outgoing = pieces[i];
2366
+ const p = f(k);
2367
+ const a = { x: p.x, y: p.y };
2368
+ const inCtrl = incoming ? incoming[incoming.length - 1][2] : k.in ? f(k.in) : null;
2369
+ const outCtrl = outgoing ? outgoing[0][1] : k.out ? f(k.out) : null;
2370
+ if (inCtrl) a.in = inCtrl;
2371
+ if (outCtrl) a.out = outCtrl;
2372
+ if (k.type && k.type !== "corner") a.type = k.type;
2373
+ if (k.corner) a.corner = { ...k.corner };
2374
+ put(a);
2375
+ if (outgoing) for (let w = 1; w < outgoing.length; w++) put({ x: outgoing[w][0].x, y: outgoing[w][0].y, in: outgoing[w - 1][2], out: outgoing[w][1], type: "smooth" });
2376
+ });
2377
+ return { closed: sp.closed, anchors };
2378
+ }
2379
+ function mapVectorSubpaths(subpaths, f, tolerance = 0.1, options = {}) {
2380
+ const tol = Number.isFinite(tolerance) && tolerance > 0 ? tolerance : 0.1;
2381
+ const segments = subpaths.reduce((s, sp) => s + sp.anchors.length, 0);
2382
+ let depth = MAX_DEPTH;
2383
+ while (depth > 0 && segments * 2 ** depth > MAX_PIECES) depth--;
2384
+ return subpaths.map((sp) => mapSubpath(sp, f, tol, depth, options.refit !== false));
2385
+ }
2386
+ function mapBezierPath(path, f, tolerance = 0.1, options = {}) {
2387
+ return subpathsToBezier(mapVectorSubpaths(bezierToSubpaths(path), f, tolerance, options), { corners: false });
2388
+ }
2389
+
2390
+ // src/vector/warp.ts
2391
+ var VECTOR_WARP_STYLES = [
2392
+ "arc",
2393
+ "arcLower",
2394
+ "arcUpper",
2395
+ "arch",
2396
+ "bulge",
2397
+ "shellLower",
2398
+ "shellUpper",
2399
+ "flag",
2400
+ "wave",
2401
+ "fish",
2402
+ "rise",
2403
+ "fisheye",
2404
+ "inflate",
2405
+ "squeeze",
2406
+ "twist"
2407
+ ];
2408
+ var VECTOR_WARP_STYLE_LABELS = {
2409
+ arc: "Arc",
2410
+ arcLower: "Arc Lower",
2411
+ arcUpper: "Arc Upper",
2412
+ arch: "Arch",
2413
+ bulge: "Bulge",
2414
+ shellLower: "Shell Lower",
2415
+ shellUpper: "Shell Upper",
2416
+ flag: "Flag",
2417
+ wave: "Wave",
2418
+ fish: "Fish",
2419
+ rise: "Rise",
2420
+ fisheye: "Fisheye",
2421
+ inflate: "Inflate",
2422
+ squeeze: "Squeeze",
2423
+ twist: "Twist"
2424
+ };
2425
+ function restGrid() {
2426
+ return [0, 1, 2, 3].map((j) => [0, 1, 2, 3].map((i) => [i / 3, j / 3]));
2427
+ }
2428
+ function edgeArc(y, b, up, h) {
2429
+ if (Math.abs(b) < 1e-9) return [0, 1, 2, 3].map((i) => [i / 3, y]);
2430
+ const th = Math.abs(b) * Math.PI;
2431
+ const k = 4 / 3 * Math.tan(th / 4) * 0.5 / Math.sin(th / 2);
2432
+ const s = b > 0 === up ? -1 : 1;
2433
+ const c = Math.cos(th / 2);
2434
+ const n = Math.sin(th / 2);
2435
+ const dy = s * k * n / h;
2436
+ return [
2437
+ [0, y],
2438
+ [k * c, y + dy],
2439
+ [1 - k * c, y + dy],
2440
+ [1, y]
2441
+ ];
2442
+ }
2443
+ function arcGrid(b, h) {
2444
+ if (Math.abs(b) < 1e-9) return restGrid();
2445
+ const up = b > 0;
2446
+ const th = Math.abs(b) * Math.PI;
2447
+ const r = 0.5 / Math.sin(th / 2);
2448
+ const k = 4 / 3 * Math.tan(th / 4);
2449
+ const s = up ? -1 : 1;
2450
+ const cy = up ? h + r * Math.cos(th / 2) : -r * Math.cos(th / 2);
2451
+ return [0, 1, 2, 3].map((j) => {
2452
+ const v2 = j / 3;
2453
+ const big = r + h * (up ? 1 - v2 : v2);
2454
+ const at2 = (a) => [0.5 + big * Math.sin(a), cy + s * big * Math.cos(a)];
2455
+ const tangent2 = (a) => [Math.cos(a), -s * Math.sin(a)];
2456
+ const p0 = at2(-th / 2);
2457
+ const p3 = at2(th / 2);
2458
+ const t0 = tangent2(-th / 2);
2459
+ const t1 = tangent2(th / 2);
2460
+ const l = k * big;
2461
+ const pts = [p0, [p0[0] + l * t0[0], p0[1] + l * t0[1]], [p3[0] - l * t1[0], p3[1] - l * t1[1]], p3];
2462
+ return pts.map((p) => [p[0], p[1] / h]);
2463
+ });
2464
+ }
2465
+ function blendRows(top, bottom) {
2466
+ return [0, 1, 2, 3].map((j) => {
2467
+ const t = j / 3;
2468
+ return [0, 1, 2, 3].map((i) => [top[i][0] + (bottom[i][0] - top[i][0]) * t, top[i][1] + (bottom[i][1] - top[i][1]) * t]);
2469
+ });
2470
+ }
2471
+ var INNER = [
2472
+ [1, 1, -1, -1],
2473
+ [1, 2, 1, -1],
2474
+ [2, 1, -1, 1],
2475
+ [2, 2, 1, 1]
2476
+ ];
2477
+ function vectorWarpPresetGrid(style, b, hd, vd, vertical, aspect) {
2478
+ const h = vertical ? 1 / aspect : aspect;
2479
+ if (vertical) [hd, vd] = [vd, hd];
2480
+ let g = restGrid();
2481
+ switch (style) {
2482
+ case "arc":
2483
+ g = arcGrid(b, h);
2484
+ break;
2485
+ case "arch":
2486
+ g = [0, 1, 2, 3].map((j) => edgeArc(j / 3, b, true, h));
2487
+ break;
2488
+ case "bulge":
2489
+ g = blendRows(edgeArc(0, b, true, h), edgeArc(1, b, false, h));
2490
+ break;
2491
+ case "arcLower":
2492
+ g = blendRows(g[0], edgeArc(1, b, false, h));
2493
+ break;
2494
+ case "arcUpper":
2495
+ g = blendRows(edgeArc(0, b, true, h), g[3]);
2496
+ break;
2497
+ // The bulging edge and the outer points of the row next to it come from Arc (bent the other
2498
+ // way for Shell Lower); the rest stays put.
2499
+ case "shellLower": {
2500
+ const a = arcGrid(-b, h);
2501
+ g[3] = a[3];
2502
+ g[2][0] = a[2][0];
2503
+ g[2][3] = a[2][3];
2504
+ break;
2505
+ }
2506
+ case "shellUpper": {
2507
+ const a = arcGrid(b, h);
2508
+ g[0] = a[0];
2509
+ g[1][0] = a[1][0];
2510
+ g[1][3] = a[1][3];
2511
+ break;
2512
+ }
2513
+ case "flag":
2514
+ for (const row of g) {
2515
+ row[1][1] -= 2 * b;
2516
+ row[2][1] += 2 * b;
2517
+ }
2518
+ break;
2519
+ case "wave":
2520
+ for (const row of g.slice(1, 3)) {
2521
+ row[1][1] += 4 / 3 * b;
2522
+ row[2][1] -= 4 / 3 * b;
2523
+ }
2524
+ break;
2525
+ case "fish":
2526
+ [-2, -2 / 3, 2 / 3, 2].forEach((f, j) => {
2527
+ g[j][1][1] += f * b;
2528
+ g[j][2][1] -= f * b;
2529
+ });
2530
+ break;
2531
+ case "rise":
2532
+ for (const row of g) {
2533
+ row[0][1] += 2 * b;
2534
+ row[1][1] += 2 * b;
2535
+ }
2536
+ break;
2537
+ case "fisheye":
2538
+ for (const [j, i, dx, dy] of INNER) {
2539
+ g[j][i][0] += dx * 2 * b / 3;
2540
+ g[j][i][1] += dy * 2 * b / 3;
2541
+ }
2542
+ break;
2543
+ // Edge midpoints move out (Inflate) or the sides in (Squeeze) by b/3, the inner points by b/9.
2544
+ case "inflate":
2545
+ case "squeeze": {
2546
+ const s = style === "inflate" ? 1 : -1;
2547
+ for (const p of g[0].slice(1, 3)) p[1] -= b / 3;
2548
+ for (const p of g[3].slice(1, 3)) p[1] += b / 3;
2549
+ for (const row of g.slice(1, 3)) {
2550
+ row[0][0] -= s * b / 3;
2551
+ row[3][0] += s * b / 3;
2552
+ }
2553
+ for (const [j, i, dx, dy] of INNER) {
2554
+ g[j][i][0] += s * dx * b / 9;
2555
+ g[j][i][1] += dy * b / 9;
2556
+ }
2557
+ break;
2558
+ }
2559
+ // The inner points turn round the centre (the other way for a negative bend).
2560
+ case "twist": {
2561
+ const a = Math.abs(b);
2562
+ if (b >= 0) {
2563
+ g[1][1][0] += a;
2564
+ g[1][2][1] += a;
2565
+ g[2][1][1] -= a;
2566
+ g[2][2][0] -= a;
2567
+ } else {
2568
+ g[1][1][1] += a;
2569
+ g[1][2][0] -= a;
2570
+ g[2][1][0] += a;
2571
+ g[2][2][1] -= a;
2572
+ }
2573
+ break;
2574
+ }
2575
+ }
2576
+ if (vd !== 0) {
2577
+ const moved = (row, s) => {
2578
+ const m = [(row[0][0] + row[3][0]) / 2, (row[0][1] + row[3][1]) / 2];
2579
+ return row.map((p) => [m[0] + (p[0] - m[0]) * s - p[0], m[1] + (p[1] - m[1]) * s - p[1]]);
2580
+ };
2581
+ const d0 = moved(g[0], 1 - vd);
2582
+ const d3 = moved(g[3], 1 + vd);
2583
+ g.forEach((row, j) => {
2584
+ const t = j / 3;
2585
+ row.forEach((p, i) => {
2586
+ p[0] += d0[i][0] + (d3[i][0] - d0[i][0]) * t;
2587
+ p[1] += d0[i][1] + (d3[i][1] - d0[i][1]) * t;
2588
+ });
2589
+ });
2590
+ }
2591
+ if (hd !== 0) {
2592
+ for (let i = 0; i < 4; i++) {
2593
+ const s = 1 + hd * (2 * i / 3 - 1);
2594
+ const m = [(g[0][i][0] + g[3][i][0]) / 2, (g[0][i][1] + g[3][i][1]) / 2];
2595
+ for (const row of g) row[i] = [m[0] + (row[i][0] - m[0]) * s, m[1] + (row[i][1] - m[1]) * s];
2596
+ }
2597
+ }
2598
+ if (vertical) g = [0, 1, 2, 3].map((j) => [0, 1, 2, 3].map((i) => [g[i][j][1], g[i][j][0]]));
2599
+ return g;
2600
+ }
2601
+ function bernstein(t) {
2602
+ const s = 1 - t;
2603
+ return [s * s * s, 3 * s * s * t, 3 * s * t * t, t * t * t];
2604
+ }
2605
+ var lerp3 = (a, b, t) => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });
2606
+ function splitCubic(p, t) {
2607
+ const a = lerp3(p[0], p[1], t);
2608
+ const b = lerp3(p[1], p[2], t);
2609
+ const c = lerp3(p[2], p[3], t);
2610
+ const d = lerp3(a, b, t);
2611
+ const e = lerp3(b, c, t);
2612
+ return [p[0], a, d, lerp3(d, e, t), e, c, p[3]];
2613
+ }
2614
+ var BERN_INV = [
2615
+ [1, 0, 0, 0],
2616
+ [-5 / 6, 3, -1.5, 1 / 3],
2617
+ [1 / 3, -1.5, 3, -5 / 6],
2618
+ [0, 0, 0, 1]
2619
+ ];
2620
+ var pct = (v2) => typeof v2 === "number" && Number.isFinite(v2) ? Math.min(1, Math.max(-1, v2 / 100)) : 0;
2621
+ function vectorWarpPresetMesh(preset, bounds) {
2622
+ if (!VECTOR_WARP_STYLES.includes(preset.style)) return null;
2623
+ const { x, y, width: w, height: h } = bounds;
2624
+ if (!(w > 0 && h > 0)) return null;
2625
+ const b = pct(preset.bend);
2626
+ const hd = pct(preset.horizontal);
2627
+ const vd = pct(preset.vertical);
2628
+ if (b === 0 && hd === 0 && vd === 0) return null;
2629
+ const grid = vectorWarpPresetGrid(preset.style, b, hd, vd, preset.orientation === "vertical", h / w);
2630
+ return { us: [0, 1], vs: [0, 1], points: grid.flat().map((p) => ({ x: x + p[0] * w, y: y + p[1] * h })) };
2631
+ }
2632
+ var vectorMeshColumns = (m) => 3 * (m.us.length - 1) + 1;
2633
+ var vectorMeshRows = (m) => 3 * (m.vs.length - 1) + 1;
2634
+ function isValidVectorMesh(m) {
2635
+ if (!m || typeof m !== "object") return false;
2636
+ const { us, vs, points } = m;
2637
+ const knots = (k) => Array.isArray(k) && k.length >= 2 && k.length <= 65 && k[0] === 0 && k[k.length - 1] === 1 && k.every((v2, i) => typeof v2 === "number" && (i === 0 || v2 > k[i - 1]));
2638
+ if (!knots(us) || !knots(vs) || !Array.isArray(points)) return false;
2639
+ return points.length === (3 * (us.length - 1) + 1) * (3 * (vs.length - 1) + 1) && points.every((p) => !!p && Number.isFinite(p.x) && Number.isFinite(p.y));
2640
+ }
2641
+ function fitVectorMesh(f, us, vs) {
2642
+ const pc = us.length - 1;
2643
+ const pr = vs.length - 1;
2644
+ const nx = 3 * pc + 1;
2645
+ const points = new Array((3 * pr + 1) * nx);
2646
+ for (let pj = 0; pj < pr; pj++) {
2647
+ for (let pi = 0; pi < pc; pi++) {
2648
+ const s = [0, 1, 2, 3].map(
2649
+ (j) => [0, 1, 2, 3].map((i) => f(us[pi] + (us[pi + 1] - us[pi]) * i / 3, vs[pj] + (vs[pj + 1] - vs[pj]) * j / 3))
2650
+ );
2651
+ for (let j = 0; j < 4; j++) {
2652
+ for (let i = 0; i < 4; i++) {
2653
+ let x = 0;
2654
+ let y = 0;
2655
+ for (let a = 0; a < 4; a++) {
2656
+ for (let b = 0; b < 4; b++) {
2657
+ const k = BERN_INV[j][a] * BERN_INV[i][b];
2658
+ x += k * s[a][b].x;
2659
+ y += k * s[a][b].y;
2660
+ }
2661
+ }
2662
+ points[(3 * pj + j) * nx + 3 * pi + i] = { x, y };
2663
+ }
2664
+ }
2665
+ }
2666
+ }
2667
+ return { us: us.slice(), vs: vs.slice(), points };
2668
+ }
2669
+ var evenKnots = (n) => Array.from({ length: n + 1 }, (_, i) => i / n);
2670
+ var clampCount = (n) => Math.min(64, Math.max(1, Math.round(Number.isFinite(n) ? n : 1)));
2671
+ function vectorIdentityMesh(bounds, columns = 1, rows = 1) {
2672
+ return fitVectorMesh((s, t) => ({ x: bounds.x + s * bounds.width, y: bounds.y + t * bounds.height }), evenKnots(clampCount(columns)), evenKnots(clampCount(rows)));
2673
+ }
2674
+ function locate(knots, s) {
2675
+ const n = knots.length - 1;
2676
+ let i = 0;
2677
+ while (i + 1 < n && s > knots[i + 1]) i++;
2678
+ const w = knots[i + 1] - knots[i];
2679
+ return [i, w > 0 ? (s - knots[i]) / w : 0];
2680
+ }
2681
+ function evalVectorMesh(m, s, t) {
2682
+ const [pi, a] = locate(m.us, s);
2683
+ const [pj, b] = locate(m.vs, t);
2684
+ const ba = bernstein(a);
2685
+ const bb = bernstein(b);
2686
+ const nx = vectorMeshColumns(m);
2687
+ let x = 0;
2688
+ let y = 0;
2689
+ for (let j = 0; j < 4; j++) {
2690
+ for (let i = 0; i < 4; i++) {
2691
+ const p = m.points[(3 * pj + j) * nx + 3 * pi + i];
2692
+ const k = ba[i] * bb[j];
2693
+ x += p.x * k;
2694
+ y += p.y * k;
2695
+ }
2696
+ }
2697
+ return { x, y };
2698
+ }
2699
+ function transposeMesh(m) {
2700
+ const nx = vectorMeshColumns(m);
2701
+ const ny = vectorMeshRows(m);
2702
+ const points = new Array(nx * ny);
2703
+ for (let j = 0; j < ny; j++) for (let i = 0; i < nx; i++) points[i * ny + j] = m.points[j * nx + i];
2704
+ return { us: m.vs.slice(), vs: m.us.slice(), points };
2705
+ }
2706
+ function splitVectorMeshU(m, s) {
2707
+ const [pi, a] = locate(m.us, s);
2708
+ if (!(a >= 0.01 && a <= 0.99)) return null;
2709
+ const nx = vectorMeshColumns(m);
2710
+ const ny = vectorMeshRows(m);
2711
+ const points = [];
2712
+ for (let j = 0; j < ny; j++) {
2713
+ const row = m.points.slice(j * nx, (j + 1) * nx);
2714
+ points.push(...row.slice(0, 3 * pi), ...splitCubic(row.slice(3 * pi, 3 * pi + 4), a), ...row.slice(3 * pi + 4));
2715
+ }
2716
+ const us = m.us.slice();
2717
+ us.splice(pi + 1, 0, Math.min(1, Math.max(0, s)));
2718
+ return { us, vs: m.vs.slice(), points };
2719
+ }
2720
+ function splitVectorMeshV(m, t) {
2721
+ const out = splitVectorMeshU(transposeMesh(m), t);
2722
+ return out && transposeMesh(out);
2723
+ }
2724
+ function removeVectorMeshSplitU(m, k) {
2725
+ if (k <= 0 || k + 1 >= m.us.length) return null;
2726
+ const a = (m.us[k] - m.us[k - 1]) / (m.us[k + 1] - m.us[k - 1]);
2727
+ const nx = vectorMeshColumns(m);
2728
+ const ny = vectorMeshRows(m);
2729
+ const c0 = 3 * (k - 1);
2730
+ const points = [];
2731
+ for (let j = 0; j < ny; j++) {
2732
+ const row = m.points.slice(j * nx, (j + 1) * nx);
2733
+ const [p0, q1, r2, p3] = [row[c0], row[c0 + 1], row[c0 + 5], row[c0 + 6]];
2734
+ const p1 = { x: (q1.x - (1 - a) * p0.x) / a, y: (q1.y - (1 - a) * p0.y) / a };
2735
+ const p2 = { x: (r2.x - a * p3.x) / (1 - a), y: (r2.y - a * p3.y) / (1 - a) };
2736
+ points.push(...row.slice(0, c0), p0, p1, p2, p3, ...row.slice(c0 + 7));
2737
+ }
2738
+ const us = m.us.slice();
2739
+ us.splice(k, 1);
2740
+ return { us, vs: m.vs.slice(), points };
2741
+ }
2742
+ function removeVectorMeshSplitV(m, k) {
2743
+ const out = removeVectorMeshSplitU(transposeMesh(m), k);
2744
+ return out && transposeMesh(out);
2745
+ }
2746
+ function mapVectorMeshPoints(m, f) {
2747
+ return { us: m.us.slice(), vs: m.vs.slice(), points: m.points.map((p) => f(p)) };
2748
+ }
2749
+ function resizeVectorMesh(m, columns, rows) {
2750
+ const cols = clampCount(columns);
2751
+ const rws = clampCount(rows);
2752
+ if (m.us.length - 1 === cols && m.vs.length - 1 === rws) return m;
2753
+ const us = evenKnots(cols);
2754
+ const vs = evenKnots(rws);
2755
+ const has = (list, v2) => list.some((k) => Math.abs(k - v2) < 1e-12);
2756
+ if (m.us.every((u) => has(us, u)) && m.vs.every((v2) => has(vs, v2))) {
2757
+ let out = m;
2758
+ for (const u of us) if (!has(out.us, u)) out = splitVectorMeshU(out, u) ?? out;
2759
+ for (const v2 of vs) if (!has(out.vs, v2)) out = splitVectorMeshV(out, v2) ?? out;
2760
+ return out;
2761
+ }
2762
+ return fitVectorMesh((s, t) => evalVectorMesh(m, s, t), us, vs);
2763
+ }
2764
+ function vectorMeshControlBounds(m) {
2765
+ let minX = Infinity;
2766
+ let minY = Infinity;
2767
+ let maxX = -Infinity;
2768
+ let maxY = -Infinity;
2769
+ for (const p of m.points) {
2770
+ minX = Math.min(minX, p.x);
2771
+ minY = Math.min(minY, p.y);
2772
+ maxX = Math.max(maxX, p.x);
2773
+ maxY = Math.max(maxY, p.y);
2774
+ }
2775
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
2776
+ }
2777
+ function vectorMeshParamAt(m, p) {
2778
+ const n = 48;
2779
+ let best = { s: 0.5, t: 0.5, d: Infinity };
2780
+ for (let j = 0; j <= n; j++) {
2781
+ for (let i = 0; i <= n; i++) {
2782
+ const q2 = evalVectorMesh(m, i / n, j / n);
2783
+ const d = (q2.x - p.x) ** 2 + (q2.y - p.y) ** 2;
2784
+ if (d < best.d) best = { s: i / n, t: j / n, d };
2785
+ }
2786
+ }
2787
+ let { s, t } = best;
2788
+ for (let k = 0; k < 12; k++) {
2789
+ const h = 1e-5;
2790
+ const q2 = evalVectorMesh(m, s, t);
2791
+ const qs = evalVectorMesh(m, s + h, t);
2792
+ const qt = evalVectorMesh(m, s, t + h);
2793
+ const a = (qs.x - q2.x) / h;
2794
+ const b = (qt.x - q2.x) / h;
2795
+ const c = (qs.y - q2.y) / h;
2796
+ const d = (qt.y - q2.y) / h;
2797
+ const det = a * d - b * c;
2798
+ if (Math.abs(det) < 1e-12) break;
2799
+ const ex = p.x - q2.x;
2800
+ const ey = p.y - q2.y;
2801
+ s = Math.min(1, Math.max(0, s + (d * ex - b * ey) / det));
2802
+ t = Math.min(1, Math.max(0, t + (a * ey - c * ex) / det));
2803
+ }
2804
+ const q = evalVectorMesh(m, s, t);
2805
+ return { s, t, distance: Math.hypot(q.x - p.x, q.y - p.y) };
2806
+ }
2807
+ function smoothLines(m, i0, i1, j0, j1) {
2808
+ const nx = vectorMeshColumns(m);
2809
+ i1 = Math.min(i1, nx);
2810
+ j1 = Math.min(j1, vectorMeshRows(m));
2811
+ const triples = [];
2812
+ for (let a = 1; a < m.us.length - 1; a++) {
2813
+ if (3 * a >= i0 && 3 * a < i1)
2814
+ for (let j = j0; j < j1; j++) triples.push([j * nx + 3 * a - 1, j * nx + 3 * a, j * nx + 3 * a + 1, m.us[a] - m.us[a - 1], m.us[a + 1] - m.us[a]]);
2815
+ }
2816
+ for (let b = 1; b < m.vs.length - 1; b++) {
2817
+ if (3 * b >= j0 && 3 * b < j1)
2818
+ for (let i = i0; i < i1; i++) triples.push([(3 * b - 1) * nx + i, 3 * b * nx + i, (3 * b + 1) * nx + i, m.vs[b] - m.vs[b - 1], m.vs[b + 1] - m.vs[b]]);
2819
+ }
2820
+ for (const [l, c, r, hl, hr] of triples) {
2821
+ const pl = m.points[l];
2822
+ const pc = m.points[c];
2823
+ const pr = m.points[r];
2824
+ const tx = ((pc.x - pl.x) / hl + (pr.x - pc.x) / hr) / 2;
2825
+ const ty = ((pc.y - pl.y) / hl + (pr.y - pc.y) / hr) / 2;
2826
+ m.points[l] = { x: pc.x - tx * hl, y: pc.y - ty * hl };
2827
+ m.points[r] = { x: pc.x + tx * hr, y: pc.y + ty * hr };
2828
+ }
2829
+ }
2830
+ function dragVectorMeshPoint(base, index, d) {
2831
+ if (index < 0 || index >= base.points.length || !Number.isFinite(d.x) || !Number.isFinite(d.y)) return base;
2832
+ const out = { us: base.us.slice(), vs: base.vs.slice(), points: base.points.slice() };
2833
+ const nx = vectorMeshColumns(base);
2834
+ const ny = vectorMeshRows(base);
2835
+ const i = index % nx;
2836
+ const j = Math.floor(index / nx);
2837
+ const shift = (ii, jj, v2) => {
2838
+ if (ii < 0 || jj < 0 || ii >= nx || jj >= ny) return;
2839
+ const p = out.points[jj * nx + ii];
2840
+ out.points[jj * nx + ii] = { x: p.x + v2.x, y: p.y + v2.y };
2841
+ };
2842
+ const span = (knots, k) => k > 0 && k + 1 < knots.length ? [knots[k] - knots[k - 1], knots[k + 1] - knots[k]] : null;
2843
+ const ri = i % 3;
2844
+ const rj = j % 3;
2845
+ if (ri === 0 && rj === 0) {
2846
+ for (let dj = -1; dj <= 1; dj++) for (let di = -1; di <= 1; di++) shift(i + di, j + dj, d);
2847
+ } else if (ri === 0) {
2848
+ for (let di = -1; di <= 1; di++) shift(i + di, j, d);
2849
+ const sp = span(base.vs, Math.floor(j / 3) + (rj === 2 ? 1 : 0));
2850
+ if (sp) {
2851
+ const [other, k] = rj === 1 ? [j - 2, sp[0] / sp[1]] : [j + 2, sp[1] / sp[0]];
2852
+ for (let di = -1; di <= 1; di++) shift(i + di, other, { x: -d.x * k, y: -d.y * k });
2853
+ }
2854
+ } else if (rj === 0) {
2855
+ for (let dj = -1; dj <= 1; dj++) shift(i, j + dj, d);
2856
+ const sp = span(base.us, Math.floor(i / 3) + (ri === 2 ? 1 : 0));
2857
+ if (sp) {
2858
+ const [other, k] = ri === 1 ? [i - 2, sp[0] / sp[1]] : [i + 2, sp[1] / sp[0]];
2859
+ for (let dj = -1; dj <= 1; dj++) shift(other, j + dj, { x: -d.x * k, y: -d.y * k });
2860
+ }
2861
+ } else shift(i, j, d);
2862
+ return out;
2863
+ }
2864
+ function pullVectorMesh(base, s, t, d) {
2865
+ const BORDER_ZONE = 0.12;
2866
+ const REACH = 1.8;
2867
+ if (!Number.isFinite(d.x) || !Number.isFinite(d.y)) return base;
2868
+ const m = { us: base.us.slice(), vs: base.vs.slice(), points: base.points.slice() };
2869
+ const cols = m.us.length - 1;
2870
+ const rows = m.vs.length - 1;
2871
+ const [pi, a] = locate(m.us, s);
2872
+ const [pj, b] = locate(m.vs, t);
2873
+ const ba = bernstein(a);
2874
+ const bb = bernstein(b);
2875
+ const nx = vectorMeshColumns(m);
2876
+ const ny = vectorMeshRows(m);
2877
+ const anchor = (ai, aj) => base.points[3 * aj * nx + 3 * ai] ?? { x: 0, y: 0 };
2878
+ const at0 = evalVectorMesh(base, s, t);
2879
+ const target = { x: at0.x + d.x, y: at0.y + d.y };
2880
+ let size = 1;
2881
+ for (const k of ["x", "y"]) {
2882
+ const vals = [anchor(pi, pj)[k], anchor(pi + 1, pj)[k], anchor(pi, pj + 1)[k], anchor(pi + 1, pj + 1)[k]];
2883
+ size = Math.max(size, Math.max(...vals) - Math.min(...vals));
2884
+ }
2885
+ const radius = REACH * size;
2886
+ const innerW = (ba[1] + ba[2]) * (bb[1] + bb[2]);
2887
+ const interior = Math.min(1, Math.max(0, innerW / 0.5625));
2888
+ const toBorder = Math.min(s, 1 - s, t, 1 - t);
2889
+ const nearBorder = Math.min(1, Math.max(0, 1 - toBorder / BORDER_ZONE));
2890
+ const meshCorner = (ai, aj) => (ai === 0 || ai === cols) && (aj === 0 || aj === rows);
2891
+ const onBorder = (ai, aj) => ai === 0 || ai === cols || aj === 0 || aj === rows;
2892
+ const reach = (ai, aj, wa, wb) => meshCorner(ai, aj) ? 0 : wa * wb * (onBorder(ai, aj) ? nearBorder : 1);
2893
+ const corner = [
2894
+ [pi, pj, reach(pi, pj, ba[0] + ba[1], bb[0] + bb[1])],
2895
+ [pi + 1, pj, reach(pi + 1, pj, ba[2] + ba[3], bb[0] + bb[1])],
2896
+ [pi, pj + 1, reach(pi, pj + 1, ba[0] + ba[1], bb[2] + bb[3])],
2897
+ [pi + 1, pj + 1, reach(pi + 1, pj + 1, ba[2] + ba[3], bb[2] + bb[3])]
2898
+ ];
2899
+ const share = new Array((cols + 1) * (rows + 1)).fill(0);
2900
+ for (let aj = 0; aj <= rows; aj++) {
2901
+ for (let ai = 0; ai <= cols; ai++) {
2902
+ const p = anchor(ai, aj);
2903
+ const x = Math.min(1, Math.max(0, Math.hypot(p.x - at0.x, p.y - at0.y) / radius));
2904
+ const fall = 0.5 * (1 + Math.cos(Math.PI * x));
2905
+ const edge = meshCorner(ai, aj) ? 0 : onBorder(ai, aj) ? nearBorder : 1;
2906
+ share[aj * (cols + 1) + ai] = fall * edge;
2907
+ }
2908
+ }
2909
+ for (const [ai, aj, w] of corner) share[aj * (cols + 1) + ai] = Math.max(share[aj * (cols + 1) + ai], (1 - interior) * w);
2910
+ const move = (idx, k, v2) => {
2911
+ const p = m.points[idx];
2912
+ m.points[idx] = { x: p.x + v2.x * k, y: p.y + v2.y * k };
2913
+ };
2914
+ for (let j = 0; j < ny; j++) {
2915
+ for (let i = 0; i < nx; i++) {
2916
+ const ai = Math.min(cols, Math.floor((i + 1) / 3));
2917
+ const aj = Math.min(rows, Math.floor((j + 1) / 3));
2918
+ move(j * nx + i, share[aj * (cols + 1) + ai], d);
2919
+ }
2920
+ }
2921
+ let norm = 0;
2922
+ for (const j of [1, 2]) for (const i of [1, 2]) norm += (ba[i] * bb[j]) ** 2;
2923
+ const cornerNorm = corner.reduce((acc, c) => acc + c[2] * c[2], 0);
2924
+ const innerPart = cornerNorm > 1e-12 ? Math.min(1, innerW / 0.15) : 1;
2925
+ const edgeHandles = [];
2926
+ for (const k of [1, 2]) {
2927
+ if (pi === 0) edgeHandles.push([(3 * pj + k) * nx + 3 * pi, ba[0] * bb[k]]);
2928
+ if (pi + 1 === cols) edgeHandles.push([(3 * pj + k) * nx + 3 * pi + 3, ba[3] * bb[k]]);
2929
+ if (pj === 0) edgeHandles.push([3 * pj * nx + 3 * pi + k, ba[k] * bb[0]]);
2930
+ if (pj + 1 === rows) edgeHandles.push([(3 * pj + 3) * nx + 3 * pi + k, ba[k] * bb[3]]);
2931
+ }
2932
+ const edgeNorm = edgeHandles.reduce((acc, e) => acc + e[1] * e[1], 0);
2933
+ const edgePart = edgeNorm > 1e-12 ? nearBorder : 0;
2934
+ const innerShare = (1 - edgePart) * innerPart;
2935
+ const cornerShare = (1 - edgePart) * (1 - innerPart);
2936
+ for (let iter = 0; iter < 60; iter++) {
2937
+ const now = evalVectorMesh(m, s, t);
2938
+ const rem = { x: target.x - now.x, y: target.y - now.y };
2939
+ if (Math.hypot(rem.x, rem.y) < 1e-9) break;
2940
+ if (innerShare > 0 && norm > 0) for (const j of [1, 2]) for (const i of [1, 2]) move((3 * pj + j) * nx + 3 * pi + i, ba[i] * bb[j] / norm * innerShare, rem);
2941
+ if (edgePart > 0) for (const [idx, w] of edgeHandles) move(idx, w / edgeNorm * edgePart, rem);
2942
+ if (cornerShare > 0 && cornerNorm > 0) {
2943
+ for (const [ai, aj, w] of corner) {
2944
+ const k = w / cornerNorm * cornerShare;
2945
+ for (let dj = -1; dj <= 1; dj++) {
2946
+ for (let di = -1; di <= 1; di++) {
2947
+ const i = 3 * ai + di;
2948
+ const j = 3 * aj + dj;
2949
+ if (i >= 0 && j >= 0 && i < nx && j < ny) move(j * nx + i, k, rem);
2950
+ }
2951
+ }
2952
+ }
2953
+ }
2954
+ for (let k = 0; k < 3; k++) smoothLines(m, Math.max(0, 3 * pi - 3), 3 * pi + 7, Math.max(0, 3 * pj - 3), 3 * pj + 7);
2955
+ }
2956
+ return m;
2957
+ }
2958
+ function polylineAt(pts) {
2959
+ const acc = [0];
2960
+ for (let i = 1; i < pts.length; i++) acc.push(acc[i - 1] + Math.hypot(pts[i].x - pts[i - 1].x, pts[i].y - pts[i - 1].y));
2961
+ const total = acc[acc.length - 1];
2962
+ return (u) => {
2963
+ if (!(total > 0)) return pts[0];
2964
+ const L = Math.min(1, Math.max(0, u)) * total;
2965
+ let i = 1;
2966
+ while (i < acc.length - 1 && acc[i] < L) i++;
2967
+ const seg = acc[i] - acc[i - 1];
2968
+ return lerp3(pts[i - 1], pts[i], seg > 0 ? (L - acc[i - 1]) / seg : 0);
2969
+ };
2970
+ }
2971
+ function sampleSubpath(sp, per = 24) {
2972
+ const n = sp.anchors.length;
2973
+ const out = [];
2974
+ for (let i = 0; i < n; i++) {
2975
+ const a = sp.anchors[i];
2976
+ const b = sp.anchors[(i + 1) % n];
2977
+ const c = [{ x: a.x, y: a.y }, a.out ?? a, b.in ?? b, { x: b.x, y: b.y }];
2978
+ for (let k = 0; k < per; k++) {
2979
+ const t = k / per;
2980
+ const ab = lerp3(c[0], c[1], t);
2981
+ const bc = lerp3(c[1], c[2], t);
2982
+ const cd = lerp3(c[2], c[3], t);
2983
+ out.push(lerp3(lerp3(ab, bc, t), lerp3(bc, cd, t), t));
2984
+ }
2985
+ }
2986
+ return out;
2987
+ }
2988
+ function vectorMeshFromOutline(sp, columns = 4, rows = 4) {
2989
+ if (!sp.closed || sp.anchors.length < 2) return null;
2990
+ let ring = sampleSubpath(sp);
2991
+ const xs = ring.map((p) => p.x);
2992
+ const ys = ring.map((p) => p.y);
2993
+ const box = { x0: Math.min(...xs), y0: Math.min(...ys), x1: Math.max(...xs), y1: Math.max(...ys) };
2994
+ if (!(box.x1 - box.x0 > 1e-9 && box.y1 - box.y0 > 1e-9)) return null;
2995
+ const nearest = (c2) => {
2996
+ let best = 0;
2997
+ for (let i = 1; i < ring.length; i++) if (Math.hypot(ring[i].x - c2.x, ring[i].y - c2.y) < Math.hypot(ring[best].x - c2.x, ring[best].y - c2.y)) best = i;
2998
+ return best;
2999
+ };
3000
+ const cornersOf = () => [
3001
+ { x: box.x0, y: box.y0 },
3002
+ { x: box.x1, y: box.y0 },
3003
+ { x: box.x1, y: box.y1 },
3004
+ { x: box.x0, y: box.y1 }
3005
+ ].map(nearest);
3006
+ let c = cornersOf();
3007
+ const n = ring.length;
3008
+ const fwd = (from, to) => (to - from + n) % n;
3009
+ if (!(fwd(c[0], c[1]) < fwd(c[0], c[2]) && fwd(c[0], c[2]) < fwd(c[0], c[3]))) {
3010
+ ring = ring.slice().reverse();
3011
+ c = cornersOf();
3012
+ if (!(fwd(c[0], c[1]) < fwd(c[0], c[2]) && fwd(c[0], c[2]) < fwd(c[0], c[3]))) return null;
3013
+ }
3014
+ if (new Set(c).size < 4) return null;
3015
+ const walk = (from, to) => {
3016
+ const out = [];
3017
+ for (let i = from; ; i = (i + 1) % n) {
3018
+ out.push(ring[i]);
3019
+ if (i === to) break;
3020
+ }
3021
+ return out;
3022
+ };
3023
+ const top = polylineAt(walk(c[0], c[1]));
3024
+ const right = polylineAt(walk(c[1], c[2]));
3025
+ const bottomLR = polylineAt(walk(c[2], c[3]).reverse());
3026
+ const leftTB = polylineAt(walk(c[3], c[0]).reverse());
3027
+ const p00 = top(0);
3028
+ const p10 = top(1);
3029
+ const p01 = bottomLR(0);
3030
+ const p11 = bottomLR(1);
3031
+ const coons = (s, t) => {
3032
+ const T = top(s);
3033
+ const B = bottomLR(s);
3034
+ const L = leftTB(t);
3035
+ const R = right(t);
3036
+ return {
3037
+ x: (1 - t) * T.x + t * B.x + (1 - s) * L.x + s * R.x - ((1 - s) * (1 - t) * p00.x + s * (1 - t) * p10.x + (1 - s) * t * p01.x + s * t * p11.x),
3038
+ y: (1 - t) * T.y + t * B.y + (1 - s) * L.y + s * R.y - ((1 - s) * (1 - t) * p00.y + s * (1 - t) * p10.y + (1 - s) * t * p01.y + s * t * p11.y)
3039
+ };
3040
+ };
3041
+ return fitVectorMesh(coons, evenKnots(clampCount(columns)), evenKnots(clampCount(rows)));
3042
+ }
3043
+ function vectorMeshOutline(m) {
3044
+ const nx = vectorMeshColumns(m);
3045
+ const ny = vectorMeshRows(m);
3046
+ const pt2 = (i, j) => m.points[j * nx + i];
3047
+ const runs = [];
3048
+ for (let i = 0; i + 3 < nx; i += 3) runs.push([pt2(i, 0), pt2(i + 1, 0), pt2(i + 2, 0), pt2(i + 3, 0)]);
3049
+ for (let j = 0; j + 3 < ny; j += 3) runs.push([pt2(nx - 1, j), pt2(nx - 1, j + 1), pt2(nx - 1, j + 2), pt2(nx - 1, j + 3)]);
3050
+ for (let i = nx - 1; i - 3 >= 0; i -= 3) runs.push([pt2(i, ny - 1), pt2(i - 1, ny - 1), pt2(i - 2, ny - 1), pt2(i - 3, ny - 1)]);
3051
+ for (let j = ny - 1; j - 3 >= 0; j -= 3) runs.push([pt2(0, j), pt2(0, j - 1), pt2(0, j - 2), pt2(0, j - 3)]);
3052
+ const anchors = runs.map((r, k) => {
3053
+ const prev = runs[(k + runs.length - 1) % runs.length];
3054
+ return { x: r[0].x, y: r[0].y, in: { ...prev[2] }, out: { ...r[1] } };
3055
+ });
3056
+ return { closed: true, anchors };
3057
+ }
3058
+ function vectorMeshEditPoints(m) {
3059
+ const nx = vectorMeshColumns(m);
3060
+ const ny = vectorMeshRows(m);
3061
+ const anchors = [];
3062
+ const handles = [];
3063
+ for (let j = 0; j < ny; j++) {
3064
+ for (let i = 0; i < nx; i++) {
3065
+ const ri = i % 3;
3066
+ const rj = j % 3;
3067
+ if (ri === 0 && rj === 0) anchors.push(j * nx + i);
3068
+ else if (ri === 0) handles.push({ index: j * nx + i, anchor: (rj === 1 ? j - 1 : j + 1) * nx + i });
3069
+ else if (rj === 0) handles.push({ index: j * nx + i, anchor: j * nx + (ri === 1 ? i - 1 : i + 1) });
3070
+ }
3071
+ }
3072
+ return { anchors, handles };
3073
+ }
3074
+ function vectorMeshGridCurves(m) {
3075
+ const nx = vectorMeshColumns(m);
3076
+ const ny = vectorMeshRows(m);
3077
+ const pt2 = (i, j) => m.points[j * nx + i];
3078
+ const out = [];
3079
+ for (let j = 0; j < ny; j += 3) for (let i = 0; i + 3 < nx; i += 3) out.push([pt2(i, j), pt2(i + 1, j), pt2(i + 2, j), pt2(i + 3, j)]);
3080
+ for (let i = 0; i < nx; i += 3) for (let j = 0; j + 3 < ny; j += 3) out.push([pt2(i, j), pt2(i, j + 1), pt2(i, j + 2), pt2(i, j + 3)]);
3081
+ return out;
3082
+ }
3083
+
2234
3084
  // src/vector/appearance.ts
2235
3085
  var VECTOR_EFFECT_DEFAULTS = {
2236
3086
  trim: { start: 0, end: 50, offset: 0, mode: "simultaneous" },
@@ -2251,7 +3101,8 @@ var VECTOR_EFFECT_DEFAULTS = {
2251
3101
  startOpacity: 100,
2252
3102
  endOpacity: 100,
2253
3103
  composite: "below"
2254
- }
3104
+ },
3105
+ envelope: { style: "arc", bend: 50, horizontal: 0, vertical: 0, orientation: "horizontal", rows: 2, columns: 2, mesh: null }
2255
3106
  };
2256
3107
  var VECTOR_EFFECT_LABELS = {
2257
3108
  trim: "Trim Paths",
@@ -2262,7 +3113,8 @@ var VECTOR_EFFECT_LABELS = {
2262
3113
  wiggle: "Wiggle Paths",
2263
3114
  offset: "Offset Paths",
2264
3115
  merge: "Merge Paths",
2265
- repeater: "Repeater"
3116
+ repeater: "Repeater",
3117
+ envelope: "Envelope"
2266
3118
  };
2267
3119
  var VECTOR_EFFECT_PARAM_SPECS = {
2268
3120
  trim: [
@@ -2348,8 +3200,65 @@ var VECTOR_EFFECT_PARAM_SPECS = {
2348
3200
  { value: "above", label: "Above" }
2349
3201
  ]
2350
3202
  }
3203
+ ],
3204
+ envelope: [
3205
+ {
3206
+ key: "style",
3207
+ label: "Warp",
3208
+ kind: "select",
3209
+ options: [...VECTOR_WARP_STYLES.map((value) => ({ value, label: VECTOR_WARP_STYLE_LABELS[value] })), { value: "mesh", label: "Mesh" }]
3210
+ },
3211
+ { key: "bend", label: "Bend %", kind: "number", min: -100, max: 100, step: 1, slider: true },
3212
+ { key: "horizontal", label: "Horizontal distortion %", kind: "number", min: -100, max: 100, step: 1, slider: true },
3213
+ { key: "vertical", label: "Vertical distortion %", kind: "number", min: -100, max: 100, step: 1, slider: true },
3214
+ {
3215
+ key: "orientation",
3216
+ label: "Orientation",
3217
+ kind: "select",
3218
+ options: [
3219
+ { value: "horizontal", label: "Horizontal" },
3220
+ { value: "vertical", label: "Vertical" }
3221
+ ]
3222
+ },
3223
+ { key: "rows", label: "Mesh rows", kind: "number", min: 1, max: 16, step: 1 },
3224
+ { key: "columns", label: "Mesh columns", kind: "number", min: 1, max: 16, step: 1 }
2351
3225
  ]
2352
3226
  };
3227
+ var cloneMesh = (m) => ({ us: m.us.slice(), vs: m.vs.slice(), points: m.points.map((p) => ({ x: p.x, y: p.y })) });
3228
+ function vectorEnvelopeMesh(params, bounds) {
3229
+ if (params.style === "mesh") return params.mesh ?? vectorIdentityMesh(bounds, params.columns, params.rows);
3230
+ const preset = vectorWarpPresetMesh({ style: params.style, bend: params.bend, horizontal: params.horizontal, vertical: params.vertical, orientation: params.orientation }, bounds);
3231
+ return preset ? resizeVectorMesh(preset, params.columns, params.rows) : vectorIdentityMesh(bounds, params.columns, params.rows);
3232
+ }
3233
+ function vectorEnvelopeMap(params, bounds) {
3234
+ if (!bounds || !(bounds.width > 0 && bounds.height > 0)) return null;
3235
+ const mesh = params.style === "mesh" ? params.mesh : vectorWarpPresetMesh({ style: params.style, bend: params.bend, horizontal: params.horizontal, vertical: params.vertical, orientation: params.orientation }, bounds);
3236
+ if (!mesh) return null;
3237
+ const { x, y, width, height } = bounds;
3238
+ return (p) => evalVectorMesh(mesh, (p.x - x) / width, (p.y - y) / height);
3239
+ }
3240
+ function pathsBounds(paths) {
3241
+ let minX = Infinity;
3242
+ let minY = Infinity;
3243
+ let maxX = -Infinity;
3244
+ let maxY = -Infinity;
3245
+ for (const p of paths) {
3246
+ const b = bezierPathBounds(p);
3247
+ if (!b) continue;
3248
+ minX = Math.min(minX, b.minX);
3249
+ minY = Math.min(minY, b.minY);
3250
+ maxX = Math.max(maxX, b.maxX);
3251
+ maxY = Math.max(maxY, b.maxY);
3252
+ }
3253
+ return minX <= maxX ? { x: minX, y: minY, width: maxX - minX, height: maxY - minY } : null;
3254
+ }
3255
+ function envelopePaths(paths, params) {
3256
+ const bounds = pathsBounds(paths);
3257
+ const f = vectorEnvelopeMap(params, bounds);
3258
+ if (!f || !bounds) return paths;
3259
+ const tol = Math.min(0.25, Math.max(0.01, Math.hypot(bounds.width, bounds.height) / 2e3));
3260
+ return paths.map((p) => p.length ? mapBezierPath(p, f, tol) : p);
3261
+ }
2353
3262
  var isVec2 = (v2) => !!v2 && typeof v2 === "object" && Number.isFinite(v2.x) && Number.isFinite(v2.y);
2354
3263
  function normalizeVectorEffectParams(type, params, base) {
2355
3264
  const defaults = VECTOR_EFFECT_DEFAULTS[type];
@@ -2363,6 +3272,19 @@ function normalizeVectorEffectParams(type, params, base) {
2363
3272
  else if (spec.kind === "vec2" && isVec2(v2)) out[spec.key] = { x: v2.x, y: v2.y };
2364
3273
  else if (spec.kind === "select" && spec.options.some((o) => o.value === v2)) out[spec.key] = v2;
2365
3274
  }
3275
+ if (type === "envelope") {
3276
+ const o = out;
3277
+ o.rows = Math.round(o.rows);
3278
+ o.columns = Math.round(o.columns);
3279
+ const given = params?.mesh;
3280
+ let mesh = given !== void 0 ? isValidVectorMesh(given) ? cloneMesh(given) : null : o.mesh;
3281
+ if (o.style !== "mesh") mesh = null;
3282
+ if (mesh && given !== void 0) {
3283
+ o.columns = mesh.us.length - 1;
3284
+ o.rows = mesh.vs.length - 1;
3285
+ } else if (mesh) mesh = resizeVectorMesh(mesh, o.columns, o.rows);
3286
+ o.mesh = mesh;
3287
+ }
2366
3288
  return out;
2367
3289
  }
2368
3290
  function createVectorEffect(type, id, params) {
@@ -2543,6 +3465,11 @@ function applyVectorEffects(shapes, effects) {
2543
3465
  list = [{ ...first, path: mergePaths(list.map((s) => s.path), e.params.mode) }];
2544
3466
  break;
2545
3467
  }
3468
+ case "envelope": {
3469
+ const params = e.params;
3470
+ list = mapPaths(list, (p) => envelopePaths(p, params));
3471
+ break;
3472
+ }
2546
3473
  case "repeater": {
2547
3474
  const r = e.params;
2548
3475
  const copies = repeatPaths(
@@ -2589,14 +3516,35 @@ function liveBooleanShapes(group) {
2589
3516
  return [{ source, path: foldVectorBooleanOperands(operands), opacity: 1, order: [0] }];
2590
3517
  }
2591
3518
  var shapeCache = /* @__PURE__ */ new WeakMap();
3519
+ function baseShapes(node) {
3520
+ if (node.type === "path") return [{ source: node, path: subpathsToBezier(node.subpaths), opacity: 1, order: [] }];
3521
+ if (node.liveBoolean) return liveBooleanShapes(node);
3522
+ return node.children.flatMap((c, i) => childShapes(c, i));
3523
+ }
3524
+ function vectorEffectInputPaths(node, effectId) {
3525
+ const list = node.effects ?? [];
3526
+ const i = list.findIndex((e) => e.id === effectId);
3527
+ if (i < 0) return null;
3528
+ return applyVectorEffects(baseShapes(node), list.slice(0, i)).map((s) => s.path);
3529
+ }
3530
+ function vectorEffectInputBounds(node, effectId) {
3531
+ const paths = vectorEffectInputPaths(node, effectId);
3532
+ return paths ? pathsBounds(paths) : null;
3533
+ }
3534
+ function vectorNodeEnvelope(node) {
3535
+ return node?.effects?.find((e) => e.type === "envelope" && e.enabled !== false) ?? null;
3536
+ }
3537
+ function vectorEnvelopeFrame(node, effectId) {
3538
+ const e = effectId ? node.effects?.find((x) => x.id === effectId) : vectorNodeEnvelope(node);
3539
+ if (!e || e.type !== "envelope") return null;
3540
+ const bounds = vectorEffectInputBounds(node, e.id);
3541
+ if (!bounds || !(bounds.width > 0 && bounds.height > 0)) return null;
3542
+ return { effectId: e.id, bounds, mesh: vectorEnvelopeMesh(e.params, bounds) };
3543
+ }
2592
3544
  function vectorAppearanceShapes(node) {
2593
3545
  const hit = shapeCache.get(node);
2594
3546
  if (hit) return hit;
2595
- let base;
2596
- if (node.type === "path") base = [{ source: node, path: subpathsToBezier(node.subpaths), opacity: 1, order: [] }];
2597
- else if (node.liveBoolean) base = liveBooleanShapes(node);
2598
- else base = node.children.flatMap((c, i) => childShapes(c, i));
2599
- const out = applyVectorEffects(base, node.effects).sort((a, b) => compareOrder(a.order, b.order));
3547
+ const out = applyVectorEffects(baseShapes(node), node.effects).sort((a, b) => compareOrder(a.order, b.order));
2600
3548
  shapeCache.set(node, out);
2601
3549
  return out;
2602
3550
  }
@@ -2693,10 +3641,19 @@ function sanitizeVectorEffects(value) {
2693
3641
  }
2694
3642
  return out;
2695
3643
  }
2696
- function transformVectorEffects(effects, m) {
3644
+ function transformVectorEffects(effects, m, node) {
2697
3645
  const lin = (v2) => ({ x: m[0] * v2.x + m[2] * v2.y, y: m[1] * v2.x + m[3] * v2.y });
3646
+ const full = (v2) => ({ x: m[0] * v2.x + m[2] * v2.y + m[4], y: m[1] * v2.x + m[3] * v2.y + m[5] });
2698
3647
  const mirror = m[0] * m[3] - m[1] * m[2] < 0 ? -1 : 1;
3648
+ const axisScale = Math.abs(m[1]) < 1e-12 && Math.abs(m[2]) < 1e-12 && m[0] > 0 && m[3] > 0;
2699
3649
  return effects.map((e) => {
3650
+ if (e.type === "envelope") {
3651
+ if (e.params.style === "mesh") return e.params.mesh ? { ...e, params: { ...e.params, mesh: mapVectorMeshPoints(e.params.mesh, full) } } : e;
3652
+ if (axisScale || !node) return e;
3653
+ const bounds = vectorEffectInputBounds(node, e.id);
3654
+ if (!bounds || !vectorEnvelopeMap(e.params, bounds)) return e;
3655
+ return { ...e, params: { ...e.params, style: "mesh", mesh: mapVectorMeshPoints(vectorEnvelopeMesh(e.params, bounds), full) } };
3656
+ }
2700
3657
  if (e.type === "twist") return { ...e, params: { ...e.params, center: lin(e.params.center), angle: e.params.angle * mirror } };
2701
3658
  if (e.type === "repeater")
2702
3659
  return { ...e, params: { ...e.params, position: lin(e.params.position), anchor: lin(e.params.anchor), rotation: e.params.rotation * mirror } };
@@ -3030,7 +3987,7 @@ function mapAnchor(a, m) {
3030
3987
  }
3031
3988
  function bakeIntoGeometry(node, m) {
3032
3989
  if (isIdentityMatrix(m)) return node;
3033
- if (node.effects?.length) node = { ...node, effects: transformVectorEffects(node.effects, m) };
3990
+ if (node.effects?.length) node = { ...node, effects: transformVectorEffects(node.effects, m, node) };
3034
3991
  if (node.type === "group") {
3035
3992
  const group = node;
3036
3993
  const children = group.children.map((c) => transformVectorNode(c, m));
@@ -3835,8 +4792,8 @@ function resolveGradient(id, raw) {
3835
4792
  const len2 = Math.hypot(x2 - x1, y2 - y1);
3836
4793
  const d = len2 > 0 ? { x: (x2 - x1) / len2, y: (y2 - y1) / len2 } : { x: 0, y: 1 };
3837
4794
  const along2 = (x, y) => x * d.x + y * d.y;
3838
- const same = len2 > 0 && Math.abs((c.x2 - c.x1) * d.y - (c.y2 - c.y1) * d.x) < 1e-3 && Math.abs(along2(c.x1, c.y1) - along2(x1, y1)) < 1e-3 && Math.abs(along2(c.x2, c.y2) - along2(x2, y2)) < 1e-3;
3839
- if (same) return { type: "linear", angle, stops, ...spread };
4795
+ const same2 = len2 > 0 && Math.abs((c.x2 - c.x1) * d.y - (c.y2 - c.y1) * d.x) < 1e-3 && Math.abs(along2(c.x1, c.y1) - along2(x1, y1)) < 1e-3 && Math.abs(along2(c.x2, c.y2) - along2(x2, y2)) < 1e-3;
4796
+ if (same2) return { type: "linear", angle, stops, ...spread };
3840
4797
  }
3841
4798
  const start = { x: x1, y: y1 };
3842
4799
  const end = { x: x2, y: y2 };
@@ -4893,8 +5850,8 @@ function vectorBlendStepCount(a, b, options, length2) {
4893
5850
  if (cd > 1 / 255) return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(cd * 255 / 2)));
4894
5851
  const ba = vectorNodeBounds(a);
4895
5852
  const bb = vectorNodeBounds(b);
4896
- const dist4 = ba && bb ? Math.max(Math.abs(ba.x - bb.x), Math.abs(ba.y - bb.y), Math.abs(ba.x + ba.width - bb.x - bb.width), Math.abs(ba.y + ba.height - bb.y - bb.height)) : length2;
4897
- return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(dist4 / 2)));
5853
+ const dist5 = ba && bb ? Math.max(Math.abs(ba.x - bb.x), Math.abs(ba.y - bb.y), Math.abs(ba.x + ba.width - bb.x - bb.width), Math.abs(ba.y + ba.height - bb.y - bb.height)) : length2;
5854
+ return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(dist5 / 2)));
4898
5855
  }
4899
5856
  return Math.min(VECTOR_BLEND_MAX_STEPS, Math.max(1, Math.round(Number.isFinite(o.steps) ? o.steps : DEFAULT_VECTOR_BLEND_STEPS)));
4900
5857
  }
@@ -5065,6 +6022,169 @@ function releaseVectorBlend(group, newId) {
5065
6022
  return [...keys, group.transform ? transformVectorNode(path, m) : path];
5066
6023
  }
5067
6024
 
6025
+ // src/vector/perspective.ts
6026
+ function solve8(rows) {
6027
+ const m = rows.map((r) => r.slice());
6028
+ for (let col = 0; col < 8; col++) {
6029
+ let pivot = col;
6030
+ for (let r = col + 1; r < 8; r++) if (Math.abs(m[r][col]) > Math.abs(m[pivot][col])) pivot = r;
6031
+ if (!(Math.abs(m[pivot][col]) >= 1e-12)) return null;
6032
+ [m[col], m[pivot]] = [m[pivot], m[col]];
6033
+ const p = m[col];
6034
+ for (let r = 0; r < 8; r++) {
6035
+ if (r === col) continue;
6036
+ const f = m[r][col] / p[col];
6037
+ if (f !== 0) for (let k = 0; k < 9; k++) m[r][k] -= f * p[k];
6038
+ }
6039
+ }
6040
+ const out = [];
6041
+ for (let i = 0; i < 8; i++) {
6042
+ const v2 = m[i][8] / m[i][i];
6043
+ if (!Number.isFinite(v2)) return null;
6044
+ out.push(v2);
6045
+ }
6046
+ return out;
6047
+ }
6048
+ function applyVectorHomography(h, p) {
6049
+ const w = h[6] * p.x + h[7] * p.y + h[8];
6050
+ if (!Number.isFinite(w) || Math.abs(w) < 1e-12) return null;
6051
+ const x = (h[0] * p.x + h[1] * p.y + h[2]) / w;
6052
+ const y = (h[3] * p.x + h[4] * p.y + h[5]) / w;
6053
+ return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
6054
+ }
6055
+ var finite = (p) => Number.isFinite(p.x) && Number.isFinite(p.y);
6056
+ function vectorHomography(from, to) {
6057
+ if (from.length !== 4 || to.length !== 4 || ![...from, ...to].every(finite)) return null;
6058
+ const rows = [];
6059
+ for (let i = 0; i < 4; i++) {
6060
+ const { x, y } = from[i];
6061
+ const { x: u, y: v2 } = to[i];
6062
+ rows.push([x, y, 1, 0, 0, 0, -u * x, -u * y, u]);
6063
+ rows.push([0, 0, 0, x, y, 1, -v2 * x, -v2 * y, v2]);
6064
+ }
6065
+ const h = solve8(rows);
6066
+ if (!h) return null;
6067
+ const out = [h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], 1];
6068
+ for (let i = 0; i < 4; i++) {
6069
+ const r = applyVectorHomography(out, from[i]);
6070
+ const q = to[i];
6071
+ const scale3 = 1 + Math.max(Math.abs(q.x), Math.abs(q.y));
6072
+ if (!r || Math.abs(r.x - q.x) > 1e-6 * scale3 || Math.abs(r.y - q.y) > 1e-6 * scale3) return null;
6073
+ }
6074
+ return out;
6075
+ }
6076
+ function vectorHomographyKeepsQuad(h, quad) {
6077
+ const ws = quad.map((p) => h[6] * p.x + h[7] * p.y + h[8]);
6078
+ return ws.every((w) => w > 1e-9) || ws.every((w) => w < -1e-9);
6079
+ }
6080
+ function orderVectorQuad(points) {
6081
+ if (points.length !== 4 || !points.every(finite)) return null;
6082
+ const cx = points.reduce((s, p) => s + p.x, 0) / 4;
6083
+ const cy = points.reduce((s, p) => s + p.y, 0) / 4;
6084
+ const q = points.slice().sort((a, b) => Math.atan2(a.y - cy, a.x - cx) - Math.atan2(b.y - cy, b.x - cx));
6085
+ let first = 0;
6086
+ for (let i = 1; i < 4; i++) if (q[i].x + q[i].y < q[first].x + q[first].y) first = i;
6087
+ const ordered = [...q.slice(first), ...q.slice(0, first)];
6088
+ const cross2 = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
6089
+ const turns = ordered.map((_, i) => cross2(ordered[i], ordered[(i + 1) % 4], ordered[(i + 2) % 4]));
6090
+ const area = turns.reduce((s, t) => s + t, 0) / 2;
6091
+ const xs = ordered.map((p) => p.x);
6092
+ const ys = ordered.map((p) => p.y);
6093
+ const size = Math.max(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));
6094
+ const convex = turns.every((t) => t > 1e-9 * size * size);
6095
+ return convex && area > 1e-6 * size * size ? ordered : null;
6096
+ }
6097
+
6098
+ // src/vector/distort.ts
6099
+ var VECTOR_DISTORT_TOLERANCE = 0.05;
6100
+ var CORNER_INDEX = { nw: 0, ne: 1, se: 2, sw: 3 };
6101
+ var SIDE_CORNERS = { n: [0, 1], e: [1, 2], s: [2, 3], w: [3, 0] };
6102
+ var vectorBoxCorners = (b) => [
6103
+ { x: b.x, y: b.y },
6104
+ { x: b.x + b.width, y: b.y },
6105
+ { x: b.x + b.width, y: b.y + b.height },
6106
+ { x: b.x, y: b.y + b.height }
6107
+ ];
6108
+ function vectorDistortQuad(b, handle, d, mode = "distort") {
6109
+ const q = vectorBoxCorners(b);
6110
+ const i = CORNER_INDEX[handle];
6111
+ if (i !== void 0) {
6112
+ if (mode === "perspective") {
6113
+ if (Math.abs(d.x) >= Math.abs(d.y)) {
6114
+ const partner = [1, 0, 3, 2][i];
6115
+ q[i].x += d.x;
6116
+ q[partner].x -= d.x;
6117
+ } else {
6118
+ const partner = [3, 2, 1, 0][i];
6119
+ q[i].y += d.y;
6120
+ q[partner].y -= d.y;
6121
+ }
6122
+ } else {
6123
+ q[i].x += d.x;
6124
+ q[i].y += d.y;
6125
+ }
6126
+ return q;
6127
+ }
6128
+ const side = SIDE_CORNERS[handle];
6129
+ if (side) for (const k of side) q[k] = { x: q[k].x + d.x, y: q[k].y + d.y };
6130
+ return q;
6131
+ }
6132
+ var about = (o, m) => multiplyMatrices(multiplyMatrices([1, 0, 0, 1, -o.x, -o.y], m), [1, 0, 0, 1, o.x, o.y]);
6133
+ function vectorShearForSide(b, handle, d, fromCenter = false) {
6134
+ const center = { x: b.x + b.width / 2, y: b.y + b.height / 2 };
6135
+ if (handle === "n" || handle === "s") {
6136
+ const o = fromCenter ? center : { x: center.x, y: handle === "n" ? b.y + b.height : b.y };
6137
+ const lever = (handle === "n" ? b.y : b.y + b.height) - o.y;
6138
+ return about(o, [1, 0, Math.abs(lever) > 1e-9 ? d.x / lever : 0, 1, 0, 0]);
6139
+ }
6140
+ if (handle === "e" || handle === "w") {
6141
+ const o = fromCenter ? center : { x: handle === "w" ? b.x + b.width : b.x, y: center.y };
6142
+ const lever = (handle === "w" ? b.x : b.x + b.width) - o.x;
6143
+ return about(o, [1, Math.abs(lever) > 1e-9 ? d.y / lever : 0, 0, 1, 0, 0]);
6144
+ }
6145
+ return [1, 0, 0, 1, 0, 0];
6146
+ }
6147
+ function vectorSkewMatrix(ax, ay, origin = { x: 0, y: 0 }) {
6148
+ const tx = Math.tan(ax * Math.PI / 180);
6149
+ const ty = Math.tan(ay * Math.PI / 180);
6150
+ return about(origin, [1, ty, tx, 1, 0, 0]);
6151
+ }
6152
+ function mapEffects(node, f) {
6153
+ if (!node.effects?.length) return void 0;
6154
+ return node.effects.map((e) => {
6155
+ if (e.type !== "envelope") return e;
6156
+ if (e.params.style === "mesh") return e.params.mesh ? { ...e, params: { ...e.params, mesh: mapVectorMeshPoints(e.params.mesh, f) } } : e;
6157
+ const bounds = vectorEffectInputBounds(node, e.id);
6158
+ if (!bounds || !vectorEnvelopeMap(e.params, bounds)) return e;
6159
+ return { ...e, params: { ...e.params, style: "mesh", mesh: mapVectorMeshPoints(vectorEnvelopeMesh(e.params, bounds), f) } };
6160
+ });
6161
+ }
6162
+ function distortVectorNode(node, parentToDoc, f, tolerance = VECTOR_DISTORT_TOLERANCE) {
6163
+ const toDoc = multiplyMatrices(nodeMatrix(node), parentToDoc);
6164
+ const inv = invertMatrix(toDoc);
6165
+ const local = (p) => applyMatrix(inv, f(applyMatrix(toDoc, p)));
6166
+ const effects = mapEffects(node, local);
6167
+ if (node.type === "group") {
6168
+ const group = node;
6169
+ const children = group.children.map((c) => distortVectorNode(c, toDoc, f, tolerance));
6170
+ const spine = group.blend?.spine;
6171
+ return {
6172
+ ...group,
6173
+ children,
6174
+ ...effects ? { effects } : {},
6175
+ ...spine ? { blend: { ...group.blend, spine: mapVectorSubpaths([spine], local, tolerance)[0] } } : {}
6176
+ };
6177
+ }
6178
+ const path = node;
6179
+ return {
6180
+ ...path,
6181
+ ...effects ? { effects } : {},
6182
+ ...path.fill !== void 0 ? { fill: mapVectorPaint(path.fill, local) } : {},
6183
+ ...path.stroke !== void 0 ? { stroke: mapVectorPaint(path.stroke, local) } : {},
6184
+ subpaths: mapVectorSubpaths(path.subpaths, local, tolerance)
6185
+ };
6186
+ }
6187
+
5068
6188
  // src/vector/snap.ts
5069
6189
  var VECTOR_CONSTRUCTION_ANGLES = [0, 45, 90, 135];
5070
6190
  function constrainToLeave(leave, p) {
@@ -5106,8 +6226,8 @@ function within(sorted, key, v2, tol) {
5106
6226
  }
5107
6227
  var linesNear = (lines, v2, tol) => within(lines, (l) => l.v, v2, tol);
5108
6228
  var distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
5109
- var toPoint = (p) => [p.x, p.y];
5110
- var toVec2 = (p) => ({ x: p[0], y: p[1] });
6229
+ var toPoint2 = (p) => [p.x, p.y];
6230
+ var toVec3 = (p) => ({ x: p[0], y: p[1] });
5111
6231
  var LABELS = {
5112
6232
  anchor: "anchor",
5113
6233
  centre: "centre",
@@ -5225,7 +6345,7 @@ var VectorSnapTargets = class _VectorSnapTargets {
5225
6345
  const count = subpathSegmentCount(sp);
5226
6346
  for (let i = 0; i < count && budget > 0; i++) {
5227
6347
  if (moving(i) || moving((i + 1) % n)) continue;
5228
- const seg = subpathSegment(sp, i).map((q) => toPoint(applyMatrix(m, toVec2(q))));
6348
+ const seg = subpathSegment(sp, i).map((q) => toPoint2(applyMatrix(m, toVec3(q))));
5229
6349
  let minX = Infinity;
5230
6350
  let minY = Infinity;
5231
6351
  let maxX = -Infinity;
@@ -5432,7 +6552,7 @@ var VectorSnapTargets = class _VectorSnapTargets {
5432
6552
  /** Target segments whose control box reaches `p` and which pass within `tol` of it, nearest first. */
5433
6553
  segmentsNear(p, tol) {
5434
6554
  const out = [];
5435
- const pt2 = toPoint(p);
6555
+ const pt2 = toPoint2(p);
5436
6556
  for (const s of this.segments) {
5437
6557
  if (p.x < s.minX - tol || p.x > s.maxX + tol || p.y < s.minY - tol || p.y > s.maxY + tol) continue;
5438
6558
  const hit = segmentNearest(s.seg, pt2);
@@ -5441,7 +6561,7 @@ var VectorSnapTargets = class _VectorSnapTargets {
5441
6561
  return out.sort((a, b) => a.distance - b.distance);
5442
6562
  }
5443
6563
  onSegment(near2) {
5444
- return near2.length ? toVec2(segmentPoint(near2[0].seg, near2[0].t)) : null;
6564
+ return near2.length ? toVec3(segmentPoint(near2[0].seg, near2[0].t)) : null;
5445
6565
  }
5446
6566
  /** Where two of the target segments near `p` cross, the crossing nearest `p` within `tol`. */
5447
6567
  crossing(near2, p, tol) {
@@ -5451,7 +6571,7 @@ var VectorSnapTargets = class _VectorSnapTargets {
5451
6571
  for (let i = 0; i < list.length; i++) {
5452
6572
  for (let j = i + 1; j < list.length; j++) {
5453
6573
  for (const hit of segmentIntersections(list[i].seg, list[j].seg)) {
5454
- const q = toVec2(hit.point);
6574
+ const q = toVec3(hit.point);
5455
6575
  const d = distance(q, p);
5456
6576
  if (d <= tol && d < bestD) {
5457
6577
  best = q;
@@ -6030,7 +7150,7 @@ function splitSubpathAt(sp, segment, t, endTolerance = 1e-6) {
6030
7150
  return cutSubpathAt(subpath, [index]);
6031
7151
  }
6032
7152
  var endOf2 = (sp, last) => sp.anchors[last ? sp.anchors.length - 1 : 0];
6033
- var dist2 = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
7153
+ var dist3 = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
6034
7154
  function reverse(sp) {
6035
7155
  return {
6036
7156
  closed: sp.closed,
@@ -6057,7 +7177,7 @@ function joinSubpaths(subpaths, tolerance) {
6057
7177
  const sp = open[0];
6058
7178
  const first = sp.anchors[0];
6059
7179
  const last = sp.anchors[sp.anchors.length - 1];
6060
- if (sp.anchors.length > 2 && dist2(first, last) <= tolerance) {
7180
+ if (sp.anchors.length > 2 && dist3(first, last) <= tolerance) {
6061
7181
  sp.anchors.pop();
6062
7182
  sp.anchors[0] = tidy({ ...first, in: last.in ?? null }, false);
6063
7183
  } else {
@@ -6073,7 +7193,7 @@ function joinSubpaths(subpaths, tolerance) {
6073
7193
  for (let j = i + 1; j < open.length; j++) {
6074
7194
  for (const ei of [false, true]) {
6075
7195
  for (const ej of [false, true]) {
6076
- const d = dist2(endOf2(open[i], ei), endOf2(open[j], ej));
7196
+ const d = dist3(endOf2(open[i], ei), endOf2(open[j], ej));
6077
7197
  if (d < best.d) best = { d, i, j, ei, ej };
6078
7198
  }
6079
7199
  }
@@ -6113,8 +7233,8 @@ function smoothSubpath(sp, amount = 1, only) {
6113
7233
  const p = orig[i];
6114
7234
  const dir = unit4(next.x - prev.x, next.y - prev.y);
6115
7235
  if (!dir) continue;
6116
- const li = dist2(p, prev) / 3;
6117
- const lo = dist2(p, next) / 3;
7236
+ const li = dist3(p, prev) / 3;
7237
+ const lo = dist3(p, next) / 3;
6118
7238
  const hin = p.in ?? p;
6119
7239
  const hout = p.out ?? p;
6120
7240
  const target = (h, x, y) => ({ x: h.x + (x - h.x) * t, y: h.y + (y - h.y) * t });
@@ -6605,7 +7725,7 @@ function vectorInkShapeName(shape) {
6605
7725
  return shape.points.length === 3 ? "triangle" : "polygon";
6606
7726
  }
6607
7727
  }
6608
- var dist3 = (a, b) => Math.hypot(b[0] - a[0], b[1] - a[1]);
7728
+ var dist4 = (a, b) => Math.hypot(b[0] - a[0], b[1] - a[1]);
6609
7729
  function bbox(pts) {
6610
7730
  if (!pts.length) return [0, 0, 0, 0];
6611
7731
  let [x0, y0] = pts[0];
@@ -6623,7 +7743,7 @@ function segDistance(a, b, p) {
6623
7743
  const dy = b[1] - a[1];
6624
7744
  const len2 = dx * dx + dy * dy;
6625
7745
  const t = len2 > 0 ? Math.min(1, Math.max(0, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2)) : 0;
6626
- return dist3([a[0] + t * dx, a[1] + t * dy], p);
7746
+ return dist4([a[0] + t * dx, a[1] + t * dy], p);
6627
7747
  }
6628
7748
  function polygonDistance(c, p) {
6629
7749
  let best = Infinity;
@@ -6685,7 +7805,7 @@ function corners(p, diag) {
6685
7805
  let far = 0;
6686
7806
  let farD = 0;
6687
7807
  p.forEach((q, i) => {
6688
- const d = dist3(start, q);
7808
+ const d = dist4(start, q);
6689
7809
  if (d > farD) {
6690
7810
  farD = d;
6691
7811
  far = i;
@@ -6712,7 +7832,7 @@ function corners(p, diag) {
6712
7832
  }
6713
7833
  let short = -1;
6714
7834
  for (let j = 0; j < n; j++) {
6715
- if (dist3(ring[j], ring[(j + 1) % n]) < 0.1 * diag) {
7835
+ if (dist4(ring[j], ring[(j + 1) % n]) < 0.1 * diag) {
6716
7836
  short = j;
6717
7837
  break;
6718
7838
  }
@@ -6752,8 +7872,8 @@ function recognizeInkShape(points, options = {}) {
6752
7872
  const diag = Math.hypot(w, h);
6753
7873
  if (size < minSize) return null;
6754
7874
  let length2 = 0;
6755
- for (let i = 1; i < p.length; i++) length2 += dist3(p[i - 1], p[i]);
6756
- const gap = dist3(first, last);
7875
+ for (let i = 1; i < p.length; i++) length2 += dist4(p[i - 1], p[i]);
7876
+ const gap = dist4(first, last);
6757
7877
  if (gap > 0.85 * length2 && gap >= minSize) {
6758
7878
  const off = Math.max(...p.map((q) => segDistance(first, last, q)));
6759
7879
  if (off <= Math.max(0.06 * gap, 1.5 * minSize / 6)) return { kind: "line", a: first, b: last };
@@ -7194,8 +8314,8 @@ function getVectorSelectionFrame(doc, ids) {
7194
8314
  const nodes = ids.map((id) => ({ node: findVectorNode(doc.nodes, id), parent: vectorParentMatrix(doc.nodes, id) ?? IDENTITY_MATRIX })).filter((n) => n.node);
7195
8315
  if (!nodes.length) return null;
7196
8316
  const angles = nodes.map((n) => matrixRotation(multiplyMatrices(nodeMatrix(n.node), n.parent)));
7197
- const same = angles.every((a) => Math.abs(normalizeAngle(a - angles[0])) < 1e-6);
7198
- const angle = same ? angles[0] : 0;
8317
+ const same2 = angles.every((a) => Math.abs(normalizeAngle(a - angles[0])) < 1e-6);
8318
+ const angle = same2 ? angles[0] : 0;
7199
8319
  const unrotate = rotateMatrix(-angle);
7200
8320
  const b = unionBounds(nodes.map((n) => vectorNodeBounds(n.node, multiplyMatrices(n.parent, unrotate))));
7201
8321
  return b ? { ...b, angle } : null;
@@ -7230,6 +8350,13 @@ function getVectorSelectionBoxGeometry(frame2, viewport) {
7230
8350
  const r = (n) => Math.round(n * 1e3) / 1e3;
7231
8351
  return { box, angle: frame2.angle, transform: frame2.angle ? `rotate(${r(frame2.angle)} ${r(viewport.x)} ${r(viewport.y)})` : "", handles, corners: corners2 };
7232
8352
  }
8353
+ function getVectorEnvelopeTarget(doc, selection) {
8354
+ if (selection.length !== 1) return null;
8355
+ const node = findVectorNode(doc.nodes, selection[0]);
8356
+ if (!node || !vectorNodeEnvelope(node)) return null;
8357
+ const frame2 = vectorEnvelopeFrame(node);
8358
+ return frame2 ? { node, frame: frame2, world: vectorWorldMatrix(doc.nodes, node.id) ?? IDENTITY_MATRIX } : null;
8359
+ }
7233
8360
  var RESIZE_CURSOR_ORDER = ["ew-resize", "nwse-resize", "ns-resize", "nesw-resize"];
7234
8361
  var HANDLE_ANGLES = { e: 0, se: 45, s: 90, sw: 135, w: 180, nw: 225, n: 270, ne: 315 };
7235
8362
  function vectorResizeCursor(handle, angle = 0) {
@@ -7254,7 +8381,8 @@ var TOOL_LABELS = {
7254
8381
  shapeBuilder: "Shape Builder",
7255
8382
  paintBucket: "Paint Bucket",
7256
8383
  blend: "Blend",
7257
- gradient: "Gradient"
8384
+ gradient: "Gradient",
8385
+ envelope: "Envelope Mesh"
7258
8386
  };
7259
8387
  var TOOL_KEYS = {
7260
8388
  v: "select",
@@ -7273,7 +8401,8 @@ var TOOL_KEYS = {
7273
8401
  m: "shapeBuilder",
7274
8402
  b: "paintBucket",
7275
8403
  j: "blend",
7276
- g: "gradient"
8404
+ g: "gradient",
8405
+ u: "envelope"
7277
8406
  };
7278
8407
  function vectorToolLabel(tool) {
7279
8408
  return TOOL_LABELS[tool];
@@ -7295,7 +8424,8 @@ var VECTOR_TOOL_SHORTCUTS = {
7295
8424
  shapeBuilder: "M",
7296
8425
  paintBucket: "B",
7297
8426
  blend: "J",
7298
- gradient: "G"
8427
+ gradient: "G",
8428
+ envelope: "U"
7299
8429
  };
7300
8430
  var VECTOR_TOOL_GLYPHS = {
7301
8431
  select: "\u2196",
@@ -7314,7 +8444,8 @@ var VECTOR_TOOL_GLYPHS = {
7314
8444
  shapeBuilder: "\u2295",
7315
8445
  paintBucket: "\u25CD",
7316
8446
  blend: "\u29D3",
7317
- gradient: "\u25D0"
8447
+ gradient: "\u25D0",
8448
+ envelope: "\u2317"
7318
8449
  };
7319
8450
  var SHAPE_TOOLS = /* @__PURE__ */ new Set(["rect", "ellipse", "polygon", "star", "line"]);
7320
8451
  var BOOLEAN_OPS = {
@@ -7367,7 +8498,7 @@ function spliceTree(nodes, id, replacements) {
7367
8498
  }
7368
8499
  return changed ? out : nodes;
7369
8500
  }
7370
- var toPoint2 = (p) => [p.x, p.y];
8501
+ var toPoint3 = (p) => [p.x, p.y];
7371
8502
  var placementOf = (n) => ({
7372
8503
  ...n.transform ? { transform: n.transform } : {},
7373
8504
  ...n.visible === false ? { visible: false } : {},
@@ -7412,6 +8543,7 @@ var VectorEditorController = class {
7412
8543
  /** Options new blends get (the toolbars edit them along with the selected blend's). */
7413
8544
  blendDefaults = {};
7414
8545
  gradientEdit = { target: "fill", stop: null };
8546
+ freeDistort = false;
7415
8547
  /** What the highlight shows, to skip notifying when the pointer stays over the same face. */
7416
8548
  builderHoverKey = null;
7417
8549
  createId;
@@ -7435,6 +8567,7 @@ var VectorEditorController = class {
7435
8567
  this.hitRadius = options.hitRadius ?? 6;
7436
8568
  this.createId = options.createId ?? defaultCreateId;
7437
8569
  this.onChange = options.onChange;
8570
+ this.freeDistort = !!options.freeDistort;
7438
8571
  this.state = this.computeState();
7439
8572
  this.connect();
7440
8573
  }
@@ -7506,6 +8639,7 @@ var VectorEditorController = class {
7506
8639
  blendStart: this.blendStart,
7507
8640
  blendDefaults: this.blendDefaults,
7508
8641
  gradientEdit: this.gradientEdit,
8642
+ freeDistort: this.freeDistort,
7509
8643
  canUndo: h.canUndo,
7510
8644
  canRedo: h.canRedo,
7511
8645
  undoLabel: h.undoName,
@@ -7560,8 +8694,13 @@ var VectorEditorController = class {
7560
8694
  const h = this.hover;
7561
8695
  switch (this.tool) {
7562
8696
  case "select":
8697
+ if (h?.transformHandle && this.freeDistort && h.transformHandle !== "rotate") return "crosshair";
7563
8698
  if (h?.transformHandle) return vectorResizeCursor(h.transformHandle, this.frameOf(this.selectedRoots())?.angle ?? 0);
7564
8699
  return h?.nodeId ? "move" : "default";
8700
+ case "envelope":
8701
+ if (this.drag?.kind === "envelope") return "grabbing";
8702
+ if (h?.envelope) return h.envelope.index === null ? "grab" : "pointer";
8703
+ return h?.nodeId ? "pointer" : "default";
7565
8704
  case "direct":
7566
8705
  if (h?.anchor || h?.handle || h?.corner) return "pointer";
7567
8706
  if (h?.segment) return "copy";
@@ -8521,7 +9660,8 @@ var VectorEditorController = class {
8521
9660
  const i = list.findIndex((e2) => e2.id === effectId);
8522
9661
  if (i < 0) return `Node "${nodeId}" has no effect "${effectId}".`;
8523
9662
  const e = list[i];
8524
- const params = patch.params ? normalizeVectorEffectParams(e.type, patch.params, e.params) : e.params;
9663
+ const raw = patch.params && e.type === "envelope" && node ? this.envelopeParams(node, e, patch.params) : patch.params;
9664
+ const params = raw ? normalizeVectorEffectParams(e.type, raw, e.params) : e.params;
8525
9665
  const next = { ...e, params, ...patch.enabled !== void 0 ? { enabled: patch.enabled } : {} };
8526
9666
  if (next.enabled !== false) delete next.enabled;
8527
9667
  return list.map((x, j) => j === i ? next : x);
@@ -8661,6 +9801,318 @@ var VectorEditorController = class {
8661
9801
  if (!angle) return m;
8662
9802
  return multiplyMatrices(multiplyMatrices(rotateMatrix(-angle), m), rotateMatrix(angle));
8663
9803
  }
9804
+ /** The selection frame's corners in document space, clockwise from the top left. */
9805
+ frameCorners(frame2) {
9806
+ const rot = rotateMatrix(frame2.angle);
9807
+ return vectorBoxCorners(frame2).map((c) => frame2.angle ? applyMatrix(rot, c) : c);
9808
+ }
9809
+ /** `doc` with nodes `ids` sent through `f` (a map of document space), each in its own space (see `distortVectorNode`). */
9810
+ distorted(doc, ids, f, tolerance = VECTOR_DISTORT_TOLERANCE) {
9811
+ const visit = (list, parent) => {
9812
+ let changed = false;
9813
+ const out = list.map((n) => {
9814
+ let next = n;
9815
+ if (ids.has(n.id) && !n.locked) next = distortVectorNode(n, parent, f, tolerance);
9816
+ else if (n.type === "group") {
9817
+ const children = visit(n.children, multiplyMatrices(nodeMatrix(n), parent));
9818
+ if (children !== n.children) next = { ...n, children };
9819
+ }
9820
+ if (next !== n) changed = true;
9821
+ return next;
9822
+ });
9823
+ return changed ? out : list;
9824
+ };
9825
+ const nodes = visit(doc.nodes, IDENTITY_MATRIX);
9826
+ return nodes === doc.nodes ? doc : { ...doc, nodes };
9827
+ }
9828
+ /**
9829
+ * Free Distort mode of the selection box (`state.freeDistort`): dragging a corner distorts the
9830
+ * selection in perspective, each corner on its own (Shift+Alt+Mod: symmetric perspective, the
9831
+ * corner beside it moving the other way); dragging a side moves its two corners; Mod-dragging a
9832
+ * side skews along it (Alt: about the centre). Each drag is one undo step.
9833
+ */
9834
+ setFreeDistort(on) {
9835
+ if (this.freeDistort === on) return;
9836
+ this.freeDistort = on;
9837
+ this.notify();
9838
+ }
9839
+ /**
9840
+ * Distorts the selected (or given) nodes so the corners of their selection box (clockwise from
9841
+ * the top left, in the box's own rotation) land on `corners` (document space): a projective map,
9842
+ * so straight lines stay straight. Curves are followed within `tolerance` (document units).
9843
+ * One undo step.
9844
+ */
9845
+ distort(corners2, options = {}) {
9846
+ const ids = options.ids ?? this.selectedRoots();
9847
+ const frame2 = this.frameOf(ids);
9848
+ if (!frame2) return { ok: false, message: "Nothing is selected." };
9849
+ if (!(frame2.width > 0 && frame2.height > 0)) return { ok: false, message: "The selection has no area to distort." };
9850
+ const from = this.frameCorners(frame2);
9851
+ const h = corners2.length === 4 ? vectorHomography(from, corners2) : null;
9852
+ if (!h || !vectorHomographyKeepsQuad(h, from)) return { ok: false, message: "The four corners must make a convex quadrilateral, clockwise from the top left." };
9853
+ this.change("Distort", () => {
9854
+ this.doc = this.distorted(this.doc, new Set(ids), (p) => applyVectorHomography(h, p) ?? p, options.tolerance);
9855
+ });
9856
+ return { ok: true };
9857
+ }
9858
+ /**
9859
+ * Skews the selected (or given) nodes along the selection box's own axes by `ax` degrees
9860
+ * horizontally (x' = x + tan(ax)·y: vertical lines lean right for positive angles) and `ay`
9861
+ * vertically, about `origin` (default: the box centre, document space). One undo step.
9862
+ */
9863
+ skew(ax, ay = 0, options = {}) {
9864
+ const frame2 = this.frameOf(options.ids ?? this.selectedRoots());
9865
+ if (!frame2) return { ok: false, message: "Nothing is selected." };
9866
+ if (![ax, ay].every((v2) => Number.isFinite(v2) && Math.abs(v2) < 89.9)) return { ok: false, message: "Skew angles must be between \u221289.9\xB0 and 89.9\xB0." };
9867
+ const origin = applyMatrix(rotateMatrix(-frame2.angle), options.origin ?? this.frameCenter(frame2));
9868
+ return this.transform(this.inFrame(frame2.angle, vectorSkewMatrix(ax, ay, origin)), { ids: options.ids, label: "Skew" });
9869
+ }
9870
+ /** A selection box handle dragged in Free Distort mode. */
9871
+ moveDistort(drag, p, input) {
9872
+ const angle = drag.frame.angle;
9873
+ const unrotate = (v2) => angle ? applyMatrix(rotateMatrix(-angle), v2) : v2;
9874
+ const q = unrotate(p);
9875
+ const q0 = unrotate(drag.start);
9876
+ const d = { x: q.x - q0.x, y: q.y - q0.y };
9877
+ const ids = new Set(this.selectedRoots());
9878
+ const from = this.frameCorners(drag.frame);
9879
+ const corner = drag.handle.length === 2;
9880
+ this.setGuides(null);
9881
+ if (!corner && input.mod) {
9882
+ const m = this.inFrame(angle, vectorShearForSide(drag.frame, drag.handle, d, !!input.alt));
9883
+ drag.op = "Skew";
9884
+ this.doc = this.transformed(drag.base, ids, m);
9885
+ this.interaction = { type: "distort", mode: "skew", corners: from.map((c) => applyMatrix(m, c)) };
9886
+ this.notify(true);
9887
+ return;
9888
+ }
9889
+ const mode = corner && input.mod && input.alt && input.shift ? "perspective" : "distort";
9890
+ const rot = rotateMatrix(angle);
9891
+ const quad = vectorDistortQuad(drag.frame, drag.handle, d, mode).map((c) => angle ? applyMatrix(rot, c) : c);
9892
+ const h = vectorHomography(from, quad);
9893
+ if (!h || !vectorHomographyKeepsQuad(h, from)) return;
9894
+ drag.op = mode === "perspective" ? "Perspective" : "Distort";
9895
+ this.doc = this.distorted(drag.base, ids, (pt2) => applyVectorHomography(h, pt2) ?? pt2);
9896
+ this.interaction = { type: "distort", mode, corners: quad };
9897
+ this.notify(true);
9898
+ }
9899
+ // -------------------------------------------------------------------------
9900
+ // Envelopes
9901
+ // -------------------------------------------------------------------------
9902
+ /** Selected (or given) roots that can carry an envelope. */
9903
+ envelopeTargets(ids) {
9904
+ return this.effectTargets(ids).filter((n) => !n.locked);
9905
+ }
9906
+ /** Envelope parameters for `node` after `patch`: a preset switched to a mesh becomes the mesh it stood for. */
9907
+ envelopeParams(node, current, patch) {
9908
+ if (patch.style !== "mesh" || patch.mesh !== void 0 || !current || current.params.style === "mesh") return patch;
9909
+ const frame2 = vectorEnvelopeFrame(node, current.id);
9910
+ if (!frame2) return patch;
9911
+ const rows = typeof patch.rows === "number" ? patch.rows : current.params.rows;
9912
+ const columns = typeof patch.columns === "number" ? patch.columns : current.params.columns;
9913
+ return { ...patch, mesh: resizeVectorMesh(frame2.mesh, columns, rows) };
9914
+ }
9915
+ /**
9916
+ * Gives the selected (or given) nodes an envelope distortion, or changes the one they have: a
9917
+ * warp preset (`style` with `bend`, `horizontal` and `vertical` in percent and `orientation`) or
9918
+ * a mesh (`style: 'mesh'` with `rows` and `columns`, and optionally its `mesh` points). The
9919
+ * envelope stays live (an `envelope` effect: edit it with the Envelope tool, Expand Appearance
9920
+ * bakes it). One undo step (with `merge`, consecutive changes fold into one, as a slider drag
9921
+ * should). Returns the envelope of each node.
9922
+ */
9923
+ envelope(params = {}, options = {}) {
9924
+ const targets = this.envelopeTargets(options.ids);
9925
+ if (!targets.length) return [];
9926
+ const out = [];
9927
+ const next = /* @__PURE__ */ new Map();
9928
+ const raw = params;
9929
+ for (const node of targets) {
9930
+ const list = node.effects ?? [];
9931
+ const current = list.find((e) => e.type === "envelope") ?? null;
9932
+ if (current) {
9933
+ const p = normalizeVectorEffectParams("envelope", this.envelopeParams(node, current, raw), current.params);
9934
+ const { enabled: _e, ...rest } = current;
9935
+ next.set(node.id, { ...node, effects: list.map((e) => e === current ? { ...rest, params: p } : e) });
9936
+ out.push({ nodeId: node.id, effectId: current.id });
9937
+ } else {
9938
+ let k = list.length + 1;
9939
+ while (list.some((e) => e.id === `fx${k}`)) k++;
9940
+ const effect = createVectorEffect("envelope", `fx${k}`, raw);
9941
+ next.set(node.id, { ...node, effects: [...list, effect] });
9942
+ out.push({ nodeId: node.id, effectId: effect.id });
9943
+ }
9944
+ }
9945
+ const made = out.some((o) => !targets.find((n) => n.id === o.nodeId)?.effects?.some((e) => e.id === o.effectId));
9946
+ const merge = options.merge ? `envelope:${out.map((o) => o.nodeId).join(",")}` : void 0;
9947
+ this.change(made ? params.style === "mesh" ? "Make Envelope with Mesh" : "Make Envelope with Warp" : "Edit Envelope", () => this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n)), merge);
9948
+ return out;
9949
+ }
9950
+ /**
9951
+ * Make with Top Object: the topmost selected object's outline (a closed path) becomes the envelope
9952
+ * of the other selected objects (grouped when there are several; they must share a parent), as a
9953
+ * mesh of `columns × rows` patches whose border follows the outline. The top object is used up.
9954
+ * One undo step; returns the id of the enveloped node.
9955
+ */
9956
+ makeEnvelopeWithTopObject(options = {}) {
9957
+ const ids = options.ids ?? this.selectedRoots();
9958
+ if (ids.length < 2) return { ok: false, message: "Select the objects and, on top of them, the shape to use as the envelope." };
9959
+ const order = /* @__PURE__ */ new Map();
9960
+ walkVectorNodes(this.doc.nodes, (n) => order.set(n.id, order.size));
9961
+ const sorted = ids.filter((id) => order.has(id)).sort((a, b) => order.get(a) - order.get(b));
9962
+ const topId = sorted[sorted.length - 1];
9963
+ const top = this.getNode(topId);
9964
+ const outline = top?.type === "path" ? top.subpaths.find((sp) => sp.closed && sp.anchors.length > 1) : void 0;
9965
+ if (!top || !outline) return { ok: false, message: "The top object must be a path with a closed outline." };
9966
+ const contents = sorted.slice(0, -1).map((id) => this.getNode(id));
9967
+ if (contents.some((n) => n.locked)) return { ok: false, message: "Locked objects cannot be enveloped." };
9968
+ const parents = contents.map((n) => locateVectorNode(this.doc.nodes, n.id)?.parent?.id ?? null);
9969
+ if (parents.some((p) => p !== parents[0])) return { ok: false, message: "The objects to envelope must be in the same group." };
9970
+ const target = contents.length === 1 ? contents[0] : { id: this.newId(), type: "group", name: "Envelope", children: contents.map((n) => n) };
9971
+ const parentToDoc = parents[0] ? this.worldOf(parents[0]) : IDENTITY_MATRIX;
9972
+ const targetToDoc = multiplyMatrices(nodeMatrix(target), parentToDoc);
9973
+ const toLocal = multiplyMatrices(this.worldOf(topId), invertMatrix(targetToDoc));
9974
+ const local = {
9975
+ closed: true,
9976
+ anchors: outline.anchors.map((a) => ({
9977
+ ...applyMatrix(toLocal, a),
9978
+ ...a.in ? { in: applyMatrix(toLocal, a.in) } : {},
9979
+ ...a.out ? { out: applyMatrix(toLocal, a.out) } : {}
9980
+ }))
9981
+ };
9982
+ const mesh = vectorMeshFromOutline(local, options.columns ?? 4, options.rows ?? 4);
9983
+ if (!mesh) return { ok: false, message: "The top object\u2019s outline is too small to use as an envelope." };
9984
+ const list = target.effects ?? [];
9985
+ let k = list.length + 1;
9986
+ while (list.some((e) => e.id === `fx${k}`)) k++;
9987
+ const effect = createVectorEffect("envelope", `fx${k}`, { style: "mesh", mesh });
9988
+ const enveloped = { ...target, effects: [...list, effect] };
9989
+ this.change("Make Envelope with Top Object", () => {
9990
+ let nodes = spliceTree(this.doc.nodes, topId, []);
9991
+ if (contents.length === 1) nodes = spliceTree(nodes, target.id, [enveloped]);
9992
+ else {
9993
+ const last = contents[contents.length - 1].id;
9994
+ for (const n of contents.slice(0, -1)) nodes = spliceTree(nodes, n.id, []);
9995
+ nodes = spliceTree(nodes, last, [enveloped]);
9996
+ }
9997
+ this.setDoc(nodes);
9998
+ this.selection = [target.id];
9999
+ this.anchors = [];
10000
+ this.editingId = null;
10001
+ });
10002
+ return { ok: true, nodeId: target.id };
10003
+ }
10004
+ /**
10005
+ * Release: the selected (or given) nodes lose their envelope, and its shape (the mesh's border)
10006
+ * comes back as a path above each of them. One undo step; returns the new paths' ids.
10007
+ */
10008
+ releaseEnvelope(options = {}) {
10009
+ const targets = this.envelopeTargets(options.ids).filter((n) => n.effects?.some((e) => e.type === "envelope"));
10010
+ if (!targets.length) return [];
10011
+ const created = [];
10012
+ const replace = /* @__PURE__ */ new Map();
10013
+ for (const node of targets) {
10014
+ const effect = node.effects.find((e) => e.type === "envelope");
10015
+ const frame2 = vectorEnvelopeFrame(node, effect.id);
10016
+ const rest = node.effects.filter((e) => e !== effect);
10017
+ const { effects: _e, ...plain3 } = node;
10018
+ const kept = rest.length ? { ...plain3, effects: rest } : plain3;
10019
+ if (!frame2) {
10020
+ replace.set(node.id, [kept]);
10021
+ continue;
10022
+ }
10023
+ const id = this.newId();
10024
+ created.push(id);
10025
+ const shape = {
10026
+ id,
10027
+ type: "path",
10028
+ name: "Envelope",
10029
+ ...node.transform ? { transform: node.transform } : {},
10030
+ subpaths: [vectorMeshOutline(frame2.mesh)],
10031
+ fill: null,
10032
+ stroke: "#808080",
10033
+ strokeWidth: 1
10034
+ };
10035
+ replace.set(node.id, [kept, shape]);
10036
+ }
10037
+ this.change("Release Envelope", () => {
10038
+ let nodes = this.doc.nodes;
10039
+ for (const [id, list] of replace) nodes = spliceTree(nodes, id, list);
10040
+ this.setDoc(nodes);
10041
+ this.selection = [...targets.map((n) => n.id), ...created];
10042
+ this.anchors = [];
10043
+ });
10044
+ return created;
10045
+ }
10046
+ /** The selected node the Envelope tool edits, with its mesh (node space) and node → document matrix. */
10047
+ envelopeTarget() {
10048
+ const target = getVectorEnvelopeTarget(this.doc, this.selection);
10049
+ return target && !target.node.locked ? target : null;
10050
+ }
10051
+ /** The envelope mesh point under `screen` (handles first), or a spot inside the mesh. */
10052
+ hitEnvelope(screen, p) {
10053
+ const target = this.envelopeTarget();
10054
+ if (!target) return null;
10055
+ const { mesh } = target.frame;
10056
+ const pts = vectorMeshEditPoints(mesh);
10057
+ let best = null;
10058
+ let bestD = Infinity;
10059
+ for (const index of [...pts.handles.map((h) => h.index), ...pts.anchors]) {
10060
+ const s = this.documentToScreen(applyMatrix(target.world, mesh.points[index]));
10061
+ const d = Math.hypot(s.x - screen.x, s.y - screen.y);
10062
+ if (d <= this.hitRadius && d < bestD - 0.5) {
10063
+ best = index;
10064
+ bestD = d;
10065
+ }
10066
+ }
10067
+ if (best !== null) return { target, index: best, s: 0, t: 0 };
10068
+ const local = applyMatrix(invertMatrix(target.world), p);
10069
+ const at2 = vectorMeshParamAt(mesh, local);
10070
+ if (at2.distance > this.tolerance()) return null;
10071
+ return { target, index: null, s: at2.s, t: at2.t };
10072
+ }
10073
+ downEnvelope(screen, p, input) {
10074
+ const hit = this.hitEnvelope(screen, p);
10075
+ if (hit) {
10076
+ const { node: node2, frame: frame2 } = hit.target;
10077
+ this.drag = {
10078
+ kind: "envelope",
10079
+ nodeId: node2.id,
10080
+ effectId: frame2.effectId,
10081
+ base: frame2.mesh,
10082
+ baseDoc: this.doc,
10083
+ before: this.snapshot(),
10084
+ index: hit.index,
10085
+ s: hit.s,
10086
+ t: hit.t,
10087
+ start: this.toLocal(node2.id, p),
10088
+ moved: false
10089
+ };
10090
+ this.interaction = { type: "envelope", nodeId: node2.id, index: hit.index };
10091
+ this.notify();
10092
+ return;
10093
+ }
10094
+ const node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance(), { entered: this.enteredGroups() });
10095
+ if (node) this.selection = input.shift ? this.selection.includes(node.id) ? this.selection.filter((id) => id !== node.id) : [...this.selection, node.id] : [node.id];
10096
+ else if (!input.shift) this.selection = [];
10097
+ this.anchors = [];
10098
+ this.notify();
10099
+ }
10100
+ moveEnvelope(drag, p) {
10101
+ const local = this.toLocal(drag.nodeId, p, drag.baseDoc);
10102
+ const d = { x: local.x - drag.start.x, y: local.y - drag.start.y };
10103
+ if (!drag.moved && Math.hypot(d.x, d.y) * this.viewport.zoom < 1) return;
10104
+ drag.moved = true;
10105
+ const mesh = drag.index !== null ? dragVectorMeshPoint(drag.base, drag.index, d) : pullVectorMesh(drag.base, drag.s, drag.t, d);
10106
+ const nodes = mapTree(drag.baseDoc.nodes, (n) => {
10107
+ if (n.id !== drag.nodeId) return n;
10108
+ const effects = (n.effects ?? []).map(
10109
+ (e) => e.id === drag.effectId && e.type === "envelope" ? { ...e, params: { ...e.params, style: "mesh", mesh, columns: mesh.us.length - 1, rows: mesh.vs.length - 1 } } : e
10110
+ );
10111
+ return { ...n, effects };
10112
+ });
10113
+ this.doc = { ...drag.baseDoc, nodes };
10114
+ this.notify(true);
10115
+ }
8664
10116
  /** Applies a matrix to the selected (or given) nodes as one undo step. */
8665
10117
  transform(matrix, options = {}) {
8666
10118
  const ids = new Set(options.ids ?? this.selectedRoots());
@@ -9809,7 +11261,7 @@ var VectorEditorController = class {
9809
11261
  });
9810
11262
  const visible = (p) => !blocked.has(p.id);
9811
11263
  if (ids?.length || this.selection.length) return this.pathTargets(ids ?? this.selection).filter(visible);
9812
- const box = polylineBounds(points.map(toPoint2), pad);
11264
+ const box = polylineBounds(points.map(toPoint3), pad);
9813
11265
  if (!box) return [];
9814
11266
  const out = [];
9815
11267
  walkVectorNodes(this.doc.nodes, (n) => {
@@ -9822,7 +11274,7 @@ var VectorEditorController = class {
9822
11274
  /** A stroke's points in node `id`'s own space. */
9823
11275
  localPoints(id, points) {
9824
11276
  const inv = invertMatrix(this.worldOf(id));
9825
- return points.map((p) => toPoint2(applyMatrix(inv, p)));
11277
+ return points.map((p) => toPoint3(applyMatrix(inv, p)));
9826
11278
  }
9827
11279
  /**
9828
11280
  * Knife: cuts closed paths along the polyline `points` (document space) wherever it crosses
@@ -9831,7 +11283,7 @@ var VectorEditorController = class {
9831
11283
  * when nothing was cut).
9832
11284
  */
9833
11285
  knife(points, options = {}) {
9834
- const pts = simplifyPolyline(points.map(toPoint2), 0.25 / this.viewport.zoom).map(([x, y]) => ({ x, y }));
11286
+ const pts = simplifyPolyline(points.map(toPoint3), 0.25 / this.viewport.zoom).map(([x, y]) => ({ x, y }));
9835
11287
  if (pts.length < 2) return [];
9836
11288
  const plan = [];
9837
11289
  for (const node of this.strokeTargets(pts, options.ids, 0)) {
@@ -9860,7 +11312,7 @@ var VectorEditorController = class {
9860
11312
  erase(points, options = {}) {
9861
11313
  const width = options.size ?? this.editing.eraserSize;
9862
11314
  if (!(width > 0) || !points.length) return [];
9863
- const pts = simplifyPolyline(points.map(toPoint2), Math.min(width * 0.05, 0.25 / this.viewport.zoom));
11315
+ const pts = simplifyPolyline(points.map(toPoint3), Math.min(width * 0.05, 0.25 / this.viewport.zoom));
9864
11316
  const region = eraserRegion(pts, width);
9865
11317
  if (!region.length) return [];
9866
11318
  const plan = [];
@@ -10128,6 +11580,12 @@ var VectorEditorController = class {
10128
11580
  const path = this.hitPath(p);
10129
11581
  return path ? { nodeId: path.id } : null;
10130
11582
  }
11583
+ case "envelope": {
11584
+ const hit = this.hitEnvelope(screen, p);
11585
+ if (hit) return { nodeId: hit.target.node.id, envelope: { nodeId: hit.target.node.id, index: hit.index } };
11586
+ const node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance(), { entered: this.enteredGroups() });
11587
+ return node ? { nodeId: node.id } : null;
11588
+ }
10131
11589
  default:
10132
11590
  return null;
10133
11591
  }
@@ -10221,15 +11679,15 @@ var VectorEditorController = class {
10221
11679
  subpath = hit.ref.subpath;
10222
11680
  if (hit.ref.part === "center") mode = input.alt && profile.length < VECTOR_WIDTH_PROFILE_MAX_POINTS ? "copy" : "move";
10223
11681
  else if (input.alt) side = hit.ref.part;
10224
- const same = this.widthPoints.length > 0 && this.widthPoints[0].nodeId === node.id;
10225
- const selected = same && this.widthPoints.some((r) => r.index === ref.index);
11682
+ const same2 = this.widthPoints.length > 0 && this.widthPoints[0].nodeId === node.id;
11683
+ const selected = same2 && this.widthPoints.some((r) => r.index === ref.index);
10226
11684
  if (input.shift && mode !== "copy") {
10227
11685
  if (selected) {
10228
11686
  this.widthPoints = this.widthPoints.filter((r) => r.index !== ref.index);
10229
11687
  this.notify();
10230
11688
  return;
10231
11689
  }
10232
- this.widthPoints = same ? [...this.widthPoints, ref] : [ref];
11690
+ this.widthPoints = same2 ? [...this.widthPoints, ref] : [ref];
10233
11691
  shift = true;
10234
11692
  } else if (mode === "copy" || !selected) this.widthPoints = [ref];
10235
11693
  if (mode !== "copy") group = this.widthPoints.filter((r) => r.index !== ref.index).map((r) => r.index);
@@ -10352,6 +11810,8 @@ var VectorEditorController = class {
10352
11810
  return this.downBlend(screen, p);
10353
11811
  case "gradient":
10354
11812
  return this.downGradient(screen, p, input);
11813
+ case "envelope":
11814
+ return this.downEnvelope(screen, p, input);
10355
11815
  case "scissors": {
10356
11816
  const seg = this.nearestOutline(p);
10357
11817
  if (seg) this.cutPathAt(seg.nodeId, seg.point);
@@ -10645,6 +12105,8 @@ var VectorEditorController = class {
10645
12105
  return;
10646
12106
  case "gradient":
10647
12107
  return this.moveGradient(drag, screen, p, input);
12108
+ case "envelope":
12109
+ return this.moveEnvelope(drag, p);
10648
12110
  case "move": {
10649
12111
  let dx = p.x - drag.start.x;
10650
12112
  let dy = p.y - drag.start.y;
@@ -10665,6 +12127,7 @@ var VectorEditorController = class {
10665
12127
  return;
10666
12128
  }
10667
12129
  case "scale": {
12130
+ if (this.freeDistort && drag.handle !== "rotate") return this.moveDistort(drag, p, input);
10668
12131
  const angle = drag.frame.angle;
10669
12132
  const q = angle ? applyMatrix(rotateMatrix(-angle), p) : p;
10670
12133
  let { sx, sy, origin } = scaleFor(drag.handle, drag.frame, q, input);
@@ -10965,7 +12428,13 @@ var VectorEditorController = class {
10965
12428
  }
10966
12429
  return this.commit(drag.duplicated ? "Duplicate" : "Move", drag.before);
10967
12430
  case "scale":
10968
- return this.commit("Scale", drag.before);
12431
+ return this.commit(drag.op ?? "Scale", drag.before);
12432
+ case "envelope":
12433
+ if (!drag.moved) {
12434
+ this.notify();
12435
+ return;
12436
+ }
12437
+ return this.commit("Edit Envelope", drag.before);
10969
12438
  case "rotate":
10970
12439
  return this.commit("Rotate", drag.before);
10971
12440
  case "anchors":
@@ -11426,9 +12895,9 @@ function getVectorOverlayRenderData(state) {
11426
12895
  if (state.editingId) outline(state.editingId, true);
11427
12896
  if (state.hover?.nodeId && !state.interaction) outline(state.hover.nodeId, false);
11428
12897
  let selectionBox = null;
11429
- if (state.tool === "select" && state.selectionFrame && state.interaction?.type !== "marquee") {
12898
+ if (state.tool === "select" && state.selectionFrame && state.interaction?.type !== "marquee" && state.interaction?.type !== "distort") {
11430
12899
  const g = getVectorSelectionBoxGeometry(state.selectionFrame, state.viewport);
11431
- selectionBox = { ...g.box, angle: g.angle, transform: g.transform, corners: g.corners, handles: g.handles };
12900
+ selectionBox = { ...g.box, angle: g.angle, transform: g.transform, corners: g.corners, handles: g.handles, freeDistort: state.freeDistort };
11432
12901
  }
11433
12902
  const selected = (r) => state.selectedAnchors.some((a) => a.nodeId === r.nodeId && a.subpath === r.subpath && a.index === r.index);
11434
12903
  const sameAnchor = (a, b) => !!a && a.nodeId === b.nodeId && a.subpath === b.subpath && a.index === b.index;
@@ -11555,7 +13024,61 @@ function getVectorOverlayRenderData(state) {
11555
13024
  if (!gradient && !it) status = "Drag across a shape to set a gradient";
11556
13025
  else if (it?.type === "gradient" && it.removing) status = "Release to delete the stop";
11557
13026
  }
11558
- return { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint, cornerWidgets, widthPoints, widthSpot, builder, blend, gradient };
13027
+ let distort = null;
13028
+ if (it?.type === "distort") {
13029
+ const corners2 = it.corners.map(sc);
13030
+ distort = { mode: it.mode, corners: corners2, d: corners2.map((c, i) => `${i ? "L" : "M"}${c.x} ${c.y}`).join("") + "Z" };
13031
+ status = it.mode === "skew" ? "Skew" : it.mode === "perspective" ? "Perspective" : "Distort";
13032
+ }
13033
+ const envelope = state.tool === "envelope" ? envelopeOverlay(state) : null;
13034
+ if (state.tool === "envelope" && !envelope && !it) status = "Select an object with an envelope to edit its mesh";
13035
+ return { outlines, selectionBox, distort, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint, cornerWidgets, widthPoints, widthSpot, builder, blend, gradient, envelope };
13036
+ }
13037
+ function envelopeOverlay(state) {
13038
+ const target = getVectorEnvelopeTarget(state.document, state.selection);
13039
+ if (!target) return null;
13040
+ const v2 = state.viewport;
13041
+ const m = multiplyMatrices(target.world, [v2.zoom, 0, 0, v2.zoom, v2.x, v2.y]);
13042
+ const sc = (p) => ({ x: fmt4(m[0] * p.x + m[2] * p.y + m[4]), y: fmt4(m[1] * p.x + m[3] * p.y + m[5]) });
13043
+ const { mesh } = target.frame;
13044
+ const grid = vectorMeshGridCurves(mesh).map((c) => {
13045
+ const [a, b, cc, d] = c.map(sc);
13046
+ return `M${a.x} ${a.y}C${b.x} ${b.y} ${cc.x} ${cc.y} ${d.x} ${d.y}`;
13047
+ }).join("");
13048
+ const hover = state.hover?.envelope?.nodeId === target.node.id ? state.hover.envelope.index : void 0;
13049
+ const it = state.interaction;
13050
+ const active = it?.type === "envelope" && it.nodeId === target.node.id ? it.index : void 0;
13051
+ const pts = vectorMeshEditPoints(mesh);
13052
+ return {
13053
+ nodeId: target.node.id,
13054
+ grid,
13055
+ anchors: pts.anchors.map((index) => ({ index, ...sc(mesh.points[index]), hovered: !it && hover === index, active: active === index })),
13056
+ handles: pts.handles.map((h) => {
13057
+ const a = sc(mesh.points[h.anchor]);
13058
+ return { index: h.index, ...sc(mesh.points[h.index]), anchorX: a.x, anchorY: a.y, hovered: !it && hover === h.index, active: active === h.index };
13059
+ })
13060
+ };
13061
+ }
13062
+ var VECTOR_ENVELOPE_STYLE_OPTIONS = [
13063
+ ...VECTOR_WARP_STYLES.map((value) => ({ value, label: VECTOR_WARP_STYLE_LABELS[value] })),
13064
+ { value: "mesh", label: "Mesh" }
13065
+ ];
13066
+ function getVectorEnvelopePanel(state) {
13067
+ const nodes = state.selection.map((id) => findVectorNode(state.document.nodes, id)).filter((n) => !!n && !n.blendStep);
13068
+ if (!nodes.length) return null;
13069
+ const one = nodes.length === 1 ? nodes[0] : null;
13070
+ const first = one?.effects?.find((e) => e.type === "envelope");
13071
+ const order = /* @__PURE__ */ new Map();
13072
+ walkVectorNodes(state.document.nodes, (n) => order.set(n.id, order.size));
13073
+ const top = nodes.length > 1 ? nodes.slice().sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0))[nodes.length - 1] : null;
13074
+ return {
13075
+ nodeId: one?.id ?? null,
13076
+ count: nodes.length,
13077
+ envelope: first?.type === "envelope" ? { effectId: first.id, enabled: first.enabled !== false, params: first.params } : null,
13078
+ canMake: nodes.some((n) => !n.locked),
13079
+ canMakeWithTopObject: top?.type === "path" && top.subpaths.some((sp) => sp.closed && sp.anchors.length > 1),
13080
+ canRelease: nodes.some((n) => !n.locked && !!n.effects?.some((e) => e.type === "envelope"))
13081
+ };
11559
13082
  }
11560
13083
  function gradientOverlay(state) {
11561
13084
  const a = getVectorGradientAnnotator(state.document, state.selection, state.viewport, state.gradientEdit.target);
@@ -11719,7 +13242,7 @@ function getVectorSelectionStyle(state) {
11719
13242
  }
11720
13243
  if (!paths.length) return null;
11721
13244
  const first = paths[0];
11722
- const same = (get) => paths.every((p) => JSON.stringify(get(p) ?? null) === JSON.stringify(get(first) ?? null));
13245
+ const same2 = (get) => paths.every((p) => JSON.stringify(get(p) ?? null) === JSON.stringify(get(first) ?? null));
11723
13246
  const corners2 = [];
11724
13247
  for (const p of paths) {
11725
13248
  for (const c of vectorCorners(p.subpaths)) {
@@ -11747,17 +13270,17 @@ function getVectorSelectionStyle(state) {
11747
13270
  corners: corners2.length,
11748
13271
  hasOpenPath: paths.some((p) => p.subpaths.some((sp) => !sp.closed && sp.anchors.length > 1)),
11749
13272
  mixed: {
11750
- fill: !same((p) => p.fill),
11751
- stroke: !same((p) => p.stroke),
11752
- strokeWidth: !same((p) => p.strokeWidth ?? 1),
11753
- opacity: !same((p) => p.opacity ?? 1),
11754
- strokeAlign: !same((p) => p.strokeAlign ?? "center"),
11755
- startArrow: !same((p) => p.startArrow ?? null),
11756
- endArrow: !same((p) => p.endArrow ?? null),
11757
- arrowAlign: !same((p) => p.arrowAlign ?? "extend"),
11758
- startArrowScale: !same((p) => p.startArrowScale ?? 1),
11759
- endArrowScale: !same((p) => p.endArrowScale ?? 1),
11760
- widthProfile: !same(profileOf),
13273
+ fill: !same2((p) => p.fill),
13274
+ stroke: !same2((p) => p.stroke),
13275
+ strokeWidth: !same2((p) => p.strokeWidth ?? 1),
13276
+ opacity: !same2((p) => p.opacity ?? 1),
13277
+ strokeAlign: !same2((p) => p.strokeAlign ?? "center"),
13278
+ startArrow: !same2((p) => p.startArrow ?? null),
13279
+ endArrow: !same2((p) => p.endArrow ?? null),
13280
+ arrowAlign: !same2((p) => p.arrowAlign ?? "extend"),
13281
+ startArrowScale: !same2((p) => p.startArrowScale ?? 1),
13282
+ endArrowScale: !same2((p) => p.endArrowScale ?? 1),
13283
+ widthProfile: !same2(profileOf),
11761
13284
  cornerRadius: corners2.some((c) => c.radius !== c0.radius),
11762
13285
  cornerKind: corners2.some((c) => c.kind !== c0.kind)
11763
13286
  },
@@ -11859,14 +13382,15 @@ function getVectorEffectsPanel(state) {
11859
13382
  label: VECTOR_EFFECT_LABELS[e.type] ?? e.type,
11860
13383
  enabled: e.enabled !== false,
11861
13384
  params: e.params,
11862
- specs: VECTOR_EFFECT_PARAM_SPECS[e.type] ?? []
13385
+ // The Envelope panel (`getVectorEnvelopePanel`) edits an envelope's warp and mesh.
13386
+ specs: e.type === "envelope" ? [] : VECTOR_EFFECT_PARAM_SPECS[e.type] ?? []
11863
13387
  })),
11864
13388
  expandable: nodes.some(hasVectorAppearance)
11865
13389
  };
11866
13390
  }
11867
13391
 
11868
13392
  // src/vector/commands.ts
11869
- var EFFECT_PARAMS_DESCRIPTION = "Effect parameters (missing ones keep their value or default). trim: start, end (percent), offset (degrees), mode simultaneous|individual. roundCorners: radius. puckerBloat: amount (\u2212100\u2026100). twist: angle, center {x, y} (offset from the middle). zigzag: size, ridges, smooth. wiggle: size, detail, smooth, speed, correlation, phase, seed, time (seconds). offset: amount, join miter|round|bevel, miterLimit, copies, copyOffset. merge: mode merge|add|subtract|intersect|exclude. repeater: copies (fractions fade the last), offset, position {x, y}, anchor {x, y} (offset from the middle), rotation, scale {x, y} (percent), startOpacity, endOpacity (percent), composite below|above.";
13393
+ var EFFECT_PARAMS_DESCRIPTION = "Effect parameters (missing ones keep their value or default). trim: start, end (percent), offset (degrees), mode simultaneous|individual. roundCorners: radius. puckerBloat: amount (\u2212100\u2026100). twist: angle, center {x, y} (offset from the middle). zigzag: size, ridges, smooth. wiggle: size, detail, smooth, speed, correlation, phase, seed, time (seconds). offset: amount, join miter|round|bevel, miterLimit, copies, copyOffset. merge: mode merge|add|subtract|intersect|exclude. repeater: copies (fractions fade the last), offset, position {x, y}, anchor {x, y} (offset from the middle), rotation, scale {x, y} (percent), startOpacity, endOpacity (percent), composite below|above. envelope: style (a warp preset: arc|arcLower|arcUpper|arch|bulge|shellLower|shellUpper|flag|wave|fish|rise|fisheye|inflate|squeeze|twist, or mesh), bend, horizontal, vertical (percent, \u2212100\u2026100), orientation horizontal|vertical, rows, columns, mesh { us, vs, points } (node space; prefer vector.envelope).";
11870
13394
  var fail = (message) => {
11871
13395
  throw new Error(message);
11872
13396
  };
@@ -12225,6 +13749,114 @@ function createVectorCommands(controller, options = {}) {
12225
13749
  return { ids: created };
12226
13750
  }
12227
13751
  },
13752
+ {
13753
+ ...base,
13754
+ id: `${prefix}distort`,
13755
+ label: "Free Distort\u2026",
13756
+ keywords: ["distort", "perspective", "free distort", "corners", "projective", "keystone"],
13757
+ description: "Free Distort: moves the four corners of the selection box (clockwise from the top left, in the box\u2019s own rotation) to `corners` in document units, distorting the nodes in perspective (straight lines stay straight; curves are followed within `tolerance`). The corners must make a convex quadrilateral. One undo step.",
13758
+ params: {
13759
+ type: "object",
13760
+ properties: {
13761
+ ids: IDS,
13762
+ corners: { type: "array", items: POINT, minItems: 4, maxItems: 4, description: "New top-left, top-right, bottom-right and bottom-left corners." },
13763
+ tolerance: { type: "number", exclusiveMinimum: 0, description: "How closely curves follow the distortion (document units, default 0.05)." }
13764
+ },
13765
+ required: ["corners"]
13766
+ },
13767
+ enabled: enabledFor,
13768
+ undoable: true,
13769
+ run: (_ctx, params) => {
13770
+ check(controller.distort(params.corners, { ids: ids(params), tolerance: params.tolerance }));
13771
+ const frame2 = controller.getState().selectionFrame;
13772
+ return { ok: true, bounds: frame2 ? { x: frame2.x, y: frame2.y, width: frame2.width, height: frame2.height } : null };
13773
+ }
13774
+ },
13775
+ {
13776
+ ...base,
13777
+ id: `${prefix}skew`,
13778
+ label: "Skew\u2026",
13779
+ keywords: ["skew", "shear", "slant", "oblique"],
13780
+ description: "Skews nodes along the selection box\u2019s axes: `ax` degrees horizontally (vertical lines lean right for positive angles) and `ay` degrees vertically, about the box centre or `origin`. One undo step.",
13781
+ params: {
13782
+ type: "object",
13783
+ properties: {
13784
+ ids: IDS,
13785
+ ax: { type: "number", minimum: -89.9, maximum: 89.9 },
13786
+ ay: { type: "number", minimum: -89.9, maximum: 89.9 },
13787
+ origin: POINT
13788
+ }
13789
+ },
13790
+ enabled: enabledFor,
13791
+ undoable: true,
13792
+ run: (_ctx, params) => {
13793
+ check(controller.skew(params.ax ?? 0, params.ay ?? 0, { ids: ids(params), origin: params.origin }));
13794
+ return { ok: true };
13795
+ }
13796
+ },
13797
+ {
13798
+ ...base,
13799
+ id: `${prefix}envelope`,
13800
+ label: "Envelope Distort\u2026",
13801
+ keywords: ["envelope", "warp", "mesh", "arc", "arch", "bulge", "flag", "wave", "fish", "rise", "fisheye", "inflate", "squeeze", "twist", "shell", "bend", "distort"],
13802
+ description: "Envelope distortion that stays editable (a live `envelope` effect). Give `preset` (with `bend`, `horizontal`, `vertical` in percent, \u2212100\u2026100, and `orientation`) to warp, or `mesh` ({ rows, columns } and optionally `points`, the control grid row by row in the node\u2019s own space) for an editable mesh; a node that already has an envelope is changed. `topObject: true` uses the topmost selected closed path as the envelope of the others; `release: true` removes the envelope and gives back its shape as a path; `expand: true` bakes it (Expand Appearance). One undo step.",
13803
+ params: {
13804
+ type: "object",
13805
+ properties: {
13806
+ ids: IDS,
13807
+ preset: { type: "string", enum: VECTOR_WARP_STYLES },
13808
+ bend: { type: "number", minimum: -100, maximum: 100 },
13809
+ horizontal: { type: "number", minimum: -100, maximum: 100 },
13810
+ vertical: { type: "number", minimum: -100, maximum: 100 },
13811
+ orientation: { type: "string", enum: ["horizontal", "vertical"] },
13812
+ mesh: {
13813
+ type: "object",
13814
+ properties: {
13815
+ rows: { type: "integer", minimum: 1, maximum: 16 },
13816
+ columns: { type: "integer", minimum: 1, maximum: 16 },
13817
+ points: { type: "array", items: POINT, description: "(3\xB7columns + 1) \xD7 (3\xB7rows + 1) control points, row by row (node space)." }
13818
+ }
13819
+ },
13820
+ topObject: { type: "boolean" },
13821
+ release: { type: "boolean" },
13822
+ expand: { type: "boolean" }
13823
+ }
13824
+ },
13825
+ enabled: enabledFor,
13826
+ undoable: true,
13827
+ run: (_ctx, params) => {
13828
+ const target = ids(params);
13829
+ if (params.release) return { released: target, shapes: controller.releaseEnvelope({ ids: target }) };
13830
+ if (params.expand) return { expanded: controller.expandAppearance({ ids: target }) };
13831
+ if (params.topObject) {
13832
+ const r = controller.makeEnvelopeWithTopObject({ ids: target, rows: params.mesh?.rows, columns: params.mesh?.columns });
13833
+ check(r);
13834
+ return { nodeId: r.nodeId };
13835
+ }
13836
+ if (params.preset !== void 0 && !VECTOR_WARP_STYLES.includes(params.preset)) fail(`Unknown warp preset "${params.preset}".`);
13837
+ const patch = {};
13838
+ if (params.preset) patch.style = params.preset;
13839
+ for (const k of ["bend", "horizontal", "vertical"]) if (params[k] !== void 0) patch[k] = params[k];
13840
+ if (params.orientation) patch.orientation = params.orientation;
13841
+ if (params.mesh) {
13842
+ if (params.preset) fail("Give either `preset` or `mesh`, not both.");
13843
+ patch.style = "mesh";
13844
+ const columns = params.mesh.columns ?? 2;
13845
+ const rows = params.mesh.rows ?? 2;
13846
+ patch.columns = columns;
13847
+ patch.rows = rows;
13848
+ if (params.mesh.points) {
13849
+ const even = (n) => Array.from({ length: n + 1 }, (_, i) => i / n);
13850
+ const mesh = { us: even(columns), vs: even(rows), points: params.mesh.points.map((p) => ({ x: p.x, y: p.y })) };
13851
+ if (!isValidVectorMesh(mesh)) fail(`A ${columns} \xD7 ${rows} mesh needs ${(3 * columns + 1) * (3 * rows + 1)} finite points.`);
13852
+ patch.mesh = mesh;
13853
+ }
13854
+ }
13855
+ const made = controller.envelope(patch, { ids: target });
13856
+ if (!made.length) fail("Nothing to envelope.");
13857
+ return { envelopes: made };
13858
+ }
13859
+ },
12228
13860
  {
12229
13861
  ...base,
12230
13862
  id: `${prefix}transform`,
@@ -12814,12 +14446,14 @@ export {
12814
14446
  VECTOR_CORNER_KINDS,
12815
14447
  VECTOR_CORNER_KIND_LABELS,
12816
14448
  VECTOR_CORNER_WIDGET_INSET,
14449
+ VECTOR_DISTORT_TOLERANCE,
12817
14450
  VECTOR_EDIT_TOGGLES,
12818
14451
  VECTOR_EFFECT_DEFAULTS,
12819
14452
  VECTOR_EFFECT_LABELS,
12820
14453
  VECTOR_EFFECT_MENU,
12821
14454
  VECTOR_EFFECT_PARAM_SPECS,
12822
14455
  VECTOR_EFFECT_TYPES,
14456
+ VECTOR_ENVELOPE_STYLE_OPTIONS,
12823
14457
  VECTOR_FILL_GAP,
12824
14458
  VECTOR_GRADIENT_INTERPOLATIONS,
12825
14459
  VECTOR_GRADIENT_INTERPOLATION_LABELS,
@@ -12850,6 +14484,8 @@ export {
12850
14484
  VECTOR_TOOL_GLYPHS,
12851
14485
  VECTOR_TOOL_SHORTCUTS,
12852
14486
  VECTOR_TRANSFORM_HANDLES,
14487
+ VECTOR_WARP_STYLES,
14488
+ VECTOR_WARP_STYLE_LABELS,
12853
14489
  VECTOR_WIDTH_PRESETS,
12854
14490
  VECTOR_WIDTH_PRESET_IDS,
12855
14491
  VECTOR_WIDTH_PROFILE_MAX_POINTS,
@@ -12866,6 +14502,7 @@ export {
12866
14502
  applyVectorEffects,
12867
14503
  applyVectorGradientPatch,
12868
14504
  applyVectorGradientSpread,
14505
+ applyVectorHomography,
12869
14506
  arrowheadInset,
12870
14507
  arrowheadPath,
12871
14508
  arrowheadWeight,
@@ -12894,6 +14531,8 @@ export {
12894
14531
  dashIntervals,
12895
14532
  decomposeRigid,
12896
14533
  defaultVectorGradient,
14534
+ distortVectorNode,
14535
+ dragVectorMeshPoint,
12897
14536
  effectPathLength,
12898
14537
  effectPathVertexCount,
12899
14538
  effectiveStrokeAlign,
@@ -12901,6 +14540,7 @@ export {
12901
14540
  eraseFromFill,
12902
14541
  eraseFromOutline,
12903
14542
  eraserRegion,
14543
+ evalVectorMesh,
12904
14544
  evaluateVectorPathKeyframes,
12905
14545
  expandVectorAppearance,
12906
14546
  expandVectorAppearances,
@@ -12912,6 +14552,7 @@ export {
12912
14552
  explicitVectorGradient,
12913
14553
  findVectorNode,
12914
14554
  fitCornerRadius,
14555
+ fitVectorMesh,
12915
14556
  foldVectorBooleanOperands,
12916
14557
  generateVectorBlendChildren,
12917
14558
  generateWidthProfile,
@@ -12923,6 +14564,8 @@ export {
12923
14564
  getVectorCornerWidgets,
12924
14565
  getVectorEffectsPanel,
12925
14566
  getVectorEnteredGroups,
14567
+ getVectorEnvelopePanel,
14568
+ getVectorEnvelopeTarget,
12926
14569
  getVectorGradientAnnotator,
12927
14570
  getVectorGradientEditor,
12928
14571
  getVectorOverlayRenderData,
@@ -12960,6 +14603,7 @@ export {
12960
14603
  isClosedBezierShape,
12961
14604
  isIdentityMatrix,
12962
14605
  isUniformWidthProfile,
14606
+ isValidVectorMesh,
12963
14607
  isVectorBlendGroup,
12964
14608
  isVectorGradient,
12965
14609
  isVectorLiveBoolean,
@@ -12968,6 +14612,12 @@ export {
12968
14612
  knifePath,
12969
14613
  lineSubpath,
12970
14614
  locateVectorNode,
14615
+ mapBezierPath,
14616
+ mapVectorGradient,
14617
+ mapVectorMeshPoints,
14618
+ mapVectorPaint,
14619
+ mapVectorPoints,
14620
+ mapVectorSubpaths,
12971
14621
  matrixRotation,
12972
14622
  mergePaths,
12973
14623
  mirrorWidthProfile,
@@ -12983,6 +14633,7 @@ export {
12983
14633
  nodeMatrix,
12984
14634
  normalizeVectorEffectParams,
12985
14635
  offsetPaths,
14636
+ orderVectorQuad,
12986
14637
  parseSvgTransform,
12987
14638
  parseVectorColor,
12988
14639
  parseVectorSvg,
@@ -12995,6 +14646,7 @@ export {
12995
14646
  polylineBounds,
12996
14647
  pressureWidthProfile,
12997
14648
  puckerBloatPaths,
14649
+ pullVectorMesh,
12998
14650
  recognizeInkShape,
12999
14651
  rectSubpath,
13000
14652
  refineMorphContour,
@@ -13005,9 +14657,12 @@ export {
13005
14657
  removeAnchorsKeepingShape,
13006
14658
  removeRedundantPoints,
13007
14659
  removeVectorGradientStop,
14660
+ removeVectorMeshSplitU,
14661
+ removeVectorMeshSplitV,
13008
14662
  removeWidthPoints,
13009
14663
  repeatPaths,
13010
14664
  resampleGradientStops,
14665
+ resizeVectorMesh,
13011
14666
  resolveVectorBlendOptions,
13012
14667
  reverseMorphContour,
13013
14668
  reverseSubpath,
@@ -13032,10 +14687,13 @@ export {
13032
14687
  setWidthPoint,
13033
14688
  simplifyPolyline,
13034
14689
  smoothSubpath,
14690
+ solve8,
13035
14691
  sortedVectorGradientStops,
13036
14692
  spanVectorGradientStops,
13037
14693
  splitIntoFaces,
13038
14694
  splitSubpathAt,
14695
+ splitVectorMeshU,
14696
+ splitVectorMeshV,
13039
14697
  starSubpath,
13040
14698
  strokePositionPoint,
13041
14699
  strokeWidthProfileOf,
@@ -13073,14 +14731,21 @@ export {
13073
14731
  vectorBlendRailAt,
13074
14732
  vectorBlendSpine,
13075
14733
  vectorBlendStepCount,
14734
+ vectorBoxCorners,
13076
14735
  vectorBoxMatrix,
13077
14736
  vectorBuilderErasedEdges,
13078
14737
  vectorCornerAt,
13079
14738
  vectorCorners,
13080
14739
  vectorCornersAttribute,
13081
14740
  vectorDiamondPath,
14741
+ vectorDistortQuad,
13082
14742
  vectorDocumentToSvg,
14743
+ vectorEffectInputBounds,
14744
+ vectorEffectInputPaths,
13083
14745
  vectorEnteredGroups,
14746
+ vectorEnvelopeFrame,
14747
+ vectorEnvelopeMap,
14748
+ vectorEnvelopeMesh,
13084
14749
  vectorFillMap,
13085
14750
  vectorGradientArea,
13086
14751
  vectorGradientAspect,
@@ -13098,9 +14763,21 @@ export {
13098
14763
  vectorGradientTAt,
13099
14764
  vectorGradientTargetPath,
13100
14765
  vectorGradientUnitsMatrix,
14766
+ vectorHomography,
14767
+ vectorHomographyKeepsQuad,
14768
+ vectorIdentityMesh,
13101
14769
  vectorInkShapeName,
13102
14770
  vectorLiveBooleanSteps,
14771
+ vectorMeshColumns,
14772
+ vectorMeshControlBounds,
14773
+ vectorMeshEditPoints,
14774
+ vectorMeshFromOutline,
14775
+ vectorMeshGridCurves,
14776
+ vectorMeshOutline,
14777
+ vectorMeshParamAt,
14778
+ vectorMeshRows,
13103
14779
  vectorNodeBounds,
14780
+ vectorNodeEnvelope,
13104
14781
  vectorNodeRotation,
13105
14782
  vectorNodeToBezier,
13106
14783
  vectorOperandOperation,
@@ -13117,6 +14794,8 @@ export {
13117
14794
  vectorResultStyle,
13118
14795
  vectorShapeBuilderHit,
13119
14796
  vectorShapeBuilderMap,
14797
+ vectorShearForSide,
14798
+ vectorSkewMatrix,
13120
14799
  vectorStrokeGradientDefs,
13121
14800
  vectorStrokeLineArea,
13122
14801
  vectorStrokeOutline,
@@ -13126,6 +14805,8 @@ export {
13126
14805
  vectorStrokeSvgParts,
13127
14806
  vectorStrokeWidthGeometry,
13128
14807
  vectorToolLabel,
14808
+ vectorWarpPresetGrid,
14809
+ vectorWarpPresetMesh,
13129
14810
  vectorWorldMatrix,
13130
14811
  walkVectorNodes,
13131
14812
  widthProfileAt,