@pixerate/editor 0.41.0 → 0.42.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 +4 -0
- package/dist/vector/index.cjs +1605 -330
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +459 -13
- package/dist/vector/index.d.ts +459 -13
- package/dist/vector/index.js +1369 -94
- package/dist/vector/index.js.map +1 -1
- package/package.json +1 -1
package/dist/vector/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
hatchPath,
|
|
19
19
|
isStraightCubic,
|
|
20
20
|
normalizeBezierPath,
|
|
21
|
+
offsetPath,
|
|
21
22
|
outlineStroke,
|
|
22
23
|
pathBoolean,
|
|
23
24
|
pathToContours,
|
|
@@ -73,6 +74,8 @@ var VECTOR_TOOLS = [
|
|
|
73
74
|
];
|
|
74
75
|
var VECTOR_TRANSFORM_HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w", "rotate"];
|
|
75
76
|
var VECTOR_PATHFINDER_OPERATIONS = ["divide", "trim", "merge", "crop", "outline", "minusBack"];
|
|
77
|
+
var VECTOR_EFFECT_TYPES = ["trim", "roundCorners", "puckerBloat", "twist", "zigzag", "wiggle", "offset", "merge", "repeater"];
|
|
78
|
+
var VECTOR_MERGE_MODES = ["merge", "add", "subtract", "intersect", "exclude"];
|
|
76
79
|
|
|
77
80
|
// src/vector/corners.ts
|
|
78
81
|
var EPS = 1e-9;
|
|
@@ -1042,8 +1045,872 @@ function vectorStrokeReach(style) {
|
|
|
1042
1045
|
return reach;
|
|
1043
1046
|
}
|
|
1044
1047
|
|
|
1045
|
-
// src/vector/
|
|
1048
|
+
// src/vector/pathEffects.ts
|
|
1046
1049
|
var EPS2 = 1e-9;
|
|
1050
|
+
var degenerate = (s) => s.every((p) => Math.abs(p[0] - s[0][0]) < EPS2 && Math.abs(p[1] - s[0][1]) < EPS2);
|
|
1051
|
+
function raise(seg) {
|
|
1052
|
+
if (seg.length !== 3) return seg.map((p) => [p[0], p[1]]);
|
|
1053
|
+
const [p0, p1, p2] = seg;
|
|
1054
|
+
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];
|
|
1055
|
+
}
|
|
1056
|
+
function subsOf(path) {
|
|
1057
|
+
return pathToContours(path).map((c) => ({ segs: closeContour(c).filter((s) => !degenerate(s)).map(raise), closed: c.closed })).filter((s) => s.segs.length > 0);
|
|
1058
|
+
}
|
|
1059
|
+
var subsToPath = (subs) => contoursToPath(subs.map((s) => ({ segments: s.segs, closed: s.closed })));
|
|
1060
|
+
var unit2 = (x, y) => {
|
|
1061
|
+
const l = Math.hypot(x, y);
|
|
1062
|
+
return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
|
|
1063
|
+
};
|
|
1064
|
+
function startTangent(s) {
|
|
1065
|
+
for (let i = 1; i < s.length; i++) {
|
|
1066
|
+
const u = unit2(s[i][0] - s[0][0], s[i][1] - s[0][1]);
|
|
1067
|
+
if (u) return u;
|
|
1068
|
+
}
|
|
1069
|
+
return [1, 0];
|
|
1070
|
+
}
|
|
1071
|
+
function endTangent(s) {
|
|
1072
|
+
const end = s[s.length - 1];
|
|
1073
|
+
for (let i = s.length - 2; i >= 0; i--) {
|
|
1074
|
+
const u = unit2(end[0] - s[i][0], end[1] - s[i][1]);
|
|
1075
|
+
if (u) return u;
|
|
1076
|
+
}
|
|
1077
|
+
return [1, 0];
|
|
1078
|
+
}
|
|
1079
|
+
function tangentAt(s, t) {
|
|
1080
|
+
if (t <= 1e-9) return startTangent(s);
|
|
1081
|
+
if (t >= 1 - 1e-9) return endTangent(s);
|
|
1082
|
+
const d = segmentDerivative(s, t);
|
|
1083
|
+
return unit2(d[0], d[1]) ?? startTangent(s);
|
|
1084
|
+
}
|
|
1085
|
+
var perp = (v2) => [-v2[1], v2[0]];
|
|
1086
|
+
function effectPathLength(path) {
|
|
1087
|
+
let total = 0;
|
|
1088
|
+
for (const s of subsOf(path)) for (const seg of s.segs) total += segmentLength(seg);
|
|
1089
|
+
return total;
|
|
1090
|
+
}
|
|
1091
|
+
function effectPathVertexCount(path) {
|
|
1092
|
+
return subsOf(path).reduce((n, s) => n + s.segs.length + (s.closed ? 0 : 1), 0);
|
|
1093
|
+
}
|
|
1094
|
+
var Measured = class {
|
|
1095
|
+
segs;
|
|
1096
|
+
lens;
|
|
1097
|
+
total;
|
|
1098
|
+
constructor(path) {
|
|
1099
|
+
this.segs = subsOf(path).flatMap((s) => s.segs);
|
|
1100
|
+
this.lens = this.segs.map((s) => segmentLength(s));
|
|
1101
|
+
this.total = this.lens.reduce((a, b) => a + b, 0);
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Appends the part between lengths `sa` and `sb`, continuing the last piece when this one
|
|
1105
|
+
* starts where it ends (across the start of a closed path).
|
|
1106
|
+
*/
|
|
1107
|
+
append(sa, sb, out) {
|
|
1108
|
+
for (const piece of trimSegments(this.segs, sa, sb, this.lens)) {
|
|
1109
|
+
if (degenerate(piece)) continue;
|
|
1110
|
+
const last = out[out.length - 1]?.segs;
|
|
1111
|
+
const end = last?.[last.length - 1]?.at(-1);
|
|
1112
|
+
if (!end || Math.hypot(end[0] - piece[0][0], end[1] - piece[0][1]) > 1e-6) out.push({ segs: [piece], closed: false });
|
|
1113
|
+
else last.push(piece);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
function trimIntervals(start, end, offsetDegrees) {
|
|
1118
|
+
let s = start / 100;
|
|
1119
|
+
let e = end / 100;
|
|
1120
|
+
if (!Number.isFinite(s) || !Number.isFinite(e)) return null;
|
|
1121
|
+
if (s > e) [s, e] = [e, s];
|
|
1122
|
+
s = Math.min(1, Math.max(0, s));
|
|
1123
|
+
e = Math.min(1, Math.max(0, e));
|
|
1124
|
+
if (e - s >= 1 - 1e-9) return null;
|
|
1125
|
+
if (e - s <= 1e-9) return [];
|
|
1126
|
+
const off = Number.isFinite(offsetDegrees) ? (offsetDegrees / 360 % 1 + 1) % 1 : 0;
|
|
1127
|
+
s += off;
|
|
1128
|
+
e += off;
|
|
1129
|
+
if (e <= 1) return [[s, e]];
|
|
1130
|
+
if (s >= 1) return [[s - 1, e - 1]];
|
|
1131
|
+
return [
|
|
1132
|
+
[s, 1],
|
|
1133
|
+
[0, e - 1]
|
|
1134
|
+
];
|
|
1135
|
+
}
|
|
1136
|
+
function trimPaths(paths, start, end, offsetDegrees = 0) {
|
|
1137
|
+
const iv = trimIntervals(start, end, offsetDegrees);
|
|
1138
|
+
if (!iv) return paths.slice();
|
|
1139
|
+
return paths.map((p) => {
|
|
1140
|
+
const m = new Measured(p);
|
|
1141
|
+
const out = [];
|
|
1142
|
+
if (m.total > 0) for (const [a, b] of iv) m.append(a * m.total, b * m.total, out);
|
|
1143
|
+
return subsToPath(out);
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
function trimPathsIndividually(paths, start, end, offsetDegrees = 0) {
|
|
1147
|
+
const iv = trimIntervals(start, end, offsetDegrees);
|
|
1148
|
+
if (!iv) return paths.slice();
|
|
1149
|
+
const ms = paths.map((p) => new Measured(p));
|
|
1150
|
+
const total = ms.reduce((n, m) => n + m.total, 0);
|
|
1151
|
+
let base = 0;
|
|
1152
|
+
return ms.map((m) => {
|
|
1153
|
+
const out = [];
|
|
1154
|
+
for (const [a, b] of iv) {
|
|
1155
|
+
const la = Math.max(0, a * total - base);
|
|
1156
|
+
const lb = Math.min(m.total, b * total - base);
|
|
1157
|
+
if (lb > la) m.append(la, lb, out);
|
|
1158
|
+
}
|
|
1159
|
+
base += m.total;
|
|
1160
|
+
return subsToPath(out);
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
function puckerBloatPaths(paths, amount) {
|
|
1164
|
+
const a = amount / 100;
|
|
1165
|
+
if (!a || !Number.isFinite(a)) return paths.slice();
|
|
1166
|
+
return paths.map((p) => {
|
|
1167
|
+
const verts = p.filter((c) => c.type !== "Z");
|
|
1168
|
+
if (!verts.length) return p;
|
|
1169
|
+
const cx = verts.reduce((s, c) => s + c.x, 0) / verts.length;
|
|
1170
|
+
const cy = verts.reduce((s, c) => s + c.y, 0) / verts.length;
|
|
1171
|
+
const mv = (x, y) => ({ x: x + (cx - x) * a, y: y + (cy - y) * a });
|
|
1172
|
+
const mvh = (x, y) => ({ x: x + (x - cx) * a, y: y + (y - cy) * a });
|
|
1173
|
+
const out = [];
|
|
1174
|
+
let prev = { x: 0, y: 0 };
|
|
1175
|
+
let start = { x: 0, y: 0 };
|
|
1176
|
+
const curve = (c1, c2, q) => {
|
|
1177
|
+
const h1 = mvh(c1.x, c1.y);
|
|
1178
|
+
const h2 = mvh(c2.x, c2.y);
|
|
1179
|
+
const e = mv(q.x, q.y);
|
|
1180
|
+
out.push({ type: "C", x1: h1.x, y1: h1.y, x2: h2.x, y2: h2.y, x: e.x, y: e.y });
|
|
1181
|
+
};
|
|
1182
|
+
for (const c of p) {
|
|
1183
|
+
switch (c.type) {
|
|
1184
|
+
case "M": {
|
|
1185
|
+
const e = mv(c.x, c.y);
|
|
1186
|
+
out.push({ type: "M", x: e.x, y: e.y });
|
|
1187
|
+
prev = start = { x: c.x, y: c.y };
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
case "L":
|
|
1191
|
+
curve(prev, c, c);
|
|
1192
|
+
prev = { x: c.x, y: c.y };
|
|
1193
|
+
break;
|
|
1194
|
+
case "Q": {
|
|
1195
|
+
const c1 = { x: prev.x + 2 / 3 * (c.x1 - prev.x), y: prev.y + 2 / 3 * (c.y1 - prev.y) };
|
|
1196
|
+
const c2 = { x: c.x + 2 / 3 * (c.x1 - c.x), y: c.y + 2 / 3 * (c.y1 - c.y) };
|
|
1197
|
+
curve(c1, c2, c);
|
|
1198
|
+
prev = { x: c.x, y: c.y };
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
case "C":
|
|
1202
|
+
curve({ x: c.x1, y: c.y1 }, { x: c.x2, y: c.y2 }, c);
|
|
1203
|
+
prev = { x: c.x, y: c.y };
|
|
1204
|
+
break;
|
|
1205
|
+
case "Z":
|
|
1206
|
+
if (Math.hypot(prev.x - start.x, prev.y - start.y) > EPS2) curve(prev, start, start);
|
|
1207
|
+
out.push({ type: "Z" });
|
|
1208
|
+
prev = start;
|
|
1209
|
+
break;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return out;
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
function roundCornerPaths(paths, radius) {
|
|
1216
|
+
if (!(radius > 0) || !Number.isFinite(radius)) return paths.slice();
|
|
1217
|
+
return paths.map((p) => subpathsToBezier(bezierToSubpaths(p).map((sp) => setSubpathCorners(sp, radius))));
|
|
1218
|
+
}
|
|
1219
|
+
function offsetPaths(paths, amount, join = "miter", miterLimit = 4, copies = 1, copyOffset = 0) {
|
|
1220
|
+
const n = Math.min(1e3, Math.max(1, Math.round(Number.isFinite(copies) ? copies : 1)));
|
|
1221
|
+
return paths.map((p) => {
|
|
1222
|
+
const out = [];
|
|
1223
|
+
for (let i = 0; i < n; i++) out.push(...offsetPath(p, amount * (i + 1 + copyOffset), { join, miterLimit }));
|
|
1224
|
+
return out;
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
function twistPaths(paths, angle, center) {
|
|
1228
|
+
let rmax = 0;
|
|
1229
|
+
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));
|
|
1230
|
+
const a = angle * Math.PI / 180;
|
|
1231
|
+
if (!a || rmax <= 1e-9 || !Number.isFinite(a)) return paths.slice();
|
|
1232
|
+
const k = a / rmax;
|
|
1233
|
+
const rot = (v2, th) => {
|
|
1234
|
+
const s = Math.sin(th);
|
|
1235
|
+
const co = Math.cos(th);
|
|
1236
|
+
return [v2[0] * co - v2[1] * s, v2[0] * s + v2[1] * co];
|
|
1237
|
+
};
|
|
1238
|
+
const map = (p) => {
|
|
1239
|
+
const v2 = [p[0] - center.x, p[1] - center.y];
|
|
1240
|
+
const r = rot(v2, k * Math.hypot(v2[0], v2[1]));
|
|
1241
|
+
return [center.x + r[0], center.y + r[1]];
|
|
1242
|
+
};
|
|
1243
|
+
const jac = (p, dv) => {
|
|
1244
|
+
const v2 = [p[0] - center.x, p[1] - center.y];
|
|
1245
|
+
const r = Math.hypot(v2[0], v2[1]);
|
|
1246
|
+
let w = dv;
|
|
1247
|
+
if (r > 1e-12) {
|
|
1248
|
+
const f = k * (v2[0] * dv[0] + v2[1] * dv[1]) / r;
|
|
1249
|
+
w = [dv[0] - v2[1] * f, dv[1] + v2[0] * f];
|
|
1250
|
+
}
|
|
1251
|
+
return rot(w, k * r);
|
|
1252
|
+
};
|
|
1253
|
+
return paths.map((p) => {
|
|
1254
|
+
const out = [];
|
|
1255
|
+
for (const sub3 of subsOf(p)) {
|
|
1256
|
+
const segs = [];
|
|
1257
|
+
for (const seg of sub3.segs) {
|
|
1258
|
+
const cubic = seg.length === 2 ? [seg[0], seg[0], seg[1], seg[1]] : seg;
|
|
1259
|
+
const len2 = segmentLength(seg);
|
|
1260
|
+
const m = Math.min(64, Math.max(1, Math.ceil(Math.abs(a) * len2 / rmax / 0.15 + len2 / 50)));
|
|
1261
|
+
for (let i = 0; i < m; i++) {
|
|
1262
|
+
const q = subSegment(cubic, i / m, (i + 1) / m);
|
|
1263
|
+
const p0 = map(q[0]);
|
|
1264
|
+
const p3 = map(q[3]);
|
|
1265
|
+
const h1 = jac(q[0], [q[1][0] - q[0][0], q[1][1] - q[0][1]]);
|
|
1266
|
+
const h2 = jac(q[3], [q[2][0] - q[3][0], q[2][1] - q[3][1]]);
|
|
1267
|
+
segs.push([p0, [p0[0] + h1[0], p0[1] + h1[1]], [p3[0] + h2[0], p3[1] + h2[1]], p3]);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
out.push({ segs, closed: sub3.closed });
|
|
1271
|
+
}
|
|
1272
|
+
return subsToPath(out);
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
function resample(sub3, m) {
|
|
1276
|
+
const segs = sub3.segs;
|
|
1277
|
+
const n = segs.length;
|
|
1278
|
+
const lens = segs.map((s) => segmentLength(s));
|
|
1279
|
+
const out = [];
|
|
1280
|
+
for (let j = 0; j < n; j++) {
|
|
1281
|
+
const piece = lens[j] / m;
|
|
1282
|
+
for (let k = 0; k < m; k++) {
|
|
1283
|
+
if (k === 0) {
|
|
1284
|
+
const tout = startTangent(segs[j]);
|
|
1285
|
+
const prev = sub3.closed || j > 0 ? (j + n - 1) % n : null;
|
|
1286
|
+
let tan = tout;
|
|
1287
|
+
if (prev !== null) {
|
|
1288
|
+
const tin = endTangent(segs[prev]);
|
|
1289
|
+
tan = unit2(tin[0] + tout[0], tin[1] + tout[1]) ?? tout;
|
|
1290
|
+
}
|
|
1291
|
+
out.push({ p: segs[j][0], tan, lenPrev: prev === null ? 0 : lens[prev] / m, lenNext: piece });
|
|
1292
|
+
} else {
|
|
1293
|
+
const t = segmentParamAtLength(segs[j], piece * k, lens[j]);
|
|
1294
|
+
out.push({ p: segmentPoint(segs[j], t), tan: tangentAt(segs[j], t), lenPrev: piece, lenNext: piece });
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
if (!sub3.closed) {
|
|
1299
|
+
const last = segs[n - 1];
|
|
1300
|
+
out.push({ p: last[last.length - 1], tan: endTangent(last), lenPrev: lens[n - 1] / m, lenNext: 0 });
|
|
1301
|
+
}
|
|
1302
|
+
return out;
|
|
1303
|
+
}
|
|
1304
|
+
function emit(samples, disp, closed, smooth, handle) {
|
|
1305
|
+
const n = samples.length;
|
|
1306
|
+
if (!n) return null;
|
|
1307
|
+
const pt2 = (i) => [samples[i].p[0] + disp[i][0], samples[i].p[1] + disp[i][1]];
|
|
1308
|
+
const segs = [];
|
|
1309
|
+
const count = closed ? n : n - 1;
|
|
1310
|
+
for (let i = 0; i < count; i++) {
|
|
1311
|
+
const j = (i + 1) % n;
|
|
1312
|
+
const a = samples[i];
|
|
1313
|
+
const b = samples[j];
|
|
1314
|
+
const p = pt2(i);
|
|
1315
|
+
const q = pt2(j);
|
|
1316
|
+
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]);
|
|
1317
|
+
else segs.push([p, q]);
|
|
1318
|
+
}
|
|
1319
|
+
if (!segs.length) return null;
|
|
1320
|
+
return { segs, closed };
|
|
1321
|
+
}
|
|
1322
|
+
function zigzagPaths(paths, size, ridges, smooth = false) {
|
|
1323
|
+
const m = Math.min(1e3, Math.max(0, Math.round(Number.isFinite(ridges) ? ridges : 0))) + 1;
|
|
1324
|
+
return paths.map((p) => {
|
|
1325
|
+
const out = [];
|
|
1326
|
+
for (const sub3 of subsOf(p)) {
|
|
1327
|
+
const s = resample(sub3, m);
|
|
1328
|
+
const disp = s.map((x, i) => {
|
|
1329
|
+
const n = perp(x.tan);
|
|
1330
|
+
const d = i % 2 === 0 ? size : -size;
|
|
1331
|
+
return [n[0] * d, n[1] * d];
|
|
1332
|
+
});
|
|
1333
|
+
const e = emit(s, disp, sub3.closed, smooth, 0.36);
|
|
1334
|
+
if (e) out.push(e);
|
|
1335
|
+
}
|
|
1336
|
+
return subsToPath(out);
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
var MASK64 = (1n << 64n) - 1n;
|
|
1340
|
+
function splitmix(x) {
|
|
1341
|
+
x = x + 0x9e3779b97f4a7c15n & MASK64;
|
|
1342
|
+
let z = x;
|
|
1343
|
+
z = (z ^ z >> 30n) * 0xbf58476d1ce4e5b9n & MASK64;
|
|
1344
|
+
z = (z ^ z >> 27n) * 0x94d049bb133111ebn & MASK64;
|
|
1345
|
+
return z ^ z >> 31n;
|
|
1346
|
+
}
|
|
1347
|
+
function hash(seed, idx, t) {
|
|
1348
|
+
const h = splitmix(splitmix(splitmix(seed) ^ idx) ^ BigInt.asUintN(64, BigInt(t)));
|
|
1349
|
+
return Number(h >> 11n) / 2 ** 53 * 2 - 1;
|
|
1350
|
+
}
|
|
1351
|
+
function noise(seed, idx, t) {
|
|
1352
|
+
const i = Math.floor(t);
|
|
1353
|
+
const f = t - i;
|
|
1354
|
+
const p0 = hash(seed, idx, i - 1);
|
|
1355
|
+
const p1 = hash(seed, idx, i);
|
|
1356
|
+
const p2 = hash(seed, idx, i + 1);
|
|
1357
|
+
const p3 = hash(seed, idx, i + 2);
|
|
1358
|
+
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);
|
|
1359
|
+
return Math.min(1, Math.max(-1, v2));
|
|
1360
|
+
}
|
|
1361
|
+
function wigglePaths(paths, w) {
|
|
1362
|
+
if (!w.size || !Number.isFinite(w.size)) return paths.slice();
|
|
1363
|
+
const m = Math.min(1e3, Math.max(0, Math.round(Number.isFinite(w.detail) ? w.detail : 0))) + 1;
|
|
1364
|
+
const seed = BigInt.asUintN(64, BigInt(Math.round(Number.isFinite(w.seed) ? w.seed : 0)));
|
|
1365
|
+
const t = (Number.isFinite(w.time) ? w.time : 0) * (Number.isFinite(w.speed) ? w.speed : 0) + (Number.isFinite(w.phase) ? w.phase : 0) / 360;
|
|
1366
|
+
const corr = Math.min(1, Math.max(0, (Number.isFinite(w.correlation) ? w.correlation : 0) / 100));
|
|
1367
|
+
const sx = noise(seed, MASK64, t);
|
|
1368
|
+
const sy = noise(seed, MASK64 - 1n, t);
|
|
1369
|
+
let idx = 0;
|
|
1370
|
+
return paths.map((p) => {
|
|
1371
|
+
const out = [];
|
|
1372
|
+
for (const sub3 of subsOf(p)) {
|
|
1373
|
+
const s = resample(sub3, m);
|
|
1374
|
+
const disp = s.map(() => {
|
|
1375
|
+
const i = BigInt(idx);
|
|
1376
|
+
const ox = noise(seed, i * 2n, t + idx * 0.37);
|
|
1377
|
+
const oy = noise(seed, i * 2n + 1n, t + idx * 0.61);
|
|
1378
|
+
idx++;
|
|
1379
|
+
return [(sx * corr + ox * (1 - corr)) * w.size, (sy * corr + oy * (1 - corr)) * w.size];
|
|
1380
|
+
});
|
|
1381
|
+
const e = emit(s, disp, sub3.closed, w.smooth, 1 / 3);
|
|
1382
|
+
if (e) out.push(e);
|
|
1383
|
+
}
|
|
1384
|
+
return subsToPath(out);
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
var MERGE_OPS = { add: "union", subtract: "difference", intersect: "intersection", exclude: "xor" };
|
|
1388
|
+
function mergePaths(paths, mode = "merge") {
|
|
1389
|
+
if (mode === "merge" || !(mode in MERGE_OPS)) return paths.flat();
|
|
1390
|
+
const inputs = paths.filter((p) => p.length > 0).map((path) => ({ path, fillRule: "nonzero" }));
|
|
1391
|
+
if (!inputs.length) return [];
|
|
1392
|
+
return pathBoolean(MERGE_OPS[mode], inputs).path;
|
|
1393
|
+
}
|
|
1394
|
+
function vectorRepeaterCopies(r, pivot) {
|
|
1395
|
+
const copies = Math.max(0, Number.isFinite(r.copies) ? Math.min(r.copies, 1e3) : 0);
|
|
1396
|
+
const n = Math.ceil(copies);
|
|
1397
|
+
const offset = Number.isFinite(r.offset) ? r.offset : 0;
|
|
1398
|
+
const so = (r.startOpacity ?? 100) / 100;
|
|
1399
|
+
const eo = (r.endOpacity ?? 100) / 100;
|
|
1400
|
+
const fract = copies - Math.floor(copies);
|
|
1401
|
+
const out = [];
|
|
1402
|
+
for (let i = 0; i < n; i++) {
|
|
1403
|
+
const k = i + offset;
|
|
1404
|
+
let m = translateMatrix(-pivot.x, -pivot.y);
|
|
1405
|
+
m = multiplyMatrices(m, scaleMatrix(Math.pow((r.scale?.x ?? 100) / 100, k), Math.pow((r.scale?.y ?? 100) / 100, k)));
|
|
1406
|
+
m = multiplyMatrices(m, rotateMatrix((r.rotation ?? 0) * k));
|
|
1407
|
+
m = multiplyMatrices(m, translateMatrix(pivot.x + (r.position?.x ?? 0) * k, pivot.y + (r.position?.y ?? 0) * k));
|
|
1408
|
+
const t = n > 1 ? i / (n - 1) : 0;
|
|
1409
|
+
const partial = i + 1 === n && fract > 0 ? fract : 1;
|
|
1410
|
+
out.push({ index: i, matrix: m, opacity: Math.min(1, Math.max(0, (so + (eo - so) * t) * partial)) });
|
|
1411
|
+
}
|
|
1412
|
+
return out;
|
|
1413
|
+
}
|
|
1414
|
+
function repeatPaths(paths, r, pivot) {
|
|
1415
|
+
return vectorRepeaterCopies(r, pivot).map((copy) => ({ copy, paths: paths.map((p) => transformBezierPath(p, copy.matrix)) }));
|
|
1416
|
+
}
|
|
1417
|
+
function pathsCenter(paths) {
|
|
1418
|
+
const b = bezierPathBounds(paths.flat());
|
|
1419
|
+
return b ? { x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 } : { x: 0, y: 0 };
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// src/vector/appearance.ts
|
|
1423
|
+
var VECTOR_EFFECT_DEFAULTS = {
|
|
1424
|
+
trim: { start: 0, end: 50, offset: 0, mode: "simultaneous" },
|
|
1425
|
+
roundCorners: { radius: 10 },
|
|
1426
|
+
puckerBloat: { amount: 30 },
|
|
1427
|
+
twist: { angle: 45, center: { x: 0, y: 0 } },
|
|
1428
|
+
zigzag: { size: 5, ridges: 4, smooth: false },
|
|
1429
|
+
wiggle: { size: 5, detail: 6, smooth: true, speed: 2, correlation: 50, phase: 0, seed: 1, time: 0 },
|
|
1430
|
+
offset: { amount: 5, join: "miter", miterLimit: 4, copies: 1, copyOffset: 0 },
|
|
1431
|
+
merge: { mode: "add" },
|
|
1432
|
+
repeater: {
|
|
1433
|
+
copies: 3,
|
|
1434
|
+
offset: 0,
|
|
1435
|
+
position: { x: 100, y: 0 },
|
|
1436
|
+
anchor: { x: 0, y: 0 },
|
|
1437
|
+
rotation: 0,
|
|
1438
|
+
scale: { x: 100, y: 100 },
|
|
1439
|
+
startOpacity: 100,
|
|
1440
|
+
endOpacity: 100,
|
|
1441
|
+
composite: "below"
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
var VECTOR_EFFECT_LABELS = {
|
|
1445
|
+
trim: "Trim Paths",
|
|
1446
|
+
roundCorners: "Round Corners",
|
|
1447
|
+
puckerBloat: "Pucker & Bloat",
|
|
1448
|
+
twist: "Twist",
|
|
1449
|
+
zigzag: "Zig Zag",
|
|
1450
|
+
wiggle: "Wiggle Paths",
|
|
1451
|
+
offset: "Offset Paths",
|
|
1452
|
+
merge: "Merge Paths",
|
|
1453
|
+
repeater: "Repeater"
|
|
1454
|
+
};
|
|
1455
|
+
var VECTOR_EFFECT_PARAM_SPECS = {
|
|
1456
|
+
trim: [
|
|
1457
|
+
{ key: "start", label: "Start %", kind: "number", min: 0, max: 100, step: 1, slider: true },
|
|
1458
|
+
{ key: "end", label: "End %", kind: "number", min: 0, max: 100, step: 1, slider: true },
|
|
1459
|
+
{ key: "offset", label: "Offset \xB0", kind: "number", min: -360, max: 360, step: 1, slider: true },
|
|
1460
|
+
{
|
|
1461
|
+
key: "mode",
|
|
1462
|
+
label: "Paths",
|
|
1463
|
+
kind: "select",
|
|
1464
|
+
options: [
|
|
1465
|
+
{ value: "simultaneous", label: "Simultaneously" },
|
|
1466
|
+
{ value: "individual", label: "Individually" }
|
|
1467
|
+
]
|
|
1468
|
+
}
|
|
1469
|
+
],
|
|
1470
|
+
roundCorners: [{ key: "radius", label: "Radius", kind: "number", min: 0, max: 200, step: 1, slider: true }],
|
|
1471
|
+
puckerBloat: [{ key: "amount", label: "Amount %", kind: "number", min: -100, max: 100, step: 1, slider: true }],
|
|
1472
|
+
twist: [
|
|
1473
|
+
{ key: "angle", label: "Angle \xB0", kind: "number", min: -720, max: 720, step: 1, slider: true },
|
|
1474
|
+
{ key: "center", label: "Centre", kind: "vec2", step: 1 }
|
|
1475
|
+
],
|
|
1476
|
+
zigzag: [
|
|
1477
|
+
{ key: "size", label: "Size", kind: "number", min: 0, max: 100, step: 0.5, slider: true },
|
|
1478
|
+
{ key: "ridges", label: "Ridges", kind: "number", min: 0, max: 50, step: 1, slider: true },
|
|
1479
|
+
{ key: "smooth", label: "Smooth", kind: "boolean" }
|
|
1480
|
+
],
|
|
1481
|
+
wiggle: [
|
|
1482
|
+
{ key: "size", label: "Size", kind: "number", min: 0, max: 100, step: 0.5, slider: true },
|
|
1483
|
+
{ key: "detail", label: "Detail", kind: "number", min: 0, max: 50, step: 1, slider: true },
|
|
1484
|
+
{ key: "smooth", label: "Smooth", kind: "boolean" },
|
|
1485
|
+
{ key: "correlation", label: "Correlation %", kind: "number", min: 0, max: 100, step: 1, slider: true },
|
|
1486
|
+
{ key: "speed", label: "Speed", kind: "number", min: 0, step: 0.1 },
|
|
1487
|
+
{ key: "phase", label: "Phase \xB0", kind: "number", step: 1 },
|
|
1488
|
+
{ key: "time", label: "Time (s)", kind: "number", min: 0, max: 10, step: 0.05, slider: true },
|
|
1489
|
+
{ key: "seed", label: "Seed", kind: "number", step: 1 }
|
|
1490
|
+
],
|
|
1491
|
+
offset: [
|
|
1492
|
+
{ key: "amount", label: "Amount", kind: "number", min: -100, max: 100, step: 0.5, slider: true },
|
|
1493
|
+
{
|
|
1494
|
+
key: "join",
|
|
1495
|
+
label: "Join",
|
|
1496
|
+
kind: "select",
|
|
1497
|
+
options: [
|
|
1498
|
+
{ value: "miter", label: "Miter" },
|
|
1499
|
+
{ value: "round", label: "Round" },
|
|
1500
|
+
{ value: "bevel", label: "Bevel" }
|
|
1501
|
+
]
|
|
1502
|
+
},
|
|
1503
|
+
{ key: "miterLimit", label: "Miter limit", kind: "number", min: 1, step: 0.5 },
|
|
1504
|
+
{ key: "copies", label: "Copies", kind: "number", min: 1, max: 20, step: 1 },
|
|
1505
|
+
{ key: "copyOffset", label: "Copy offset", kind: "number", step: 0.5 }
|
|
1506
|
+
],
|
|
1507
|
+
merge: [
|
|
1508
|
+
{
|
|
1509
|
+
key: "mode",
|
|
1510
|
+
label: "Mode",
|
|
1511
|
+
kind: "select",
|
|
1512
|
+
options: [
|
|
1513
|
+
{ value: "merge", label: "Merge" },
|
|
1514
|
+
{ value: "add", label: "Add" },
|
|
1515
|
+
{ value: "subtract", label: "Subtract" },
|
|
1516
|
+
{ value: "intersect", label: "Intersect" },
|
|
1517
|
+
{ value: "exclude", label: "Exclude" }
|
|
1518
|
+
]
|
|
1519
|
+
}
|
|
1520
|
+
],
|
|
1521
|
+
repeater: [
|
|
1522
|
+
{ key: "copies", label: "Copies", kind: "number", min: 0, max: 50, step: 0.1, slider: true },
|
|
1523
|
+
{ key: "offset", label: "Offset", kind: "number", min: -10, max: 10, step: 0.1, slider: true },
|
|
1524
|
+
{ key: "position", label: "Position", kind: "vec2", step: 1 },
|
|
1525
|
+
{ key: "anchor", label: "Anchor", kind: "vec2", step: 1 },
|
|
1526
|
+
{ key: "rotation", label: "Rotation \xB0", kind: "number", min: -360, max: 360, step: 1, slider: true },
|
|
1527
|
+
{ key: "scale", label: "Scale %", kind: "vec2", step: 1 },
|
|
1528
|
+
{ key: "startOpacity", label: "Start opacity %", kind: "number", min: 0, max: 100, step: 1, slider: true },
|
|
1529
|
+
{ key: "endOpacity", label: "End opacity %", kind: "number", min: 0, max: 100, step: 1, slider: true },
|
|
1530
|
+
{
|
|
1531
|
+
key: "composite",
|
|
1532
|
+
label: "Copies",
|
|
1533
|
+
kind: "select",
|
|
1534
|
+
options: [
|
|
1535
|
+
{ value: "below", label: "Below" },
|
|
1536
|
+
{ value: "above", label: "Above" }
|
|
1537
|
+
]
|
|
1538
|
+
}
|
|
1539
|
+
]
|
|
1540
|
+
};
|
|
1541
|
+
var isVec2 = (v2) => !!v2 && typeof v2 === "object" && Number.isFinite(v2.x) && Number.isFinite(v2.y);
|
|
1542
|
+
function normalizeVectorEffectParams(type, params, base) {
|
|
1543
|
+
const defaults = VECTOR_EFFECT_DEFAULTS[type];
|
|
1544
|
+
if (!defaults) return null;
|
|
1545
|
+
const out = { ...defaults, ...base ?? {} };
|
|
1546
|
+
for (const spec of VECTOR_EFFECT_PARAM_SPECS[type]) {
|
|
1547
|
+
const v2 = params?.[spec.key];
|
|
1548
|
+
if (v2 === void 0) continue;
|
|
1549
|
+
if (spec.kind === "number" && typeof v2 === "number" && Number.isFinite(v2)) out[spec.key] = Math.min(spec.max ?? Infinity, Math.max(spec.min ?? -Infinity, v2));
|
|
1550
|
+
else if (spec.kind === "boolean" && typeof v2 === "boolean") out[spec.key] = v2;
|
|
1551
|
+
else if (spec.kind === "vec2" && isVec2(v2)) out[spec.key] = { x: v2.x, y: v2.y };
|
|
1552
|
+
else if (spec.kind === "select" && spec.options.some((o) => o.value === v2)) out[spec.key] = v2;
|
|
1553
|
+
}
|
|
1554
|
+
return out;
|
|
1555
|
+
}
|
|
1556
|
+
function createVectorEffect(type, id, params) {
|
|
1557
|
+
const p = normalizeVectorEffectParams(type, params);
|
|
1558
|
+
return p ? { id, type, params: p } : null;
|
|
1559
|
+
}
|
|
1560
|
+
var isEnabled = (e) => e.enabled !== false && !!VECTOR_EFFECT_DEFAULTS[e.type];
|
|
1561
|
+
function activeVectorEffects(node) {
|
|
1562
|
+
return node.effects?.filter(isEnabled) ?? [];
|
|
1563
|
+
}
|
|
1564
|
+
function hasOwnVectorAppearance(node) {
|
|
1565
|
+
return node.type === "group" && !!node.liveBoolean || !!node.effects?.some(isEnabled);
|
|
1566
|
+
}
|
|
1567
|
+
var liveCache = /* @__PURE__ */ new WeakMap();
|
|
1568
|
+
function hasVectorAppearance(node) {
|
|
1569
|
+
let v2 = liveCache.get(node);
|
|
1570
|
+
if (v2 === void 0) {
|
|
1571
|
+
v2 = hasOwnVectorAppearance(node) || node.type === "group" && node.children.some(hasVectorAppearance);
|
|
1572
|
+
liveCache.set(node, v2);
|
|
1573
|
+
}
|
|
1574
|
+
return v2;
|
|
1575
|
+
}
|
|
1576
|
+
function isVectorLiveBoolean(node) {
|
|
1577
|
+
return node?.type === "group" && !!node.liveBoolean;
|
|
1578
|
+
}
|
|
1579
|
+
function vectorOperandOperation(group, child) {
|
|
1580
|
+
return child.booleanOperation ?? group.liveBoolean ?? "union";
|
|
1581
|
+
}
|
|
1582
|
+
var PATH_OPS = { union: "union", subtract: "difference", intersect: "intersection", exclude: "xor" };
|
|
1583
|
+
var FoldCache = class {
|
|
1584
|
+
constructor(cap) {
|
|
1585
|
+
this.cap = cap;
|
|
1586
|
+
}
|
|
1587
|
+
cap;
|
|
1588
|
+
map = /* @__PURE__ */ new Map();
|
|
1589
|
+
get(key) {
|
|
1590
|
+
const hit = this.map.get(key);
|
|
1591
|
+
if (hit) {
|
|
1592
|
+
this.map.delete(key);
|
|
1593
|
+
this.map.set(key, hit);
|
|
1594
|
+
}
|
|
1595
|
+
return hit;
|
|
1596
|
+
}
|
|
1597
|
+
set(key, path) {
|
|
1598
|
+
if (this.map.size >= this.cap) {
|
|
1599
|
+
let drop = Math.floor(this.map.size / 2);
|
|
1600
|
+
for (const k of this.map.keys()) {
|
|
1601
|
+
if (drop-- <= 0) break;
|
|
1602
|
+
this.map.delete(k);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
this.map.set(key, path);
|
|
1606
|
+
}
|
|
1607
|
+
clear() {
|
|
1608
|
+
this.map.clear();
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
var foldCache = new FoldCache(4096);
|
|
1612
|
+
var foldSteps = 0;
|
|
1613
|
+
function vectorLiveBooleanSteps() {
|
|
1614
|
+
return foldSteps;
|
|
1615
|
+
}
|
|
1616
|
+
function clearVectorLiveBooleanCache() {
|
|
1617
|
+
foldCache.clear();
|
|
1618
|
+
}
|
|
1619
|
+
var RollingHash = class _RollingHash {
|
|
1620
|
+
a = 2166136261;
|
|
1621
|
+
b = 16777619 ^ 1540483477;
|
|
1622
|
+
static view = new DataView(new ArrayBuffer(8));
|
|
1623
|
+
byte(v2) {
|
|
1624
|
+
this.a = Math.imul(this.a ^ v2 & 255, 16777619);
|
|
1625
|
+
this.b = Math.imul(this.b ^ v2 & 255, 1540483477) ^ this.b >>> 15;
|
|
1626
|
+
}
|
|
1627
|
+
num(n) {
|
|
1628
|
+
_RollingHash.view.setFloat64(0, n);
|
|
1629
|
+
for (let i = 0; i < 8; i++) this.byte(_RollingHash.view.getUint8(i));
|
|
1630
|
+
}
|
|
1631
|
+
path(p) {
|
|
1632
|
+
for (const c of p) {
|
|
1633
|
+
this.byte(c.type.charCodeAt(0));
|
|
1634
|
+
if (c.type === "Z") continue;
|
|
1635
|
+
if (c.type === "C") {
|
|
1636
|
+
this.num(c.x1);
|
|
1637
|
+
this.num(c.y1);
|
|
1638
|
+
this.num(c.x2);
|
|
1639
|
+
this.num(c.y2);
|
|
1640
|
+
} else if (c.type === "Q") {
|
|
1641
|
+
this.num(c.x1);
|
|
1642
|
+
this.num(c.y1);
|
|
1643
|
+
}
|
|
1644
|
+
this.num(c.x);
|
|
1645
|
+
this.num(c.y);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
key() {
|
|
1649
|
+
return `${(this.a >>> 0).toString(36)}.${(this.b >>> 0).toString(36)}`;
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
function foldVectorBooleanOperands(operands) {
|
|
1653
|
+
if (!operands.length) return [];
|
|
1654
|
+
if (operands.length === 1) return operands[0].path;
|
|
1655
|
+
const h = new RollingHash();
|
|
1656
|
+
const keys = operands.map((o, i) => {
|
|
1657
|
+
if (i > 0) h.byte(o.operation.charCodeAt(0) + o.operation.length);
|
|
1658
|
+
h.byte(o.fillRule === "evenodd" ? 1 : 0);
|
|
1659
|
+
h.path(o.path);
|
|
1660
|
+
h.byte(9);
|
|
1661
|
+
return h.key();
|
|
1662
|
+
});
|
|
1663
|
+
let start = 0;
|
|
1664
|
+
let acc = operands[0].path;
|
|
1665
|
+
for (let i = keys.length - 1; i >= 1; i--) {
|
|
1666
|
+
const hit = foldCache.get(keys[i]);
|
|
1667
|
+
if (hit) {
|
|
1668
|
+
if (i === keys.length - 1) return hit;
|
|
1669
|
+
start = i;
|
|
1670
|
+
acc = hit;
|
|
1671
|
+
break;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
let accRule = start === 0 ? operands[0].fillRule : "nonzero";
|
|
1675
|
+
for (let i = start + 1; i < operands.length; i++) {
|
|
1676
|
+
const o = operands[i];
|
|
1677
|
+
foldSteps++;
|
|
1678
|
+
const r = pathBoolean(PATH_OPS[o.operation] ?? "union", [
|
|
1679
|
+
{ path: acc, fillRule: accRule },
|
|
1680
|
+
{ path: o.path, fillRule: o.fillRule }
|
|
1681
|
+
]);
|
|
1682
|
+
if (r.error) return [];
|
|
1683
|
+
acc = r.path;
|
|
1684
|
+
accRule = "nonzero";
|
|
1685
|
+
foldCache.set(keys[i], acc);
|
|
1686
|
+
}
|
|
1687
|
+
return acc;
|
|
1688
|
+
}
|
|
1689
|
+
var compareOrder = (a, b) => {
|
|
1690
|
+
const n = Math.min(a.length, b.length);
|
|
1691
|
+
for (let i = 0; i < n; i++) if (a[i] !== b[i]) return a[i] - b[i];
|
|
1692
|
+
return a.length - b.length;
|
|
1693
|
+
};
|
|
1694
|
+
var add2 = (a, b) => ({ x: a.x + (b?.x ?? 0), y: a.y + (b?.y ?? 0) });
|
|
1695
|
+
var mapPaths = (shapes, fn) => {
|
|
1696
|
+
const out = fn(shapes.map((s) => s.path));
|
|
1697
|
+
return shapes.map((s, i) => ({ ...s, path: out[i] ?? [] }));
|
|
1698
|
+
};
|
|
1699
|
+
function applyVectorEffects(shapes, effects) {
|
|
1700
|
+
let list = shapes.slice();
|
|
1701
|
+
for (const e of effects ?? []) {
|
|
1702
|
+
if (!isEnabled(e) || !list.length) continue;
|
|
1703
|
+
switch (e.type) {
|
|
1704
|
+
case "trim": {
|
|
1705
|
+
const { start, end, offset, mode } = e.params;
|
|
1706
|
+
list = mapPaths(list, (p) => mode === "individual" ? trimPathsIndividually(p, start, end, offset) : trimPaths(p, start, end, offset));
|
|
1707
|
+
break;
|
|
1708
|
+
}
|
|
1709
|
+
case "roundCorners":
|
|
1710
|
+
list = mapPaths(list, (p) => roundCornerPaths(p, e.params.radius));
|
|
1711
|
+
break;
|
|
1712
|
+
case "puckerBloat":
|
|
1713
|
+
list = mapPaths(list, (p) => puckerBloatPaths(p, e.params.amount));
|
|
1714
|
+
break;
|
|
1715
|
+
case "twist":
|
|
1716
|
+
list = mapPaths(list, (p) => twistPaths(p, e.params.angle, add2(pathsCenter(p), e.params.center)));
|
|
1717
|
+
break;
|
|
1718
|
+
case "zigzag":
|
|
1719
|
+
list = mapPaths(list, (p) => zigzagPaths(p, e.params.size, e.params.ridges, e.params.smooth));
|
|
1720
|
+
break;
|
|
1721
|
+
case "wiggle":
|
|
1722
|
+
list = mapPaths(list, (p) => wigglePaths(p, e.params));
|
|
1723
|
+
break;
|
|
1724
|
+
case "offset": {
|
|
1725
|
+
const o = e.params;
|
|
1726
|
+
list = mapPaths(list, (p) => offsetPaths(p, o.amount, o.join, o.miterLimit, o.copies, o.copyOffset));
|
|
1727
|
+
break;
|
|
1728
|
+
}
|
|
1729
|
+
case "merge": {
|
|
1730
|
+
const first = list[0];
|
|
1731
|
+
list = [{ ...first, path: mergePaths(list.map((s) => s.path), e.params.mode) }];
|
|
1732
|
+
break;
|
|
1733
|
+
}
|
|
1734
|
+
case "repeater": {
|
|
1735
|
+
const r = e.params;
|
|
1736
|
+
const copies = repeatPaths(
|
|
1737
|
+
list.map((s) => s.path),
|
|
1738
|
+
r,
|
|
1739
|
+
add2(pathsCenter(list.map((s) => s.path)), r.anchor)
|
|
1740
|
+
);
|
|
1741
|
+
const n = copies.length;
|
|
1742
|
+
const next = [];
|
|
1743
|
+
for (const { copy, paths } of copies) {
|
|
1744
|
+
const rank = r.composite === "above" ? copy.index : n - 1 - copy.index;
|
|
1745
|
+
list.forEach((s, i) => next.push({ source: s.source, path: paths[i], opacity: s.opacity * copy.opacity, order: [rank, ...s.order] }));
|
|
1746
|
+
}
|
|
1747
|
+
list = next;
|
|
1748
|
+
break;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return list;
|
|
1753
|
+
}
|
|
1754
|
+
function childShapes(child, index) {
|
|
1755
|
+
if (child.visible === false) return [];
|
|
1756
|
+
const m = child.transform;
|
|
1757
|
+
const k = child.opacity ?? 1;
|
|
1758
|
+
return vectorAppearanceShapes(child).map((s) => ({
|
|
1759
|
+
source: s.source,
|
|
1760
|
+
path: m && !isIdentityMatrix(m) ? transformBezierPath(s.path, m) : s.path,
|
|
1761
|
+
opacity: s.opacity * k,
|
|
1762
|
+
order: [index, ...s.order]
|
|
1763
|
+
}));
|
|
1764
|
+
}
|
|
1765
|
+
function liveBooleanShapes(group) {
|
|
1766
|
+
const operands = [];
|
|
1767
|
+
let source = null;
|
|
1768
|
+
group.children.forEach((child, i) => {
|
|
1769
|
+
const shapes = childShapes(child, i);
|
|
1770
|
+
if (!shapes.length) return;
|
|
1771
|
+
source ??= shapes[0].source;
|
|
1772
|
+
const path = shapes.length === 1 ? shapes[0].path : pathBoolean("union", shapes.map((s) => ({ path: s.path, fillRule: s.source.fillRule ?? "nonzero" }))).path;
|
|
1773
|
+
const fillRule = shapes.length === 1 ? shapes[0].source.fillRule ?? "nonzero" : "nonzero";
|
|
1774
|
+
operands.push({ path, fillRule, operation: vectorOperandOperation(group, child) });
|
|
1775
|
+
});
|
|
1776
|
+
if (!source) return [];
|
|
1777
|
+
return [{ source, path: foldVectorBooleanOperands(operands), opacity: 1, order: [0] }];
|
|
1778
|
+
}
|
|
1779
|
+
var shapeCache = /* @__PURE__ */ new WeakMap();
|
|
1780
|
+
function vectorAppearanceShapes(node) {
|
|
1781
|
+
const hit = shapeCache.get(node);
|
|
1782
|
+
if (hit) return hit;
|
|
1783
|
+
let base;
|
|
1784
|
+
if (node.type === "path") base = [{ source: node, path: subpathsToBezier(node.subpaths), opacity: 1, order: [] }];
|
|
1785
|
+
else if (node.liveBoolean) base = liveBooleanShapes(node);
|
|
1786
|
+
else base = node.children.flatMap((c, i) => childShapes(c, i));
|
|
1787
|
+
const out = applyVectorEffects(base, node.effects).sort((a, b) => compareOrder(a.order, b.order));
|
|
1788
|
+
shapeCache.set(node, out);
|
|
1789
|
+
return out;
|
|
1790
|
+
}
|
|
1791
|
+
var outlineCache = /* @__PURE__ */ new WeakMap();
|
|
1792
|
+
function vectorAppearanceOutline(node) {
|
|
1793
|
+
let hit = outlineCache.get(node);
|
|
1794
|
+
if (!hit) {
|
|
1795
|
+
hit = node.type === "path" && !hasOwnVectorAppearance(node) ? subpathsToBezier(node.subpaths) : vectorAppearanceShapes(node).flatMap((s) => s.path);
|
|
1796
|
+
outlineCache.set(node, hit);
|
|
1797
|
+
}
|
|
1798
|
+
return hit;
|
|
1799
|
+
}
|
|
1800
|
+
function shapeNode(shape, id) {
|
|
1801
|
+
const {
|
|
1802
|
+
id: _id,
|
|
1803
|
+
type: _type,
|
|
1804
|
+
subpaths: _s,
|
|
1805
|
+
transform: _t,
|
|
1806
|
+
effects: _e,
|
|
1807
|
+
booleanOperation: _b,
|
|
1808
|
+
blendStep: _bs,
|
|
1809
|
+
locked: _l,
|
|
1810
|
+
visible: _v,
|
|
1811
|
+
opacity: _o,
|
|
1812
|
+
...style
|
|
1813
|
+
} = shape.source;
|
|
1814
|
+
return { ...style, id, type: "path", subpaths: bezierToSubpaths(shape.path), ...shape.opacity < 1 ? { opacity: shape.opacity } : {} };
|
|
1815
|
+
}
|
|
1816
|
+
function placement(node) {
|
|
1817
|
+
return {
|
|
1818
|
+
...node.name !== void 0 ? { name: node.name } : {},
|
|
1819
|
+
...node.transform ? { transform: node.transform } : {},
|
|
1820
|
+
...node.visible === false ? { visible: false } : {},
|
|
1821
|
+
...node.locked ? { locked: true } : {},
|
|
1822
|
+
...node.blendStep ? { blendStep: true } : {},
|
|
1823
|
+
...node.booleanOperation ? { booleanOperation: node.booleanOperation } : {}
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
function vectorAppearanceNodes(node) {
|
|
1827
|
+
return vectorAppearanceShapes(node).map((s, k) => shapeNode(s, `${node.id}~fx${k}`));
|
|
1828
|
+
}
|
|
1829
|
+
var expandCache = /* @__PURE__ */ new WeakMap();
|
|
1830
|
+
function expandVectorAppearance(node) {
|
|
1831
|
+
if (!hasVectorAppearance(node)) return node;
|
|
1832
|
+
const hit = expandCache.get(node);
|
|
1833
|
+
if (hit) return hit;
|
|
1834
|
+
let out;
|
|
1835
|
+
if (!hasOwnVectorAppearance(node)) {
|
|
1836
|
+
const group = node;
|
|
1837
|
+
out = { ...group, children: group.children.map(expandVectorAppearance) };
|
|
1838
|
+
} else {
|
|
1839
|
+
const shapes = vectorAppearanceShapes(node);
|
|
1840
|
+
if (node.type === "path" && shapes.length === 1) {
|
|
1841
|
+
const one = shapes[0];
|
|
1842
|
+
const { effects: _e, opacity: _o, ...rest } = node;
|
|
1843
|
+
const opacity = (node.opacity ?? 1) * one.opacity;
|
|
1844
|
+
out = { ...rest, subpaths: bezierToSubpaths(one.path), ...opacity < 1 ? { opacity } : {} };
|
|
1845
|
+
} else {
|
|
1846
|
+
out = {
|
|
1847
|
+
id: node.id,
|
|
1848
|
+
type: "group",
|
|
1849
|
+
...placement(node),
|
|
1850
|
+
...node.opacity !== void 0 ? { opacity: node.opacity } : {},
|
|
1851
|
+
children: vectorAppearanceNodes(node)
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
expandCache.set(node, out);
|
|
1856
|
+
return out;
|
|
1857
|
+
}
|
|
1858
|
+
function expandVectorAppearances(nodes) {
|
|
1859
|
+
let changed = false;
|
|
1860
|
+
const out = nodes.map((n) => {
|
|
1861
|
+
const e = expandVectorAppearance(n);
|
|
1862
|
+
if (e !== n) changed = true;
|
|
1863
|
+
return e;
|
|
1864
|
+
});
|
|
1865
|
+
return changed ? out : nodes;
|
|
1866
|
+
}
|
|
1867
|
+
function sanitizeVectorEffects(value) {
|
|
1868
|
+
if (!Array.isArray(value)) return [];
|
|
1869
|
+
const out = [];
|
|
1870
|
+
const used = /* @__PURE__ */ new Set();
|
|
1871
|
+
for (const raw of value) {
|
|
1872
|
+
if (!raw || typeof raw !== "object") continue;
|
|
1873
|
+
const r = raw;
|
|
1874
|
+
const type = r.type;
|
|
1875
|
+
if (typeof type !== "string" || !VECTOR_EFFECT_DEFAULTS[type]) continue;
|
|
1876
|
+
let id = typeof r.id === "string" && r.id && !used.has(r.id) ? r.id : "";
|
|
1877
|
+
for (let k = out.length + 1; !id; k++) if (!used.has(`fx${k}`)) id = `fx${k}`;
|
|
1878
|
+
used.add(id);
|
|
1879
|
+
const effect = createVectorEffect(type, id, r.params ?? {});
|
|
1880
|
+
out.push(r.enabled === false ? { ...effect, enabled: false } : effect);
|
|
1881
|
+
}
|
|
1882
|
+
return out;
|
|
1883
|
+
}
|
|
1884
|
+
function transformVectorEffects(effects, m) {
|
|
1885
|
+
const lin = (v2) => ({ x: m[0] * v2.x + m[2] * v2.y, y: m[1] * v2.x + m[3] * v2.y });
|
|
1886
|
+
const mirror = m[0] * m[3] - m[1] * m[2] < 0 ? -1 : 1;
|
|
1887
|
+
return effects.map((e) => {
|
|
1888
|
+
if (e.type === "twist") return { ...e, params: { ...e.params, center: lin(e.params.center), angle: e.params.angle * mirror } };
|
|
1889
|
+
if (e.type === "repeater")
|
|
1890
|
+
return { ...e, params: { ...e.params, position: lin(e.params.position), anchor: lin(e.params.anchor), rotation: e.params.rotation * mirror } };
|
|
1891
|
+
return e;
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
function vectorEnteredGroups(nodes, ids) {
|
|
1895
|
+
const out = /* @__PURE__ */ new Set();
|
|
1896
|
+
if (!ids.length) return out;
|
|
1897
|
+
const want = new Set(ids);
|
|
1898
|
+
const visit = (list, live) => {
|
|
1899
|
+
for (const n of list) {
|
|
1900
|
+
const own = n.type === "group" && hasOwnVectorAppearance(n);
|
|
1901
|
+
if (want.has(n.id)) {
|
|
1902
|
+
for (const id of live) out.add(id);
|
|
1903
|
+
if (own) out.add(n.id);
|
|
1904
|
+
}
|
|
1905
|
+
if (n.type === "group") visit(n.children, own ? [...live, n.id] : live);
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
visit(nodes, []);
|
|
1909
|
+
return out;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
// src/vector/geometry.ts
|
|
1913
|
+
var EPS3 = 1e-9;
|
|
1047
1914
|
var cloneAnchor = (a) => ({
|
|
1048
1915
|
x: a.x,
|
|
1049
1916
|
y: a.y,
|
|
@@ -1068,7 +1935,7 @@ function hasPaint(paint) {
|
|
|
1068
1935
|
return true;
|
|
1069
1936
|
}
|
|
1070
1937
|
var handleOf = (a, side) => a[side] ?? { x: a.x, y: a.y };
|
|
1071
|
-
var isOnAnchor = (a, h) => !h || Math.abs(h.x - a.x) <
|
|
1938
|
+
var isOnAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS3 && Math.abs(h.y - a.y) < EPS3;
|
|
1072
1939
|
function subpathSegmentCount(sp) {
|
|
1073
1940
|
const n = sp.anchors.length;
|
|
1074
1941
|
if (n < 2) return 0;
|
|
@@ -1100,7 +1967,7 @@ function subpathsToBezier(subpaths, options = {}) {
|
|
|
1100
1967
|
return out;
|
|
1101
1968
|
}
|
|
1102
1969
|
function vectorNodeToBezier(node) {
|
|
1103
|
-
return node.type === "path" ?
|
|
1970
|
+
return node.type === "path" ? vectorAppearanceOutline(node) : [];
|
|
1104
1971
|
}
|
|
1105
1972
|
function vectorPathData(input, precision = 3) {
|
|
1106
1973
|
const subpaths = Array.isArray(input) ? input : input.subpaths;
|
|
@@ -1214,12 +2081,13 @@ function collectPathNodes(node) {
|
|
|
1214
2081
|
function nodeMatrix(node) {
|
|
1215
2082
|
return node.transform ?? IDENTITY_MATRIX;
|
|
1216
2083
|
}
|
|
1217
|
-
function vectorPathWorldBezier(node, parent = IDENTITY_MATRIX) {
|
|
2084
|
+
function vectorPathWorldBezier(node, parent = IDENTITY_MATRIX, options = {}) {
|
|
1218
2085
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
1219
|
-
const path = subpathsToBezier(node.subpaths);
|
|
2086
|
+
const path = options.effects === false || !hasOwnVectorAppearance(node) ? subpathsToBezier(node.subpaths) : vectorAppearanceOutline(node);
|
|
1220
2087
|
return isIdentityMatrix(m) ? path : transformBezierPath(path, m);
|
|
1221
2088
|
}
|
|
1222
2089
|
function vectorNodeBounds(node, parent = IDENTITY_MATRIX) {
|
|
2090
|
+
node = expandVectorAppearance(node);
|
|
1223
2091
|
if (node.type === "group") {
|
|
1224
2092
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
1225
2093
|
return unionBounds(node.children.map((c) => vectorNodeBounds(c, m)));
|
|
@@ -1249,11 +2117,12 @@ function boundsIntersect(a, b) {
|
|
|
1249
2117
|
function boundsFromPoints(a, b) {
|
|
1250
2118
|
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
2119
|
}
|
|
1252
|
-
function hitTestVectorNode(node, p, tolerance = 0, parent = IDENTITY_MATRIX) {
|
|
2120
|
+
function hitTestVectorNode(node, p, tolerance = 0, parent = IDENTITY_MATRIX, options = {}) {
|
|
1253
2121
|
if (node.visible === false) return false;
|
|
2122
|
+
if (!(node.type === "group" && (!hasOwnVectorAppearance(node) || options.entered?.has(node.id)))) node = expandVectorAppearance(node);
|
|
1254
2123
|
if (node.type === "group") {
|
|
1255
2124
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
1256
|
-
return node.children.some((c) => hitTestVectorNode(c, p, tolerance, m));
|
|
2125
|
+
return node.children.some((c) => hitTestVectorNode(c, p, tolerance, m, options));
|
|
1257
2126
|
}
|
|
1258
2127
|
const path = vectorPathWorldBezier(node, parent);
|
|
1259
2128
|
if (path.length === 0) return false;
|
|
@@ -1266,11 +2135,11 @@ function vectorStrokeOutline(node, parent = IDENTITY_MATRIX) {
|
|
|
1266
2135
|
if (!hasPaint(node.stroke) || !((node.strokeWidth ?? 1) > 0)) return [];
|
|
1267
2136
|
return vectorStrokeOutlinePath(vectorPathWorldBezier(node, parent), node);
|
|
1268
2137
|
}
|
|
1269
|
-
function hitTestVectorNodes(nodes, p, tolerance = 0) {
|
|
2138
|
+
function hitTestVectorNodes(nodes, p, tolerance = 0, options = {}) {
|
|
1270
2139
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
1271
2140
|
const node = nodes[i];
|
|
1272
2141
|
if (node.visible === false || node.locked) continue;
|
|
1273
|
-
if (hitTestVectorNode(node, p, tolerance)) return node;
|
|
2142
|
+
if (hitTestVectorNode(node, p, tolerance, IDENTITY_MATRIX, options)) return node;
|
|
1274
2143
|
}
|
|
1275
2144
|
return null;
|
|
1276
2145
|
}
|
|
@@ -1349,6 +2218,7 @@ function mapAnchor(a, m) {
|
|
|
1349
2218
|
}
|
|
1350
2219
|
function bakeIntoGeometry(node, m) {
|
|
1351
2220
|
if (isIdentityMatrix(m)) return node;
|
|
2221
|
+
if (node.effects?.length) node = { ...node, effects: transformVectorEffects(node.effects, m) };
|
|
1352
2222
|
if (node.type === "group") {
|
|
1353
2223
|
const group = node;
|
|
1354
2224
|
const children = group.children.map((c) => transformVectorNode(c, m));
|
|
@@ -1416,7 +2286,7 @@ function moveAnchorHandle(a, side, pos, breakHandles = false) {
|
|
|
1416
2286
|
const dy = pos.y - a.y;
|
|
1417
2287
|
const len2 = Math.hypot(dx, dy);
|
|
1418
2288
|
if (type === "symmetric") next[other] = { x: a.x - dx, y: a.y - dy };
|
|
1419
|
-
else if (type === "smooth" && len2 >
|
|
2289
|
+
else if (type === "smooth" && len2 > EPS3) {
|
|
1420
2290
|
const o = a[other];
|
|
1421
2291
|
const olen = o ? Math.hypot(o.x - a.x, o.y - a.y) : len2;
|
|
1422
2292
|
next[other] = { x: a.x - dx / len2 * olen, y: a.y - dy / len2 * olen };
|
|
@@ -1446,15 +2316,15 @@ function convertAnchor(sp, index, type) {
|
|
|
1446
2316
|
else if (prev && next) dir = { x: next.x - prev.x, y: next.y - prev.y };
|
|
1447
2317
|
else if (next) dir = { x: next.x - a.x, y: next.y - a.y };
|
|
1448
2318
|
else if (prev) dir = { x: a.x - prev.x, y: a.y - prev.y };
|
|
1449
|
-
if (!dir || Math.hypot(dir.x, dir.y) <
|
|
2319
|
+
if (!dir || Math.hypot(dir.x, dir.y) < EPS3) return { anchors, closed: sp.closed };
|
|
1450
2320
|
const dl = Math.hypot(dir.x, dir.y);
|
|
1451
2321
|
const ux = dir.x / dl;
|
|
1452
2322
|
const uy = dir.y / dl;
|
|
1453
2323
|
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
2324
|
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
2325
|
if (type === "symmetric") lenIn = lenOut = (lenIn + lenOut) / 2 || Math.max(lenIn, lenOut);
|
|
1456
|
-
if (lenIn >
|
|
1457
|
-
if (lenOut >
|
|
2326
|
+
if (lenIn > EPS3) a.in = { x: a.x - ux * lenIn, y: a.y - uy * lenIn };
|
|
2327
|
+
if (lenOut > EPS3) a.out = { x: a.x + ux * lenOut, y: a.y + uy * lenOut };
|
|
1458
2328
|
a.type = type;
|
|
1459
2329
|
return { anchors, closed: sp.closed };
|
|
1460
2330
|
}
|
|
@@ -1674,13 +2544,33 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
1674
2544
|
if (node.visible === false) out2 += ` display="none"`;
|
|
1675
2545
|
if (node.blendStep) out2 += ` data-vector-blend-step=""`;
|
|
1676
2546
|
if (node.type === "group" && node.blend) out2 += ` data-vector-blend="${escapeAttr(JSON.stringify(node.blend))}"`;
|
|
2547
|
+
if (node.booleanOperation) out2 += ` data-vector-boolean-operation="${node.booleanOperation}"`;
|
|
1677
2548
|
return out2;
|
|
1678
2549
|
};
|
|
1679
|
-
const
|
|
2550
|
+
const emit2 = (node, depth) => {
|
|
1680
2551
|
if (node.visible === false && !options.includeHidden) return;
|
|
2552
|
+
if (hasOwnVectorAppearance(node)) {
|
|
2553
|
+
const live2 = {
|
|
2554
|
+
kind: node.type,
|
|
2555
|
+
...node.effects?.length ? { effects: node.effects } : {},
|
|
2556
|
+
...node.type === "group" && node.liveBoolean ? { liveBoolean: node.liveBoolean } : {}
|
|
2557
|
+
};
|
|
2558
|
+
lines.push(`${indent(depth)}<g${common(node)} data-vector-live="${escapeAttr(JSON.stringify(live2))}">`);
|
|
2559
|
+
lines.push(`${indent(depth + 1)}<g data-vector-part="appearance">`);
|
|
2560
|
+
for (const shape of vectorAppearanceNodes(node)) emit2(shape, depth + 2);
|
|
2561
|
+
lines.push(`${indent(depth + 1)}</g>`);
|
|
2562
|
+
lines.push(`${indent(depth + 1)}<g data-vector-part="source" display="none">`);
|
|
2563
|
+
if (node.type === "path") {
|
|
2564
|
+
const { effects: _e, transform: _t, opacity: _o, visible: _v, booleanOperation: _b, blendStep: _s, ...source } = node;
|
|
2565
|
+
emit2({ ...source, id: `${node.id}~src` }, depth + 2);
|
|
2566
|
+
} else for (const child of node.children) emit2(child, depth + 2);
|
|
2567
|
+
lines.push(`${indent(depth + 1)}</g>`);
|
|
2568
|
+
lines.push(`${indent(depth)}</g>`);
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
1681
2571
|
if (node.type === "group") {
|
|
1682
2572
|
lines.push(`${indent(depth)}<g${common(node)}>`);
|
|
1683
|
-
for (const child of node.children)
|
|
2573
|
+
for (const child of node.children) emit2(child, depth + 1);
|
|
1684
2574
|
lines.push(`${indent(depth)}</g>`);
|
|
1685
2575
|
return;
|
|
1686
2576
|
}
|
|
@@ -1744,7 +2634,7 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
1744
2634
|
}
|
|
1745
2635
|
lines.push(`${indent(depth)}</g>`);
|
|
1746
2636
|
};
|
|
1747
|
-
for (const node of doc.nodes)
|
|
2637
|
+
for (const node of doc.nodes) emit2(node, 1);
|
|
1748
2638
|
const w = num(doc.width);
|
|
1749
2639
|
const h = num(doc.height);
|
|
1750
2640
|
const nl = pretty ? "\n" : "";
|
|
@@ -1756,6 +2646,7 @@ function vectorDocumentToSvg(doc, options = {}) {
|
|
|
1756
2646
|
out.push(...lines, "</svg>");
|
|
1757
2647
|
return out.join(nl);
|
|
1758
2648
|
}
|
|
2649
|
+
var BOOLEAN_OPERATIONS = ["union", "subtract", "intersect", "exclude"];
|
|
1759
2650
|
function parseXml(source) {
|
|
1760
2651
|
const text = source.replace(/<!--[\s\S]*?-->/g, "").replace(/<\?[\s\S]*?\?>/g, "").replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, "").replace(/<!DOCTYPE[^>[]*(\[[\s\S]*?\])?\s*>/gi, "");
|
|
1761
2652
|
const root = { name: "#root", attrs: {}, children: [] };
|
|
@@ -2030,6 +2921,34 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
2030
2921
|
};
|
|
2031
2922
|
return placed(node, m);
|
|
2032
2923
|
};
|
|
2924
|
+
const liveNode = (g, style, m, meta) => {
|
|
2925
|
+
let raw = {};
|
|
2926
|
+
try {
|
|
2927
|
+
raw = JSON.parse(g.attrs["data-vector-live"]) ?? {};
|
|
2928
|
+
} catch {
|
|
2929
|
+
raw = {};
|
|
2930
|
+
}
|
|
2931
|
+
const sourceEl = g.children.find((c) => c.name === "g" && c.attrs["data-vector-part"] === "source");
|
|
2932
|
+
if (!sourceEl) return null;
|
|
2933
|
+
const inner = visit(sourceEl, style, m);
|
|
2934
|
+
const effects = sanitizeVectorEffects(raw.effects);
|
|
2935
|
+
const liveBoolean = BOOLEAN_OPERATIONS.includes(raw.liveBoolean) ? raw.liveBoolean : null;
|
|
2936
|
+
const id = idFor(g);
|
|
2937
|
+
if (raw.kind === "path") {
|
|
2938
|
+
const path = inner.find((n) => n.type === "path");
|
|
2939
|
+
if (!path) return null;
|
|
2940
|
+
const { residual } = decomposeRigid(m);
|
|
2941
|
+
const { visible: _v, ...rest } = path;
|
|
2942
|
+
return { ...rest, id, ...meta, ...effects.length ? { effects: transformVectorEffects(effects, residual) } : {} };
|
|
2943
|
+
}
|
|
2944
|
+
if (!inner.length) return null;
|
|
2945
|
+
const group = { id, type: "group", ...meta, children: inner };
|
|
2946
|
+
if (effects.length) group.effects = transformVectorEffects(effects, m);
|
|
2947
|
+
if (liveBoolean) group.liveBoolean = liveBoolean;
|
|
2948
|
+
const blend = blendFromAttribute(g.attrs["data-vector-blend"], m);
|
|
2949
|
+
if (blend) group.blend = blend;
|
|
2950
|
+
return group;
|
|
2951
|
+
};
|
|
2033
2952
|
const visit = (el, style, matrix) => {
|
|
2034
2953
|
const out = [];
|
|
2035
2954
|
for (const child of el.children) {
|
|
@@ -2044,6 +2963,13 @@ function parseVectorSvg(svg, options = {}) {
|
|
|
2044
2963
|
...hidden ? { visible: false } : {}
|
|
2045
2964
|
};
|
|
2046
2965
|
if (child.attrs["data-vector-blend-step"] !== void 0) continue;
|
|
2966
|
+
const operation = child.attrs["data-vector-boolean-operation"];
|
|
2967
|
+
if (BOOLEAN_OPERATIONS.includes(operation)) meta.booleanOperation = operation;
|
|
2968
|
+
if (child.name === "g" && child.attrs["data-vector-live"] !== void 0) {
|
|
2969
|
+
const node2 = liveNode(child, s, m, meta);
|
|
2970
|
+
if (node2) out.push(node2);
|
|
2971
|
+
continue;
|
|
2972
|
+
}
|
|
2047
2973
|
if (child.name === "g" && child.attrs["data-vector-path"] !== void 0) {
|
|
2048
2974
|
const node2 = mergedPath(child, s, m, meta);
|
|
2049
2975
|
if (node2) out.push(node2);
|
|
@@ -3197,8 +4123,9 @@ var VectorSnapTargets = class _VectorSnapTargets {
|
|
|
3197
4123
|
let anchors = BUDGET;
|
|
3198
4124
|
let segments = BUDGET;
|
|
3199
4125
|
const visit = (list, parent) => {
|
|
3200
|
-
for (const
|
|
3201
|
-
if (exclude.has(
|
|
4126
|
+
for (const stored of list) {
|
|
4127
|
+
if (exclude.has(stored.id) || stored.visible === false) continue;
|
|
4128
|
+
const n = contains(stored, exclude) ? stored : expandVectorAppearance(stored);
|
|
3202
4129
|
const m = multiplyMatrices(nodeMatrix(n), parent);
|
|
3203
4130
|
if (n.type === "group") {
|
|
3204
4131
|
visit(n.children, m);
|
|
@@ -3736,8 +4663,8 @@ function solveOnSegment(seg, base, cond) {
|
|
|
3736
4663
|
}
|
|
3737
4664
|
|
|
3738
4665
|
// src/vector/pathEdit.ts
|
|
3739
|
-
var
|
|
3740
|
-
var
|
|
4666
|
+
var EPS4 = 1e-9;
|
|
4667
|
+
var unit3 = (x, y) => {
|
|
3741
4668
|
const l = Math.hypot(x, y);
|
|
3742
4669
|
return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
|
|
3743
4670
|
};
|
|
@@ -3748,16 +4675,16 @@ function cubicOf(sp, i) {
|
|
|
3748
4675
|
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
4676
|
}
|
|
3750
4677
|
var isLine = (sp, i) => subpathSegment(sp, i).length === 2;
|
|
3751
|
-
function
|
|
4678
|
+
function startTangent2(c) {
|
|
3752
4679
|
for (let k = 1; k < 4; k++) {
|
|
3753
|
-
const u =
|
|
4680
|
+
const u = unit3(c[k][0] - c[0][0], c[k][1] - c[0][1]);
|
|
3754
4681
|
if (u) return u;
|
|
3755
4682
|
}
|
|
3756
4683
|
return [1, 0];
|
|
3757
4684
|
}
|
|
3758
|
-
function
|
|
4685
|
+
function endTangent2(c) {
|
|
3759
4686
|
for (let k = 2; k >= 0; k--) {
|
|
3760
|
-
const u =
|
|
4687
|
+
const u = unit3(c[3][0] - c[k][0], c[3][1] - c[k][1]);
|
|
3761
4688
|
if (u) return u;
|
|
3762
4689
|
}
|
|
3763
4690
|
return [1, 0];
|
|
@@ -3765,7 +4692,7 @@ function endTangent(c) {
|
|
|
3765
4692
|
function sample(c, n, out, withStart) {
|
|
3766
4693
|
for (let i = withStart ? 0 : 1; i <= n; i++) out.push(segmentPoint(c, i / n));
|
|
3767
4694
|
}
|
|
3768
|
-
var onAnchor2 = (a, h) => !h || Math.abs(h.x - a.x) <
|
|
4695
|
+
var onAnchor2 = (a, h) => !h || Math.abs(h.x - a.x) < EPS4 && Math.abs(h.y - a.y) < EPS4;
|
|
3769
4696
|
function tidy(a, keepType = true) {
|
|
3770
4697
|
const next = cloneAnchor(a);
|
|
3771
4698
|
if (onAnchor2(next, next.in)) delete next.in;
|
|
@@ -3827,8 +4754,8 @@ function removeAnchorKeepingShape(sp, index) {
|
|
|
3827
4754
|
const pts = [];
|
|
3828
4755
|
sample(left, N, pts, true);
|
|
3829
4756
|
sample(right, N, pts, false);
|
|
3830
|
-
const t0 =
|
|
3831
|
-
const t1 =
|
|
4757
|
+
const t0 = startTangent2(left);
|
|
4758
|
+
const t1 = endTangent2(right);
|
|
3832
4759
|
const { curve } = fitAcross(pts, N, t0, t1);
|
|
3833
4760
|
if ([curve.p1, curve.p2].every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]))) {
|
|
3834
4761
|
anchors[pi] = tidy({ ...anchors[pi], out: { x: curve.p1[0], y: curve.p1[1] } });
|
|
@@ -4029,7 +4956,7 @@ function smoothSubpath(sp, amount = 1, only) {
|
|
|
4029
4956
|
const prev = orig[(i + n - 1) % n];
|
|
4030
4957
|
const next = orig[(i + 1) % n];
|
|
4031
4958
|
const p = orig[i];
|
|
4032
|
-
const dir =
|
|
4959
|
+
const dir = unit3(next.x - prev.x, next.y - prev.y);
|
|
4033
4960
|
if (!dir) continue;
|
|
4034
4961
|
const li = dist2(p, prev) / 3;
|
|
4035
4962
|
const lo = dist2(p, next) / 3;
|
|
@@ -4095,13 +5022,13 @@ function mergePair(a, b, tol) {
|
|
|
4095
5022
|
}
|
|
4096
5023
|
const ca = a.line ? [a0, a0, a3, a3] : a.c;
|
|
4097
5024
|
const cb = b.line ? [a3, a3, b3, b3] : b.c;
|
|
4098
|
-
const ta =
|
|
4099
|
-
const tb =
|
|
5025
|
+
const ta = endTangent2(ca);
|
|
5026
|
+
const tb = startTangent2(cb);
|
|
4100
5027
|
if (ta[0] * tb[0] + ta[1] * tb[1] < 0.99) return null;
|
|
4101
5028
|
const pts = [];
|
|
4102
5029
|
sample(ca, 16, pts, true);
|
|
4103
5030
|
sample(cb, 16, pts, false);
|
|
4104
|
-
const { curve, error } = fitAcross(pts, 16,
|
|
5031
|
+
const { curve, error } = fitAcross(pts, 16, startTangent2(ca), endTangent2(cb));
|
|
4105
5032
|
return error <= tol ? { c: [curve.p0, curve.p1, curve.p2, curve.p3], line: false } : null;
|
|
4106
5033
|
}
|
|
4107
5034
|
function removeRedundantPoints(sp, tolerance) {
|
|
@@ -4233,14 +5160,14 @@ function lineHits(p, q, seg) {
|
|
|
4233
5160
|
return out;
|
|
4234
5161
|
}
|
|
4235
5162
|
var near = (a, b, eps) => Math.abs(a[0] - b[0]) <= eps && Math.abs(a[1] - b[1]) <= eps;
|
|
4236
|
-
var
|
|
5163
|
+
var degenerate2 = (seg, eps) => seg.every((p) => near(p, seg[0], eps));
|
|
4237
5164
|
function arc(l, a, b, eps) {
|
|
4238
5165
|
const n = l.segs.length;
|
|
4239
5166
|
const segs = [];
|
|
4240
5167
|
const chord = [];
|
|
4241
5168
|
const push = (k, t0, t1) => {
|
|
4242
5169
|
const piece = t0 <= 0 && t1 >= 1 ? l.segs[k].slice() : subSegment(l.segs[k], t0, t1);
|
|
4243
|
-
if (!
|
|
5170
|
+
if (!degenerate2(piece, eps)) {
|
|
4244
5171
|
segs.push(piece);
|
|
4245
5172
|
chord.push(l.chord[k]);
|
|
4246
5173
|
}
|
|
@@ -4698,8 +5625,9 @@ function recognizeInkShape(points, options = {}) {
|
|
|
4698
5625
|
// src/vector/builder.ts
|
|
4699
5626
|
function vectorPathEntries(doc, roots, options = {}) {
|
|
4700
5627
|
const out = [];
|
|
4701
|
-
const visit = (
|
|
4702
|
-
if (
|
|
5628
|
+
const visit = (stored, parent, inside) => {
|
|
5629
|
+
if (stored.visible === false || stored.locked && !options.locked) return;
|
|
5630
|
+
const node = options.expand === false ? stored : expandVectorAppearance(stored);
|
|
4703
5631
|
const picked = inside || roots === null || roots.includes(node.id);
|
|
4704
5632
|
if (node.type === "group") {
|
|
4705
5633
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
@@ -4753,7 +5681,7 @@ var STROKE_KEYS = [
|
|
|
4753
5681
|
"strokeWidthProfile"
|
|
4754
5682
|
];
|
|
4755
5683
|
function vectorResultStyle(node, paint, edge = false) {
|
|
4756
|
-
const { id: _id, subpaths: _s, transform: _t, ...rest } = node;
|
|
5684
|
+
const { id: _id, subpaths: _s, transform: _t, effects: _e, booleanOperation: _b, ...rest } = node;
|
|
4757
5685
|
const base = { ...rest, fillRule: "nonzero" };
|
|
4758
5686
|
if (edge) {
|
|
4759
5687
|
for (const k of STROKE_KEYS) delete base[k];
|
|
@@ -5281,6 +6209,12 @@ function spliceTree(nodes, id, replacements) {
|
|
|
5281
6209
|
return changed ? out : nodes;
|
|
5282
6210
|
}
|
|
5283
6211
|
var toPoint2 = (p) => [p.x, p.y];
|
|
6212
|
+
var placementOf = (n) => ({
|
|
6213
|
+
...n.transform ? { transform: n.transform } : {},
|
|
6214
|
+
...n.visible === false ? { visible: false } : {},
|
|
6215
|
+
...n.locked ? { locked: true } : {},
|
|
6216
|
+
...n.booleanOperation ? { booleanOperation: n.booleanOperation } : {}
|
|
6217
|
+
});
|
|
5284
6218
|
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
6219
|
var idCounter = 0;
|
|
5286
6220
|
var defaultCreateId = () => `vn-${Date.now().toString(36)}${(++idCounter).toString(36)}`;
|
|
@@ -5505,8 +6439,11 @@ var VectorEditorController = class {
|
|
|
5505
6439
|
}
|
|
5506
6440
|
this.notify(docChanged);
|
|
5507
6441
|
}
|
|
5508
|
-
/**
|
|
5509
|
-
|
|
6442
|
+
/**
|
|
6443
|
+
* Records the change from `before` to the current state as one undo step. Consecutive
|
|
6444
|
+
* changes with the same `merge` key (a slider drag) fold into one step.
|
|
6445
|
+
*/
|
|
6446
|
+
commit(name, before, merge) {
|
|
5510
6447
|
this.doc = this.refreshed(this.doc);
|
|
5511
6448
|
const after = this.snapshot();
|
|
5512
6449
|
if (before.doc === after.doc) {
|
|
@@ -5517,17 +6454,23 @@ var VectorEditorController = class {
|
|
|
5517
6454
|
name,
|
|
5518
6455
|
execute: () => this.restore(after),
|
|
5519
6456
|
undo: () => this.restore(before),
|
|
5520
|
-
redo: () => this.restore(after)
|
|
6457
|
+
redo: () => this.restore(after),
|
|
6458
|
+
...merge ? { tag: merge } : {}
|
|
5521
6459
|
};
|
|
5522
6460
|
this.committing = true;
|
|
5523
|
-
this.history.
|
|
6461
|
+
if (merge) this.history.executeMerged(command, { key: merge });
|
|
6462
|
+
else this.history.execute(command);
|
|
5524
6463
|
this.committing = false;
|
|
5525
6464
|
}
|
|
5526
|
-
/** Runs `fn` (which updates fields) and records it as one undo step. */
|
|
5527
|
-
change(name, fn) {
|
|
6465
|
+
/** Runs `fn` (which updates fields) and records it as one undo step (merged by `merge` key). */
|
|
6466
|
+
change(name, fn, merge) {
|
|
5528
6467
|
const before = this.snapshot();
|
|
5529
6468
|
fn();
|
|
5530
|
-
this.commit(name, before);
|
|
6469
|
+
this.commit(name, before, merge);
|
|
6470
|
+
}
|
|
6471
|
+
/** Ends a merged edit (a slider released): the next change starts a new undo step. */
|
|
6472
|
+
endMergedEdit() {
|
|
6473
|
+
this.history.endGesture();
|
|
5531
6474
|
}
|
|
5532
6475
|
setDoc(nodes) {
|
|
5533
6476
|
if (nodes !== this.doc.nodes) this.doc = { ...this.doc, nodes };
|
|
@@ -6077,7 +7020,7 @@ var VectorEditorController = class {
|
|
|
6077
7020
|
const created = [];
|
|
6078
7021
|
const replace = /* @__PURE__ */ new Map();
|
|
6079
7022
|
for (const p of paths) {
|
|
6080
|
-
const outline = bezierToSubpaths(vectorStrokeOutlinePath(
|
|
7023
|
+
const outline = bezierToSubpaths(vectorStrokeOutlinePath(vectorAppearanceOutline(p), p));
|
|
6081
7024
|
if (!outline.length) continue;
|
|
6082
7025
|
const {
|
|
6083
7026
|
stroke,
|
|
@@ -6092,11 +7035,12 @@ var VectorEditorController = class {
|
|
|
6092
7035
|
startArrowScale: _ss,
|
|
6093
7036
|
endArrowScale: _es,
|
|
6094
7037
|
strokeWidthProfile: _wp,
|
|
7038
|
+
effects,
|
|
6095
7039
|
...rest
|
|
6096
7040
|
} = cloneVectorNode(p);
|
|
6097
7041
|
const id = this.newId();
|
|
6098
7042
|
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];
|
|
7043
|
+
const list = hasPaint(p.fill) ? [{ ...rest, ...effects ? { effects } : {}, stroke: null }, strokeNode] : [strokeNode];
|
|
6100
7044
|
created.push(id);
|
|
6101
7045
|
replace.set(p.id, list);
|
|
6102
7046
|
}
|
|
@@ -6111,6 +7055,121 @@ var VectorEditorController = class {
|
|
|
6111
7055
|
});
|
|
6112
7056
|
return created;
|
|
6113
7057
|
}
|
|
7058
|
+
// -------------------------------------------------------------------------
|
|
7059
|
+
// Path effects
|
|
7060
|
+
// -------------------------------------------------------------------------
|
|
7061
|
+
/** Selected (or given) nodes that can carry effects: the roots of the selection. */
|
|
7062
|
+
effectTargets(ids) {
|
|
7063
|
+
return (ids ?? this.selectedRoots()).map((id) => this.getNode(id)).filter((n) => !!n && !n.blendStep);
|
|
7064
|
+
}
|
|
7065
|
+
/**
|
|
7066
|
+
* Adds an effect of `type` to the selected (or given) nodes, at the bottom of each stack (or
|
|
7067
|
+
* at `index`), with default parameters merged with `params`. A repeater's copies start one
|
|
7068
|
+
* shape width apart. One undo step; returns the new effect ids (one per node, in order).
|
|
7069
|
+
*/
|
|
7070
|
+
addEffect(type, options = {}) {
|
|
7071
|
+
const targets = this.effectTargets(options.ids);
|
|
7072
|
+
if (!targets.length || !createVectorEffect(type, "")) return [];
|
|
7073
|
+
const created = [];
|
|
7074
|
+
const next = /* @__PURE__ */ new Map();
|
|
7075
|
+
for (const node of targets) {
|
|
7076
|
+
const list = node.effects ?? [];
|
|
7077
|
+
let k = list.length + 1;
|
|
7078
|
+
while (list.some((e) => e.id === `fx${k}`)) k++;
|
|
7079
|
+
const params = { ...options.params ?? {} };
|
|
7080
|
+
if (type === "repeater" && params.position === void 0) {
|
|
7081
|
+
const b = bezierPathBounds(vectorAppearanceOutline(node));
|
|
7082
|
+
if (b && b.maxX - b.minX > 0) params.position = { x: Math.round((b.maxX - b.minX) * 1.25 * 100) / 100, y: 0 };
|
|
7083
|
+
}
|
|
7084
|
+
const effect = createVectorEffect(type, `fx${k}`, params);
|
|
7085
|
+
const at2 = options.index === void 0 ? list.length : Math.min(list.length, Math.max(0, Math.round(options.index)));
|
|
7086
|
+
next.set(node.id, { ...node, effects: [...list.slice(0, at2), effect, ...list.slice(at2)] });
|
|
7087
|
+
created.push(effect.id);
|
|
7088
|
+
}
|
|
7089
|
+
this.change(`Add ${VECTOR_EFFECT_LABELS[type]}`, () => this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n)));
|
|
7090
|
+
return created;
|
|
7091
|
+
}
|
|
7092
|
+
editEffects(nodeId, label, fn, merge) {
|
|
7093
|
+
const node = this.getNode(nodeId);
|
|
7094
|
+
if (!node) return { ok: false, message: `There is no node "${nodeId}".` };
|
|
7095
|
+
const result = fn(node.effects ?? []);
|
|
7096
|
+
if (typeof result === "string") return { ok: false, message: result };
|
|
7097
|
+
this.change(label, () => this.replaceNode(nodeId, result.length ? { ...node, effects: result } : (({ effects: _e, ...rest }) => rest)(node)), merge);
|
|
7098
|
+
return { ok: true };
|
|
7099
|
+
}
|
|
7100
|
+
/**
|
|
7101
|
+
* Turns an effect on or off and changes its parameters (unknown or invalid values are
|
|
7102
|
+
* ignored). With `merge` (true, or a key of your own), consecutive updates of the same effect
|
|
7103
|
+
* fold into one undo step, as a slider drag should; call `endMergedEdit` when it ends.
|
|
7104
|
+
*/
|
|
7105
|
+
updateEffect(nodeId, effectId, patch, options = {}) {
|
|
7106
|
+
const merge = options.merge === true ? `effect:${nodeId}:${effectId}` : options.merge || void 0;
|
|
7107
|
+
const node = this.getNode(nodeId);
|
|
7108
|
+
const current = node?.effects?.find((e) => e.id === effectId);
|
|
7109
|
+
const label = current ? VECTOR_EFFECT_LABELS[current.type] ?? "Effect" : "Effect";
|
|
7110
|
+
return this.editEffects(
|
|
7111
|
+
nodeId,
|
|
7112
|
+
`Edit ${label}`,
|
|
7113
|
+
(list) => {
|
|
7114
|
+
const i = list.findIndex((e2) => e2.id === effectId);
|
|
7115
|
+
if (i < 0) return `Node "${nodeId}" has no effect "${effectId}".`;
|
|
7116
|
+
const e = list[i];
|
|
7117
|
+
const params = patch.params ? normalizeVectorEffectParams(e.type, patch.params, e.params) : e.params;
|
|
7118
|
+
const next = { ...e, params, ...patch.enabled !== void 0 ? { enabled: patch.enabled } : {} };
|
|
7119
|
+
if (next.enabled !== false) delete next.enabled;
|
|
7120
|
+
return list.map((x, j) => j === i ? next : x);
|
|
7121
|
+
},
|
|
7122
|
+
merge
|
|
7123
|
+
);
|
|
7124
|
+
}
|
|
7125
|
+
/** Removes an effect from a node's stack. */
|
|
7126
|
+
removeEffect(nodeId, effectId) {
|
|
7127
|
+
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}".`);
|
|
7128
|
+
}
|
|
7129
|
+
/** Moves an effect to position `index` of its stack (0 is applied first). */
|
|
7130
|
+
moveEffect(nodeId, effectId, index) {
|
|
7131
|
+
return this.editEffects(nodeId, "Reorder Effects", (list) => {
|
|
7132
|
+
const i = list.findIndex((e2) => e2.id === effectId);
|
|
7133
|
+
if (i < 0) return `Node "${nodeId}" has no effect "${effectId}".`;
|
|
7134
|
+
const to = Math.min(list.length - 1, Math.max(0, Math.round(index)));
|
|
7135
|
+
const out = list.slice();
|
|
7136
|
+
const [e] = out.splice(i, 1);
|
|
7137
|
+
out.splice(to, 0, e);
|
|
7138
|
+
return out;
|
|
7139
|
+
});
|
|
7140
|
+
}
|
|
7141
|
+
/**
|
|
7142
|
+
* Expand Appearance: bakes the path effects and live booleans of the selected (or given)
|
|
7143
|
+
* nodes, and of everything inside them, into plain paths. A node keeps its id (a group when
|
|
7144
|
+
* it draws as several shapes); the shapes it made get new ids. Returns the ids of the expanded
|
|
7145
|
+
* nodes (empty when none was live).
|
|
7146
|
+
*/
|
|
7147
|
+
expandAppearance(options = {}) {
|
|
7148
|
+
const roots = (options.ids ?? this.selectedRoots()).map((id) => this.getNode(id)).filter((n) => !!n && hasVectorAppearance(n));
|
|
7149
|
+
if (!roots.length) return [];
|
|
7150
|
+
const fresh = (n) => {
|
|
7151
|
+
const id = n.id.includes("~fx") ? this.newId() : n.id;
|
|
7152
|
+
return n.type === "group" ? { ...n, id, children: n.children.map(fresh) } : { ...n, id };
|
|
7153
|
+
};
|
|
7154
|
+
const bake = (n) => {
|
|
7155
|
+
if (!hasVectorAppearance(n)) return n;
|
|
7156
|
+
if (!hasOwnVectorAppearance(n)) return { ...n, children: n.children.map(bake) };
|
|
7157
|
+
let e = expandVectorAppearance(n);
|
|
7158
|
+
if (e.type === "group" && e.children.length === 1 && e.children[0].type === "path") {
|
|
7159
|
+
const { id: _id, opacity, name, ...child } = e.children[0];
|
|
7160
|
+
const k = (e.opacity ?? 1) * (opacity ?? 1);
|
|
7161
|
+
e = { ...child, id: e.id, name: e.name ?? name, ...placementOf(e), ...k < 1 ? { opacity: k } : {} };
|
|
7162
|
+
}
|
|
7163
|
+
return fresh(e);
|
|
7164
|
+
};
|
|
7165
|
+
const next = new Map(roots.map((n) => [n.id, bake(n)]));
|
|
7166
|
+
this.change("Expand Appearance", () => {
|
|
7167
|
+
this.setDoc(mapTree(this.doc.nodes, (n) => next.get(n.id) ?? n));
|
|
7168
|
+
this.anchors = [];
|
|
7169
|
+
this.editingId = null;
|
|
7170
|
+
});
|
|
7171
|
+
return roots.map((n) => n.id);
|
|
7172
|
+
}
|
|
6114
7173
|
rename(id, name) {
|
|
6115
7174
|
return this.updateNode(id, { name }, { label: "Rename" });
|
|
6116
7175
|
}
|
|
@@ -6402,7 +7461,7 @@ var VectorEditorController = class {
|
|
|
6402
7461
|
const opacity = n.opacity;
|
|
6403
7462
|
const m = nodeMatrix(n);
|
|
6404
7463
|
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)
|
|
7464
|
+
...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
7465
|
);
|
|
6407
7466
|
} else if (n.type === "group") {
|
|
6408
7467
|
const children = flatten2(n.children);
|
|
@@ -6439,34 +7498,33 @@ var VectorEditorController = class {
|
|
|
6439
7498
|
/**
|
|
6440
7499
|
* Combines the selected paths (two or more) into one with a boolean operation. `subtract`
|
|
6441
7500
|
* removes the upper shapes from the lowest one. The result takes the lowest shape's style
|
|
6442
|
-
* and place.
|
|
7501
|
+
* and place. Shapes take part as drawn (path effects and live booleans applied).
|
|
7502
|
+
*
|
|
7503
|
+
* With `live`, the operands stay editable instead: a lone live boolean group gets the
|
|
7504
|
+
* operation (its operands follow it again), operands of live boolean groups get it as their
|
|
7505
|
+
* own, and anything else (two or more nodes with one parent) is wrapped in a new live boolean
|
|
7506
|
+
* group. Returns the result's (or the live group's) id, or null.
|
|
6443
7507
|
*/
|
|
6444
7508
|
booleanOp(op, options = {}) {
|
|
6445
7509
|
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;
|
|
7510
|
+
if (options.live) return this.liveBooleanOp(op, roots);
|
|
7511
|
+
const entries = vectorPathEntries(this.doc, roots, { locked: true });
|
|
7512
|
+
if (entries.length < 2) return null;
|
|
6452
7513
|
const result = pathBoolean(
|
|
6453
7514
|
BOOLEAN_OPS[op],
|
|
6454
|
-
|
|
6455
|
-
path: vectorPathWorldBezier(
|
|
6456
|
-
|
|
6457
|
-
vectorParentMatrix(this.doc.nodes, p.id) ?? IDENTITY_MATRIX
|
|
6458
|
-
),
|
|
6459
|
-
fillRule: p.fillRule ?? "nonzero"
|
|
7515
|
+
entries.map((e) => ({
|
|
7516
|
+
path: vectorPathWorldBezier({ ...e.node, subpaths: e.node.subpaths.filter((s) => s.anchors.length > 1).map((s) => ({ ...s, closed: true })) }, e.parent),
|
|
7517
|
+
fillRule: e.node.fillRule ?? "nonzero"
|
|
6460
7518
|
}))
|
|
6461
7519
|
);
|
|
6462
|
-
const base =
|
|
6463
|
-
const anchorRoot = roots.find((r) =>
|
|
7520
|
+
const base = entries[0].node;
|
|
7521
|
+
const anchorRoot = roots.find((r) => this.holds(r, base.id)) ?? base.id;
|
|
6464
7522
|
const subpaths = bezierToSubpaths(this.intoParentSpace(anchorRoot, result.path));
|
|
6465
7523
|
if (result.error || !subpaths.length) return null;
|
|
6466
7524
|
const id = this.newId();
|
|
6467
|
-
const { transform: _t, ...baseRest } = cloneVectorNode(base);
|
|
7525
|
+
const { transform: _t, effects: _e, booleanOperation: _b, ...baseRest } = cloneVectorNode(base);
|
|
6468
7526
|
const node = { ...baseRest, id, name: BOOLEAN_LABELS[op], subpaths };
|
|
6469
|
-
const drop = /* @__PURE__ */ new Set([...
|
|
7527
|
+
const drop = /* @__PURE__ */ new Set([...entries.map((e) => e.node.id), ...roots]);
|
|
6470
7528
|
this.change(BOOLEAN_LABELS[op], () => {
|
|
6471
7529
|
this.setDoc(mapTree(this.doc.nodes, (n) => n.id === anchorRoot ? node : drop.has(n.id) ? null : n));
|
|
6472
7530
|
this.selection = [id];
|
|
@@ -6475,18 +7533,75 @@ var VectorEditorController = class {
|
|
|
6475
7533
|
});
|
|
6476
7534
|
return id;
|
|
6477
7535
|
}
|
|
7536
|
+
/** Whether stored node `rootId` holds `id` as drawn (`id` may be an expanded path inside it). */
|
|
7537
|
+
holds(rootId, id) {
|
|
7538
|
+
const n = this.getNode(rootId);
|
|
7539
|
+
return !!n && (!!findVectorNode([n], id) || !!findVectorNode([expandVectorAppearance(n)], id));
|
|
7540
|
+
}
|
|
7541
|
+
liveBooleanOp(op, roots) {
|
|
7542
|
+
const nodes = roots.map((id2) => this.getNode(id2));
|
|
7543
|
+
if (!nodes.length || nodes.some((n) => !n)) return null;
|
|
7544
|
+
const label = `Live ${BOOLEAN_LABELS[op]}`;
|
|
7545
|
+
const lone = nodes.length === 1 ? nodes[0] : null;
|
|
7546
|
+
if (isVectorLiveBoolean(lone)) {
|
|
7547
|
+
const children = lone.children.map((c) => c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest)(c) : c);
|
|
7548
|
+
this.change(label, () => this.replaceNode(lone.id, { ...lone, liveBoolean: op, children }));
|
|
7549
|
+
return lone.id;
|
|
7550
|
+
}
|
|
7551
|
+
const parents = roots.map((id2) => locateVectorNode(this.doc.nodes, id2)?.parent ?? null);
|
|
7552
|
+
if (parents.every((g) => isVectorLiveBoolean(g))) {
|
|
7553
|
+
const set = new Set(roots);
|
|
7554
|
+
this.change(
|
|
7555
|
+
label,
|
|
7556
|
+
() => this.setDoc(
|
|
7557
|
+
mapTree(this.doc.nodes, (n) => {
|
|
7558
|
+
if (n.type !== "group" || !n.liveBoolean || !n.children.some((c) => set.has(c.id))) return n;
|
|
7559
|
+
const group = n;
|
|
7560
|
+
const children = group.children.map((c) => {
|
|
7561
|
+
if (!set.has(c.id)) return c;
|
|
7562
|
+
const { booleanOperation: _b, ...rest } = c;
|
|
7563
|
+
return op === group.liveBoolean ? rest : { ...rest, booleanOperation: op };
|
|
7564
|
+
});
|
|
7565
|
+
return { ...group, children };
|
|
7566
|
+
})
|
|
7567
|
+
)
|
|
7568
|
+
);
|
|
7569
|
+
return parents[0].id;
|
|
7570
|
+
}
|
|
7571
|
+
if (nodes.length < 2) return null;
|
|
7572
|
+
const parent = parents[0]?.id ?? null;
|
|
7573
|
+
if (parents.some((g) => (g?.id ?? null) !== parent)) return null;
|
|
7574
|
+
const id = this.newId();
|
|
7575
|
+
this.change(label, () => {
|
|
7576
|
+
const set = new Set(roots);
|
|
7577
|
+
const wrap = (list) => {
|
|
7578
|
+
const members = list.filter((n) => set.has(n.id)).map((c) => c.booleanOperation ? (({ booleanOperation: _b, ...rest }) => rest)(c) : c);
|
|
7579
|
+
const top = Math.max(...list.map((n, i) => set.has(n.id) ? i : -1));
|
|
7580
|
+
const out = [];
|
|
7581
|
+
list.forEach((n, i) => {
|
|
7582
|
+
if (i === top) out.push({ id, type: "group", name: BOOLEAN_LABELS[op], liveBoolean: op, children: members });
|
|
7583
|
+
if (!set.has(n.id)) out.push(n);
|
|
7584
|
+
});
|
|
7585
|
+
return out;
|
|
7586
|
+
};
|
|
7587
|
+
if (parent === null) this.setDoc(wrap(this.doc.nodes));
|
|
7588
|
+
else this.setDoc(mapTree(this.doc.nodes, (n) => n.id === parent && n.type === "group" ? { ...n, children: wrap(n.children) } : n));
|
|
7589
|
+
this.selection = [id];
|
|
7590
|
+
this.anchors = [];
|
|
7591
|
+
this.editingId = null;
|
|
7592
|
+
});
|
|
7593
|
+
return id;
|
|
7594
|
+
}
|
|
6478
7595
|
/** Joins the selected paths into one compound path (subpaths kept, lowest style). */
|
|
6479
7596
|
combine(options = {}) {
|
|
6480
7597
|
const roots = options.ids ?? this.selectedRoots();
|
|
6481
|
-
const
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
});
|
|
6485
|
-
if (paths.length < 2) return null;
|
|
7598
|
+
const entries = vectorPathEntries(this.doc, roots, { locked: true });
|
|
7599
|
+
if (entries.length < 2) return null;
|
|
7600
|
+
const paths = entries.map((e) => e.node);
|
|
6486
7601
|
const id = this.newId();
|
|
6487
|
-
const first = roots.find((r) =>
|
|
6488
|
-
const subpaths =
|
|
6489
|
-
const { transform: _t, ...baseRest } = cloneVectorNode(paths[0]);
|
|
7602
|
+
const first = roots.find((r) => this.holds(r, paths[0].id)) ?? paths[0].id;
|
|
7603
|
+
const subpaths = entries.flatMap((e) => bezierToSubpaths(this.intoParentSpace(first, vectorPathWorldBezier(e.node, e.parent))));
|
|
7604
|
+
const { transform: _t, effects: _e, booleanOperation: _b, ...baseRest } = cloneVectorNode(paths[0]);
|
|
6490
7605
|
const node = { ...baseRest, id, name: "Compound Path", subpaths };
|
|
6491
7606
|
const drop = /* @__PURE__ */ new Set([...paths.map((p) => p.id), ...roots]);
|
|
6492
7607
|
this.change("Combine", () => {
|
|
@@ -6541,10 +7656,7 @@ var VectorEditorController = class {
|
|
|
6541
7656
|
const result = pathfinder(op, shapes);
|
|
6542
7657
|
if (result.error || !result.pieces.length) return [];
|
|
6543
7658
|
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;
|
|
7659
|
+
const topRoot = [...roots].reverse().find((r) => this.holds(r, last)) ?? last;
|
|
6548
7660
|
const nodes = [];
|
|
6549
7661
|
for (const piece of result.pieces) {
|
|
6550
7662
|
const subpaths = bezierToSubpaths(this.intoParentSpace(topRoot, piece.path)).map((sp) => piece.open ? { ...sp, closed: false } : sp);
|
|
@@ -6737,7 +7849,7 @@ var VectorEditorController = class {
|
|
|
6737
7849
|
const roots = ids ?? this.selectedRoots();
|
|
6738
7850
|
const key = roots.join("\0");
|
|
6739
7851
|
if (this.builderCache?.doc === this.doc && this.builderCache.key === key) return this.builderCache.map;
|
|
6740
|
-
const entries = roots.length ? vectorPathEntries(this.doc, roots) : [];
|
|
7852
|
+
const entries = roots.length ? vectorPathEntries(this.doc, roots, { expand: false }) : [];
|
|
6741
7853
|
const map = entries.length ? vectorShapeBuilderMap(entries) : null;
|
|
6742
7854
|
this.builderCache = { doc: this.doc, key, map };
|
|
6743
7855
|
return map;
|
|
@@ -7521,16 +8633,22 @@ var VectorEditorController = class {
|
|
|
7521
8633
|
return best;
|
|
7522
8634
|
}
|
|
7523
8635
|
/** Deepest unlocked visible path under `p`. */
|
|
8636
|
+
/** Live groups being edited (selected, or holding the selection or the edited path): their children are hit as stored. */
|
|
8637
|
+
enteredGroups() {
|
|
8638
|
+
return vectorEnteredGroups(this.doc.nodes, this.editingId ? [...this.selection, this.editingId] : this.selection);
|
|
8639
|
+
}
|
|
7524
8640
|
hitPath(p) {
|
|
7525
8641
|
const tol = this.tolerance();
|
|
8642
|
+
const entered = this.enteredGroups();
|
|
7526
8643
|
const search = (list, parent) => {
|
|
7527
8644
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
7528
8645
|
const n = list[i];
|
|
7529
8646
|
if (n.visible === false || n.locked) continue;
|
|
7530
8647
|
if (n.type === "group") {
|
|
8648
|
+
if (hasOwnVectorAppearance(n) && !entered.has(n.id) && !hitTestVectorNode(n, p, tol, parent, { entered })) continue;
|
|
7531
8649
|
const found = search(n.children, multiplyMatrices(nodeMatrix(n), parent));
|
|
7532
8650
|
if (found) return found;
|
|
7533
|
-
} else if (hitTestVectorNode(n, p, tol, parent)) return n;
|
|
8651
|
+
} else if (hitTestVectorNode(n, p, tol, parent, { entered })) return n;
|
|
7534
8652
|
}
|
|
7535
8653
|
return null;
|
|
7536
8654
|
};
|
|
@@ -7555,7 +8673,7 @@ var VectorEditorController = class {
|
|
|
7555
8673
|
case "select": {
|
|
7556
8674
|
const handle = this.hitTransformHandle(screen);
|
|
7557
8675
|
if (handle) return { nodeId: null, transformHandle: handle };
|
|
7558
|
-
const node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance());
|
|
8676
|
+
const node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance(), { entered: this.enteredGroups() });
|
|
7559
8677
|
return node ? { nodeId: node.id } : null;
|
|
7560
8678
|
}
|
|
7561
8679
|
case "direct": {
|
|
@@ -7884,7 +9002,7 @@ var VectorEditorController = class {
|
|
|
7884
9002
|
this.notify();
|
|
7885
9003
|
return;
|
|
7886
9004
|
}
|
|
7887
|
-
const node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance());
|
|
9005
|
+
const node = hitTestVectorNodes(this.doc.nodes, p, this.tolerance(), { entered: this.enteredGroups() });
|
|
7888
9006
|
if (node) {
|
|
7889
9007
|
const selected = this.selection.includes(node.id);
|
|
7890
9008
|
if (input.shift) {
|
|
@@ -8809,8 +9927,9 @@ function pencilPressureProfile(points, pressures) {
|
|
|
8809
9927
|
var fmt = (n) => Math.round(n * 100) / 100;
|
|
8810
9928
|
var fmtMatrix = (m) => `matrix(${m.map((n) => Math.round(n * 1e6) / 1e6).join(" ")})`;
|
|
8811
9929
|
var VECTOR_BUILDER_HATCH_GAP = 6;
|
|
8812
|
-
function renderNode(
|
|
8813
|
-
if (
|
|
9930
|
+
function renderNode(stored, defs, idPrefix) {
|
|
9931
|
+
if (stored.visible === false) return null;
|
|
9932
|
+
const node = expandVectorAppearance(stored);
|
|
8814
9933
|
if (node.type === "group") {
|
|
8815
9934
|
const children = node.children.map((c) => renderNode(c, defs, idPrefix)).filter((c) => !!c);
|
|
8816
9935
|
return { kind: "group", id: node.id, ...node.transform ? { transform: fmtMatrix(node.transform) } : {}, opacity: node.opacity ?? 1, children };
|
|
@@ -8851,16 +9970,20 @@ function getVectorSceneRenderData(state, options = {}) {
|
|
|
8851
9970
|
};
|
|
8852
9971
|
}
|
|
8853
9972
|
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) {
|
|
9973
|
+
function screenOutline(state, node, entered) {
|
|
8855
9974
|
const v2 = state.viewport;
|
|
8856
9975
|
const view = [v2.zoom, 0, 0, v2.zoom, v2.x, v2.y];
|
|
8857
9976
|
const parent = vectorParentMatrix(state.document.nodes, node.id) ?? IDENTITY_MATRIX;
|
|
8858
|
-
return
|
|
9977
|
+
return outlinePaths(node, parent, entered).map((path) => bezierPathToSvg(transformBezierPath(path, view), { precision: 2 })).join(" ");
|
|
8859
9978
|
}
|
|
8860
|
-
function
|
|
8861
|
-
if (node.type === "path") return [{
|
|
9979
|
+
function outlinePaths(node, parent, entered) {
|
|
9980
|
+
if (node.type === "path") return [vectorPathWorldBezier(node, parent, { effects: false })];
|
|
8862
9981
|
const m = multiplyMatrices(nodeMatrix(node), parent);
|
|
8863
|
-
|
|
9982
|
+
if (hasOwnVectorAppearance(node) && !entered.has(node.id)) return [transformBezierPath(vectorAppearanceOutline(node), m)];
|
|
9983
|
+
return node.children.filter((c) => c.visible !== false).flatMap((c) => outlinePaths(c, m, entered));
|
|
9984
|
+
}
|
|
9985
|
+
function getVectorEnteredGroups(state) {
|
|
9986
|
+
return vectorEnteredGroups(state.document.nodes, state.editingId ? [...state.selection, state.editingId] : state.selection);
|
|
8864
9987
|
}
|
|
8865
9988
|
function collect(node) {
|
|
8866
9989
|
if (node.type === "path") return [node];
|
|
@@ -8870,13 +9993,14 @@ function getVectorOverlayRenderData(state) {
|
|
|
8870
9993
|
const doc = state.document;
|
|
8871
9994
|
const z = state.viewport.zoom;
|
|
8872
9995
|
const outlines = [];
|
|
9996
|
+
const entered = getVectorEnteredGroups(state);
|
|
8873
9997
|
const seen = /* @__PURE__ */ new Set();
|
|
8874
9998
|
const outline = (id, selected2) => {
|
|
8875
9999
|
if (seen.has(id)) return;
|
|
8876
10000
|
const node = findVectorNode(doc.nodes, id);
|
|
8877
10001
|
if (!node || node.visible === false) return;
|
|
8878
10002
|
seen.add(id);
|
|
8879
|
-
outlines.push({ id, d: screenOutline(state, node), selected: selected2 });
|
|
10003
|
+
outlines.push({ id, d: screenOutline(state, node, entered), selected: selected2 });
|
|
8880
10004
|
};
|
|
8881
10005
|
for (const id of state.selection) outline(id, true);
|
|
8882
10006
|
if (state.editingId) outline(state.editingId, true);
|
|
@@ -9041,7 +10165,7 @@ function blendOverlay(state) {
|
|
|
9041
10165
|
const from = state.blendStart ? findVectorNode(doc.nodes, state.blendStart.nodeId) : void 0;
|
|
9042
10166
|
if (from && state.blendStart) {
|
|
9043
10167
|
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 };
|
|
10168
|
+
start = { d: screenOutline(state, from, /* @__PURE__ */ new Set()), anchor: a ? sc(a, view) : null };
|
|
9045
10169
|
}
|
|
9046
10170
|
const hovered = state.hover?.nodeId ? findVectorNode(doc.nodes, state.hover.nodeId) : void 0;
|
|
9047
10171
|
if (hovered?.type === "path" && hovered.subpaths[0]) {
|
|
@@ -9226,8 +10350,28 @@ var VECTOR_EDIT_TOGGLES = [
|
|
|
9226
10350
|
{ key: "curvatureMoves", label: "Curv", title: "Curvature point moves (Alt flips while dragging)" },
|
|
9227
10351
|
{ key: "recognizeShapes", label: "Shapes", title: "Turn pencil scribbles into shapes" }
|
|
9228
10352
|
];
|
|
10353
|
+
var VECTOR_EFFECT_MENU = VECTOR_EFFECT_TYPES.map((type) => ({ type, label: VECTOR_EFFECT_LABELS[type] }));
|
|
10354
|
+
function getVectorEffectsPanel(state) {
|
|
10355
|
+
const nodes = state.selection.map((id) => findVectorNode(state.document.nodes, id)).filter((n) => !!n && !n.blendStep);
|
|
10356
|
+
if (!nodes.length) return null;
|
|
10357
|
+
const one = nodes.length === 1 ? nodes[0] : null;
|
|
10358
|
+
return {
|
|
10359
|
+
nodeId: one?.id ?? null,
|
|
10360
|
+
count: nodes.length,
|
|
10361
|
+
effects: (one?.effects ?? []).map((e) => ({
|
|
10362
|
+
id: e.id,
|
|
10363
|
+
type: e.type,
|
|
10364
|
+
label: VECTOR_EFFECT_LABELS[e.type] ?? e.type,
|
|
10365
|
+
enabled: e.enabled !== false,
|
|
10366
|
+
params: e.params,
|
|
10367
|
+
specs: VECTOR_EFFECT_PARAM_SPECS[e.type] ?? []
|
|
10368
|
+
})),
|
|
10369
|
+
expandable: nodes.some(hasVectorAppearance)
|
|
10370
|
+
};
|
|
10371
|
+
}
|
|
9229
10372
|
|
|
9230
10373
|
// src/vector/commands.ts
|
|
10374
|
+
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
10375
|
var fail = (message) => {
|
|
9232
10376
|
throw new Error(message);
|
|
9233
10377
|
};
|
|
@@ -9338,7 +10482,10 @@ function createVectorCommands(controller, options = {}) {
|
|
|
9338
10482
|
selected: state.selection.includes(n.id),
|
|
9339
10483
|
...n.type === "path" ? { fill: n.fill ?? null, stroke: n.stroke ?? null, strokeWidth: n.strokeWidth ?? 1, d: vectorPathData(n, 2) } : { children: n.children.length },
|
|
9340
10484
|
...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 } : {}
|
|
10485
|
+
...n.blendStep ? { blendStep: true } : {},
|
|
10486
|
+
...n.effects?.length ? { effects: n.effects } : {},
|
|
10487
|
+
...n.type === "group" && n.liveBoolean ? { liveBoolean: n.liveBoolean } : {},
|
|
10488
|
+
...n.booleanOperation ? { booleanOperation: n.booleanOperation } : {}
|
|
9342
10489
|
});
|
|
9343
10490
|
if (n.type === "group") visit(n.children, depth + 1);
|
|
9344
10491
|
}
|
|
@@ -9574,15 +10721,101 @@ function createVectorCommands(controller, options = {}) {
|
|
|
9574
10721
|
id: `${prefix}boolean`,
|
|
9575
10722
|
label: "Boolean Operation\u2026",
|
|
9576
10723
|
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: {
|
|
10724
|
+
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).",
|
|
10725
|
+
params: {
|
|
10726
|
+
type: "object",
|
|
10727
|
+
properties: {
|
|
10728
|
+
operation: { type: "string", enum: ["union", "subtract", "intersect", "exclude"] },
|
|
10729
|
+
ids: IDS,
|
|
10730
|
+
live: { type: "boolean", description: "Keep the operands editable in a live boolean group (default false: one new path)." }
|
|
10731
|
+
},
|
|
10732
|
+
required: ["operation"]
|
|
10733
|
+
},
|
|
9579
10734
|
undoable: true,
|
|
9580
10735
|
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).");
|
|
10736
|
+
const id = controller.booleanOp(params.operation, { ids: ids(params), live: params.live });
|
|
10737
|
+
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
10738
|
return { id };
|
|
9584
10739
|
}
|
|
9585
10740
|
},
|
|
10741
|
+
{
|
|
10742
|
+
...base,
|
|
10743
|
+
id: `${prefix}addEffect`,
|
|
10744
|
+
label: "Add Path Effect\u2026",
|
|
10745
|
+
keywords: ["effect", "trim", "round corners", "pucker", "bloat", "twist", "zig zag", "wiggle", "offset", "merge", "repeater", "appearance", "live"],
|
|
10746
|
+
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.",
|
|
10747
|
+
params: {
|
|
10748
|
+
type: "object",
|
|
10749
|
+
properties: {
|
|
10750
|
+
type: { type: "string", enum: VECTOR_EFFECT_TYPES },
|
|
10751
|
+
params: { type: "object", description: EFFECT_PARAMS_DESCRIPTION },
|
|
10752
|
+
index: { type: "integer", minimum: 0, description: "Position in the stack (default: last)." },
|
|
10753
|
+
ids: IDS
|
|
10754
|
+
},
|
|
10755
|
+
required: ["type"]
|
|
10756
|
+
},
|
|
10757
|
+
undoable: true,
|
|
10758
|
+
enabled: enabledFor,
|
|
10759
|
+
run: (_ctx, params) => {
|
|
10760
|
+
if (!VECTOR_EFFECT_TYPES.includes(params.type)) fail(`Unknown effect type "${params.type}".`);
|
|
10761
|
+
const targets = ids(params);
|
|
10762
|
+
const created = controller.addEffect(params.type, { ids: targets, params: params.params, index: params.index });
|
|
10763
|
+
if (!created.length) fail("Nothing to add the effect to.");
|
|
10764
|
+
return { effects: targets.map((id, i) => ({ nodeId: id, effectId: created[i] })) };
|
|
10765
|
+
}
|
|
10766
|
+
},
|
|
10767
|
+
{
|
|
10768
|
+
...base,
|
|
10769
|
+
id: `${prefix}updateEffect`,
|
|
10770
|
+
label: "Edit Path Effect\u2026",
|
|
10771
|
+
keywords: ["effect", "parameters", "enable", "disable", "reorder"],
|
|
10772
|
+
description: "Changes a path effect: its parameters, whether it is enabled, and its position in the stack.",
|
|
10773
|
+
params: {
|
|
10774
|
+
type: "object",
|
|
10775
|
+
properties: {
|
|
10776
|
+
nodeId: { type: "string" },
|
|
10777
|
+
effectId: { type: "string" },
|
|
10778
|
+
params: { type: "object", description: EFFECT_PARAMS_DESCRIPTION },
|
|
10779
|
+
enabled: { type: "boolean" },
|
|
10780
|
+
index: { type: "integer", minimum: 0, description: "Move it to this position in the stack (0 applies first)." }
|
|
10781
|
+
},
|
|
10782
|
+
required: ["nodeId", "effectId"]
|
|
10783
|
+
},
|
|
10784
|
+
undoable: true,
|
|
10785
|
+
run: (_ctx, params) => {
|
|
10786
|
+
if (params.params !== void 0 || params.enabled !== void 0) check(controller.updateEffect(params.nodeId, params.effectId, { params: params.params, enabled: params.enabled }));
|
|
10787
|
+
if (params.index !== void 0) check(controller.moveEffect(params.nodeId, params.effectId, params.index));
|
|
10788
|
+
return { effects: controller.getNode(params.nodeId)?.effects ?? [] };
|
|
10789
|
+
}
|
|
10790
|
+
},
|
|
10791
|
+
{
|
|
10792
|
+
...base,
|
|
10793
|
+
id: `${prefix}removeEffect`,
|
|
10794
|
+
label: "Remove Path Effect",
|
|
10795
|
+
keywords: ["effect", "delete"],
|
|
10796
|
+
description: "Removes a path effect from a node.",
|
|
10797
|
+
params: { type: "object", properties: { nodeId: { type: "string" }, effectId: { type: "string" } }, required: ["nodeId", "effectId"] },
|
|
10798
|
+
undoable: true,
|
|
10799
|
+
run: (_ctx, params) => {
|
|
10800
|
+
check(controller.removeEffect(params.nodeId, params.effectId));
|
|
10801
|
+
return { ok: true };
|
|
10802
|
+
}
|
|
10803
|
+
},
|
|
10804
|
+
{
|
|
10805
|
+
...base,
|
|
10806
|
+
id: `${prefix}expandAppearance`,
|
|
10807
|
+
label: "Expand Appearance",
|
|
10808
|
+
keywords: ["effects", "bake", "flatten", "live boolean", "expand"],
|
|
10809
|
+
description: "Bakes the path effects and live booleans of nodes (and everything inside them) into plain paths.",
|
|
10810
|
+
params: { type: "object", properties: { ids: IDS } },
|
|
10811
|
+
undoable: true,
|
|
10812
|
+
enabled: enabledFor,
|
|
10813
|
+
run: (_ctx, params) => {
|
|
10814
|
+
const expanded = controller.expandAppearance({ ids: ids(params) });
|
|
10815
|
+
if (!expanded.length) fail("Nothing selected has path effects or a live boolean.");
|
|
10816
|
+
return { ids: expanded };
|
|
10817
|
+
}
|
|
10818
|
+
},
|
|
9586
10819
|
{
|
|
9587
10820
|
...base,
|
|
9588
10821
|
id: `${prefix}blend`,
|
|
@@ -10030,8 +11263,14 @@ export {
|
|
|
10030
11263
|
VECTOR_CORNER_KIND_LABELS,
|
|
10031
11264
|
VECTOR_CORNER_WIDGET_INSET,
|
|
10032
11265
|
VECTOR_EDIT_TOGGLES,
|
|
11266
|
+
VECTOR_EFFECT_DEFAULTS,
|
|
11267
|
+
VECTOR_EFFECT_LABELS,
|
|
11268
|
+
VECTOR_EFFECT_MENU,
|
|
11269
|
+
VECTOR_EFFECT_PARAM_SPECS,
|
|
11270
|
+
VECTOR_EFFECT_TYPES,
|
|
10033
11271
|
VECTOR_FILL_GAP,
|
|
10034
11272
|
VECTOR_INK_MAX_CORNERS,
|
|
11273
|
+
VECTOR_MERGE_MODES,
|
|
10035
11274
|
VECTOR_OVERLAY_THEME,
|
|
10036
11275
|
VECTOR_PATHFINDER_ACTIONS,
|
|
10037
11276
|
VECTOR_PATHFINDER_LABELS,
|
|
@@ -10053,11 +11292,13 @@ export {
|
|
|
10053
11292
|
VECTOR_WIDTH_SAME_T,
|
|
10054
11293
|
VectorEditorController,
|
|
10055
11294
|
VectorSnapTargets,
|
|
11295
|
+
activeVectorEffects,
|
|
10056
11296
|
addBuilderHit,
|
|
10057
11297
|
applyMatrix,
|
|
10058
11298
|
applyMatrixLinear,
|
|
10059
11299
|
applyVectorBlendOptions,
|
|
10060
11300
|
applyVectorCornersAttribute,
|
|
11301
|
+
applyVectorEffects,
|
|
10061
11302
|
arrowheadInset,
|
|
10062
11303
|
arrowheadPath,
|
|
10063
11304
|
arrowheadWeight,
|
|
@@ -10065,6 +11306,7 @@ export {
|
|
|
10065
11306
|
bezierToSubpaths,
|
|
10066
11307
|
boundsFromPoints,
|
|
10067
11308
|
boundsIntersect,
|
|
11309
|
+
clearVectorLiveBooleanCache,
|
|
10068
11310
|
cloneAnchor,
|
|
10069
11311
|
cloneSubpath,
|
|
10070
11312
|
cloneVectorDocument,
|
|
@@ -10077,22 +11319,28 @@ export {
|
|
|
10077
11319
|
cornerSetback,
|
|
10078
11320
|
correspondMorphContours,
|
|
10079
11321
|
createVectorCommands,
|
|
11322
|
+
createVectorEffect,
|
|
10080
11323
|
createVectorMorph,
|
|
10081
11324
|
curvatureMoveAnchor,
|
|
10082
11325
|
cutSubpathAt,
|
|
10083
11326
|
dashIntervals,
|
|
10084
11327
|
decomposeRigid,
|
|
11328
|
+
effectPathLength,
|
|
11329
|
+
effectPathVertexCount,
|
|
10085
11330
|
effectiveStrokeAlign,
|
|
10086
11331
|
ellipseSubpath,
|
|
10087
11332
|
eraseFromFill,
|
|
10088
11333
|
eraseFromOutline,
|
|
10089
11334
|
eraserRegion,
|
|
10090
11335
|
evaluateVectorPathKeyframes,
|
|
11336
|
+
expandVectorAppearance,
|
|
11337
|
+
expandVectorAppearances,
|
|
10091
11338
|
expandVectorBlend,
|
|
10092
11339
|
expandVectorCorners,
|
|
10093
11340
|
expandVectorCornersOf,
|
|
10094
11341
|
findVectorNode,
|
|
10095
11342
|
fitCornerRadius,
|
|
11343
|
+
foldVectorBooleanOperands,
|
|
10096
11344
|
generateVectorBlendChildren,
|
|
10097
11345
|
generateWidthProfile,
|
|
10098
11346
|
getAnchor,
|
|
@@ -10101,6 +11349,8 @@ export {
|
|
|
10101
11349
|
getTransformHandlePositions,
|
|
10102
11350
|
getVectorBlendControls,
|
|
10103
11351
|
getVectorCornerWidgets,
|
|
11352
|
+
getVectorEffectsPanel,
|
|
11353
|
+
getVectorEnteredGroups,
|
|
10104
11354
|
getVectorOverlayRenderData,
|
|
10105
11355
|
getVectorSceneRenderData,
|
|
10106
11356
|
getVectorSelectionBoxGeometry,
|
|
@@ -10109,8 +11359,10 @@ export {
|
|
|
10109
11359
|
getVectorWidthHandles,
|
|
10110
11360
|
getVisibleAnchors,
|
|
10111
11361
|
getVisibleHandles,
|
|
11362
|
+
hasOwnVectorAppearance,
|
|
10112
11363
|
hasPaint,
|
|
10113
11364
|
hasStrokeExtras,
|
|
11365
|
+
hasVectorAppearance,
|
|
10114
11366
|
hasVectorCorners,
|
|
10115
11367
|
hitTestVectorNode,
|
|
10116
11368
|
hitTestVectorNodes,
|
|
@@ -10130,12 +11382,14 @@ export {
|
|
|
10130
11382
|
isIdentityMatrix,
|
|
10131
11383
|
isUniformWidthProfile,
|
|
10132
11384
|
isVectorBlendGroup,
|
|
11385
|
+
isVectorLiveBoolean,
|
|
10133
11386
|
isVectorTextTarget,
|
|
10134
11387
|
joinSubpaths,
|
|
10135
11388
|
knifePath,
|
|
10136
11389
|
lineSubpath,
|
|
10137
11390
|
locateVectorNode,
|
|
10138
11391
|
matrixRotation,
|
|
11392
|
+
mergePaths,
|
|
10139
11393
|
mirrorWidthProfile,
|
|
10140
11394
|
morphContourToSubpath,
|
|
10141
11395
|
morphPathsCross,
|
|
@@ -10145,12 +11399,16 @@ export {
|
|
|
10145
11399
|
nearestStrokePosition,
|
|
10146
11400
|
nextVectorCornerKind,
|
|
10147
11401
|
nodeMatrix,
|
|
11402
|
+
normalizeVectorEffectParams,
|
|
11403
|
+
offsetPaths,
|
|
10148
11404
|
parseSvgTransform,
|
|
10149
11405
|
parseVectorSvg,
|
|
11406
|
+
pathsCenter,
|
|
10150
11407
|
planShapeBuilder,
|
|
10151
11408
|
polygonSubpath,
|
|
10152
11409
|
polylineBounds,
|
|
10153
11410
|
pressureWidthProfile,
|
|
11411
|
+
puckerBloatPaths,
|
|
10154
11412
|
recognizeInkShape,
|
|
10155
11413
|
rectSubpath,
|
|
10156
11414
|
refineMorphContour,
|
|
@@ -10161,14 +11419,17 @@ export {
|
|
|
10161
11419
|
removeAnchorsKeepingShape,
|
|
10162
11420
|
removeRedundantPoints,
|
|
10163
11421
|
removeWidthPoints,
|
|
11422
|
+
repeatPaths,
|
|
10164
11423
|
resampleGradientStops,
|
|
10165
11424
|
resolveVectorBlendOptions,
|
|
10166
11425
|
reverseMorphContour,
|
|
10167
11426
|
reverseSubpath,
|
|
10168
11427
|
reverseWidthProfile,
|
|
10169
11428
|
rotateMatrix,
|
|
11429
|
+
roundCornerPaths,
|
|
10170
11430
|
roundSubpaths,
|
|
10171
11431
|
samplePolyline,
|
|
11432
|
+
sanitizeVectorEffects,
|
|
10172
11433
|
sanitizeWidthProfile,
|
|
10173
11434
|
scaleFor,
|
|
10174
11435
|
scaleMatrix,
|
|
@@ -10192,13 +11453,21 @@ export {
|
|
|
10192
11453
|
toVectorKeyInput,
|
|
10193
11454
|
toVectorModifiers,
|
|
10194
11455
|
toVectorPointerInput,
|
|
11456
|
+
transformVectorEffects,
|
|
10195
11457
|
transformVectorNode,
|
|
10196
11458
|
translateAnchor,
|
|
10197
11459
|
translateMatrix,
|
|
11460
|
+
trimIntervals,
|
|
11461
|
+
trimPaths,
|
|
11462
|
+
trimPathsIndividually,
|
|
10198
11463
|
turnMorphContour,
|
|
11464
|
+
twistPaths,
|
|
10199
11465
|
unionBounds,
|
|
10200
11466
|
variableStrokeOutline,
|
|
10201
11467
|
variableStrokeRings,
|
|
11468
|
+
vectorAppearanceNodes,
|
|
11469
|
+
vectorAppearanceOutline,
|
|
11470
|
+
vectorAppearanceShapes,
|
|
10202
11471
|
vectorBlendColorDistance,
|
|
10203
11472
|
vectorBlendKeyTargets,
|
|
10204
11473
|
vectorBlendKeys,
|
|
@@ -10212,11 +11481,14 @@ export {
|
|
|
10212
11481
|
vectorCornersAttribute,
|
|
10213
11482
|
vectorDiamondPath,
|
|
10214
11483
|
vectorDocumentToSvg,
|
|
11484
|
+
vectorEnteredGroups,
|
|
10215
11485
|
vectorFillMap,
|
|
10216
11486
|
vectorInkShapeName,
|
|
11487
|
+
vectorLiveBooleanSteps,
|
|
10217
11488
|
vectorNodeBounds,
|
|
10218
11489
|
vectorNodeRotation,
|
|
10219
11490
|
vectorNodeToBezier,
|
|
11491
|
+
vectorOperandOperation,
|
|
10220
11492
|
vectorPaintToHex,
|
|
10221
11493
|
vectorPaintToSvg,
|
|
10222
11494
|
vectorParentMatrix,
|
|
@@ -10224,6 +11496,7 @@ export {
|
|
|
10224
11496
|
vectorPathEntries,
|
|
10225
11497
|
vectorPathWorldBezier,
|
|
10226
11498
|
vectorPathfinderShape,
|
|
11499
|
+
vectorRepeaterCopies,
|
|
10227
11500
|
vectorResizeCursor,
|
|
10228
11501
|
vectorResultStyle,
|
|
10229
11502
|
vectorShapeBuilderHit,
|
|
@@ -10243,6 +11516,8 @@ export {
|
|
|
10243
11516
|
widthProfileMaxScale,
|
|
10244
11517
|
widthProfilePreset,
|
|
10245
11518
|
widthProfilePresetId,
|
|
10246
|
-
widthProfileToAttribute
|
|
11519
|
+
widthProfileToAttribute,
|
|
11520
|
+
wigglePaths,
|
|
11521
|
+
zigzagPaths
|
|
10247
11522
|
};
|
|
10248
11523
|
//# sourceMappingURL=index.js.map
|