@pixerate/editor 0.41.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +12 -0
- package/dist/canvas/index.d.cts +2 -2
- package/dist/canvas/index.d.ts +2 -2
- package/dist/{pathfinder-D7hXenzu.d.ts → pathfinder-D98S2lV5.d.ts} +1 -1
- package/dist/{pathfinder-BSW2ZTO6.d.cts → pathfinder-DKRcXiLS.d.cts} +1 -1
- package/dist/vector/index.cjs +3408 -516
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +1064 -30
- package/dist/vector/index.d.ts +1064 -30
- package/dist/vector/index.js +3166 -274
- package/dist/vector/index.js.map +1 -1
- package/package.json +1 -1
package/dist/vector/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
} from "../chunk-QVZZGT4X.js";
|
|
4
4
|
import {
|
|
5
5
|
PlanarMap,
|
|
6
|
+
angleToSvgLinearCoords,
|
|
6
7
|
bezierPathBounds,
|
|
7
8
|
bezierPathToSvg,
|
|
8
9
|
closeContour,
|
|
@@ -14,10 +15,10 @@ import {
|
|
|
14
15
|
fitCubics,
|
|
15
16
|
flattenChain,
|
|
16
17
|
formatNumber,
|
|
17
|
-
gradientToSvgDef,
|
|
18
18
|
hatchPath,
|
|
19
19
|
isStraightCubic,
|
|
20
20
|
normalizeBezierPath,
|
|
21
|
+
offsetPath,
|
|
21
22
|
outlineStroke,
|
|
22
23
|
pathBoolean,
|
|
23
24
|
pathToContours,
|
|
@@ -40,17 +41,25 @@ import {
|
|
|
40
41
|
windingNumber
|
|
41
42
|
} from "../chunk-3LHWTGHW.js";
|
|
42
43
|
import {
|
|
43
|
-
mixColorsOklab
|
|
44
|
+
mixColorsOklab,
|
|
45
|
+
oklabToRgb,
|
|
46
|
+
rgbToOklab
|
|
44
47
|
} from "../chunk-ZRJMJIQU.js";
|
|
45
48
|
import {
|
|
46
49
|
evaluateEasing
|
|
47
50
|
} from "../chunk-UB2OY7FD.js";
|
|
48
51
|
import {
|
|
49
|
-
hexToRgba
|
|
52
|
+
hexToRgba,
|
|
53
|
+
normalizeTokenColor,
|
|
54
|
+
rgbaToHex
|
|
50
55
|
} from "../chunk-TGD52FEZ.js";
|
|
51
56
|
|
|
52
57
|
// src/vector/types.ts
|
|
53
58
|
var VECTOR_CORNER_KINDS = ["round", "inverted", "chamfer"];
|
|
59
|
+
var VECTOR_GRADIENT_TYPES = ["linear", "radial", "conic", "reflected", "diamond"];
|
|
60
|
+
var VECTOR_GRADIENT_SPREADS = ["pad", "reflect", "repeat"];
|
|
61
|
+
var VECTOR_GRADIENT_INTERPOLATIONS = ["srgb", "linear", "oklab"];
|
|
62
|
+
var VECTOR_STROKE_GRADIENT_MODES = ["within", "along", "across"];
|
|
54
63
|
var VECTOR_ARROWHEADS = ["triangle", "arrow", "open", "circle", "square", "bar"];
|
|
55
64
|
var VECTOR_BLEND_SPACINGS = ["steps", "distance", "smoothColor"];
|
|
56
65
|
var VECTOR_TOOLS = [
|
|
@@ -69,18 +78,824 @@ var VECTOR_TOOLS = [
|
|
|
69
78
|
"width",
|
|
70
79
|
"shapeBuilder",
|
|
71
80
|
"paintBucket",
|
|
72
|
-
"blend"
|
|
81
|
+
"blend",
|
|
82
|
+
"gradient"
|
|
73
83
|
];
|
|
74
84
|
var VECTOR_TRANSFORM_HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w", "rotate"];
|
|
75
85
|
var VECTOR_PATHFINDER_OPERATIONS = ["divide", "trim", "merge", "crop", "outline", "minusBack"];
|
|
86
|
+
var VECTOR_EFFECT_TYPES = ["trim", "roundCorners", "puckerBloat", "twist", "zigzag", "wiggle", "offset", "merge", "repeater"];
|
|
87
|
+
var VECTOR_MERGE_MODES = ["merge", "add", "subtract", "intersect", "exclude"];
|
|
76
88
|
|
|
77
|
-
// src/vector/
|
|
78
|
-
var
|
|
79
|
-
var
|
|
89
|
+
// src/vector/gradient.ts
|
|
90
|
+
var clamp = (v2, lo, hi) => Math.min(hi, Math.max(lo, v2));
|
|
91
|
+
var IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
92
|
+
var mul = (a, b) => [
|
|
93
|
+
b[0] * a[0] + b[2] * a[1],
|
|
94
|
+
b[1] * a[0] + b[3] * a[1],
|
|
95
|
+
b[0] * a[2] + b[2] * a[3],
|
|
96
|
+
b[1] * a[2] + b[3] * a[3],
|
|
97
|
+
b[0] * a[4] + b[2] * a[5] + b[4],
|
|
98
|
+
b[1] * a[4] + b[3] * a[5] + b[5]
|
|
99
|
+
];
|
|
100
|
+
var apply = (m, p) => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] });
|
|
101
|
+
var invert = (m) => {
|
|
102
|
+
const [a, b, c, d, e, f] = m;
|
|
103
|
+
const det = a * d - b * c;
|
|
104
|
+
if (!Number.isFinite(det) || Math.abs(det) < 1e-18) return null;
|
|
105
|
+
return [d / det, -b / det, -c / det, a / det, (c * f - d * e) / det, (b * e - a * f) / det];
|
|
106
|
+
};
|
|
107
|
+
var isIdentity = (m) => m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
|
|
80
108
|
var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
81
109
|
var add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
82
|
-
var
|
|
110
|
+
var scale = (a, k) => ({ x: a.x * k, y: a.y * k });
|
|
83
111
|
var dot = (a, b) => a.x * b.x + a.y * b.y;
|
|
112
|
+
var perp = (v2) => ({ x: -v2.y, y: v2.x });
|
|
113
|
+
var finitePoint = (p) => !!p && typeof p === "object" && Number.isFinite(p.x) && Number.isFinite(p.y);
|
|
114
|
+
var fmt = (n, digits = 4) => {
|
|
115
|
+
const k = 10 ** digits;
|
|
116
|
+
const v2 = Math.round(n * k) / k;
|
|
117
|
+
return Object.is(v2, -0) ? "0" : String(v2);
|
|
118
|
+
};
|
|
119
|
+
var fmtMatrix = (m) => `matrix(${m.map((n) => fmt(n, 6)).join(" ")})`;
|
|
120
|
+
function isVectorGradient(paint) {
|
|
121
|
+
return !!paint && typeof paint === "object" && VECTOR_GRADIENT_TYPES.includes(paint.type) && Array.isArray(paint.stops);
|
|
122
|
+
}
|
|
123
|
+
function parseVectorColor(color) {
|
|
124
|
+
const hex = normalizeTokenColor(color);
|
|
125
|
+
if (hex === null) return null;
|
|
126
|
+
const c = hexToRgba(hex);
|
|
127
|
+
return [c.r, c.g, c.b, c.a];
|
|
128
|
+
}
|
|
129
|
+
var toLinear = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
130
|
+
var toGamma = (c) => {
|
|
131
|
+
const v2 = clamp(c, 0, 1);
|
|
132
|
+
return v2 <= 31308e-7 ? 12.92 * v2 : 1.055 * v2 ** (1 / 2.4) - 0.055;
|
|
133
|
+
};
|
|
134
|
+
function intoSpace(space, [r, g, b]) {
|
|
135
|
+
if (space === "linear") return [toLinear(r), toLinear(g), toLinear(b)];
|
|
136
|
+
if (space === "oklab") {
|
|
137
|
+
const c = rgbToOklab(r, g, b);
|
|
138
|
+
return [c.l, c.a, c.b];
|
|
139
|
+
}
|
|
140
|
+
return [r, g, b];
|
|
141
|
+
}
|
|
142
|
+
function outOfSpace(space, [x, y, z]) {
|
|
143
|
+
if (space === "linear") return [toGamma(x), toGamma(y), toGamma(z)];
|
|
144
|
+
if (space === "oklab") {
|
|
145
|
+
const c = oklabToRgb({ l: x, a: y, b: z, alpha: 1 });
|
|
146
|
+
return [c.r, c.g, c.b];
|
|
147
|
+
}
|
|
148
|
+
return [clamp(x, 0, 1), clamp(y, 0, 1), clamp(z, 0, 1)];
|
|
149
|
+
}
|
|
150
|
+
var MID_MIN = 0.01;
|
|
151
|
+
var MID_MAX = 0.99;
|
|
152
|
+
var VECTOR_GRADIENT_MIN_STOPS = 2;
|
|
153
|
+
function gradientMidpointRemap(u, m) {
|
|
154
|
+
const mid = clamp(m, MID_MIN, MID_MAX);
|
|
155
|
+
if (Math.abs(mid - 0.5) < 1e-6) return u;
|
|
156
|
+
return u <= mid ? 0.5 * u / mid : 0.5 + 0.5 * (u - mid) / (1 - mid);
|
|
157
|
+
}
|
|
158
|
+
var hasMidpoint = (s) => s.midpoint !== void 0 && Number.isFinite(s.midpoint) && Math.abs(s.midpoint - 0.5) > 1e-3;
|
|
159
|
+
function sortedVectorGradientStops(g) {
|
|
160
|
+
return (g.stops ?? []).filter((s) => s && Number.isFinite(s.offset) && typeof s.color === "string").map((s, i) => ({ s: { ...s, offset: clamp(s.offset, 0, 1) }, i })).sort((a, b) => a.s.offset - b.s.offset || a.i - b.i).map((e) => e.s);
|
|
161
|
+
}
|
|
162
|
+
function vectorGradientNeedsExpansion(g) {
|
|
163
|
+
return (g.interpolation ?? "srgb") !== "srgb" || (g.smoothness ?? 0) > 0 || (g.stops ?? []).some(hasMidpoint);
|
|
164
|
+
}
|
|
165
|
+
function vectorGradientRamp(g) {
|
|
166
|
+
const space = g.interpolation && VECTOR_GRADIENT_INTERPOLATIONS.includes(g.interpolation) ? g.interpolation : "srgb";
|
|
167
|
+
const smooth = clamp(Number.isFinite(g.smoothness) ? g.smoothness : 0, 0, 1);
|
|
168
|
+
const pts = [];
|
|
169
|
+
for (const s of sortedVectorGradientStops(g)) {
|
|
170
|
+
const c = parseVectorColor(s.color);
|
|
171
|
+
if (!c) return null;
|
|
172
|
+
pts.push({ t: s.offset, v: intoSpace(space, c), a: c[3] * clamp(s.opacity ?? 1, 0, 1), mid: s.midpoint ?? 0.5 });
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
sample(t) {
|
|
176
|
+
const n = pts.length;
|
|
177
|
+
if (!n) return [0, 0, 0, 0];
|
|
178
|
+
const first = pts[0];
|
|
179
|
+
const last = pts[n - 1];
|
|
180
|
+
const at2 = (p) => [...outOfSpace(space, p.v), p.a];
|
|
181
|
+
if (!(t > first.t) || n < 2) return at2(first);
|
|
182
|
+
if (t >= last.t) return at2(last);
|
|
183
|
+
let i = 0;
|
|
184
|
+
while (i + 2 < n && !(t >= pts[i].t && t <= pts[i + 1].t)) i++;
|
|
185
|
+
const p0 = pts[i];
|
|
186
|
+
const p1 = pts[i + 1];
|
|
187
|
+
const h = Math.max(p1.t - p0.t, 1e-6);
|
|
188
|
+
const u = gradientMidpointRemap((t - p0.t) / h, p0.mid);
|
|
189
|
+
const alpha = p0.a + (p1.a - p0.a) * u;
|
|
190
|
+
if (smooth <= 0) return [...outOfSpace(space, [0, 1, 2].map((c) => p0.v[c] + (p1.v[c] - p0.v[c]) * u)), alpha];
|
|
191
|
+
const tangent2 = (k) => {
|
|
192
|
+
const [a, b] = k === 0 ? [0, 1] : k === n - 1 ? [n - 2, n - 1] : [k - 1, k + 1];
|
|
193
|
+
const d = Math.max(pts[b].t - pts[a].t, 1e-6);
|
|
194
|
+
const half = k === 0 || k === n - 1 ? 0.5 : 1;
|
|
195
|
+
return [0, 1, 2].map((c) => (pts[b].v[c] - pts[a].v[c]) / d * half);
|
|
196
|
+
};
|
|
197
|
+
const m0 = tangent2(i);
|
|
198
|
+
const m1 = tangent2(i + 1);
|
|
199
|
+
const u2 = u * u;
|
|
200
|
+
const u3 = u2 * u;
|
|
201
|
+
const h00 = 2 * u3 - 3 * u2 + 1;
|
|
202
|
+
const h10 = u3 - 2 * u2 + u;
|
|
203
|
+
const h01 = -2 * u3 + 3 * u2;
|
|
204
|
+
const h11 = u3 - u2;
|
|
205
|
+
const v2 = [0, 1, 2].map((c) => {
|
|
206
|
+
const linear = p0.v[c] + (p1.v[c] - p0.v[c]) * u;
|
|
207
|
+
const spline = h00 * p0.v[c] + h10 * h * m0[c] + h01 * p1.v[c] + h11 * h * m1[c];
|
|
208
|
+
const value = linear + (spline - linear) * smooth;
|
|
209
|
+
return clamp(value, Math.min(p0.v[c], p1.v[c]), Math.max(p0.v[c], p1.v[c]));
|
|
210
|
+
});
|
|
211
|
+
return [...outOfSpace(space, v2), alpha];
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function sampleVectorGradient(g, t) {
|
|
216
|
+
const ramp = vectorGradientRamp(g);
|
|
217
|
+
if (ramp) {
|
|
218
|
+
const [r, gg, b, a] = ramp.sample(t);
|
|
219
|
+
return { color: rgbaToHex(r, gg, b), opacity: a };
|
|
220
|
+
}
|
|
221
|
+
const stops = sortedVectorGradientStops(g);
|
|
222
|
+
const s = [...stops].reverse().find((x) => x.offset <= t) ?? stops[0];
|
|
223
|
+
return s ? { color: s.color, opacity: s.opacity ?? 1 } : { color: "#000000", opacity: 0 };
|
|
224
|
+
}
|
|
225
|
+
var stopOf = (offset, c) => {
|
|
226
|
+
const color = rgbaToHex(c[0], c[1], c[2]);
|
|
227
|
+
const opacity = Math.round(c[3] * 1e4) / 1e4;
|
|
228
|
+
return opacity < 1 ? { offset, color, opacity } : { offset, color };
|
|
229
|
+
};
|
|
230
|
+
var maxDiff = (a, b) => Math.max(Math.abs(a[0] - b[0]), Math.abs(a[1] - b[1]), Math.abs(a[2] - b[2]), Math.abs(a[3] - b[3]));
|
|
231
|
+
var lerpRgba = (a, b, u) => [0, 1, 2, 3].map((c) => a[c] + (b[c] - a[c]) * u);
|
|
232
|
+
function expandVectorGradientStops(g, options = {}) {
|
|
233
|
+
const stops = sortedVectorGradientStops(g);
|
|
234
|
+
const plain3 = () => stops.map(({ offset, color, opacity }) => opacity !== void 0 && opacity !== 1 ? { offset, color, opacity } : { offset, color });
|
|
235
|
+
if (!vectorGradientNeedsExpansion(g)) return plain3();
|
|
236
|
+
const ramp = vectorGradientRamp(g);
|
|
237
|
+
if (!ramp || stops.length < 2) return plain3();
|
|
238
|
+
const tol = options.tolerance ?? 0.5 / 255;
|
|
239
|
+
const sRgbOnly = (g.interpolation ?? "srgb") === "srgb" && !((g.smoothness ?? 0) > 0);
|
|
240
|
+
const own = (s) => {
|
|
241
|
+
const c = parseVectorColor(s.color);
|
|
242
|
+
return [c[0], c[1], c[2], c[3] * clamp(s.opacity ?? 1, 0, 1)];
|
|
243
|
+
};
|
|
244
|
+
const out = [];
|
|
245
|
+
const push = (offset, c) => out.push(stopOf(offset, c));
|
|
246
|
+
for (let i = 0; i < stops.length; i++) {
|
|
247
|
+
const a = stops[i];
|
|
248
|
+
const b = stops[i + 1];
|
|
249
|
+
push(a.offset, own(a));
|
|
250
|
+
if (!b || b.offset - a.offset < 1e-9) continue;
|
|
251
|
+
const knots = [a.offset];
|
|
252
|
+
if (hasMidpoint(a)) knots.push(a.offset + (b.offset - a.offset) * clamp(a.midpoint, MID_MIN, MID_MAX));
|
|
253
|
+
knots.push(b.offset);
|
|
254
|
+
const inside = (t) => ramp.sample(clamp(t, a.offset + 1e-9, b.offset - 1e-9));
|
|
255
|
+
for (let k = 0; k + 1 < knots.length; k++) {
|
|
256
|
+
const t0 = knots[k];
|
|
257
|
+
const t1 = knots[k + 1];
|
|
258
|
+
if (k > 0) push(t0, inside(t0));
|
|
259
|
+
if (sRgbOnly) continue;
|
|
260
|
+
const refine = (x0, c0, x1, c1, depth) => {
|
|
261
|
+
const xm = (x0 + x1) / 2;
|
|
262
|
+
const cm = inside(xm);
|
|
263
|
+
const ok = depth >= 8 || [0.03, 0.1, 0.25, 0.5, 0.75, 0.9, 0.97].every((f) => maxDiff(inside(x0 + (x1 - x0) * f), lerpRgba(c0, c1, f)) <= tol);
|
|
264
|
+
if (ok) return;
|
|
265
|
+
refine(x0, c0, xm, cm, depth + 1);
|
|
266
|
+
push(xm, cm);
|
|
267
|
+
refine(xm, cm, x1, c1, depth + 1);
|
|
268
|
+
};
|
|
269
|
+
refine(t0, inside(t0), t1, inside(t1), 0);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
function samplePlainStops(stops, t) {
|
|
275
|
+
const parsed = stops.map((s) => {
|
|
276
|
+
const c = parseVectorColor(s.color) ?? [0, 0, 0, 1];
|
|
277
|
+
return { t: s.offset, c: [c[0], c[1], c[2], c[3] * (s.opacity ?? 1)] };
|
|
278
|
+
});
|
|
279
|
+
if (!parsed.length) return [0, 0, 0, 0];
|
|
280
|
+
if (t <= parsed[0].t) return parsed[0].c;
|
|
281
|
+
for (let i = 0; i + 1 < parsed.length; i++) {
|
|
282
|
+
const a = parsed[i];
|
|
283
|
+
const b = parsed[i + 1];
|
|
284
|
+
if (t <= b.t) return b.t - a.t < 1e-12 ? b.c : lerpRgba(a.c, b.c, (t - a.t) / (b.t - a.t));
|
|
285
|
+
}
|
|
286
|
+
return parsed[parsed.length - 1].c;
|
|
287
|
+
}
|
|
288
|
+
function reverseVectorGradient(g) {
|
|
289
|
+
const stops = sortedVectorGradientStops(g).reverse();
|
|
290
|
+
const next = stops.map((s, i) => {
|
|
291
|
+
const following = stops[i + 1];
|
|
292
|
+
const mid = following ? 1 - (following.midpoint ?? 0.5) : 0.5;
|
|
293
|
+
const { midpoint: _m, ...rest } = s;
|
|
294
|
+
return { ...rest, offset: 1 - s.offset, ...Math.abs(mid - 0.5) > 1e-6 ? { midpoint: mid } : {} };
|
|
295
|
+
});
|
|
296
|
+
return { ...g, stops: next };
|
|
297
|
+
}
|
|
298
|
+
function placeStop(stops, stop) {
|
|
299
|
+
let i = stops.findIndex((o) => o.offset > stop.offset);
|
|
300
|
+
if (i < 0) i = stops.length;
|
|
301
|
+
stops.splice(i, 0, stop);
|
|
302
|
+
return i;
|
|
303
|
+
}
|
|
304
|
+
function insertVectorGradientStop(g, offset) {
|
|
305
|
+
const at2 = clamp(offset, 0, 1);
|
|
306
|
+
const { color, opacity } = sampleVectorGradient(g, at2);
|
|
307
|
+
const stops = sortedVectorGradientStops(g);
|
|
308
|
+
const index = placeStop(stops, opacity < 1 ? { offset: at2, color, opacity } : { offset: at2, color });
|
|
309
|
+
return { stops, index };
|
|
310
|
+
}
|
|
311
|
+
function moveVectorGradientStop(stops, index, offset) {
|
|
312
|
+
const next = stops.map((s2) => ({ ...s2 }));
|
|
313
|
+
if (index < 0 || index >= next.length) return { stops: next, index };
|
|
314
|
+
const [s] = next.splice(index, 1);
|
|
315
|
+
return { stops: next, index: placeStop(next, { ...s, offset: clamp(offset, 0, 1) }) };
|
|
316
|
+
}
|
|
317
|
+
function removeVectorGradientStop(stops, index) {
|
|
318
|
+
if (stops.length <= VECTOR_GRADIENT_MIN_STOPS || index < 0 || index >= stops.length) return null;
|
|
319
|
+
return stops.filter((_, i) => i !== index);
|
|
320
|
+
}
|
|
321
|
+
function vectorGradientMidpointPosition(stops, index) {
|
|
322
|
+
const a = stops[index];
|
|
323
|
+
const b = stops[index + 1];
|
|
324
|
+
if (!a || !b) return null;
|
|
325
|
+
return a.offset + (b.offset - a.offset) * (a.midpoint ?? 0.5);
|
|
326
|
+
}
|
|
327
|
+
function vectorGradientMidpointFromPosition(stops, index, pos) {
|
|
328
|
+
const a = stops[index];
|
|
329
|
+
const b = stops[index + 1];
|
|
330
|
+
if (!a || !b) return null;
|
|
331
|
+
const span = Math.max(b.offset - a.offset, 1e-6);
|
|
332
|
+
return clamp((pos - a.offset) / span, 0.05, 0.95);
|
|
333
|
+
}
|
|
334
|
+
function spanVectorGradientStops(g, from, to, expanded = expandVectorGradientStops(g)) {
|
|
335
|
+
if (to < from) {
|
|
336
|
+
const s = spanVectorGradientStops(g, to, from, expanded);
|
|
337
|
+
return s.map((x) => ({ ...x, offset: 1 - x.offset })).reverse();
|
|
338
|
+
}
|
|
339
|
+
const d = to - from;
|
|
340
|
+
const at2 = (offset, t) => stopOf(offset, samplePlainStops(expanded, t));
|
|
341
|
+
const out = [at2(0, from)];
|
|
342
|
+
if (d > 1e-9) {
|
|
343
|
+
for (const s of expanded) if (s.offset > from && s.offset < to) out.push({ ...s, offset: (s.offset - from) / d });
|
|
344
|
+
}
|
|
345
|
+
out.push(at2(1, to));
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
var hasExplicitGradientGeometry = (g) => finitePoint(g.start) && finitePoint(g.end);
|
|
349
|
+
function shorthandPoints(g) {
|
|
350
|
+
if (g.type === "linear" || g.type === "reflected") {
|
|
351
|
+
const c = angleToSvgLinearCoords(Number.isFinite(g.angle) ? g.angle : 180);
|
|
352
|
+
const start2 = g.type === "linear" ? { x: c.x1, y: c.y1 } : { x: 0.5, y: 0.5 };
|
|
353
|
+
const end = { x: c.x2, y: c.y2 };
|
|
354
|
+
return { start: start2, end, width: add(start2, perp(sub(end, start2))) };
|
|
355
|
+
}
|
|
356
|
+
const start = { x: Number.isFinite(g.cx) ? g.cx : 0.5, y: Number.isFinite(g.cy) ? g.cy : 0.5 };
|
|
357
|
+
const r = Number.isFinite(g.r) ? g.r : 0.5;
|
|
358
|
+
if (g.type === "radial") return { start, end: { x: start.x + r, y: start.y }, width: { x: start.x, y: start.y + r } };
|
|
359
|
+
const a = (Number.isFinite(g.angle) ? g.angle : 90) * Math.PI / 180;
|
|
360
|
+
const dir = { x: Math.sin(a), y: -Math.cos(a) };
|
|
361
|
+
return { start, end: add(start, scale(dir, r)), width: add(start, scale(perp(dir), r)) };
|
|
362
|
+
}
|
|
363
|
+
function vectorGradientPoints(g) {
|
|
364
|
+
const explicit = hasExplicitGradientGeometry(g);
|
|
365
|
+
const base = explicit ? null : shorthandPoints(g);
|
|
366
|
+
const start = explicit ? { x: g.start.x, y: g.start.y } : base.start;
|
|
367
|
+
const end = explicit ? { x: g.end.x, y: g.end.y } : base.end;
|
|
368
|
+
const width = explicit ? finitePoint(g.width) ? { x: g.width.x, y: g.width.y } : add(start, perp(sub(end, start))) : base.width;
|
|
369
|
+
return {
|
|
370
|
+
start,
|
|
371
|
+
end,
|
|
372
|
+
width,
|
|
373
|
+
focal: g.type === "radial" && finitePoint(g.focal) ? { x: g.focal.x, y: g.focal.y } : null,
|
|
374
|
+
focalRadius: g.type === "radial" ? clamp(Number.isFinite(g.focalRadius) ? g.focalRadius : 0, 0, 0.99) : 0,
|
|
375
|
+
units: explicit && g.units === "userSpaceOnUse" ? "userSpaceOnUse" : "objectBoundingBox"
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function vectorBoxMatrix(bounds) {
|
|
379
|
+
if (!bounds) return IDENTITY;
|
|
380
|
+
let { x, y, width: w, height: h } = bounds;
|
|
381
|
+
const fallback = Math.max(w, h, 1e-9) > 1e-9 ? Math.max(w, h) : 1;
|
|
382
|
+
if (!(w > 1e-9)) {
|
|
383
|
+
x -= fallback / 2 - w / 2;
|
|
384
|
+
w = fallback;
|
|
385
|
+
}
|
|
386
|
+
if (!(h > 1e-9)) {
|
|
387
|
+
y -= fallback / 2 - h / 2;
|
|
388
|
+
h = fallback;
|
|
389
|
+
}
|
|
390
|
+
return [w, 0, 0, h, x, y];
|
|
391
|
+
}
|
|
392
|
+
var framePointsMatrix = (p) => [p.end.x - p.start.x, p.end.y - p.start.y, p.width.x - p.start.x, p.width.y - p.start.y, p.start.x, p.start.y];
|
|
393
|
+
function vectorGradientUnitsMatrix(g, bounds) {
|
|
394
|
+
return vectorGradientPoints(g).units === "objectBoundingBox" ? vectorBoxMatrix(bounds) : IDENTITY;
|
|
395
|
+
}
|
|
396
|
+
function vectorGradientMatrix(g, bounds) {
|
|
397
|
+
return mul(framePointsMatrix(vectorGradientPoints(g)), vectorGradientUnitsMatrix(g, bounds));
|
|
398
|
+
}
|
|
399
|
+
var FOCAL_MAX = 0.99;
|
|
400
|
+
function unitFocal(g) {
|
|
401
|
+
const p = vectorGradientPoints(g);
|
|
402
|
+
if (!p.focal && !(p.focalRadius > 0)) return null;
|
|
403
|
+
const inv = invert(framePointsMatrix(p));
|
|
404
|
+
if (!inv) return null;
|
|
405
|
+
let f = p.focal ? apply(inv, p.focal) : { x: 0, y: 0 };
|
|
406
|
+
const reach = FOCAL_MAX * (1 - p.focalRadius);
|
|
407
|
+
const r = Math.hypot(f.x, f.y);
|
|
408
|
+
if (r > reach) f = scale(f, reach / r);
|
|
409
|
+
return { f, fr: p.focalRadius };
|
|
410
|
+
}
|
|
411
|
+
function vectorGradientFocalParam(q, f = { x: 0, y: 0 }, fr = 0) {
|
|
412
|
+
const d = sub(q, f);
|
|
413
|
+
const dr = 1 - fr;
|
|
414
|
+
const a = dot(f, f) - dr * dr;
|
|
415
|
+
const b = dot(d, f) - fr * dr;
|
|
416
|
+
const c = dot(d, d) - fr * fr;
|
|
417
|
+
if (Math.abs(a) < 1e-12) return Math.abs(b) < 1e-12 ? 0 : -c / (2 * b);
|
|
418
|
+
return (-b - Math.sqrt(Math.max(0, b * b - a * c))) / a;
|
|
419
|
+
}
|
|
420
|
+
function vectorGradientParam(type, q, focal) {
|
|
421
|
+
switch (type) {
|
|
422
|
+
case "linear":
|
|
423
|
+
return q.x;
|
|
424
|
+
case "reflected":
|
|
425
|
+
return Math.abs(q.x);
|
|
426
|
+
case "diamond":
|
|
427
|
+
return Math.abs(q.x) + Math.abs(q.y);
|
|
428
|
+
case "conic": {
|
|
429
|
+
const turn = Math.atan2(q.y, q.x) / (2 * Math.PI);
|
|
430
|
+
return turn - Math.floor(turn);
|
|
431
|
+
}
|
|
432
|
+
case "radial":
|
|
433
|
+
return focal ? vectorGradientFocalParam(q, focal.f, focal.fr) : Math.hypot(q.x, q.y);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function applyVectorGradientSpread(t, spread = "pad") {
|
|
437
|
+
if (!Number.isFinite(t)) return 0;
|
|
438
|
+
if (spread === "repeat") return t - Math.floor(t);
|
|
439
|
+
if (spread === "reflect") {
|
|
440
|
+
const m = (t % 2 + 2) % 2;
|
|
441
|
+
return m <= 1 ? m : 2 - m;
|
|
442
|
+
}
|
|
443
|
+
return clamp(t, 0, 1);
|
|
444
|
+
}
|
|
445
|
+
function vectorGradientTAt(g, bounds, p) {
|
|
446
|
+
const inv = invert(vectorGradientMatrix(g, bounds));
|
|
447
|
+
if (!inv) return 0;
|
|
448
|
+
const raw = vectorGradientParam(g.type, apply(inv, p), g.type === "radial" ? unitFocal(g) : null);
|
|
449
|
+
return g.type === "conic" ? raw : applyVectorGradientSpread(raw, g.spread ?? "pad");
|
|
450
|
+
}
|
|
451
|
+
function sampleVectorGradientAt(g, bounds, p) {
|
|
452
|
+
const t = vectorGradientTAt(g, bounds, p);
|
|
453
|
+
const ramp = vectorGradientRamp(g);
|
|
454
|
+
return ramp ? ramp.sample(t) : samplePlainStops(sortedVectorGradientStops(g), t);
|
|
455
|
+
}
|
|
456
|
+
function vectorGradientAspect(g) {
|
|
457
|
+
const p = vectorGradientPoints(g);
|
|
458
|
+
const u = sub(p.end, p.start);
|
|
459
|
+
const l2 = dot(u, u);
|
|
460
|
+
return l2 > 1e-18 ? dot(sub(p.width, p.start), perp(u)) / l2 : 1;
|
|
461
|
+
}
|
|
462
|
+
function explicitVectorGradient(g) {
|
|
463
|
+
const p = vectorGradientPoints(g);
|
|
464
|
+
const { angle: _a, cx: _cx, cy: _cy, r: _r, ...rest } = g;
|
|
465
|
+
return {
|
|
466
|
+
...rest,
|
|
467
|
+
start: p.start,
|
|
468
|
+
end: p.end,
|
|
469
|
+
width: p.width,
|
|
470
|
+
...p.focal ? { focal: p.focal } : {},
|
|
471
|
+
units: p.units
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function pinVectorGradient(g, bounds) {
|
|
475
|
+
const e = explicitVectorGradient(g);
|
|
476
|
+
if (e.units === "userSpaceOnUse") return e;
|
|
477
|
+
const m = vectorBoxMatrix(bounds);
|
|
478
|
+
return { ...e, start: apply(m, e.start), end: apply(m, e.end), width: apply(m, e.width), ...e.focal ? { focal: apply(m, e.focal) } : {}, units: "userSpaceOnUse" };
|
|
479
|
+
}
|
|
480
|
+
function transformVectorGradient(g, m) {
|
|
481
|
+
if (!hasExplicitGradientGeometry(g) || g.units !== "userSpaceOnUse" || isIdentity(m)) return g;
|
|
482
|
+
const p = vectorGradientPoints(g);
|
|
483
|
+
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
|
+
}
|
|
485
|
+
function transformVectorPaint(paint, m) {
|
|
486
|
+
return isVectorGradient(paint) ? transformVectorGradient(paint, m) : paint;
|
|
487
|
+
}
|
|
488
|
+
var isCentredVectorGradient = (type) => type === "radial" || type === "conic" || type === "diamond";
|
|
489
|
+
function vectorGradientHandles(g, bounds, toWorld = IDENTITY) {
|
|
490
|
+
const p = vectorGradientPoints(g);
|
|
491
|
+
const m = mul(vectorGradientUnitsMatrix(g, bounds), toWorld);
|
|
492
|
+
const f = g.type === "radial" ? unitFocal(g) : null;
|
|
493
|
+
const frame2 = mul(framePointsMatrix(p), m);
|
|
494
|
+
return {
|
|
495
|
+
start: apply(m, p.start),
|
|
496
|
+
end: apply(m, p.end),
|
|
497
|
+
aspect: isCentredVectorGradient(g.type) ? apply(m, p.width) : null,
|
|
498
|
+
focal: g.type === "radial" ? apply(frame2, f?.f ?? { x: 0, y: 0 }) : null
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function withMainAxis(p, start, end) {
|
|
502
|
+
const before = invert(framePointsMatrix(p));
|
|
503
|
+
const u = sub(p.end, p.start);
|
|
504
|
+
const l2 = dot(u, u);
|
|
505
|
+
const w = sub(p.width, p.start);
|
|
506
|
+
const rel = l2 > 1e-18 ? { a: dot(w, u) / l2, b: dot(w, perp(u)) / l2 } : { a: 0, b: 1 };
|
|
507
|
+
const u2 = sub(end, start);
|
|
508
|
+
const width = add(start, add(scale(u2, rel.a), scale(perp(u2), rel.b)));
|
|
509
|
+
const next = { start, end, width };
|
|
510
|
+
const focal = p.focal && before ? apply(framePointsMatrix(next), apply(before, p.focal)) : p.focal;
|
|
511
|
+
return { ...next, focal };
|
|
512
|
+
}
|
|
513
|
+
function moveVectorGradientHandle(g, bounds, handle, to, from = to) {
|
|
514
|
+
const pinned = pinVectorGradient(g, bounds);
|
|
515
|
+
const p = vectorGradientPoints(pinned);
|
|
516
|
+
const set = (q) => {
|
|
517
|
+
const next = { ...pinned, ...q };
|
|
518
|
+
if (next.focal === null || next.focal === void 0) delete next.focal;
|
|
519
|
+
return next;
|
|
520
|
+
};
|
|
521
|
+
switch (handle) {
|
|
522
|
+
case "start": {
|
|
523
|
+
const d = sub(to, from);
|
|
524
|
+
return set({ start: add(p.start, d), end: add(p.end, d), width: add(p.width, d), focal: p.focal ? add(p.focal, d) : null });
|
|
525
|
+
}
|
|
526
|
+
case "end":
|
|
527
|
+
case "radius": {
|
|
528
|
+
if (Math.hypot(to.x - p.start.x, to.y - p.start.y) < 1e-6) return pinned;
|
|
529
|
+
const n = withMainAxis(p, p.start, to);
|
|
530
|
+
return set({ end: n.end, width: n.width, focal: n.focal });
|
|
531
|
+
}
|
|
532
|
+
case "aspect": {
|
|
533
|
+
const u = sub(p.end, p.start);
|
|
534
|
+
const l2 = dot(u, u);
|
|
535
|
+
if (l2 < 1e-18) return pinned;
|
|
536
|
+
let aspect = dot(sub(to, p.start), perp(u)) / l2;
|
|
537
|
+
if (Math.abs(aspect) < 0.01) aspect = aspect < 0 ? -0.01 : 0.01;
|
|
538
|
+
const before = invert(framePointsMatrix(p));
|
|
539
|
+
const width = add(p.start, scale(perp(u), aspect));
|
|
540
|
+
const focal = p.focal && before ? apply(framePointsMatrix({ start: p.start, end: p.end, width }), apply(before, p.focal)) : null;
|
|
541
|
+
return set({ width, focal });
|
|
542
|
+
}
|
|
543
|
+
case "focal": {
|
|
544
|
+
const frame2 = framePointsMatrix(p);
|
|
545
|
+
const inv = invert(frame2);
|
|
546
|
+
if (!inv) return pinned;
|
|
547
|
+
let q = apply(inv, to);
|
|
548
|
+
const r = Math.hypot(q.x, q.y);
|
|
549
|
+
if (r < 1e-6) return set({ focal: null });
|
|
550
|
+
const reach = FOCAL_MAX * (1 - p.focalRadius);
|
|
551
|
+
if (r > reach) q = scale(q, reach / r);
|
|
552
|
+
return set({ focal: apply(frame2, q) });
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function setVectorGradientVector(g, bounds, start, end) {
|
|
557
|
+
const pinned = pinVectorGradient(g, bounds);
|
|
558
|
+
const p = vectorGradientPoints(pinned);
|
|
559
|
+
const aspect = Math.abs(vectorGradientAspect(pinned)) > 1e-6 ? vectorGradientAspect(pinned) : 1;
|
|
560
|
+
const u = sub(end, start);
|
|
561
|
+
const width = add(start, scale(perp(u), aspect));
|
|
562
|
+
const before = invert(framePointsMatrix(p));
|
|
563
|
+
const focal = p.focal && before ? apply(framePointsMatrix({ start, end, width }), apply(before, p.focal)) : null;
|
|
564
|
+
const next = { ...pinned, start, end, width };
|
|
565
|
+
if (focal) next.focal = focal;
|
|
566
|
+
else delete next.focal;
|
|
567
|
+
return next;
|
|
568
|
+
}
|
|
569
|
+
var VECTOR_GRADIENT_MAX_PERIODS = 64;
|
|
570
|
+
function wholePeriod(stops) {
|
|
571
|
+
const out = stops.map((s) => ({ ...s }));
|
|
572
|
+
if (out.length && out[0].offset > 0) out.unshift({ ...out[0], offset: 0 });
|
|
573
|
+
const last = out[out.length - 1];
|
|
574
|
+
if (last && last.offset < 1) out.push({ ...last, offset: 1 });
|
|
575
|
+
return out;
|
|
576
|
+
}
|
|
577
|
+
function periodicVectorGradientStops(stops, lo, hi, mirrored) {
|
|
578
|
+
const period = wholePeriod(stops);
|
|
579
|
+
const back = reverseVectorGradient({ stops: period }).stops;
|
|
580
|
+
const n = Math.max(1, hi - lo);
|
|
581
|
+
const out = [];
|
|
582
|
+
for (let j = 0; j < n; j++) {
|
|
583
|
+
const one = mirrored(lo + j) ? back : period;
|
|
584
|
+
for (const s of one) out.push({ ...s, offset: (j + s.offset) / n });
|
|
585
|
+
}
|
|
586
|
+
return out;
|
|
587
|
+
}
|
|
588
|
+
function expandVectorGradientSpread(g, bounds, area) {
|
|
589
|
+
const m = vectorGradientMatrix(g, bounds);
|
|
590
|
+
const inv = invert(m);
|
|
591
|
+
const spread = g.spread ?? "pad";
|
|
592
|
+
const reflected = g.type === "reflected";
|
|
593
|
+
if (!inv || !reflected && spread === "pad") return null;
|
|
594
|
+
const odd = (k) => (k % 2 + 2) % 2 === 1;
|
|
595
|
+
const stops = expandVectorGradientStops(g);
|
|
596
|
+
const along2 = (a, b, mirrored2, capped2 = false) => ({
|
|
597
|
+
start: apply(m, { x: a, y: 0 }),
|
|
598
|
+
end: apply(m, { x: b, y: 0 }),
|
|
599
|
+
from: a,
|
|
600
|
+
to: b,
|
|
601
|
+
matrix: m,
|
|
602
|
+
stops: periodicVectorGradientStops(stops, a, b, mirrored2),
|
|
603
|
+
capped: capped2
|
|
604
|
+
});
|
|
605
|
+
if (reflected && spread === "pad") return along2(-1, 1, (k) => k < 0);
|
|
606
|
+
const us = boxCorners(area).map((c) => apply(inv, c).x);
|
|
607
|
+
let lo = Math.floor(Math.min(...us));
|
|
608
|
+
let hi = Math.ceil(Math.max(...us));
|
|
609
|
+
if (reflected) {
|
|
610
|
+
lo = Math.min(lo, -1);
|
|
611
|
+
hi = Math.max(hi, 1);
|
|
612
|
+
}
|
|
613
|
+
hi = Math.max(hi, lo + 1);
|
|
614
|
+
if (!reflected && lo >= 0 && hi <= 1) return null;
|
|
615
|
+
const capped = hi - lo > VECTOR_GRADIENT_MAX_PERIODS;
|
|
616
|
+
if (capped) {
|
|
617
|
+
lo = Math.max(lo, -VECTOR_GRADIENT_MAX_PERIODS / 2);
|
|
618
|
+
hi = lo + VECTOR_GRADIENT_MAX_PERIODS;
|
|
619
|
+
}
|
|
620
|
+
const mirrored = reflected ? (k) => {
|
|
621
|
+
const j = k >= 0 ? k : -k - 1;
|
|
622
|
+
const own = spread === "reflect" && odd(j);
|
|
623
|
+
return k < 0 ? !own : own;
|
|
624
|
+
} : (k) => spread === "reflect" && odd(k);
|
|
625
|
+
return along2(lo, hi, mirrored, capped);
|
|
626
|
+
}
|
|
627
|
+
var boxCorners = (b) => [
|
|
628
|
+
{ x: b.x, y: b.y },
|
|
629
|
+
{ x: b.x + b.width, y: b.y },
|
|
630
|
+
{ x: b.x + b.width, y: b.y + b.height },
|
|
631
|
+
{ x: b.x, y: b.y + b.height }
|
|
632
|
+
];
|
|
633
|
+
var toSvgStops = (stops) => stops.map((s) => ({ offset: `${fmt(s.offset * 100, 3)}%`, stopColor: s.color, ...s.opacity !== void 0 && s.opacity !== 1 ? { stopOpacity: Math.round(s.opacity * 1e4) / 1e4 } : {} }));
|
|
634
|
+
var polygonD = (pts, precision) => `M${pts.map((p) => `${fmt(p.x, precision)} ${fmt(p.y, precision)}`).join("L")}Z`;
|
|
635
|
+
var padBounds = (b, pad) => ({ x: b.x - pad, y: b.y - pad, width: b.width + 2 * pad, height: b.height + 2 * pad });
|
|
636
|
+
var vectorGradientArea = (bounds) => padBounds(bounds ?? { x: 0, y: 0, width: 1, height: 1 }, 1);
|
|
637
|
+
var transformAttr = (m) => isIdentity(m) ? {} : { gradientTransform: fmtMatrix(m) };
|
|
638
|
+
function minScale(m) {
|
|
639
|
+
const [a, b, c, d] = m;
|
|
640
|
+
const p = a * a + b * b;
|
|
641
|
+
const q = c * c + d * d;
|
|
642
|
+
const r = a * c + b * d;
|
|
643
|
+
return Math.sqrt(Math.max(0, (p + q - Math.sqrt((p - q) ** 2 + 4 * r * r)) / 2));
|
|
644
|
+
}
|
|
645
|
+
function patternAttrs(area) {
|
|
646
|
+
const [x, y, w, h] = [area.x, area.y, Math.max(area.width, 1e-3), Math.max(area.height, 1e-3)].map((n) => fmt(n, 3));
|
|
647
|
+
return { patternUnits: "userSpaceOnUse", x, y, width: w, height: h, viewBox: `${x} ${y} ${w} ${h}` };
|
|
648
|
+
}
|
|
649
|
+
function patternDef(id, area, gradients, tiles) {
|
|
650
|
+
return {
|
|
651
|
+
id,
|
|
652
|
+
type: "pattern",
|
|
653
|
+
attrs: patternAttrs(area),
|
|
654
|
+
gradients,
|
|
655
|
+
tiles
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function vectorGradientSvgDefs(g, id, options = {}) {
|
|
659
|
+
const precision = options.precision ?? 3;
|
|
660
|
+
const bounds = options.bounds ?? null;
|
|
661
|
+
const area = options.area ?? vectorGradientArea(bounds);
|
|
662
|
+
const spread = g.spread && VECTOR_GRADIENT_SPREADS.includes(g.spread) ? g.spread : "pad";
|
|
663
|
+
const stops = expandVectorGradientStops(g);
|
|
664
|
+
const p = vectorGradientPoints(g);
|
|
665
|
+
const unitsM = vectorGradientUnitsMatrix(g, bounds);
|
|
666
|
+
const frameM = mul(framePointsMatrix(p), unitsM);
|
|
667
|
+
const spreadAttr = (s) => s === "pad" ? {} : { spreadMethod: s };
|
|
668
|
+
const base = { gradientUnits: "userSpaceOnUse" };
|
|
669
|
+
switch (g.type) {
|
|
670
|
+
case "linear":
|
|
671
|
+
case "reflected": {
|
|
672
|
+
if (g.type === "reflected" && spread !== "reflect") {
|
|
673
|
+
const ex = expandVectorGradientSpread(g, bounds, area);
|
|
674
|
+
const [a, b] = ex ? [ex.from, ex.to] : [-1, 1];
|
|
675
|
+
const st = ex?.stops ?? periodicVectorGradientStops(stops, -1, 1, (k) => k < 0);
|
|
676
|
+
return [{ id, type: "linear", attrs: { ...base, x1: a, y1: 0, x2: b, y2: 0, gradientTransform: fmtMatrix(frameM) }, stops: toSvgStops(st) }];
|
|
677
|
+
}
|
|
678
|
+
const u = sub(p.end, p.start);
|
|
679
|
+
const w = sub(p.width, p.start);
|
|
680
|
+
const square = Math.abs(dot(u, w)) <= 1e-9 * Math.max(dot(u, u), dot(w, w), 1e-18);
|
|
681
|
+
const coords = square ? { x1: fmt(p.start.x, 6), y1: fmt(p.start.y, 6), x2: fmt(p.end.x, 6), y2: fmt(p.end.y, 6), ...transformAttr(unitsM) } : { x1: 0, y1: 0, x2: 1, y2: 0, gradientTransform: fmtMatrix(frameM) };
|
|
682
|
+
return [{ id, type: "linear", attrs: { ...base, ...coords, ...spreadAttr(spread) }, stops: toSvgStops(stops) }];
|
|
683
|
+
}
|
|
684
|
+
case "radial": {
|
|
685
|
+
const f = unitFocal(g);
|
|
686
|
+
const attrs = { ...base, cx: 0, cy: 0, r: 1, gradientTransform: fmtMatrix(frameM), ...spreadAttr(spread) };
|
|
687
|
+
if (f && (Math.abs(f.f.x) > 1e-9 || Math.abs(f.f.y) > 1e-9)) {
|
|
688
|
+
attrs.fx = fmt(f.f.x, 6);
|
|
689
|
+
attrs.fy = fmt(f.f.y, 6);
|
|
690
|
+
}
|
|
691
|
+
if (f && f.fr > 0) attrs.fr = fmt(f.fr, 6);
|
|
692
|
+
return [{ id, type: "radial", attrs, stops: toSvgStops(stops) }];
|
|
693
|
+
}
|
|
694
|
+
case "diamond": {
|
|
695
|
+
const inv = invert(frameM);
|
|
696
|
+
if (!inv) return [patternDef(id, area, [], [{ d: polygonD(boxCorners(area), precision), fill: stops[stops.length - 1]?.color ?? "none" }])];
|
|
697
|
+
const reach = 2 * Math.max(...boxCorners(area).map((c) => {
|
|
698
|
+
const q = apply(inv, c);
|
|
699
|
+
return Math.abs(q.x) + Math.abs(q.y);
|
|
700
|
+
})) + 1;
|
|
701
|
+
const delta = Math.min(0.05 * reach, 0.75 / Math.max(minScale(frameM), 1e-9));
|
|
702
|
+
const gradients = [];
|
|
703
|
+
const tiles = [];
|
|
704
|
+
[[1, 1], [-1, 1], [-1, -1], [1, -1]].forEach(([sx, sy], k) => {
|
|
705
|
+
const gid = `${id}-q${k}`;
|
|
706
|
+
gradients.push({ id: gid, type: "linear", attrs: { ...base, x1: 0, y1: 0, x2: sx / 2, y2: sy / 2, gradientTransform: fmtMatrix(frameM), ...spreadAttr(spread) }, stops: toSvgStops(stops) });
|
|
707
|
+
const quad = [{ x: -delta * sx, y: -delta * sy }, { x: reach * sx, y: -delta * sy }, { x: -delta * sx, y: reach * sy }].map((q) => apply(frameM, q));
|
|
708
|
+
tiles.push({ d: polygonD(quad, precision), fill: `url(#${gid})` });
|
|
709
|
+
});
|
|
710
|
+
return [patternDef(id, area, gradients, tiles)];
|
|
711
|
+
}
|
|
712
|
+
case "conic":
|
|
713
|
+
return [conicPattern(id, frameM, stops, area, options.wedges ?? 72, precision)];
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function conicPattern(id, frameM, stops, area, wedges, precision) {
|
|
717
|
+
const inv = invert(frameM);
|
|
718
|
+
const n = Math.max(8, Math.min(720, Math.round(wedges)));
|
|
719
|
+
const half = Math.PI / n;
|
|
720
|
+
if (!inv || !stops.length) return patternDef(id, area, [], [{ d: polygonD(boxCorners(area), precision), fill: stops[0]?.color ?? "none" }]);
|
|
721
|
+
const reach = (Math.max(...boxCorners(area).map((c) => Math.hypot(apply(inv, c).x, apply(inv, c).y))) + 1) / Math.cos(half);
|
|
722
|
+
const overlap = Math.min(half, 2e-3);
|
|
723
|
+
const gradients = [];
|
|
724
|
+
const tiles = [];
|
|
725
|
+
for (let k = 0; k < n; k++) {
|
|
726
|
+
const a0 = 2 * Math.PI * k / n;
|
|
727
|
+
const a1 = 2 * Math.PI * (k + 1) / n;
|
|
728
|
+
const mid = (a0 + a1) / 2;
|
|
729
|
+
const tau = { x: -Math.sin(mid), y: Math.cos(mid) };
|
|
730
|
+
const s = Math.sin(half);
|
|
731
|
+
const at2 = (angle) => (Math.sin(angle - mid) / s + 1) / 2;
|
|
732
|
+
const t0 = k / n;
|
|
733
|
+
const t1 = (k + 1) / n;
|
|
734
|
+
const own = [stopOf(0, samplePlainStops(stops, t0))];
|
|
735
|
+
for (const st of stops) if (st.offset > t0 && st.offset < t1) own.push({ ...st, offset: at2(st.offset * 2 * Math.PI) });
|
|
736
|
+
own.push(stopOf(1, samplePlainStops(stops, t1)));
|
|
737
|
+
const gid = `${id}-w${k}`;
|
|
738
|
+
gradients.push({
|
|
739
|
+
id: gid,
|
|
740
|
+
type: "linear",
|
|
741
|
+
attrs: { gradientUnits: "userSpaceOnUse", x1: fmt(-s * tau.x, 6), y1: fmt(-s * tau.y, 6), x2: fmt(s * tau.x, 6), y2: fmt(s * tau.y, 6), gradientTransform: fmtMatrix(frameM) },
|
|
742
|
+
stops: toSvgStops(own)
|
|
743
|
+
});
|
|
744
|
+
const from = a0 - (k === 0 ? 0 : overlap);
|
|
745
|
+
const pts = [{ x: 0, y: 0 }, { x: reach * Math.cos(from), y: reach * Math.sin(from) }, { x: reach * Math.cos(a1), y: reach * Math.sin(a1) }].map((q) => apply(frameM, q));
|
|
746
|
+
tiles.push({ d: polygonD(pts, precision), fill: `url(#${gid})` });
|
|
747
|
+
}
|
|
748
|
+
return patternDef(id, area, gradients, tiles);
|
|
749
|
+
}
|
|
750
|
+
var num = (v2, fallback = 0) => {
|
|
751
|
+
if (v2 === void 0) return fallback;
|
|
752
|
+
const n = typeof v2 === "number" ? v2 : parseFloat(v2);
|
|
753
|
+
return Number.isFinite(n) ? n : fallback;
|
|
754
|
+
};
|
|
755
|
+
var parseMatrixAttr = (v2) => {
|
|
756
|
+
const m = typeof v2 === "string" ? /^matrix\(([^)]*)\)$/.exec(v2.trim()) : null;
|
|
757
|
+
if (!m) return IDENTITY;
|
|
758
|
+
const n = m[1].split(/[\s,]+/).map(Number);
|
|
759
|
+
return n.length === 6 && n.every(Number.isFinite) ? n : IDENTITY;
|
|
760
|
+
};
|
|
761
|
+
var plainOf = (stops) => stops.map((s) => ({ offset: parseFloat(s.offset) / (s.offset.endsWith("%") ? 100 : 1), color: s.stopColor, ...s.stopOpacity !== void 0 ? { opacity: s.stopOpacity } : {} }));
|
|
762
|
+
function inTile(d, p) {
|
|
763
|
+
const n = d.match(/-?[\d.]+(?:e-?\d+)?/gi)?.map(Number) ?? [];
|
|
764
|
+
let inside = false;
|
|
765
|
+
for (let i = 0, j = n.length - 2; i + 1 < n.length; j = i, i += 2) {
|
|
766
|
+
const [xi, yi, xj, yj] = [n[i], n[i + 1], n[j], n[j + 1]];
|
|
767
|
+
if (yi > p.y !== yj > p.y && p.x < (xj - xi) * (p.y - yi) / (yj - yi) + xi) inside = !inside;
|
|
768
|
+
}
|
|
769
|
+
return inside;
|
|
770
|
+
}
|
|
771
|
+
function sampleVectorPaintDefAt(defs, id, p) {
|
|
772
|
+
const all = /* @__PURE__ */ new Map();
|
|
773
|
+
for (const d of defs) {
|
|
774
|
+
all.set(d.id, d);
|
|
775
|
+
if (d.type === "pattern") for (const gd of d.gradients) all.set(gd.id, gd);
|
|
776
|
+
}
|
|
777
|
+
const paint = (ref, q) => {
|
|
778
|
+
const url = /^url\(#(.+)\)$/.exec(ref);
|
|
779
|
+
if (!url) return parseVectorColor(ref);
|
|
780
|
+
const def = all.get(url[1]);
|
|
781
|
+
if (!def) return null;
|
|
782
|
+
if (def.type === "pattern") {
|
|
783
|
+
for (let i = def.tiles.length - 1; i >= 0; i--) if (inTile(def.tiles[i].d, q)) return paint(def.tiles[i].fill, q);
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
const inv = invert(parseMatrixAttr(def.attrs.gradientTransform)) ?? IDENTITY;
|
|
787
|
+
const local = apply(inv, q);
|
|
788
|
+
const spread = def.attrs.spreadMethod ?? "pad";
|
|
789
|
+
let t;
|
|
790
|
+
if (def.type === "radial") {
|
|
791
|
+
const r = num(def.attrs.r, 0.5);
|
|
792
|
+
const c = { x: num(def.attrs.cx), y: num(def.attrs.cy) };
|
|
793
|
+
const f = { x: num(def.attrs.fx, c.x), y: num(def.attrs.fy, c.y) };
|
|
794
|
+
const u = { x: (local.x - c.x) / r, y: (local.y - c.y) / r };
|
|
795
|
+
t = vectorGradientFocalParam(u, { x: (f.x - c.x) / r, y: (f.y - c.y) / r }, num(def.attrs.fr) / r);
|
|
796
|
+
} else {
|
|
797
|
+
const a = { x: num(def.attrs.x1), y: num(def.attrs.y1) };
|
|
798
|
+
const b = { x: num(def.attrs.x2, 1), y: num(def.attrs.y2) };
|
|
799
|
+
const v2 = sub(b, a);
|
|
800
|
+
const l2 = dot(v2, v2);
|
|
801
|
+
t = l2 > 1e-18 ? dot(sub(local, a), v2) / l2 : 0;
|
|
802
|
+
}
|
|
803
|
+
return samplePlainStops(plainOf(def.stops), applyVectorGradientSpread(t, spread));
|
|
804
|
+
};
|
|
805
|
+
return paint(`url(#${id})`, p);
|
|
806
|
+
}
|
|
807
|
+
var readPoint = (v2) => finitePoint(v2) ? { x: v2.x, y: v2.y } : void 0;
|
|
808
|
+
function sanitizeVectorGradient(input) {
|
|
809
|
+
if (!input || typeof input !== "object") return null;
|
|
810
|
+
const r = input;
|
|
811
|
+
if (!VECTOR_GRADIENT_TYPES.includes(r.type) || !Array.isArray(r.stops)) return null;
|
|
812
|
+
const stops = [];
|
|
813
|
+
for (const s of r.stops) {
|
|
814
|
+
if (!s || typeof s !== "object") continue;
|
|
815
|
+
const x = s;
|
|
816
|
+
if (typeof x.color !== "string" || typeof x.offset !== "number" || !Number.isFinite(x.offset)) continue;
|
|
817
|
+
const stop = { offset: clamp(x.offset, 0, 1), color: x.color.slice(0, 100) };
|
|
818
|
+
if (typeof x.opacity === "number" && Number.isFinite(x.opacity) && x.opacity !== 1) stop.opacity = clamp(x.opacity, 0, 1);
|
|
819
|
+
if (typeof x.midpoint === "number" && Number.isFinite(x.midpoint) && Math.abs(x.midpoint - 0.5) > 1e-6) stop.midpoint = clamp(x.midpoint, 0.05, 0.95);
|
|
820
|
+
stops.push(stop);
|
|
821
|
+
}
|
|
822
|
+
if (!stops.length) return null;
|
|
823
|
+
const g = { type: r.type, stops };
|
|
824
|
+
for (const k of ["angle", "cx", "cy", "r"]) if (typeof r[k] === "number" && Number.isFinite(r[k])) g[k] = r[k];
|
|
825
|
+
const start = readPoint(r.start);
|
|
826
|
+
const end = readPoint(r.end);
|
|
827
|
+
if (start && end) {
|
|
828
|
+
g.start = start;
|
|
829
|
+
g.end = end;
|
|
830
|
+
const width = readPoint(r.width);
|
|
831
|
+
if (width) g.width = width;
|
|
832
|
+
if (r.units === "userSpaceOnUse" || r.units === "objectBoundingBox") g.units = r.units;
|
|
833
|
+
}
|
|
834
|
+
const focal = readPoint(r.focal);
|
|
835
|
+
if (focal && g.type === "radial") g.focal = focal;
|
|
836
|
+
if (typeof r.focalRadius === "number" && r.focalRadius > 0 && g.type === "radial") g.focalRadius = clamp(r.focalRadius, 0, 0.99);
|
|
837
|
+
if (VECTOR_GRADIENT_SPREADS.includes(r.spread) && r.spread !== "pad") g.spread = r.spread;
|
|
838
|
+
if (VECTOR_GRADIENT_INTERPOLATIONS.includes(r.interpolation) && r.interpolation !== "srgb") g.interpolation = r.interpolation;
|
|
839
|
+
if (typeof r.smoothness === "number" && r.smoothness > 0) g.smoothness = clamp(r.smoothness, 0, 1);
|
|
840
|
+
if (VECTOR_STROKE_GRADIENT_MODES.includes(r.strokeMode) && r.strokeMode !== "within") g.strokeMode = r.strokeMode;
|
|
841
|
+
return g;
|
|
842
|
+
}
|
|
843
|
+
function convertVectorGradient(g, type) {
|
|
844
|
+
if (g.type === type) return g;
|
|
845
|
+
const next = { ...g, type };
|
|
846
|
+
if (type !== "radial") {
|
|
847
|
+
delete next.focal;
|
|
848
|
+
delete next.focalRadius;
|
|
849
|
+
}
|
|
850
|
+
if (!hasExplicitGradientGeometry(g)) {
|
|
851
|
+
if (isCentredVectorGradient(type) !== isCentredVectorGradient(g.type)) {
|
|
852
|
+
delete next.angle;
|
|
853
|
+
delete next.cx;
|
|
854
|
+
delete next.cy;
|
|
855
|
+
delete next.r;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return next;
|
|
859
|
+
}
|
|
860
|
+
function defaultVectorGradient(type = "linear", from) {
|
|
861
|
+
if (isVectorGradient(from)) return convertVectorGradient(from, type);
|
|
862
|
+
const colour = typeof from === "string" && parseVectorColor(from) ? from : "#000000";
|
|
863
|
+
return { type, stops: [{ offset: 0, color: colour }, { offset: 1, color: "#ffffff" }] };
|
|
864
|
+
}
|
|
865
|
+
function applyVectorGradientPatch(g, patch) {
|
|
866
|
+
const { aspect, reverse: reverse2, type, ...fields } = patch;
|
|
867
|
+
let next = type && type !== g.type ? convertVectorGradient(g, type) : { ...g };
|
|
868
|
+
const record = next;
|
|
869
|
+
for (const [k, v2] of Object.entries(fields)) {
|
|
870
|
+
if (v2 === void 0) continue;
|
|
871
|
+
if (v2 === null) delete record[k];
|
|
872
|
+
else record[k] = v2;
|
|
873
|
+
}
|
|
874
|
+
if ((fields.start || fields.end) && !hasExplicitGradientGeometry(next)) {
|
|
875
|
+
const p = vectorGradientPoints({ ...g, start: void 0, end: void 0 });
|
|
876
|
+
next.start = next.start ?? p.start;
|
|
877
|
+
next.end = next.end ?? p.end;
|
|
878
|
+
}
|
|
879
|
+
if (aspect !== void 0 && Number.isFinite(aspect)) {
|
|
880
|
+
const e = explicitVectorGradient(next);
|
|
881
|
+
const u = sub(e.end, e.start);
|
|
882
|
+
const a = Math.abs(aspect) < 0.01 ? aspect < 0 ? -0.01 : 0.01 : aspect;
|
|
883
|
+
next = { ...e, width: add(e.start, scale(perp(u), a)) };
|
|
884
|
+
}
|
|
885
|
+
if (reverse2) next = reverseVectorGradient(next);
|
|
886
|
+
const clean2 = sanitizeVectorGradient(next);
|
|
887
|
+
if (!clean2) return null;
|
|
888
|
+
clean2.stops = sortedVectorGradientStops(clean2);
|
|
889
|
+
return clean2;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/vector/corners.ts
|
|
893
|
+
var EPS = 1e-9;
|
|
894
|
+
var MIN_SINE = 1e-6;
|
|
895
|
+
var sub2 = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
896
|
+
var add2 = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
897
|
+
var mul2 = (a, k) => ({ x: a.x * k, y: a.y * k });
|
|
898
|
+
var dot2 = (a, b) => a.x * b.x + a.y * b.y;
|
|
84
899
|
var cross = (a, b) => a.x * b.y - a.y * b.x;
|
|
85
900
|
var len = (a) => Math.hypot(a.x, a.y);
|
|
86
901
|
var pt = (p) => [p.x, p.y];
|
|
@@ -99,9 +914,9 @@ var Side = class {
|
|
|
99
914
|
direction() {
|
|
100
915
|
const p0 = vec(this.curve[0]);
|
|
101
916
|
for (const q of this.curve.slice(1)) {
|
|
102
|
-
const d =
|
|
917
|
+
const d = sub2(vec(q), p0);
|
|
103
918
|
const l = len(d);
|
|
104
|
-
if (l > EPS) return
|
|
919
|
+
if (l > EPS) return mul2(d, 1 / l);
|
|
105
920
|
}
|
|
106
921
|
return null;
|
|
107
922
|
}
|
|
@@ -109,12 +924,12 @@ var Side = class {
|
|
|
109
924
|
at(s, u) {
|
|
110
925
|
if (this.straight) {
|
|
111
926
|
const t2 = this.length > EPS ? Math.min(1, Math.max(0, s / this.length)) : 0;
|
|
112
|
-
return { t: t2, p:
|
|
927
|
+
return { t: t2, p: add2(vec(this.curve[0]), mul2(u, s)), dir: u };
|
|
113
928
|
}
|
|
114
929
|
const t = segmentParamAtLength(this.curve, s, this.length);
|
|
115
930
|
const d = vec(segmentDerivative(this.curve, t));
|
|
116
931
|
const l = len(d);
|
|
117
|
-
return { t, p: vec(segmentPoint(this.curve, t)), dir: l > EPS ?
|
|
932
|
+
return { t, p: vec(segmentPoint(this.curve, t)), dir: l > EPS ? mul2(d, 1 / l) : u };
|
|
118
933
|
}
|
|
119
934
|
};
|
|
120
935
|
function cornerOf(sp, s, i) {
|
|
@@ -133,7 +948,7 @@ function cornerOf(sp, s, i) {
|
|
|
133
948
|
const lu = sideU.length;
|
|
134
949
|
const lv = sideV.length;
|
|
135
950
|
if (!(lu > EPS && lv > EPS) || !Number.isFinite(lu) || !Number.isFinite(lv)) return null;
|
|
136
|
-
const cos = Math.max(-1, Math.min(1,
|
|
951
|
+
const cos = Math.max(-1, Math.min(1, dot2(u, v2)));
|
|
137
952
|
const sin = Math.abs(cross(u, v2));
|
|
138
953
|
if (sin <= MIN_SINE) return null;
|
|
139
954
|
return { subpath: s, index: i, at: { x: a.x, y: a.y }, u, v: v2, lu, lv, cos, sin, angle: Math.atan2(sin, cos) * 180 / Math.PI, maxRadius: Math.min(lu, lv) / 2 * sin / (1 + cos), sideU, sideV };
|
|
@@ -155,8 +970,8 @@ function vectorCorners(subpaths) {
|
|
|
155
970
|
}
|
|
156
971
|
var cornerSetback = (c, r) => r * (1 + c.cos) / c.sin;
|
|
157
972
|
var fitCornerRadius = (c, radius) => Number.isFinite(radius) ? Math.min(Math.max(0, radius), c.maxRadius) : 0;
|
|
158
|
-
var cornerBisectorPoint = (c, k) =>
|
|
159
|
-
var cornerRadiusChange = (c, d) =>
|
|
973
|
+
var cornerBisectorPoint = (c, k) => add2(c.at, mul2(add2(c.u, c.v), k));
|
|
974
|
+
var cornerRadiusChange = (c, d) => dot2(d, add2(c.u, c.v)) * c.sin / (2 + 2 * c.cos);
|
|
160
975
|
function arcHandle(cos) {
|
|
161
976
|
const c = Math.max(-1, Math.min(1, cos));
|
|
162
977
|
const sinHalf = Math.sqrt(Math.max(0, (1 - c) / 2));
|
|
@@ -164,9 +979,9 @@ function arcHandle(cos) {
|
|
|
164
979
|
return 4 / 3 * sinHalf / (1 + cosHalf);
|
|
165
980
|
}
|
|
166
981
|
function squareTo(a, b) {
|
|
167
|
-
const w =
|
|
982
|
+
const w = sub2(b, mul2(a, dot2(a, b)));
|
|
168
983
|
const l = len(w);
|
|
169
|
-
return l > EPS ?
|
|
984
|
+
return l > EPS ? mul2(w, 1 / l) : { x: 0, y: 0 };
|
|
170
985
|
}
|
|
171
986
|
function cutCorner(c, radius, kind) {
|
|
172
987
|
const s = Math.min(cornerSetback(c, radius), Math.min(c.lu, c.lv) / 2);
|
|
@@ -175,16 +990,16 @@ function cutCorner(c, radius, kind) {
|
|
|
175
990
|
const b = c.sideV.at(s, c.v);
|
|
176
991
|
if (kind === "chamfer") return { tu: a.t, tv: b.t, start: a.p, out: null, in: null, end: b.p };
|
|
177
992
|
if (kind === "inverted") {
|
|
178
|
-
const h = s * arcHandle(
|
|
179
|
-
return { tu: a.t, tv: b.t, start: a.p, out:
|
|
993
|
+
const h = s * arcHandle(dot2(a.dir, b.dir));
|
|
994
|
+
return { tu: a.t, tv: b.t, start: a.p, out: add2(a.p, mul2(squareTo(a.dir, b.dir), h)), in: add2(b.p, mul2(squareTo(b.dir, a.dir), h)), end: b.p };
|
|
180
995
|
}
|
|
181
|
-
const d1 =
|
|
182
|
-
const d2 =
|
|
183
|
-
const turn = Math.acos(Math.max(-1, Math.min(1, -
|
|
184
|
-
const chord = len(
|
|
996
|
+
const d1 = mul2(a.dir, -1);
|
|
997
|
+
const d2 = mul2(b.dir, -1);
|
|
998
|
+
const turn = Math.acos(Math.max(-1, Math.min(1, -dot2(d1, d2))));
|
|
999
|
+
const chord = len(sub2(b.p, a.p));
|
|
185
1000
|
const k = turn < 1e-9 ? chord / 3 : 4 / 3 * Math.tan(turn / 4) * chord / (2 * Math.sin(turn / 2));
|
|
186
1001
|
if (!Number.isFinite(k)) return null;
|
|
187
|
-
return { tu: a.t, tv: b.t, start: a.p, out:
|
|
1002
|
+
return { tu: a.t, tv: b.t, start: a.p, out: add2(a.p, mul2(d1, k)), in: add2(b.p, mul2(d2, k)), end: b.p };
|
|
188
1003
|
}
|
|
189
1004
|
var radiusOf = (a) => {
|
|
190
1005
|
const r = a.corner?.radius;
|
|
@@ -496,7 +1311,7 @@ function pressureWidthProfile(samples, options = {}) {
|
|
|
496
1311
|
// src/vector/varstroke.ts
|
|
497
1312
|
var SAMPLES_PER_SUBPATH = 128;
|
|
498
1313
|
var MAX_DASHES = 5e3;
|
|
499
|
-
var
|
|
1314
|
+
var sub3 = (a, b) => [a[0] - b[0], a[1] - b[1]];
|
|
500
1315
|
var along = (p, v2, k) => [p[0] + v2[0] * k, p[1] + v2[1] * k];
|
|
501
1316
|
var lerp = (a, b, u) => [a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u];
|
|
502
1317
|
var dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
@@ -587,11 +1402,11 @@ function arcPoints(c, a0, sweep, r0, r1, tol) {
|
|
|
587
1402
|
return out;
|
|
588
1403
|
}
|
|
589
1404
|
function crossing(a0, a1, b0, b1) {
|
|
590
|
-
const r =
|
|
591
|
-
const q =
|
|
1405
|
+
const r = sub3(a1, a0);
|
|
1406
|
+
const q = sub3(b1, b0);
|
|
592
1407
|
const den = r[0] * q[1] - r[1] * q[0];
|
|
593
1408
|
if (Math.abs(den) < 1e-12) return null;
|
|
594
|
-
const w =
|
|
1409
|
+
const w = sub3(b0, a0);
|
|
595
1410
|
const t = (w[0] * q[1] - w[1] * q[0]) / den;
|
|
596
1411
|
const u = (w[0] * r[1] - w[1] * r[0]) / den;
|
|
597
1412
|
return t > 1e-9 && t <= 1 && u >= 0 && u < 1 - 1e-9 ? [a0[0] + r[0] * t, a0[1] + r[1] * t] : null;
|
|
@@ -635,7 +1450,7 @@ function variableStrokeRings(path, options) {
|
|
|
635
1450
|
const closed = whole && poly.closed;
|
|
636
1451
|
const m = run.length - 1;
|
|
637
1452
|
const dirs = [];
|
|
638
|
-
for (let i = 0; i < m; i++) dirs.push(unitOf(
|
|
1453
|
+
for (let i = 0; i < m; i++) dirs.push(unitOf(sub3(run[i + 1].p, run[i].p)));
|
|
639
1454
|
const normals = dirs.map(leftNormal);
|
|
640
1455
|
const w = run.map((q) => sides2(q.s));
|
|
641
1456
|
const side = (sigma) => {
|
|
@@ -654,12 +1469,12 @@ function variableStrokeRings(path, options) {
|
|
|
654
1469
|
const d0 = dirs[i];
|
|
655
1470
|
const d1 = dirs[j];
|
|
656
1471
|
const cross2 = d0[0] * d1[1] - d0[1] * d1[0];
|
|
657
|
-
const
|
|
1472
|
+
const dot3 = d0[0] * d1[0] + d0[1] * d1[1];
|
|
658
1473
|
const r0 = w[i + 1][k];
|
|
659
1474
|
const r1 = w[j][k];
|
|
660
1475
|
const e = E(i);
|
|
661
1476
|
const st = S(j);
|
|
662
|
-
if (Math.abs(cross2) < 1e-12 &&
|
|
1477
|
+
if (Math.abs(cross2) < 1e-12 && dot3 > 0) {
|
|
663
1478
|
push(out, e);
|
|
664
1479
|
if (!seam) push(out, st);
|
|
665
1480
|
continue;
|
|
@@ -677,7 +1492,7 @@ function variableStrokeRings(path, options) {
|
|
|
677
1492
|
continue;
|
|
678
1493
|
}
|
|
679
1494
|
push(out, e);
|
|
680
|
-
const theta = uturn ? Math.PI : Math.atan2(cross2,
|
|
1495
|
+
const theta = uturn ? Math.PI : Math.atan2(cross2, dot3);
|
|
681
1496
|
const r = (r0 + r1) / 2;
|
|
682
1497
|
const kind = run[i + 1].corner || seam && poly.corner[0] ? join : "round";
|
|
683
1498
|
if (r > 1e-9) {
|
|
@@ -697,10 +1512,10 @@ function variableStrokeRings(path, options) {
|
|
|
697
1512
|
if (closed && out.length > 1 && dist(out[0], out[out.length - 1]) <= 1e-12) out.pop();
|
|
698
1513
|
return out;
|
|
699
1514
|
};
|
|
700
|
-
const
|
|
1515
|
+
const left2 = side(1);
|
|
701
1516
|
const right = side(-1);
|
|
702
1517
|
if (closed) {
|
|
703
|
-
rings.push(
|
|
1518
|
+
rings.push(left2, right.reverse());
|
|
704
1519
|
continue;
|
|
705
1520
|
}
|
|
706
1521
|
const capPoints = (atStart) => {
|
|
@@ -722,7 +1537,7 @@ function variableStrokeRings(path, options) {
|
|
|
722
1537
|
return atStart ? [along(R, o, ext), along(L, o, ext)] : [along(L, o, ext), along(R, o, ext)];
|
|
723
1538
|
};
|
|
724
1539
|
const ring = [];
|
|
725
|
-
for (const q of [...
|
|
1540
|
+
for (const q of [...left2, ...capPoints(false), ...right.reverse(), ...capPoints(true)]) push(ring, q);
|
|
726
1541
|
if (ring.length > 2) rings.push(ring);
|
|
727
1542
|
}
|
|
728
1543
|
}
|
|
@@ -838,8 +1653,8 @@ function vectorStrokeLineArea(parts, style, tolerance) {
|
|
|
838
1653
|
}
|
|
839
1654
|
var capExtent = (style, width) => style.strokeLinecap === "round" || style.strokeLinecap === "square" ? width / 2 : 0;
|
|
840
1655
|
function arrowheadWeight(style, end) {
|
|
841
|
-
const
|
|
842
|
-
const s = typeof
|
|
1656
|
+
const scale3 = end === "start" ? style.startArrowScale : style.endArrowScale;
|
|
1657
|
+
const s = typeof scale3 === "number" && scale3 > 0 && Number.isFinite(scale3) ? scale3 : 1;
|
|
843
1658
|
const width = style.strokeWidth ?? 1;
|
|
844
1659
|
const profile = strokeWidthProfileOf(style);
|
|
845
1660
|
const at2 = profile ? widthProfileAt(profile, end === "start" ? 0 : 1) : null;
|
|
@@ -1042,8 +1857,872 @@ function vectorStrokeReach(style) {
|
|
|
1042
1857
|
return reach;
|
|
1043
1858
|
}
|
|
1044
1859
|
|
|
1045
|
-
// src/vector/
|
|
1860
|
+
// src/vector/pathEffects.ts
|
|
1046
1861
|
var EPS2 = 1e-9;
|
|
1862
|
+
var degenerate = (s) => s.every((p) => Math.abs(p[0] - s[0][0]) < EPS2 && Math.abs(p[1] - s[0][1]) < EPS2);
|
|
1863
|
+
function raise(seg) {
|
|
1864
|
+
if (seg.length !== 3) return seg.map((p) => [p[0], p[1]]);
|
|
1865
|
+
const [p0, p1, p2] = seg;
|
|
1866
|
+
return [p0, [p0[0] + 2 / 3 * (p1[0] - p0[0]), p0[1] + 2 / 3 * (p1[1] - p0[1])], [p2[0] + 2 / 3 * (p1[0] - p2[0]), p2[1] + 2 / 3 * (p1[1] - p2[1])], p2];
|
|
1867
|
+
}
|
|
1868
|
+
function subsOf(path) {
|
|
1869
|
+
return pathToContours(path).map((c) => ({ segs: closeContour(c).filter((s) => !degenerate(s)).map(raise), closed: c.closed })).filter((s) => s.segs.length > 0);
|
|
1870
|
+
}
|
|
1871
|
+
var subsToPath = (subs) => contoursToPath(subs.map((s) => ({ segments: s.segs, closed: s.closed })));
|
|
1872
|
+
var unit2 = (x, y) => {
|
|
1873
|
+
const l = Math.hypot(x, y);
|
|
1874
|
+
return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
|
|
1875
|
+
};
|
|
1876
|
+
function startTangent(s) {
|
|
1877
|
+
for (let i = 1; i < s.length; i++) {
|
|
1878
|
+
const u = unit2(s[i][0] - s[0][0], s[i][1] - s[0][1]);
|
|
1879
|
+
if (u) return u;
|
|
1880
|
+
}
|
|
1881
|
+
return [1, 0];
|
|
1882
|
+
}
|
|
1883
|
+
function endTangent(s) {
|
|
1884
|
+
const end = s[s.length - 1];
|
|
1885
|
+
for (let i = s.length - 2; i >= 0; i--) {
|
|
1886
|
+
const u = unit2(end[0] - s[i][0], end[1] - s[i][1]);
|
|
1887
|
+
if (u) return u;
|
|
1888
|
+
}
|
|
1889
|
+
return [1, 0];
|
|
1890
|
+
}
|
|
1891
|
+
function tangentAt(s, t) {
|
|
1892
|
+
if (t <= 1e-9) return startTangent(s);
|
|
1893
|
+
if (t >= 1 - 1e-9) return endTangent(s);
|
|
1894
|
+
const d = segmentDerivative(s, t);
|
|
1895
|
+
return unit2(d[0], d[1]) ?? startTangent(s);
|
|
1896
|
+
}
|
|
1897
|
+
var perp2 = (v2) => [-v2[1], v2[0]];
|
|
1898
|
+
function effectPathLength(path) {
|
|
1899
|
+
let total = 0;
|
|
1900
|
+
for (const s of subsOf(path)) for (const seg of s.segs) total += segmentLength(seg);
|
|
1901
|
+
return total;
|
|
1902
|
+
}
|
|
1903
|
+
function effectPathVertexCount(path) {
|
|
1904
|
+
return subsOf(path).reduce((n, s) => n + s.segs.length + (s.closed ? 0 : 1), 0);
|
|
1905
|
+
}
|
|
1906
|
+
var Measured = class {
|
|
1907
|
+
segs;
|
|
1908
|
+
lens;
|
|
1909
|
+
total;
|
|
1910
|
+
constructor(path) {
|
|
1911
|
+
this.segs = subsOf(path).flatMap((s) => s.segs);
|
|
1912
|
+
this.lens = this.segs.map((s) => segmentLength(s));
|
|
1913
|
+
this.total = this.lens.reduce((a, b) => a + b, 0);
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Appends the part between lengths `sa` and `sb`, continuing the last piece when this one
|
|
1917
|
+
* starts where it ends (across the start of a closed path).
|
|
1918
|
+
*/
|
|
1919
|
+
append(sa, sb, out) {
|
|
1920
|
+
for (const piece of trimSegments(this.segs, sa, sb, this.lens)) {
|
|
1921
|
+
if (degenerate(piece)) continue;
|
|
1922
|
+
const last = out[out.length - 1]?.segs;
|
|
1923
|
+
const end = last?.[last.length - 1]?.at(-1);
|
|
1924
|
+
if (!end || Math.hypot(end[0] - piece[0][0], end[1] - piece[0][1]) > 1e-6) out.push({ segs: [piece], closed: false });
|
|
1925
|
+
else last.push(piece);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
};
|
|
1929
|
+
function trimIntervals(start, end, offsetDegrees) {
|
|
1930
|
+
let s = start / 100;
|
|
1931
|
+
let e = end / 100;
|
|
1932
|
+
if (!Number.isFinite(s) || !Number.isFinite(e)) return null;
|
|
1933
|
+
if (s > e) [s, e] = [e, s];
|
|
1934
|
+
s = Math.min(1, Math.max(0, s));
|
|
1935
|
+
e = Math.min(1, Math.max(0, e));
|
|
1936
|
+
if (e - s >= 1 - 1e-9) return null;
|
|
1937
|
+
if (e - s <= 1e-9) return [];
|
|
1938
|
+
const off = Number.isFinite(offsetDegrees) ? (offsetDegrees / 360 % 1 + 1) % 1 : 0;
|
|
1939
|
+
s += off;
|
|
1940
|
+
e += off;
|
|
1941
|
+
if (e <= 1) return [[s, e]];
|
|
1942
|
+
if (s >= 1) return [[s - 1, e - 1]];
|
|
1943
|
+
return [
|
|
1944
|
+
[s, 1],
|
|
1945
|
+
[0, e - 1]
|
|
1946
|
+
];
|
|
1947
|
+
}
|
|
1948
|
+
function trimPaths(paths, start, end, offsetDegrees = 0) {
|
|
1949
|
+
const iv = trimIntervals(start, end, offsetDegrees);
|
|
1950
|
+
if (!iv) return paths.slice();
|
|
1951
|
+
return paths.map((p) => {
|
|
1952
|
+
const m = new Measured(p);
|
|
1953
|
+
const out = [];
|
|
1954
|
+
if (m.total > 0) for (const [a, b] of iv) m.append(a * m.total, b * m.total, out);
|
|
1955
|
+
return subsToPath(out);
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
function trimPathsIndividually(paths, start, end, offsetDegrees = 0) {
|
|
1959
|
+
const iv = trimIntervals(start, end, offsetDegrees);
|
|
1960
|
+
if (!iv) return paths.slice();
|
|
1961
|
+
const ms = paths.map((p) => new Measured(p));
|
|
1962
|
+
const total = ms.reduce((n, m) => n + m.total, 0);
|
|
1963
|
+
let base = 0;
|
|
1964
|
+
return ms.map((m) => {
|
|
1965
|
+
const out = [];
|
|
1966
|
+
for (const [a, b] of iv) {
|
|
1967
|
+
const la = Math.max(0, a * total - base);
|
|
1968
|
+
const lb = Math.min(m.total, b * total - base);
|
|
1969
|
+
if (lb > la) m.append(la, lb, out);
|
|
1970
|
+
}
|
|
1971
|
+
base += m.total;
|
|
1972
|
+
return subsToPath(out);
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
function puckerBloatPaths(paths, amount) {
|
|
1976
|
+
const a = amount / 100;
|
|
1977
|
+
if (!a || !Number.isFinite(a)) return paths.slice();
|
|
1978
|
+
return paths.map((p) => {
|
|
1979
|
+
const verts = p.filter((c) => c.type !== "Z");
|
|
1980
|
+
if (!verts.length) return p;
|
|
1981
|
+
const cx = verts.reduce((s, c) => s + c.x, 0) / verts.length;
|
|
1982
|
+
const cy = verts.reduce((s, c) => s + c.y, 0) / verts.length;
|
|
1983
|
+
const mv = (x, y) => ({ x: x + (cx - x) * a, y: y + (cy - y) * a });
|
|
1984
|
+
const mvh = (x, y) => ({ x: x + (x - cx) * a, y: y + (y - cy) * a });
|
|
1985
|
+
const out = [];
|
|
1986
|
+
let prev = { x: 0, y: 0 };
|
|
1987
|
+
let start = { x: 0, y: 0 };
|
|
1988
|
+
const curve = (c1, c2, q) => {
|
|
1989
|
+
const h1 = mvh(c1.x, c1.y);
|
|
1990
|
+
const h2 = mvh(c2.x, c2.y);
|
|
1991
|
+
const e = mv(q.x, q.y);
|
|
1992
|
+
out.push({ type: "C", x1: h1.x, y1: h1.y, x2: h2.x, y2: h2.y, x: e.x, y: e.y });
|
|
1993
|
+
};
|
|
1994
|
+
for (const c of p) {
|
|
1995
|
+
switch (c.type) {
|
|
1996
|
+
case "M": {
|
|
1997
|
+
const e = mv(c.x, c.y);
|
|
1998
|
+
out.push({ type: "M", x: e.x, y: e.y });
|
|
1999
|
+
prev = start = { x: c.x, y: c.y };
|
|
2000
|
+
break;
|
|
2001
|
+
}
|
|
2002
|
+
case "L":
|
|
2003
|
+
curve(prev, c, c);
|
|
2004
|
+
prev = { x: c.x, y: c.y };
|
|
2005
|
+
break;
|
|
2006
|
+
case "Q": {
|
|
2007
|
+
const c1 = { x: prev.x + 2 / 3 * (c.x1 - prev.x), y: prev.y + 2 / 3 * (c.y1 - prev.y) };
|
|
2008
|
+
const c2 = { x: c.x + 2 / 3 * (c.x1 - c.x), y: c.y + 2 / 3 * (c.y1 - c.y) };
|
|
2009
|
+
curve(c1, c2, c);
|
|
2010
|
+
prev = { x: c.x, y: c.y };
|
|
2011
|
+
break;
|
|
2012
|
+
}
|
|
2013
|
+
case "C":
|
|
2014
|
+
curve({ x: c.x1, y: c.y1 }, { x: c.x2, y: c.y2 }, c);
|
|
2015
|
+
prev = { x: c.x, y: c.y };
|
|
2016
|
+
break;
|
|
2017
|
+
case "Z":
|
|
2018
|
+
if (Math.hypot(prev.x - start.x, prev.y - start.y) > EPS2) curve(prev, start, start);
|
|
2019
|
+
out.push({ type: "Z" });
|
|
2020
|
+
prev = start;
|
|
2021
|
+
break;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
return out;
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
function roundCornerPaths(paths, radius) {
|
|
2028
|
+
if (!(radius > 0) || !Number.isFinite(radius)) return paths.slice();
|
|
2029
|
+
return paths.map((p) => subpathsToBezier(bezierToSubpaths(p).map((sp) => setSubpathCorners(sp, radius))));
|
|
2030
|
+
}
|
|
2031
|
+
function offsetPaths(paths, amount, join = "miter", miterLimit = 4, copies = 1, copyOffset = 0) {
|
|
2032
|
+
const n = Math.min(1e3, Math.max(1, Math.round(Number.isFinite(copies) ? copies : 1)));
|
|
2033
|
+
return paths.map((p) => {
|
|
2034
|
+
const out = [];
|
|
2035
|
+
for (let i = 0; i < n; i++) out.push(...offsetPath(p, amount * (i + 1 + copyOffset), { join, miterLimit }));
|
|
2036
|
+
return out;
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
function twistPaths(paths, angle, center) {
|
|
2040
|
+
let rmax = 0;
|
|
2041
|
+
for (const p of paths) for (const c of p) if (c.type !== "Z") rmax = Math.max(rmax, Math.hypot(c.x - center.x, c.y - center.y));
|
|
2042
|
+
const a = angle * Math.PI / 180;
|
|
2043
|
+
if (!a || rmax <= 1e-9 || !Number.isFinite(a)) return paths.slice();
|
|
2044
|
+
const k = a / rmax;
|
|
2045
|
+
const rot = (v2, th) => {
|
|
2046
|
+
const s = Math.sin(th);
|
|
2047
|
+
const co = Math.cos(th);
|
|
2048
|
+
return [v2[0] * co - v2[1] * s, v2[0] * s + v2[1] * co];
|
|
2049
|
+
};
|
|
2050
|
+
const map = (p) => {
|
|
2051
|
+
const v2 = [p[0] - center.x, p[1] - center.y];
|
|
2052
|
+
const r = rot(v2, k * Math.hypot(v2[0], v2[1]));
|
|
2053
|
+
return [center.x + r[0], center.y + r[1]];
|
|
2054
|
+
};
|
|
2055
|
+
const jac = (p, dv) => {
|
|
2056
|
+
const v2 = [p[0] - center.x, p[1] - center.y];
|
|
2057
|
+
const r = Math.hypot(v2[0], v2[1]);
|
|
2058
|
+
let w = dv;
|
|
2059
|
+
if (r > 1e-12) {
|
|
2060
|
+
const f = k * (v2[0] * dv[0] + v2[1] * dv[1]) / r;
|
|
2061
|
+
w = [dv[0] - v2[1] * f, dv[1] + v2[0] * f];
|
|
2062
|
+
}
|
|
2063
|
+
return rot(w, k * r);
|
|
2064
|
+
};
|
|
2065
|
+
return paths.map((p) => {
|
|
2066
|
+
const out = [];
|
|
2067
|
+
for (const sub5 of subsOf(p)) {
|
|
2068
|
+
const segs = [];
|
|
2069
|
+
for (const seg of sub5.segs) {
|
|
2070
|
+
const cubic = seg.length === 2 ? [seg[0], seg[0], seg[1], seg[1]] : seg;
|
|
2071
|
+
const len2 = segmentLength(seg);
|
|
2072
|
+
const m = Math.min(64, Math.max(1, Math.ceil(Math.abs(a) * len2 / rmax / 0.15 + len2 / 50)));
|
|
2073
|
+
for (let i = 0; i < m; i++) {
|
|
2074
|
+
const q = subSegment(cubic, i / m, (i + 1) / m);
|
|
2075
|
+
const p0 = map(q[0]);
|
|
2076
|
+
const p3 = map(q[3]);
|
|
2077
|
+
const h1 = jac(q[0], [q[1][0] - q[0][0], q[1][1] - q[0][1]]);
|
|
2078
|
+
const h2 = jac(q[3], [q[2][0] - q[3][0], q[2][1] - q[3][1]]);
|
|
2079
|
+
segs.push([p0, [p0[0] + h1[0], p0[1] + h1[1]], [p3[0] + h2[0], p3[1] + h2[1]], p3]);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
out.push({ segs, closed: sub5.closed });
|
|
2083
|
+
}
|
|
2084
|
+
return subsToPath(out);
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
function resample(sub5, m) {
|
|
2088
|
+
const segs = sub5.segs;
|
|
2089
|
+
const n = segs.length;
|
|
2090
|
+
const lens = segs.map((s) => segmentLength(s));
|
|
2091
|
+
const out = [];
|
|
2092
|
+
for (let j = 0; j < n; j++) {
|
|
2093
|
+
const piece = lens[j] / m;
|
|
2094
|
+
for (let k = 0; k < m; k++) {
|
|
2095
|
+
if (k === 0) {
|
|
2096
|
+
const tout = startTangent(segs[j]);
|
|
2097
|
+
const prev = sub5.closed || j > 0 ? (j + n - 1) % n : null;
|
|
2098
|
+
let tan = tout;
|
|
2099
|
+
if (prev !== null) {
|
|
2100
|
+
const tin = endTangent(segs[prev]);
|
|
2101
|
+
tan = unit2(tin[0] + tout[0], tin[1] + tout[1]) ?? tout;
|
|
2102
|
+
}
|
|
2103
|
+
out.push({ p: segs[j][0], tan, lenPrev: prev === null ? 0 : lens[prev] / m, lenNext: piece });
|
|
2104
|
+
} else {
|
|
2105
|
+
const t = segmentParamAtLength(segs[j], piece * k, lens[j]);
|
|
2106
|
+
out.push({ p: segmentPoint(segs[j], t), tan: tangentAt(segs[j], t), lenPrev: piece, lenNext: piece });
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
if (!sub5.closed) {
|
|
2111
|
+
const last = segs[n - 1];
|
|
2112
|
+
out.push({ p: last[last.length - 1], tan: endTangent(last), lenPrev: lens[n - 1] / m, lenNext: 0 });
|
|
2113
|
+
}
|
|
2114
|
+
return out;
|
|
2115
|
+
}
|
|
2116
|
+
function emit(samples, disp, closed, smooth, handle) {
|
|
2117
|
+
const n = samples.length;
|
|
2118
|
+
if (!n) return null;
|
|
2119
|
+
const pt2 = (i) => [samples[i].p[0] + disp[i][0], samples[i].p[1] + disp[i][1]];
|
|
2120
|
+
const segs = [];
|
|
2121
|
+
const count = closed ? n : n - 1;
|
|
2122
|
+
for (let i = 0; i < count; i++) {
|
|
2123
|
+
const j = (i + 1) % n;
|
|
2124
|
+
const a = samples[i];
|
|
2125
|
+
const b = samples[j];
|
|
2126
|
+
const p = pt2(i);
|
|
2127
|
+
const q = pt2(j);
|
|
2128
|
+
if (smooth) segs.push([p, [p[0] + a.tan[0] * a.lenNext * handle, p[1] + a.tan[1] * a.lenNext * handle], [q[0] - b.tan[0] * b.lenPrev * handle, q[1] - b.tan[1] * b.lenPrev * handle], q]);
|
|
2129
|
+
else segs.push([p, q]);
|
|
2130
|
+
}
|
|
2131
|
+
if (!segs.length) return null;
|
|
2132
|
+
return { segs, closed };
|
|
2133
|
+
}
|
|
2134
|
+
function zigzagPaths(paths, size, ridges, smooth = false) {
|
|
2135
|
+
const m = Math.min(1e3, Math.max(0, Math.round(Number.isFinite(ridges) ? ridges : 0))) + 1;
|
|
2136
|
+
return paths.map((p) => {
|
|
2137
|
+
const out = [];
|
|
2138
|
+
for (const sub5 of subsOf(p)) {
|
|
2139
|
+
const s = resample(sub5, m);
|
|
2140
|
+
const disp = s.map((x, i) => {
|
|
2141
|
+
const n = perp2(x.tan);
|
|
2142
|
+
const d = i % 2 === 0 ? size : -size;
|
|
2143
|
+
return [n[0] * d, n[1] * d];
|
|
2144
|
+
});
|
|
2145
|
+
const e = emit(s, disp, sub5.closed, smooth, 0.36);
|
|
2146
|
+
if (e) out.push(e);
|
|
2147
|
+
}
|
|
2148
|
+
return subsToPath(out);
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
var MASK64 = (1n << 64n) - 1n;
|
|
2152
|
+
function splitmix(x) {
|
|
2153
|
+
x = x + 0x9e3779b97f4a7c15n & MASK64;
|
|
2154
|
+
let z = x;
|
|
2155
|
+
z = (z ^ z >> 30n) * 0xbf58476d1ce4e5b9n & MASK64;
|
|
2156
|
+
z = (z ^ z >> 27n) * 0x94d049bb133111ebn & MASK64;
|
|
2157
|
+
return z ^ z >> 31n;
|
|
2158
|
+
}
|
|
2159
|
+
function hash(seed, idx, t) {
|
|
2160
|
+
const h = splitmix(splitmix(splitmix(seed) ^ idx) ^ BigInt.asUintN(64, BigInt(t)));
|
|
2161
|
+
return Number(h >> 11n) / 2 ** 53 * 2 - 1;
|
|
2162
|
+
}
|
|
2163
|
+
function noise(seed, idx, t) {
|
|
2164
|
+
const i = Math.floor(t);
|
|
2165
|
+
const f = t - i;
|
|
2166
|
+
const p0 = hash(seed, idx, i - 1);
|
|
2167
|
+
const p1 = hash(seed, idx, i);
|
|
2168
|
+
const p2 = hash(seed, idx, i + 1);
|
|
2169
|
+
const p3 = hash(seed, idx, i + 2);
|
|
2170
|
+
const v2 = 0.5 * (2 * p1 + (-p0 + p2) * f + (2 * p0 - 5 * p1 + 4 * p2 - p3) * f * f + (-p0 + 3 * p1 - 3 * p2 + p3) * f * f * f);
|
|
2171
|
+
return Math.min(1, Math.max(-1, v2));
|
|
2172
|
+
}
|
|
2173
|
+
function wigglePaths(paths, w) {
|
|
2174
|
+
if (!w.size || !Number.isFinite(w.size)) return paths.slice();
|
|
2175
|
+
const m = Math.min(1e3, Math.max(0, Math.round(Number.isFinite(w.detail) ? w.detail : 0))) + 1;
|
|
2176
|
+
const seed = BigInt.asUintN(64, BigInt(Math.round(Number.isFinite(w.seed) ? w.seed : 0)));
|
|
2177
|
+
const t = (Number.isFinite(w.time) ? w.time : 0) * (Number.isFinite(w.speed) ? w.speed : 0) + (Number.isFinite(w.phase) ? w.phase : 0) / 360;
|
|
2178
|
+
const corr = Math.min(1, Math.max(0, (Number.isFinite(w.correlation) ? w.correlation : 0) / 100));
|
|
2179
|
+
const sx = noise(seed, MASK64, t);
|
|
2180
|
+
const sy = noise(seed, MASK64 - 1n, t);
|
|
2181
|
+
let idx = 0;
|
|
2182
|
+
return paths.map((p) => {
|
|
2183
|
+
const out = [];
|
|
2184
|
+
for (const sub5 of subsOf(p)) {
|
|
2185
|
+
const s = resample(sub5, m);
|
|
2186
|
+
const disp = s.map(() => {
|
|
2187
|
+
const i = BigInt(idx);
|
|
2188
|
+
const ox = noise(seed, i * 2n, t + idx * 0.37);
|
|
2189
|
+
const oy = noise(seed, i * 2n + 1n, t + idx * 0.61);
|
|
2190
|
+
idx++;
|
|
2191
|
+
return [(sx * corr + ox * (1 - corr)) * w.size, (sy * corr + oy * (1 - corr)) * w.size];
|
|
2192
|
+
});
|
|
2193
|
+
const e = emit(s, disp, sub5.closed, w.smooth, 1 / 3);
|
|
2194
|
+
if (e) out.push(e);
|
|
2195
|
+
}
|
|
2196
|
+
return subsToPath(out);
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
var MERGE_OPS = { add: "union", subtract: "difference", intersect: "intersection", exclude: "xor" };
|
|
2200
|
+
function mergePaths(paths, mode = "merge") {
|
|
2201
|
+
if (mode === "merge" || !(mode in MERGE_OPS)) return paths.flat();
|
|
2202
|
+
const inputs = paths.filter((p) => p.length > 0).map((path) => ({ path, fillRule: "nonzero" }));
|
|
2203
|
+
if (!inputs.length) return [];
|
|
2204
|
+
return pathBoolean(MERGE_OPS[mode], inputs).path;
|
|
2205
|
+
}
|
|
2206
|
+
function vectorRepeaterCopies(r, pivot) {
|
|
2207
|
+
const copies = Math.max(0, Number.isFinite(r.copies) ? Math.min(r.copies, 1e3) : 0);
|
|
2208
|
+
const n = Math.ceil(copies);
|
|
2209
|
+
const offset = Number.isFinite(r.offset) ? r.offset : 0;
|
|
2210
|
+
const so = (r.startOpacity ?? 100) / 100;
|
|
2211
|
+
const eo = (r.endOpacity ?? 100) / 100;
|
|
2212
|
+
const fract = copies - Math.floor(copies);
|
|
2213
|
+
const out = [];
|
|
2214
|
+
for (let i = 0; i < n; i++) {
|
|
2215
|
+
const k = i + offset;
|
|
2216
|
+
let m = translateMatrix(-pivot.x, -pivot.y);
|
|
2217
|
+
m = multiplyMatrices(m, scaleMatrix(Math.pow((r.scale?.x ?? 100) / 100, k), Math.pow((r.scale?.y ?? 100) / 100, k)));
|
|
2218
|
+
m = multiplyMatrices(m, rotateMatrix((r.rotation ?? 0) * k));
|
|
2219
|
+
m = multiplyMatrices(m, translateMatrix(pivot.x + (r.position?.x ?? 0) * k, pivot.y + (r.position?.y ?? 0) * k));
|
|
2220
|
+
const t = n > 1 ? i / (n - 1) : 0;
|
|
2221
|
+
const partial = i + 1 === n && fract > 0 ? fract : 1;
|
|
2222
|
+
out.push({ index: i, matrix: m, opacity: Math.min(1, Math.max(0, (so + (eo - so) * t) * partial)) });
|
|
2223
|
+
}
|
|
2224
|
+
return out;
|
|
2225
|
+
}
|
|
2226
|
+
function repeatPaths(paths, r, pivot) {
|
|
2227
|
+
return vectorRepeaterCopies(r, pivot).map((copy) => ({ copy, paths: paths.map((p) => transformBezierPath(p, copy.matrix)) }));
|
|
2228
|
+
}
|
|
2229
|
+
function pathsCenter(paths) {
|
|
2230
|
+
const b = bezierPathBounds(paths.flat());
|
|
2231
|
+
return b ? { x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 } : { x: 0, y: 0 };
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
// src/vector/appearance.ts
|
|
2235
|
+
var VECTOR_EFFECT_DEFAULTS = {
|
|
2236
|
+
trim: { start: 0, end: 50, offset: 0, mode: "simultaneous" },
|
|
2237
|
+
roundCorners: { radius: 10 },
|
|
2238
|
+
puckerBloat: { amount: 30 },
|
|
2239
|
+
twist: { angle: 45, center: { x: 0, y: 0 } },
|
|
2240
|
+
zigzag: { size: 5, ridges: 4, smooth: false },
|
|
2241
|
+
wiggle: { size: 5, detail: 6, smooth: true, speed: 2, correlation: 50, phase: 0, seed: 1, time: 0 },
|
|
2242
|
+
offset: { amount: 5, join: "miter", miterLimit: 4, copies: 1, copyOffset: 0 },
|
|
2243
|
+
merge: { mode: "add" },
|
|
2244
|
+
repeater: {
|
|
2245
|
+
copies: 3,
|
|
2246
|
+
offset: 0,
|
|
2247
|
+
position: { x: 100, y: 0 },
|
|
2248
|
+
anchor: { x: 0, y: 0 },
|
|
2249
|
+
rotation: 0,
|
|
2250
|
+
scale: { x: 100, y: 100 },
|
|
2251
|
+
startOpacity: 100,
|
|
2252
|
+
endOpacity: 100,
|
|
2253
|
+
composite: "below"
|
|
2254
|
+
}
|
|
2255
|
+
};
|
|
2256
|
+
var VECTOR_EFFECT_LABELS = {
|
|
2257
|
+
trim: "Trim Paths",
|
|
2258
|
+
roundCorners: "Round Corners",
|
|
2259
|
+
puckerBloat: "Pucker & Bloat",
|
|
2260
|
+
twist: "Twist",
|
|
2261
|
+
zigzag: "Zig Zag",
|
|
2262
|
+
wiggle: "Wiggle Paths",
|
|
2263
|
+
offset: "Offset Paths",
|
|
2264
|
+
merge: "Merge Paths",
|
|
2265
|
+
repeater: "Repeater"
|
|
2266
|
+
};
|
|
2267
|
+
var VECTOR_EFFECT_PARAM_SPECS = {
|
|
2268
|
+
trim: [
|
|
2269
|
+
{ key: "start", label: "Start %", kind: "number", min: 0, max: 100, step: 1, slider: true },
|
|
2270
|
+
{ key: "end", label: "End %", kind: "number", min: 0, max: 100, step: 1, slider: true },
|
|
2271
|
+
{ key: "offset", label: "Offset \xB0", kind: "number", min: -360, max: 360, step: 1, slider: true },
|
|
2272
|
+
{
|
|
2273
|
+
key: "mode",
|
|
2274
|
+
label: "Paths",
|
|
2275
|
+
kind: "select",
|
|
2276
|
+
options: [
|
|
2277
|
+
{ value: "simultaneous", label: "Simultaneously" },
|
|
2278
|
+
{ value: "individual", label: "Individually" }
|
|
2279
|
+
]
|
|
2280
|
+
}
|
|
2281
|
+
],
|
|
2282
|
+
roundCorners: [{ key: "radius", label: "Radius", kind: "number", min: 0, max: 200, step: 1, slider: true }],
|
|
2283
|
+
puckerBloat: [{ key: "amount", label: "Amount %", kind: "number", min: -100, max: 100, step: 1, slider: true }],
|
|
2284
|
+
twist: [
|
|
2285
|
+
{ key: "angle", label: "Angle \xB0", kind: "number", min: -720, max: 720, step: 1, slider: true },
|
|
2286
|
+
{ key: "center", label: "Centre", kind: "vec2", step: 1 }
|
|
2287
|
+
],
|
|
2288
|
+
zigzag: [
|
|
2289
|
+
{ key: "size", label: "Size", kind: "number", min: 0, max: 100, step: 0.5, slider: true },
|
|
2290
|
+
{ key: "ridges", label: "Ridges", kind: "number", min: 0, max: 50, step: 1, slider: true },
|
|
2291
|
+
{ key: "smooth", label: "Smooth", kind: "boolean" }
|
|
2292
|
+
],
|
|
2293
|
+
wiggle: [
|
|
2294
|
+
{ key: "size", label: "Size", kind: "number", min: 0, max: 100, step: 0.5, slider: true },
|
|
2295
|
+
{ key: "detail", label: "Detail", kind: "number", min: 0, max: 50, step: 1, slider: true },
|
|
2296
|
+
{ key: "smooth", label: "Smooth", kind: "boolean" },
|
|
2297
|
+
{ key: "correlation", label: "Correlation %", kind: "number", min: 0, max: 100, step: 1, slider: true },
|
|
2298
|
+
{ key: "speed", label: "Speed", kind: "number", min: 0, step: 0.1 },
|
|
2299
|
+
{ key: "phase", label: "Phase \xB0", kind: "number", step: 1 },
|
|
2300
|
+
{ key: "time", label: "Time (s)", kind: "number", min: 0, max: 10, step: 0.05, slider: true },
|
|
2301
|
+
{ key: "seed", label: "Seed", kind: "number", step: 1 }
|
|
2302
|
+
],
|
|
2303
|
+
offset: [
|
|
2304
|
+
{ key: "amount", label: "Amount", kind: "number", min: -100, max: 100, step: 0.5, slider: true },
|
|
2305
|
+
{
|
|
2306
|
+
key: "join",
|
|
2307
|
+
label: "Join",
|
|
2308
|
+
kind: "select",
|
|
2309
|
+
options: [
|
|
2310
|
+
{ value: "miter", label: "Miter" },
|
|
2311
|
+
{ value: "round", label: "Round" },
|
|
2312
|
+
{ value: "bevel", label: "Bevel" }
|
|
2313
|
+
]
|
|
2314
|
+
},
|
|
2315
|
+
{ key: "miterLimit", label: "Miter limit", kind: "number", min: 1, step: 0.5 },
|
|
2316
|
+
{ key: "copies", label: "Copies", kind: "number", min: 1, max: 20, step: 1 },
|
|
2317
|
+
{ key: "copyOffset", label: "Copy offset", kind: "number", step: 0.5 }
|
|
2318
|
+
],
|
|
2319
|
+
merge: [
|
|
2320
|
+
{
|
|
2321
|
+
key: "mode",
|
|
2322
|
+
label: "Mode",
|
|
2323
|
+
kind: "select",
|
|
2324
|
+
options: [
|
|
2325
|
+
{ value: "merge", label: "Merge" },
|
|
2326
|
+
{ value: "add", label: "Add" },
|
|
2327
|
+
{ value: "subtract", label: "Subtract" },
|
|
2328
|
+
{ value: "intersect", label: "Intersect" },
|
|
2329
|
+
{ value: "exclude", label: "Exclude" }
|
|
2330
|
+
]
|
|
2331
|
+
}
|
|
2332
|
+
],
|
|
2333
|
+
repeater: [
|
|
2334
|
+
{ key: "copies", label: "Copies", kind: "number", min: 0, max: 50, step: 0.1, slider: true },
|
|
2335
|
+
{ key: "offset", label: "Offset", kind: "number", min: -10, max: 10, step: 0.1, slider: true },
|
|
2336
|
+
{ key: "position", label: "Position", kind: "vec2", step: 1 },
|
|
2337
|
+
{ key: "anchor", label: "Anchor", kind: "vec2", step: 1 },
|
|
2338
|
+
{ key: "rotation", label: "Rotation \xB0", kind: "number", min: -360, max: 360, step: 1, slider: true },
|
|
2339
|
+
{ key: "scale", label: "Scale %", kind: "vec2", step: 1 },
|
|
2340
|
+
{ key: "startOpacity", label: "Start opacity %", kind: "number", min: 0, max: 100, step: 1, slider: true },
|
|
2341
|
+
{ key: "endOpacity", label: "End opacity %", kind: "number", min: 0, max: 100, step: 1, slider: true },
|
|
2342
|
+
{
|
|
2343
|
+
key: "composite",
|
|
2344
|
+
label: "Copies",
|
|
2345
|
+
kind: "select",
|
|
2346
|
+
options: [
|
|
2347
|
+
{ value: "below", label: "Below" },
|
|
2348
|
+
{ value: "above", label: "Above" }
|
|
2349
|
+
]
|
|
2350
|
+
}
|
|
2351
|
+
]
|
|
2352
|
+
};
|
|
2353
|
+
var isVec2 = (v2) => !!v2 && typeof v2 === "object" && Number.isFinite(v2.x) && Number.isFinite(v2.y);
|
|
2354
|
+
function normalizeVectorEffectParams(type, params, base) {
|
|
2355
|
+
const defaults = VECTOR_EFFECT_DEFAULTS[type];
|
|
2356
|
+
if (!defaults) return null;
|
|
2357
|
+
const out = { ...defaults, ...base ?? {} };
|
|
2358
|
+
for (const spec of VECTOR_EFFECT_PARAM_SPECS[type]) {
|
|
2359
|
+
const v2 = params?.[spec.key];
|
|
2360
|
+
if (v2 === void 0) continue;
|
|
2361
|
+
if (spec.kind === "number" && typeof v2 === "number" && Number.isFinite(v2)) out[spec.key] = Math.min(spec.max ?? Infinity, Math.max(spec.min ?? -Infinity, v2));
|
|
2362
|
+
else if (spec.kind === "boolean" && typeof v2 === "boolean") out[spec.key] = v2;
|
|
2363
|
+
else if (spec.kind === "vec2" && isVec2(v2)) out[spec.key] = { x: v2.x, y: v2.y };
|
|
2364
|
+
else if (spec.kind === "select" && spec.options.some((o) => o.value === v2)) out[spec.key] = v2;
|
|
2365
|
+
}
|
|
2366
|
+
return out;
|
|
2367
|
+
}
|
|
2368
|
+
function createVectorEffect(type, id, params) {
|
|
2369
|
+
const p = normalizeVectorEffectParams(type, params);
|
|
2370
|
+
return p ? { id, type, params: p } : null;
|
|
2371
|
+
}
|
|
2372
|
+
var isEnabled = (e) => e.enabled !== false && !!VECTOR_EFFECT_DEFAULTS[e.type];
|
|
2373
|
+
function activeVectorEffects(node) {
|
|
2374
|
+
return node.effects?.filter(isEnabled) ?? [];
|
|
2375
|
+
}
|
|
2376
|
+
function hasOwnVectorAppearance(node) {
|
|
2377
|
+
return node.type === "group" && !!node.liveBoolean || !!node.effects?.some(isEnabled);
|
|
2378
|
+
}
|
|
2379
|
+
var liveCache = /* @__PURE__ */ new WeakMap();
|
|
2380
|
+
function hasVectorAppearance(node) {
|
|
2381
|
+
let v2 = liveCache.get(node);
|
|
2382
|
+
if (v2 === void 0) {
|
|
2383
|
+
v2 = hasOwnVectorAppearance(node) || node.type === "group" && node.children.some(hasVectorAppearance);
|
|
2384
|
+
liveCache.set(node, v2);
|
|
2385
|
+
}
|
|
2386
|
+
return v2;
|
|
2387
|
+
}
|
|
2388
|
+
function isVectorLiveBoolean(node) {
|
|
2389
|
+
return node?.type === "group" && !!node.liveBoolean;
|
|
2390
|
+
}
|
|
2391
|
+
function vectorOperandOperation(group, child) {
|
|
2392
|
+
return child.booleanOperation ?? group.liveBoolean ?? "union";
|
|
2393
|
+
}
|
|
2394
|
+
var PATH_OPS = { union: "union", subtract: "difference", intersect: "intersection", exclude: "xor" };
|
|
2395
|
+
var FoldCache = class {
|
|
2396
|
+
constructor(cap) {
|
|
2397
|
+
this.cap = cap;
|
|
2398
|
+
}
|
|
2399
|
+
cap;
|
|
2400
|
+
map = /* @__PURE__ */ new Map();
|
|
2401
|
+
get(key) {
|
|
2402
|
+
const hit = this.map.get(key);
|
|
2403
|
+
if (hit) {
|
|
2404
|
+
this.map.delete(key);
|
|
2405
|
+
this.map.set(key, hit);
|
|
2406
|
+
}
|
|
2407
|
+
return hit;
|
|
2408
|
+
}
|
|
2409
|
+
set(key, path) {
|
|
2410
|
+
if (this.map.size >= this.cap) {
|
|
2411
|
+
let drop = Math.floor(this.map.size / 2);
|
|
2412
|
+
for (const k of this.map.keys()) {
|
|
2413
|
+
if (drop-- <= 0) break;
|
|
2414
|
+
this.map.delete(k);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
this.map.set(key, path);
|
|
2418
|
+
}
|
|
2419
|
+
clear() {
|
|
2420
|
+
this.map.clear();
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
var foldCache = new FoldCache(4096);
|
|
2424
|
+
var foldSteps = 0;
|
|
2425
|
+
function vectorLiveBooleanSteps() {
|
|
2426
|
+
return foldSteps;
|
|
2427
|
+
}
|
|
2428
|
+
function clearVectorLiveBooleanCache() {
|
|
2429
|
+
foldCache.clear();
|
|
2430
|
+
}
|
|
2431
|
+
var RollingHash = class _RollingHash {
|
|
2432
|
+
a = 2166136261;
|
|
2433
|
+
b = 16777619 ^ 1540483477;
|
|
2434
|
+
static view = new DataView(new ArrayBuffer(8));
|
|
2435
|
+
byte(v2) {
|
|
2436
|
+
this.a = Math.imul(this.a ^ v2 & 255, 16777619);
|
|
2437
|
+
this.b = Math.imul(this.b ^ v2 & 255, 1540483477) ^ this.b >>> 15;
|
|
2438
|
+
}
|
|
2439
|
+
num(n) {
|
|
2440
|
+
_RollingHash.view.setFloat64(0, n);
|
|
2441
|
+
for (let i = 0; i < 8; i++) this.byte(_RollingHash.view.getUint8(i));
|
|
2442
|
+
}
|
|
2443
|
+
path(p) {
|
|
2444
|
+
for (const c of p) {
|
|
2445
|
+
this.byte(c.type.charCodeAt(0));
|
|
2446
|
+
if (c.type === "Z") continue;
|
|
2447
|
+
if (c.type === "C") {
|
|
2448
|
+
this.num(c.x1);
|
|
2449
|
+
this.num(c.y1);
|
|
2450
|
+
this.num(c.x2);
|
|
2451
|
+
this.num(c.y2);
|
|
2452
|
+
} else if (c.type === "Q") {
|
|
2453
|
+
this.num(c.x1);
|
|
2454
|
+
this.num(c.y1);
|
|
2455
|
+
}
|
|
2456
|
+
this.num(c.x);
|
|
2457
|
+
this.num(c.y);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
key() {
|
|
2461
|
+
return `${(this.a >>> 0).toString(36)}.${(this.b >>> 0).toString(36)}`;
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
2464
|
+
function foldVectorBooleanOperands(operands) {
|
|
2465
|
+
if (!operands.length) return [];
|
|
2466
|
+
if (operands.length === 1) return operands[0].path;
|
|
2467
|
+
const h = new RollingHash();
|
|
2468
|
+
const keys = operands.map((o, i) => {
|
|
2469
|
+
if (i > 0) h.byte(o.operation.charCodeAt(0) + o.operation.length);
|
|
2470
|
+
h.byte(o.fillRule === "evenodd" ? 1 : 0);
|
|
2471
|
+
h.path(o.path);
|
|
2472
|
+
h.byte(9);
|
|
2473
|
+
return h.key();
|
|
2474
|
+
});
|
|
2475
|
+
let start = 0;
|
|
2476
|
+
let acc = operands[0].path;
|
|
2477
|
+
for (let i = keys.length - 1; i >= 1; i--) {
|
|
2478
|
+
const hit = foldCache.get(keys[i]);
|
|
2479
|
+
if (hit) {
|
|
2480
|
+
if (i === keys.length - 1) return hit;
|
|
2481
|
+
start = i;
|
|
2482
|
+
acc = hit;
|
|
2483
|
+
break;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
let accRule = start === 0 ? operands[0].fillRule : "nonzero";
|
|
2487
|
+
for (let i = start + 1; i < operands.length; i++) {
|
|
2488
|
+
const o = operands[i];
|
|
2489
|
+
foldSteps++;
|
|
2490
|
+
const r = pathBoolean(PATH_OPS[o.operation] ?? "union", [
|
|
2491
|
+
{ path: acc, fillRule: accRule },
|
|
2492
|
+
{ path: o.path, fillRule: o.fillRule }
|
|
2493
|
+
]);
|
|
2494
|
+
if (r.error) return [];
|
|
2495
|
+
acc = r.path;
|
|
2496
|
+
accRule = "nonzero";
|
|
2497
|
+
foldCache.set(keys[i], acc);
|
|
2498
|
+
}
|
|
2499
|
+
return acc;
|
|
2500
|
+
}
|
|
2501
|
+
var compareOrder = (a, b) => {
|
|
2502
|
+
const n = Math.min(a.length, b.length);
|
|
2503
|
+
for (let i = 0; i < n; i++) if (a[i] !== b[i]) return a[i] - b[i];
|
|
2504
|
+
return a.length - b.length;
|
|
2505
|
+
};
|
|
2506
|
+
var add3 = (a, b) => ({ x: a.x + (b?.x ?? 0), y: a.y + (b?.y ?? 0) });
|
|
2507
|
+
var mapPaths = (shapes, fn) => {
|
|
2508
|
+
const out = fn(shapes.map((s) => s.path));
|
|
2509
|
+
return shapes.map((s, i) => ({ ...s, path: out[i] ?? [] }));
|
|
2510
|
+
};
|
|
2511
|
+
function applyVectorEffects(shapes, effects) {
|
|
2512
|
+
let list = shapes.slice();
|
|
2513
|
+
for (const e of effects ?? []) {
|
|
2514
|
+
if (!isEnabled(e) || !list.length) continue;
|
|
2515
|
+
switch (e.type) {
|
|
2516
|
+
case "trim": {
|
|
2517
|
+
const { start, end, offset, mode } = e.params;
|
|
2518
|
+
list = mapPaths(list, (p) => mode === "individual" ? trimPathsIndividually(p, start, end, offset) : trimPaths(p, start, end, offset));
|
|
2519
|
+
break;
|
|
2520
|
+
}
|
|
2521
|
+
case "roundCorners":
|
|
2522
|
+
list = mapPaths(list, (p) => roundCornerPaths(p, e.params.radius));
|
|
2523
|
+
break;
|
|
2524
|
+
case "puckerBloat":
|
|
2525
|
+
list = mapPaths(list, (p) => puckerBloatPaths(p, e.params.amount));
|
|
2526
|
+
break;
|
|
2527
|
+
case "twist":
|
|
2528
|
+
list = mapPaths(list, (p) => twistPaths(p, e.params.angle, add3(pathsCenter(p), e.params.center)));
|
|
2529
|
+
break;
|
|
2530
|
+
case "zigzag":
|
|
2531
|
+
list = mapPaths(list, (p) => zigzagPaths(p, e.params.size, e.params.ridges, e.params.smooth));
|
|
2532
|
+
break;
|
|
2533
|
+
case "wiggle":
|
|
2534
|
+
list = mapPaths(list, (p) => wigglePaths(p, e.params));
|
|
2535
|
+
break;
|
|
2536
|
+
case "offset": {
|
|
2537
|
+
const o = e.params;
|
|
2538
|
+
list = mapPaths(list, (p) => offsetPaths(p, o.amount, o.join, o.miterLimit, o.copies, o.copyOffset));
|
|
2539
|
+
break;
|
|
2540
|
+
}
|
|
2541
|
+
case "merge": {
|
|
2542
|
+
const first = list[0];
|
|
2543
|
+
list = [{ ...first, path: mergePaths(list.map((s) => s.path), e.params.mode) }];
|
|
2544
|
+
break;
|
|
2545
|
+
}
|
|
2546
|
+
case "repeater": {
|
|
2547
|
+
const r = e.params;
|
|
2548
|
+
const copies = repeatPaths(
|
|
2549
|
+
list.map((s) => s.path),
|
|
2550
|
+
r,
|
|
2551
|
+
add3(pathsCenter(list.map((s) => s.path)), r.anchor)
|
|
2552
|
+
);
|
|
2553
|
+
const n = copies.length;
|
|
2554
|
+
const next = [];
|
|
2555
|
+
for (const { copy, paths } of copies) {
|
|
2556
|
+
const rank = r.composite === "above" ? copy.index : n - 1 - copy.index;
|
|
2557
|
+
list.forEach((s, i) => next.push({ source: s.source, path: paths[i], opacity: s.opacity * copy.opacity, order: [rank, ...s.order] }));
|
|
2558
|
+
}
|
|
2559
|
+
list = next;
|
|
2560
|
+
break;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
return list;
|
|
2565
|
+
}
|
|
2566
|
+
function childShapes(child, index) {
|
|
2567
|
+
if (child.visible === false) return [];
|
|
2568
|
+
const m = child.transform;
|
|
2569
|
+
const k = child.opacity ?? 1;
|
|
2570
|
+
return vectorAppearanceShapes(child).map((s) => ({
|
|
2571
|
+
source: s.source,
|
|
2572
|
+
path: m && !isIdentityMatrix(m) ? transformBezierPath(s.path, m) : s.path,
|
|
2573
|
+
opacity: s.opacity * k,
|
|
2574
|
+
order: [index, ...s.order]
|
|
2575
|
+
}));
|
|
2576
|
+
}
|
|
2577
|
+
function liveBooleanShapes(group) {
|
|
2578
|
+
const operands = [];
|
|
2579
|
+
let source = null;
|
|
2580
|
+
group.children.forEach((child, i) => {
|
|
2581
|
+
const shapes = childShapes(child, i);
|
|
2582
|
+
if (!shapes.length) return;
|
|
2583
|
+
source ??= shapes[0].source;
|
|
2584
|
+
const path = shapes.length === 1 ? shapes[0].path : pathBoolean("union", shapes.map((s) => ({ path: s.path, fillRule: s.source.fillRule ?? "nonzero" }))).path;
|
|
2585
|
+
const fillRule = shapes.length === 1 ? shapes[0].source.fillRule ?? "nonzero" : "nonzero";
|
|
2586
|
+
operands.push({ path, fillRule, operation: vectorOperandOperation(group, child) });
|
|
2587
|
+
});
|
|
2588
|
+
if (!source) return [];
|
|
2589
|
+
return [{ source, path: foldVectorBooleanOperands(operands), opacity: 1, order: [0] }];
|
|
2590
|
+
}
|
|
2591
|
+
var shapeCache = /* @__PURE__ */ new WeakMap();
|
|
2592
|
+
function vectorAppearanceShapes(node) {
|
|
2593
|
+
const hit = shapeCache.get(node);
|
|
2594
|
+
if (hit) return hit;
|
|
2595
|
+
let base;
|
|
2596
|
+
if (node.type === "path") base = [{ source: node, path: subpathsToBezier(node.subpaths), opacity: 1, order: [] }];
|
|
2597
|
+
else if (node.liveBoolean) base = liveBooleanShapes(node);
|
|
2598
|
+
else base = node.children.flatMap((c, i) => childShapes(c, i));
|
|
2599
|
+
const out = applyVectorEffects(base, node.effects).sort((a, b) => compareOrder(a.order, b.order));
|
|
2600
|
+
shapeCache.set(node, out);
|
|
2601
|
+
return out;
|
|
2602
|
+
}
|
|
2603
|
+
var outlineCache = /* @__PURE__ */ new WeakMap();
|
|
2604
|
+
function vectorAppearanceOutline(node) {
|
|
2605
|
+
let hit = outlineCache.get(node);
|
|
2606
|
+
if (!hit) {
|
|
2607
|
+
hit = node.type === "path" && !hasOwnVectorAppearance(node) ? subpathsToBezier(node.subpaths) : vectorAppearanceShapes(node).flatMap((s) => s.path);
|
|
2608
|
+
outlineCache.set(node, hit);
|
|
2609
|
+
}
|
|
2610
|
+
return hit;
|
|
2611
|
+
}
|
|
2612
|
+
function shapeNode(shape, id) {
|
|
2613
|
+
const {
|
|
2614
|
+
id: _id,
|
|
2615
|
+
type: _type,
|
|
2616
|
+
subpaths: _s,
|
|
2617
|
+
transform: _t,
|
|
2618
|
+
effects: _e,
|
|
2619
|
+
booleanOperation: _b,
|
|
2620
|
+
blendStep: _bs,
|
|
2621
|
+
locked: _l,
|
|
2622
|
+
visible: _v,
|
|
2623
|
+
opacity: _o,
|
|
2624
|
+
...style
|
|
2625
|
+
} = shape.source;
|
|
2626
|
+
return { ...style, id, type: "path", subpaths: bezierToSubpaths(shape.path), ...shape.opacity < 1 ? { opacity: shape.opacity } : {} };
|
|
2627
|
+
}
|
|
2628
|
+
function placement(node) {
|
|
2629
|
+
return {
|
|
2630
|
+
...node.name !== void 0 ? { name: node.name } : {},
|
|
2631
|
+
...node.transform ? { transform: node.transform } : {},
|
|
2632
|
+
...node.visible === false ? { visible: false } : {},
|
|
2633
|
+
...node.locked ? { locked: true } : {},
|
|
2634
|
+
...node.blendStep ? { blendStep: true } : {},
|
|
2635
|
+
...node.booleanOperation ? { booleanOperation: node.booleanOperation } : {}
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2638
|
+
function vectorAppearanceNodes(node) {
|
|
2639
|
+
return vectorAppearanceShapes(node).map((s, k) => shapeNode(s, `${node.id}~fx${k}`));
|
|
2640
|
+
}
|
|
2641
|
+
var expandCache = /* @__PURE__ */ new WeakMap();
|
|
2642
|
+
function expandVectorAppearance(node) {
|
|
2643
|
+
if (!hasVectorAppearance(node)) return node;
|
|
2644
|
+
const hit = expandCache.get(node);
|
|
2645
|
+
if (hit) return hit;
|
|
2646
|
+
let out;
|
|
2647
|
+
if (!hasOwnVectorAppearance(node)) {
|
|
2648
|
+
const group = node;
|
|
2649
|
+
out = { ...group, children: group.children.map(expandVectorAppearance) };
|
|
2650
|
+
} else {
|
|
2651
|
+
const shapes = vectorAppearanceShapes(node);
|
|
2652
|
+
if (node.type === "path" && shapes.length === 1) {
|
|
2653
|
+
const one = shapes[0];
|
|
2654
|
+
const { effects: _e, opacity: _o, ...rest } = node;
|
|
2655
|
+
const opacity = (node.opacity ?? 1) * one.opacity;
|
|
2656
|
+
out = { ...rest, subpaths: bezierToSubpaths(one.path), ...opacity < 1 ? { opacity } : {} };
|
|
2657
|
+
} else {
|
|
2658
|
+
out = {
|
|
2659
|
+
id: node.id,
|
|
2660
|
+
type: "group",
|
|
2661
|
+
...placement(node),
|
|
2662
|
+
...node.opacity !== void 0 ? { opacity: node.opacity } : {},
|
|
2663
|
+
children: vectorAppearanceNodes(node)
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
expandCache.set(node, out);
|
|
2668
|
+
return out;
|
|
2669
|
+
}
|
|
2670
|
+
function expandVectorAppearances(nodes) {
|
|
2671
|
+
let changed = false;
|
|
2672
|
+
const out = nodes.map((n) => {
|
|
2673
|
+
const e = expandVectorAppearance(n);
|
|
2674
|
+
if (e !== n) changed = true;
|
|
2675
|
+
return e;
|
|
2676
|
+
});
|
|
2677
|
+
return changed ? out : nodes;
|
|
2678
|
+
}
|
|
2679
|
+
function sanitizeVectorEffects(value) {
|
|
2680
|
+
if (!Array.isArray(value)) return [];
|
|
2681
|
+
const out = [];
|
|
2682
|
+
const used = /* @__PURE__ */ new Set();
|
|
2683
|
+
for (const raw of value) {
|
|
2684
|
+
if (!raw || typeof raw !== "object") continue;
|
|
2685
|
+
const r = raw;
|
|
2686
|
+
const type = r.type;
|
|
2687
|
+
if (typeof type !== "string" || !VECTOR_EFFECT_DEFAULTS[type]) continue;
|
|
2688
|
+
let id = typeof r.id === "string" && r.id && !used.has(r.id) ? r.id : "";
|
|
2689
|
+
for (let k = out.length + 1; !id; k++) if (!used.has(`fx${k}`)) id = `fx${k}`;
|
|
2690
|
+
used.add(id);
|
|
2691
|
+
const effect = createVectorEffect(type, id, r.params ?? {});
|
|
2692
|
+
out.push(r.enabled === false ? { ...effect, enabled: false } : effect);
|
|
2693
|
+
}
|
|
2694
|
+
return out;
|
|
2695
|
+
}
|
|
2696
|
+
function transformVectorEffects(effects, m) {
|
|
2697
|
+
const lin = (v2) => ({ x: m[0] * v2.x + m[2] * v2.y, y: m[1] * v2.x + m[3] * v2.y });
|
|
2698
|
+
const mirror = m[0] * m[3] - m[1] * m[2] < 0 ? -1 : 1;
|
|
2699
|
+
return effects.map((e) => {
|
|
2700
|
+
if (e.type === "twist") return { ...e, params: { ...e.params, center: lin(e.params.center), angle: e.params.angle * mirror } };
|
|
2701
|
+
if (e.type === "repeater")
|
|
2702
|
+
return { ...e, params: { ...e.params, position: lin(e.params.position), anchor: lin(e.params.anchor), rotation: e.params.rotation * mirror } };
|
|
2703
|
+
return e;
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
function vectorEnteredGroups(nodes, ids) {
|
|
2707
|
+
const out = /* @__PURE__ */ new Set();
|
|
2708
|
+
if (!ids.length) return out;
|
|
2709
|
+
const want = new Set(ids);
|
|
2710
|
+
const visit = (list, live) => {
|
|
2711
|
+
for (const n of list) {
|
|
2712
|
+
const own = n.type === "group" && hasOwnVectorAppearance(n);
|
|
2713
|
+
if (want.has(n.id)) {
|
|
2714
|
+
for (const id of live) out.add(id);
|
|
2715
|
+
if (own) out.add(n.id);
|
|
2716
|
+
}
|
|
2717
|
+
if (n.type === "group") visit(n.children, own ? [...live, n.id] : live);
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
visit(nodes, []);
|
|
2721
|
+
return out;
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
// src/vector/geometry.ts
|
|
2725
|
+
var EPS3 = 1e-9;
|
|
1047
2726
|
var cloneAnchor = (a) => ({
|
|
1048
2727
|
x: a.x,
|
|
1049
2728
|
y: a.y,
|
|
@@ -1068,7 +2747,7 @@ function hasPaint(paint) {
|
|
|
1068
2747
|
return true;
|
|
1069
2748
|
}
|
|
1070
2749
|
var handleOf = (a, side) => a[side] ?? { x: a.x, y: a.y };
|
|
1071
|
-
var isOnAnchor = (a, h) => !h || Math.abs(h.x - a.x) <
|
|
2750
|
+
var isOnAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS3 && Math.abs(h.y - a.y) < EPS3;
|
|
1072
2751
|
function subpathSegmentCount(sp) {
|
|
1073
2752
|
const n = sp.anchors.length;
|
|
1074
2753
|
if (n < 2) return 0;
|
|
@@ -1100,7 +2779,7 @@ function subpathsToBezier(subpaths, options = {}) {
|
|
|
1100
2779
|
return out;
|
|
1101
2780
|
}
|
|
1102
2781
|
function vectorNodeToBezier(node) {
|
|
1103
|
-
return node.type === "path" ?
|
|
2782
|
+
return node.type === "path" ? vectorAppearanceOutline(node) : [];
|
|
1104
2783
|
}
|
|
1105
2784
|
function vectorPathData(input, precision = 3) {
|
|
1106
2785
|
const subpaths = Array.isArray(input) ? input : input.subpaths;
|
|
@@ -1115,8 +2794,8 @@ function inferAnchorType(a) {
|
|
|
1115
2794
|
const li = Math.hypot(ix, iy);
|
|
1116
2795
|
const lo = Math.hypot(ox, oy);
|
|
1117
2796
|
const cross2 = (ix * oy - iy * ox) / (li * lo);
|
|
1118
|
-
const
|
|
1119
|
-
if (Math.abs(cross2) > 1e-3 ||
|
|
2797
|
+
const dot3 = (ix * ox + iy * oy) / (li * lo);
|
|
2798
|
+
if (Math.abs(cross2) > 1e-3 || dot3 > 0) return "corner";
|
|
1120
2799
|
return Math.abs(li - lo) <= 1e-3 * Math.max(li, lo) ? "symmetric" : "smooth";
|
|
1121
2800
|
}
|
|
1122
2801
|
function bezierToSubpaths(input) {
|
|
@@ -1214,12 +2893,13 @@ function collectPathNodes(node) {
|
|
|
1214
2893
|
function nodeMatrix(node) {
|
|
1215
2894
|
return node.transform ?? IDENTITY_MATRIX;
|
|
1216
2895
|
}
|
|
1217
|
-
function vectorPathWorldBezier(node, parent = IDENTITY_MATRIX) {
|
|
2896
|
+
function vectorPathWorldBezier(node, parent = IDENTITY_MATRIX, options = {}) {
|
|
1218
2897
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
1219
|
-
const path = subpathsToBezier(node.subpaths);
|
|
2898
|
+
const path = options.effects === false || !hasOwnVectorAppearance(node) ? subpathsToBezier(node.subpaths) : vectorAppearanceOutline(node);
|
|
1220
2899
|
return isIdentityMatrix(m) ? path : transformBezierPath(path, m);
|
|
1221
2900
|
}
|
|
1222
2901
|
function vectorNodeBounds(node, parent = IDENTITY_MATRIX) {
|
|
2902
|
+
node = expandVectorAppearance(node);
|
|
1223
2903
|
if (node.type === "group") {
|
|
1224
2904
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
1225
2905
|
return unionBounds(node.children.map((c) => vectorNodeBounds(c, m)));
|
|
@@ -1249,11 +2929,12 @@ function boundsIntersect(a, b) {
|
|
|
1249
2929
|
function boundsFromPoints(a, b) {
|
|
1250
2930
|
return { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), width: Math.abs(b.x - a.x), height: Math.abs(b.y - a.y) };
|
|
1251
2931
|
}
|
|
1252
|
-
function hitTestVectorNode(node, p, tolerance = 0, parent = IDENTITY_MATRIX) {
|
|
2932
|
+
function hitTestVectorNode(node, p, tolerance = 0, parent = IDENTITY_MATRIX, options = {}) {
|
|
1253
2933
|
if (node.visible === false) return false;
|
|
2934
|
+
if (!(node.type === "group" && (!hasOwnVectorAppearance(node) || options.entered?.has(node.id)))) node = expandVectorAppearance(node);
|
|
1254
2935
|
if (node.type === "group") {
|
|
1255
2936
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
1256
|
-
return node.children.some((c) => hitTestVectorNode(c, p, tolerance, m));
|
|
2937
|
+
return node.children.some((c) => hitTestVectorNode(c, p, tolerance, m, options));
|
|
1257
2938
|
}
|
|
1258
2939
|
const path = vectorPathWorldBezier(node, parent);
|
|
1259
2940
|
if (path.length === 0) return false;
|
|
@@ -1266,11 +2947,11 @@ function vectorStrokeOutline(node, parent = IDENTITY_MATRIX) {
|
|
|
1266
2947
|
if (!hasPaint(node.stroke) || !((node.strokeWidth ?? 1) > 0)) return [];
|
|
1267
2948
|
return vectorStrokeOutlinePath(vectorPathWorldBezier(node, parent), node);
|
|
1268
2949
|
}
|
|
1269
|
-
function hitTestVectorNodes(nodes, p, tolerance = 0) {
|
|
2950
|
+
function hitTestVectorNodes(nodes, p, tolerance = 0, options = {}) {
|
|
1270
2951
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
1271
2952
|
const node = nodes[i];
|
|
1272
2953
|
if (node.visible === false || node.locked) continue;
|
|
1273
|
-
if (hitTestVectorNode(node, p, tolerance)) return node;
|
|
2954
|
+
if (hitTestVectorNode(node, p, tolerance, IDENTITY_MATRIX, options)) return node;
|
|
1274
2955
|
}
|
|
1275
2956
|
return null;
|
|
1276
2957
|
}
|
|
@@ -1349,6 +3030,7 @@ function mapAnchor(a, m) {
|
|
|
1349
3030
|
}
|
|
1350
3031
|
function bakeIntoGeometry(node, m) {
|
|
1351
3032
|
if (isIdentityMatrix(m)) return node;
|
|
3033
|
+
if (node.effects?.length) node = { ...node, effects: transformVectorEffects(node.effects, m) };
|
|
1352
3034
|
if (node.type === "group") {
|
|
1353
3035
|
const group = node;
|
|
1354
3036
|
const children = group.children.map((c) => transformVectorNode(c, m));
|
|
@@ -1362,6 +3044,9 @@ function bakeIntoGeometry(node, m) {
|
|
|
1362
3044
|
return {
|
|
1363
3045
|
...path,
|
|
1364
3046
|
...mirrored ? { strokeWidthProfile: mirrorWidthProfile(path.strokeWidthProfile) } : {},
|
|
3047
|
+
// Gradients in the node's own space move with its geometry.
|
|
3048
|
+
...isVectorGradient(path.fill) ? { fill: transformVectorPaint(path.fill, m) } : {},
|
|
3049
|
+
...isVectorGradient(path.stroke) ? { stroke: transformVectorPaint(path.stroke, m) } : {},
|
|
1365
3050
|
subpaths: path.subpaths.map((sp) => ({
|
|
1366
3051
|
closed: sp.closed,
|
|
1367
3052
|
anchors: sp.anchors.map((a) => mapAnchor(a, m))
|
|
@@ -1416,7 +3101,7 @@ function moveAnchorHandle(a, side, pos, breakHandles = false) {
|
|
|
1416
3101
|
const dy = pos.y - a.y;
|
|
1417
3102
|
const len2 = Math.hypot(dx, dy);
|
|
1418
3103
|
if (type === "symmetric") next[other] = { x: a.x - dx, y: a.y - dy };
|
|
1419
|
-
else if (type === "smooth" && len2 >
|
|
3104
|
+
else if (type === "smooth" && len2 > EPS3) {
|
|
1420
3105
|
const o = a[other];
|
|
1421
3106
|
const olen = o ? Math.hypot(o.x - a.x, o.y - a.y) : len2;
|
|
1422
3107
|
next[other] = { x: a.x - dx / len2 * olen, y: a.y - dy / len2 * olen };
|
|
@@ -1446,15 +3131,15 @@ function convertAnchor(sp, index, type) {
|
|
|
1446
3131
|
else if (prev && next) dir = { x: next.x - prev.x, y: next.y - prev.y };
|
|
1447
3132
|
else if (next) dir = { x: next.x - a.x, y: next.y - a.y };
|
|
1448
3133
|
else if (prev) dir = { x: a.x - prev.x, y: a.y - prev.y };
|
|
1449
|
-
if (!dir || Math.hypot(dir.x, dir.y) <
|
|
3134
|
+
if (!dir || Math.hypot(dir.x, dir.y) < EPS3) return { anchors, closed: sp.closed };
|
|
1450
3135
|
const dl = Math.hypot(dir.x, dir.y);
|
|
1451
3136
|
const ux = dir.x / dl;
|
|
1452
3137
|
const uy = dir.y / dl;
|
|
1453
3138
|
lenIn = a.in && !isOnAnchor(a, a.in) ? Math.hypot(a.in.x - a.x, a.in.y - a.y) : prev ? Math.hypot(prev.x - a.x, prev.y - a.y) / 3 : 0;
|
|
1454
3139
|
lenOut = a.out && !isOnAnchor(a, a.out) ? Math.hypot(a.out.x - a.x, a.out.y - a.y) : next ? Math.hypot(next.x - a.x, next.y - a.y) / 3 : 0;
|
|
1455
3140
|
if (type === "symmetric") lenIn = lenOut = (lenIn + lenOut) / 2 || Math.max(lenIn, lenOut);
|
|
1456
|
-
if (lenIn >
|
|
1457
|
-
if (lenOut >
|
|
3141
|
+
if (lenIn > EPS3) a.in = { x: a.x - ux * lenIn, y: a.y - uy * lenIn };
|
|
3142
|
+
if (lenOut > EPS3) a.out = { x: a.x + ux * lenOut, y: a.y + uy * lenOut };
|
|
1458
3143
|
a.type = type;
|
|
1459
3144
|
return { anchors, closed: sp.closed };
|
|
1460
3145
|
}
|
|
@@ -1464,12 +3149,12 @@ function insertAnchor(sp, segment, t) {
|
|
|
1464
3149
|
const ia = segment;
|
|
1465
3150
|
const ib = (segment + 1) % n;
|
|
1466
3151
|
const seg = subpathSegment(sp, segment);
|
|
1467
|
-
const [
|
|
1468
|
-
const mid =
|
|
3152
|
+
const [left2, right] = splitSegment(seg, t);
|
|
3153
|
+
const mid = left2[left2.length - 1];
|
|
1469
3154
|
const anchor = { x: mid[0], y: mid[1] };
|
|
1470
3155
|
if (seg.length === 4) {
|
|
1471
|
-
anchors[ia].out = { x:
|
|
1472
|
-
anchor.in = { x:
|
|
3156
|
+
anchors[ia].out = { x: left2[1][0], y: left2[1][1] };
|
|
3157
|
+
anchor.in = { x: left2[2][0], y: left2[2][1] };
|
|
1473
3158
|
anchor.out = { x: right[1][0], y: right[1][1] };
|
|
1474
3159
|
anchor.type = "smooth";
|
|
1475
3160
|
anchors[ib].in = { x: right[2][0], y: right[2][1] };
|
|
@@ -1554,6 +3239,166 @@ function roundSubpaths(subpaths, precision = 3) {
|
|
|
1554
3239
|
}));
|
|
1555
3240
|
}
|
|
1556
3241
|
|
|
3242
|
+
// src/vector/gradientStroke.ts
|
|
3243
|
+
var SHARP = 1.25;
|
|
3244
|
+
var TOLERANCE = 0.2;
|
|
3245
|
+
var OVERLAP = 0.5;
|
|
3246
|
+
var fmt2 = (n, digits = 3) => {
|
|
3247
|
+
const k = 10 ** digits;
|
|
3248
|
+
const v2 = Math.round(n * k) / k;
|
|
3249
|
+
return Object.is(v2, -0) ? "0" : String(v2);
|
|
3250
|
+
};
|
|
3251
|
+
var toSvgStops2 = (stops) => stops.map((s) => ({ offset: `${fmt2(s.offset * 100)}%`, stopColor: s.color, ...s.opacity !== void 0 && s.opacity !== 1 ? { stopOpacity: Math.round(s.opacity * 1e4) / 1e4 } : {} }));
|
|
3252
|
+
var polygonD2 = (pts, precision) => `M${pts.map((p) => `${fmt2(p.x, precision)} ${fmt2(p.y, precision)}`).join("L")}Z`;
|
|
3253
|
+
var add4 = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
3254
|
+
var sub4 = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
3255
|
+
var scale2 = (a, k) => ({ x: a.x * k, y: a.y * k });
|
|
3256
|
+
var left = (d) => ({ x: d.y, y: -d.x });
|
|
3257
|
+
var unit3 = (v2) => {
|
|
3258
|
+
const l = Math.hypot(v2.x, v2.y);
|
|
3259
|
+
return l > 1e-12 ? { x: v2.x / l, y: v2.y / l } : { x: 1, y: 0 };
|
|
3260
|
+
};
|
|
3261
|
+
function flattenedSubpaths(node) {
|
|
3262
|
+
const out = [];
|
|
3263
|
+
for (const contour of pathToContours(subpathsToBezier(node.subpaths))) {
|
|
3264
|
+
const segs = closeContour(contour);
|
|
3265
|
+
if (!segs.length) continue;
|
|
3266
|
+
const ring = flattenChain(segs, TOLERANCE);
|
|
3267
|
+
const pts = ring.map(([x, y]) => ({ x, y })).filter((p, i, a) => i === 0 || Math.hypot(p.x - a[i - 1].x, p.y - a[i - 1].y) > 1e-9);
|
|
3268
|
+
if (contour.closed && pts.length > 2 && Math.hypot(pts[0].x - pts[pts.length - 1].x, pts[0].y - pts[pts.length - 1].y) < 1e-9) pts.pop();
|
|
3269
|
+
if (pts.length >= 2) out.push({ pts, closed: contour.closed && pts.length > 2 });
|
|
3270
|
+
}
|
|
3271
|
+
return out;
|
|
3272
|
+
}
|
|
3273
|
+
var signedArea = (pts) => {
|
|
3274
|
+
let a = 0;
|
|
3275
|
+
for (let i = 0; i < pts.length; i++) {
|
|
3276
|
+
const p = pts[i];
|
|
3277
|
+
const q = pts[(i + 1) % pts.length];
|
|
3278
|
+
a += p.x * q.y - q.x * p.y;
|
|
3279
|
+
}
|
|
3280
|
+
return a / 2;
|
|
3281
|
+
};
|
|
3282
|
+
function vectorStrokeGradientDefs(g, id, node, options = {}) {
|
|
3283
|
+
const precision = options.precision ?? 3;
|
|
3284
|
+
const mode = g.strokeMode === "across" ? "across" : "along";
|
|
3285
|
+
const expanded = expandVectorGradientStops(g);
|
|
3286
|
+
const reach = vectorStrokeReach(node) + 1;
|
|
3287
|
+
const area = options.area ?? vectorGradientArea(options.bounds && { x: options.bounds.x - reach, y: options.bounds.y - reach, width: options.bounds.width + 2 * reach, height: options.bounds.height + 2 * reach });
|
|
3288
|
+
const width = node.strokeWidth ?? 1;
|
|
3289
|
+
const align = node.strokeAlign ?? "center";
|
|
3290
|
+
const profile = strokeWidthProfileOf(node);
|
|
3291
|
+
const gradients = [];
|
|
3292
|
+
const patches = [];
|
|
3293
|
+
const slices = [];
|
|
3294
|
+
const colourAt = (t) => {
|
|
3295
|
+
const s = spanVectorGradientStops(g, t, t, expanded)[0];
|
|
3296
|
+
return s;
|
|
3297
|
+
};
|
|
3298
|
+
let counter = 0;
|
|
3299
|
+
const solid = (pts, t) => {
|
|
3300
|
+
const s = colourAt(t);
|
|
3301
|
+
const gid = `${id}-p${counter++}`;
|
|
3302
|
+
gradients.push({ id: gid, type: "linear", attrs: { gradientUnits: "userSpaceOnUse", x1: 0, y1: 0, x2: 1, y2: 0 }, stops: toSvgStops2([{ ...s, offset: 0 }]) });
|
|
3303
|
+
patches.push({ d: polygonD2(pts, precision), fill: `url(#${gid})` });
|
|
3304
|
+
};
|
|
3305
|
+
const disc = (c, r) => Array.from({ length: 16 }, (_, k) => add4(c, scale2({ x: Math.cos(k * Math.PI / 8), y: Math.sin(k * Math.PI / 8) }, r)));
|
|
3306
|
+
for (const { pts, closed } of flattenedSubpaths(node)) {
|
|
3307
|
+
const n = pts.length;
|
|
3308
|
+
const segCount = closed ? n : n - 1;
|
|
3309
|
+
const dirs = [];
|
|
3310
|
+
const lengths = [0];
|
|
3311
|
+
for (let i = 0; i < segCount; i++) {
|
|
3312
|
+
const a = pts[i];
|
|
3313
|
+
const b = pts[(i + 1) % n];
|
|
3314
|
+
dirs.push(unit3(sub4(b, a)));
|
|
3315
|
+
lengths.push(lengths[i] + Math.hypot(b.x - a.x, b.y - a.y));
|
|
3316
|
+
}
|
|
3317
|
+
const total = lengths[segCount];
|
|
3318
|
+
if (!(total > 1e-9)) continue;
|
|
3319
|
+
const tAt = (i) => lengths[i] / total;
|
|
3320
|
+
const clockwise = closed && signedArea(pts) > 0;
|
|
3321
|
+
const visibleLeft = align === "center" || !closed ? null : align === "inside" ? !clockwise : clockwise;
|
|
3322
|
+
const ribs = [];
|
|
3323
|
+
for (let i = 0; i < n; i++) {
|
|
3324
|
+
const p = pts[i];
|
|
3325
|
+
const din = i > 0 || closed ? dirs[(i - 1 + segCount) % segCount] : null;
|
|
3326
|
+
const dout = i < segCount ? dirs[i] : null;
|
|
3327
|
+
const square = (d) => ({ l: add4(p, scale2(left(d), reach)), r: sub4(p, scale2(left(d), reach)) });
|
|
3328
|
+
if (!din || !dout) {
|
|
3329
|
+
const rib = square(din ?? dout);
|
|
3330
|
+
ribs.push({ before: rib, after: rib });
|
|
3331
|
+
continue;
|
|
3332
|
+
}
|
|
3333
|
+
const nin = left(din);
|
|
3334
|
+
const nout = left(dout);
|
|
3335
|
+
const den = 1 + nin.x * nout.x + nin.y * nout.y;
|
|
3336
|
+
const stretch = den > 1e-12 ? Math.sqrt(2 / den) : Infinity;
|
|
3337
|
+
if (stretch <= SHARP) {
|
|
3338
|
+
const m = scale2(add4(nin, nout), reach / den);
|
|
3339
|
+
const rib = { l: add4(p, m), r: sub4(p, m) };
|
|
3340
|
+
ribs.push({ before: rib, after: rib });
|
|
3341
|
+
} else {
|
|
3342
|
+
ribs.push({ before: square(din), after: square(dout) });
|
|
3343
|
+
solid(disc(p, reach * 1.5), tAt(i));
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
if (!closed) {
|
|
3347
|
+
const d0 = dirs[0];
|
|
3348
|
+
const d1 = dirs[segCount - 1];
|
|
3349
|
+
const a = pts[0];
|
|
3350
|
+
const b = pts[n - 1];
|
|
3351
|
+
const cap = (p, d) => [add4(p, scale2(left(d), reach)), add4(add4(p, scale2(d, reach)), scale2(left(d), reach)), sub4(add4(p, scale2(d, reach)), scale2(left(d), reach)), sub4(p, scale2(left(d), reach))];
|
|
3352
|
+
solid(cap(a, scale2(d0, -1)), 0);
|
|
3353
|
+
solid(cap(b, d1), 1);
|
|
3354
|
+
}
|
|
3355
|
+
for (let i = 0; i < segCount; i++) {
|
|
3356
|
+
const j = (i + 1) % n;
|
|
3357
|
+
const a = pts[i];
|
|
3358
|
+
const b = pts[j];
|
|
3359
|
+
const d = dirs[i];
|
|
3360
|
+
const from = ribs[i].after;
|
|
3361
|
+
const to = ribs[j].before;
|
|
3362
|
+
const ext = scale2(d, OVERLAP);
|
|
3363
|
+
const quad = [from.l, add4(to.l, ext), add4(to.r, ext), from.r];
|
|
3364
|
+
const gid = `${id}-s${counter++}`;
|
|
3365
|
+
const t0 = tAt(i);
|
|
3366
|
+
const t1 = closed && i === segCount - 1 ? 1 : tAt(i + 1);
|
|
3367
|
+
if (mode === "along") {
|
|
3368
|
+
gradients.push({
|
|
3369
|
+
id: gid,
|
|
3370
|
+
type: "linear",
|
|
3371
|
+
attrs: { gradientUnits: "userSpaceOnUse", x1: fmt2(a.x, 4), y1: fmt2(a.y, 4), x2: fmt2(b.x, 4), y2: fmt2(b.y, 4) },
|
|
3372
|
+
stops: toSvgStops2(spanVectorGradientStops(g, t0, t1, expanded))
|
|
3373
|
+
});
|
|
3374
|
+
} else {
|
|
3375
|
+
const mid = scale2(add4(a, b), 0.5);
|
|
3376
|
+
const side = widthProfileAt(profile, (t0 + t1) / 2);
|
|
3377
|
+
const nrm = left(d);
|
|
3378
|
+
const reachL = visibleLeft === null ? width / 2 * side.left : visibleLeft ? width * side.left : 0;
|
|
3379
|
+
const reachR = visibleLeft === null ? width / 2 * side.right : visibleLeft ? 0 : width * side.right;
|
|
3380
|
+
const p0 = add4(mid, scale2(nrm, reachL));
|
|
3381
|
+
const p1 = sub4(mid, scale2(nrm, reachR));
|
|
3382
|
+
gradients.push({
|
|
3383
|
+
id: gid,
|
|
3384
|
+
type: "linear",
|
|
3385
|
+
attrs: { gradientUnits: "userSpaceOnUse", x1: fmt2(p0.x, 4), y1: fmt2(p0.y, 4), x2: fmt2(p1.x, 4), y2: fmt2(p1.y, 4) },
|
|
3386
|
+
stops: toSvgStops2(expanded)
|
|
3387
|
+
});
|
|
3388
|
+
}
|
|
3389
|
+
slices.push({ d: polygonD2(quad, precision), fill: `url(#${gid})` });
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
const def = {
|
|
3393
|
+
id,
|
|
3394
|
+
type: "pattern",
|
|
3395
|
+
attrs: patternAttrs(area),
|
|
3396
|
+
gradients,
|
|
3397
|
+
tiles: [...patches, ...slices]
|
|
3398
|
+
};
|
|
3399
|
+
return [def];
|
|
3400
|
+
}
|
|
3401
|
+
|
|
1557
3402
|
// src/vector/svg.ts
|
|
1558
3403
|
function vectorCornersAttribute(subpaths, precision = 3) {
|
|
1559
3404
|
const out = [];
|
|
@@ -1578,10 +3423,30 @@ function applyVectorCornersAttribute(subpaths, value) {
|
|
|
1578
3423
|
return subpaths;
|
|
1579
3424
|
}
|
|
1580
3425
|
var escapeAttr = (value) => value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
1581
|
-
function
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
3426
|
+
function vectorPathLocalBounds(node) {
|
|
3427
|
+
const b = bezierPathBounds(subpathsToBezier(node.subpaths));
|
|
3428
|
+
return b ? { x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY } : null;
|
|
3429
|
+
}
|
|
3430
|
+
var paintCache = /* @__PURE__ */ new WeakMap();
|
|
3431
|
+
function vectorPaintToSvg(paint, gradientId, options = {}) {
|
|
3432
|
+
if (!hasPaint(paint)) return { value: "none", defs: [] };
|
|
3433
|
+
if (typeof paint === "string") return { value: paint, defs: [] };
|
|
3434
|
+
if (!isVectorGradient(paint)) return { value: "none", defs: [] };
|
|
3435
|
+
const role = options.role ?? "fill";
|
|
3436
|
+
const precision = options.precision ?? 3;
|
|
3437
|
+
const key = `${gradientId}|${role}|${precision}`;
|
|
3438
|
+
const owner = options.node ?? paint;
|
|
3439
|
+
const cached = paintCache.get(owner)?.get(key);
|
|
3440
|
+
if (cached && (!options.node || (role === "fill" ? options.node.fill : options.node.stroke) === paint)) return { value: `url(#${gradientId})`, defs: cached };
|
|
3441
|
+
const node = options.node;
|
|
3442
|
+
const bounds = node ? vectorPathLocalBounds(node) : null;
|
|
3443
|
+
const reach = node && role === "stroke" ? vectorStrokeReach(node) + 1 : 1;
|
|
3444
|
+
const area = bounds ? { x: bounds.x - reach, y: bounds.y - reach, width: bounds.width + 2 * reach, height: bounds.height + 2 * reach } : null;
|
|
3445
|
+
const defs = node && role === "stroke" && (paint.strokeMode === "along" || paint.strokeMode === "across") ? vectorStrokeGradientDefs(paint, gradientId, node, { bounds, area, precision }) : vectorGradientSvgDefs(paint, gradientId, { bounds, area, precision });
|
|
3446
|
+
let byKey = paintCache.get(owner);
|
|
3447
|
+
if (!byKey) paintCache.set(owner, byKey = /* @__PURE__ */ new Map());
|
|
3448
|
+
byKey.set(key, defs);
|
|
3449
|
+
return { value: `url(#${gradientId})`, defs };
|
|
1585
3450
|
}
|
|
1586
3451
|
var partsCache = /* @__PURE__ */ new WeakMap();
|
|
1587
3452
|
function vectorStrokeSvgParts(node, clipId, precision = 3) {
|
|
@@ -1620,18 +3485,26 @@ function widthProfileFromAttribute(value) {
|
|
|
1620
3485
|
if (!value) return [];
|
|
1621
3486
|
return sanitizeWidthProfile(
|
|
1622
3487
|
value.split(";").map((part) => {
|
|
1623
|
-
const [t,
|
|
1624
|
-
return { t, left, right: Number.isFinite(right) ? right :
|
|
3488
|
+
const [t, left2, right] = part.split(",").map((n) => parseFloat(n));
|
|
3489
|
+
return { t, left: left2, right: Number.isFinite(right) ? right : left2 };
|
|
1625
3490
|
})
|
|
1626
3491
|
);
|
|
1627
3492
|
}
|
|
1628
3493
|
var round2 = (n) => Math.round(n * 100) / 100;
|
|
1629
|
-
function gradientMarkup(def) {
|
|
3494
|
+
function gradientMarkup(def, extra = "") {
|
|
1630
3495
|
const tag = def.type === "radial" ? "radialGradient" : "linearGradient";
|
|
1631
3496
|
const attrs = Object.entries(def.attrs).map(([k, v2]) => ` ${k}="${escapeAttr(String(v2))}"`).join("");
|
|
1632
3497
|
const stops = def.stops.map((s) => `<stop offset="${s.offset}" stop-color="${escapeAttr(s.stopColor)}"${s.stopOpacity !== void 0 ? ` stop-opacity="${s.stopOpacity}"` : ""}/>`).join("");
|
|
1633
|
-
return `<${tag} id="${escapeAttr(def.id)}"${attrs}>${stops}</${tag}>`;
|
|
3498
|
+
return `<${tag} id="${escapeAttr(def.id)}"${attrs}${extra}>${stops}</${tag}>`;
|
|
3499
|
+
}
|
|
3500
|
+
function paintDefMarkup(def, gradient) {
|
|
3501
|
+
const extra = gradient ? ` data-vector-gradient="${escapeAttr(JSON.stringify(gradient))}"` : "";
|
|
3502
|
+
if (def.type !== "pattern") return gradientMarkup(def, extra);
|
|
3503
|
+
const attrs = Object.entries(def.attrs).map(([k, v2]) => ` ${k}="${escapeAttr(String(v2))}"`).join("");
|
|
3504
|
+
const inner = def.gradients.map((g) => gradientMarkup(g)).join("") + def.tiles.map((t) => `<path d="${t.d}" fill="${escapeAttr(t.fill)}"/>`).join("");
|
|
3505
|
+
return `<pattern id="${escapeAttr(def.id)}"${attrs}${extra}>${inner}</pattern>`;
|
|
1634
3506
|
}
|
|
3507
|
+
var paintMarkup = (paint, defs) => defs.map((d, i) => paintDefMarkup(d, i === 0 && isVectorGradient(paint) ? paint : void 0));
|
|
1635
3508
|
function blendFromAttribute(value, m) {
|
|
1636
3509
|
if (!value) return null;
|
|
1637
3510
|
let raw;
|
|
@@ -1665,38 +3538,57 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
1665
3538
|
const pretty = options.pretty !== false;
|
|
1666
3539
|
const defs = [];
|
|
1667
3540
|
const lines = [];
|
|
1668
|
-
const
|
|
3541
|
+
const num2 = (n) => formatNumber(n, precision);
|
|
1669
3542
|
const indent = (depth) => pretty ? " ".repeat(depth) : "";
|
|
1670
3543
|
const common = (node) => {
|
|
1671
3544
|
let out2 = ` id="${escapeAttr(node.id)}"`;
|
|
1672
3545
|
if (node.transform) out2 += ` transform="matrix(${node.transform.map((n) => formatNumber(n, 6)).join(" ")})"`;
|
|
1673
|
-
if (node.opacity !== void 0 && node.opacity < 1) out2 += ` opacity="${
|
|
3546
|
+
if (node.opacity !== void 0 && node.opacity < 1) out2 += ` opacity="${num2(node.opacity)}"`;
|
|
1674
3547
|
if (node.visible === false) out2 += ` display="none"`;
|
|
1675
3548
|
if (node.blendStep) out2 += ` data-vector-blend-step=""`;
|
|
1676
3549
|
if (node.type === "group" && node.blend) out2 += ` data-vector-blend="${escapeAttr(JSON.stringify(node.blend))}"`;
|
|
3550
|
+
if (node.booleanOperation) out2 += ` data-vector-boolean-operation="${node.booleanOperation}"`;
|
|
1677
3551
|
return out2;
|
|
1678
3552
|
};
|
|
1679
|
-
const
|
|
3553
|
+
const emit2 = (node, depth) => {
|
|
1680
3554
|
if (node.visible === false && !options.includeHidden) return;
|
|
3555
|
+
if (hasOwnVectorAppearance(node)) {
|
|
3556
|
+
const live2 = {
|
|
3557
|
+
kind: node.type,
|
|
3558
|
+
...node.effects?.length ? { effects: node.effects } : {},
|
|
3559
|
+
...node.type === "group" && node.liveBoolean ? { liveBoolean: node.liveBoolean } : {}
|
|
3560
|
+
};
|
|
3561
|
+
lines.push(`${indent(depth)}<g${common(node)} data-vector-live="${escapeAttr(JSON.stringify(live2))}">`);
|
|
3562
|
+
lines.push(`${indent(depth + 1)}<g data-vector-part="appearance">`);
|
|
3563
|
+
for (const shape of vectorAppearanceNodes(node)) emit2(shape, depth + 2);
|
|
3564
|
+
lines.push(`${indent(depth + 1)}</g>`);
|
|
3565
|
+
lines.push(`${indent(depth + 1)}<g data-vector-part="source" display="none">`);
|
|
3566
|
+
if (node.type === "path") {
|
|
3567
|
+
const { effects: _e, transform: _t, opacity: _o, visible: _v, booleanOperation: _b, blendStep: _s, ...source } = node;
|
|
3568
|
+
emit2({ ...source, id: `${node.id}~src` }, depth + 2);
|
|
3569
|
+
} else for (const child of node.children) emit2(child, depth + 2);
|
|
3570
|
+
lines.push(`${indent(depth + 1)}</g>`);
|
|
3571
|
+
lines.push(`${indent(depth)}</g>`);
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
1681
3574
|
if (node.type === "group") {
|
|
1682
3575
|
lines.push(`${indent(depth)}<g${common(node)}>`);
|
|
1683
|
-
for (const child of node.children)
|
|
3576
|
+
for (const child of node.children) emit2(child, depth + 1);
|
|
1684
3577
|
lines.push(`${indent(depth)}</g>`);
|
|
1685
3578
|
return;
|
|
1686
3579
|
}
|
|
1687
|
-
const fill = vectorPaintToSvg(node.fill, `${prefix}fill-${node.id}
|
|
1688
|
-
const stroke = vectorPaintToSvg(node.stroke, `${prefix}stroke-${node.id}
|
|
1689
|
-
|
|
1690
|
-
if (stroke.gradient) defs.push(gradientMarkup(stroke.gradient));
|
|
3580
|
+
const fill = vectorPaintToSvg(node.fill, `${prefix}fill-${node.id}`, { node, precision });
|
|
3581
|
+
const stroke = vectorPaintToSvg(node.stroke, `${prefix}stroke-${node.id}`, { node, role: "stroke", precision });
|
|
3582
|
+
defs.push(...paintMarkup(node.fill, fill.defs), ...paintMarkup(node.stroke, stroke.defs));
|
|
1691
3583
|
const d = vectorPathData(node, precision);
|
|
1692
3584
|
const corners2 = vectorCornersAttribute(node.subpaths, precision);
|
|
1693
3585
|
const live = corners2 ? ` data-vector-d="${bezierPathToSvg(subpathsToBezier(node.subpaths, { corners: false }), { precision })}" data-vector-corners="${corners2}"` : "";
|
|
1694
3586
|
const fillAttrs = ` fill="${escapeAttr(fill.value)}"${node.fillRule === "evenodd" ? ` fill-rule="evenodd"` : ""}`;
|
|
1695
3587
|
const lineAttrs = (width) => {
|
|
1696
|
-
let out2 = ` stroke="${escapeAttr(stroke.value)}" stroke-width="${
|
|
3588
|
+
let out2 = ` stroke="${escapeAttr(stroke.value)}" stroke-width="${num2(width)}"`;
|
|
1697
3589
|
if (node.strokeLinecap && node.strokeLinecap !== "butt") out2 += ` stroke-linecap="${node.strokeLinecap}"`;
|
|
1698
3590
|
if (node.strokeLinejoin && node.strokeLinejoin !== "miter") out2 += ` stroke-linejoin="${node.strokeLinejoin}"`;
|
|
1699
|
-
if (node.strokeDasharray?.length) out2 += ` stroke-dasharray="${node.strokeDasharray.map(
|
|
3591
|
+
if (node.strokeDasharray?.length) out2 += ` stroke-dasharray="${node.strokeDasharray.map(num2).join(" ")}"`;
|
|
1700
3592
|
return out2;
|
|
1701
3593
|
};
|
|
1702
3594
|
const parts = stroke.value !== "none" ? vectorStrokeSvgParts(node, `${prefix}clip-${node.id}`, precision) : void 0;
|
|
@@ -1704,19 +3596,19 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
1704
3596
|
lines.push(`${indent(depth)}<path${common(node)} d="${d}"${fillAttrs}${stroke.value !== "none" ? lineAttrs(node.strokeWidth ?? 1) : ""}${live}/>`);
|
|
1705
3597
|
return;
|
|
1706
3598
|
}
|
|
1707
|
-
let data = ` data-vector-path="" data-vector-stroke-width="${
|
|
3599
|
+
let data = ` data-vector-path="" data-vector-stroke-width="${num2(node.strokeWidth ?? 1)}"`;
|
|
1708
3600
|
if (parts.align !== "center") data += ` data-vector-stroke-align="${parts.align}"`;
|
|
1709
3601
|
if (node.startArrow) data += ` data-vector-start-arrow="${node.startArrow}"`;
|
|
1710
3602
|
if (node.endArrow) data += ` data-vector-end-arrow="${node.endArrow}"`;
|
|
1711
3603
|
if (node.arrowAlign && node.arrowAlign !== "extend") data += ` data-vector-arrow-align="${node.arrowAlign}"`;
|
|
1712
|
-
if (node.startArrowScale !== void 0 && node.startArrowScale !== 1) data += ` data-vector-start-arrow-scale="${
|
|
1713
|
-
if (node.endArrowScale !== void 0 && node.endArrowScale !== 1) data += ` data-vector-end-arrow-scale="${
|
|
3604
|
+
if (node.startArrowScale !== void 0 && node.startArrowScale !== 1) data += ` data-vector-start-arrow-scale="${num2(node.startArrowScale)}"`;
|
|
3605
|
+
if (node.endArrowScale !== void 0 && node.endArrowScale !== 1) data += ` data-vector-end-arrow-scale="${num2(node.endArrowScale)}"`;
|
|
1714
3606
|
const profile = strokeWidthProfileOf(node);
|
|
1715
3607
|
if (profile) {
|
|
1716
3608
|
data += ` data-vector-width-profile="${widthProfileToAttribute(profile)}"`;
|
|
1717
3609
|
if (node.strokeLinecap && node.strokeLinecap !== "butt") data += ` data-vector-stroke-linecap="${node.strokeLinecap}"`;
|
|
1718
3610
|
if (node.strokeLinejoin && node.strokeLinejoin !== "miter") data += ` data-vector-stroke-linejoin="${node.strokeLinejoin}"`;
|
|
1719
|
-
if (node.strokeDasharray?.length) data += ` data-vector-stroke-dasharray="${node.strokeDasharray.map(
|
|
3611
|
+
if (node.strokeDasharray?.length) data += ` data-vector-stroke-dasharray="${node.strokeDasharray.map(num2).join(" ")}"`;
|
|
1720
3612
|
}
|
|
1721
3613
|
lines.push(`${indent(depth)}<g${common(node)}${data}>`);
|
|
1722
3614
|
lines.push(`${indent(depth + 1)}<path data-vector-part="fill" d="${d}"${fillAttrs}${live}/>`);
|
|
@@ -1733,7 +3625,7 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
1733
3625
|
clip = ` clip-path="url(#${id})"`;
|
|
1734
3626
|
} else {
|
|
1735
3627
|
const b = parts.clip.bounds;
|
|
1736
|
-
const box = `x="${
|
|
3628
|
+
const box = `x="${num2(b.x)}" y="${num2(b.y)}" width="${num2(b.width)}" height="${num2(b.height)}"`;
|
|
1737
3629
|
defs.push(`<mask id="${id}" maskUnits="userSpaceOnUse" ${box}><rect ${box} fill="#fff"/><path d="${d}" fill="#000" fill-rule="${rule}"/></mask>`);
|
|
1738
3630
|
clip = ` mask="url(#${id})"`;
|
|
1739
3631
|
}
|
|
@@ -1744,9 +3636,9 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
1744
3636
|
}
|
|
1745
3637
|
lines.push(`${indent(depth)}</g>`);
|
|
1746
3638
|
};
|
|
1747
|
-
for (const node of doc.nodes)
|
|
1748
|
-
const w =
|
|
1749
|
-
const h =
|
|
3639
|
+
for (const node of doc.nodes) emit2(node, 1);
|
|
3640
|
+
const w = num2(doc.width);
|
|
3641
|
+
const h = num2(doc.height);
|
|
1750
3642
|
const nl = pretty ? "\n" : "";
|
|
1751
3643
|
const out = [`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`];
|
|
1752
3644
|
if (defs.length) out.push(`${indent(1)}<defs>${defs.join("")}</defs>`);
|
|
@@ -1756,6 +3648,7 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
1756
3648
|
out.push(...lines, "</svg>");
|
|
1757
3649
|
return out.join(nl);
|
|
1758
3650
|
}
|
|
3651
|
+
var BOOLEAN_OPERATIONS = ["union", "subtract", "intersect", "exclude"];
|
|
1759
3652
|
function parseXml(source) {
|
|
1760
3653
|
const text = source.replace(/<!--[\s\S]*?-->/g, "").replace(/<\?[\s\S]*?\?>/g, "").replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, "").replace(/<!DOCTYPE[^>[]*(\[[\s\S]*?\])?\s*>/gi, "");
|
|
1761
3654
|
const root = { name: "#root", attrs: {}, children: [] };
|
|
@@ -1875,28 +3768,89 @@ var fraction = (s, fallback) => {
|
|
|
1875
3768
|
if (!Number.isFinite(n)) return fallback;
|
|
1876
3769
|
return s.trim().endsWith("%") ? n / 100 : n;
|
|
1877
3770
|
};
|
|
3771
|
+
var PAINT_SERVERS = /* @__PURE__ */ new Set(["linearGradient", "radialGradient", "pattern"]);
|
|
1878
3772
|
function collectGradients(el, raw) {
|
|
1879
|
-
if ((el.name
|
|
3773
|
+
if (PAINT_SERVERS.has(el.name) && el.attrs.id) raw.set(el.attrs.id, el);
|
|
1880
3774
|
for (const c of el.children) collectGradients(c, raw);
|
|
1881
3775
|
}
|
|
1882
|
-
function
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
if (!stops.length && href?.startsWith("#")) stops = resolveGradient(href.slice(1), raw, seen)?.stops ?? [];
|
|
1889
|
-
if (!stops.length) return null;
|
|
1890
|
-
if (el.name === "radialGradient") {
|
|
1891
|
-
return { type: "radial", cx: fraction(el.attrs.cx, 0.5), cy: fraction(el.attrs.cy, 0.5), r: fraction(el.attrs.r, 0.5), stops };
|
|
3776
|
+
function gradientFromAttribute(value) {
|
|
3777
|
+
if (!value) return null;
|
|
3778
|
+
try {
|
|
3779
|
+
return sanitizeVectorGradient(JSON.parse(value));
|
|
3780
|
+
} catch {
|
|
3781
|
+
return null;
|
|
1892
3782
|
}
|
|
1893
|
-
const x1 = fraction(el.attrs.x1, 0);
|
|
1894
|
-
const y1 = fraction(el.attrs.y1, 0);
|
|
1895
|
-
const x2 = fraction(el.attrs.x2, 1);
|
|
1896
|
-
const y2 = fraction(el.attrs.y2, 0);
|
|
1897
|
-
const angle = Math.round((Math.atan2(x2 - x1, -(y2 - y1)) * 180 / Math.PI + 360) % 360);
|
|
1898
|
-
return { type: "linear", angle, stops };
|
|
1899
3783
|
}
|
|
3784
|
+
function resolveGradient(id, raw) {
|
|
3785
|
+
const first = raw.get(id);
|
|
3786
|
+
if (!first) return null;
|
|
3787
|
+
const own = gradientFromAttribute(first.attrs["data-vector-gradient"]);
|
|
3788
|
+
if (own) return own;
|
|
3789
|
+
if (first.name === "pattern") return null;
|
|
3790
|
+
const chain = [];
|
|
3791
|
+
for (let el = first; el && !chain.includes(el) && chain.length < 32; ) {
|
|
3792
|
+
chain.push(el);
|
|
3793
|
+
const href = el.attrs.href ?? el.attrs["xlink:href"];
|
|
3794
|
+
el = href?.startsWith("#") ? raw.get(href.slice(1)) : void 0;
|
|
3795
|
+
}
|
|
3796
|
+
const attr = (name) => chain.find((el) => el.attrs[name] !== void 0)?.attrs[name];
|
|
3797
|
+
const stops = chain.map(readStops).find((s) => s.length) ?? [];
|
|
3798
|
+
if (!stops.length) return null;
|
|
3799
|
+
const user = attr("gradientUnits") === "userSpaceOnUse";
|
|
3800
|
+
const t = parseSvgTransform(attr("gradientTransform"));
|
|
3801
|
+
const plain3 = t === IDENTITY_MATRIX || t[0] === 1 && t[1] === 0 && t[2] === 0 && t[3] === 1 && t[4] === 0 && t[5] === 0;
|
|
3802
|
+
const at2 = (name, fallback) => user ? length(attr(name), fallback) : fraction(attr(name), fallback);
|
|
3803
|
+
const spreadAttr = attr("spreadMethod");
|
|
3804
|
+
const spread = spreadAttr === "reflect" || spreadAttr === "repeat" ? { spread: spreadAttr } : {};
|
|
3805
|
+
const map = (p) => applySvgMatrix(t, p);
|
|
3806
|
+
if (first.name === "radialGradient") {
|
|
3807
|
+
const cx = at2("cx", user ? 0 : 0.5);
|
|
3808
|
+
const cy = at2("cy", user ? 0 : 0.5);
|
|
3809
|
+
const r = at2("r", user ? 0 : 0.5);
|
|
3810
|
+
const fx = attr("fx") !== void 0 ? at2("fx", cx) : cx;
|
|
3811
|
+
const fy = attr("fy") !== void 0 ? at2("fy", cy) : cy;
|
|
3812
|
+
const fr = attr("fr") !== void 0 ? at2("fr", 0) : 0;
|
|
3813
|
+
const focal = Math.abs(fx - cx) > 1e-9 || Math.abs(fy - cy) > 1e-9;
|
|
3814
|
+
if (!user && plain3 && !focal && !(fr > 0)) return { type: "radial", cx, cy, r, stops, ...spread };
|
|
3815
|
+
const g = {
|
|
3816
|
+
type: "radial",
|
|
3817
|
+
stops,
|
|
3818
|
+
start: map({ x: cx, y: cy }),
|
|
3819
|
+
end: map({ x: cx + r, y: cy }),
|
|
3820
|
+
width: map({ x: cx, y: cy + r }),
|
|
3821
|
+
units: user ? "userSpaceOnUse" : "objectBoundingBox",
|
|
3822
|
+
...spread
|
|
3823
|
+
};
|
|
3824
|
+
if (focal) g.focal = map({ x: fx, y: fy });
|
|
3825
|
+
if (fr > 0 && r > 0) g.focalRadius = Math.min(0.99, fr / r);
|
|
3826
|
+
return g;
|
|
3827
|
+
}
|
|
3828
|
+
const x1 = at2("x1", 0);
|
|
3829
|
+
const y1 = at2("y1", 0);
|
|
3830
|
+
const x2 = attr("x2") !== void 0 ? at2("x2", 1) : user ? 0 : 1;
|
|
3831
|
+
const y2 = at2("y2", 0);
|
|
3832
|
+
if (!user && plain3) {
|
|
3833
|
+
const angle = Math.round((Math.atan2(x2 - x1, -(y2 - y1)) * 180 / Math.PI + 360) % 360);
|
|
3834
|
+
const c = angleToSvgLinearCoords(angle);
|
|
3835
|
+
const len2 = Math.hypot(x2 - x1, y2 - y1);
|
|
3836
|
+
const d = len2 > 0 ? { x: (x2 - x1) / len2, y: (y2 - y1) / len2 } : { x: 0, y: 1 };
|
|
3837
|
+
const along2 = (x, y) => x * d.x + y * d.y;
|
|
3838
|
+
const same = len2 > 0 && Math.abs((c.x2 - c.x1) * d.y - (c.y2 - c.y1) * d.x) < 1e-3 && Math.abs(along2(c.x1, c.y1) - along2(x1, y1)) < 1e-3 && Math.abs(along2(c.x2, c.y2) - along2(x2, y2)) < 1e-3;
|
|
3839
|
+
if (same) return { type: "linear", angle, stops, ...spread };
|
|
3840
|
+
}
|
|
3841
|
+
const start = { x: x1, y: y1 };
|
|
3842
|
+
const end = { x: x2, y: y2 };
|
|
3843
|
+
return {
|
|
3844
|
+
type: "linear",
|
|
3845
|
+
stops,
|
|
3846
|
+
start: map(start),
|
|
3847
|
+
end: map(end),
|
|
3848
|
+
width: map({ x: x1 - (y2 - y1), y: y1 + (x2 - x1) }),
|
|
3849
|
+
units: user ? "userSpaceOnUse" : "objectBoundingBox",
|
|
3850
|
+
...spread
|
|
3851
|
+
};
|
|
3852
|
+
}
|
|
3853
|
+
var applySvgMatrix = (m, p) => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] });
|
|
1900
3854
|
function parseVectorSvg(svg, options = {}) {
|
|
1901
3855
|
const root = parseXml(svg);
|
|
1902
3856
|
if (!root) throw new Error("The markup has no <svg> element.");
|
|
@@ -1997,7 +3951,7 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
1997
3951
|
const ss = strokeEl ? readStyle(strokeEl, style) : null;
|
|
1998
3952
|
const width2 = length(a["data-vector-stroke-width"], ss ? length(ss.strokeWidth, 1) : 1);
|
|
1999
3953
|
const arrow = (v2) => VECTOR_ARROWHEADS.includes(v2) ? v2 : void 0;
|
|
2000
|
-
const
|
|
3954
|
+
const scale3 = (v2) => {
|
|
2001
3955
|
const n = v2 === void 0 ? NaN : parseFloat(v2);
|
|
2002
3956
|
return n > 0 ? n : void 0;
|
|
2003
3957
|
};
|
|
@@ -2021,8 +3975,8 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
2021
3975
|
...arrow(a["data-vector-start-arrow"]) ? { startArrow: arrow(a["data-vector-start-arrow"]) } : {},
|
|
2022
3976
|
...arrow(a["data-vector-end-arrow"]) ? { endArrow: arrow(a["data-vector-end-arrow"]) } : {},
|
|
2023
3977
|
...a["data-vector-arrow-align"] === "tip" ? { arrowAlign: "tip" } : {},
|
|
2024
|
-
...
|
|
2025
|
-
...
|
|
3978
|
+
...scale3(a["data-vector-start-arrow-scale"]) ? { startArrowScale: scale3(a["data-vector-start-arrow-scale"]) } : {},
|
|
3979
|
+
...scale3(a["data-vector-end-arrow-scale"]) ? { endArrowScale: scale3(a["data-vector-end-arrow-scale"]) } : {},
|
|
2026
3980
|
...profile.length ? { strokeWidthProfile: profile } : {},
|
|
2027
3981
|
...keptCap ? { strokeLinecap: keptCap } : {},
|
|
2028
3982
|
...keptJoin ? { strokeLinejoin: keptJoin } : {},
|
|
@@ -2030,6 +3984,34 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
2030
3984
|
};
|
|
2031
3985
|
return placed(node, m);
|
|
2032
3986
|
};
|
|
3987
|
+
const liveNode = (g, style, m, meta) => {
|
|
3988
|
+
let raw = {};
|
|
3989
|
+
try {
|
|
3990
|
+
raw = JSON.parse(g.attrs["data-vector-live"]) ?? {};
|
|
3991
|
+
} catch {
|
|
3992
|
+
raw = {};
|
|
3993
|
+
}
|
|
3994
|
+
const sourceEl = g.children.find((c) => c.name === "g" && c.attrs["data-vector-part"] === "source");
|
|
3995
|
+
if (!sourceEl) return null;
|
|
3996
|
+
const inner = visit(sourceEl, style, m);
|
|
3997
|
+
const effects = sanitizeVectorEffects(raw.effects);
|
|
3998
|
+
const liveBoolean = BOOLEAN_OPERATIONS.includes(raw.liveBoolean) ? raw.liveBoolean : null;
|
|
3999
|
+
const id = idFor(g);
|
|
4000
|
+
if (raw.kind === "path") {
|
|
4001
|
+
const path = inner.find((n) => n.type === "path");
|
|
4002
|
+
if (!path) return null;
|
|
4003
|
+
const { residual } = decomposeRigid(m);
|
|
4004
|
+
const { visible: _v, ...rest } = path;
|
|
4005
|
+
return { ...rest, id, ...meta, ...effects.length ? { effects: transformVectorEffects(effects, residual) } : {} };
|
|
4006
|
+
}
|
|
4007
|
+
if (!inner.length) return null;
|
|
4008
|
+
const group = { id, type: "group", ...meta, children: inner };
|
|
4009
|
+
if (effects.length) group.effects = transformVectorEffects(effects, m);
|
|
4010
|
+
if (liveBoolean) group.liveBoolean = liveBoolean;
|
|
4011
|
+
const blend = blendFromAttribute(g.attrs["data-vector-blend"], m);
|
|
4012
|
+
if (blend) group.blend = blend;
|
|
4013
|
+
return group;
|
|
4014
|
+
};
|
|
2033
4015
|
const visit = (el, style, matrix) => {
|
|
2034
4016
|
const out = [];
|
|
2035
4017
|
for (const child of el.children) {
|
|
@@ -2044,6 +4026,13 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
2044
4026
|
...hidden ? { visible: false } : {}
|
|
2045
4027
|
};
|
|
2046
4028
|
if (child.attrs["data-vector-blend-step"] !== void 0) continue;
|
|
4029
|
+
const operation = child.attrs["data-vector-boolean-operation"];
|
|
4030
|
+
if (BOOLEAN_OPERATIONS.includes(operation)) meta.booleanOperation = operation;
|
|
4031
|
+
if (child.name === "g" && child.attrs["data-vector-live"] !== void 0) {
|
|
4032
|
+
const node2 = liveNode(child, s, m, meta);
|
|
4033
|
+
if (node2) out.push(node2);
|
|
4034
|
+
continue;
|
|
4035
|
+
}
|
|
2047
4036
|
if (child.name === "g" && child.attrs["data-vector-path"] !== void 0) {
|
|
2048
4037
|
const node2 = mergedPath(child, s, m, meta);
|
|
2049
4038
|
if (node2) out.push(node2);
|
|
@@ -2080,6 +4069,72 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
2080
4069
|
return { width, height, nodes };
|
|
2081
4070
|
}
|
|
2082
4071
|
|
|
4072
|
+
// src/vector/gradientTool.ts
|
|
4073
|
+
var VECTOR_GRADIENT_STOP_GAP = 12;
|
|
4074
|
+
var VECTOR_GRADIENT_MIDPOINT_GAP = 9;
|
|
4075
|
+
var VECTOR_GRADIENT_OFF_BAR = 24;
|
|
4076
|
+
var STOP_HIT = 6;
|
|
4077
|
+
var MIDPOINT_HIT = 5;
|
|
4078
|
+
var HANDLE_HIT = 6;
|
|
4079
|
+
var BAR_HIT = 5;
|
|
4080
|
+
var fmt3 = (n) => Math.round(n * 100) / 100;
|
|
4081
|
+
var apply2 = (m, p) => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] });
|
|
4082
|
+
function vectorGradientTargetPath(doc, selection, target) {
|
|
4083
|
+
for (const id of selection) {
|
|
4084
|
+
const node = findVectorNode(doc.nodes, id);
|
|
4085
|
+
if (!node) continue;
|
|
4086
|
+
const path = collectPathNodes(node).find((p) => isVectorGradient(p[target]));
|
|
4087
|
+
if (path) return path;
|
|
4088
|
+
}
|
|
4089
|
+
return null;
|
|
4090
|
+
}
|
|
4091
|
+
function getVectorGradientAnnotator(doc, selection, viewport, target) {
|
|
4092
|
+
const path = vectorGradientTargetPath(doc, selection, target);
|
|
4093
|
+
if (!path) return null;
|
|
4094
|
+
const gradient = path[target];
|
|
4095
|
+
const world = vectorWorldMatrix(doc.nodes, path.id) ?? IDENTITY_MATRIX;
|
|
4096
|
+
const screen = multiplyMatrices(world, [viewport.zoom, 0, 0, viewport.zoom, viewport.x, viewport.y]);
|
|
4097
|
+
const bounds = vectorPathLocalBounds(path);
|
|
4098
|
+
const h = vectorGradientHandles(gradient, bounds, screen);
|
|
4099
|
+
const v2 = { x: h.end.x - h.start.x, y: h.end.y - h.start.y };
|
|
4100
|
+
const len2 = Math.hypot(v2.x, v2.y);
|
|
4101
|
+
const normal = len2 > 1e-9 ? { x: -v2.y / len2, y: v2.x / len2 } : { x: 0, y: 1 };
|
|
4102
|
+
const at2 = (t, gap) => ({ x: h.start.x + v2.x * t + normal.x * gap, y: h.start.y + v2.y * t + normal.y * gap });
|
|
4103
|
+
const sorted = sortedVectorGradientStops(gradient);
|
|
4104
|
+
const stops = sorted.map((s, index) => ({ ...s, index, ...at2(s.offset, VECTOR_GRADIENT_STOP_GAP) }));
|
|
4105
|
+
const midpoints = sorted.slice(0, -1).flatMap((s, index) => {
|
|
4106
|
+
const pos = vectorGradientMidpointPosition(sorted, index);
|
|
4107
|
+
return pos === null || sorted[index + 1].offset - s.offset < 1e-6 ? [] : [{ index, midpoint: s.midpoint ?? 0.5, ...at2(pos, -VECTOR_GRADIENT_MIDPOINT_GAP) }];
|
|
4108
|
+
});
|
|
4109
|
+
let extent = null;
|
|
4110
|
+
if (isCentredVectorGradient(gradient.type)) {
|
|
4111
|
+
const frame2 = multiplyMatrices(vectorGradientMatrix(gradient, bounds), screen);
|
|
4112
|
+
const pts = gradient.type === "diamond" ? [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 0, y: -1 }] : Array.from({ length: 48 }, (_, k) => ({ x: Math.cos(k * Math.PI / 24), y: Math.sin(k * Math.PI / 24) }));
|
|
4113
|
+
extent = `M${pts.map((p) => apply2(frame2, p)).map((p) => `${fmt3(p.x)} ${fmt3(p.y)}`).join("L")}Z`;
|
|
4114
|
+
}
|
|
4115
|
+
return { nodeId: path.id, target, gradient, bounds, world, start: h.start, end: h.end, aspect: h.aspect, focal: h.focal, normal, stops, midpoints, extent };
|
|
4116
|
+
}
|
|
4117
|
+
function vectorGradientBarPosition(a, p) {
|
|
4118
|
+
const v2 = { x: a.end.x - a.start.x, y: a.end.y - a.start.y };
|
|
4119
|
+
const l2 = v2.x * v2.x + v2.y * v2.y;
|
|
4120
|
+
const d = { x: p.x - a.start.x, y: p.y - a.start.y };
|
|
4121
|
+
if (l2 < 1e-12) return { offset: 0, across: Math.hypot(d.x, d.y) };
|
|
4122
|
+
return { offset: (d.x * v2.x + d.y * v2.y) / l2, across: d.x * a.normal.x + d.y * a.normal.y };
|
|
4123
|
+
}
|
|
4124
|
+
function hitVectorGradientAnnotator(a, p) {
|
|
4125
|
+
const near2 = (q, r) => !!q && Math.hypot(q.x - p.x, q.y - p.y) <= r;
|
|
4126
|
+
for (let i = a.stops.length - 1; i >= 0; i--) if (near2(a.stops[i], STOP_HIT)) return { kind: "stop", index: a.stops[i].index };
|
|
4127
|
+
for (const m of a.midpoints) if (near2(m, MIDPOINT_HIT)) return { kind: "midpoint", index: m.index };
|
|
4128
|
+
const centred = isCentredVectorGradient(a.gradient.type);
|
|
4129
|
+
if (near2(a.end, HANDLE_HIT)) return { kind: "handle", handle: centred ? "radius" : "end" };
|
|
4130
|
+
if (a.focal && near2(a.focal, HANDLE_HIT) && !near2(a.start, 1)) return { kind: "handle", handle: "focal" };
|
|
4131
|
+
if (near2(a.start, HANDLE_HIT)) return { kind: "handle", handle: "start" };
|
|
4132
|
+
if (near2(a.aspect, HANDLE_HIT)) return { kind: "handle", handle: "aspect" };
|
|
4133
|
+
const { offset, across } = vectorGradientBarPosition(a, p);
|
|
4134
|
+
if (offset >= 0 && offset <= 1 && across >= -BAR_HIT && across <= VECTOR_GRADIENT_STOP_GAP + STOP_HIT) return { kind: "bar", offset };
|
|
4135
|
+
return null;
|
|
4136
|
+
}
|
|
4137
|
+
|
|
2083
4138
|
// src/vector/blend.ts
|
|
2084
4139
|
var lerpN = (a, b, t) => a + (b - a) * t;
|
|
2085
4140
|
var lerpP = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
@@ -2171,10 +4226,10 @@ function refineMorphContour(c, segments) {
|
|
|
2171
4226
|
const extra = segments - have;
|
|
2172
4227
|
const want = lens.map((l) => sum > 1e-12 ? l / sum * extra : extra / have);
|
|
2173
4228
|
const parts = want.map((w) => 1 + Math.floor(w));
|
|
2174
|
-
let
|
|
4229
|
+
let left2 = segments - parts.reduce((a, b) => a + b, 0);
|
|
2175
4230
|
const frac = want.map((w) => w - Math.floor(w));
|
|
2176
4231
|
const order = parts.map((_, i) => i).sort((a, b) => Math.abs(frac[b] - frac[a]) > 1e-9 ? frac[b] - frac[a] : a - b);
|
|
2177
|
-
for (let i = 0;
|
|
4232
|
+
for (let i = 0; left2 > 0; i = (i + 1) % order.length, left2--) parts[order[i]]++;
|
|
2178
4233
|
const pts = [c.pts[0]];
|
|
2179
4234
|
const anchors = [0];
|
|
2180
4235
|
let at2 = 0;
|
|
@@ -2283,19 +4338,19 @@ function bestStart(fixed, free, useShapeContext) {
|
|
|
2283
4338
|
const gsc = useShapeContext ? shapeContexts(ga, free.closed) : null;
|
|
2284
4339
|
const m = Math.max(1, fn.length);
|
|
2285
4340
|
const cost = (s) => {
|
|
2286
|
-
let
|
|
4341
|
+
let dot3 = 0;
|
|
2287
4342
|
let cross2 = 0;
|
|
2288
4343
|
let sc = 0;
|
|
2289
4344
|
for (let i = 0; i < fn.length; i++) {
|
|
2290
4345
|
const j = free.closed ? (i + s) % n : i;
|
|
2291
4346
|
const p = fn[i];
|
|
2292
4347
|
const q = gn[j];
|
|
2293
|
-
|
|
4348
|
+
dot3 += p[0] * q[0] + p[1] * q[1];
|
|
2294
4349
|
cross2 += p[0] * q[1] - p[1] * q[0];
|
|
2295
4350
|
if (fsc && gsc) sc += chi2(fsc[i], gsc[j]);
|
|
2296
4351
|
}
|
|
2297
|
-
const residual = Math.max(0, 1 - (
|
|
2298
|
-
const turn = 1 - Math.cos(Math.atan2(cross2,
|
|
4352
|
+
const residual = Math.max(0, 1 - (dot3 * dot3 + cross2 * cross2) / (m * m));
|
|
4353
|
+
const turn = 1 - Math.cos(Math.atan2(cross2, dot3));
|
|
2299
4354
|
return residual + TURN_WEIGHT * turn + sc / m;
|
|
2300
4355
|
};
|
|
2301
4356
|
if (!free.closed) return { start: 0, cost: cost(0) };
|
|
@@ -2440,16 +4495,16 @@ function procrustes(a, b) {
|
|
|
2440
4495
|
const ca = frame(pa).c;
|
|
2441
4496
|
const cb = frame(pb).c;
|
|
2442
4497
|
let ss = 0;
|
|
2443
|
-
let
|
|
4498
|
+
let dot3 = 0;
|
|
2444
4499
|
let cross2 = 0;
|
|
2445
4500
|
for (let i = 0; i < Math.min(pa.length, pb.length); i++) {
|
|
2446
4501
|
const p = [pa[i][0] - ca[0], pa[i][1] - ca[1]];
|
|
2447
4502
|
const q = [pb[i][0] - cb[0], pb[i][1] - cb[1]];
|
|
2448
4503
|
ss += p[0] * p[0] + p[1] * p[1];
|
|
2449
|
-
|
|
4504
|
+
dot3 += p[0] * q[0] + p[1] * q[1];
|
|
2450
4505
|
cross2 += p[0] * q[1] - p[1] * q[0];
|
|
2451
4506
|
}
|
|
2452
|
-
return { ca, cb, theta: Math.atan2(cross2,
|
|
4507
|
+
return { ca, cb, theta: Math.atan2(cross2, dot3), s: ss > 1e-12 ? Math.hypot(dot3, cross2) / ss : 1 };
|
|
2453
4508
|
}
|
|
2454
4509
|
function interpolateMorphContours(a, b, u, options = {}) {
|
|
2455
4510
|
const w = options.linear ? 0 : Math.min(1, Math.max(0, options.rigidity ?? 1));
|
|
@@ -2503,39 +4558,24 @@ function interpolateVectorColor(a, b, t) {
|
|
|
2503
4558
|
if (t >= 1) return b;
|
|
2504
4559
|
return mixColorsOklab(a, b, t) ?? (t < 0.5 ? a : b);
|
|
2505
4560
|
}
|
|
2506
|
-
function sortedStops(g) {
|
|
2507
|
-
return g.stops.slice().sort((x, y) => x.offset - y.offset);
|
|
2508
|
-
}
|
|
2509
|
-
function sampleGradient(stops, offset) {
|
|
2510
|
-
if (!stops.length) return { color: "transparent", opacity: 1 };
|
|
2511
|
-
const first = stops[0];
|
|
2512
|
-
const last = stops[stops.length - 1];
|
|
2513
|
-
if (offset <= first.offset) return { color: first.color, opacity: first.opacity ?? 1 };
|
|
2514
|
-
if (offset >= last.offset) return { color: last.color, opacity: last.opacity ?? 1 };
|
|
2515
|
-
let i = 0;
|
|
2516
|
-
while (i + 1 < stops.length && stops[i + 1].offset < offset) i++;
|
|
2517
|
-
const x = stops[i];
|
|
2518
|
-
const y = stops[i + 1];
|
|
2519
|
-
const u = y.offset - x.offset > 1e-12 ? (offset - x.offset) / (y.offset - x.offset) : 1;
|
|
2520
|
-
return { color: interpolateVectorColor(x.color, y.color, u), opacity: lerpN(x.opacity ?? 1, y.opacity ?? 1, u) };
|
|
2521
|
-
}
|
|
2522
4561
|
function resampleGradientStops(g, offsets) {
|
|
2523
|
-
const stops = sortedStops(g);
|
|
2524
4562
|
return offsets.map((offset) => {
|
|
2525
|
-
const s =
|
|
2526
|
-
return s.opacity < 1 ? { offset, color: s.color, opacity: s.opacity } : { offset, color: s.color };
|
|
4563
|
+
const s = sampleVectorGradient(g, offset);
|
|
4564
|
+
return s.opacity < 1 ? { offset, color: s.color, opacity: Math.round(s.opacity * 1e4) / 1e4 } : { offset, color: s.color };
|
|
2527
4565
|
});
|
|
2528
4566
|
}
|
|
2529
4567
|
function lerpAngle(a, b, t) {
|
|
2530
4568
|
const d = ((b - a + 180) % 360 + 360) % 360 - 180;
|
|
2531
4569
|
return a + d * t;
|
|
2532
4570
|
}
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
let
|
|
2537
|
-
|
|
2538
|
-
|
|
4571
|
+
var lerpPoint = (a, b, t) => ({ x: lerpN(a.x, b.x, t), y: lerpN(a.y, b.y, t) });
|
|
4572
|
+
var sameColourModel = (a, b) => (a.interpolation ?? "srgb") === (b.interpolation ?? "srgb") && (a.smoothness ?? 0) === (b.smoothness ?? 0);
|
|
4573
|
+
function lerpStops(a, b, t) {
|
|
4574
|
+
let stopsA = sortedVectorGradientStops(a);
|
|
4575
|
+
let stopsB = sortedVectorGradientStops(b);
|
|
4576
|
+
const resampled = stopsA.length !== stopsB.length || !sameColourModel(a, b);
|
|
4577
|
+
if (resampled) {
|
|
4578
|
+
const offs = [...expandVectorGradientStops(a), ...expandVectorGradientStops(b), ...stopsA, ...stopsB].map((s) => s.offset).filter(Number.isFinite).sort((x, y) => x - y);
|
|
2539
4579
|
const common = offs.filter((o, i) => i === 0 || o - offs[i - 1] > 1e-4);
|
|
2540
4580
|
stopsA = resampleGradientStops(a, common);
|
|
2541
4581
|
stopsB = resampleGradientStops(b, common);
|
|
@@ -2545,12 +4585,53 @@ function lerpGradient(a, b, t) {
|
|
|
2545
4585
|
const opacity = lerpN(x.opacity ?? 1, y.opacity ?? 1, t);
|
|
2546
4586
|
const stop = { offset: lerpN(x.offset, y.offset, t), color: interpolateVectorColor(x.color, y.color, t) };
|
|
2547
4587
|
if (opacity < 1) stop.opacity = opacity;
|
|
4588
|
+
const mid = lerpN(x.midpoint ?? 0.5, y.midpoint ?? 0.5, t);
|
|
4589
|
+
if (Math.abs(mid - 0.5) > 1e-6) stop.midpoint = mid;
|
|
2548
4590
|
return stop;
|
|
2549
4591
|
});
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
4592
|
+
return { stops, resampled };
|
|
4593
|
+
}
|
|
4594
|
+
function lerpGradient(a, b, t) {
|
|
4595
|
+
if (a.type !== b.type) return null;
|
|
4596
|
+
const { stops, resampled } = lerpStops(a, b, t);
|
|
4597
|
+
const near2 = t < 0.5 ? a : b;
|
|
4598
|
+
const out = { type: a.type, stops };
|
|
4599
|
+
if (!resampled) {
|
|
4600
|
+
if (a.interpolation && a.interpolation !== "srgb") out.interpolation = a.interpolation;
|
|
4601
|
+
if (a.smoothness) out.smoothness = a.smoothness;
|
|
4602
|
+
}
|
|
4603
|
+
if (near2.spread && near2.spread !== "pad") out.spread = near2.spread;
|
|
4604
|
+
if (near2.strokeMode && near2.strokeMode !== "within") out.strokeMode = near2.strokeMode;
|
|
4605
|
+
const ea = hasExplicitGradientGeometry(a);
|
|
4606
|
+
const eb = hasExplicitGradientGeometry(b);
|
|
4607
|
+
if (!ea && !eb) {
|
|
4608
|
+
if (a.angle !== void 0 || b.angle !== void 0 || a.type === "linear" || a.type === "reflected") {
|
|
4609
|
+
const fallback = a.type === "linear" || a.type === "reflected" ? 180 : 90;
|
|
4610
|
+
out.angle = lerpAngle(a.angle ?? fallback, b.angle ?? fallback, t);
|
|
4611
|
+
}
|
|
4612
|
+
if (a.type !== "linear" && a.type !== "reflected") {
|
|
4613
|
+
out.cx = lerpN(a.cx ?? 0.5, b.cx ?? 0.5, t);
|
|
4614
|
+
out.cy = lerpN(a.cy ?? 0.5, b.cy ?? 0.5, t);
|
|
4615
|
+
out.r = lerpN(a.r ?? 0.5, b.r ?? 0.5, t);
|
|
4616
|
+
}
|
|
4617
|
+
return out;
|
|
4618
|
+
}
|
|
4619
|
+
const pa = vectorGradientPoints(a);
|
|
4620
|
+
const pb = vectorGradientPoints(b);
|
|
4621
|
+
if (pa.units !== pb.units) {
|
|
4622
|
+
const src = explicitVectorGradient(near2);
|
|
4623
|
+
return { ...src, stops: out.stops, ...out.interpolation ? { interpolation: out.interpolation } : {}, ...out.smoothness ? { smoothness: out.smoothness } : {} };
|
|
4624
|
+
}
|
|
4625
|
+
out.start = lerpPoint(pa.start, pb.start, t);
|
|
4626
|
+
out.end = lerpPoint(pa.end, pb.end, t);
|
|
4627
|
+
out.width = lerpPoint(pa.width, pb.width, t);
|
|
4628
|
+
out.units = pa.units;
|
|
4629
|
+
if (a.type === "radial") {
|
|
4630
|
+
if (pa.focal || pb.focal) out.focal = lerpPoint(pa.focal ?? pa.start, pb.focal ?? pb.start, t);
|
|
4631
|
+
const fr = lerpN(pa.focalRadius, pb.focalRadius, t);
|
|
4632
|
+
if (fr > 0) out.focalRadius = fr;
|
|
4633
|
+
}
|
|
4634
|
+
return out;
|
|
2554
4635
|
}
|
|
2555
4636
|
function solidLike(color, like) {
|
|
2556
4637
|
return { ...like, stops: like.stops.map((s) => color === null ? { offset: s.offset, color: s.color, opacity: 0 } : { offset: s.offset, color }) };
|
|
@@ -2564,8 +4645,8 @@ function interpolateVectorPaint(a, b, t) {
|
|
|
2564
4645
|
if (typeof a === "string" && pa && typeof b === "string" && pb) return interpolateVectorColor(a, b, t);
|
|
2565
4646
|
if (!pa && typeof b === "string") return interpolateVectorColor("transparent", b, t);
|
|
2566
4647
|
if (!pb && typeof a === "string") return interpolateVectorColor(a, "transparent", t);
|
|
2567
|
-
const ga = pa &&
|
|
2568
|
-
const gb = pb &&
|
|
4648
|
+
const ga = pa && isVectorGradient(a) ? a : null;
|
|
4649
|
+
const gb = pb && isVectorGradient(b) ? b : null;
|
|
2569
4650
|
if (ga && gb) return lerpGradient(ga, gb, t) ?? (t < 0.5 ? a : b);
|
|
2570
4651
|
if (gb) return lerpGradient(solidLike(pa ? a : null, gb), gb, t) ?? b;
|
|
2571
4652
|
if (ga) return lerpGradient(ga, solidLike(pb ? b : null, ga), t) ?? a;
|
|
@@ -2702,15 +4783,15 @@ function createVectorMorph(a, b, options = {}) {
|
|
|
2702
4783
|
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
2703
4784
|
const x = pa[i];
|
|
2704
4785
|
const y = pb[i];
|
|
2705
|
-
const
|
|
4786
|
+
const sub5 = { ...options, id: `${id}-${i}`, starts: void 0 };
|
|
2706
4787
|
if (x && y) {
|
|
2707
|
-
parts.push(preparePathMorph(x.path, y.path,
|
|
4788
|
+
parts.push(preparePathMorph(x.path, y.path, sub5, x.m, y.m));
|
|
2708
4789
|
opacities.push([x.opacity, y.opacity]);
|
|
2709
4790
|
} else if (x) {
|
|
2710
|
-
parts.push(preparePathMorph(x.path, collapsedPath(x.path, x.m, ba, bb),
|
|
4791
|
+
parts.push(preparePathMorph(x.path, collapsedPath(x.path, x.m, ba, bb), sub5, x.m, IDENTITY_MATRIX));
|
|
2711
4792
|
opacities.push([x.opacity, x.opacity]);
|
|
2712
4793
|
} else if (y) {
|
|
2713
|
-
parts.push(preparePathMorph(collapsedPath(y.path, y.m, bb, ba), y.path,
|
|
4794
|
+
parts.push(preparePathMorph(collapsedPath(y.path, y.m, bb, ba), y.path, sub5, IDENTITY_MATRIX, y.m));
|
|
2714
4795
|
opacities.push([y.opacity, y.opacity]);
|
|
2715
4796
|
}
|
|
2716
4797
|
}
|
|
@@ -3197,8 +5278,9 @@ var VectorSnapTargets = class _VectorSnapTargets {
|
|
|
3197
5278
|
let anchors = BUDGET;
|
|
3198
5279
|
let segments = BUDGET;
|
|
3199
5280
|
const visit = (list, parent) => {
|
|
3200
|
-
for (const
|
|
3201
|
-
if (exclude.has(
|
|
5281
|
+
for (const stored of list) {
|
|
5282
|
+
if (exclude.has(stored.id) || stored.visible === false) continue;
|
|
5283
|
+
const n = contains(stored, exclude) ? stored : expandVectorAppearance(stored);
|
|
3202
5284
|
const m = multiplyMatrices(nodeMatrix(n), parent);
|
|
3203
5285
|
if (n.type === "group") {
|
|
3204
5286
|
visit(n.children, m);
|
|
@@ -3694,16 +5776,16 @@ function solveOnSegment(seg, base, cond) {
|
|
|
3694
5776
|
return cond([p[0] - b[0], p[1] - b[1]], segmentDerivative(seg, t));
|
|
3695
5777
|
};
|
|
3696
5778
|
const vals = [];
|
|
3697
|
-
let
|
|
5779
|
+
let scale3 = 0;
|
|
3698
5780
|
for (let i = 0; i <= CURVE_SAMPLES; i++) {
|
|
3699
5781
|
const t = i / CURVE_SAMPLES;
|
|
3700
5782
|
const p = segmentPoint(seg, t);
|
|
3701
5783
|
const d = segmentDerivative(seg, t);
|
|
3702
5784
|
const r = [p[0] - b[0], p[1] - b[1]];
|
|
3703
5785
|
vals.push(cond(r, d));
|
|
3704
|
-
|
|
5786
|
+
scale3 = Math.max(scale3, Math.hypot(r[0], r[1]) * Math.hypot(d[0], d[1]));
|
|
3705
5787
|
}
|
|
3706
|
-
if (!(
|
|
5788
|
+
if (!(scale3 > 0) || vals.every((v2) => Math.abs(v2) <= scale3 * 1e-12)) return [];
|
|
3707
5789
|
const out = [];
|
|
3708
5790
|
for (let i = 0; i < CURVE_SAMPLES; i++) {
|
|
3709
5791
|
const ga = vals[i];
|
|
@@ -3736,8 +5818,8 @@ function solveOnSegment(seg, base, cond) {
|
|
|
3736
5818
|
}
|
|
3737
5819
|
|
|
3738
5820
|
// src/vector/pathEdit.ts
|
|
3739
|
-
var
|
|
3740
|
-
var
|
|
5821
|
+
var EPS4 = 1e-9;
|
|
5822
|
+
var unit4 = (x, y) => {
|
|
3741
5823
|
const l = Math.hypot(x, y);
|
|
3742
5824
|
return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
|
|
3743
5825
|
};
|
|
@@ -3748,16 +5830,16 @@ function cubicOf(sp, i) {
|
|
|
3748
5830
|
return [a, [a[0] + (b[0] - a[0]) / 3, a[1] + (b[1] - a[1]) / 3], [a[0] + 2 * (b[0] - a[0]) / 3, a[1] + 2 * (b[1] - a[1]) / 3], b];
|
|
3749
5831
|
}
|
|
3750
5832
|
var isLine = (sp, i) => subpathSegment(sp, i).length === 2;
|
|
3751
|
-
function
|
|
5833
|
+
function startTangent2(c) {
|
|
3752
5834
|
for (let k = 1; k < 4; k++) {
|
|
3753
|
-
const u =
|
|
5835
|
+
const u = unit4(c[k][0] - c[0][0], c[k][1] - c[0][1]);
|
|
3754
5836
|
if (u) return u;
|
|
3755
5837
|
}
|
|
3756
5838
|
return [1, 0];
|
|
3757
5839
|
}
|
|
3758
|
-
function
|
|
5840
|
+
function endTangent2(c) {
|
|
3759
5841
|
for (let k = 2; k >= 0; k--) {
|
|
3760
|
-
const u =
|
|
5842
|
+
const u = unit4(c[3][0] - c[k][0], c[3][1] - c[k][1]);
|
|
3761
5843
|
if (u) return u;
|
|
3762
5844
|
}
|
|
3763
5845
|
return [1, 0];
|
|
@@ -3765,7 +5847,7 @@ function endTangent(c) {
|
|
|
3765
5847
|
function sample(c, n, out, withStart) {
|
|
3766
5848
|
for (let i = withStart ? 0 : 1; i <= n; i++) out.push(segmentPoint(c, i / n));
|
|
3767
5849
|
}
|
|
3768
|
-
var onAnchor2 = (a, h) => !h || Math.abs(h.x - a.x) <
|
|
5850
|
+
var onAnchor2 = (a, h) => !h || Math.abs(h.x - a.x) < EPS4 && Math.abs(h.y - a.y) < EPS4;
|
|
3769
5851
|
function tidy(a, keepType = true) {
|
|
3770
5852
|
const next = cloneAnchor(a);
|
|
3771
5853
|
if (onAnchor2(next, next.in)) delete next.in;
|
|
@@ -3821,14 +5903,14 @@ function removeAnchorKeepingShape(sp, index) {
|
|
|
3821
5903
|
const pi = (index + n - 1) % n;
|
|
3822
5904
|
const ni = (index + 1) % n;
|
|
3823
5905
|
if (!(isLine(sp, pi) && isLine(sp, index))) {
|
|
3824
|
-
const
|
|
5906
|
+
const left2 = cubicOf(sp, pi);
|
|
3825
5907
|
const right = cubicOf(sp, index);
|
|
3826
5908
|
const N = 16;
|
|
3827
5909
|
const pts = [];
|
|
3828
|
-
sample(
|
|
5910
|
+
sample(left2, N, pts, true);
|
|
3829
5911
|
sample(right, N, pts, false);
|
|
3830
|
-
const t0 =
|
|
3831
|
-
const t1 =
|
|
5912
|
+
const t0 = startTangent2(left2);
|
|
5913
|
+
const t1 = endTangent2(right);
|
|
3832
5914
|
const { curve } = fitAcross(pts, N, t0, t1);
|
|
3833
5915
|
if ([curve.p1, curve.p2].every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]))) {
|
|
3834
5916
|
anchors[pi] = tidy({ ...anchors[pi], out: { x: curve.p1[0], y: curve.p1[1] } });
|
|
@@ -4029,7 +6111,7 @@ function smoothSubpath(sp, amount = 1, only) {
|
|
|
4029
6111
|
const prev = orig[(i + n - 1) % n];
|
|
4030
6112
|
const next = orig[(i + 1) % n];
|
|
4031
6113
|
const p = orig[i];
|
|
4032
|
-
const dir =
|
|
6114
|
+
const dir = unit4(next.x - prev.x, next.y - prev.y);
|
|
4033
6115
|
if (!dir) continue;
|
|
4034
6116
|
const li = dist2(p, prev) / 3;
|
|
4035
6117
|
const lo = dist2(p, next) / 3;
|
|
@@ -4095,13 +6177,13 @@ function mergePair(a, b, tol) {
|
|
|
4095
6177
|
}
|
|
4096
6178
|
const ca = a.line ? [a0, a0, a3, a3] : a.c;
|
|
4097
6179
|
const cb = b.line ? [a3, a3, b3, b3] : b.c;
|
|
4098
|
-
const ta =
|
|
4099
|
-
const tb =
|
|
6180
|
+
const ta = endTangent2(ca);
|
|
6181
|
+
const tb = startTangent2(cb);
|
|
4100
6182
|
if (ta[0] * tb[0] + ta[1] * tb[1] < 0.99) return null;
|
|
4101
6183
|
const pts = [];
|
|
4102
6184
|
sample(ca, 16, pts, true);
|
|
4103
6185
|
sample(cb, 16, pts, false);
|
|
4104
|
-
const { curve, error } = fitAcross(pts, 16,
|
|
6186
|
+
const { curve, error } = fitAcross(pts, 16, startTangent2(ca), endTangent2(cb));
|
|
4105
6187
|
return error <= tol ? { c: [curve.p0, curve.p1, curve.p2, curve.p3], line: false } : null;
|
|
4106
6188
|
}
|
|
4107
6189
|
function removeRedundantPoints(sp, tolerance) {
|
|
@@ -4164,9 +6246,9 @@ function facesOf(path, fillRule) {
|
|
|
4164
6246
|
function splitIntoFaces(path, fillRule = "nonzero") {
|
|
4165
6247
|
return facesOf(path, fillRule).map(faceToPath);
|
|
4166
6248
|
}
|
|
4167
|
-
function rootsInUnit(a, b, c, d,
|
|
6249
|
+
function rootsInUnit(a, b, c, d, scale3) {
|
|
4168
6250
|
const f = (t) => ((a * t + b) * t + c) * t + d;
|
|
4169
|
-
const tol = 1e-12 * Math.max(1,
|
|
6251
|
+
const tol = 1e-12 * Math.max(1, scale3);
|
|
4170
6252
|
const cuts = [0];
|
|
4171
6253
|
const qa = 3 * a;
|
|
4172
6254
|
const qb = 2 * b;
|
|
@@ -4223,9 +6305,9 @@ function lineHits(p, q, seg) {
|
|
|
4223
6305
|
coeffs = [-h0 + 3 * h1 - 3 * h2 + h3, 3 * h0 - 6 * h1 + 3 * h2, -3 * h0 + 3 * h1, h0];
|
|
4224
6306
|
}
|
|
4225
6307
|
if (coeffs.every((c) => Math.abs(c) < 1e-12 * Math.max(1, len2))) return [];
|
|
4226
|
-
const
|
|
6308
|
+
const scale3 = Math.sqrt(len2) * Math.max(1, ...seg.map((pt2) => Math.abs(pt2[0]) + Math.abs(pt2[1])));
|
|
4227
6309
|
const out = [];
|
|
4228
|
-
for (const t of rootsInUnit(...coeffs,
|
|
6310
|
+
for (const t of rootsInUnit(...coeffs, scale3)) {
|
|
4229
6311
|
const point = segmentPoint(seg, t);
|
|
4230
6312
|
const s = ((point[0] - p[0]) * dx + (point[1] - p[1]) * dy) / len2;
|
|
4231
6313
|
if (s >= -1e-9 && s <= 1 + 1e-9) out.push({ s: Math.min(1, Math.max(0, s)), t, point });
|
|
@@ -4233,14 +6315,14 @@ function lineHits(p, q, seg) {
|
|
|
4233
6315
|
return out;
|
|
4234
6316
|
}
|
|
4235
6317
|
var near = (a, b, eps) => Math.abs(a[0] - b[0]) <= eps && Math.abs(a[1] - b[1]) <= eps;
|
|
4236
|
-
var
|
|
6318
|
+
var degenerate2 = (seg, eps) => seg.every((p) => near(p, seg[0], eps));
|
|
4237
6319
|
function arc(l, a, b, eps) {
|
|
4238
6320
|
const n = l.segs.length;
|
|
4239
6321
|
const segs = [];
|
|
4240
6322
|
const chord = [];
|
|
4241
6323
|
const push = (k, t0, t1) => {
|
|
4242
6324
|
const piece = t0 <= 0 && t1 >= 1 ? l.segs[k].slice() : subSegment(l.segs[k], t0, t1);
|
|
4243
|
-
if (!
|
|
6325
|
+
if (!degenerate2(piece, eps)) {
|
|
4244
6326
|
segs.push(piece);
|
|
4245
6327
|
chord.push(l.chord[k]);
|
|
4246
6328
|
}
|
|
@@ -4698,8 +6780,9 @@ function recognizeInkShape(points, options = {}) {
|
|
|
4698
6780
|
// src/vector/builder.ts
|
|
4699
6781
|
function vectorPathEntries(doc, roots, options = {}) {
|
|
4700
6782
|
const out = [];
|
|
4701
|
-
const visit = (
|
|
4702
|
-
if (
|
|
6783
|
+
const visit = (stored, parent, inside) => {
|
|
6784
|
+
if (stored.visible === false || stored.locked && !options.locked) return;
|
|
6785
|
+
const node = options.expand === false ? stored : expandVectorAppearance(stored);
|
|
4703
6786
|
const picked = inside || roots === null || roots.includes(node.id);
|
|
4704
6787
|
if (node.type === "group") {
|
|
4705
6788
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
@@ -4753,7 +6836,7 @@ var STROKE_KEYS = [
|
|
|
4753
6836
|
"strokeWidthProfile"
|
|
4754
6837
|
];
|
|
4755
6838
|
function vectorResultStyle(node, paint, edge = false) {
|
|
4756
|
-
const { id: _id, subpaths: _s, transform: _t, ...rest } = node;
|
|
6839
|
+
const { id: _id, subpaths: _s, transform: _t, effects: _e, booleanOperation: _b, ...rest } = node;
|
|
4757
6840
|
const base = { ...rest, fillRule: "nonzero" };
|
|
4758
6841
|
if (edge) {
|
|
4759
6842
|
for (const k of STROKE_KEYS) delete base[k];
|
|
@@ -5170,7 +7253,8 @@ var TOOL_LABELS = {
|
|
|
5170
7253
|
width: "Width",
|
|
5171
7254
|
shapeBuilder: "Shape Builder",
|
|
5172
7255
|
paintBucket: "Paint Bucket",
|
|
5173
|
-
blend: "Blend"
|
|
7256
|
+
blend: "Blend",
|
|
7257
|
+
gradient: "Gradient"
|
|
5174
7258
|
};
|
|
5175
7259
|
var TOOL_KEYS = {
|
|
5176
7260
|
v: "select",
|
|
@@ -5188,7 +7272,8 @@ var TOOL_KEYS = {
|
|
|
5188
7272
|
w: "width",
|
|
5189
7273
|
m: "shapeBuilder",
|
|
5190
7274
|
b: "paintBucket",
|
|
5191
|
-
j: "blend"
|
|
7275
|
+
j: "blend",
|
|
7276
|
+
g: "gradient"
|
|
5192
7277
|
};
|
|
5193
7278
|
function vectorToolLabel(tool) {
|
|
5194
7279
|
return TOOL_LABELS[tool];
|
|
@@ -5209,7 +7294,8 @@ var VECTOR_TOOL_SHORTCUTS = {
|
|
|
5209
7294
|
width: "W",
|
|
5210
7295
|
shapeBuilder: "M",
|
|
5211
7296
|
paintBucket: "B",
|
|
5212
|
-
blend: "J"
|
|
7297
|
+
blend: "J",
|
|
7298
|
+
gradient: "G"
|
|
5213
7299
|
};
|
|
5214
7300
|
var VECTOR_TOOL_GLYPHS = {
|
|
5215
7301
|
select: "\u2196",
|
|
@@ -5227,7 +7313,8 @@ var VECTOR_TOOL_GLYPHS = {
|
|
|
5227
7313
|
width: "\u21F9",
|
|
5228
7314
|
shapeBuilder: "\u2295",
|
|
5229
7315
|
paintBucket: "\u25CD",
|
|
5230
|
-
blend: "\u29D3"
|
|
7316
|
+
blend: "\u29D3",
|
|
7317
|
+
gradient: "\u25D0"
|
|
5231
7318
|
};
|
|
5232
7319
|
var SHAPE_TOOLS = /* @__PURE__ */ new Set(["rect", "ellipse", "polygon", "star", "line"]);
|
|
5233
7320
|
var BOOLEAN_OPS = {
|
|
@@ -5281,6 +7368,12 @@ function spliceTree(nodes, id, replacements) {
|
|
|
5281
7368
|
return changed ? out : nodes;
|
|
5282
7369
|
}
|
|
5283
7370
|
var toPoint2 = (p) => [p.x, p.y];
|
|
7371
|
+
var placementOf = (n) => ({
|
|
7372
|
+
...n.transform ? { transform: n.transform } : {},
|
|
7373
|
+
...n.visible === false ? { visible: false } : {},
|
|
7374
|
+
...n.locked ? { locked: true } : {},
|
|
7375
|
+
...n.booleanOperation ? { booleanOperation: n.booleanOperation } : {}
|
|
7376
|
+
});
|
|
5284
7377
|
var isRoot = (m) => m === IDENTITY_MATRIX || m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
|
|
5285
7378
|
var idCounter = 0;
|
|
5286
7379
|
var defaultCreateId = () => `vn-${Date.now().toString(36)}${(++idCounter).toString(36)}`;
|
|
@@ -5318,6 +7411,7 @@ var VectorEditorController = class {
|
|
|
5318
7411
|
blendStart = null;
|
|
5319
7412
|
/** Options new blends get (the toolbars edit them along with the selected blend's). */
|
|
5320
7413
|
blendDefaults = {};
|
|
7414
|
+
gradientEdit = { target: "fill", stop: null };
|
|
5321
7415
|
/** What the highlight shows, to skip notifying when the pointer stays over the same face. */
|
|
5322
7416
|
builderHoverKey = null;
|
|
5323
7417
|
createId;
|
|
@@ -5342,15 +7436,26 @@ var VectorEditorController = class {
|
|
|
5342
7436
|
this.createId = options.createId ?? defaultCreateId;
|
|
5343
7437
|
this.onChange = options.onChange;
|
|
5344
7438
|
this.state = this.computeState();
|
|
7439
|
+
this.connect();
|
|
7440
|
+
}
|
|
7441
|
+
/** Listens to the history (once; again after `destroy`). */
|
|
7442
|
+
connect() {
|
|
7443
|
+
if (this.cleanups.length) return;
|
|
5345
7444
|
this.cleanups.push(
|
|
5346
7445
|
this.history.subscribe((h) => {
|
|
5347
|
-
|
|
5348
|
-
if (this.applying || s.canUndo === h.canUndo && s.canRedo === h.canRedo && s.undoLabel === h.undoName && s.redoLabel === h.redoName) return;
|
|
5349
|
-
this.notify();
|
|
7446
|
+
if (!this.applying && this.historyDiffers(h)) this.notify();
|
|
5350
7447
|
})
|
|
5351
7448
|
);
|
|
7449
|
+
if (this.historyDiffers(this.history.getState())) this.state = this.computeState();
|
|
5352
7450
|
}
|
|
5353
|
-
|
|
7451
|
+
historyDiffers(h) {
|
|
7452
|
+
const s = this.state;
|
|
7453
|
+
return s.canUndo !== h.canUndo || s.canRedo !== h.canRedo || s.undoLabel !== h.undoName || s.redoLabel !== h.redoName;
|
|
7454
|
+
}
|
|
7455
|
+
/**
|
|
7456
|
+
* Stops listening to the history and drops all listeners. A later `subscribe` reconnects, so a
|
|
7457
|
+
* controller torn down by React StrictMode's double effect keeps working when it is reused.
|
|
7458
|
+
*/
|
|
5354
7459
|
destroy() {
|
|
5355
7460
|
this.cleanups.splice(0).forEach((fn) => fn());
|
|
5356
7461
|
this.listeners.clear();
|
|
@@ -5362,6 +7467,7 @@ var VectorEditorController = class {
|
|
|
5362
7467
|
getState = () => this.state;
|
|
5363
7468
|
/** Calls `listener` after every change. Returns an unsubscribe function. */
|
|
5364
7469
|
subscribe = (listener) => {
|
|
7470
|
+
this.connect();
|
|
5365
7471
|
this.listeners.add(listener);
|
|
5366
7472
|
return () => {
|
|
5367
7473
|
this.listeners.delete(listener);
|
|
@@ -5399,6 +7505,7 @@ var VectorEditorController = class {
|
|
|
5399
7505
|
builder: this.builderPreview,
|
|
5400
7506
|
blendStart: this.blendStart,
|
|
5401
7507
|
blendDefaults: this.blendDefaults,
|
|
7508
|
+
gradientEdit: this.gradientEdit,
|
|
5402
7509
|
canUndo: h.canUndo,
|
|
5403
7510
|
canRedo: h.canRedo,
|
|
5404
7511
|
undoLabel: h.undoName,
|
|
@@ -5472,6 +7579,13 @@ var VectorEditorController = class {
|
|
|
5472
7579
|
return this.builderPreview ? "cell" : "crosshair";
|
|
5473
7580
|
case "blend":
|
|
5474
7581
|
return h?.nodeId ? this.blendStart && this.blendStart.nodeId !== h.nodeId ? "copy" : "pointer" : "crosshair";
|
|
7582
|
+
case "gradient": {
|
|
7583
|
+
const part = this.drag?.kind === "gradient" ? this.drag.part : h?.gradient;
|
|
7584
|
+
if (!part) return "crosshair";
|
|
7585
|
+
if (part.kind === "bar") return this.drag ? "grabbing" : "copy";
|
|
7586
|
+
if (part.kind === "handle" && part.handle === "start") return this.drag ? "grabbing" : "move";
|
|
7587
|
+
return "pointer";
|
|
7588
|
+
}
|
|
5475
7589
|
default:
|
|
5476
7590
|
return "crosshair";
|
|
5477
7591
|
}
|
|
@@ -5505,8 +7619,11 @@ var VectorEditorController = class {
|
|
|
5505
7619
|
}
|
|
5506
7620
|
this.notify(docChanged);
|
|
5507
7621
|
}
|
|
5508
|
-
/**
|
|
5509
|
-
|
|
7622
|
+
/**
|
|
7623
|
+
* Records the change from `before` to the current state as one undo step. Consecutive
|
|
7624
|
+
* changes with the same `merge` key (a slider drag) fold into one step.
|
|
7625
|
+
*/
|
|
7626
|
+
commit(name, before, merge) {
|
|
5510
7627
|
this.doc = this.refreshed(this.doc);
|
|
5511
7628
|
const after = this.snapshot();
|
|
5512
7629
|
if (before.doc === after.doc) {
|
|
@@ -5517,17 +7634,23 @@ var VectorEditorController = class {
|
|
|
5517
7634
|
name,
|
|
5518
7635
|
execute: () => this.restore(after),
|
|
5519
7636
|
undo: () => this.restore(before),
|
|
5520
|
-
redo: () => this.restore(after)
|
|
7637
|
+
redo: () => this.restore(after),
|
|
7638
|
+
...merge ? { tag: merge } : {}
|
|
5521
7639
|
};
|
|
5522
7640
|
this.committing = true;
|
|
5523
|
-
this.history.
|
|
7641
|
+
if (merge) this.history.executeMerged(command, { key: merge });
|
|
7642
|
+
else this.history.execute(command);
|
|
5524
7643
|
this.committing = false;
|
|
5525
7644
|
}
|
|
5526
|
-
/** Runs `fn` (which updates fields) and records it as one undo step. */
|
|
5527
|
-
change(name, fn) {
|
|
7645
|
+
/** Runs `fn` (which updates fields) and records it as one undo step (merged by `merge` key). */
|
|
7646
|
+
change(name, fn, merge) {
|
|
5528
7647
|
const before = this.snapshot();
|
|
5529
7648
|
fn();
|
|
5530
|
-
this.commit(name, before);
|
|
7649
|
+
this.commit(name, before, merge);
|
|
7650
|
+
}
|
|
7651
|
+
/** Ends a merged edit (a slider released): the next change starts a new undo step. */
|
|
7652
|
+
endMergedEdit() {
|
|
7653
|
+
this.history.endGesture();
|
|
5531
7654
|
}
|
|
5532
7655
|
setDoc(nodes) {
|
|
5533
7656
|
if (nodes !== this.doc.nodes) this.doc = { ...this.doc, nodes };
|
|
@@ -5888,6 +8011,233 @@ var VectorEditorController = class {
|
|
|
5888
8011
|
this.widthEdit = null;
|
|
5889
8012
|
return this.setStyle({ strokeWidthProfile: points.length ? points : null }, options.ids ?? this.selection, { label: points.length ? "Width Profile" : "Uniform Width" });
|
|
5890
8013
|
}
|
|
8014
|
+
// -------------------------------------------------------------------------
|
|
8015
|
+
// Gradients
|
|
8016
|
+
// -------------------------------------------------------------------------
|
|
8017
|
+
/** Which paint (fill or stroke) the gradient tool and editor work on. */
|
|
8018
|
+
setGradientTarget(target) {
|
|
8019
|
+
if (target === this.gradientEdit.target) return;
|
|
8020
|
+
this.gradientEdit = { target, stop: null };
|
|
8021
|
+
this.notify();
|
|
8022
|
+
}
|
|
8023
|
+
/** Selects a stop of the gradient being edited (index into its sorted stops), or none. */
|
|
8024
|
+
selectGradientStop(index) {
|
|
8025
|
+
if (index === this.gradientEdit.stop) return;
|
|
8026
|
+
this.gradientEdit = { ...this.gradientEdit, stop: index };
|
|
8027
|
+
this.notify();
|
|
8028
|
+
}
|
|
8029
|
+
/** The gradient tool's annotator for the selection (screen pixels), or null when its paint is not a gradient. */
|
|
8030
|
+
getGradientAnnotator() {
|
|
8031
|
+
return getVectorGradientAnnotator(this.doc, this.selection, this.viewport, this.gradientEdit.target);
|
|
8032
|
+
}
|
|
8033
|
+
/** Unlocked paths of `ids` (default: the selection). */
|
|
8034
|
+
gradientPaths(ids = this.selection) {
|
|
8035
|
+
const out = /* @__PURE__ */ new Map();
|
|
8036
|
+
for (const id of ids) {
|
|
8037
|
+
const node = this.getNode(id);
|
|
8038
|
+
if (node && !node.locked) {
|
|
8039
|
+
for (const p of collectPathNodes(node)) if (!p.locked) out.set(p.id, p);
|
|
8040
|
+
}
|
|
8041
|
+
}
|
|
8042
|
+
return [...out.values()];
|
|
8043
|
+
}
|
|
8044
|
+
/**
|
|
8045
|
+
* Edits the gradient of the `target` paint (default: the editor's) of the selected (or given)
|
|
8046
|
+
* paths with `patch`: a colour or no paint becomes a gradient (`patch.type`, default linear,
|
|
8047
|
+
* from the colour to white). Consecutive edits with the same `merge` key are one undo step.
|
|
8048
|
+
*/
|
|
8049
|
+
setGradient(patch, options = {}) {
|
|
8050
|
+
const target = options.target ?? this.gradientEdit.target;
|
|
8051
|
+
const paths = this.gradientPaths(options.ids ?? this.selection);
|
|
8052
|
+
if (!paths.length) return { ok: false, message: "Nothing is selected." };
|
|
8053
|
+
const next = /* @__PURE__ */ new Map();
|
|
8054
|
+
for (const path of paths) {
|
|
8055
|
+
const paint = path[target];
|
|
8056
|
+
const base = isVectorGradient(paint) ? paint : defaultVectorGradient(patch.type ?? "linear", paint);
|
|
8057
|
+
const g = applyVectorGradientPatch(base, patch);
|
|
8058
|
+
if (!g) return { ok: false, message: "That is not a valid gradient: it needs at least one stop with an offset and a colour." };
|
|
8059
|
+
next.set(path.id, { ...path, [target]: g });
|
|
8060
|
+
}
|
|
8061
|
+
this.change(options.label ?? "Gradient", () => this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n)), options.merge);
|
|
8062
|
+
const stops = next.values().next().value?.[target]?.stops.length ?? 0;
|
|
8063
|
+
if (this.gradientEdit.stop !== null && this.gradientEdit.stop >= stops) this.gradientEdit = { ...this.gradientEdit, stop: null };
|
|
8064
|
+
return { ok: true };
|
|
8065
|
+
}
|
|
8066
|
+
/** Applies `fn` to every target gradient of the selection (paths whose paint is not a gradient are skipped). */
|
|
8067
|
+
editGradients(label, fn, merge) {
|
|
8068
|
+
const target = this.gradientEdit.target;
|
|
8069
|
+
const paths = this.gradientPaths().filter((p) => isVectorGradient(p[target]));
|
|
8070
|
+
if (!paths.length) return { ok: false, message: "The selection has no gradient." };
|
|
8071
|
+
const next = /* @__PURE__ */ new Map();
|
|
8072
|
+
for (const path of paths) {
|
|
8073
|
+
const g = path[target];
|
|
8074
|
+
const stops = fn(sortedVectorGradientStops(g));
|
|
8075
|
+
if (!stops) return { ok: false, message: "A gradient keeps at least two stops." };
|
|
8076
|
+
next.set(path.id, { ...path, [target]: { ...g, stops } });
|
|
8077
|
+
}
|
|
8078
|
+
this.change(label, () => this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n)), merge);
|
|
8079
|
+
return { ok: true };
|
|
8080
|
+
}
|
|
8081
|
+
/** Changes stop `index` (colour, opacity, offset or midpoint) of the selection's gradients; selects it. */
|
|
8082
|
+
setGradientStop(index, patch, options = {}) {
|
|
8083
|
+
let moved = index;
|
|
8084
|
+
const result = this.editGradients(
|
|
8085
|
+
"Gradient Stop",
|
|
8086
|
+
(stops) => {
|
|
8087
|
+
if (!stops[index]) return stops;
|
|
8088
|
+
let next = stops.map((s, i) => {
|
|
8089
|
+
if (i !== index) return s;
|
|
8090
|
+
const out = { ...s };
|
|
8091
|
+
if (patch.color !== void 0) out.color = patch.color;
|
|
8092
|
+
if (patch.opacity !== void 0 && Number.isFinite(patch.opacity)) {
|
|
8093
|
+
const o = Math.min(1, Math.max(0, patch.opacity));
|
|
8094
|
+
if (o < 1) out.opacity = o;
|
|
8095
|
+
else delete out.opacity;
|
|
8096
|
+
}
|
|
8097
|
+
if (patch.midpoint !== void 0 && Number.isFinite(patch.midpoint)) {
|
|
8098
|
+
const m = Math.min(0.95, Math.max(0.05, patch.midpoint));
|
|
8099
|
+
if (Math.abs(m - 0.5) > 1e-3) out.midpoint = m;
|
|
8100
|
+
else delete out.midpoint;
|
|
8101
|
+
}
|
|
8102
|
+
return out;
|
|
8103
|
+
});
|
|
8104
|
+
if (patch.offset !== void 0 && Number.isFinite(patch.offset)) ({ stops: next, index: moved } = moveVectorGradientStop(next, index, patch.offset));
|
|
8105
|
+
return next;
|
|
8106
|
+
},
|
|
8107
|
+
options.merge
|
|
8108
|
+
);
|
|
8109
|
+
if (result.ok) this.selectGradientStop(moved);
|
|
8110
|
+
return result;
|
|
8111
|
+
}
|
|
8112
|
+
/** Adds a stop at `offset` (painting what the gradient paints there) to the selection's gradients and selects it. */
|
|
8113
|
+
addGradientStop(offset) {
|
|
8114
|
+
let index = 0;
|
|
8115
|
+
const target = this.gradientEdit.target;
|
|
8116
|
+
const result = this.editGradients("Add Gradient Stop", (stops) => {
|
|
8117
|
+
const path = this.gradientPaths().find((p) => isVectorGradient(p[target]));
|
|
8118
|
+
const r = insertVectorGradientStop({ ...path[target], stops }, offset);
|
|
8119
|
+
index = r.index;
|
|
8120
|
+
return r.stops;
|
|
8121
|
+
});
|
|
8122
|
+
if (result.ok) this.selectGradientStop(index);
|
|
8123
|
+
return result;
|
|
8124
|
+
}
|
|
8125
|
+
/** Removes stop `index` of the selection's gradients (never below two stops). */
|
|
8126
|
+
removeGradientStop(index) {
|
|
8127
|
+
const result = this.editGradients("Delete Gradient Stop", (stops) => removeVectorGradientStop(stops, index));
|
|
8128
|
+
if (result.ok) this.selectGradientStop(null);
|
|
8129
|
+
return result;
|
|
8130
|
+
}
|
|
8131
|
+
/** Reverses the selection's gradients (stops and midpoints). */
|
|
8132
|
+
reverseGradient() {
|
|
8133
|
+
return this.setGradient({ reverse: true }, { label: "Reverse Gradient" });
|
|
8134
|
+
}
|
|
8135
|
+
/** `doc` with path `id`'s `target` paint replaced. */
|
|
8136
|
+
withGradient(doc, id, target, g) {
|
|
8137
|
+
return { ...doc, nodes: mapTree(doc.nodes, (n) => n.id === id ? { ...n, [target]: g } : n) };
|
|
8138
|
+
}
|
|
8139
|
+
downGradient(screen, p, input) {
|
|
8140
|
+
const before = this.snapshot();
|
|
8141
|
+
const a = this.getGradientAnnotator();
|
|
8142
|
+
let part = a ? hitVectorGradientAnnotator(a, screen) : null;
|
|
8143
|
+
if (a && part) {
|
|
8144
|
+
if (part.kind === "handle" && part.handle === "start" && input.alt && a.gradient.type === "radial") part = { kind: "handle", handle: "focal" };
|
|
8145
|
+
if (part.kind === "stop") this.gradientEdit = { ...this.gradientEdit, stop: part.index };
|
|
8146
|
+
this.drag = { kind: "gradient", part, nodeId: a.nodeId, targets: [a.nodeId], annotator: a, start: screen, startDoc: p, before, base: this.doc, moved: false, stop: part.kind === "stop" ? part.index : null };
|
|
8147
|
+
this.interaction = { type: "gradient", part: part.kind, ...part.kind === "handle" ? { handle: part.handle } : {} };
|
|
8148
|
+
this.notify();
|
|
8149
|
+
return;
|
|
8150
|
+
}
|
|
8151
|
+
const hit = this.hitPath(p);
|
|
8152
|
+
if (hit && !this.gradientPaths().some((n) => n.id === hit.id)) {
|
|
8153
|
+
this.selection = [hit.id];
|
|
8154
|
+
this.gradientEdit = { ...this.gradientEdit, stop: null };
|
|
8155
|
+
}
|
|
8156
|
+
const targets = this.gradientPaths().map((n) => n.id);
|
|
8157
|
+
if (!targets.length) {
|
|
8158
|
+
if (this.selection.length) this.clearSelection();
|
|
8159
|
+
return;
|
|
8160
|
+
}
|
|
8161
|
+
this.drag = { kind: "gradient", part: null, nodeId: null, targets, annotator: null, start: screen, startDoc: p, before, base: this.doc, moved: false, stop: null };
|
|
8162
|
+
this.interaction = { type: "gradient", part: "create" };
|
|
8163
|
+
this.notify();
|
|
8164
|
+
}
|
|
8165
|
+
moveGradient(drag, screen, p, input) {
|
|
8166
|
+
if (!drag.moved && Math.hypot(screen.x - drag.start.x, screen.y - drag.start.y) < 3) return;
|
|
8167
|
+
drag.moved = true;
|
|
8168
|
+
const target = this.gradientEdit.target;
|
|
8169
|
+
const base = drag.base;
|
|
8170
|
+
const constrained = (from, to) => {
|
|
8171
|
+
if (!input.shift) return to;
|
|
8172
|
+
const ang = Math.round(Math.atan2(to.y - from.y, to.x - from.x) / (Math.PI / 4)) * (Math.PI / 4);
|
|
8173
|
+
const len2 = Math.hypot(to.x - from.x, to.y - from.y);
|
|
8174
|
+
return { x: from.x + Math.cos(ang) * len2, y: from.y + Math.sin(ang) * len2 };
|
|
8175
|
+
};
|
|
8176
|
+
if (!drag.part) {
|
|
8177
|
+
const end = constrained(drag.startDoc, p);
|
|
8178
|
+
let doc = base;
|
|
8179
|
+
for (const id2 of drag.targets) {
|
|
8180
|
+
const node2 = findVectorNode(base.nodes, id2);
|
|
8181
|
+
if (!node2) continue;
|
|
8182
|
+
const paint = node2[target];
|
|
8183
|
+
const g2 = isVectorGradient(paint) ? paint : defaultVectorGradient("linear", paint);
|
|
8184
|
+
doc = this.withGradient(doc, id2, target, setVectorGradientVector(g2, vectorPathLocalBounds(node2), this.toLocal(id2, drag.startDoc, base), this.toLocal(id2, end, base)));
|
|
8185
|
+
}
|
|
8186
|
+
this.doc = doc;
|
|
8187
|
+
this.notify(true);
|
|
8188
|
+
return;
|
|
8189
|
+
}
|
|
8190
|
+
const id = drag.nodeId;
|
|
8191
|
+
const node = findVectorNode(base.nodes, id);
|
|
8192
|
+
const g = node[target];
|
|
8193
|
+
const bounds = vectorPathLocalBounds(node);
|
|
8194
|
+
const local = (q) => this.toLocal(id, q, base);
|
|
8195
|
+
const part = drag.part;
|
|
8196
|
+
let next = g;
|
|
8197
|
+
if (part.kind === "handle") {
|
|
8198
|
+
let to = p;
|
|
8199
|
+
if (part.handle === "end" || part.handle === "radius") to = constrained(this.screenToDocument(drag.annotator.start), p);
|
|
8200
|
+
next = moveVectorGradientHandle(g, bounds, part.handle, local(to), local(drag.startDoc));
|
|
8201
|
+
} else if (part.kind === "bar") {
|
|
8202
|
+
next = moveVectorGradientHandle(g, bounds, "start", local(p), local(drag.startDoc));
|
|
8203
|
+
} else {
|
|
8204
|
+
const a = drag.annotator;
|
|
8205
|
+
const pos = vectorGradientBarPosition(a, screen);
|
|
8206
|
+
const stops = sortedVectorGradientStops(g);
|
|
8207
|
+
if (part.kind === "stop") {
|
|
8208
|
+
const off = Math.abs(pos.across - VECTOR_GRADIENT_STOP_GAP) > VECTOR_GRADIENT_OFF_BAR;
|
|
8209
|
+
const removed = off ? removeVectorGradientStop(stops, part.index) : null;
|
|
8210
|
+
if (removed) {
|
|
8211
|
+
next = { ...g, stops: removed };
|
|
8212
|
+
drag.stop = null;
|
|
8213
|
+
} else {
|
|
8214
|
+
const r = moveVectorGradientStop(stops, part.index, Math.min(1, Math.max(0, pos.offset)));
|
|
8215
|
+
next = { ...g, stops: r.stops };
|
|
8216
|
+
drag.stop = r.index;
|
|
8217
|
+
}
|
|
8218
|
+
this.interaction = { type: "gradient", part: "stop", ...removed ? { removing: true } : {} };
|
|
8219
|
+
this.gradientEdit = { ...this.gradientEdit, stop: drag.stop };
|
|
8220
|
+
} else {
|
|
8221
|
+
const m = vectorGradientMidpointFromPosition(stops, part.index, pos.offset);
|
|
8222
|
+
if (m !== null) next = { ...g, stops: stops.map((s, i) => i !== part.index ? s : Math.abs(m - 0.5) < 1e-3 ? (({ midpoint: _m, ...rest }) => rest)(s) : { ...s, midpoint: m }) };
|
|
8223
|
+
}
|
|
8224
|
+
}
|
|
8225
|
+
this.doc = this.withGradient(base, id, target, next);
|
|
8226
|
+
this.notify(true);
|
|
8227
|
+
}
|
|
8228
|
+
upGradient(drag) {
|
|
8229
|
+
const part = drag.part;
|
|
8230
|
+
if (!drag.moved) {
|
|
8231
|
+
if (part?.kind === "bar") {
|
|
8232
|
+
this.addGradientStop(part.offset);
|
|
8233
|
+
return;
|
|
8234
|
+
}
|
|
8235
|
+
this.notify();
|
|
8236
|
+
return;
|
|
8237
|
+
}
|
|
8238
|
+
const label = !part ? "Gradient" : part.kind === "stop" ? drag.stop === null ? "Delete Gradient Stop" : "Move Gradient Stop" : part.kind === "midpoint" ? "Gradient Midpoint" : part.kind === "bar" || part.kind === "handle" && part.handle === "start" ? "Move Gradient" : "Gradient Handle";
|
|
8239
|
+
return this.commit(label, drag.before);
|
|
8240
|
+
}
|
|
5891
8241
|
/** Removes one width point of a path's profile. */
|
|
5892
8242
|
removeWidthPoint(ref) {
|
|
5893
8243
|
return this.removeWidthPoints([ref]);
|
|
@@ -5938,11 +8288,11 @@ var VectorEditorController = class {
|
|
|
5938
8288
|
if (node?.type !== "path") return { ok: false, message: "There is no such path." };
|
|
5939
8289
|
const g = vectorStrokeWidthGeometry(node);
|
|
5940
8290
|
if (!g || !(g.unit > 0)) return { ok: false, message: "The path has no visible stroke." };
|
|
5941
|
-
const
|
|
5942
|
-
const t =
|
|
5943
|
-
const
|
|
5944
|
-
const right =
|
|
5945
|
-
const
|
|
8291
|
+
const num2 = (v2) => typeof v2 === "number" && Number.isFinite(v2) ? v2 : void 0;
|
|
8292
|
+
const t = num2(values.t);
|
|
8293
|
+
const left2 = num2(values.left);
|
|
8294
|
+
const right = num2(values.right);
|
|
8295
|
+
const scale3 = (w) => w === void 0 ? void 0 : Math.max(0, w) / g.unit;
|
|
5946
8296
|
const profile = sanitizeWidthProfile(node.strokeWidthProfile);
|
|
5947
8297
|
let points;
|
|
5948
8298
|
let index;
|
|
@@ -5950,11 +8300,11 @@ var VectorEditorController = class {
|
|
|
5950
8300
|
if (existing === null) {
|
|
5951
8301
|
if (t === void 0) return { ok: false, message: "A new width point needs a position t." };
|
|
5952
8302
|
if (profile.length >= VECTOR_WIDTH_PROFILE_MAX_POINTS) return { ok: false, message: "The profile has too many width points." };
|
|
5953
|
-
({ points, index } = insertWidthPoint(profile.length ? profile : null, t, { left:
|
|
8303
|
+
({ points, index } = insertWidthPoint(profile.length ? profile : null, t, { left: scale3(left2), right: scale3(right) }, { step: true }));
|
|
5954
8304
|
} else {
|
|
5955
8305
|
const p = profile[existing];
|
|
5956
8306
|
if (!p) return { ok: false, message: "There is no such width point." };
|
|
5957
|
-
const sides2 = { left:
|
|
8307
|
+
const sides2 = { left: scale3(left2) ?? p.left, right: scale3(right) ?? p.right };
|
|
5958
8308
|
const tt = t === void 0 ? p.t : Math.min(Math.max(t, 0), 1);
|
|
5959
8309
|
if (Math.abs(tt - p.t) <= VECTOR_WIDTH_SAME_T) {
|
|
5960
8310
|
points = setWidthPoint(profile, existing, sides2);
|
|
@@ -6077,7 +8427,7 @@ var VectorEditorController = class {
|
|
|
6077
8427
|
const created = [];
|
|
6078
8428
|
const replace = /* @__PURE__ */ new Map();
|
|
6079
8429
|
for (const p of paths) {
|
|
6080
|
-
const outline = bezierToSubpaths(vectorStrokeOutlinePath(
|
|
8430
|
+
const outline = bezierToSubpaths(vectorStrokeOutlinePath(vectorAppearanceOutline(p), p));
|
|
6081
8431
|
if (!outline.length) continue;
|
|
6082
8432
|
const {
|
|
6083
8433
|
stroke,
|
|
@@ -6092,11 +8442,12 @@ var VectorEditorController = class {
|
|
|
6092
8442
|
startArrowScale: _ss,
|
|
6093
8443
|
endArrowScale: _es,
|
|
6094
8444
|
strokeWidthProfile: _wp,
|
|
8445
|
+
effects,
|
|
6095
8446
|
...rest
|
|
6096
8447
|
} = cloneVectorNode(p);
|
|
6097
8448
|
const id = this.newId();
|
|
6098
8449
|
const strokeNode = { ...rest, id, name: `${p.name ?? "Path"} Stroke`, subpaths: outline, fill: stroke ?? null, stroke: null, fillRule: "nonzero" };
|
|
6099
|
-
const list = hasPaint(p.fill) ? [{ ...rest, stroke: null }, strokeNode] : [strokeNode];
|
|
8450
|
+
const list = hasPaint(p.fill) ? [{ ...rest, ...effects ? { effects } : {}, stroke: null }, strokeNode] : [strokeNode];
|
|
6100
8451
|
created.push(id);
|
|
6101
8452
|
replace.set(p.id, list);
|
|
6102
8453
|
}
|
|
@@ -6111,6 +8462,121 @@ var VectorEditorController = class {
|
|
|
6111
8462
|
});
|
|
6112
8463
|
return created;
|
|
6113
8464
|
}
|
|
8465
|
+
// -------------------------------------------------------------------------
|
|
8466
|
+
// Path effects
|
|
8467
|
+
// -------------------------------------------------------------------------
|
|
8468
|
+
/** Selected (or given) nodes that can carry effects: the roots of the selection. */
|
|
8469
|
+
effectTargets(ids) {
|
|
8470
|
+
return (ids ?? this.selectedRoots()).map((id) => this.getNode(id)).filter((n) => !!n && !n.blendStep);
|
|
8471
|
+
}
|
|
8472
|
+
/**
|
|
8473
|
+
* Adds an effect of `type` to the selected (or given) nodes, at the bottom of each stack (or
|
|
8474
|
+
* at `index`), with default parameters merged with `params`. A repeater's copies start one
|
|
8475
|
+
* shape width apart. One undo step; returns the new effect ids (one per node, in order).
|
|
8476
|
+
*/
|
|
8477
|
+
addEffect(type, options = {}) {
|
|
8478
|
+
const targets = this.effectTargets(options.ids);
|
|
8479
|
+
if (!targets.length || !createVectorEffect(type, "")) return [];
|
|
8480
|
+
const created = [];
|
|
8481
|
+
const next = /* @__PURE__ */ new Map();
|
|
8482
|
+
for (const node of targets) {
|
|
8483
|
+
const list = node.effects ?? [];
|
|
8484
|
+
let k = list.length + 1;
|
|
8485
|
+
while (list.some((e) => e.id === `fx${k}`)) k++;
|
|
8486
|
+
const params = { ...options.params ?? {} };
|
|
8487
|
+
if (type === "repeater" && params.position === void 0) {
|
|
8488
|
+
const b = bezierPathBounds(vectorAppearanceOutline(node));
|
|
8489
|
+
if (b && b.maxX - b.minX > 0) params.position = { x: Math.round((b.maxX - b.minX) * 1.25 * 100) / 100, y: 0 };
|
|
8490
|
+
}
|
|
8491
|
+
const effect = createVectorEffect(type, `fx${k}`, params);
|
|
8492
|
+
const at2 = options.index === void 0 ? list.length : Math.min(list.length, Math.max(0, Math.round(options.index)));
|
|
8493
|
+
next.set(node.id, { ...node, effects: [...list.slice(0, at2), effect, ...list.slice(at2)] });
|
|
8494
|
+
created.push(effect.id);
|
|
8495
|
+
}
|
|
8496
|
+
this.change(`Add ${VECTOR_EFFECT_LABELS[type]}`, () => this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n)));
|
|
8497
|
+
return created;
|
|
8498
|
+
}
|
|
8499
|
+
editEffects(nodeId, label, fn, merge) {
|
|
8500
|
+
const node = this.getNode(nodeId);
|
|
8501
|
+
if (!node) return { ok: false, message: `There is no node "${nodeId}".` };
|
|
8502
|
+
const result = fn(node.effects ?? []);
|
|
8503
|
+
if (typeof result === "string") return { ok: false, message: result };
|
|
8504
|
+
this.change(label, () => this.replaceNode(nodeId, result.length ? { ...node, effects: result } : (({ effects: _e, ...rest }) => rest)(node)), merge);
|
|
8505
|
+
return { ok: true };
|
|
8506
|
+
}
|
|
8507
|
+
/**
|
|
8508
|
+
* Turns an effect on or off and changes its parameters (unknown or invalid values are
|
|
8509
|
+
* ignored). With `merge` (true, or a key of your own), consecutive updates of the same effect
|
|
8510
|
+
* fold into one undo step, as a slider drag should; call `endMergedEdit` when it ends.
|
|
8511
|
+
*/
|
|
8512
|
+
updateEffect(nodeId, effectId, patch, options = {}) {
|
|
8513
|
+
const merge = options.merge === true ? `effect:${nodeId}:${effectId}` : options.merge || void 0;
|
|
8514
|
+
const node = this.getNode(nodeId);
|
|
8515
|
+
const current = node?.effects?.find((e) => e.id === effectId);
|
|
8516
|
+
const label = current ? VECTOR_EFFECT_LABELS[current.type] ?? "Effect" : "Effect";
|
|
8517
|
+
return this.editEffects(
|
|
8518
|
+
nodeId,
|
|
8519
|
+
`Edit ${label}`,
|
|
8520
|
+
(list) => {
|
|
8521
|
+
const i = list.findIndex((e2) => e2.id === effectId);
|
|
8522
|
+
if (i < 0) return `Node "${nodeId}" has no effect "${effectId}".`;
|
|
8523
|
+
const e = list[i];
|
|
8524
|
+
const params = patch.params ? normalizeVectorEffectParams(e.type, patch.params, e.params) : e.params;
|
|
8525
|
+
const next = { ...e, params, ...patch.enabled !== void 0 ? { enabled: patch.enabled } : {} };
|
|
8526
|
+
if (next.enabled !== false) delete next.enabled;
|
|
8527
|
+
return list.map((x, j) => j === i ? next : x);
|
|
8528
|
+
},
|
|
8529
|
+
merge
|
|
8530
|
+
);
|
|
8531
|
+
}
|
|
8532
|
+
/** Removes an effect from a node's stack. */
|
|
8533
|
+
removeEffect(nodeId, effectId) {
|
|
8534
|
+
return this.editEffects(nodeId, "Remove Effect", (list) => list.some((e) => e.id === effectId) ? list.filter((e) => e.id !== effectId) : `Node "${nodeId}" has no effect "${effectId}".`);
|
|
8535
|
+
}
|
|
8536
|
+
/** Moves an effect to position `index` of its stack (0 is applied first). */
|
|
8537
|
+
moveEffect(nodeId, effectId, index) {
|
|
8538
|
+
return this.editEffects(nodeId, "Reorder Effects", (list) => {
|
|
8539
|
+
const i = list.findIndex((e2) => e2.id === effectId);
|
|
8540
|
+
if (i < 0) return `Node "${nodeId}" has no effect "${effectId}".`;
|
|
8541
|
+
const to = Math.min(list.length - 1, Math.max(0, Math.round(index)));
|
|
8542
|
+
const out = list.slice();
|
|
8543
|
+
const [e] = out.splice(i, 1);
|
|
8544
|
+
out.splice(to, 0, e);
|
|
8545
|
+
return out;
|
|
8546
|
+
});
|
|
8547
|
+
}
|
|
8548
|
+
/**
|
|
8549
|
+
* Expand Appearance: bakes the path effects and live booleans of the selected (or given)
|
|
8550
|
+
* nodes, and of everything inside them, into plain paths. A node keeps its id (a group when
|
|
8551
|
+
* it draws as several shapes); the shapes it made get new ids. Returns the ids of the expanded
|
|
8552
|
+
* nodes (empty when none was live).
|
|
8553
|
+
*/
|
|
8554
|
+
expandAppearance(options = {}) {
|
|
8555
|
+
const roots = (options.ids ?? this.selectedRoots()).map((id) => this.getNode(id)).filter((n) => !!n && hasVectorAppearance(n));
|
|
8556
|
+
if (!roots.length) return [];
|
|
8557
|
+
const fresh = (n) => {
|
|
8558
|
+
const id = n.id.includes("~fx") ? this.newId() : n.id;
|
|
8559
|
+
return n.type === "group" ? { ...n, id, children: n.children.map(fresh) } : { ...n, id };
|
|
8560
|
+
};
|
|
8561
|
+
const bake = (n) => {
|
|
8562
|
+
if (!hasVectorAppearance(n)) return n;
|
|
8563
|
+
if (!hasOwnVectorAppearance(n)) return { ...n, children: n.children.map(bake) };
|
|
8564
|
+
let e = expandVectorAppearance(n);
|
|
8565
|
+
if (e.type === "group" && e.children.length === 1 && e.children[0].type === "path") {
|
|
8566
|
+
const { id: _id, opacity, name, ...child } = e.children[0];
|
|
8567
|
+
const k = (e.opacity ?? 1) * (opacity ?? 1);
|
|
8568
|
+
e = { ...child, id: e.id, name: e.name ?? name, ...placementOf(e), ...k < 1 ? { opacity: k } : {} };
|
|
8569
|
+
}
|
|
8570
|
+
return fresh(e);
|
|
8571
|
+
};
|
|
8572
|
+
const next = new Map(roots.map((n) => [n.id, bake(n)]));
|
|
8573
|
+
this.change("Expand Appearance", () => {
|
|
8574
|
+
this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n));
|
|
8575
|
+
this.anchors = [];
|
|
8576
|
+
this.editingId = null;
|
|
8577
|
+
});
|
|
8578
|
+
return roots.map((n) => n.id);
|
|
8579
|
+
}
|
|
6114
8580
|
rename(id, name) {
|
|
6115
8581
|
return this.updateNode(id, { name }, { label: "Rename" });
|
|
6116
8582
|
}
|
|
@@ -6402,7 +8868,7 @@ var VectorEditorController = class {
|
|
|
6402
8868
|
const opacity = n.opacity;
|
|
6403
8869
|
const m = nodeMatrix(n);
|
|
6404
8870
|
out.push(
|
|
6405
|
-
...n.children.map((c) => c.blendStep ? (({ blendStep: _s, locked: _l, ...rest }) => rest)(c) : c).map((c) => n.transform ? transformVectorNode(c, m) : c).map((c) => opacity !== void 0 && opacity < 1 ? { ...c, opacity: (c.opacity ?? 1) * opacity } : c)
|
|
8871
|
+
...n.children.map((c) => c.blendStep ? (({ blendStep: _s, locked: _l, ...rest }) => rest)(c) : c).map((c) => c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest)(c) : c).map((c) => n.transform ? transformVectorNode(c, m) : c).map((c) => opacity !== void 0 && opacity < 1 ? { ...c, opacity: (c.opacity ?? 1) * opacity } : c)
|
|
6406
8872
|
);
|
|
6407
8873
|
} else if (n.type === "group") {
|
|
6408
8874
|
const children = flatten2(n.children);
|
|
@@ -6439,34 +8905,33 @@ var VectorEditorController = class {
|
|
|
6439
8905
|
/**
|
|
6440
8906
|
* Combines the selected paths (two or more) into one with a boolean operation. `subtract`
|
|
6441
8907
|
* removes the upper shapes from the lowest one. The result takes the lowest shape's style
|
|
6442
|
-
* and place.
|
|
8908
|
+
* and place. Shapes take part as drawn (path effects and live booleans applied).
|
|
8909
|
+
*
|
|
8910
|
+
* With `live`, the operands stay editable instead: a lone live boolean group gets the
|
|
8911
|
+
* operation (its operands follow it again), operands of live boolean groups get it as their
|
|
8912
|
+
* own, and anything else (two or more nodes with one parent) is wrapped in a new live boolean
|
|
8913
|
+
* group. Returns the result's (or the live group's) id, or null.
|
|
6443
8914
|
*/
|
|
6444
8915
|
booleanOp(op, options = {}) {
|
|
6445
8916
|
const roots = options.ids ?? this.selectedRoots();
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
});
|
|
6450
|
-
const paths = [...new Set(order)].map((id2) => this.getNode(id2));
|
|
6451
|
-
if (paths.length < 2) return null;
|
|
8917
|
+
if (options.live) return this.liveBooleanOp(op, roots);
|
|
8918
|
+
const entries = vectorPathEntries(this.doc, roots, { locked: true });
|
|
8919
|
+
if (entries.length < 2) return null;
|
|
6452
8920
|
const result = pathBoolean(
|
|
6453
8921
|
BOOLEAN_OPS[op],
|
|
6454
|
-
|
|
6455
|
-
path: vectorPathWorldBezier(
|
|
6456
|
-
|
|
6457
|
-
vectorParentMatrix(this.doc.nodes, p.id) ?? IDENTITY_MATRIX
|
|
6458
|
-
),
|
|
6459
|
-
fillRule: p.fillRule ?? "nonzero"
|
|
8922
|
+
entries.map((e) => ({
|
|
8923
|
+
path: vectorPathWorldBezier({ ...e.node, subpaths: e.node.subpaths.filter((s) => s.anchors.length > 1).map((s) => ({ ...s, closed: true })) }, e.parent),
|
|
8924
|
+
fillRule: e.node.fillRule ?? "nonzero"
|
|
6460
8925
|
}))
|
|
6461
8926
|
);
|
|
6462
|
-
const base =
|
|
6463
|
-
const anchorRoot = roots.find((r) =>
|
|
8927
|
+
const base = entries[0].node;
|
|
8928
|
+
const anchorRoot = roots.find((r) => this.holds(r, base.id)) ?? base.id;
|
|
6464
8929
|
const subpaths = bezierToSubpaths(this.intoParentSpace(anchorRoot, result.path));
|
|
6465
8930
|
if (result.error || !subpaths.length) return null;
|
|
6466
8931
|
const id = this.newId();
|
|
6467
|
-
const { transform: _t, ...baseRest } = cloneVectorNode(base);
|
|
8932
|
+
const { transform: _t, effects: _e, booleanOperation: _b, ...baseRest } = cloneVectorNode(base);
|
|
6468
8933
|
const node = { ...baseRest, id, name: BOOLEAN_LABELS[op], subpaths };
|
|
6469
|
-
const drop = /* @__PURE__ */ new Set([...
|
|
8934
|
+
const drop = /* @__PURE__ */ new Set([...entries.map((e) => e.node.id), ...roots]);
|
|
6470
8935
|
this.change(BOOLEAN_LABELS[op], () => {
|
|
6471
8936
|
this.setDoc(mapTree(this.doc.nodes, (n) => n.id === anchorRoot ? node : drop.has(n.id) ? null : n));
|
|
6472
8937
|
this.selection = [id];
|
|
@@ -6475,18 +8940,75 @@ var VectorEditorController = class {
|
|
|
6475
8940
|
});
|
|
6476
8941
|
return id;
|
|
6477
8942
|
}
|
|
8943
|
+
/** Whether stored node `rootId` holds `id` as drawn (`id` may be an expanded path inside it). */
|
|
8944
|
+
holds(rootId, id) {
|
|
8945
|
+
const n = this.getNode(rootId);
|
|
8946
|
+
return !!n && (!!findVectorNode([n], id) || !!findVectorNode([expandVectorAppearance(n)], id));
|
|
8947
|
+
}
|
|
8948
|
+
liveBooleanOp(op, roots) {
|
|
8949
|
+
const nodes = roots.map((id2) => this.getNode(id2));
|
|
8950
|
+
if (!nodes.length || nodes.some((n) => !n)) return null;
|
|
8951
|
+
const label = `Live ${BOOLEAN_LABELS[op]}`;
|
|
8952
|
+
const lone = nodes.length === 1 ? nodes[0] : null;
|
|
8953
|
+
if (isVectorLiveBoolean(lone)) {
|
|
8954
|
+
const children = lone.children.map((c) => c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest)(c) : c);
|
|
8955
|
+
this.change(label, () => this.replaceNode(lone.id, { ...lone, liveBoolean: op, children }));
|
|
8956
|
+
return lone.id;
|
|
8957
|
+
}
|
|
8958
|
+
const parents = roots.map((id2) => locateVectorNode(this.doc.nodes, id2)?.parent ?? null);
|
|
8959
|
+
if (parents.every((g) => isVectorLiveBoolean(g))) {
|
|
8960
|
+
const set = new Set(roots);
|
|
8961
|
+
this.change(
|
|
8962
|
+
label,
|
|
8963
|
+
() => this.setDoc(
|
|
8964
|
+
mapTree(this.doc.nodes, (n) => {
|
|
8965
|
+
if (n.type !== "group" || !n.liveBoolean || !n.children.some((c) => set.has(c.id))) return n;
|
|
8966
|
+
const group = n;
|
|
8967
|
+
const children = group.children.map((c) => {
|
|
8968
|
+
if (!set.has(c.id)) return c;
|
|
8969
|
+
const { booleanOperation: _b, ...rest } = c;
|
|
8970
|
+
return op === group.liveBoolean ? rest : { ...rest, booleanOperation: op };
|
|
8971
|
+
});
|
|
8972
|
+
return { ...group, children };
|
|
8973
|
+
})
|
|
8974
|
+
)
|
|
8975
|
+
);
|
|
8976
|
+
return parents[0].id;
|
|
8977
|
+
}
|
|
8978
|
+
if (nodes.length < 2) return null;
|
|
8979
|
+
const parent = parents[0]?.id ?? null;
|
|
8980
|
+
if (parents.some((g) => (g?.id ?? null) !== parent)) return null;
|
|
8981
|
+
const id = this.newId();
|
|
8982
|
+
this.change(label, () => {
|
|
8983
|
+
const set = new Set(roots);
|
|
8984
|
+
const wrap = (list) => {
|
|
8985
|
+
const members = list.filter((n) => set.has(n.id)).map((c) => c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest)(c) : c);
|
|
8986
|
+
const top = Math.max(...list.map((n, i) => set.has(n.id) ? i : -1));
|
|
8987
|
+
const out = [];
|
|
8988
|
+
list.forEach((n, i) => {
|
|
8989
|
+
if (i === top) out.push({ id, type: "group", name: BOOLEAN_LABELS[op], liveBoolean: op, children: members });
|
|
8990
|
+
if (!set.has(n.id)) out.push(n);
|
|
8991
|
+
});
|
|
8992
|
+
return out;
|
|
8993
|
+
};
|
|
8994
|
+
if (parent === null) this.setDoc(wrap(this.doc.nodes));
|
|
8995
|
+
else this.setDoc(mapTree(this.doc.nodes, (n) => n.id === parent && n.type === "group" ? { ...n, children: wrap(n.children) } : n));
|
|
8996
|
+
this.selection = [id];
|
|
8997
|
+
this.anchors = [];
|
|
8998
|
+
this.editingId = null;
|
|
8999
|
+
});
|
|
9000
|
+
return id;
|
|
9001
|
+
}
|
|
6478
9002
|
/** Joins the selected paths into one compound path (subpaths kept, lowest style). */
|
|
6479
9003
|
combine(options = {}) {
|
|
6480
9004
|
const roots = options.ids ?? this.selectedRoots();
|
|
6481
|
-
const
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
});
|
|
6485
|
-
if (paths.length < 2) return null;
|
|
9005
|
+
const entries = vectorPathEntries(this.doc, roots, { locked: true });
|
|
9006
|
+
if (entries.length < 2) return null;
|
|
9007
|
+
const paths = entries.map((e) => e.node);
|
|
6486
9008
|
const id = this.newId();
|
|
6487
|
-
const first = roots.find((r) =>
|
|
6488
|
-
const subpaths =
|
|
6489
|
-
const { transform: _t, ...baseRest } = cloneVectorNode(paths[0]);
|
|
9009
|
+
const first = roots.find((r) => this.holds(r, paths[0].id)) ?? paths[0].id;
|
|
9010
|
+
const subpaths = entries.flatMap((e) => bezierToSubpaths(this.intoParentSpace(first, vectorPathWorldBezier(e.node, e.parent))));
|
|
9011
|
+
const { transform: _t, effects: _e, booleanOperation: _b, ...baseRest } = cloneVectorNode(paths[0]);
|
|
6490
9012
|
const node = { ...baseRest, id, name: "Compound Path", subpaths };
|
|
6491
9013
|
const drop = /* @__PURE__ */ new Set([...paths.map((p) => p.id), ...roots]);
|
|
6492
9014
|
this.change("Combine", () => {
|
|
@@ -6541,10 +9063,7 @@ var VectorEditorController = class {
|
|
|
6541
9063
|
const result = pathfinder(op, shapes);
|
|
6542
9064
|
if (result.error || !result.pieces.length) return [];
|
|
6543
9065
|
const last = entries[entries.length - 1].node.id;
|
|
6544
|
-
const topRoot = [...roots].reverse().find((r) =>
|
|
6545
|
-
const n = this.getNode(r);
|
|
6546
|
-
return !!n && !!findVectorNode([n], last);
|
|
6547
|
-
}) ?? last;
|
|
9066
|
+
const topRoot = [...roots].reverse().find((r) => this.holds(r, last)) ?? last;
|
|
6548
9067
|
const nodes = [];
|
|
6549
9068
|
for (const piece of result.pieces) {
|
|
6550
9069
|
const subpaths = bezierToSubpaths(this.intoParentSpace(topRoot, piece.path)).map((sp) => piece.open ? { ...sp, closed: false } : sp);
|
|
@@ -6737,7 +9256,7 @@ var VectorEditorController = class {
|
|
|
6737
9256
|
const roots = ids ?? this.selectedRoots();
|
|
6738
9257
|
const key = roots.join("\0");
|
|
6739
9258
|
if (this.builderCache?.doc === this.doc && this.builderCache.key === key) return this.builderCache.map;
|
|
6740
|
-
const entries = roots.length ? vectorPathEntries(this.doc, roots) : [];
|
|
9259
|
+
const entries = roots.length ? vectorPathEntries(this.doc, roots, { expand: false }) : [];
|
|
6741
9260
|
const map = entries.length ? vectorShapeBuilderMap(entries) : null;
|
|
6742
9261
|
this.builderCache = { doc: this.doc, key, map };
|
|
6743
9262
|
return map;
|
|
@@ -7521,16 +10040,22 @@ var VectorEditorController = class {
|
|
|
7521
10040
|
return best;
|
|
7522
10041
|
}
|
|
7523
10042
|
/** Deepest unlocked visible path under `p`. */
|
|
10043
|
+
/** Live groups being edited (selected, or holding the selection or the edited path): their children are hit as stored. */
|
|
10044
|
+
enteredGroups() {
|
|
10045
|
+
return vectorEnteredGroups(this.doc.nodes, this.editingId ? [...this.selection, this.editingId] : this.selection);
|
|
10046
|
+
}
|
|
7524
10047
|
hitPath(p) {
|
|
7525
10048
|
const tol = this.tolerance();
|
|
10049
|
+
const entered = this.enteredGroups();
|
|
7526
10050
|
const search = (list, parent) => {
|
|
7527
10051
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
7528
10052
|
const n = list[i];
|
|
7529
10053
|
if (n.visible === false || n.locked) continue;
|
|
7530
10054
|
if (n.type === "group") {
|
|
10055
|
+
if (hasOwnVectorAppearance(n) && !entered.has(n.id) && !hitTestVectorNode(n, p, tol, parent, { entered })) continue;
|
|
7531
10056
|
const found = search(n.children, multiplyMatrices(nodeMatrix(n), parent));
|
|
7532
10057
|
if (found) return found;
|
|
7533
|
-
} else if (hitTestVectorNode(n, p, tol, parent)) return n;
|
|
10058
|
+
} else if (hitTestVectorNode(n, p, tol, parent, { entered })) return n;
|
|
7534
10059
|
}
|
|
7535
10060
|
return null;
|
|
7536
10061
|
};
|
|
@@ -7555,7 +10080,7 @@ var VectorEditorController = class {
|
|
|
7555
10080
|
case "select": {
|
|
7556
10081
|
const handle = this.hitTransformHandle(screen);
|
|
7557
10082
|
if (handle) return { nodeId: null, transformHandle: handle };
|
|
7558
|
-
const node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance());
|
|
10083
|
+
const node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance(), { entered: this.enteredGroups() });
|
|
7559
10084
|
return node ? { nodeId: node.id } : null;
|
|
7560
10085
|
}
|
|
7561
10086
|
case "direct": {
|
|
@@ -7596,6 +10121,13 @@ var VectorEditorController = class {
|
|
|
7596
10121
|
const spot = this.strokeSpotAt(p);
|
|
7597
10122
|
return spot ? { nodeId: spot.nodeId, strokeSpot: spot } : null;
|
|
7598
10123
|
}
|
|
10124
|
+
case "gradient": {
|
|
10125
|
+
const a = this.getGradientAnnotator();
|
|
10126
|
+
const part = a ? hitVectorGradientAnnotator(a, screen) : null;
|
|
10127
|
+
if (a && part) return { nodeId: a.nodeId, gradient: part };
|
|
10128
|
+
const path = this.hitPath(p);
|
|
10129
|
+
return path ? { nodeId: path.id } : null;
|
|
10130
|
+
}
|
|
7599
10131
|
default:
|
|
7600
10132
|
return null;
|
|
7601
10133
|
}
|
|
@@ -7818,6 +10350,8 @@ var VectorEditorController = class {
|
|
|
7818
10350
|
return this.downBuilder(p, input);
|
|
7819
10351
|
case "blend":
|
|
7820
10352
|
return this.downBlend(screen, p);
|
|
10353
|
+
case "gradient":
|
|
10354
|
+
return this.downGradient(screen, p, input);
|
|
7821
10355
|
case "scissors": {
|
|
7822
10356
|
const seg = this.nearestOutline(p);
|
|
7823
10357
|
if (seg) this.cutPathAt(seg.nodeId, seg.point);
|
|
@@ -7884,7 +10418,7 @@ var VectorEditorController = class {
|
|
|
7884
10418
|
this.notify();
|
|
7885
10419
|
return;
|
|
7886
10420
|
}
|
|
7887
|
-
const node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance());
|
|
10421
|
+
const node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance(), { entered: this.enteredGroups() });
|
|
7888
10422
|
if (node) {
|
|
7889
10423
|
const selected = this.selection.includes(node.id);
|
|
7890
10424
|
if (input.shift) {
|
|
@@ -8109,6 +10643,8 @@ var VectorEditorController = class {
|
|
|
8109
10643
|
this.viewport = { ...drag.viewport, x: drag.viewport.x + screen.x - drag.start.x, y: drag.viewport.y + screen.y - drag.start.y };
|
|
8110
10644
|
this.notify();
|
|
8111
10645
|
return;
|
|
10646
|
+
case "gradient":
|
|
10647
|
+
return this.moveGradient(drag, screen, p, input);
|
|
8112
10648
|
case "move": {
|
|
8113
10649
|
let dx = p.x - drag.start.x;
|
|
8114
10650
|
let dy = p.y - drag.start.y;
|
|
@@ -8484,6 +11020,8 @@ var VectorEditorController = class {
|
|
|
8484
11020
|
}
|
|
8485
11021
|
case "builder":
|
|
8486
11022
|
return this.upBuilder(drag);
|
|
11023
|
+
case "gradient":
|
|
11024
|
+
return this.upGradient(drag);
|
|
8487
11025
|
case "stroke": {
|
|
8488
11026
|
this.notify();
|
|
8489
11027
|
if (drag.tool === "knife") this.knife(drag.points);
|
|
@@ -8698,6 +11236,7 @@ var VectorEditorController = class {
|
|
|
8698
11236
|
case "Delete":
|
|
8699
11237
|
case "Backspace":
|
|
8700
11238
|
if (this.tool === "width" && this.widthPoints.length) return this.removeWidthPoints().ok;
|
|
11239
|
+
if (this.tool === "gradient" && this.gradientEdit.stop !== null) return this.removeGradientStop(this.gradientEdit.stop).ok;
|
|
8701
11240
|
if (this.pen) {
|
|
8702
11241
|
const node = this.getNode(this.pen.nodeId);
|
|
8703
11242
|
const sp = node.subpaths[this.pen.subpath];
|
|
@@ -8806,24 +11345,24 @@ function pencilPressureProfile(points, pressures) {
|
|
|
8806
11345
|
}
|
|
8807
11346
|
|
|
8808
11347
|
// src/vector/render.ts
|
|
8809
|
-
var
|
|
8810
|
-
var
|
|
11348
|
+
var fmt4 = (n) => Math.round(n * 100) / 100;
|
|
11349
|
+
var fmtMatrix2 = (m) => `matrix(${m.map((n) => Math.round(n * 1e6) / 1e6).join(" ")})`;
|
|
8811
11350
|
var VECTOR_BUILDER_HATCH_GAP = 6;
|
|
8812
|
-
function renderNode(
|
|
8813
|
-
if (
|
|
11351
|
+
function renderNode(stored, defs, idPrefix) {
|
|
11352
|
+
if (stored.visible === false) return null;
|
|
11353
|
+
const node = expandVectorAppearance(stored);
|
|
8814
11354
|
if (node.type === "group") {
|
|
8815
11355
|
const children = node.children.map((c) => renderNode(c, defs, idPrefix)).filter((c) => !!c);
|
|
8816
|
-
return { kind: "group", id: node.id, ...node.transform ? { transform:
|
|
11356
|
+
return { kind: "group", id: node.id, ...node.transform ? { transform: fmtMatrix2(node.transform) } : {}, opacity: node.opacity ?? 1, children };
|
|
8817
11357
|
}
|
|
8818
|
-
const fill = vectorPaintToSvg(node.fill, `${idPrefix}fill-${node.id}
|
|
8819
|
-
const stroke = vectorPaintToSvg(node.stroke, `${idPrefix}stroke-${node.id}
|
|
8820
|
-
|
|
8821
|
-
if (stroke.gradient) defs.push(stroke.gradient);
|
|
11358
|
+
const fill = vectorPaintToSvg(node.fill, `${idPrefix}fill-${node.id}`, { node });
|
|
11359
|
+
const stroke = vectorPaintToSvg(node.stroke, `${idPrefix}stroke-${node.id}`, { node, role: "stroke" });
|
|
11360
|
+
defs.push(...fill.defs, ...stroke.defs);
|
|
8822
11361
|
const strokeParts = stroke.value !== "none" ? vectorStrokeSvgParts(node, `${idPrefix}clip-${node.id}`) : void 0;
|
|
8823
11362
|
return {
|
|
8824
11363
|
kind: "path",
|
|
8825
11364
|
id: node.id,
|
|
8826
|
-
...node.transform ? { transform:
|
|
11365
|
+
...node.transform ? { transform: fmtMatrix2(node.transform) } : {},
|
|
8827
11366
|
...strokeParts ? { strokeParts } : {},
|
|
8828
11367
|
d: vectorPathData(node),
|
|
8829
11368
|
fill: fill.value,
|
|
@@ -8842,7 +11381,7 @@ function getVectorSceneRenderData(state, options = {}) {
|
|
|
8842
11381
|
const nodes = state.document.nodes.map((n) => renderNode(n, defs, prefix)).filter((n) => !!n);
|
|
8843
11382
|
const v2 = state.viewport;
|
|
8844
11383
|
return {
|
|
8845
|
-
transform: `translate(${
|
|
11384
|
+
transform: `translate(${fmt4(v2.x)} ${fmt4(v2.y)}) scale(${v2.zoom})`,
|
|
8846
11385
|
width: state.document.width,
|
|
8847
11386
|
height: state.document.height,
|
|
8848
11387
|
...state.document.background ? { background: state.document.background } : {},
|
|
@@ -8851,16 +11390,20 @@ function getVectorSceneRenderData(state, options = {}) {
|
|
|
8851
11390
|
};
|
|
8852
11391
|
}
|
|
8853
11392
|
var toScreen = (state, p) => ({ x: p.x * state.viewport.zoom + state.viewport.x, y: p.y * state.viewport.zoom + state.viewport.y });
|
|
8854
|
-
function screenOutline(state, node) {
|
|
11393
|
+
function screenOutline(state, node, entered) {
|
|
8855
11394
|
const v2 = state.viewport;
|
|
8856
11395
|
const view = [v2.zoom, 0, 0, v2.zoom, v2.x, v2.y];
|
|
8857
11396
|
const parent = vectorParentMatrix(state.document.nodes, node.id) ?? IDENTITY_MATRIX;
|
|
8858
|
-
return
|
|
11397
|
+
return outlinePaths(node, parent, entered).map((path) => bezierPathToSvg(transformBezierPath(path, view), { precision: 2 })).join(" ");
|
|
8859
11398
|
}
|
|
8860
|
-
function
|
|
8861
|
-
if (node.type === "path") return [{
|
|
11399
|
+
function outlinePaths(node, parent, entered) {
|
|
11400
|
+
if (node.type === "path") return [vectorPathWorldBezier(node, parent, { effects: false })];
|
|
8862
11401
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
8863
|
-
|
|
11402
|
+
if (hasOwnVectorAppearance(node) && !entered.has(node.id)) return [transformBezierPath(vectorAppearanceOutline(node), m)];
|
|
11403
|
+
return node.children.filter((c) => c.visible !== false).flatMap((c) => outlinePaths(c, m, entered));
|
|
11404
|
+
}
|
|
11405
|
+
function getVectorEnteredGroups(state) {
|
|
11406
|
+
return vectorEnteredGroups(state.document.nodes, state.editingId ? [...state.selection, state.editingId] : state.selection);
|
|
8864
11407
|
}
|
|
8865
11408
|
function collect(node) {
|
|
8866
11409
|
if (node.type === "path") return [node];
|
|
@@ -8870,13 +11413,14 @@ function getVectorOverlayRenderData(state) {
|
|
|
8870
11413
|
const doc = state.document;
|
|
8871
11414
|
const z = state.viewport.zoom;
|
|
8872
11415
|
const outlines = [];
|
|
11416
|
+
const entered = getVectorEnteredGroups(state);
|
|
8873
11417
|
const seen = /* @__PURE__ */ new Set();
|
|
8874
11418
|
const outline = (id, selected2) => {
|
|
8875
11419
|
if (seen.has(id)) return;
|
|
8876
11420
|
const node = findVectorNode(doc.nodes, id);
|
|
8877
11421
|
if (!node || node.visible === false) return;
|
|
8878
11422
|
seen.add(id);
|
|
8879
|
-
outlines.push({ id, d: screenOutline(state, node), selected: selected2 });
|
|
11423
|
+
outlines.push({ id, d: screenOutline(state, node, entered), selected: selected2 });
|
|
8880
11424
|
};
|
|
8881
11425
|
for (const id of state.selection) outline(id, true);
|
|
8882
11426
|
if (state.editingId) outline(state.editingId, true);
|
|
@@ -8925,7 +11469,7 @@ function getVectorOverlayRenderData(state) {
|
|
|
8925
11469
|
const a = toScreen(state, last);
|
|
8926
11470
|
const p = toScreen(state, state.pointer);
|
|
8927
11471
|
const c = last.out ? toScreen(state, last.out) : null;
|
|
8928
|
-
penPreview = c ? `M${
|
|
11472
|
+
penPreview = c ? `M${fmt4(a.x)} ${fmt4(a.y)}Q${fmt4(c.x)} ${fmt4(c.y)} ${fmt4(p.x)} ${fmt4(p.y)}` : `M${fmt4(a.x)} ${fmt4(a.y)}L${fmt4(p.x)} ${fmt4(p.y)}`;
|
|
8929
11473
|
}
|
|
8930
11474
|
}
|
|
8931
11475
|
const origin = toScreen(state, { x: 0, y: 0 });
|
|
@@ -8933,45 +11477,45 @@ function getVectorOverlayRenderData(state) {
|
|
|
8933
11477
|
let status = null;
|
|
8934
11478
|
const it = state.interaction;
|
|
8935
11479
|
if (it?.type === "rotate") status = `${Math.round(it.angle)}\xB0`;
|
|
8936
|
-
else if (it?.type === "move" || it?.type === "anchors") status = `\u0394 ${
|
|
11480
|
+
else if (it?.type === "move" || it?.type === "anchors") status = `\u0394 ${fmt4(it.dx)}, ${fmt4(it.dy)}`;
|
|
8937
11481
|
else if (it?.type === "scale" || it?.type === "draw") {
|
|
8938
11482
|
const b = it.type === "draw" ? findVectorNode(doc.nodes, it.nodeId) ? vectorNodeBounds(findVectorNode(doc.nodes, it.nodeId)) : null : state.selectionFrame;
|
|
8939
|
-
if (b) status = `W ${
|
|
11483
|
+
if (b) status = `W ${fmt4(b.width)} \xD7 H ${fmt4(b.height)}`;
|
|
8940
11484
|
}
|
|
8941
11485
|
const guides = { lines: [], labels: [] };
|
|
8942
11486
|
if (state.guides) {
|
|
8943
11487
|
for (const l of state.guides.lines) {
|
|
8944
11488
|
const a = toScreen(state, { x: l.x1, y: l.y1 });
|
|
8945
11489
|
const b = toScreen(state, { x: l.x2, y: l.y2 });
|
|
8946
|
-
guides.lines.push({ kind: l.kind, x1:
|
|
11490
|
+
guides.lines.push({ kind: l.kind, x1: fmt4(a.x), y1: fmt4(a.y), x2: fmt4(b.x), y2: fmt4(b.y) });
|
|
8947
11491
|
}
|
|
8948
11492
|
for (const l of state.guides.labels) {
|
|
8949
11493
|
const p = toScreen(state, l);
|
|
8950
|
-
guides.labels.push({ kind: l.kind, x:
|
|
11494
|
+
guides.labels.push({ kind: l.kind, x: fmt4(p.x), y: fmt4(p.y), text: l.text });
|
|
8951
11495
|
}
|
|
8952
11496
|
}
|
|
8953
11497
|
let toolStroke = null;
|
|
8954
11498
|
if (it?.type === "stroke") {
|
|
8955
11499
|
const pts = it.points.map((q) => toScreen(state, q));
|
|
8956
|
-
const d = pts.map((q, i) => `${i ? "L" : "M"}${
|
|
8957
|
-
toolStroke = { tool: it.tool, d, width:
|
|
11500
|
+
const d = pts.map((q, i) => `${i ? "L" : "M"}${fmt4(q.x)} ${fmt4(q.y)}`).join("") + (pts.length === 1 ? `L${fmt4(pts[0].x)} ${fmt4(pts[0].y)}` : "");
|
|
11501
|
+
toolStroke = { tool: it.tool, d, width: fmt4(it.width * z) };
|
|
8958
11502
|
}
|
|
8959
11503
|
let pencilString = null;
|
|
8960
11504
|
if (it?.type === "draw" && it.string) {
|
|
8961
11505
|
const a = toScreen(state, it.string[0]);
|
|
8962
11506
|
const b = toScreen(state, it.string[1]);
|
|
8963
|
-
pencilString = { x1:
|
|
11507
|
+
pencilString = { x1: fmt4(a.x), y1: fmt4(a.y), x2: fmt4(b.x), y2: fmt4(b.y) };
|
|
8964
11508
|
}
|
|
8965
11509
|
const dragged = it?.type === "corner" ? it.ref : null;
|
|
8966
11510
|
const cornerWidgets = getVectorCornerWidgets(state).map((w) => {
|
|
8967
11511
|
const s = toScreen(state, w);
|
|
8968
|
-
return { ref: w.ref, x:
|
|
11512
|
+
return { ref: w.ref, x: fmt4(s.x), y: fmt4(s.y), radius: w.radius, kind: w.kind, hovered: sameAnchor(state.hover?.corner, w.ref), active: !!dragged };
|
|
8969
11513
|
});
|
|
8970
|
-
if (it?.type === "corner") status = `R ${
|
|
8971
|
-
const cutPoint = state.tool === "scissors" && state.hover?.segment && !it ? (({ x, y }) => ({ x:
|
|
11514
|
+
if (it?.type === "corner") status = `R ${fmt4(it.radius)}`;
|
|
11515
|
+
const cutPoint = state.tool === "scissors" && state.hover?.segment && !it ? (({ x, y }) => ({ x: fmt4(x), y: fmt4(y) }))(toScreen(state, state.hover.segment.point)) : null;
|
|
8972
11516
|
const sc = (p) => {
|
|
8973
11517
|
const q = toScreen(state, p);
|
|
8974
|
-
return { x:
|
|
11518
|
+
return { x: fmt4(q.x), y: fmt4(q.y) };
|
|
8975
11519
|
};
|
|
8976
11520
|
const sameWidth = (a, b) => !!a && a.nodeId === b.nodeId && a.index === b.index;
|
|
8977
11521
|
const widthPoints = getVectorWidthHandles(state).map((h) => {
|
|
@@ -8982,7 +11526,7 @@ function getVectorOverlayRenderData(state) {
|
|
|
8982
11526
|
});
|
|
8983
11527
|
const spot = state.tool === "width" && !it ? state.hover?.strokeSpot : null;
|
|
8984
11528
|
const widthSpot = spot ? { ...sc(spot.point), left: sc(spot.left), right: sc(spot.right) } : null;
|
|
8985
|
-
if (it?.type === "width") status = it.mode === "move" ? "Move width point" : it.mode === "copy" ? "Copy width point" : `W ${
|
|
11529
|
+
if (it?.type === "width") status = it.mode === "move" ? "Move width point" : it.mode === "copy" ? "Copy width point" : `W ${fmt4(it.width)}`;
|
|
8986
11530
|
let builder = null;
|
|
8987
11531
|
if (state.builder) {
|
|
8988
11532
|
const v2 = state.viewport;
|
|
@@ -9005,7 +11549,43 @@ function getVectorOverlayRenderData(state) {
|
|
|
9005
11549
|
}
|
|
9006
11550
|
const blend = blendOverlay(state);
|
|
9007
11551
|
if (state.tool === "blend" && !it) status = state.blendStart ? "Click another shape to blend into" : "Click a shape to blend from";
|
|
9008
|
-
|
|
11552
|
+
let gradient = null;
|
|
11553
|
+
if (state.tool === "gradient") {
|
|
11554
|
+
gradient = gradientOverlay(state);
|
|
11555
|
+
if (!gradient && !it) status = "Drag across a shape to set a gradient";
|
|
11556
|
+
else if (it?.type === "gradient" && it.removing) status = "Release to delete the stop";
|
|
11557
|
+
}
|
|
11558
|
+
return { outlines, selectionBox, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint, cornerWidgets, widthPoints, widthSpot, builder, blend, gradient };
|
|
11559
|
+
}
|
|
11560
|
+
function gradientOverlay(state) {
|
|
11561
|
+
const a = getVectorGradientAnnotator(state.document, state.selection, state.viewport, state.gradientEdit.target);
|
|
11562
|
+
if (!a) return null;
|
|
11563
|
+
const hover = state.interaction ? null : state.hover?.gradient;
|
|
11564
|
+
const handleHovered = (h) => hover?.kind === "handle" && hover.handle === h;
|
|
11565
|
+
const pt2 = (p) => ({ x: fmt4(p.x), y: fmt4(p.y) });
|
|
11566
|
+
const centred = isCentredVectorGradient(a.gradient.type);
|
|
11567
|
+
const removing = state.interaction?.type === "gradient" && !!state.interaction.removing;
|
|
11568
|
+
return {
|
|
11569
|
+
nodeId: a.nodeId,
|
|
11570
|
+
type: a.gradient.type,
|
|
11571
|
+
start: { ...pt2(a.start), hovered: handleHovered("start") },
|
|
11572
|
+
end: { ...pt2(a.end), hovered: handleHovered(centred ? "radius" : "end"), handle: centred ? "radius" : "end" },
|
|
11573
|
+
aspect: a.aspect ? { ...pt2(a.aspect), hovered: handleHovered("aspect") } : null,
|
|
11574
|
+
focal: a.focal ? { ...pt2(a.focal), hovered: handleHovered("focal") } : null,
|
|
11575
|
+
stops: a.stops.map((s) => ({
|
|
11576
|
+
index: s.index,
|
|
11577
|
+
...pt2(s),
|
|
11578
|
+
barX: fmt4(s.x - a.normal.x * VECTOR_GRADIENT_STOP_GAP),
|
|
11579
|
+
barY: fmt4(s.y - a.normal.y * VECTOR_GRADIENT_STOP_GAP),
|
|
11580
|
+
color: s.color,
|
|
11581
|
+
opacity: s.opacity ?? 1,
|
|
11582
|
+
selected: state.gradientEdit.stop === s.index,
|
|
11583
|
+
hovered: hover?.kind === "stop" && hover.index === s.index
|
|
11584
|
+
})),
|
|
11585
|
+
midpoints: a.midpoints.map((m) => ({ index: m.index, ...pt2(m), hovered: hover?.kind === "midpoint" && hover.index === m.index })),
|
|
11586
|
+
extent: a.extent,
|
|
11587
|
+
removing
|
|
11588
|
+
};
|
|
9009
11589
|
}
|
|
9010
11590
|
function selectedBlends(state) {
|
|
9011
11591
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -9022,7 +11602,7 @@ function blendOverlay(state) {
|
|
|
9022
11602
|
const doc = state.document;
|
|
9023
11603
|
const sc = (p, m) => {
|
|
9024
11604
|
const q = { x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] };
|
|
9025
|
-
return { x:
|
|
11605
|
+
return { x: fmt4(q.x), y: fmt4(q.y) };
|
|
9026
11606
|
};
|
|
9027
11607
|
const spines = selectedBlends(state).flatMap((g) => {
|
|
9028
11608
|
const s = vectorBlendSpine(g);
|
|
@@ -9041,7 +11621,7 @@ function blendOverlay(state) {
|
|
|
9041
11621
|
const from = state.blendStart ? findVectorNode(doc.nodes, state.blendStart.nodeId) : void 0;
|
|
9042
11622
|
if (from && state.blendStart) {
|
|
9043
11623
|
const a = state.blendStart.anchor === null ? void 0 : getAnchorWorld(doc, { nodeId: from.id, subpath: 0, index: state.blendStart.anchor });
|
|
9044
|
-
start = { d: screenOutline(state, from), anchor: a ? sc(a, view) : null };
|
|
11624
|
+
start = { d: screenOutline(state, from, /* @__PURE__ */ new Set()), anchor: a ? sc(a, view) : null };
|
|
9045
11625
|
}
|
|
9046
11626
|
const hovered = state.hover?.nodeId ? findVectorNode(doc.nodes, state.hover.nodeId) : void 0;
|
|
9047
11627
|
if (hovered?.type === "path" && hovered.subpaths[0]) {
|
|
@@ -9100,7 +11680,7 @@ var VECTOR_OVERLAY_THEME = {
|
|
|
9100
11680
|
builderHatchOpacity: 0.6
|
|
9101
11681
|
};
|
|
9102
11682
|
function vectorDiamondPath(x, y, r = VECTOR_OVERLAY_THEME.widthPointSize) {
|
|
9103
|
-
return `M${
|
|
11683
|
+
return `M${fmt4(x)} ${fmt4(y - r)}L${fmt4(x + r)} ${fmt4(y)}L${fmt4(x)} ${fmt4(y + r)}L${fmt4(x - r)} ${fmt4(y)}Z`;
|
|
9104
11684
|
}
|
|
9105
11685
|
function toVectorModifiers(event) {
|
|
9106
11686
|
return { shift: !!event.shiftKey, alt: !!event.altKey, mod: !!(event.metaKey || event.ctrlKey) };
|
|
@@ -9205,6 +11785,45 @@ function vectorPaintToHex(paint) {
|
|
|
9205
11785
|
const hex = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
|
|
9206
11786
|
return `#${hex.toLowerCase()}`;
|
|
9207
11787
|
}
|
|
11788
|
+
var VECTOR_GRADIENT_TYPE_LABELS = { linear: "Linear", radial: "Radial", conic: "Conic", reflected: "Reflected", diamond: "Diamond" };
|
|
11789
|
+
var VECTOR_GRADIENT_SPREAD_LABELS = { pad: "Pad", reflect: "Reflect", repeat: "Repeat" };
|
|
11790
|
+
var VECTOR_GRADIENT_INTERPOLATION_LABELS = { srgb: "sRGB", linear: "Linear light", oklab: "Oklab" };
|
|
11791
|
+
var VECTOR_STROKE_GRADIENT_MODE_LABELS = { within: "Within stroke", along: "Along stroke", across: "Across stroke" };
|
|
11792
|
+
function getVectorGradientEditor(state) {
|
|
11793
|
+
const paths = [];
|
|
11794
|
+
for (const id of state.selection) {
|
|
11795
|
+
const node = findVectorNode(state.document.nodes, id);
|
|
11796
|
+
if (node) paths.push(...collect(node));
|
|
11797
|
+
}
|
|
11798
|
+
if (!paths.length) return null;
|
|
11799
|
+
const target = state.gradientEdit.target;
|
|
11800
|
+
const paint = paths[0][target];
|
|
11801
|
+
const gradient = isVectorGradient(paint) ? paint : null;
|
|
11802
|
+
const sorted = gradient ? sortedVectorGradientStops(gradient) : [];
|
|
11803
|
+
const stopIndex = state.gradientEdit.stop;
|
|
11804
|
+
const stops = sorted.map((s, index) => ({ ...s, index, selected: index === stopIndex }));
|
|
11805
|
+
const sel = stopIndex !== null && sorted[stopIndex] ? { ...sorted[stopIndex], index: stopIndex, last: stopIndex === sorted.length - 1 } : null;
|
|
11806
|
+
const preview = gradient ? `linear-gradient(to right, ${expandVectorGradientStops(gradient).map((s) => {
|
|
11807
|
+
const c = parseVectorColor(s.color);
|
|
11808
|
+
const a = (c?.[3] ?? 1) * (s.opacity ?? 1);
|
|
11809
|
+
const color = c ? `rgba(${Math.round(c[0] * 255)}, ${Math.round(c[1] * 255)}, ${Math.round(c[2] * 255)}, ${Math.round(a * 1e3) / 1e3})` : s.color;
|
|
11810
|
+
return `${color} ${Math.round(s.offset * 1e4) / 100}%`;
|
|
11811
|
+
}).join(", ")})` : null;
|
|
11812
|
+
return {
|
|
11813
|
+
target,
|
|
11814
|
+
gradient,
|
|
11815
|
+
color: vectorPaintToHex(paint),
|
|
11816
|
+
stops,
|
|
11817
|
+
selected: sel,
|
|
11818
|
+
preview,
|
|
11819
|
+
type: gradient?.type ?? null,
|
|
11820
|
+
spread: gradient?.spread ?? "pad",
|
|
11821
|
+
interpolation: gradient?.interpolation ?? "srgb",
|
|
11822
|
+
smoothness: gradient?.smoothness ?? 0,
|
|
11823
|
+
strokeMode: gradient?.strokeMode ?? "within",
|
|
11824
|
+
mixed: paths.some((p) => JSON.stringify(p[target] ?? null) !== JSON.stringify(paint ?? null))
|
|
11825
|
+
};
|
|
11826
|
+
}
|
|
9208
11827
|
var VECTOR_PATH_ACTIONS = [
|
|
9209
11828
|
{ id: "join", label: "\u22C8", title: "Join", shortcut: "J", enabled: (s, p) => s + p > 0, run: (c) => c.joinPaths() },
|
|
9210
11829
|
{ id: "average", label: "\u22B9", title: "Average Points", shortcut: "Alt+J", enabled: (_s, p) => p > 1, run: (c) => c.averageAnchors("both") },
|
|
@@ -9226,8 +11845,28 @@ var VECTOR_EDIT_TOGGLES = [
|
|
|
9226
11845
|
{ key: "curvatureMoves", label: "Curv", title: "Curvature point moves (Alt flips while dragging)" },
|
|
9227
11846
|
{ key: "recognizeShapes", label: "Shapes", title: "Turn pencil scribbles into shapes" }
|
|
9228
11847
|
];
|
|
11848
|
+
var VECTOR_EFFECT_MENU = VECTOR_EFFECT_TYPES.map((type) => ({ type, label: VECTOR_EFFECT_LABELS[type] }));
|
|
11849
|
+
function getVectorEffectsPanel(state) {
|
|
11850
|
+
const nodes = state.selection.map((id) => findVectorNode(state.document.nodes, id)).filter((n) => !!n && !n.blendStep);
|
|
11851
|
+
if (!nodes.length) return null;
|
|
11852
|
+
const one = nodes.length === 1 ? nodes[0] : null;
|
|
11853
|
+
return {
|
|
11854
|
+
nodeId: one?.id ?? null,
|
|
11855
|
+
count: nodes.length,
|
|
11856
|
+
effects: (one?.effects ?? []).map((e) => ({
|
|
11857
|
+
id: e.id,
|
|
11858
|
+
type: e.type,
|
|
11859
|
+
label: VECTOR_EFFECT_LABELS[e.type] ?? e.type,
|
|
11860
|
+
enabled: e.enabled !== false,
|
|
11861
|
+
params: e.params,
|
|
11862
|
+
specs: VECTOR_EFFECT_PARAM_SPECS[e.type] ?? []
|
|
11863
|
+
})),
|
|
11864
|
+
expandable: nodes.some(hasVectorAppearance)
|
|
11865
|
+
};
|
|
11866
|
+
}
|
|
9229
11867
|
|
|
9230
11868
|
// 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.";
|
|
9231
11870
|
var fail = (message) => {
|
|
9232
11871
|
throw new Error(message);
|
|
9233
11872
|
};
|
|
@@ -9272,8 +11911,41 @@ var checkBlendOptions = (params) => {
|
|
|
9272
11911
|
if (params?.steps !== void 0 && !(Number.isFinite(params.steps) && params.steps >= 1)) fail("steps must be at least 1.");
|
|
9273
11912
|
if (params?.distance !== void 0 && !(Number.isFinite(params.distance) && params.distance > 0)) fail("distance must be positive.");
|
|
9274
11913
|
};
|
|
11914
|
+
var POINT = { type: "object", properties: { x: { type: "number" }, y: { type: "number" } }, required: ["x", "y"] };
|
|
11915
|
+
var GRADIENT_PROPS = {
|
|
11916
|
+
type: { type: "string", enum: VECTOR_GRADIENT_TYPES, description: "linear, radial, conic (an angle sweep), reflected (linear mirrored about the start) or diamond." },
|
|
11917
|
+
stops: {
|
|
11918
|
+
type: "array",
|
|
11919
|
+
minItems: 1,
|
|
11920
|
+
items: {
|
|
11921
|
+
type: "object",
|
|
11922
|
+
properties: {
|
|
11923
|
+
offset: { type: "number", minimum: 0, maximum: 1 },
|
|
11924
|
+
color: { type: "string", description: "A CSS colour." },
|
|
11925
|
+
opacity: { type: "number", minimum: 0, maximum: 1 },
|
|
11926
|
+
midpoint: { type: "number", minimum: 0.05, maximum: 0.95, description: "Where between this stop and the next the colours are half-mixed (default 0.5)." }
|
|
11927
|
+
},
|
|
11928
|
+
required: ["offset", "color"]
|
|
11929
|
+
}
|
|
11930
|
+
},
|
|
11931
|
+
angle: { type: "number", description: "Shorthand direction in degrees (0 up, clockwise): linear / reflected direction across the box, conic start angle, diamond turn." },
|
|
11932
|
+
cx: { type: "number", description: "Shorthand centre x of radial / conic / diamond gradients, a fraction of the box (default 0.5)." },
|
|
11933
|
+
cy: { type: "number" },
|
|
11934
|
+
r: { type: "number", description: "Shorthand radius, a fraction of the box (default 0.5)." },
|
|
11935
|
+
start: { ...POINT, description: "Explicit start (centre of radial / conic / diamond), in `units`. Wins over the shorthand." },
|
|
11936
|
+
end: { ...POINT, description: "Explicit end of the main axis (the radius of radial / conic / diamond)." },
|
|
11937
|
+
width: { ...POINT, description: "End of the secondary axis (aspect and turn of radial / conic / diamond); default square to the main axis, as long." },
|
|
11938
|
+
focal: { ...POINT, description: "Radial focal point (where t = 0), in `units`." },
|
|
11939
|
+
focalRadius: { type: "number", minimum: 0, maximum: 0.99, description: "Radial focal circle radius as a fraction of the radius (SVG fr)." },
|
|
11940
|
+
units: { type: "string", enum: ["objectBoundingBox", "userSpaceOnUse"], description: "Space of start / end / width / focal: fractions of the shape box (default) or the node's own coordinates." },
|
|
11941
|
+
spread: { type: "string", enum: VECTOR_GRADIENT_SPREADS, description: "What paints past the ends: pad (default), reflect or repeat." },
|
|
11942
|
+
interpolation: { type: "string", enum: VECTOR_GRADIENT_INTERPOLATIONS, description: "Colour mixing space: srgb (default), linear (light) or oklab." },
|
|
11943
|
+
smoothness: { type: "number", minimum: 0, maximum: 1, description: "Bends the ramp along a smooth spline through the stops (0 = straight)." },
|
|
11944
|
+
strokeMode: { type: "string", enum: VECTOR_STROKE_GRADIENT_MODES, description: "On strokes: within (default, like a fill), along the path or across it." }
|
|
11945
|
+
};
|
|
9275
11946
|
var PAINT = {
|
|
9276
|
-
description: 'A CSS colour, `null` for none, or a gradient `{ type: "linear"
|
|
11947
|
+
description: 'A CSS colour, `null` for none, or a gradient object: `{ type: "linear" | "radial" | "conic" | "reflected" | "diamond", stops: [{ offset, color, opacity?, midpoint? }] }` plus the shorthand `angle` / `cx, cy, r` or explicit `start`, `end`, `width`, `focal`, `focalRadius`, `units`, and `spread`, `interpolation`, `smoothness`, `strokeMode` (see vector.setGradient).',
|
|
11948
|
+
anyOf: [{ type: "string" }, { type: "null" }, { type: "object", properties: GRADIENT_PROPS, required: ["type", "stops"] }]
|
|
9277
11949
|
};
|
|
9278
11950
|
var STYLE_PROPS = {
|
|
9279
11951
|
fill: PAINT,
|
|
@@ -9294,6 +11966,10 @@ var STYLE_PROPS = {
|
|
|
9294
11966
|
var pickStyle = (params) => {
|
|
9295
11967
|
const out = {};
|
|
9296
11968
|
for (const key of Object.keys(STYLE_PROPS)) if (params[key] !== void 0) out[key] = params[key];
|
|
11969
|
+
for (const key of ["fill", "stroke"]) {
|
|
11970
|
+
const paint = out[key];
|
|
11971
|
+
if (paint && typeof paint === "object") out[key] = sanitizeVectorGradient(paint) ?? fail(`${key} is not a valid gradient: give a type and at least one stop { offset, color }.`);
|
|
11972
|
+
}
|
|
9297
11973
|
return out;
|
|
9298
11974
|
};
|
|
9299
11975
|
var check = (result) => {
|
|
@@ -9338,7 +12014,10 @@ function createVectorCommands(controller, options = {}) {
|
|
|
9338
12014
|
selected: state.selection.includes(n.id),
|
|
9339
12015
|
...n.type === "path" ? { fill: n.fill ?? null, stroke: n.stroke ?? null, strokeWidth: n.strokeWidth ?? 1, d: vectorPathData(n, 2) } : { children: n.children.length },
|
|
9340
12016
|
...n.type === "group" && n.blend ? { blend: { ...resolveVectorBlendOptions(n.blend), spine: !!n.blend.spine, keys: vectorBlendKeys(n).map((k) => k.id) } } : {},
|
|
9341
|
-
...n.blendStep ? { blendStep: true } : {}
|
|
12017
|
+
...n.blendStep ? { blendStep: true } : {},
|
|
12018
|
+
...n.effects?.length ? { effects: n.effects } : {},
|
|
12019
|
+
...n.type === "group" && n.liveBoolean ? { liveBoolean: n.liveBoolean } : {},
|
|
12020
|
+
...n.booleanOperation ? { booleanOperation: n.booleanOperation } : {}
|
|
9342
12021
|
});
|
|
9343
12022
|
if (n.type === "group") visit(n.children, depth + 1);
|
|
9344
12023
|
}
|
|
@@ -9431,6 +12110,26 @@ function createVectorCommands(controller, options = {}) {
|
|
|
9431
12110
|
return { ok: true };
|
|
9432
12111
|
}
|
|
9433
12112
|
},
|
|
12113
|
+
{
|
|
12114
|
+
...base,
|
|
12115
|
+
id: `${prefix}setGradient`,
|
|
12116
|
+
label: "Gradient\u2026",
|
|
12117
|
+
keywords: ["gradient", "linear", "radial", "conic", "angle", "reflected", "diamond", "stops", "midpoint", "oklab", "spread", "focal", "along stroke", "across stroke", "reverse"],
|
|
12118
|
+
description: "Edits the gradient of the fill (default) or stroke of paths (groups apply to every path inside): any gradient field (missing ones keep their value; a colour becomes a gradient from it to white), `aspect` (secondary axis over main axis), `reverse` (flip the stops and midpoints), `focal` / `width` null to remove them. Midpoints, smoothness and oklab / linear interpolation export to SVG as extra stops; conic and diamond gradients as patterns.",
|
|
12119
|
+
params: { type: "object", properties: { ids: IDS, target: { type: "string", enum: ["fill", "stroke"] }, ...GRADIENT_PROPS, aspect: { type: "number" }, reverse: { type: "boolean" } } },
|
|
12120
|
+
enabled: enabledFor,
|
|
12121
|
+
undoable: true,
|
|
12122
|
+
run: (_ctx, params) => {
|
|
12123
|
+
const { ids: _i, target, ...patch } = params ?? {};
|
|
12124
|
+
if (patch.type !== void 0 && !VECTOR_GRADIENT_TYPES.includes(patch.type)) fail(`Unknown gradient type "${patch.type}".`);
|
|
12125
|
+
if (patch.stops !== void 0 && !(Array.isArray(patch.stops) && patch.stops.length)) fail("stops needs at least one { offset, color }.");
|
|
12126
|
+
check(controller.setGradient(patch, { ids: ids(params), target: target ?? "fill" }));
|
|
12127
|
+
const id = ids(params)[0];
|
|
12128
|
+
const node = id ? controller.getNode(id) : void 0;
|
|
12129
|
+
const path = node?.type === "path" ? node : void 0;
|
|
12130
|
+
return { ok: true, ...path ? { gradient: path[target ?? "fill"] } : {} };
|
|
12131
|
+
}
|
|
12132
|
+
},
|
|
9434
12133
|
{
|
|
9435
12134
|
...base,
|
|
9436
12135
|
id: `${prefix}setCorners`,
|
|
@@ -9574,15 +12273,101 @@ function createVectorCommands(controller, options = {}) {
|
|
|
9574
12273
|
id: `${prefix}boolean`,
|
|
9575
12274
|
label: "Boolean Operation\u2026",
|
|
9576
12275
|
keywords: ["union", "unite", "subtract", "intersect", "exclude", "combine", "pathfinder"],
|
|
9577
|
-
description: "Combines two or more paths with union, subtract (upper shapes from the lowest), intersect or exclude.",
|
|
9578
|
-
params: {
|
|
12276
|
+
description: "Combines two or more paths with union, subtract (upper shapes from the lowest), intersect or exclude. With `live`, the shapes stay editable operands of a live boolean group instead (a lone live group gets the operation; its operands get their own).",
|
|
12277
|
+
params: {
|
|
12278
|
+
type: "object",
|
|
12279
|
+
properties: {
|
|
12280
|
+
operation: { type: "string", enum: ["union", "subtract", "intersect", "exclude"] },
|
|
12281
|
+
ids: IDS,
|
|
12282
|
+
live: { type: "boolean", description: "Keep the operands editable in a live boolean group (default false: one new path)." }
|
|
12283
|
+
},
|
|
12284
|
+
required: ["operation"]
|
|
12285
|
+
},
|
|
9579
12286
|
undoable: true,
|
|
9580
12287
|
run: (_ctx, params) => {
|
|
9581
|
-
const id = controller.booleanOp(params.operation, { ids: ids(params) });
|
|
9582
|
-
if (!id) fail("Select at least two overlapping paths (the result was empty).");
|
|
12288
|
+
const id = controller.booleanOp(params.operation, { ids: ids(params), live: params.live });
|
|
12289
|
+
if (!id) fail(params.live ? "Select two or more nodes with one parent, a live boolean group or its operands." : "Select at least two overlapping paths (the result was empty).");
|
|
9583
12290
|
return { id };
|
|
9584
12291
|
}
|
|
9585
12292
|
},
|
|
12293
|
+
{
|
|
12294
|
+
...base,
|
|
12295
|
+
id: `${prefix}addEffect`,
|
|
12296
|
+
label: "Add Path Effect\u2026",
|
|
12297
|
+
keywords: ["effect", "trim", "round corners", "pucker", "bloat", "twist", "zig zag", "wiggle", "offset", "merge", "repeater", "appearance", "live"],
|
|
12298
|
+
description: "Adds a live path effect to the bottom of the effect stack of nodes (paths or groups). Effects apply top to bottom; effects after a repeater act on its copies. Returns the new effect ids.",
|
|
12299
|
+
params: {
|
|
12300
|
+
type: "object",
|
|
12301
|
+
properties: {
|
|
12302
|
+
type: { type: "string", enum: VECTOR_EFFECT_TYPES },
|
|
12303
|
+
params: { type: "object", description: EFFECT_PARAMS_DESCRIPTION },
|
|
12304
|
+
index: { type: "integer", minimum: 0, description: "Position in the stack (default: last)." },
|
|
12305
|
+
ids: IDS
|
|
12306
|
+
},
|
|
12307
|
+
required: ["type"]
|
|
12308
|
+
},
|
|
12309
|
+
undoable: true,
|
|
12310
|
+
enabled: enabledFor,
|
|
12311
|
+
run: (_ctx, params) => {
|
|
12312
|
+
if (!VECTOR_EFFECT_TYPES.includes(params.type)) fail(`Unknown effect type "${params.type}".`);
|
|
12313
|
+
const targets = ids(params);
|
|
12314
|
+
const created = controller.addEffect(params.type, { ids: targets, params: params.params, index: params.index });
|
|
12315
|
+
if (!created.length) fail("Nothing to add the effect to.");
|
|
12316
|
+
return { effects: targets.map((id, i) => ({ nodeId: id, effectId: created[i] })) };
|
|
12317
|
+
}
|
|
12318
|
+
},
|
|
12319
|
+
{
|
|
12320
|
+
...base,
|
|
12321
|
+
id: `${prefix}updateEffect`,
|
|
12322
|
+
label: "Edit Path Effect\u2026",
|
|
12323
|
+
keywords: ["effect", "parameters", "enable", "disable", "reorder"],
|
|
12324
|
+
description: "Changes a path effect: its parameters, whether it is enabled, and its position in the stack.",
|
|
12325
|
+
params: {
|
|
12326
|
+
type: "object",
|
|
12327
|
+
properties: {
|
|
12328
|
+
nodeId: { type: "string" },
|
|
12329
|
+
effectId: { type: "string" },
|
|
12330
|
+
params: { type: "object", description: EFFECT_PARAMS_DESCRIPTION },
|
|
12331
|
+
enabled: { type: "boolean" },
|
|
12332
|
+
index: { type: "integer", minimum: 0, description: "Move it to this position in the stack (0 applies first)." }
|
|
12333
|
+
},
|
|
12334
|
+
required: ["nodeId", "effectId"]
|
|
12335
|
+
},
|
|
12336
|
+
undoable: true,
|
|
12337
|
+
run: (_ctx, params) => {
|
|
12338
|
+
if (params.params !== void 0 || params.enabled !== void 0) check(controller.updateEffect(params.nodeId, params.effectId, { params: params.params, enabled: params.enabled }));
|
|
12339
|
+
if (params.index !== void 0) check(controller.moveEffect(params.nodeId, params.effectId, params.index));
|
|
12340
|
+
return { effects: controller.getNode(params.nodeId)?.effects ?? [] };
|
|
12341
|
+
}
|
|
12342
|
+
},
|
|
12343
|
+
{
|
|
12344
|
+
...base,
|
|
12345
|
+
id: `${prefix}removeEffect`,
|
|
12346
|
+
label: "Remove Path Effect",
|
|
12347
|
+
keywords: ["effect", "delete"],
|
|
12348
|
+
description: "Removes a path effect from a node.",
|
|
12349
|
+
params: { type: "object", properties: { nodeId: { type: "string" }, effectId: { type: "string" } }, required: ["nodeId", "effectId"] },
|
|
12350
|
+
undoable: true,
|
|
12351
|
+
run: (_ctx, params) => {
|
|
12352
|
+
check(controller.removeEffect(params.nodeId, params.effectId));
|
|
12353
|
+
return { ok: true };
|
|
12354
|
+
}
|
|
12355
|
+
},
|
|
12356
|
+
{
|
|
12357
|
+
...base,
|
|
12358
|
+
id: `${prefix}expandAppearance`,
|
|
12359
|
+
label: "Expand Appearance",
|
|
12360
|
+
keywords: ["effects", "bake", "flatten", "live boolean", "expand"],
|
|
12361
|
+
description: "Bakes the path effects and live booleans of nodes (and everything inside them) into plain paths.",
|
|
12362
|
+
params: { type: "object", properties: { ids: IDS } },
|
|
12363
|
+
undoable: true,
|
|
12364
|
+
enabled: enabledFor,
|
|
12365
|
+
run: (_ctx, params) => {
|
|
12366
|
+
const expanded = controller.expandAppearance({ ids: ids(params) });
|
|
12367
|
+
if (!expanded.length) fail("Nothing selected has path effects or a live boolean.");
|
|
12368
|
+
return { ids: expanded };
|
|
12369
|
+
}
|
|
12370
|
+
},
|
|
9586
12371
|
{
|
|
9587
12372
|
...base,
|
|
9588
12373
|
id: `${prefix}blend`,
|
|
@@ -10030,8 +12815,25 @@ export {
|
|
|
10030
12815
|
VECTOR_CORNER_KIND_LABELS,
|
|
10031
12816
|
VECTOR_CORNER_WIDGET_INSET,
|
|
10032
12817
|
VECTOR_EDIT_TOGGLES,
|
|
12818
|
+
VECTOR_EFFECT_DEFAULTS,
|
|
12819
|
+
VECTOR_EFFECT_LABELS,
|
|
12820
|
+
VECTOR_EFFECT_MENU,
|
|
12821
|
+
VECTOR_EFFECT_PARAM_SPECS,
|
|
12822
|
+
VECTOR_EFFECT_TYPES,
|
|
10033
12823
|
VECTOR_FILL_GAP,
|
|
12824
|
+
VECTOR_GRADIENT_INTERPOLATIONS,
|
|
12825
|
+
VECTOR_GRADIENT_INTERPOLATION_LABELS,
|
|
12826
|
+
VECTOR_GRADIENT_MAX_PERIODS,
|
|
12827
|
+
VECTOR_GRADIENT_MIDPOINT_GAP,
|
|
12828
|
+
VECTOR_GRADIENT_MIN_STOPS,
|
|
12829
|
+
VECTOR_GRADIENT_OFF_BAR,
|
|
12830
|
+
VECTOR_GRADIENT_SPREADS,
|
|
12831
|
+
VECTOR_GRADIENT_SPREAD_LABELS,
|
|
12832
|
+
VECTOR_GRADIENT_STOP_GAP,
|
|
12833
|
+
VECTOR_GRADIENT_TYPES,
|
|
12834
|
+
VECTOR_GRADIENT_TYPE_LABELS,
|
|
10034
12835
|
VECTOR_INK_MAX_CORNERS,
|
|
12836
|
+
VECTOR_MERGE_MODES,
|
|
10035
12837
|
VECTOR_OVERLAY_THEME,
|
|
10036
12838
|
VECTOR_PATHFINDER_ACTIONS,
|
|
10037
12839
|
VECTOR_PATHFINDER_LABELS,
|
|
@@ -10041,6 +12843,8 @@ export {
|
|
|
10041
12843
|
VECTOR_PRESSURE_NOMINAL,
|
|
10042
12844
|
VECTOR_ROTATE_HANDLE_OFFSET,
|
|
10043
12845
|
VECTOR_STROKE_ALIGN_LABELS,
|
|
12846
|
+
VECTOR_STROKE_GRADIENT_MODES,
|
|
12847
|
+
VECTOR_STROKE_GRADIENT_MODE_LABELS,
|
|
10044
12848
|
VECTOR_STYLE_KEYS,
|
|
10045
12849
|
VECTOR_TOOLS,
|
|
10046
12850
|
VECTOR_TOOL_GLYPHS,
|
|
@@ -10053,11 +12857,15 @@ export {
|
|
|
10053
12857
|
VECTOR_WIDTH_SAME_T,
|
|
10054
12858
|
VectorEditorController,
|
|
10055
12859
|
VectorSnapTargets,
|
|
12860
|
+
activeVectorEffects,
|
|
10056
12861
|
addBuilderHit,
|
|
10057
12862
|
applyMatrix,
|
|
10058
12863
|
applyMatrixLinear,
|
|
10059
12864
|
applyVectorBlendOptions,
|
|
10060
12865
|
applyVectorCornersAttribute,
|
|
12866
|
+
applyVectorEffects,
|
|
12867
|
+
applyVectorGradientPatch,
|
|
12868
|
+
applyVectorGradientSpread,
|
|
10061
12869
|
arrowheadInset,
|
|
10062
12870
|
arrowheadPath,
|
|
10063
12871
|
arrowheadWeight,
|
|
@@ -10065,6 +12873,7 @@ export {
|
|
|
10065
12873
|
bezierToSubpaths,
|
|
10066
12874
|
boundsFromPoints,
|
|
10067
12875
|
boundsIntersect,
|
|
12876
|
+
clearVectorLiveBooleanCache,
|
|
10068
12877
|
cloneAnchor,
|
|
10069
12878
|
cloneSubpath,
|
|
10070
12879
|
cloneVectorDocument,
|
|
@@ -10072,27 +12881,38 @@ export {
|
|
|
10072
12881
|
collectPathNodes,
|
|
10073
12882
|
constrainToLeave,
|
|
10074
12883
|
convertAnchor,
|
|
12884
|
+
convertVectorGradient,
|
|
10075
12885
|
cornerBisectorPoint,
|
|
10076
12886
|
cornerRadiusChange,
|
|
10077
12887
|
cornerSetback,
|
|
10078
12888
|
correspondMorphContours,
|
|
10079
12889
|
createVectorCommands,
|
|
12890
|
+
createVectorEffect,
|
|
10080
12891
|
createVectorMorph,
|
|
10081
12892
|
curvatureMoveAnchor,
|
|
10082
12893
|
cutSubpathAt,
|
|
10083
12894
|
dashIntervals,
|
|
10084
12895
|
decomposeRigid,
|
|
12896
|
+
defaultVectorGradient,
|
|
12897
|
+
effectPathLength,
|
|
12898
|
+
effectPathVertexCount,
|
|
10085
12899
|
effectiveStrokeAlign,
|
|
10086
12900
|
ellipseSubpath,
|
|
10087
12901
|
eraseFromFill,
|
|
10088
12902
|
eraseFromOutline,
|
|
10089
12903
|
eraserRegion,
|
|
10090
12904
|
evaluateVectorPathKeyframes,
|
|
12905
|
+
expandVectorAppearance,
|
|
12906
|
+
expandVectorAppearances,
|
|
10091
12907
|
expandVectorBlend,
|
|
10092
12908
|
expandVectorCorners,
|
|
10093
12909
|
expandVectorCornersOf,
|
|
12910
|
+
expandVectorGradientSpread,
|
|
12911
|
+
expandVectorGradientStops,
|
|
12912
|
+
explicitVectorGradient,
|
|
10094
12913
|
findVectorNode,
|
|
10095
12914
|
fitCornerRadius,
|
|
12915
|
+
foldVectorBooleanOperands,
|
|
10096
12916
|
generateVectorBlendChildren,
|
|
10097
12917
|
generateWidthProfile,
|
|
10098
12918
|
getAnchor,
|
|
@@ -10101,6 +12921,10 @@ export {
|
|
|
10101
12921
|
getTransformHandlePositions,
|
|
10102
12922
|
getVectorBlendControls,
|
|
10103
12923
|
getVectorCornerWidgets,
|
|
12924
|
+
getVectorEffectsPanel,
|
|
12925
|
+
getVectorEnteredGroups,
|
|
12926
|
+
getVectorGradientAnnotator,
|
|
12927
|
+
getVectorGradientEditor,
|
|
10104
12928
|
getVectorOverlayRenderData,
|
|
10105
12929
|
getVectorSceneRenderData,
|
|
10106
12930
|
getVectorSelectionBoxGeometry,
|
|
@@ -10109,14 +12933,20 @@ export {
|
|
|
10109
12933
|
getVectorWidthHandles,
|
|
10110
12934
|
getVisibleAnchors,
|
|
10111
12935
|
getVisibleHandles,
|
|
12936
|
+
gradientMidpointRemap,
|
|
12937
|
+
hasExplicitGradientGeometry,
|
|
12938
|
+
hasOwnVectorAppearance,
|
|
10112
12939
|
hasPaint,
|
|
10113
12940
|
hasStrokeExtras,
|
|
12941
|
+
hasVectorAppearance,
|
|
10114
12942
|
hasVectorCorners,
|
|
10115
12943
|
hitTestVectorNode,
|
|
10116
12944
|
hitTestVectorNodes,
|
|
10117
12945
|
hitTestVectorStroke,
|
|
12946
|
+
hitVectorGradientAnnotator,
|
|
10118
12947
|
inferAnchorType,
|
|
10119
12948
|
insertAnchor,
|
|
12949
|
+
insertVectorGradientStop,
|
|
10120
12950
|
insertWidthPoint,
|
|
10121
12951
|
interpolateMorphContours,
|
|
10122
12952
|
interpolateRigidMatrix,
|
|
@@ -10126,31 +12956,45 @@ export {
|
|
|
10126
12956
|
interpolateVectorStyle,
|
|
10127
12957
|
interpolateWidthProfiles,
|
|
10128
12958
|
invertMatrix,
|
|
12959
|
+
isCentredVectorGradient,
|
|
10129
12960
|
isClosedBezierShape,
|
|
10130
12961
|
isIdentityMatrix,
|
|
10131
12962
|
isUniformWidthProfile,
|
|
10132
12963
|
isVectorBlendGroup,
|
|
12964
|
+
isVectorGradient,
|
|
12965
|
+
isVectorLiveBoolean,
|
|
10133
12966
|
isVectorTextTarget,
|
|
10134
12967
|
joinSubpaths,
|
|
10135
12968
|
knifePath,
|
|
10136
12969
|
lineSubpath,
|
|
10137
12970
|
locateVectorNode,
|
|
10138
12971
|
matrixRotation,
|
|
12972
|
+
mergePaths,
|
|
10139
12973
|
mirrorWidthProfile,
|
|
10140
12974
|
morphContourToSubpath,
|
|
10141
12975
|
morphPathsCross,
|
|
10142
12976
|
moveAnchorHandle,
|
|
12977
|
+
moveVectorGradientHandle,
|
|
12978
|
+
moveVectorGradientStop,
|
|
10143
12979
|
multiplyMatrices,
|
|
10144
12980
|
nearestSegment,
|
|
10145
12981
|
nearestStrokePosition,
|
|
10146
12982
|
nextVectorCornerKind,
|
|
10147
12983
|
nodeMatrix,
|
|
12984
|
+
normalizeVectorEffectParams,
|
|
12985
|
+
offsetPaths,
|
|
10148
12986
|
parseSvgTransform,
|
|
12987
|
+
parseVectorColor,
|
|
10149
12988
|
parseVectorSvg,
|
|
12989
|
+
pathsCenter,
|
|
12990
|
+
patternAttrs,
|
|
12991
|
+
periodicVectorGradientStops,
|
|
12992
|
+
pinVectorGradient,
|
|
10150
12993
|
planShapeBuilder,
|
|
10151
12994
|
polygonSubpath,
|
|
10152
12995
|
polylineBounds,
|
|
10153
12996
|
pressureWidthProfile,
|
|
12997
|
+
puckerBloatPaths,
|
|
10154
12998
|
recognizeInkShape,
|
|
10155
12999
|
rectSubpath,
|
|
10156
13000
|
refineMorphContour,
|
|
@@ -10160,23 +13004,36 @@ export {
|
|
|
10160
13004
|
removeAnchors,
|
|
10161
13005
|
removeAnchorsKeepingShape,
|
|
10162
13006
|
removeRedundantPoints,
|
|
13007
|
+
removeVectorGradientStop,
|
|
10163
13008
|
removeWidthPoints,
|
|
13009
|
+
repeatPaths,
|
|
10164
13010
|
resampleGradientStops,
|
|
10165
13011
|
resolveVectorBlendOptions,
|
|
10166
13012
|
reverseMorphContour,
|
|
10167
13013
|
reverseSubpath,
|
|
13014
|
+
reverseVectorGradient,
|
|
10168
13015
|
reverseWidthProfile,
|
|
10169
13016
|
rotateMatrix,
|
|
13017
|
+
roundCornerPaths,
|
|
10170
13018
|
roundSubpaths,
|
|
13019
|
+
samplePlainStops,
|
|
10171
13020
|
samplePolyline,
|
|
13021
|
+
sampleVectorGradient,
|
|
13022
|
+
sampleVectorGradientAt,
|
|
13023
|
+
sampleVectorPaintDefAt,
|
|
13024
|
+
sanitizeVectorEffects,
|
|
13025
|
+
sanitizeVectorGradient,
|
|
10172
13026
|
sanitizeWidthProfile,
|
|
10173
13027
|
scaleFor,
|
|
10174
13028
|
scaleMatrix,
|
|
10175
13029
|
setAnchorCorner,
|
|
10176
13030
|
setSubpathCorners,
|
|
13031
|
+
setVectorGradientVector,
|
|
10177
13032
|
setWidthPoint,
|
|
10178
13033
|
simplifyPolyline,
|
|
10179
13034
|
smoothSubpath,
|
|
13035
|
+
sortedVectorGradientStops,
|
|
13036
|
+
spanVectorGradientStops,
|
|
10180
13037
|
splitIntoFaces,
|
|
10181
13038
|
splitSubpathAt,
|
|
10182
13039
|
starSubpath,
|
|
@@ -10192,13 +13049,23 @@ export {
|
|
|
10192
13049
|
toVectorKeyInput,
|
|
10193
13050
|
toVectorModifiers,
|
|
10194
13051
|
toVectorPointerInput,
|
|
13052
|
+
transformVectorEffects,
|
|
13053
|
+
transformVectorGradient,
|
|
10195
13054
|
transformVectorNode,
|
|
13055
|
+
transformVectorPaint,
|
|
10196
13056
|
translateAnchor,
|
|
10197
13057
|
translateMatrix,
|
|
13058
|
+
trimIntervals,
|
|
13059
|
+
trimPaths,
|
|
13060
|
+
trimPathsIndividually,
|
|
10198
13061
|
turnMorphContour,
|
|
13062
|
+
twistPaths,
|
|
10199
13063
|
unionBounds,
|
|
10200
13064
|
variableStrokeOutline,
|
|
10201
13065
|
variableStrokeRings,
|
|
13066
|
+
vectorAppearanceNodes,
|
|
13067
|
+
vectorAppearanceOutline,
|
|
13068
|
+
vectorAppearanceShapes,
|
|
10202
13069
|
vectorBlendColorDistance,
|
|
10203
13070
|
vectorBlendKeyTargets,
|
|
10204
13071
|
vectorBlendKeys,
|
|
@@ -10206,28 +13073,51 @@ export {
|
|
|
10206
13073
|
vectorBlendRailAt,
|
|
10207
13074
|
vectorBlendSpine,
|
|
10208
13075
|
vectorBlendStepCount,
|
|
13076
|
+
vectorBoxMatrix,
|
|
10209
13077
|
vectorBuilderErasedEdges,
|
|
10210
13078
|
vectorCornerAt,
|
|
10211
13079
|
vectorCorners,
|
|
10212
13080
|
vectorCornersAttribute,
|
|
10213
13081
|
vectorDiamondPath,
|
|
10214
13082
|
vectorDocumentToSvg,
|
|
13083
|
+
vectorEnteredGroups,
|
|
10215
13084
|
vectorFillMap,
|
|
13085
|
+
vectorGradientArea,
|
|
13086
|
+
vectorGradientAspect,
|
|
13087
|
+
vectorGradientBarPosition,
|
|
13088
|
+
vectorGradientFocalParam,
|
|
13089
|
+
vectorGradientHandles,
|
|
13090
|
+
vectorGradientMatrix,
|
|
13091
|
+
vectorGradientMidpointFromPosition,
|
|
13092
|
+
vectorGradientMidpointPosition,
|
|
13093
|
+
vectorGradientNeedsExpansion,
|
|
13094
|
+
vectorGradientParam,
|
|
13095
|
+
vectorGradientPoints,
|
|
13096
|
+
vectorGradientRamp,
|
|
13097
|
+
vectorGradientSvgDefs,
|
|
13098
|
+
vectorGradientTAt,
|
|
13099
|
+
vectorGradientTargetPath,
|
|
13100
|
+
vectorGradientUnitsMatrix,
|
|
10216
13101
|
vectorInkShapeName,
|
|
13102
|
+
vectorLiveBooleanSteps,
|
|
10217
13103
|
vectorNodeBounds,
|
|
10218
13104
|
vectorNodeRotation,
|
|
10219
13105
|
vectorNodeToBezier,
|
|
13106
|
+
vectorOperandOperation,
|
|
10220
13107
|
vectorPaintToHex,
|
|
10221
13108
|
vectorPaintToSvg,
|
|
10222
13109
|
vectorParentMatrix,
|
|
10223
13110
|
vectorPathData,
|
|
10224
13111
|
vectorPathEntries,
|
|
13112
|
+
vectorPathLocalBounds,
|
|
10225
13113
|
vectorPathWorldBezier,
|
|
10226
13114
|
vectorPathfinderShape,
|
|
13115
|
+
vectorRepeaterCopies,
|
|
10227
13116
|
vectorResizeCursor,
|
|
10228
13117
|
vectorResultStyle,
|
|
10229
13118
|
vectorShapeBuilderHit,
|
|
10230
13119
|
vectorShapeBuilderMap,
|
|
13120
|
+
vectorStrokeGradientDefs,
|
|
10231
13121
|
vectorStrokeLineArea,
|
|
10232
13122
|
vectorStrokeOutline,
|
|
10233
13123
|
vectorStrokeOutlinePath,
|
|
@@ -10243,6 +13133,8 @@ export {
|
|
|
10243
13133
|
widthProfileMaxScale,
|
|
10244
13134
|
widthProfilePreset,
|
|
10245
13135
|
widthProfilePresetId,
|
|
10246
|
-
widthProfileToAttribute
|
|
13136
|
+
widthProfileToAttribute,
|
|
13137
|
+
wigglePaths,
|
|
13138
|
+
zigzagPaths
|
|
10247
13139
|
};
|
|
10248
13140
|
//# sourceMappingURL=index.js.map
|