@pixerate/editor 0.266.2 → 0.267.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/dist/agent/index.cjs +2 -2
- package/dist/agent/index.js +1 -1
- package/dist/animation-editor/controller.d.cts +8 -0
- package/dist/animation-editor/controller.d.ts +8 -0
- package/dist/animation-editor/effect-tools.d.cts +1 -1
- package/dist/animation-editor/effect-tools.d.ts +1 -1
- package/dist/animation-editor/index.cjs +172 -105
- package/dist/animation-editor/index.js +91 -24
- package/dist/brushes/index.cjs +59 -59
- package/dist/brushes/index.js +5 -5
- package/dist/brushes/profiles.d.cts +6 -1
- package/dist/brushes/profiles.d.ts +6 -1
- package/dist/{brushstrokeRender-FSUWXUZY.cjs → brushstrokeRender-L7JYRR2R.cjs} +16 -16
- package/dist/{brushstrokeRender-KD453OBX.js → brushstrokeRender-NLFPEBGI.js} +4 -4
- package/dist/canvas/brushTypes.d.cts +12 -0
- package/dist/canvas/brushTypes.d.ts +12 -0
- package/dist/canvas/brushstroke.d.cts +1 -1
- package/dist/canvas/brushstroke.d.ts +1 -1
- package/dist/canvas/drawController.d.cts +19 -4
- package/dist/canvas/drawController.d.ts +19 -4
- package/dist/canvas/index.cjs +144 -105
- package/dist/canvas/index.js +46 -7
- package/dist/{chunk-XORJ7UZO.cjs → chunk-3VHH6V67.cjs} +13 -12
- package/dist/{chunk-T2LDTEXK.js → chunk-4IDUYB45.js} +2217 -56
- package/dist/chunk-7UKFXXWL.js +802 -0
- package/dist/chunk-ADMRNWR7.js +340 -0
- package/dist/{chunk-CIJBSUM5.js → chunk-AGNUD5BI.js} +2 -2
- package/dist/{chunk-WLUVZKVD.cjs → chunk-DT4YQUGT.cjs} +32 -32
- package/dist/{chunk-DXYHQ2VN.js → chunk-EF7LZAXQ.js} +1 -1
- package/dist/chunk-EXB2EGBE.cjs +802 -0
- package/dist/{chunk-4ODRXQRW.js → chunk-EYSHTYLG.js} +1 -1
- package/dist/{chunk-R44Y3EFS.js → chunk-FGJHJIMN.js} +8 -336
- package/dist/{chunk-AQZQWXEU.cjs → chunk-G7NHE2VD.cjs} +35 -35
- package/dist/{chunk-XC6DPRYI.js → chunk-GSTSZPSR.js} +154 -949
- package/dist/{chunk-VRK322ZC.cjs → chunk-HIO6E7LC.cjs} +2 -2
- package/dist/{chunk-TOID45I2.cjs → chunk-J2TNIZ55.cjs} +1 -1
- package/dist/{chunk-YHDHFJUC.cjs → chunk-J5VQYK7T.cjs} +217 -1012
- package/dist/{chunk-3C33YTNN.js → chunk-JGWVZPT6.js} +2 -2
- package/dist/{chunk-ACFSFUQH.js → chunk-JL6YH5WD.js} +4 -0
- package/dist/{chunk-3SBLVGOM.cjs → chunk-LA5Y4ETA.cjs} +23 -23
- package/dist/{chunk-MVUQSK5J.cjs → chunk-LHPEG5AU.cjs} +107 -435
- package/dist/{chunk-DOZ6F2K2.js → chunk-NSETJI74.js} +3 -2
- package/dist/{chunk-XRXFVRGY.cjs → chunk-OKHKA43L.cjs} +2364 -203
- package/dist/{chunk-LK4SXL3V.js → chunk-OSEXT7JL.js} +1 -1
- package/dist/{chunk-PE6YJMQT.cjs → chunk-PFHJS4HE.cjs} +20 -19
- package/dist/{chunk-7KNW3WXZ.cjs → chunk-QEVLRLUN.cjs} +63 -0
- package/dist/{chunk-OTN6VB5S.cjs → chunk-RDFHWAHL.cjs} +5 -1
- package/dist/{chunk-6JL2D5SG.js → chunk-VE6UKYQ2.js} +3 -2
- package/dist/{chunk-RFJZGTHA.cjs → chunk-XVOP3CGQ.cjs} +9 -9
- package/dist/{chunk-EZOJBP4D.js → chunk-YIC6MAJ2.js} +1 -1
- package/dist/chunk-YZWM6ZIP.cjs +340 -0
- package/dist/{chunk-472INMWH.js → chunk-ZA7L5YJB.js} +63 -0
- package/dist/commands/builtins.d.cts +12 -1
- package/dist/commands/builtins.d.ts +12 -1
- package/dist/commands/index.cjs +2 -2
- package/dist/commands/index.js +1 -1
- package/dist/image-editor/controller.d.cts +1 -1
- package/dist/image-editor/controller.d.ts +1 -1
- package/dist/image-editor/index.cjs +837 -833
- package/dist/image-editor/index.js +105 -101
- package/dist/paint/index.cjs +5 -3
- package/dist/paint/index.js +46 -44
- package/dist/paint-editor/controller.d.cts +5 -3
- package/dist/paint-editor/controller.d.ts +5 -3
- package/dist/paint-editor/index.cjs +832 -2295
- package/dist/paint-editor/index.js +285 -1748
- package/dist/paint-editor/path-samples.d.cts +2 -0
- package/dist/paint-editor/path-samples.d.ts +2 -0
- package/dist/paint-editor/presence.d.cts +20 -1
- package/dist/paint-editor/presence.d.ts +20 -1
- package/dist/stickers/helpers.d.cts +2 -0
- package/dist/stickers/helpers.d.ts +2 -0
- package/dist/stickers/index.cjs +41 -35
- package/dist/stickers/index.js +22 -16
- package/dist/stickers/types.d.cts +4 -2
- package/dist/stickers/types.d.ts +4 -2
- package/dist/vector/backdrop.d.cts +13 -0
- package/dist/vector/backdrop.d.ts +13 -0
- package/dist/vector/brushstroke.d.cts +54 -7
- package/dist/vector/brushstroke.d.ts +54 -7
- package/dist/vector/brushstrokeMedia.d.cts +96 -0
- package/dist/vector/brushstrokeMedia.d.ts +96 -0
- package/dist/vector/controller.d.cts +17 -3
- package/dist/vector/controller.d.ts +17 -3
- package/dist/vector/index.cjs +1846 -1386
- package/dist/vector/index.d.cts +2 -0
- package/dist/vector/index.d.ts +2 -0
- package/dist/vector/index.js +517 -57
- package/dist/vector/render.d.cts +6 -0
- package/dist/vector/render.d.ts +6 -0
- package/dist/vector/types.d.cts +14 -0
- package/dist/vector/types.d.ts +14 -0
- package/package.json +1 -1
- package/skills/pixerate-animation-editor/SKILL.md +1 -0
- package/skills/pixerate-brushes/SKILL.md +2 -2
- package/skills/pixerate-brushes-design/SKILL.md +7 -4
- package/skills/pixerate-brushes-design/evals/port-to-vector.json +8 -8
- package/skills/pixerate-canvas/SKILL.md +11 -1
- package/skills/pixerate-image-editor/SKILL.md +19 -64
- package/skills/pixerate-image-editor/references/color-management.md +2 -1
- package/skills/pixerate-image-editor/references/free-transform.md +12 -0
- package/skills/pixerate-image-editor/references/layer-effects.md +10 -0
- package/skills/pixerate-image-editor/references/painting.md +17 -0
- package/skills/pixerate-image-editor/references/photoshop-files.md +1 -1
- package/skills/pixerate-image-editor/references/shapes-and-type.md +14 -0
- package/skills/pixerate-image-editor/references/smart-objects.md +10 -0
- package/skills/pixerate-paint-editor/SKILL.md +8 -4
- package/skills/pixerate-paint-editor/references/wet-media.md +9 -0
- package/skills/pixerate-vector/SKILL.md +16 -6
- package/skills/pixerate-vector/references/best-practices.md +1 -0
- package/skills/pixerate-vector/references/commands.md +19 -0
package/dist/vector/index.js
CHANGED
|
@@ -35,7 +35,10 @@ import {
|
|
|
35
35
|
traceField,
|
|
36
36
|
traceVectorImage,
|
|
37
37
|
vectorTraceContours
|
|
38
|
-
} from "../chunk-
|
|
38
|
+
} from "../chunk-YIC6MAJ2.js";
|
|
39
|
+
import {
|
|
40
|
+
rasterizeCoverage
|
|
41
|
+
} from "../chunk-ADMRNWR7.js";
|
|
39
42
|
import {
|
|
40
43
|
applyHomography,
|
|
41
44
|
homographyFromPoints,
|
|
@@ -63,7 +66,7 @@ import {
|
|
|
63
66
|
reverseMorphContour,
|
|
64
67
|
subpathToMorphContour,
|
|
65
68
|
turnMorphContour
|
|
66
|
-
} from "../chunk-
|
|
69
|
+
} from "../chunk-OSEXT7JL.js";
|
|
67
70
|
import {
|
|
68
71
|
DEFAULT_VECTOR_FONT,
|
|
69
72
|
IDENTITY_MATRIX,
|
|
@@ -119,6 +122,7 @@ import {
|
|
|
119
122
|
VECTOR_TRANSFORM_HANDLES,
|
|
120
123
|
VECTOR_VERTICAL_RESIZING,
|
|
121
124
|
VECTOR_VERTICAL_RESIZING_LABELS,
|
|
125
|
+
VECTOR_WET_STEPS,
|
|
122
126
|
VECTOR_WIDTH_PRESETS,
|
|
123
127
|
VECTOR_WIDTH_PRESET_IDS,
|
|
124
128
|
VECTOR_WIDTH_PROFILE_MAX_POINTS,
|
|
@@ -128,6 +132,7 @@ import {
|
|
|
128
132
|
VectorTextError,
|
|
129
133
|
WARP_STYLES,
|
|
130
134
|
WARP_STYLE_LABELS,
|
|
135
|
+
WetBrushstrokeJob,
|
|
131
136
|
activeVectorEffects,
|
|
132
137
|
activeVectorFilters,
|
|
133
138
|
applyMatrix,
|
|
@@ -220,6 +225,7 @@ import {
|
|
|
220
225
|
isUniformWidthProfile,
|
|
221
226
|
isValidWarpMesh,
|
|
222
227
|
isVectorBlendMode,
|
|
228
|
+
isVectorBrushstrokeEngine,
|
|
223
229
|
isVectorBrushstrokeLayer,
|
|
224
230
|
isVectorConnector,
|
|
225
231
|
isVectorFilterType,
|
|
@@ -270,6 +276,7 @@ import {
|
|
|
270
276
|
normalizeVectorPaintStacks,
|
|
271
277
|
normalizeVectorTextRuns,
|
|
272
278
|
offsetPaths,
|
|
279
|
+
onVectorBrushstrokesSettled,
|
|
273
280
|
parseSvgTransform,
|
|
274
281
|
parseVectorColor,
|
|
275
282
|
parseVectorSvg,
|
|
@@ -287,6 +294,7 @@ import {
|
|
|
287
294
|
puckerBloatPaths,
|
|
288
295
|
pullWarpMesh,
|
|
289
296
|
randomVectorBrushstrokeSeed,
|
|
297
|
+
readVectorBrushstrokePaint,
|
|
290
298
|
rectSubpath,
|
|
291
299
|
removeAnchors,
|
|
292
300
|
removeVectorFreeformPoint,
|
|
@@ -294,6 +302,8 @@ import {
|
|
|
294
302
|
removeWarpMeshSplitU,
|
|
295
303
|
removeWarpMeshSplitV,
|
|
296
304
|
removeWidthPoints,
|
|
305
|
+
renderBackdropBrushstroke,
|
|
306
|
+
renderWetBrushstroke,
|
|
297
307
|
repeatPaths,
|
|
298
308
|
replaceVectorText,
|
|
299
309
|
rerouteVectorConnectors,
|
|
@@ -335,9 +345,11 @@ import {
|
|
|
335
345
|
scaleVectorStrokeWidths,
|
|
336
346
|
setAnchorCorner,
|
|
337
347
|
setSubpathCorners,
|
|
348
|
+
setVectorBackdropSource,
|
|
338
349
|
setVectorGradientVector,
|
|
339
350
|
setVectorTextShaper,
|
|
340
351
|
setWidthPoint,
|
|
352
|
+
settleVectorBrushstrokes,
|
|
341
353
|
shadeVectorColor,
|
|
342
354
|
simplifyPolyline,
|
|
343
355
|
skewMatrix,
|
|
@@ -374,12 +386,14 @@ import {
|
|
|
374
386
|
trimPathsIndividually,
|
|
375
387
|
twistPaths,
|
|
376
388
|
unionBounds,
|
|
389
|
+
usesVectorBackdrop,
|
|
377
390
|
variableStrokeOutline,
|
|
378
391
|
variableStrokeRings,
|
|
379
392
|
vectorAppearanceNodes,
|
|
380
393
|
vectorAppearanceOutline,
|
|
381
394
|
vectorAppearanceShapes,
|
|
382
395
|
vectorArtboards,
|
|
396
|
+
vectorBackdropRect,
|
|
383
397
|
vectorBevelFaces,
|
|
384
398
|
vectorBlendModeCss,
|
|
385
399
|
vectorBlendModeFromCss,
|
|
@@ -392,7 +406,9 @@ import {
|
|
|
392
406
|
vectorBrushstrokeCharacterError,
|
|
393
407
|
vectorBrushstrokeDefaultColour,
|
|
394
408
|
vectorBrushstrokeImages,
|
|
409
|
+
vectorBrushstrokePaintError,
|
|
395
410
|
vectorBrushstrokeReach,
|
|
411
|
+
vectorBrushstrokesSettling,
|
|
396
412
|
vectorChildAppearanceShapes,
|
|
397
413
|
vectorConnectionSites,
|
|
398
414
|
vectorConnectorTargetAt,
|
|
@@ -455,6 +471,7 @@ import {
|
|
|
455
471
|
vectorMaskedShapes,
|
|
456
472
|
vectorMasterArt,
|
|
457
473
|
vectorMasterPages,
|
|
474
|
+
vectorMediaEngine,
|
|
458
475
|
vectorMeshColorAt,
|
|
459
476
|
vectorMeshFromOutline,
|
|
460
477
|
vectorMeshLines,
|
|
@@ -539,11 +556,11 @@ import {
|
|
|
539
556
|
withVectorStrokes,
|
|
540
557
|
withVectorTargetPaint,
|
|
541
558
|
zigzagPaths
|
|
542
|
-
} from "../chunk-
|
|
559
|
+
} from "../chunk-4IDUYB45.js";
|
|
543
560
|
import {
|
|
544
561
|
snapshotBrushLibrary
|
|
545
|
-
} from "../chunk-
|
|
546
|
-
import "../chunk-
|
|
562
|
+
} from "../chunk-AGNUD5BI.js";
|
|
563
|
+
import "../chunk-JL6YH5WD.js";
|
|
547
564
|
import {
|
|
548
565
|
createProvenance
|
|
549
566
|
} from "../chunk-M6DNZNVY.js";
|
|
@@ -580,6 +597,7 @@ import {
|
|
|
580
597
|
resolveEffectValues
|
|
581
598
|
} from "../chunk-MV7CYFMJ.js";
|
|
582
599
|
import "../chunk-V2ZCJRDM.js";
|
|
600
|
+
import "../chunk-GSTSZPSR.js";
|
|
583
601
|
import {
|
|
584
602
|
GRAIN_PATTERNS
|
|
585
603
|
} from "../chunk-TXFWZ5ZJ.js";
|
|
@@ -1240,6 +1258,245 @@ function releaseVectorBlend(group, newId) {
|
|
|
1240
1258
|
return [...keys, group.transform ? transformVectorNode(path, m) : path];
|
|
1241
1259
|
}
|
|
1242
1260
|
|
|
1261
|
+
// src/vector/backdrop.ts
|
|
1262
|
+
var IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
1263
|
+
var mul = (a, b) => [a[0] * b[0] + a[2] * b[1], a[1] * b[0] + a[3] * b[1], a[0] * b[2] + a[2] * b[3], a[1] * b[2] + a[3] * b[3], a[0] * b[4] + a[2] * b[5] + a[4], a[1] * b[4] + a[3] * b[5] + a[5]];
|
|
1264
|
+
var inv = (m) => {
|
|
1265
|
+
const det = m[0] * m[3] - m[1] * m[2];
|
|
1266
|
+
if (!det || !Number.isFinite(det)) return null;
|
|
1267
|
+
return [m[3] / det, -m[1] / det, -m[2] / det, m[0] / det, (m[2] * m[5] - m[3] * m[4]) / det, (m[1] * m[4] - m[0] * m[5]) / det];
|
|
1268
|
+
};
|
|
1269
|
+
function parseVectorTransform(value) {
|
|
1270
|
+
if (!value) return IDENTITY;
|
|
1271
|
+
let m = IDENTITY;
|
|
1272
|
+
for (const [, fn, args] of value.matchAll(/(matrix|translate|scale|rotate)\s*\(([^)]*)\)/g)) {
|
|
1273
|
+
const n2 = args.split(/[\s,]+/).filter(Boolean).map(Number);
|
|
1274
|
+
if (n2.some((v) => !Number.isFinite(v))) continue;
|
|
1275
|
+
let t = IDENTITY;
|
|
1276
|
+
if (fn === "matrix" && n2.length === 6) t = n2;
|
|
1277
|
+
else if (fn === "translate") t = [1, 0, 0, 1, n2[0] ?? 0, n2[1] ?? 0];
|
|
1278
|
+
else if (fn === "scale") t = [n2[0] ?? 1, 0, 0, n2[1] ?? n2[0] ?? 1, 0, 0];
|
|
1279
|
+
else if (fn === "rotate") {
|
|
1280
|
+
const a = (n2[0] ?? 0) * Math.PI / 180;
|
|
1281
|
+
const [cx, cy] = [n2[1] ?? 0, n2[2] ?? 0];
|
|
1282
|
+
t = mul(mul([1, 0, 0, 1, cx, cy], [Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]), [1, 0, 0, 1, -cx, -cy]);
|
|
1283
|
+
}
|
|
1284
|
+
m = mul(m, t);
|
|
1285
|
+
}
|
|
1286
|
+
return m;
|
|
1287
|
+
}
|
|
1288
|
+
function flatten(d, m, tolerance = 0.25) {
|
|
1289
|
+
const out = [];
|
|
1290
|
+
let cur = null;
|
|
1291
|
+
let x = 0, y = 0, sx = 0, sy = 0;
|
|
1292
|
+
const push = (px, py) => cur.pts.push(m[0] * px + m[2] * py + m[4], m[1] * px + m[3] * py + m[5]);
|
|
1293
|
+
const scale = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2])) || 1;
|
|
1294
|
+
const steps = (len2) => Math.max(2, Math.min(64, Math.ceil(Math.sqrt(len2 * scale / tolerance))));
|
|
1295
|
+
for (const c of parseSvgPath(d)) {
|
|
1296
|
+
if (c.type === "M") {
|
|
1297
|
+
cur = { pts: [], closed: false };
|
|
1298
|
+
out.push(cur);
|
|
1299
|
+
x = sx = c.x;
|
|
1300
|
+
y = sy = c.y;
|
|
1301
|
+
push(x, y);
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
if (!cur) continue;
|
|
1305
|
+
if (c.type === "Z") {
|
|
1306
|
+
cur.closed = true;
|
|
1307
|
+
x = sx;
|
|
1308
|
+
y = sy;
|
|
1309
|
+
cur = { pts: [], closed: false };
|
|
1310
|
+
out.push(cur);
|
|
1311
|
+
push(x, y);
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
if (c.type === "C") {
|
|
1315
|
+
const n2 = steps(Math.hypot(c.x1 - x, c.y1 - y) + Math.hypot(c.x2 - c.x1, c.y2 - c.y1) + Math.hypot(c.x - c.x2, c.y - c.y2));
|
|
1316
|
+
for (let i = 1; i <= n2; i++) {
|
|
1317
|
+
const t = i / n2, u = 1 - t;
|
|
1318
|
+
push(u * u * u * x + 3 * u * u * t * c.x1 + 3 * u * t * t * c.x2 + t * t * t * c.x, u * u * u * y + 3 * u * u * t * c.y1 + 3 * u * t * t * c.y2 + t * t * t * c.y);
|
|
1319
|
+
}
|
|
1320
|
+
} else if (c.type === "Q") {
|
|
1321
|
+
const n2 = steps(Math.hypot(c.x1 - x, c.y1 - y) + Math.hypot(c.x - c.x1, c.y - c.y1));
|
|
1322
|
+
for (let i = 1; i <= n2; i++) {
|
|
1323
|
+
const t = i / n2, u = 1 - t;
|
|
1324
|
+
push(u * u * x + 2 * u * t * c.x1 + t * t * c.x, u * u * y + 2 * u * t * c.y1 + t * t * c.y);
|
|
1325
|
+
}
|
|
1326
|
+
} else push(c.x, c.y);
|
|
1327
|
+
x = c.x;
|
|
1328
|
+
y = c.y;
|
|
1329
|
+
}
|
|
1330
|
+
return out.filter((p) => p.pts.length >= 4);
|
|
1331
|
+
}
|
|
1332
|
+
function strokeCoverage(lines, width, rect) {
|
|
1333
|
+
const r = width / 2;
|
|
1334
|
+
if (!(r > 0)) return null;
|
|
1335
|
+
const w = rect.width, h = rect.height;
|
|
1336
|
+
let out = null;
|
|
1337
|
+
for (const line2 of lines) {
|
|
1338
|
+
const p = line2.pts;
|
|
1339
|
+
const n2 = p.length / 2;
|
|
1340
|
+
const segs = line2.closed ? n2 : n2 - 1;
|
|
1341
|
+
for (let s = 0; s < segs; s++) {
|
|
1342
|
+
const ax = p[2 * s] - rect.x, ay = p[2 * s + 1] - rect.y;
|
|
1343
|
+
const k = (s + 1) % n2;
|
|
1344
|
+
const bx = p[2 * k] - rect.x, by = p[2 * k + 1] - rect.y;
|
|
1345
|
+
const x0 = Math.max(0, Math.floor(Math.min(ax, bx) - r - 1)), x1 = Math.min(w - 1, Math.ceil(Math.max(ax, bx) + r + 1));
|
|
1346
|
+
const y0 = Math.max(0, Math.floor(Math.min(ay, by) - r - 1)), y1 = Math.min(h - 1, Math.ceil(Math.max(ay, by) + r + 1));
|
|
1347
|
+
if (x0 > x1 || y0 > y1) continue;
|
|
1348
|
+
const dx = bx - ax, dy = by - ay;
|
|
1349
|
+
const l2 = dx * dx + dy * dy;
|
|
1350
|
+
for (let yy = y0; yy <= y1; yy++) {
|
|
1351
|
+
for (let xx = x0; xx <= x1; xx++) {
|
|
1352
|
+
const px = xx + 0.5 - ax, py = yy + 0.5 - ay;
|
|
1353
|
+
const t = l2 > 0 ? Math.min(1, Math.max(0, (px * dx + py * dy) / l2)) : 0;
|
|
1354
|
+
const dist3 = Math.hypot(px - t * dx, py - t * dy);
|
|
1355
|
+
const c = Math.min(1, Math.max(0, r + 0.5 - dist3));
|
|
1356
|
+
if (c <= 0) continue;
|
|
1357
|
+
out ??= new Float32Array(w * h);
|
|
1358
|
+
const i = yy * w + xx;
|
|
1359
|
+
if (c > out[i]) out[i] = c;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return out;
|
|
1365
|
+
}
|
|
1366
|
+
var pngs = /* @__PURE__ */ new Map();
|
|
1367
|
+
function decoded(href) {
|
|
1368
|
+
if (pngs.has(href)) return pngs.get(href);
|
|
1369
|
+
let img = null;
|
|
1370
|
+
try {
|
|
1371
|
+
const parsed = parseDataUrl(href);
|
|
1372
|
+
const png = parsed ? decodePng(parsed.bytes) : null;
|
|
1373
|
+
if (png && png.channels === 4) img = { width: png.width, height: png.height, data: png.data };
|
|
1374
|
+
} catch {
|
|
1375
|
+
img = null;
|
|
1376
|
+
}
|
|
1377
|
+
if (pngs.size >= 64) pngs.delete(pngs.keys().next().value);
|
|
1378
|
+
pngs.set(href, img);
|
|
1379
|
+
return img;
|
|
1380
|
+
}
|
|
1381
|
+
var Canvas = class {
|
|
1382
|
+
constructor(rect) {
|
|
1383
|
+
this.rect = rect;
|
|
1384
|
+
this.px = new Float32Array(rect.width * rect.height * 4);
|
|
1385
|
+
}
|
|
1386
|
+
rect;
|
|
1387
|
+
px;
|
|
1388
|
+
over(i, c, a) {
|
|
1389
|
+
const k = Math.min(1, Math.max(0, c[3] * a));
|
|
1390
|
+
if (k <= 0) return;
|
|
1391
|
+
const o = i * 4, px = this.px, rest = 1 - k;
|
|
1392
|
+
px[o] = c[0] * k + px[o] * rest;
|
|
1393
|
+
px[o + 1] = c[1] * k + px[o + 1] * rest;
|
|
1394
|
+
px[o + 2] = c[2] * k + px[o + 2] * rest;
|
|
1395
|
+
px[o + 3] = k + px[o + 3] * rest;
|
|
1396
|
+
}
|
|
1397
|
+
bytes() {
|
|
1398
|
+
const out = new Uint8ClampedArray(this.px.length);
|
|
1399
|
+
for (let i = 0; i < this.px.length; i += 4) {
|
|
1400
|
+
const a = this.px[i + 3];
|
|
1401
|
+
if (a <= 0) continue;
|
|
1402
|
+
out[i] = Math.round(this.px[i] / a * 255);
|
|
1403
|
+
out[i + 1] = Math.round(this.px[i + 1] / a * 255);
|
|
1404
|
+
out[i + 2] = Math.round(this.px[i + 2] / a * 255);
|
|
1405
|
+
out[i + 3] = Math.round(a * 255);
|
|
1406
|
+
}
|
|
1407
|
+
return out;
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
function paintCoverage(canvas, coverage, paint, defs, toLocal, alpha) {
|
|
1411
|
+
if (!coverage || paint === "none") return;
|
|
1412
|
+
const url = /^url\(#(.+)\)$/.exec(paint);
|
|
1413
|
+
const solid = url ? null : parseVectorColor(paint);
|
|
1414
|
+
if (!url && !solid) return;
|
|
1415
|
+
const { x: rx, y: ry, width: w } = canvas.rect;
|
|
1416
|
+
for (let i = 0; i < coverage.length; i++) {
|
|
1417
|
+
const c = coverage[i];
|
|
1418
|
+
if (c <= 0) continue;
|
|
1419
|
+
let col = solid;
|
|
1420
|
+
if (url) {
|
|
1421
|
+
const px = rx + i % w + 0.5, py = ry + Math.floor(i / w) + 0.5;
|
|
1422
|
+
col = sampleVectorPaintDefAt(defs, url[1], { x: toLocal[0] * px + toLocal[2] * py + toLocal[4], y: toLocal[1] * px + toLocal[3] * py + toLocal[5] });
|
|
1423
|
+
}
|
|
1424
|
+
if (col) canvas.over(i, col, c * alpha);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
function drawImage(canvas, im, toLocal, alpha) {
|
|
1428
|
+
const img = decoded(im.href);
|
|
1429
|
+
if (!img || !(im.width > 0 && im.height > 0)) return;
|
|
1430
|
+
const { x: rx, y: ry, width: w, height: h } = canvas.rect;
|
|
1431
|
+
const c = [0, 0, 0, 0];
|
|
1432
|
+
for (let yy = 0; yy < h; yy++) {
|
|
1433
|
+
for (let xx = 0; xx < w; xx++) {
|
|
1434
|
+
const px = rx + xx + 0.5, py = ry + yy + 0.5;
|
|
1435
|
+
const lx = toLocal[0] * px + toLocal[2] * py + toLocal[4], ly = toLocal[1] * px + toLocal[3] * py + toLocal[5];
|
|
1436
|
+
const u = (lx - im.x) / im.width * img.width - 0.5, v = (ly - im.y) / im.height * img.height - 0.5;
|
|
1437
|
+
if (u < -1 || v < -1 || u > img.width || v > img.height) continue;
|
|
1438
|
+
const u0 = Math.floor(u), v0 = Math.floor(v), fu = u - u0, fv = v - v0;
|
|
1439
|
+
c[0] = c[1] = c[2] = c[3] = 0;
|
|
1440
|
+
for (let k = 0; k < 4; k++) {
|
|
1441
|
+
const sx = u0 + (k & 1), sy = v0 + (k >> 1);
|
|
1442
|
+
if (sx < 0 || sy < 0 || sx >= img.width || sy >= img.height) continue;
|
|
1443
|
+
const wgt = (k & 1 ? fu : 1 - fu) * (k >> 1 ? fv : 1 - fv);
|
|
1444
|
+
const o = (sy * img.width + sx) * 4, a = img.data[o + 3] / 255 * wgt;
|
|
1445
|
+
c[0] += img.data[o] / 255 * a;
|
|
1446
|
+
c[1] += img.data[o + 1] / 255 * a;
|
|
1447
|
+
c[2] += img.data[o + 2] / 255 * a;
|
|
1448
|
+
c[3] += a;
|
|
1449
|
+
}
|
|
1450
|
+
if (c[3] <= 0) continue;
|
|
1451
|
+
canvas.over(yy * w + xx, [c[0] / c[3], c[1] / c[3], c[2] / c[3], c[3]], alpha);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
function drawPath(canvas, p, defs, m, alpha) {
|
|
1456
|
+
const toLocal = inv(m);
|
|
1457
|
+
if (!toLocal) return;
|
|
1458
|
+
const scale = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
|
|
1459
|
+
const rule = p.fillRule === "evenodd" ? "evenodd" : "nonzero";
|
|
1460
|
+
const layers = p.layers ?? [
|
|
1461
|
+
{ kind: "fill", index: 0, fill: p.fill, stroke: "none", strokeWidth: 0, opacity: 1 },
|
|
1462
|
+
{ kind: "stroke", index: 0, fill: "none", stroke: p.stroke, strokeWidth: p.strokeWidth, opacity: 1, strokeParts: p.strokeParts }
|
|
1463
|
+
];
|
|
1464
|
+
let lines = null;
|
|
1465
|
+
const shape = () => lines ??= flatten(p.d, m);
|
|
1466
|
+
const fill = (d, rl) => {
|
|
1467
|
+
const flat = (d ? flatten(d, m) : shape()).map((l) => Float64Array.from(l.pts));
|
|
1468
|
+
return rasterizeCoverage(flat, canvas.rect, rl);
|
|
1469
|
+
};
|
|
1470
|
+
for (const l of layers) {
|
|
1471
|
+
const a = alpha * l.opacity;
|
|
1472
|
+
if (a <= 0) continue;
|
|
1473
|
+
if (l.kind === "brushstroke") {
|
|
1474
|
+
for (const im of l.images ?? []) drawImage(canvas, im, toLocal, a);
|
|
1475
|
+
} else if (l.kind === "fill") {
|
|
1476
|
+
if (l.fill !== "none") paintCoverage(canvas, fill(null, rule), l.fill, defs, toLocal, a);
|
|
1477
|
+
} else if (l.stroke !== "none" && l.strokeWidth > 0) {
|
|
1478
|
+
const parts = l.strokeParts;
|
|
1479
|
+
const cov = parts?.outline ? fill(parts.outline, "nonzero") : strokeCoverage(parts ? flatten(parts.d, m) : shape(), (parts ? parts.align === "center" ? parts.width : parts.width / 2 : l.strokeWidth) * scale, canvas.rect);
|
|
1480
|
+
paintCoverage(canvas, cov, l.stroke, defs, toLocal, a);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
function drawNodes(canvas, nodes, defs, m, alpha) {
|
|
1485
|
+
for (const n2 of nodes) {
|
|
1486
|
+
if (n2.kind === "text") continue;
|
|
1487
|
+
const mm = mul(m, parseVectorTransform(n2.transform));
|
|
1488
|
+
const a = alpha * (n2.opacity ?? 1);
|
|
1489
|
+
if (a <= 0) continue;
|
|
1490
|
+
if (n2.kind === "group") drawNodes(canvas, n2.children, defs, mm, a);
|
|
1491
|
+
else drawPath(canvas, n2, defs, mm, a);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
function rasterizeVectorBackdrop(nodes, defs, toPixels, rect) {
|
|
1495
|
+
const canvas = new Canvas(rect);
|
|
1496
|
+
if (rect.width > 0 && rect.height > 0) drawNodes(canvas, nodes, defs, toPixels, 1);
|
|
1497
|
+
return canvas.bytes();
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1243
1500
|
// src/vector/perspective.ts
|
|
1244
1501
|
function vectorPerspectivePlanes(p) {
|
|
1245
1502
|
return p.vanishingPoints.length <= 1 ? ["floor", "side", "front"] : ["floor", "left", "right"];
|
|
@@ -1295,11 +1552,11 @@ function vectorPerspectiveFlatToPlane(p, plane) {
|
|
|
1295
1552
|
}
|
|
1296
1553
|
function vectorPerspectivePlaneToFlat(p, plane) {
|
|
1297
1554
|
const h = vectorPerspectivePlaneMap(p, plane);
|
|
1298
|
-
const
|
|
1299
|
-
if (!
|
|
1555
|
+
const inv2 = h ? invertHomography(h) : null;
|
|
1556
|
+
if (!inv2) return null;
|
|
1300
1557
|
const o = p.origin;
|
|
1301
1558
|
return (q) => {
|
|
1302
|
-
const f = applyHomography(
|
|
1559
|
+
const f = applyHomography(inv2, q);
|
|
1303
1560
|
if (!f) return null;
|
|
1304
1561
|
const w = h[6] * f.x + h[7] * f.y + h[8];
|
|
1305
1562
|
if (!(w > 1e-6)) return null;
|
|
@@ -1890,8 +2147,8 @@ var Model = class {
|
|
|
1890
2147
|
const changed = /* @__PURE__ */ new Map();
|
|
1891
2148
|
for (const [nodeId, slots] of byNode) {
|
|
1892
2149
|
const loc = this.located.get(nodeId);
|
|
1893
|
-
const
|
|
1894
|
-
const toLocal = (p) => applyMatrix(
|
|
2150
|
+
const inv2 = invertMatrix(loc.world);
|
|
2151
|
+
const toLocal = (p) => applyMatrix(inv2, p);
|
|
1895
2152
|
let subpaths = loc.node.subpaths;
|
|
1896
2153
|
const circle = slots.find((s) => s.kind === "circle");
|
|
1897
2154
|
if (circle) {
|
|
@@ -2678,7 +2935,7 @@ function renderLayers(node, defs, idPrefix) {
|
|
|
2678
2935
|
const out = [];
|
|
2679
2936
|
for (const l of vectorPaintLayerNodes(node)) {
|
|
2680
2937
|
if (l.kind === "stroke" && isVectorBrushstrokeLayer(l.layer) && l.layer.visible !== false && (l.layer.opacity ?? 1) > 0) {
|
|
2681
|
-
const images = vectorBrushstrokeImages(node, l.layer);
|
|
2938
|
+
const images = vectorBrushstrokeImages(node, l.layer, scene ?? {});
|
|
2682
2939
|
const blend2 = vectorBlendModeCss(l.layer.blendMode);
|
|
2683
2940
|
if (images.length) out.push({ kind: "brushstroke", index: l.index, fill: "none", stroke: "none", strokeWidth: 0, opacity: l.layer.opacity ?? 1, ...blend2 ? { blendMode: blend2 } : {}, images });
|
|
2684
2941
|
continue;
|
|
@@ -2852,6 +3109,68 @@ function resolvedLensNodes(doc) {
|
|
|
2852
3109
|
return nodes;
|
|
2853
3110
|
}
|
|
2854
3111
|
function getVectorSceneRenderData(state, options = {}) {
|
|
3112
|
+
const outer = scene;
|
|
3113
|
+
scene = { document: state.document, settle: options.brushstrokes === "settled" ? "now" : "async" };
|
|
3114
|
+
try {
|
|
3115
|
+
return sceneRenderData(state, options);
|
|
3116
|
+
} finally {
|
|
3117
|
+
scene = outer;
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
var scene = null;
|
|
3121
|
+
function nodeChain(nodes, id) {
|
|
3122
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
3123
|
+
const n2 = nodes[i];
|
|
3124
|
+
if (n2.id === id) return [{ list: nodes, at: i }];
|
|
3125
|
+
if (n2.type === "group") {
|
|
3126
|
+
const inner = nodeChain(n2.children, id);
|
|
3127
|
+
if (inner) return [{ list: nodes, at: i }, ...inner];
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return null;
|
|
3131
|
+
}
|
|
3132
|
+
function nodesBelow(chain, last) {
|
|
3133
|
+
const [level, ...rest] = chain;
|
|
3134
|
+
const before = level.list.slice(0, level.at);
|
|
3135
|
+
if (!rest.length) return [...before, last];
|
|
3136
|
+
const group = level.list[level.at];
|
|
3137
|
+
return [...before, { ...group, children: nodesBelow(rest, last) }];
|
|
3138
|
+
}
|
|
3139
|
+
setVectorBackdropSource((document, node, index, settle) => {
|
|
3140
|
+
const nodes = document ? resolvedLensNodes(document) : [];
|
|
3141
|
+
const chain = document ? nodeChain(nodes, node.id) : null;
|
|
3142
|
+
const deps = [];
|
|
3143
|
+
if (chain) {
|
|
3144
|
+
for (let k = 0; k < chain.length; k++) {
|
|
3145
|
+
const { list, at } = chain[k];
|
|
3146
|
+
deps.push(...list.slice(0, at));
|
|
3147
|
+
const n2 = list[at];
|
|
3148
|
+
if (k < chain.length - 1) deps.push(JSON.stringify([n2.transform ?? null, n2.opacity ?? 1, n2.visible ?? true]));
|
|
3149
|
+
}
|
|
3150
|
+
if (document?.symbols) deps.push(document.symbols);
|
|
3151
|
+
}
|
|
3152
|
+
deps.push(...vectorFills(node), ...vectorStrokes(node).slice(0, index));
|
|
3153
|
+
return {
|
|
3154
|
+
deps,
|
|
3155
|
+
backdrop: (rect, scale) => {
|
|
3156
|
+
const lower = withVectorStrokes(node, vectorStrokes(node).slice(0, index));
|
|
3157
|
+
const below = chain ? nodesBelow(chain, lower) : [lower];
|
|
3158
|
+
const world = (chain ? vectorWorldMatrix(nodes, node.id) : null) ?? nodeMatrix(node);
|
|
3159
|
+
const defs = [];
|
|
3160
|
+
const outer = scene;
|
|
3161
|
+
scene = { document: document ?? outer?.document ?? { width: 0, height: 0, nodes: [] }, settle };
|
|
3162
|
+
let items;
|
|
3163
|
+
try {
|
|
3164
|
+
items = below.map((n2) => renderNode(n2, defs, [], "ve-backdrop-")).filter((n2) => !!n2);
|
|
3165
|
+
} finally {
|
|
3166
|
+
scene = outer;
|
|
3167
|
+
}
|
|
3168
|
+
const toPixels = multiplyMatrices(invertMatrix(world), [scale, 0, 0, scale, -rect.x, -rect.y]);
|
|
3169
|
+
return rasterizeVectorBackdrop(items, defs, toPixels, { x: 0, y: 0, width: rect.width, height: rect.height });
|
|
3170
|
+
}
|
|
3171
|
+
};
|
|
3172
|
+
});
|
|
3173
|
+
function sceneRenderData(state, options) {
|
|
2855
3174
|
const defs = [];
|
|
2856
3175
|
const filters = [];
|
|
2857
3176
|
const prefix = options.idPrefix ?? "ve-";
|
|
@@ -2974,13 +3293,13 @@ function symmetryOverlay(state) {
|
|
|
2974
3293
|
if (!isVectorSymmetryGroup(n2)) continue;
|
|
2975
3294
|
const world = vectorWorldMatrix(state.document.nodes, id);
|
|
2976
3295
|
if (!world) continue;
|
|
2977
|
-
const
|
|
3296
|
+
const inv2 = invertMatrix(world);
|
|
2978
3297
|
const corners2 = [
|
|
2979
3298
|
{ x: 0, y: 0 },
|
|
2980
3299
|
{ x: state.document.width, y: 0 },
|
|
2981
3300
|
{ x: 0, y: state.document.height },
|
|
2982
3301
|
{ x: state.document.width, y: state.document.height }
|
|
2983
|
-
].map((p) => applyMatrix(
|
|
3302
|
+
].map((p) => applyMatrix(inv2, p));
|
|
2984
3303
|
const { transform: _t, ...own } = n2;
|
|
2985
3304
|
const b = unionBoxes([vectorNodeBounds(own), boundsFromCorners(corners2)]);
|
|
2986
3305
|
if (!b) continue;
|
|
@@ -3793,7 +4112,7 @@ var BLEND = {
|
|
|
3793
4112
|
function matrixOf(t) {
|
|
3794
4113
|
if (!t) return [1, 0, 0, 1, 0, 0];
|
|
3795
4114
|
let m = [1, 0, 0, 1, 0, 0];
|
|
3796
|
-
const
|
|
4115
|
+
const mul2 = (a, b) => [a[0] * b[0] + a[2] * b[1], a[1] * b[0] + a[3] * b[1], a[0] * b[2] + a[2] * b[3], a[1] * b[2] + a[3] * b[3], a[0] * b[4] + a[2] * b[5] + a[4], a[1] * b[4] + a[3] * b[5] + a[5]];
|
|
3797
4116
|
for (const [, name, args] of t.matchAll(/(\w+)\s*\(([^)]*)\)/g)) {
|
|
3798
4117
|
const v = args.split(/[\s,]+/).filter(Boolean).map(Number);
|
|
3799
4118
|
let k = [1, 0, 0, 1, 0, 0];
|
|
@@ -3803,9 +4122,9 @@ function matrixOf(t) {
|
|
|
3803
4122
|
else if (name === "rotate") {
|
|
3804
4123
|
const r = (v[0] ?? 0) * Math.PI / 180;
|
|
3805
4124
|
const [cx, cy] = [v[1] ?? 0, v[2] ?? 0];
|
|
3806
|
-
k =
|
|
4125
|
+
k = mul2(mul2([1, 0, 0, 1, cx, cy], [Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0]), [1, 0, 0, 1, -cx, -cy]);
|
|
3807
4126
|
}
|
|
3808
|
-
m =
|
|
4127
|
+
m = mul2(m, k);
|
|
3809
4128
|
}
|
|
3810
4129
|
return m;
|
|
3811
4130
|
}
|
|
@@ -4150,8 +4469,8 @@ function vectorDocumentToPdf(doc, options = {}) {
|
|
|
4150
4469
|
const pageIds = [];
|
|
4151
4470
|
docs.forEach((d, k) => {
|
|
4152
4471
|
w.pageIndex = k;
|
|
4153
|
-
const
|
|
4154
|
-
const defs = new Map(
|
|
4472
|
+
const scene2 = getVectorSceneRenderData({ document: d, viewport: { x: 0, y: 0, zoom: 1 } }, { idPrefix: `p${k}-`, brushstrokes: "settled" });
|
|
4473
|
+
const defs = new Map(scene2.defs.map((x) => [x.id, x]));
|
|
4155
4474
|
const page = new Page(w, defs, notes);
|
|
4156
4475
|
const W = d.width * scale;
|
|
4157
4476
|
const H = d.height * scale;
|
|
@@ -4160,7 +4479,7 @@ function vectorDocumentToPdf(doc, options = {}) {
|
|
|
4160
4479
|
const c = parseVectorColor(d.background);
|
|
4161
4480
|
if (c) page.content.push(`${n(c[0])} ${n(c[1])} ${n(c[2])} rg`, `0 0 ${n(d.width)} ${n(d.height)} re`, "f");
|
|
4162
4481
|
}
|
|
4163
|
-
for (const node of
|
|
4482
|
+
for (const node of scene2.nodes) page.node(node, 1);
|
|
4164
4483
|
const content = w.add(w.stream("", new TextEncoder().encode(page.content.join("\n")), compress));
|
|
4165
4484
|
const gsEntries = [...page.states.values()].map((name) => `/${name} ${w.add(w.gsDicts.get(`${k}:${name}`))} 0 R`);
|
|
4166
4485
|
const shEntries = [...page.shadings.values()].map((name) => `/${name} ${w.add(w.shadingDicts.get(`${k}:${name}`))} 0 R`);
|
|
@@ -4988,8 +5307,8 @@ function mapEffects(node, f) {
|
|
|
4988
5307
|
}
|
|
4989
5308
|
function distortVectorNode(node, parentToDoc, f, tolerance = VECTOR_DISTORT_TOLERANCE) {
|
|
4990
5309
|
const toDoc = multiplyMatrices(nodeMatrix(node), parentToDoc);
|
|
4991
|
-
const
|
|
4992
|
-
const local = (p) => applyMatrix(
|
|
5310
|
+
const inv2 = invertMatrix(toDoc);
|
|
5311
|
+
const local = (p) => applyMatrix(inv2, f(applyMatrix(toDoc, p)));
|
|
4993
5312
|
const effects = mapEffects(node, local);
|
|
4994
5313
|
if (node.type === "group") {
|
|
4995
5314
|
const group = node;
|
|
@@ -6838,8 +7157,10 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
6838
7157
|
revision = 0;
|
|
6839
7158
|
state;
|
|
6840
7159
|
colorScheme;
|
|
7160
|
+
colorLocked;
|
|
6841
7161
|
constructor(options = {}) {
|
|
6842
|
-
this.colorScheme = options.colorScheme
|
|
7162
|
+
this.colorScheme = options.colorScheme === "dark" ? "dark" : "light";
|
|
7163
|
+
this.colorLocked = !!options.colorLocked;
|
|
6843
7164
|
this.brushLibrary = options.brushLibrary ?? null;
|
|
6844
7165
|
this.brushPreview = options.brushPreview ?? null;
|
|
6845
7166
|
this.brushes = options.brushLibrary ? snapshotBrushLibrary(options.brushLibrary, () => this.notify()) : null;
|
|
@@ -6866,7 +7187,9 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
6866
7187
|
this.cleanups.push(
|
|
6867
7188
|
this.history.subscribe((h) => {
|
|
6868
7189
|
if (!this.applying && this.historyDiffers(h)) this.notify();
|
|
6869
|
-
})
|
|
7190
|
+
}),
|
|
7191
|
+
// A wet brushstroke settled: views redraw with its final image.
|
|
7192
|
+
onVectorBrushstrokesSettled(() => this.notify())
|
|
6870
7193
|
);
|
|
6871
7194
|
if (this.historyDiffers(this.history.getState())) this.state = this.computeState();
|
|
6872
7195
|
}
|
|
@@ -6949,6 +7272,8 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
6949
7272
|
blendDefaults: this.blendDefaults,
|
|
6950
7273
|
gradientEdit: this.gradientEdit,
|
|
6951
7274
|
freeDistort: this.freeDistort,
|
|
7275
|
+
colorScheme: this.colorScheme,
|
|
7276
|
+
colorLocked: this.colorLocked,
|
|
6952
7277
|
textEdit: this.textEdit ? { nodeId: this.textEdit.nodeId, anchor: this.textEdit.anchor, focus: this.textEdit.focus } : null,
|
|
6953
7278
|
textDefaults: this.textDefaults,
|
|
6954
7279
|
symbolEdit: this.symbolEditState(),
|
|
@@ -7507,8 +7832,8 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
7507
7832
|
const node = this.getNode(id);
|
|
7508
7833
|
if (!node || node.type !== "group" || !node.generation) return { ok: false, message: `"${id}" is not generated art.` };
|
|
7509
7834
|
if (node.locked) return { ok: false, message: `"${id}" is locked; unlock it first.` };
|
|
7510
|
-
const
|
|
7511
|
-
const placed = children.map((c) => this.freshIds(isIdentityMatrix(
|
|
7835
|
+
const inv2 = invertMatrix(this.worldOf(id));
|
|
7836
|
+
const placed = children.map((c) => this.freshIds(isIdentityMatrix(inv2) ? c : transformVectorNode(c, inv2)));
|
|
7512
7837
|
this.change("Regenerate Art", () => {
|
|
7513
7838
|
this.replaceNode(id, { ...node, generation, children: placed });
|
|
7514
7839
|
this.anchors = [];
|
|
@@ -8208,7 +8533,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
8208
8533
|
* Adds a brushstroke to the selected (or given) paths: a stroke layer that paints the path with
|
|
8209
8534
|
* brush `brushId` (a preset, or a saved brush of the brush library: `listBrushstrokeBrushes`), `size` document
|
|
8210
8535
|
* units across (default the brush's own), an optional `pressure` profile along the length, in
|
|
8211
|
-
* `color` (default the brush's own default colour, unless `keepColour
|
|
8536
|
+
* `color` (default the brush's own default colour, unless `keepColour` or the colour is locked; else the top stroke's
|
|
8212
8537
|
* paint, else black) at `opacity`. At the top of the stroke
|
|
8213
8538
|
* stack or at `index`. A `seed` gives it the brush's character (jitter and the grain of `paper`,
|
|
8214
8539
|
* `null` for none); without one it renders plain. One undo step.
|
|
@@ -8221,9 +8546,11 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
8221
8546
|
if (brush.size !== void 0 && !(brush.size > 0 && brush.size <= 500)) return { ok: false, message: "size must be above 0 and at most 500 (document units)." };
|
|
8222
8547
|
const characterError = vectorBrushstrokeCharacterError(brush);
|
|
8223
8548
|
if (characterError) return { ok: false, message: characterError };
|
|
8549
|
+
const paintError = brush.paint !== void 0 ? vectorBrushstrokePaintError(brush.paint) : null;
|
|
8550
|
+
if (paintError) return { ok: false, message: paintError };
|
|
8224
8551
|
if (options.color !== void 0 && !parseVectorColor(options.color)) return { ok: false, message: `"${options.color}" is not a colour.` };
|
|
8225
8552
|
if (options.opacity !== void 0 && !(options.opacity >= 0 && options.opacity <= 1)) return { ok: false, message: "opacity must be from 0 to 1." };
|
|
8226
|
-
const own = options.keepColour ? null : vectorBrushstrokeDefaultColour(clean.brushId, user, this.colorScheme);
|
|
8553
|
+
const own = options.keepColour || this.colorLocked ? null : vectorBrushstrokeDefaultColour(clean.brushId, user, this.colorScheme);
|
|
8227
8554
|
return this.editStacks("stroke", options.ids, "Add Brushstroke", (stack, path) => {
|
|
8228
8555
|
const top = [...stack].reverse().find((l) => (l.kind ?? "paint") === "paint");
|
|
8229
8556
|
const paint = options.color ?? own ?? (typeof top?.paint === "string" && top.paint !== "none" ? top.paint : typeof path.stroke === "string" && path.stroke !== "none" ? path.stroke : "#000000");
|
|
@@ -8237,7 +8564,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
8237
8564
|
* given) paths: another brush, size, pressure profile (null removes it), colour or opacity, the
|
|
8238
8565
|
* character `seed` (null removes it: plain dabs) or the `paper` (replaced whole: `null` for no
|
|
8239
8566
|
* grain, `{}` for the default paper). Another brush without a `color` takes that brush's default
|
|
8240
|
-
* colour, if it has one, unless `keepColour
|
|
8567
|
+
* colour, if it has one, unless `keepColour` or the colour is locked. Fails, changing nothing, when that layer is not a
|
|
8241
8568
|
* brushstroke. With `merge`, consecutive edits fold into one undo step.
|
|
8242
8569
|
*/
|
|
8243
8570
|
setBrushstroke(index, patch, options = {}) {
|
|
@@ -8249,6 +8576,8 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
8249
8576
|
if (patch.opacity !== void 0 && !(patch.opacity >= 0 && patch.opacity <= 1)) return { ok: false, message: "opacity must be from 0 to 1." };
|
|
8250
8577
|
const characterError = vectorBrushstrokeCharacterError({ ...patch.seed !== null && patch.seed !== void 0 ? { seed: patch.seed } : {}, ...patch.paper !== void 0 ? { paper: patch.paper } : {} });
|
|
8251
8578
|
if (characterError) return { ok: false, message: characterError };
|
|
8579
|
+
const paintError = patch.paint !== void 0 && patch.paint !== null ? vectorBrushstrokePaintError(patch.paint) : null;
|
|
8580
|
+
if (paintError) return { ok: false, message: paintError };
|
|
8252
8581
|
const merge = options.merge === true ? `brushstroke:${index}` : options.merge || void 0;
|
|
8253
8582
|
return this.editStacks(
|
|
8254
8583
|
"stroke",
|
|
@@ -8265,7 +8594,9 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
8265
8594
|
if (patch.seed === null) delete brush.seed;
|
|
8266
8595
|
else if (patch.seed !== void 0) brush.seed = patch.seed;
|
|
8267
8596
|
if (patch.paper !== void 0) brush.paper = patch.paper;
|
|
8268
|
-
|
|
8597
|
+
if (patch.paint === null) delete brush.paint;
|
|
8598
|
+
else if (patch.paint !== void 0) brush.paint = patch.paint;
|
|
8599
|
+
const own = patch.brushId && patch.brushId !== layer.brush.brushId && !patch.keepColour && !this.colorLocked ? vectorBrushstrokeDefaultColour(patch.brushId, user, this.colorScheme) : null;
|
|
8269
8600
|
const colour = patch.color ?? own;
|
|
8270
8601
|
const next = { ...layer, brush: sanitizeVectorBrushstroke(brush, user) ?? layer.brush, ...colour ? { paint: colour } : {}, ...patch.opacity !== void 0 ? { opacity: patch.opacity } : {} };
|
|
8271
8602
|
return stack.map((l, i) => i === index ? next : l);
|
|
@@ -8275,7 +8606,20 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
8275
8606
|
}
|
|
8276
8607
|
/** Sets the colour scheme that picks the side of a brush's light / dark default colour. Existing strokes keep their colour. */
|
|
8277
8608
|
setColorScheme(scheme) {
|
|
8278
|
-
|
|
8609
|
+
const next = scheme === "dark" ? "dark" : "light";
|
|
8610
|
+
if (this.colorScheme === next) return;
|
|
8611
|
+
this.colorScheme = next;
|
|
8612
|
+
this.notify();
|
|
8613
|
+
}
|
|
8614
|
+
/**
|
|
8615
|
+
* Locks the brushstroke colour ("Keep my colour"): `addBrushstroke` and `setBrushstroke` with
|
|
8616
|
+
* another brush keep the colour instead of taking the brush's default; an explicit `color` still
|
|
8617
|
+
* applies. Existing strokes are unchanged. Editor state, not an undo step.
|
|
8618
|
+
*/
|
|
8619
|
+
setColorLocked(locked) {
|
|
8620
|
+
if (this.colorLocked === !!locked) return;
|
|
8621
|
+
this.colorLocked = !!locked;
|
|
8622
|
+
this.notify();
|
|
8279
8623
|
}
|
|
8280
8624
|
/** Removes layer `index` of the fill or stroke stack of the selected (or given) paths. */
|
|
8281
8625
|
removePaintLayer(kind, index, options = {}) {
|
|
@@ -9098,7 +9442,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
9098
9442
|
if (!groups.length) return { ok: false, message: "Select a group to ungroup." };
|
|
9099
9443
|
const set = new Set(groups.map((g) => g.id));
|
|
9100
9444
|
this.change("Ungroup", () => {
|
|
9101
|
-
const
|
|
9445
|
+
const flatten2 = (list) => {
|
|
9102
9446
|
const out = [];
|
|
9103
9447
|
let changed = false;
|
|
9104
9448
|
for (const n2 of list) {
|
|
@@ -9110,14 +9454,14 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
9110
9454
|
...n2.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) => n2.transform ? transformVectorNode(c, m) : c).map((c) => opacity !== void 0 && opacity < 1 ? { ...c, opacity: (c.opacity ?? 1) * opacity } : c)
|
|
9111
9455
|
);
|
|
9112
9456
|
} else if (n2.type === "group") {
|
|
9113
|
-
const children =
|
|
9457
|
+
const children = flatten2(n2.children);
|
|
9114
9458
|
if (children !== n2.children) changed = true;
|
|
9115
9459
|
out.push(children !== n2.children ? { ...n2, children } : n2);
|
|
9116
9460
|
} else out.push(n2);
|
|
9117
9461
|
}
|
|
9118
9462
|
return changed ? out : list;
|
|
9119
9463
|
};
|
|
9120
|
-
this.setDoc(
|
|
9464
|
+
this.setDoc(flatten2(this.doc.nodes));
|
|
9121
9465
|
this.selection = groups.flatMap((g) => g.children.map((c) => c.id));
|
|
9122
9466
|
});
|
|
9123
9467
|
return { ok: true };
|
|
@@ -9702,8 +10046,8 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
9702
10046
|
const parentM = this.parentMatrixOf(id);
|
|
9703
10047
|
const r = project(node, parentM);
|
|
9704
10048
|
if (typeof r === "string") return { ok: false, message: r };
|
|
9705
|
-
const
|
|
9706
|
-
next.set(id, isIdentityMatrix(
|
|
10049
|
+
const inv2 = invertMatrix(parentM);
|
|
10050
|
+
next.set(id, isIdentityMatrix(inv2) ? r : transformVectorNode(r, inv2));
|
|
9707
10051
|
}
|
|
9708
10052
|
this.change(`Attach to ${plane[0].toUpperCase()}${plane.slice(1)} Plane`, () => {
|
|
9709
10053
|
this.setDoc(mapTree(this.doc.nodes, (n2) => next.get(n2.id) ?? n2));
|
|
@@ -11663,11 +12007,11 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
11663
12007
|
const nodes = mapTree(base.nodes, (n2) => {
|
|
11664
12008
|
if (!targets.has(n2.id) || n2.type !== "path" || n2.locked) return n2;
|
|
11665
12009
|
const world = vectorWorldMatrix(base.nodes, n2.id) ?? IDENTITY_MATRIX;
|
|
11666
|
-
const
|
|
12010
|
+
const inv2 = invertMatrix(world);
|
|
11667
12011
|
const k = 1 / Math.sqrt(Math.abs(world[0] * world[3] - world[1] * world[2]) || 1);
|
|
11668
12012
|
const local = dabs.map((d) => {
|
|
11669
|
-
const c = applyMatrix(
|
|
11670
|
-
const moved = d.dx !== void 0 || d.dy !== void 0 ? applyMatrix(
|
|
12013
|
+
const c = applyMatrix(inv2, d);
|
|
12014
|
+
const moved = d.dx !== void 0 || d.dy !== void 0 ? applyMatrix(inv2, { x: d.x + (d.dx ?? 0), y: d.y + (d.dy ?? 0) }) : null;
|
|
11671
12015
|
return { ...d, x: c.x, y: c.y, radius: d.radius * k, ...moved ? { dx: moved.x - c.x, dy: moved.y - c.y } : {} };
|
|
11672
12016
|
});
|
|
11673
12017
|
const path = subpathsToBezier(n2.subpaths);
|
|
@@ -11712,8 +12056,8 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
11712
12056
|
}
|
|
11713
12057
|
/** A stroke's points in node `id`'s own space. */
|
|
11714
12058
|
localPoints(id, points) {
|
|
11715
|
-
const
|
|
11716
|
-
return points.map((p) => toPoint2(applyMatrix(
|
|
12059
|
+
const inv2 = invertMatrix(this.worldOf(id));
|
|
12060
|
+
return points.map((p) => toPoint2(applyMatrix(inv2, p)));
|
|
11717
12061
|
}
|
|
11718
12062
|
/**
|
|
11719
12063
|
* Knife: cuts closed paths along the polyline `points` (document space) wherever it crosses
|
|
@@ -12682,8 +13026,8 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
12682
13026
|
if (!drag.moved && Math.hypot(p.x - drag.start.x, p.y - drag.start.y) * this.viewport.zoom < 3) return;
|
|
12683
13027
|
drag.moved = true;
|
|
12684
13028
|
const c = drag.widget.corner;
|
|
12685
|
-
const
|
|
12686
|
-
const d = applyMatrixLinear(
|
|
13029
|
+
const inv2 = invertMatrix(this.worldOf(drag.widget.ref.nodeId, drag.base));
|
|
13030
|
+
const d = applyMatrixLinear(inv2, { x: p.x - drag.start.x, y: p.y - drag.start.y });
|
|
12687
13031
|
const radius = Math.max(0, Math.min(c.maxRadius, drag.widget.drawnRadius + cornerRadiusChange(c, d)));
|
|
12688
13032
|
this.doc = this.updateAnchors(drag.base, drag.refs, (a) => setAnchorCorner(a, radius));
|
|
12689
13033
|
this.interaction = { type: "corner", ref: drag.widget.ref, radius };
|
|
@@ -15932,6 +16276,65 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15932
16276
|
}
|
|
15933
16277
|
}
|
|
15934
16278
|
];
|
|
16279
|
+
const BRUSH_PAINT = {
|
|
16280
|
+
anyOf: [
|
|
16281
|
+
{
|
|
16282
|
+
type: "object",
|
|
16283
|
+
additionalProperties: false,
|
|
16284
|
+
required: ["type", "color"],
|
|
16285
|
+
description: "One colour instead of the layer colour (the layer colour still gives the alpha).",
|
|
16286
|
+
properties: {
|
|
16287
|
+
type: { type: "string", enum: ["color"], description: 'Always "color".' },
|
|
16288
|
+
color: { type: "string", pattern: "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$", description: "The colour as #rrggbb." }
|
|
16289
|
+
}
|
|
16290
|
+
},
|
|
16291
|
+
{
|
|
16292
|
+
type: "object",
|
|
16293
|
+
additionalProperties: false,
|
|
16294
|
+
required: ["type", "stops"],
|
|
16295
|
+
description: "Gradient paint: the brush samples it dab by dab along the path.",
|
|
16296
|
+
properties: {
|
|
16297
|
+
type: { type: "string", enum: ["gradient"], description: 'Always "gradient".' },
|
|
16298
|
+
stops: {
|
|
16299
|
+
type: "array",
|
|
16300
|
+
minItems: 2,
|
|
16301
|
+
maxItems: 16,
|
|
16302
|
+
description: "2..16 stops sorted by offset.",
|
|
16303
|
+
items: {
|
|
16304
|
+
type: "object",
|
|
16305
|
+
additionalProperties: false,
|
|
16306
|
+
required: ["offset", "color"],
|
|
16307
|
+
properties: {
|
|
16308
|
+
offset: { type: "number", minimum: 0, maximum: 1, description: "Position 0..1 along the gradient." },
|
|
16309
|
+
color: { type: "string", description: 'The stop colour as #rrggbb, or "paint" (the layer colour) or "secondary" (white).', anyOf: [{ pattern: "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" }, { enum: ["paint", "secondary"] }] },
|
|
16310
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Multiplies the brush flow where this colour is laid, 0..1 (default 1)." },
|
|
16311
|
+
midpoint: { type: "number", minimum: 0.05, maximum: 0.95, description: "Where between this stop and the next the colours are half mixed, 0.05..0.95 (default 0.5)." }
|
|
16312
|
+
}
|
|
16313
|
+
}
|
|
16314
|
+
},
|
|
16315
|
+
space: { type: "string", enum: ["pigment", "oklab", "srgb"], description: "How neighbouring stops mix: pigment (default; like paint, blue to yellow passes green), oklab (perceptual) or srgb (plain numbers)." },
|
|
16316
|
+
map: {
|
|
16317
|
+
type: "object",
|
|
16318
|
+
additionalProperties: false,
|
|
16319
|
+
required: ["by"],
|
|
16320
|
+
description: "What moves the stroke along the gradient (default distance, 300 document units, mirror).",
|
|
16321
|
+
properties: {
|
|
16322
|
+
by: {
|
|
16323
|
+
type: "string",
|
|
16324
|
+
enum: ["distance", "pressure", "tilt", "direction", "across-tip", "random"],
|
|
16325
|
+
description: "distance: document units (or dabs) travelled along the path; pressure: the pressure profile 0..1; tilt: no tilt on paths, so the start of the gradient; direction: direction of travel, once round per 360\xB0; across-tip: left to right across the tip (a brush loaded with several colours); random: a seeded random point per dab (the layer seed). Paths have no timing, so velocity and time are not available."
|
|
16326
|
+
},
|
|
16327
|
+
length: { type: "number", minimum: 1, maximum: 1e5, description: 'distance: document units (or dabs, with unit "dabs") per run through the gradient (default 300).' },
|
|
16328
|
+
repeat: { type: "string", enum: ["clamp", "repeat", "mirror"], description: "distance: past the end, clamp (hold the last colour), repeat (start again) or mirror (run back; default)." },
|
|
16329
|
+
unit: { type: "string", enum: ["px", "dabs"], description: 'distance: document units ("px", default) or dabs.' },
|
|
16330
|
+
every: { type: "integer", minimum: 1, maximum: 500, description: "random: dabs per random pick (default 1)." }
|
|
16331
|
+
}
|
|
16332
|
+
},
|
|
16333
|
+
softness: { type: "number", minimum: 0, maximum: 1, description: "How softly neighbouring stops blend: 1 smooth (default) .. 0 hard steps." }
|
|
16334
|
+
}
|
|
16335
|
+
}
|
|
16336
|
+
]
|
|
16337
|
+
};
|
|
15935
16338
|
const BRUSH_SEED = { type: "integer", minimum: 0, maximum: VECTOR_BRUSHSTROKE_MAX_SEED };
|
|
15936
16339
|
const BRUSH_PAPER = {
|
|
15937
16340
|
type: "object",
|
|
@@ -15943,6 +16346,10 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15943
16346
|
},
|
|
15944
16347
|
additionalProperties: false
|
|
15945
16348
|
};
|
|
16349
|
+
const brushColourResult = () => {
|
|
16350
|
+
const st2 = controller.getState();
|
|
16351
|
+
return { colorScheme: st2.colorScheme, colorLocked: st2.colorLocked };
|
|
16352
|
+
};
|
|
15946
16353
|
const brushstrokeCommands = () => [
|
|
15947
16354
|
{
|
|
15948
16355
|
...base,
|
|
@@ -15950,16 +16357,16 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15950
16357
|
label: "Brushstroke Brushes",
|
|
15951
16358
|
readOnly: true,
|
|
15952
16359
|
keywords: ["brush", "brushstroke", "pencil", "pastel", "charcoal", "ink", "marker", "natural media"],
|
|
15953
|
-
description: "Lists the brushes a brushstroke can use (the dry presets, then saved brushes from the brush library the vector profile admits): { brushes: [{ id, name, category, size, builtin }] } (size: the brush's own diameter in document units).",
|
|
16360
|
+
description: "Lists the brushes a brushstroke can use (the dry presets, wet paint (watercolour, gouache, wet oils), the Mixer Brush, smudge and blender, then saved brushes from the brush library the vector profile admits): { brushes: [{ id, name, category, size, builtin }], colorScheme, colorLocked } (size: the brush's own diameter in document units; colorScheme picks the side of light / dark default colours; colorLocked: brushstrokes keep their colour, vector.setColorLocked).",
|
|
15954
16361
|
params: { type: "object", properties: {}, additionalProperties: false },
|
|
15955
|
-
run: () => ({ brushes: controller.listBrushstrokeBrushes().map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size, builtin: vectorBrushstrokeBrush(b.id) !== null })) })
|
|
16362
|
+
run: () => ({ brushes: controller.listBrushstrokeBrushes().map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size, builtin: vectorBrushstrokeBrush(b.id) !== null })), ...brushColourResult() })
|
|
15956
16363
|
},
|
|
15957
16364
|
{
|
|
15958
16365
|
...base,
|
|
15959
16366
|
id: `${prefix}addBrushstroke`,
|
|
15960
16367
|
label: "Add Brushstroke\u2026",
|
|
15961
16368
|
keywords: ["brushstroke", "brush", "natural media", "pencil line", "chalk", "sketchy", "hand-drawn", "ink stroke"],
|
|
15962
|
-
description: "Paints paths (`ids`, default the selection; groups: every path inside) with a natural-media brush along their outline, as a stroke layer of kind brushstroke: `brushId` from vector.listBrushes, `size` (document units), an optional `pressure` profile (0\u20131 values spread from start to end, e.g. [0.2, 1, 0.2] for a tapered stroke), `color` and `
|
|
16369
|
+
description: "Paints paths (`ids`, default the selection; groups: every path inside) with a natural-media brush along their outline, as a stroke layer of kind brushstroke: `brushId` from vector.listBrushes, `size` (document units), an optional `pressure` profile (0\u20131 values spread from start to end, e.g. [0.2, 1, 0.2] for a tapered stroke), `color`, `opacity` and gradient `paint` sampled dab by dab along the path. Wet paint (watercolour, gouache, wet oils) bleeds and dries; the scene shows it as laid until it settles, exports wait for it. The Mixer Brush, smudge and blender act on the shapes beneath the stroke (fills, strokes and brushstrokes; not text, images or effects). Pass a `seed` for the brush's character (seeded size, angle, opacity and scatter jitter plus a paper grain: what makes pencil, pastel and charcoal look real), with `paper` to choose the grain or null for none; without a seed the brush paints plain, even dabs. The path stays editable; the strokes follow it. One undo step. Returns { paths: [{ id, strokes }] } (the brushstroke is a stroke layer; change it with vector.setBrushstroke, remove it with vector.removeStroke).",
|
|
15963
16370
|
params: {
|
|
15964
16371
|
type: "object",
|
|
15965
16372
|
properties: {
|
|
@@ -15972,7 +16379,8 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15972
16379
|
opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0\u20131 (default 1)." },
|
|
15973
16380
|
index: { type: "integer", minimum: 0, description: "Position in the stroke stack, 0 the bottom (default: on top)." },
|
|
15974
16381
|
seed: { ...BRUSH_SEED, description: "Character seed 0\u20132147483647: turns on the brush's jitter and paper grain; the same seed always renders the same stroke (default: none, plain dabs)." },
|
|
15975
|
-
paper: { anyOf: [BRUSH_PAPER, { type: "null" }], description: "With `seed`: the paper the grain comes from { pattern, seed, scale, depth }, or null for jitter without grain (default: the sanded paper)." }
|
|
16382
|
+
paper: { anyOf: [BRUSH_PAPER, { type: "null" }], description: "With `seed`: the paper the grain comes from { pattern, seed, scale, depth }, or null for jitter without grain (default: the sanded paper)." },
|
|
16383
|
+
paint: { ...BRUSH_PAINT, description: 'Gradient paint sampled dab by dab along the path instead of the colour, e.g. { type: "gradient", stops: [{ offset: 0, color: "#e11d48" }, { offset: 1, color: "#2563eb" }], map: { by: "distance", length: 200, repeat: "clamp" } } (default: the colour; a gradient layer colour or a gradient brush paints its own gradient).' }
|
|
15976
16384
|
},
|
|
15977
16385
|
required: ["brushId"],
|
|
15978
16386
|
additionalProperties: false
|
|
@@ -15988,7 +16396,8 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15988
16396
|
...params.size !== void 0 ? { size: params.size } : {},
|
|
15989
16397
|
...params.pressure ? { pressure: params.pressure } : {},
|
|
15990
16398
|
...params.seed !== void 0 ? { seed: params.seed } : {},
|
|
15991
|
-
...params.paper !== void 0 ? { paper: params.paper } : {}
|
|
16399
|
+
...params.paper !== void 0 ? { paper: params.paper } : {},
|
|
16400
|
+
...params.paint !== void 0 ? { paint: params.paint } : {}
|
|
15992
16401
|
},
|
|
15993
16402
|
{ ...params.ids ? { ids: params.ids } : {}, ...params.index !== void 0 ? { index: params.index } : {}, ...params.color ? { color: params.color } : {}, ...params.keepColour ? { keepColour: true } : {}, ...params.opacity !== void 0 ? { opacity: params.opacity } : {} }
|
|
15994
16403
|
)
|
|
@@ -16006,7 +16415,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16006
16415
|
id: `${prefix}setBrushstroke`,
|
|
16007
16416
|
label: "Edit Brushstroke\u2026",
|
|
16008
16417
|
keywords: ["brushstroke", "brush", "size", "pressure", "taper"],
|
|
16009
|
-
description: "Changes the brushstroke at stroke layer `index` of path `id` (vector.getAppearance lists strokes; 0 is the bottom): `brushId` (the same id again takes that saved brush as the library has it now), `size` (null: the brush's own), `pressure` (null: full pressure), `color`, `opacity`, `seed` (turns the brush's character on; null: plain dabs)
|
|
16418
|
+
description: "Changes the brushstroke at stroke layer `index` of path `id` (vector.getAppearance lists strokes; 0 is the bottom): `brushId` (the same id again takes that saved brush as the library has it now), `size` (null: the brush's own), `pressure` (null: full pressure), `color`, `opacity`, `seed` (turns the brush's character on; null: plain dabs), `paper` (replaced whole; null: no grain; {}: the default paper) and gradient `paint` (null: the colour). Pass at least one. One undo step. Returns { id, strokes }.",
|
|
16010
16419
|
params: {
|
|
16011
16420
|
type: "object",
|
|
16012
16421
|
properties: {
|
|
@@ -16019,7 +16428,8 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16019
16428
|
keepColour: { type: "boolean", description: "With another brushId: keep the layer's colour instead of taking the new brush's default colour (default false)." },
|
|
16020
16429
|
opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0\u20131." },
|
|
16021
16430
|
seed: { anyOf: [BRUSH_SEED, { type: "null" }], description: "Character seed 0\u20132147483647 (jitter and paper grain; a new seed gives a new take on the same stroke), or null for plain dabs." },
|
|
16022
|
-
paper: { anyOf: [BRUSH_PAPER, { type: "null" }], description: "The paper the grain comes from { pattern, seed, scale, depth } (applies with a seed), null for no grain, or {} for the default sanded paper." }
|
|
16431
|
+
paper: { anyOf: [BRUSH_PAPER, { type: "null" }], description: "The paper the grain comes from { pattern, seed, scale, depth } (applies with a seed), null for no grain, or {} for the default sanded paper." },
|
|
16432
|
+
paint: { anyOf: [...BRUSH_PAINT.anyOf, { type: "null" }], description: "Gradient paint sampled dab by dab along the path (replaced whole), or null to paint the colour again." }
|
|
16023
16433
|
},
|
|
16024
16434
|
required: ["id", "index"],
|
|
16025
16435
|
additionalProperties: false
|
|
@@ -16027,11 +16437,45 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16027
16437
|
undoable: true,
|
|
16028
16438
|
run: (_ctx, params) => {
|
|
16029
16439
|
const { id, index, ...patch } = params;
|
|
16030
|
-
if (!Object.keys(patch).length) fail("Pass at least one of brushId, size, pressure, color, opacity, seed, paper.");
|
|
16440
|
+
if (!Object.keys(patch).length) fail("Pass at least one of brushId, size, pressure, color, opacity, seed, paper, paint.");
|
|
16031
16441
|
layerIndex(id, "stroke", index);
|
|
16032
16442
|
check2(controller.setBrushstroke(index, patch, { ids: [id] }));
|
|
16033
16443
|
return stackResult(id, "stroke");
|
|
16034
16444
|
}
|
|
16445
|
+
},
|
|
16446
|
+
{
|
|
16447
|
+
...base,
|
|
16448
|
+
id: `${prefix}setColorLocked`,
|
|
16449
|
+
label: "Keep My Colour",
|
|
16450
|
+
keywords: ["lock colour", "keep colour", "brush colour", "color lock", "brushstroke"],
|
|
16451
|
+
description: `Locks or unlocks the brushstroke colour ("Keep my colour"): while locked, vector.addBrushstroke and vector.setBrushstroke with another brushId keep the colour (the path's stroke colour, or the layer's) instead of taking the brush's default colour; an explicit color still applies. Strokes already made are unchanged. Editor state, not an undo step. Returns { colorScheme, colorLocked }.`,
|
|
16452
|
+
params: {
|
|
16453
|
+
type: "object",
|
|
16454
|
+
properties: { locked: { type: "boolean", description: "true locks the colour, false unlocks it (the editor starts unlocked)." } },
|
|
16455
|
+
required: ["locked"],
|
|
16456
|
+
additionalProperties: false
|
|
16457
|
+
},
|
|
16458
|
+
run: (_ctx, params) => {
|
|
16459
|
+
controller.setColorLocked(params.locked);
|
|
16460
|
+
return brushColourResult();
|
|
16461
|
+
}
|
|
16462
|
+
},
|
|
16463
|
+
{
|
|
16464
|
+
...base,
|
|
16465
|
+
id: `${prefix}setColorScheme`,
|
|
16466
|
+
label: "Brush Colours for Light / Dark",
|
|
16467
|
+
keywords: ["colour scheme", "dark mode", "light mode", "theme", "brush colour"],
|
|
16468
|
+
description: "Sets the colour scheme that picks the side of a brush's light / dark default colour for brushstrokes made from now on (white chalk in dark, dark chalk in light). Strokes already made keep their colour. The host usually sets it to match its theme. Editor state, not an undo step. Returns { colorScheme, colorLocked }.",
|
|
16469
|
+
params: {
|
|
16470
|
+
type: "object",
|
|
16471
|
+
properties: { scheme: { type: "string", enum: ["light", "dark"], description: "light (dark chalk on a light background) or dark (light chalk on a dark one)." } },
|
|
16472
|
+
required: ["scheme"],
|
|
16473
|
+
additionalProperties: false
|
|
16474
|
+
},
|
|
16475
|
+
run: (_ctx, params) => {
|
|
16476
|
+
controller.setColorScheme(params.scheme);
|
|
16477
|
+
return brushColourResult();
|
|
16478
|
+
}
|
|
16035
16479
|
}
|
|
16036
16480
|
];
|
|
16037
16481
|
const pdfCommands = () => [
|
|
@@ -16139,13 +16583,13 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16139
16583
|
const { image, ...options2 } = params;
|
|
16140
16584
|
const parsed = parseDataUrl(image);
|
|
16141
16585
|
if (!parsed || !isPng(parsed.bytes)) fail("image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
16142
|
-
let
|
|
16586
|
+
let decoded2;
|
|
16143
16587
|
try {
|
|
16144
|
-
|
|
16588
|
+
decoded2 = decodePng(parsed.bytes, { maxPixels: VECTOR_TRACE_MAX_PIXELS });
|
|
16145
16589
|
} catch (e) {
|
|
16146
16590
|
return fail(`The PNG could not be read: ${e.message}`);
|
|
16147
16591
|
}
|
|
16148
|
-
const r = controller.traceImage(
|
|
16592
|
+
const r = controller.traceImage(decoded2, options2);
|
|
16149
16593
|
check2(r);
|
|
16150
16594
|
return {
|
|
16151
16595
|
id: r.id,
|
|
@@ -18184,13 +18628,13 @@ function getVectorSymbolsPanel(state, options = {}) {
|
|
|
18184
18628
|
const symbols = (host.symbols ?? []).map((s) => {
|
|
18185
18629
|
const b = unionBounds(s.nodes.map((n2) => vectorNodeBounds(n2, IDENTITY_MATRIX))) ?? { x: -10, y: -10, width: 20, height: 20 };
|
|
18186
18630
|
const pad = Math.max(b.width, b.height) * 0.08 + 1;
|
|
18187
|
-
const
|
|
18631
|
+
const scene2 = getVectorSceneRenderData({ document: { width: 0, height: 0, nodes: s.nodes }, viewport: { x: 0, y: 0, zoom: 1 } }, { idPrefix: `${prefix}${s.id}-` });
|
|
18188
18632
|
return {
|
|
18189
18633
|
id: s.id,
|
|
18190
18634
|
name: s.name,
|
|
18191
18635
|
instances: counts.get(s.id) ?? 0,
|
|
18192
18636
|
viewBox: `${fmt3(b.x - pad)} ${fmt3(b.y - pad)} ${fmt3(b.width + 2 * pad)} ${fmt3(b.height + 2 * pad)}`,
|
|
18193
|
-
scene,
|
|
18637
|
+
scene: scene2,
|
|
18194
18638
|
editing: state.symbolEdit?.symbolId === s.id
|
|
18195
18639
|
};
|
|
18196
18640
|
});
|
|
@@ -18207,9 +18651,9 @@ function getVectorSymbolEditBackground(state, options = {}) {
|
|
|
18207
18651
|
const e = state.symbolEdit;
|
|
18208
18652
|
if (!e || !e.hostTransform) return null;
|
|
18209
18653
|
const drop = (list) => list.filter((n2) => n2.id !== e.instanceId).map((n2) => n2.type === "group" ? { ...n2, children: drop(n2.children) } : n2);
|
|
18210
|
-
const
|
|
18654
|
+
const scene2 = getVectorSceneRenderData({ document: { ...e.host, nodes: drop(e.host.nodes) }, viewport: state.viewport }, { idPrefix: options.idPrefix ?? "ve-host-" });
|
|
18211
18655
|
const m = e.hostTransform;
|
|
18212
|
-
return { ...
|
|
18656
|
+
return { ...scene2, transform: `${scene2.transform} matrix(${m.map((v) => Math.round(v * 1e6) / 1e6).join(" ")})`, name: e.name };
|
|
18213
18657
|
}
|
|
18214
18658
|
|
|
18215
18659
|
// src/vector/effect-tools.ts
|
|
@@ -18705,6 +19149,7 @@ export {
|
|
|
18705
19149
|
VECTOR_VERTICAL_RESIZING_LABELS,
|
|
18706
19150
|
WARP_STYLES as VECTOR_WARP_STYLES,
|
|
18707
19151
|
WARP_STYLE_LABELS as VECTOR_WARP_STYLE_LABELS,
|
|
19152
|
+
VECTOR_WET_STEPS,
|
|
18708
19153
|
VECTOR_WIDTH_PRESETS,
|
|
18709
19154
|
VECTOR_WIDTH_PRESET_IDS,
|
|
18710
19155
|
VECTOR_WIDTH_PROFILE_MAX_POINTS,
|
|
@@ -18717,6 +19162,7 @@ export {
|
|
|
18717
19162
|
VectorRTree,
|
|
18718
19163
|
VectorSnapTargets,
|
|
18719
19164
|
VectorTextError,
|
|
19165
|
+
WetBrushstrokeJob,
|
|
18720
19166
|
activeVectorEffects,
|
|
18721
19167
|
activeVectorFilters,
|
|
18722
19168
|
addBuilderHit,
|
|
@@ -18877,6 +19323,7 @@ export {
|
|
|
18877
19323
|
isValidVectorParameterName,
|
|
18878
19324
|
isVectorBlendGroup,
|
|
18879
19325
|
isVectorBlendMode,
|
|
19326
|
+
isVectorBrushstrokeEngine,
|
|
18880
19327
|
isVectorBrushstrokeLayer,
|
|
18881
19328
|
isVectorConnector,
|
|
18882
19329
|
isVectorFilterType,
|
|
@@ -18933,12 +19380,14 @@ export {
|
|
|
18933
19380
|
normalizeVectorPaintStacks,
|
|
18934
19381
|
normalizeVectorTextRuns,
|
|
18935
19382
|
offsetPaths,
|
|
19383
|
+
onVectorBrushstrokesSettled,
|
|
18936
19384
|
orderQuad as orderVectorQuad,
|
|
18937
19385
|
parseOpenTypeFont,
|
|
18938
19386
|
parseSvgTransform,
|
|
18939
19387
|
parseVectorColor,
|
|
18940
19388
|
parseVectorLottie,
|
|
18941
19389
|
parseVectorSvg,
|
|
19390
|
+
parseVectorTransform,
|
|
18942
19391
|
patchVectorCharacterStyle,
|
|
18943
19392
|
patchVectorParagraphStyle,
|
|
18944
19393
|
pathTextFrameFromBezier,
|
|
@@ -18956,6 +19405,8 @@ export {
|
|
|
18956
19405
|
purgeVectorConstraints,
|
|
18957
19406
|
quantizeVectorTraceColors,
|
|
18958
19407
|
randomVectorBrushstrokeSeed,
|
|
19408
|
+
rasterizeVectorBackdrop,
|
|
19409
|
+
readVectorBrushstrokePaint,
|
|
18959
19410
|
recognizeInkShape,
|
|
18960
19411
|
rectSubpath,
|
|
18961
19412
|
refineMorphContour,
|
|
@@ -18971,6 +19422,8 @@ export {
|
|
|
18971
19422
|
removeWarpMeshSplitU as removeVectorMeshSplitU,
|
|
18972
19423
|
removeWarpMeshSplitV as removeVectorMeshSplitV,
|
|
18973
19424
|
removeWidthPoints,
|
|
19425
|
+
renderBackdropBrushstroke,
|
|
19426
|
+
renderWetBrushstroke,
|
|
18974
19427
|
repeatPaths,
|
|
18975
19428
|
replaceVectorText,
|
|
18976
19429
|
rerouteVectorConnectors,
|
|
@@ -19018,9 +19471,11 @@ export {
|
|
|
19018
19471
|
scaleVectorStrokeWidths,
|
|
19019
19472
|
setAnchorCorner,
|
|
19020
19473
|
setSubpathCorners,
|
|
19474
|
+
setVectorBackdropSource,
|
|
19021
19475
|
setVectorGradientVector,
|
|
19022
19476
|
setVectorTextShaper,
|
|
19023
19477
|
setWidthPoint,
|
|
19478
|
+
settleVectorBrushstrokes,
|
|
19024
19479
|
shadeVectorColor,
|
|
19025
19480
|
simplifyPolyline,
|
|
19026
19481
|
smoothSubpath,
|
|
@@ -19069,6 +19524,7 @@ export {
|
|
|
19069
19524
|
turnMorphContour,
|
|
19070
19525
|
twistPaths,
|
|
19071
19526
|
unionBounds,
|
|
19527
|
+
usesVectorBackdrop,
|
|
19072
19528
|
validateVectorConstraint,
|
|
19073
19529
|
variableStrokeOutline,
|
|
19074
19530
|
variableStrokeRings,
|
|
@@ -19078,6 +19534,7 @@ export {
|
|
|
19078
19534
|
vectorAppearanceShapes,
|
|
19079
19535
|
vectorArtModels,
|
|
19080
19536
|
vectorArtboards,
|
|
19537
|
+
vectorBackdropRect,
|
|
19081
19538
|
vectorBevelFaces,
|
|
19082
19539
|
vectorBlendColorDistance,
|
|
19083
19540
|
vectorBlendKeyTargets,
|
|
@@ -19098,7 +19555,9 @@ export {
|
|
|
19098
19555
|
vectorBrushstrokeCharacterError,
|
|
19099
19556
|
vectorBrushstrokeDefaultColour,
|
|
19100
19557
|
vectorBrushstrokeImages,
|
|
19558
|
+
vectorBrushstrokePaintError,
|
|
19101
19559
|
vectorBrushstrokeReach,
|
|
19560
|
+
vectorBrushstrokesSettling,
|
|
19102
19561
|
vectorBuilderErasedEdges,
|
|
19103
19562
|
vectorChildAppearanceShapes,
|
|
19104
19563
|
vectorConnectionSites,
|
|
@@ -19191,6 +19650,7 @@ export {
|
|
|
19191
19650
|
vectorMaskedShapes,
|
|
19192
19651
|
vectorMasterArt,
|
|
19193
19652
|
vectorMasterPages,
|
|
19653
|
+
vectorMediaEngine,
|
|
19194
19654
|
vectorMeshColorAt,
|
|
19195
19655
|
warpMeshColumns as vectorMeshColumns,
|
|
19196
19656
|
warpMeshControlBounds as vectorMeshControlBounds,
|