@pixerate/editor 0.43.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/THIRD_PARTY_NOTICES.md +4 -0
- package/dist/agent/index.cjs +548 -45
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +47 -17
- package/dist/agent/index.d.ts +47 -17
- package/dist/agent/index.js +536 -33
- package/dist/agent/index.js.map +1 -1
- package/dist/{builtins-BLud97fC.d.cts → builtins-CZW-aYKS.d.cts} +1 -1
- package/dist/{builtins-D0BDyAgw.d.ts → builtins-DghNQbT-.d.ts} +1 -1
- package/dist/{chunk-I6BZB5ZH.cjs → chunk-EFDCLENW.cjs} +73 -6
- package/dist/chunk-EFDCLENW.cjs.map +1 -0
- package/dist/chunk-HHQ4XJWT.js +826 -0
- package/dist/chunk-HHQ4XJWT.js.map +1 -0
- package/dist/chunk-J3R2OWOE.cjs +1465 -0
- package/dist/chunk-J3R2OWOE.cjs.map +1 -0
- package/dist/chunk-K2LVZ4FA.cjs +826 -0
- package/dist/chunk-K2LVZ4FA.cjs.map +1 -0
- package/dist/{chunk-6MDQKHRH.cjs → chunk-N4MLNJPO.cjs} +21 -899
- package/dist/chunk-N4MLNJPO.cjs.map +1 -0
- package/dist/chunk-PQM5W7ZN.js +168 -0
- package/dist/chunk-PQM5W7ZN.js.map +1 -0
- package/dist/{chunk-DDADOHJW.js → chunk-Q25ZS4F7.js} +5 -883
- package/dist/chunk-Q25ZS4F7.js.map +1 -0
- package/dist/chunk-TK2XZK6Z.js +1465 -0
- package/dist/chunk-TK2XZK6Z.js.map +1 -0
- package/dist/commands/index.cjs +3 -2
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.d.cts +5 -5
- package/dist/commands/index.d.ts +5 -5
- package/dist/commands/index.js +2 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/extensions/index.cjs +4 -4
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +3 -3
- package/dist/extensions/index.d.ts +3 -3
- package/dist/extensions/index.js +12 -12
- package/dist/generation/index.cjs +2 -2
- package/dist/generation/index.d.cts +4 -4
- package/dist/generation/index.d.ts +4 -4
- package/dist/generation/index.js +1 -1
- package/dist/image-editor/index.cjs +24 -24
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +2 -2
- package/dist/image-editor/index.d.ts +2 -2
- package/dist/image-editor/index.js +24 -24
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{index-C6gaQRH0.d.cts → index-CfOQf70b.d.cts} +1 -1
- package/dist/{index-DXAv_R08.d.ts → index-jwJ_onWf.d.ts} +1 -1
- package/dist/index.cjs +9 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +12 -12
- package/dist/parameters/index.d.cts +2 -2
- package/dist/parameters/index.d.ts +2 -2
- package/dist/{registry-DnzpgY27.d.cts → registry-B4l-lYts.d.cts} +1 -1
- package/dist/{registry-DhVUZioj.d.ts → registry-BfVD-vVg.d.ts} +1 -1
- package/dist/{registry-BDe-vlv3.d.ts → registry-CSlH4iUn.d.ts} +1 -1
- package/dist/{registry-D0GADKc7.d.cts → registry-vz6HOiiy.d.cts} +1 -1
- package/dist/{schema-DYDGdYL9.d.cts → schema-Di0CS6Pe.d.cts} +26 -2
- package/dist/{schema-DYDGdYL9.d.ts → schema-Di0CS6Pe.d.ts} +26 -2
- package/dist/tools/index.cjs +37 -3
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.d.cts +871 -11
- package/dist/tools/index.d.ts +871 -11
- package/dist/tools/index.js +36 -2
- package/dist/vector/index.cjs +1982 -301
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +624 -154
- package/dist/vector/index.d.ts +624 -154
- package/dist/vector/index.js +1755 -74
- package/dist/vector/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-36BLROHV.js +0 -438
- package/dist/chunk-36BLROHV.js.map +0 -1
- package/dist/chunk-5H2OPMXZ.js +0 -586
- package/dist/chunk-5H2OPMXZ.js.map +0 -1
- package/dist/chunk-6MDQKHRH.cjs.map +0 -1
- package/dist/chunk-D6S5M7HV.js +0 -101
- package/dist/chunk-D6S5M7HV.js.map +0 -1
- package/dist/chunk-DDADOHJW.js.map +0 -1
- package/dist/chunk-I6BZB5ZH.cjs.map +0 -1
- package/dist/chunk-MC2LQN5S.cjs +0 -586
- package/dist/chunk-MC2LQN5S.cjs.map +0 -1
- package/dist/chunk-V7CC7RZ6.cjs +0 -438
- package/dist/chunk-V7CC7RZ6.cjs.map +0 -1
package/dist/vector/index.js
CHANGED
|
@@ -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
|
|
1183
|
-
return VECTOR_WIDTH_PRESETS.find((p) => p.points.length &&
|
|
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
|
|
1204
|
-
if (
|
|
1205
|
-
pts[
|
|
1206
|
-
return { points: pts, index:
|
|
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,
|
|
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 >=
|
|
1752
|
-
const want = fromStart ?
|
|
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
|
-
|
|
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
|
|
3839
|
-
if (
|
|
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
|
|
4897
|
-
return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(
|
|
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
|
|
5110
|
-
var
|
|
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) =>
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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
|
|
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 &&
|
|
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 =
|
|
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 =
|
|
6117
|
-
const lo =
|
|
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
|
|
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
|
|
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 =
|
|
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 (
|
|
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 +=
|
|
6756
|
-
const gap =
|
|
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
|
|
7198
|
-
const angle =
|
|
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
|
|
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
|
|
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(
|
|
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) =>
|
|
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(
|
|
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(
|
|
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
|
|
10225
|
-
const selected =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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: !
|
|
11751
|
-
stroke: !
|
|
11752
|
-
strokeWidth: !
|
|
11753
|
-
opacity: !
|
|
11754
|
-
strokeAlign: !
|
|
11755
|
-
startArrow: !
|
|
11756
|
-
endArrow: !
|
|
11757
|
-
arrowAlign: !
|
|
11758
|
-
startArrowScale: !
|
|
11759
|
-
endArrowScale: !
|
|
11760
|
-
widthProfile: !
|
|
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
|
-
|
|
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,
|