@pixerate/editor 0.135.0 → 0.137.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/animation-editor/index.cjs +63 -55
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +8 -5
- package/dist/animation-editor/index.d.ts +8 -5
- package/dist/animation-editor/index.js +10 -2
- package/dist/animation-editor/index.js.map +1 -1
- package/dist/{bezier-J8i4uqSH.d.cts → bezier-CZ2AhjID.d.cts} +1 -1
- package/dist/{bezier-lNEOp0Jx.d.ts → bezier-Dywv3Bw8.d.ts} +1 -1
- package/dist/blending-D7jCmjO6.d.cts +1026 -0
- package/dist/blending-NgF3qo3d.d.ts +1026 -0
- package/dist/brushes-CnvZSYYU.d.cts +744 -0
- package/dist/brushes-DJmUbIgz.d.ts +744 -0
- package/dist/{brushstrokeRender-SII6MG4R.js → brushstrokeRender-2FAXA3XC.js} +13 -16
- package/dist/{brushstrokeRender-SII6MG4R.js.map → brushstrokeRender-2FAXA3XC.js.map} +1 -1
- package/dist/{brushstrokeRender-JNHVEUFG.cjs → brushstrokeRender-IMFY3FF6.cjs} +16 -19
- package/dist/brushstrokeRender-IMFY3FF6.cjs.map +1 -0
- package/dist/canvas/index.cjs +10 -10
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +10 -7
- package/dist/canvas/index.d.ts +10 -7
- package/dist/canvas/index.js +4 -4
- package/dist/{chunk-W4LNKTHT.cjs → chunk-2XS6LTUP.cjs} +63 -63
- package/dist/{chunk-W4LNKTHT.cjs.map → chunk-2XS6LTUP.cjs.map} +1 -1
- package/dist/{chunk-HHTCDFZC.js → chunk-3D77LOBM.js} +234 -136
- package/dist/chunk-3D77LOBM.js.map +1 -0
- package/dist/{chunk-BR7EKQUP.js → chunk-3G6J7UFV.js} +322 -2
- package/dist/chunk-3G6J7UFV.js.map +1 -0
- package/dist/{chunk-62XUO5LB.js → chunk-3X6IAJUQ.js} +2 -2
- package/dist/{chunk-EM3FMJWE.js → chunk-5FSRTP6E.js} +320 -3
- package/dist/chunk-5FSRTP6E.js.map +1 -0
- package/dist/{chunk-Z2H7KECK.cjs → chunk-5OOIUXNJ.cjs} +345 -247
- package/dist/chunk-5OOIUXNJ.cjs.map +1 -0
- package/dist/{chunk-7RJ6P7MI.js → chunk-6S6362D2.js} +2 -2
- package/dist/chunk-A3BVTI3G.js +150 -0
- package/dist/chunk-A3BVTI3G.js.map +1 -0
- package/dist/{chunk-24ATT2PE.cjs → chunk-AEAAN4AD.cjs} +120 -120
- package/dist/{chunk-24ATT2PE.cjs.map → chunk-AEAAN4AD.cjs.map} +1 -1
- package/dist/chunk-ECCXGELC.cjs +727 -0
- package/dist/chunk-ECCXGELC.cjs.map +1 -0
- package/dist/{chunk-SVYE5HIM.js → chunk-EQQMGO42.js} +2 -2
- package/dist/{chunk-FHPVDTI2.cjs → chunk-F2Y4E4GU.cjs} +22 -22
- package/dist/{chunk-FHPVDTI2.cjs.map → chunk-F2Y4E4GU.cjs.map} +1 -1
- package/dist/{chunk-OXSV64JX.js → chunk-G6IKY6PL.js} +4 -4
- package/dist/{chunk-UYQUPUR6.js → chunk-HUO3TI4C.js} +3 -3
- package/dist/chunk-I52DRGGJ.js +727 -0
- package/dist/chunk-I52DRGGJ.js.map +1 -0
- package/dist/{chunk-DDSO4HS7.cjs → chunk-KEENUAFN.cjs} +2 -2
- package/dist/{chunk-DDSO4HS7.cjs.map → chunk-KEENUAFN.cjs.map} +1 -1
- package/dist/{chunk-2HC4DRF4.js → chunk-LLFHCSU2.js} +2 -2
- package/dist/{chunk-J5BA3BQ6.cjs → chunk-O7LUD7MH.cjs} +323 -3
- package/dist/chunk-O7LUD7MH.cjs.map +1 -0
- package/dist/{chunk-ILYFOJUN.js → chunk-OIBIRXRG.js} +3 -3
- package/dist/{chunk-FWEO3HSL.js → chunk-PQQF5KOW.js} +8 -8
- package/dist/{chunk-T6TV6HY6.cjs → chunk-PRSRYHQD.cjs} +36 -36
- package/dist/{chunk-T6TV6HY6.cjs.map → chunk-PRSRYHQD.cjs.map} +1 -1
- package/dist/chunk-Q6OUXEK3.cjs +150 -0
- package/dist/chunk-Q6OUXEK3.cjs.map +1 -0
- package/dist/{chunk-6GFRKLLS.cjs → chunk-QBWEH3IM.cjs} +429 -112
- package/dist/chunk-QBWEH3IM.cjs.map +1 -0
- package/dist/{chunk-ESIWZMMK.js → chunk-SYT2TLQF.js} +35 -37
- package/dist/{chunk-ESIWZMMK.js.map → chunk-SYT2TLQF.js.map} +1 -1
- package/dist/{chunk-YT3JL7YM.cjs → chunk-TLCTTQFF.cjs} +53 -53
- package/dist/{chunk-YT3JL7YM.cjs.map → chunk-TLCTTQFF.cjs.map} +1 -1
- package/dist/{chunk-P3RQ5UJE.cjs → chunk-UTQKC66X.cjs} +4 -4
- package/dist/{chunk-P3RQ5UJE.cjs.map → chunk-UTQKC66X.cjs.map} +1 -1
- package/dist/{chunk-J3B45HTA.cjs → chunk-XB25WOC2.cjs} +6 -6
- package/dist/{chunk-J3B45HTA.cjs.map → chunk-XB25WOC2.cjs.map} +1 -1
- package/dist/{chunk-YAFS2QFZ.cjs → chunk-XN2NBLV5.cjs} +313 -315
- package/dist/chunk-XN2NBLV5.cjs.map +1 -0
- package/dist/effects/index.d.cts +7 -4
- package/dist/effects/index.d.ts +7 -4
- package/dist/{export-DMeG6E06.d.ts → export-41rL6Cne.d.ts} +1 -1
- package/dist/{export-AZSwX087.d.cts → export-CyJdfP_Z.d.cts} +5 -4
- package/dist/{export-KzubBKo7.d.cts → export-D9mXp05Q.d.cts} +1 -1
- package/dist/{export-BDkyDNM8.d.ts → export-DwnZ_hvQ.d.ts} +5 -4
- package/dist/extensions/index.cjs +3 -3
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.js +2 -2
- package/dist/generation/index.cjs +150 -945
- package/dist/generation/index.cjs.map +1 -1
- package/dist/generation/index.d.cts +3 -423
- package/dist/generation/index.d.ts +3 -423
- package/dist/generation/index.js +59 -854
- package/dist/generation/index.js.map +1 -1
- package/dist/grammar/index.cjs +3 -3
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.js +2 -2
- package/dist/{helpers-Coa4e2j_.d.ts → helpers-C-RgeyvH.d.ts} +25 -8
- package/dist/{helpers-DHl28yLO.d.cts → helpers-v6GMazzg.d.cts} +25 -8
- package/dist/image-editor/index.cjs +986 -986
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +15 -11
- package/dist/image-editor/index.d.ts +15 -11
- package/dist/image-editor/index.js +94 -94
- package/dist/index.cjs +28 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +36 -36
- package/dist/{opentype-BWR7Qv8W.d.cts → opentype-KR_jj6b_.d.cts} +1 -1
- package/dist/{opentype-Dfcx6wFx.d.ts → opentype-a1KAW-qs.d.ts} +1 -1
- package/dist/paint/index.cjs +5 -8
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +4 -2
- package/dist/paint/index.d.ts +4 -2
- package/dist/paint/index.js +15 -18
- package/dist/paint-editor/index.cjs +405 -403
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +15 -748
- package/dist/paint-editor/index.d.ts +15 -748
- package/dist/paint-editor/index.js +79 -77
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/{pathfinder-D8KQ6blQ.d.cts → pathfinder-BHS4vQNm.d.cts} +1 -1
- package/dist/{pathfinder-Celbi3e6.d.ts → pathfinder-BVvTIBMo.d.ts} +1 -1
- package/dist/{pool-8JZGWHvR.d.ts → pool-CZQOrSXZ.d.ts} +1 -1
- package/dist/{pool-BfuxBKTd.d.cts → pool-FjZZmZ80.d.cts} +1 -1
- package/dist/provenance-DmlcgdzT.d.cts +426 -0
- package/dist/provenance-uh5r2TQ8.d.ts +426 -0
- package/dist/rand-8xbUb6ur.d.ts +1118 -0
- package/dist/rand-Cw9VLrVC.d.cts +1118 -0
- package/dist/raster/index.cjs +17 -19
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.js +9 -11
- package/dist/raster/index.js.map +1 -1
- package/dist/{svg-CxxqG3_W.d.ts → svg-CSz3mzkw.d.ts} +2 -2
- package/dist/{svg-pwXWq6s2.d.cts → svg-DTb40ICU.d.cts} +2 -2
- package/dist/{tools-y1M6O3UT.d.ts → tools-B0wXP5cF.d.ts} +1 -1
- package/dist/{tools-3EBjR91b.d.cts → tools-Ca0kI1qo.d.cts} +1 -1
- package/dist/vector/index.cjs +2698 -1674
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +390 -72
- package/dist/vector/index.d.ts +390 -72
- package/dist/vector/index.js +1665 -641
- package/dist/vector/index.js.map +1 -1
- package/dist/video-editor/index.d.cts +7 -4
- package/dist/video-editor/index.d.ts +7 -4
- package/dist/{warp-Ceqdz7kj.d.cts → warp-DTOp0lON.d.cts} +1 -1
- package/dist/{warp-C1gwu2G_.d.ts → warp-pd7hrH4T.d.ts} +1 -1
- package/package.json +1 -1
- package/skills/pixerate-vector/SKILL.md +22 -0
- package/dist/brushstrokeRender-JNHVEUFG.cjs.map +0 -1
- package/dist/chunk-64PYNEHJ.js +0 -340
- package/dist/chunk-64PYNEHJ.js.map +0 -1
- package/dist/chunk-6GFRKLLS.cjs.map +0 -1
- package/dist/chunk-7QN76ESJ.js +0 -323
- package/dist/chunk-7QN76ESJ.js.map +0 -1
- package/dist/chunk-BR7EKQUP.js.map +0 -1
- package/dist/chunk-DCLJNVZS.cjs +0 -323
- package/dist/chunk-DCLJNVZS.cjs.map +0 -1
- package/dist/chunk-EM3FMJWE.js.map +0 -1
- package/dist/chunk-HHTCDFZC.js.map +0 -1
- package/dist/chunk-J5BA3BQ6.cjs.map +0 -1
- package/dist/chunk-YAFS2QFZ.cjs.map +0 -1
- package/dist/chunk-Z2H7KECK.cjs.map +0 -1
- package/dist/chunk-Z6OUCS2K.cjs +0 -340
- package/dist/chunk-Z6OUCS2K.cjs.map +0 -1
- package/dist/rand-BszBST4s.d.ts +0 -2140
- package/dist/rand-DOH_aMvm.d.cts +0 -2140
- /package/dist/{chunk-62XUO5LB.js.map → chunk-3X6IAJUQ.js.map} +0 -0
- /package/dist/{chunk-7RJ6P7MI.js.map → chunk-6S6362D2.js.map} +0 -0
- /package/dist/{chunk-SVYE5HIM.js.map → chunk-EQQMGO42.js.map} +0 -0
- /package/dist/{chunk-OXSV64JX.js.map → chunk-G6IKY6PL.js.map} +0 -0
- /package/dist/{chunk-UYQUPUR6.js.map → chunk-HUO3TI4C.js.map} +0 -0
- /package/dist/{chunk-2HC4DRF4.js.map → chunk-LLFHCSU2.js.map} +0 -0
- /package/dist/{chunk-ILYFOJUN.js.map → chunk-OIBIRXRG.js.map} +0 -0
- /package/dist/{chunk-FWEO3HSL.js.map → chunk-PQQF5KOW.js.map} +0 -0
package/dist/vector/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
planGenerationRequest,
|
|
3
|
+
stableStringify
|
|
4
|
+
} from "../chunk-I52DRGGJ.js";
|
|
1
5
|
import {
|
|
2
6
|
PlanarMap,
|
|
3
7
|
cutOutPiece,
|
|
@@ -16,15 +20,6 @@ import {
|
|
|
16
20
|
orderQuad,
|
|
17
21
|
solve8
|
|
18
22
|
} from "../chunk-GX332W2C.js";
|
|
19
|
-
import {
|
|
20
|
-
bytesToBase64,
|
|
21
|
-
decodePng,
|
|
22
|
-
isPng,
|
|
23
|
-
parseDataUrl
|
|
24
|
-
} from "../chunk-7QN76ESJ.js";
|
|
25
|
-
import {
|
|
26
|
-
deflate
|
|
27
|
-
} from "../chunk-6FCKOS2B.js";
|
|
28
23
|
import {
|
|
29
24
|
correspondMorphContours,
|
|
30
25
|
createVectorMorph,
|
|
@@ -43,7 +38,7 @@ import {
|
|
|
43
38
|
reverseMorphContour,
|
|
44
39
|
subpathToMorphContour,
|
|
45
40
|
turnMorphContour
|
|
46
|
-
} from "../chunk-
|
|
41
|
+
} from "../chunk-6S6362D2.js";
|
|
47
42
|
import {
|
|
48
43
|
DEFAULT_VECTOR_FONT,
|
|
49
44
|
IDENTITY_MATRIX,
|
|
@@ -52,6 +47,7 @@ import {
|
|
|
52
47
|
VECTOR_BLEND_MODES,
|
|
53
48
|
VECTOR_BLEND_MODE_LABELS,
|
|
54
49
|
VECTOR_BLEND_SPACINGS,
|
|
50
|
+
VECTOR_BRUSHSTROKE_SCALE,
|
|
55
51
|
VECTOR_CHARACTER_STYLE_KEYS,
|
|
56
52
|
VECTOR_CONNECTOR_ROUTES,
|
|
57
53
|
VECTOR_CONNECTOR_ROUTE_LABELS,
|
|
@@ -198,6 +194,7 @@ import {
|
|
|
198
194
|
isUniformWidthProfile,
|
|
199
195
|
isValidWarpMesh,
|
|
200
196
|
isVectorBlendMode,
|
|
197
|
+
isVectorBrushstrokeLayer,
|
|
201
198
|
isVectorConnector,
|
|
202
199
|
isVectorFilterType,
|
|
203
200
|
isVectorFrame,
|
|
@@ -288,6 +285,7 @@ import {
|
|
|
288
285
|
sampleVectorGradient,
|
|
289
286
|
sampleVectorGradientAt,
|
|
290
287
|
sampleVectorPaintDefAt,
|
|
288
|
+
sanitizeVectorBrushstroke,
|
|
291
289
|
sanitizeVectorConnector,
|
|
292
290
|
sanitizeVectorEffects,
|
|
293
291
|
sanitizeVectorFills,
|
|
@@ -356,6 +354,10 @@ import {
|
|
|
356
354
|
vectorBlendModeFromCss,
|
|
357
355
|
vectorBoundsInside,
|
|
358
356
|
vectorBoxMatrix,
|
|
357
|
+
vectorBrushstrokeBrush,
|
|
358
|
+
vectorBrushstrokeBrushes,
|
|
359
|
+
vectorBrushstrokeImages,
|
|
360
|
+
vectorBrushstrokeReach,
|
|
359
361
|
vectorChildAppearanceShapes,
|
|
360
362
|
vectorConnectionSites,
|
|
361
363
|
vectorConnectorTargetAt,
|
|
@@ -501,7 +503,11 @@ import {
|
|
|
501
503
|
withVectorStrokes,
|
|
502
504
|
withVectorTargetPaint,
|
|
503
505
|
zigzagPaths
|
|
504
|
-
} from "../chunk-
|
|
506
|
+
} from "../chunk-3D77LOBM.js";
|
|
507
|
+
import "../chunk-HUO3TI4C.js";
|
|
508
|
+
import {
|
|
509
|
+
createProvenance
|
|
510
|
+
} from "../chunk-A3BVTI3G.js";
|
|
505
511
|
import {
|
|
506
512
|
bezierPathBounds,
|
|
507
513
|
bezierPathToSvg,
|
|
@@ -509,6 +515,7 @@ import {
|
|
|
509
515
|
contourSignedArea,
|
|
510
516
|
contoursToPath,
|
|
511
517
|
distanceToBezierPath,
|
|
518
|
+
flattenChain,
|
|
512
519
|
pathBoolean,
|
|
513
520
|
pathToContours,
|
|
514
521
|
pointInBezierPath,
|
|
@@ -532,6 +539,19 @@ import {
|
|
|
532
539
|
resolveEffectValues
|
|
533
540
|
} from "../chunk-QFSTQHZY.js";
|
|
534
541
|
import "../chunk-4FX5ACVG.js";
|
|
542
|
+
import "../chunk-5FSRTP6E.js";
|
|
543
|
+
import "../chunk-63AZAUSN.js";
|
|
544
|
+
import {
|
|
545
|
+
bytesToBase64,
|
|
546
|
+
decodePng,
|
|
547
|
+
isPng,
|
|
548
|
+
parseDataUrl
|
|
549
|
+
} from "../chunk-3G6J7UFV.js";
|
|
550
|
+
import {
|
|
551
|
+
deflate
|
|
552
|
+
} from "../chunk-6FCKOS2B.js";
|
|
553
|
+
import "../chunk-FPENXAEJ.js";
|
|
554
|
+
import "../chunk-P6ODPZ4W.js";
|
|
535
555
|
import {
|
|
536
556
|
HistoryManager
|
|
537
557
|
} from "../chunk-QVZZGT4X.js";
|
|
@@ -541,225 +561,831 @@ import {
|
|
|
541
561
|
hexToRgba
|
|
542
562
|
} from "../chunk-TGD52FEZ.js";
|
|
543
563
|
|
|
544
|
-
// src/vector/
|
|
545
|
-
var
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
var fmt = (n2) => Math.round(n2 * 100) / 100;
|
|
553
|
-
var apply = (m, p) => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] });
|
|
554
|
-
function vectorGradientTargetPath(doc, selection, target, layer = null) {
|
|
555
|
-
for (const id of selection) {
|
|
556
|
-
const node = findVectorNode(doc.nodes, id);
|
|
557
|
-
if (!node) continue;
|
|
558
|
-
const path = collectPathNodes(node).find((p) => isVectorGradient(vectorTargetPaint(p, target, layer)));
|
|
559
|
-
if (path) return path;
|
|
564
|
+
// src/vector/trace.ts
|
|
565
|
+
var VECTOR_TRACE_MAX_PIXELS = 4e6;
|
|
566
|
+
function signedArea(p) {
|
|
567
|
+
let a = 0;
|
|
568
|
+
for (let i = 0; i < p.length; i++) {
|
|
569
|
+
const [x0, y0] = p[i];
|
|
570
|
+
const [x1, y1] = p[(i + 1) % p.length];
|
|
571
|
+
a += x0 * y1 - x1 * y0;
|
|
560
572
|
}
|
|
561
|
-
return
|
|
573
|
+
return a / 2;
|
|
562
574
|
}
|
|
563
|
-
function
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
const bounds = vectorPathLocalBounds(path);
|
|
570
|
-
const h = vectorGradientHandles(gradient, bounds, screen);
|
|
571
|
-
const v = { x: h.end.x - h.start.x, y: h.end.y - h.start.y };
|
|
572
|
-
const len2 = Math.hypot(v.x, v.y);
|
|
573
|
-
const normal = len2 > 1e-9 ? { x: -v.y / len2, y: v.x / len2 } : { x: 0, y: 1 };
|
|
574
|
-
const at = (t, gap) => ({ x: h.start.x + v.x * t + normal.x * gap, y: h.start.y + v.y * t + normal.y * gap });
|
|
575
|
-
const sorted = sortedVectorGradientStops(gradient);
|
|
576
|
-
const stops = sorted.map((s, index) => ({ ...s, index, ...at(s.offset, VECTOR_GRADIENT_STOP_GAP) }));
|
|
577
|
-
const midpoints = sorted.slice(0, -1).flatMap((s, index) => {
|
|
578
|
-
const pos = vectorGradientMidpointPosition(sorted, index);
|
|
579
|
-
return pos === null || sorted[index + 1].offset - s.offset < 1e-6 ? [] : [{ index, midpoint: s.midpoint ?? 0.5, ...at(pos, -VECTOR_GRADIENT_MIDPOINT_GAP) }];
|
|
580
|
-
});
|
|
581
|
-
let extent = null;
|
|
582
|
-
if (isCentredVectorGradient(gradient.type)) {
|
|
583
|
-
const frame = multiplyMatrices(vectorGradientMatrix(gradient, bounds), screen);
|
|
584
|
-
const pts = gradient.type === "diamond" ? [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 0, y: -1 }] : Array.from({ length: 48 }, (_, k) => ({ x: Math.cos(k * Math.PI / 24), y: Math.sin(k * Math.PI / 24) }));
|
|
585
|
-
extent = `M${pts.map((p) => apply(frame, p)).map((p) => `${fmt(p.x)} ${fmt(p.y)}`).join("L")}Z`;
|
|
575
|
+
function pointInPoly(p, poly) {
|
|
576
|
+
let inside = false;
|
|
577
|
+
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
578
|
+
const [xi, yi] = poly[i];
|
|
579
|
+
const [xj, yj] = poly[j];
|
|
580
|
+
if (yi > p[1] !== yj > p[1] && p[0] < (xj - xi) * (p[1] - yi) / (yj - yi) + xi) inside = !inside;
|
|
586
581
|
}
|
|
587
|
-
return
|
|
588
|
-
}
|
|
589
|
-
function vectorGradientBarPosition(a, p) {
|
|
590
|
-
const v = { x: a.end.x - a.start.x, y: a.end.y - a.start.y };
|
|
591
|
-
const l2 = v.x * v.x + v.y * v.y;
|
|
592
|
-
const d = { x: p.x - a.start.x, y: p.y - a.start.y };
|
|
593
|
-
if (l2 < 1e-12) return { offset: 0, across: Math.hypot(d.x, d.y) };
|
|
594
|
-
return { offset: (d.x * v.x + d.y * v.y) / l2, across: d.x * a.normal.x + d.y * a.normal.y };
|
|
595
|
-
}
|
|
596
|
-
function hitVectorGradientAnnotator(a, p) {
|
|
597
|
-
const near = (q, r) => !!q && Math.hypot(q.x - p.x, q.y - p.y) <= r;
|
|
598
|
-
for (let i = a.stops.length - 1; i >= 0; i--) if (near(a.stops[i], STOP_HIT)) return { kind: "stop", index: a.stops[i].index };
|
|
599
|
-
for (const m of a.midpoints) if (near(m, MIDPOINT_HIT)) return { kind: "midpoint", index: m.index };
|
|
600
|
-
const centred = isCentredVectorGradient(a.gradient.type);
|
|
601
|
-
if (near(a.end, HANDLE_HIT)) return { kind: "handle", handle: centred ? "radius" : "end" };
|
|
602
|
-
if (a.focal && near(a.focal, HANDLE_HIT) && !near(a.start, 1)) return { kind: "handle", handle: "focal" };
|
|
603
|
-
if (near(a.start, HANDLE_HIT)) return { kind: "handle", handle: "start" };
|
|
604
|
-
if (near(a.aspect, HANDLE_HIT)) return { kind: "handle", handle: "aspect" };
|
|
605
|
-
const { offset, across } = vectorGradientBarPosition(a, p);
|
|
606
|
-
if (offset >= 0 && offset <= 1 && across >= -BAR_HIT && across <= VECTOR_GRADIENT_STOP_GAP + STOP_HIT) return { kind: "bar", offset };
|
|
607
|
-
return null;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// src/vector/blendGroup.ts
|
|
611
|
-
var DEFAULT_VECTOR_BLEND_STEPS = 5;
|
|
612
|
-
var DEFAULT_VECTOR_BLEND_DISTANCE = 20;
|
|
613
|
-
var VECTOR_BLEND_MAX_STEPS = 1e3;
|
|
614
|
-
var VECTOR_BLEND_MAX_SMOOTH_STEPS = 256;
|
|
615
|
-
function isVectorBlendGroup(node) {
|
|
616
|
-
return !!node && node.type === "group" && !!node.blend;
|
|
617
|
-
}
|
|
618
|
-
function vectorBlendKeys(group) {
|
|
619
|
-
return group.children.filter((c) => !c.blendStep);
|
|
582
|
+
return inside;
|
|
620
583
|
}
|
|
621
|
-
function
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
584
|
+
function vectorTraceContours(field, w, h, threshold) {
|
|
585
|
+
const gw = w + 2;
|
|
586
|
+
const gh = h + 2;
|
|
587
|
+
const val = (i, j) => i === 0 || j === 0 || i > w || j > h ? -Infinity : field[(j - 1) * w + (i - 1)];
|
|
588
|
+
const inside = (i, j) => val(i, j) >= threshold;
|
|
589
|
+
const pos = (i, j) => [i - 0.5, j - 0.5];
|
|
590
|
+
const cross2 = (a, b) => {
|
|
591
|
+
const va = val(a[0], a[1]);
|
|
592
|
+
const vb = val(b[0], b[1]);
|
|
593
|
+
let t;
|
|
594
|
+
if (Number.isFinite(va) && Number.isFinite(vb) && Math.abs(vb - va) > 1e-12) t = Math.min(1, Math.max(0, (threshold - va) / (vb - va)));
|
|
595
|
+
else if (Number.isFinite(va)) t = va >= threshold ? 0.5 : 0;
|
|
596
|
+
else t = vb >= threshold ? 0.5 : 1;
|
|
597
|
+
const pa = pos(a[0], a[1]);
|
|
598
|
+
const pb = pos(b[0], b[1]);
|
|
599
|
+
return [pa[0] + (pb[0] - pa[0]) * t, pa[1] + (pb[1] - pa[1]) * t];
|
|
628
600
|
};
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
601
|
+
const hid = (i, j) => 2 * (j * gw + i);
|
|
602
|
+
const vid = (i, j) => 2 * (j * gw + i) + 1;
|
|
603
|
+
const next = /* @__PURE__ */ new Map();
|
|
604
|
+
const point = /* @__PURE__ */ new Map();
|
|
605
|
+
for (let j = 0; j < gh - 1; j++) {
|
|
606
|
+
for (let i = 0; i < gw - 1; i++) {
|
|
607
|
+
const tl = inside(i, j);
|
|
608
|
+
const tr = inside(i + 1, j);
|
|
609
|
+
const br = inside(i + 1, j + 1);
|
|
610
|
+
const bl = inside(i, j + 1);
|
|
611
|
+
const code = +tl << 3 | +tr << 2 | +br << 1 | +bl;
|
|
612
|
+
if (code === 0 || code === 15) continue;
|
|
613
|
+
const top = hid(i, j);
|
|
614
|
+
const bottom = hid(i, j + 1);
|
|
615
|
+
const left = vid(i, j);
|
|
616
|
+
const right = vid(i + 1, j);
|
|
617
|
+
const seg = (a, b) => next.set(a, b);
|
|
618
|
+
const mean = () => (val(i, j) + val(i + 1, j) + val(i + 1, j + 1) + val(i, j + 1)) / 4;
|
|
619
|
+
switch (code) {
|
|
620
|
+
case 1:
|
|
621
|
+
seg(left, bottom);
|
|
622
|
+
break;
|
|
623
|
+
case 2:
|
|
624
|
+
seg(bottom, right);
|
|
625
|
+
break;
|
|
626
|
+
case 3:
|
|
627
|
+
seg(left, right);
|
|
628
|
+
break;
|
|
629
|
+
case 4:
|
|
630
|
+
seg(right, top);
|
|
631
|
+
break;
|
|
632
|
+
case 5:
|
|
633
|
+
if (mean() >= threshold) {
|
|
634
|
+
seg(left, top);
|
|
635
|
+
seg(right, bottom);
|
|
636
|
+
} else {
|
|
637
|
+
seg(left, bottom);
|
|
638
|
+
seg(right, top);
|
|
639
|
+
}
|
|
640
|
+
break;
|
|
641
|
+
case 6:
|
|
642
|
+
seg(bottom, top);
|
|
643
|
+
break;
|
|
644
|
+
case 7:
|
|
645
|
+
seg(left, top);
|
|
646
|
+
break;
|
|
647
|
+
case 8:
|
|
648
|
+
seg(top, left);
|
|
649
|
+
break;
|
|
650
|
+
case 9:
|
|
651
|
+
seg(top, bottom);
|
|
652
|
+
break;
|
|
653
|
+
case 10:
|
|
654
|
+
if (mean() >= threshold) {
|
|
655
|
+
seg(top, right);
|
|
656
|
+
seg(bottom, left);
|
|
657
|
+
} else {
|
|
658
|
+
seg(top, left);
|
|
659
|
+
seg(bottom, right);
|
|
660
|
+
}
|
|
661
|
+
break;
|
|
662
|
+
case 11:
|
|
663
|
+
seg(top, right);
|
|
664
|
+
break;
|
|
665
|
+
case 12:
|
|
666
|
+
seg(right, left);
|
|
667
|
+
break;
|
|
668
|
+
case 13:
|
|
669
|
+
seg(right, bottom);
|
|
670
|
+
break;
|
|
671
|
+
case 14:
|
|
672
|
+
seg(bottom, left);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
const edges = [
|
|
676
|
+
[top, [i, j], [i + 1, j]],
|
|
677
|
+
[bottom, [i, j + 1], [i + 1, j + 1]],
|
|
678
|
+
[left, [i, j], [i, j + 1]],
|
|
679
|
+
[right, [i + 1, j], [i + 1, j + 1]]
|
|
680
|
+
];
|
|
681
|
+
for (const [e, a, b] of edges) if (inside(a[0], a[1]) !== inside(b[0], b[1]) && !point.has(e)) point.set(e, cross2(a, b));
|
|
682
|
+
}
|
|
636
683
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
if (!x || !y) return;
|
|
653
|
-
d = Math.max(d, Math.abs(x.r - y.r), Math.abs(x.g - y.g), Math.abs(x.b - y.b), Math.abs(x.a - y.a));
|
|
654
|
-
});
|
|
684
|
+
const out = [];
|
|
685
|
+
for (const start of [...next.keys()].sort((a, b) => a - b)) {
|
|
686
|
+
if (!next.has(start)) continue;
|
|
687
|
+
const poly = [];
|
|
688
|
+
let e = start;
|
|
689
|
+
for (; ; ) {
|
|
690
|
+
const n2 = next.get(e);
|
|
691
|
+
if (n2 === void 0) break;
|
|
692
|
+
next.delete(e);
|
|
693
|
+
const p = point.get(e);
|
|
694
|
+
if (p) poly.push(p);
|
|
695
|
+
e = n2;
|
|
696
|
+
if (e === start) break;
|
|
697
|
+
}
|
|
698
|
+
if (poly.length >= 3) out.push(poly);
|
|
655
699
|
}
|
|
656
|
-
return
|
|
700
|
+
return out;
|
|
657
701
|
}
|
|
658
|
-
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return Math.min(VECTOR_BLEND_MAX_STEPS, Math.max(0, Math.round(length / d) - 1));
|
|
667
|
-
}
|
|
668
|
-
if (o.spacing === "smoothColor") {
|
|
669
|
-
const cd = vectorBlendColorDistance(a, b);
|
|
670
|
-
if (cd > 1 / 255) return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(cd * 255 / 2)));
|
|
671
|
-
const ba = vectorNodeBounds(a);
|
|
672
|
-
const bb = vectorNodeBounds(b);
|
|
673
|
-
const dist4 = ba && bb ? Math.max(Math.abs(ba.x - bb.x), Math.abs(ba.y - bb.y), Math.abs(ba.x + ba.width - bb.x - bb.width), Math.abs(ba.y + ba.height - bb.y - bb.height)) : length;
|
|
674
|
-
return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(dist4 / 2)));
|
|
702
|
+
function loopsOf(field, w, h, threshold, tolerance, minArea) {
|
|
703
|
+
const loops = [];
|
|
704
|
+
for (const raw of vectorTraceContours(field, w, h, threshold)) {
|
|
705
|
+
const area = signedArea(raw);
|
|
706
|
+
if (Math.abs(area) < Math.max(0, minArea)) continue;
|
|
707
|
+
const simple = simplifyRing(raw, Math.max(0.01, tolerance));
|
|
708
|
+
if (simple.length < 3) continue;
|
|
709
|
+
loops.push({ raw, simple, area, depth: 0 });
|
|
675
710
|
}
|
|
676
|
-
|
|
711
|
+
for (const l of loops) l.depth = loops.filter((o) => o !== l && Math.abs(o.area) > Math.abs(l.area) && pointInPoly(l.simple[0], o.simple)).length;
|
|
712
|
+
return loops.sort((a, b) => a.depth - b.depth || Math.abs(b.area) - Math.abs(a.area));
|
|
677
713
|
}
|
|
678
|
-
function
|
|
679
|
-
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
714
|
+
function loopSubpath(l, tolerance, roundness) {
|
|
715
|
+
if (roundness <= 0) return { closed: true, anchors: l.simple.map(([x, y]) => ({ x, y })) };
|
|
716
|
+
const cornerAngleDeg = 25 + roundness * 125;
|
|
717
|
+
const cubics = fitCubics(l.raw, Math.max(0.05, tolerance), {
|
|
718
|
+
closed: true,
|
|
719
|
+
cornerAngleDeg
|
|
720
|
+
});
|
|
721
|
+
if (!cubics.length) return { closed: true, anchors: l.simple.map(([x, y]) => ({ x, y })) };
|
|
722
|
+
const anchors = cubics.map((c, k) => {
|
|
723
|
+
const prev = cubics[(k - 1 + cubics.length) % cubics.length];
|
|
724
|
+
return {
|
|
725
|
+
x: c.p0[0],
|
|
726
|
+
y: c.p0[1],
|
|
727
|
+
in: { x: prev.p2[0], y: prev.p2[1] },
|
|
728
|
+
out: { x: c.p1[0], y: c.p1[1] }
|
|
729
|
+
};
|
|
730
|
+
});
|
|
731
|
+
return { closed: true, anchors };
|
|
732
|
+
}
|
|
733
|
+
function traceField(field, w, h, threshold, o) {
|
|
734
|
+
const tolerance = o.tolerance ?? 1;
|
|
735
|
+
const roundness = Math.min(1, Math.max(0, o.cornerRoundness ?? 0.5));
|
|
736
|
+
return loopsOf(field, w, h, threshold, tolerance, o.minArea ?? 10).map((l) => loopSubpath(l, tolerance, roundness));
|
|
737
|
+
}
|
|
738
|
+
function quantizeVectorTraceColors(pixels, count) {
|
|
739
|
+
const n2 = pixels.width * pixels.height;
|
|
740
|
+
const d = pixels.data;
|
|
741
|
+
const opaque = [];
|
|
742
|
+
for (let i = 0; i < n2; i++) if (d[i * 4 + 3] >= 128) opaque.push(i);
|
|
743
|
+
const boxes = opaque.length ? [opaque] : [];
|
|
744
|
+
const range = (box, c) => {
|
|
745
|
+
let lo = 255;
|
|
746
|
+
let hi = 0;
|
|
747
|
+
for (const i of box) {
|
|
748
|
+
const v = d[i * 4 + c];
|
|
749
|
+
if (v < lo) lo = v;
|
|
750
|
+
if (v > hi) hi = v;
|
|
751
|
+
}
|
|
752
|
+
return hi - lo;
|
|
686
753
|
};
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
754
|
+
while (boxes.length < count) {
|
|
755
|
+
let best = -1;
|
|
756
|
+
let bestRange = 0;
|
|
757
|
+
let bestC = 0;
|
|
758
|
+
boxes.forEach((b, k) => {
|
|
759
|
+
if (b.length < 2) return;
|
|
760
|
+
for (let c = 0; c < 3; c++) {
|
|
761
|
+
const r = range(b, c);
|
|
762
|
+
if (r > bestRange) {
|
|
763
|
+
bestRange = r;
|
|
764
|
+
best = k;
|
|
765
|
+
bestC = c;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
if (best < 0) break;
|
|
770
|
+
const box = boxes[best].slice().sort((a, b) => d[a * 4 + bestC] - d[b * 4 + bestC]);
|
|
771
|
+
const key = (k) => d[box[k] * 4 + bestC];
|
|
772
|
+
const v = key(box.length >> 1);
|
|
773
|
+
let mid = box.findIndex((_, k) => key(k) >= v);
|
|
774
|
+
if (mid <= 0) mid = box.findIndex((_, k) => key(k) > v);
|
|
775
|
+
boxes.splice(best, 1, box.slice(0, mid), box.slice(mid));
|
|
693
776
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
777
|
+
const palette = boxes.map((b) => {
|
|
778
|
+
const s = [0, 0, 0];
|
|
779
|
+
for (const i of b) for (let c = 0; c < 3; c++) s[c] += d[i * 4 + c];
|
|
780
|
+
return [Math.round(s[0] / b.length), Math.round(s[1] / b.length), Math.round(s[2] / b.length)];
|
|
781
|
+
});
|
|
782
|
+
const index = new Int16Array(n2).fill(-1);
|
|
783
|
+
const counts = palette.map(() => 0);
|
|
784
|
+
for (const i of opaque) {
|
|
785
|
+
let best = 0;
|
|
786
|
+
let bestD = Infinity;
|
|
787
|
+
palette.forEach((p, k) => {
|
|
788
|
+
const dd = (d[i * 4] - p[0]) ** 2 + (d[i * 4 + 1] - p[1]) ** 2 + (d[i * 4 + 2] - p[2]) ** 2;
|
|
789
|
+
if (dd < bestD) {
|
|
790
|
+
bestD = dd;
|
|
791
|
+
best = k;
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
index[i] = best;
|
|
795
|
+
counts[best]++;
|
|
796
|
+
}
|
|
797
|
+
return { palette, index, counts };
|
|
698
798
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
799
|
+
var hex = (c) => `#${c.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("")}`;
|
|
800
|
+
function traceVectorImage(pixels, options = {}) {
|
|
801
|
+
const { width: w, height: h, data } = pixels;
|
|
802
|
+
if (!(w > 0 && h > 0) || data.length < w * h * 4) throw new Error("The image has no pixels (or fewer than width \xD7 height \xD7 4 values).");
|
|
803
|
+
if (w * h > VECTOR_TRACE_MAX_PIXELS) throw new Error(`The image has ${w * h} pixels; scale it down to at most ${VECTOR_TRACE_MAX_PIXELS} to trace it.`);
|
|
804
|
+
const n2 = w * h;
|
|
805
|
+
if ((options.mode ?? "threshold") === "threshold") {
|
|
806
|
+
const field = new Float32Array(n2);
|
|
807
|
+
const byAlpha = options.channel === "alpha";
|
|
808
|
+
for (let i = 0; i < n2; i++) {
|
|
809
|
+
const a = data[i * 4 + 3] / 255;
|
|
810
|
+
field[i] = byAlpha ? a : a * (1 - (0.2126 * data[i * 4] + 0.7152 * data[i * 4 + 1] + 0.0722 * data[i * 4 + 2]) / 255);
|
|
811
|
+
}
|
|
812
|
+
const subpaths = traceField(field, w, h, Math.min(1, Math.max(0, options.threshold ?? 0.5)), options);
|
|
813
|
+
return subpaths.length ? [
|
|
814
|
+
{
|
|
815
|
+
id: "trace-1",
|
|
816
|
+
type: "path",
|
|
817
|
+
name: "Trace",
|
|
818
|
+
fill: options.color ?? "#000000",
|
|
819
|
+
stroke: null,
|
|
820
|
+
fillRule: "evenodd",
|
|
821
|
+
subpaths
|
|
822
|
+
}
|
|
823
|
+
] : [];
|
|
709
824
|
}
|
|
710
|
-
const
|
|
711
|
-
const
|
|
712
|
-
const
|
|
713
|
-
const
|
|
714
|
-
|
|
825
|
+
const count = Math.min(32, Math.max(2, Math.round(options.colors ?? 6)));
|
|
826
|
+
const q = quantizeVectorTraceColors(pixels, count);
|
|
827
|
+
const order = q.palette.map((_, k) => k).filter((k) => q.counts[k] > 0).sort((a, b) => q.counts[b] - q.counts[a]);
|
|
828
|
+
const rank = new Map(order.map((k, r) => [k, r]));
|
|
829
|
+
const out = [];
|
|
830
|
+
order.forEach((k, r) => {
|
|
831
|
+
const field = new Uint8Array(n2);
|
|
832
|
+
for (let i = 0; i < n2; i++) {
|
|
833
|
+
const ix = q.index[i];
|
|
834
|
+
field[i] = ix >= 0 && rank.get(ix) >= r ? 1 : 0;
|
|
835
|
+
}
|
|
836
|
+
const subpaths = traceField(field, w, h, 0.5, options);
|
|
837
|
+
if (subpaths.length)
|
|
838
|
+
out.push({
|
|
839
|
+
id: `trace-${out.length + 1}`,
|
|
840
|
+
type: "path",
|
|
841
|
+
name: hex(q.palette[k]),
|
|
842
|
+
fill: hex(q.palette[k]),
|
|
843
|
+
stroke: null,
|
|
844
|
+
fillRule: "evenodd",
|
|
845
|
+
subpaths
|
|
846
|
+
});
|
|
847
|
+
});
|
|
848
|
+
return out;
|
|
715
849
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
850
|
+
|
|
851
|
+
// src/vector/stylize.ts
|
|
852
|
+
var VECTOR_STYLIZE_STYLES = ["mosaic", "halftone", "lowPoly", "hatch"];
|
|
853
|
+
var VECTOR_STYLIZE_LABELS = { mosaic: "Mosaic", halftone: "Halftone", lowPoly: "Low Poly", hatch: "Engraving" };
|
|
854
|
+
var VECTOR_STYLIZE_MAX_SHAPES = 6e4;
|
|
855
|
+
function sampleBox(px, cx, cy, size) {
|
|
856
|
+
const { width: w, height: h, data: d } = px;
|
|
857
|
+
const n2 = Math.max(1, Math.min(5, Math.round(size)));
|
|
858
|
+
const s = [0, 0, 0, 0];
|
|
859
|
+
let count = 0;
|
|
860
|
+
for (let j = 0; j < n2; j++) {
|
|
861
|
+
for (let i = 0; i < n2; i++) {
|
|
862
|
+
const x = Math.floor(cx - size / 2 + (i + 0.5) * size / n2);
|
|
863
|
+
const y = Math.floor(cy - size / 2 + (j + 0.5) * size / n2);
|
|
864
|
+
if (x < 0 || y < 0 || x >= w || y >= h) continue;
|
|
865
|
+
const k = (y * w + x) * 4;
|
|
866
|
+
const a = d[k + 3];
|
|
867
|
+
s[0] += d[k] * a;
|
|
868
|
+
s[1] += d[k + 1] * a;
|
|
869
|
+
s[2] += d[k + 2] * a;
|
|
870
|
+
s[3] += a;
|
|
871
|
+
count++;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (!count || s[3] <= 0) return [0, 0, 0, 0];
|
|
875
|
+
return [s[0] / s[3], s[1] / s[3], s[2] / s[3], s[3] / count];
|
|
876
|
+
}
|
|
877
|
+
var darkness = (c) => (1 - (0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]) / 255) * (c[3] / 255);
|
|
878
|
+
var hex2 = (c) => `#${c.slice(0, 3).map((v) => Math.round(Math.min(255, Math.max(0, v))).toString(16).padStart(2, "0")).join("")}`;
|
|
879
|
+
function paletteOf(colors, count) {
|
|
880
|
+
const data = new Uint8ClampedArray(colors.length * 4);
|
|
881
|
+
colors.forEach((c, i) => {
|
|
882
|
+
data[i * 4] = c[0];
|
|
883
|
+
data[i * 4 + 1] = c[1];
|
|
884
|
+
data[i * 4 + 2] = c[2];
|
|
885
|
+
data[i * 4 + 3] = c[3];
|
|
886
|
+
});
|
|
887
|
+
const q = quantizeVectorTraceColors({ width: colors.length, height: 1, data }, Math.min(32, Math.max(2, Math.round(count))));
|
|
888
|
+
return { palette: q.palette.map(hex2), index: q.index };
|
|
889
|
+
}
|
|
890
|
+
function byColour(shapes, palette, index, name, extra = {}) {
|
|
891
|
+
const groups = palette.map(() => []);
|
|
892
|
+
shapes.forEach((s, i) => {
|
|
893
|
+
if (index[i] >= 0) groups[index[i]].push(...s);
|
|
894
|
+
});
|
|
895
|
+
return groups.map((subpaths, k) => ({ subpaths, k })).filter((g) => g.subpaths.length).sort((a, b) => b.subpaths.length - a.subpaths.length).map((g, n2) => ({ id: `stylize-${n2}`, type: "path", name: `${name} ${palette[g.k]}`, fill: palette[g.k], stroke: null, subpaths: g.subpaths, ...extra }));
|
|
896
|
+
}
|
|
897
|
+
function check(px, cell) {
|
|
898
|
+
const { width: w, height: h, data } = px;
|
|
899
|
+
if (!(w > 0 && h > 0) || data.length < w * h * 4) throw new Error("The image has no pixels (or fewer than width \xD7 height \xD7 4 values).");
|
|
900
|
+
if (w * h > VECTOR_TRACE_MAX_PIXELS) throw new Error(`The image has ${w * h} pixels; scale it down to at most ${VECTOR_TRACE_MAX_PIXELS}.`);
|
|
901
|
+
if (!(cell >= 1)) throw new Error("cell must be at least 1 pixel.");
|
|
902
|
+
}
|
|
903
|
+
function tooMany(count) {
|
|
904
|
+
if (count > VECTOR_STYLIZE_MAX_SHAPES) throw new Error(`That would draw about ${count} shapes (at most ${VECTOR_STYLIZE_MAX_SHAPES}): use a larger cell.`);
|
|
905
|
+
}
|
|
906
|
+
function turnedGrid(w, h, cell, angle) {
|
|
907
|
+
const a = angle * Math.PI / 180;
|
|
908
|
+
const c = Math.cos(a);
|
|
909
|
+
const s = Math.sin(a);
|
|
910
|
+
const cx = w / 2;
|
|
911
|
+
const cy = h / 2;
|
|
912
|
+
const reach = Math.ceil(Math.hypot(w, h) / 2 / cell) + 1;
|
|
913
|
+
tooMany((2 * reach + 1) ** 2 * 0.7);
|
|
914
|
+
const out = [];
|
|
915
|
+
for (let j = -reach; j <= reach; j++) {
|
|
916
|
+
for (let i = -reach; i <= reach; i++) {
|
|
917
|
+
const x = cx + (i * c - j * s) * cell;
|
|
918
|
+
const y = cy + (i * s + j * c) * cell;
|
|
919
|
+
if (x < -cell || y < -cell || x > w + cell || y > h + cell) continue;
|
|
920
|
+
out.push({ x, y, row: j, col: i });
|
|
725
921
|
}
|
|
726
922
|
}
|
|
727
|
-
|
|
728
|
-
const spine = { closed: false, anchors: centres.map((c) => ({ x: c[0], y: c[1] })) };
|
|
729
|
-
const stops = [0];
|
|
730
|
-
for (let i = 1; i < centres.length; i++) stops.push(stops[i - 1] + Math.hypot(centres[i][0] - centres[i - 1][0], centres[i][1] - centres[i - 1][1]));
|
|
731
|
-
const rail = vectorBlendRail(spine) ?? { pts: centres, cum: stops, angles: centres.map(() => 0), total: stops[stops.length - 1], closed: false };
|
|
732
|
-
return { spine, rail, stops, replaced: false };
|
|
923
|
+
return out;
|
|
733
924
|
}
|
|
734
|
-
function
|
|
735
|
-
const
|
|
736
|
-
|
|
925
|
+
function vectorMosaic(px, o = {}) {
|
|
926
|
+
const cell = o.cell ?? 12;
|
|
927
|
+
check(px, cell);
|
|
928
|
+
const cols = Math.ceil(px.width / cell);
|
|
929
|
+
const rows = Math.ceil(px.height / cell);
|
|
930
|
+
tooMany(cols * rows);
|
|
931
|
+
const gap = Math.min(0.9, Math.max(0, o.gap ?? 0.1)) * cell;
|
|
932
|
+
const colors = [];
|
|
933
|
+
const shapes = [];
|
|
934
|
+
for (let r = 0; r < rows; r++) {
|
|
935
|
+
for (let c = 0; c < cols; c++) {
|
|
936
|
+
const color = sampleBox(px, (c + 0.5) * cell, (r + 0.5) * cell, cell);
|
|
937
|
+
if (color[3] < 128) continue;
|
|
938
|
+
const box = { x: c * cell + gap / 2, y: r * cell + gap / 2, width: cell - gap, height: cell - gap };
|
|
939
|
+
colors.push(color);
|
|
940
|
+
shapes.push([o.shape === "circle" ? ellipseSubpath(box) : rectSubpath(box)]);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (!shapes.length) return [];
|
|
944
|
+
const { palette, index } = paletteOf(colors, o.colors ?? 12);
|
|
945
|
+
return byColour(shapes, palette, index, "Mosaic");
|
|
946
|
+
}
|
|
947
|
+
function vectorHalftone(px, o = {}) {
|
|
948
|
+
const cell = o.cell ?? 8;
|
|
949
|
+
check(px, cell);
|
|
950
|
+
const colors = [];
|
|
951
|
+
const shapes = [];
|
|
952
|
+
for (const p of turnedGrid(px.width, px.height, cell, o.angle ?? 45)) {
|
|
953
|
+
if (p.x < 0 || p.y < 0 || p.x >= px.width || p.y >= px.height) continue;
|
|
954
|
+
const color = sampleBox(px, p.x, p.y, cell);
|
|
955
|
+
const r = cell / Math.SQRT2 * Math.sqrt(darkness(color));
|
|
956
|
+
if (r < cell * 0.04) continue;
|
|
957
|
+
colors.push(color);
|
|
958
|
+
shapes.push([ellipseSubpath({ x: p.x - r, y: p.y - r, width: 2 * r, height: 2 * r })]);
|
|
959
|
+
}
|
|
960
|
+
if (!shapes.length) return [];
|
|
961
|
+
if (o.color === null) {
|
|
962
|
+
const { palette, index } = paletteOf(colors, o.colors ?? 12);
|
|
963
|
+
return byColour(shapes, palette, index, "Halftone");
|
|
964
|
+
}
|
|
965
|
+
return [{ id: "stylize-0", type: "path", name: "Halftone", fill: o.color ?? "#000000", stroke: null, subpaths: shapes.flat() }];
|
|
966
|
+
}
|
|
967
|
+
function hash01(a, b, seed) {
|
|
968
|
+
let h = Math.imul(a ^ 2654435769, 2246822507) ^ Math.imul(b ^ 3266489909, 668265263) ^ Math.imul(seed, 374761393);
|
|
969
|
+
h ^= h >>> 15;
|
|
970
|
+
h = Math.imul(h, 739982445);
|
|
971
|
+
h ^= h >>> 12;
|
|
972
|
+
return (h >>> 0) / 4294967295;
|
|
973
|
+
}
|
|
974
|
+
function vectorLowPoly(px, o = {}) {
|
|
975
|
+
const cell = o.cell ?? 24;
|
|
976
|
+
check(px, cell);
|
|
977
|
+
const cols = Math.max(1, Math.round(px.width / cell));
|
|
978
|
+
const rows = Math.max(1, Math.round(px.height / cell));
|
|
979
|
+
tooMany(cols * rows * 2);
|
|
980
|
+
const sx = px.width / cols;
|
|
981
|
+
const sy = px.height / rows;
|
|
982
|
+
const jitter = Math.min(0.5, Math.max(0, o.jitter ?? 0.35));
|
|
983
|
+
const seed = Math.round(o.seed ?? 1);
|
|
984
|
+
const pt2 = (c, r) => {
|
|
985
|
+
const jx = c === 0 || c === cols ? 0 : (hash01(c, r, seed) * 2 - 1) * jitter * sx;
|
|
986
|
+
const jy = r === 0 || r === rows ? 0 : (hash01(c, r, seed + 7919) * 2 - 1) * jitter * sy;
|
|
987
|
+
return [c * sx + jx, r * sy + jy];
|
|
988
|
+
};
|
|
989
|
+
const colors = [];
|
|
990
|
+
const shapes = [];
|
|
991
|
+
const tri = (a, b, c) => {
|
|
992
|
+
const cx = (a[0] + b[0] + c[0]) / 3;
|
|
993
|
+
const cy = (a[1] + b[1] + c[1]) / 3;
|
|
994
|
+
const color = sampleBox(px, cx, cy, Math.min(sx, sy) * 0.6);
|
|
995
|
+
if (color[3] < 128) return;
|
|
996
|
+
colors.push(color);
|
|
997
|
+
shapes.push([{ closed: true, anchors: [a, b, c].map(([x, y]) => ({ x, y })) }]);
|
|
998
|
+
};
|
|
999
|
+
for (let r = 0; r < rows; r++) {
|
|
1000
|
+
for (let c = 0; c < cols; c++) {
|
|
1001
|
+
const p00 = pt2(c, r);
|
|
1002
|
+
const p10 = pt2(c + 1, r);
|
|
1003
|
+
const p01 = pt2(c, r + 1);
|
|
1004
|
+
const p11 = pt2(c + 1, r + 1);
|
|
1005
|
+
if ((r + c) % 2) {
|
|
1006
|
+
tri(p00, p10, p11);
|
|
1007
|
+
tri(p00, p11, p01);
|
|
1008
|
+
} else {
|
|
1009
|
+
tri(p00, p10, p01);
|
|
1010
|
+
tri(p10, p11, p01);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (!shapes.length) return [];
|
|
1015
|
+
const { palette, index } = paletteOf(colors, o.colors ?? 16);
|
|
1016
|
+
return byColour(shapes, palette, index, "Low Poly").map((n2) => ({ ...n2, stroke: n2.fill, strokeWidth: 0.5, strokeLinejoin: "round" }));
|
|
1017
|
+
}
|
|
1018
|
+
function vectorHatch(px, o = {}) {
|
|
1019
|
+
const cell = o.cell ?? 6;
|
|
1020
|
+
check(px, cell);
|
|
1021
|
+
const a = (o.angle ?? 45) * Math.PI / 180;
|
|
1022
|
+
const dir = { x: Math.cos(a), y: Math.sin(a) };
|
|
1023
|
+
const nrm = { x: -dir.y, y: dir.x };
|
|
1024
|
+
const pts = turnedGrid(px.width, px.height, cell, o.angle ?? 45);
|
|
1025
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1026
|
+
for (const p of pts) {
|
|
1027
|
+
const list = rows.get(p.row);
|
|
1028
|
+
if (list) list.push(p);
|
|
1029
|
+
else rows.set(p.row, [p]);
|
|
1030
|
+
}
|
|
1031
|
+
const subpaths = [];
|
|
1032
|
+
const min = cell * 0.04;
|
|
1033
|
+
for (const list of rows.values()) {
|
|
1034
|
+
list.sort((p, q) => p.col - q.col);
|
|
1035
|
+
let run = [];
|
|
1036
|
+
const flush = () => {
|
|
1037
|
+
if (run.length >= 2) {
|
|
1038
|
+
const top = run.map((p) => ({ x: p.x + nrm.x * p.t, y: p.y + nrm.y * p.t }));
|
|
1039
|
+
const bottom = run.map((p) => ({ x: p.x - nrm.x * p.t, y: p.y - nrm.y * p.t })).reverse();
|
|
1040
|
+
subpaths.push({ closed: true, anchors: [...top, ...bottom] });
|
|
1041
|
+
}
|
|
1042
|
+
run = [];
|
|
1043
|
+
};
|
|
1044
|
+
for (const p of list) {
|
|
1045
|
+
const inside = p.x >= 0 && p.y >= 0 && p.x < px.width && p.y < px.height;
|
|
1046
|
+
const t = inside ? cell * 0.45 * darkness(sampleBox(px, p.x, p.y, cell)) : 0;
|
|
1047
|
+
if (t < min) {
|
|
1048
|
+
if (run.length) run.push({ x: p.x, y: p.y, t: 0 });
|
|
1049
|
+
flush();
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
if (!run.length) run.push({ x: p.x - dir.x * cell, y: p.y - dir.y * cell, t: 0 });
|
|
1053
|
+
run.push({ x: p.x, y: p.y, t });
|
|
1054
|
+
}
|
|
1055
|
+
flush();
|
|
1056
|
+
}
|
|
1057
|
+
if (!subpaths.length) return [];
|
|
1058
|
+
tooMany(subpaths.length);
|
|
1059
|
+
return [{ id: "stylize-0", type: "path", name: "Hatch", fill: o.color ?? "#000000", stroke: null, subpaths }];
|
|
737
1060
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1061
|
+
function stylizeVectorImage(px, style, options = {}) {
|
|
1062
|
+
switch (style) {
|
|
1063
|
+
case "mosaic":
|
|
1064
|
+
return vectorMosaic(px, options);
|
|
1065
|
+
case "halftone":
|
|
1066
|
+
return vectorHalftone(px, options);
|
|
1067
|
+
case "lowPoly":
|
|
1068
|
+
return vectorLowPoly(px, options);
|
|
1069
|
+
case "hatch":
|
|
1070
|
+
return vectorHatch(px, options);
|
|
1071
|
+
}
|
|
749
1072
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
1073
|
+
|
|
1074
|
+
// src/vector/distortBrush.ts
|
|
1075
|
+
var VECTOR_DISTORT_BRUSH_MODES = ["smudge", "twirl", "attract", "repel", "roughen"];
|
|
1076
|
+
function vectorDistortFalloff(d, r) {
|
|
1077
|
+
if (!(r > 0) || d >= r) return 0;
|
|
1078
|
+
const u = d / r;
|
|
1079
|
+
const k = 1 - u * u;
|
|
1080
|
+
return k * k;
|
|
1081
|
+
}
|
|
1082
|
+
function vectorDistortDabMap(mode, dab) {
|
|
1083
|
+
const s = Math.min(1, Math.max(0, dab.strength));
|
|
1084
|
+
return (p) => {
|
|
1085
|
+
const dx = p.x - dab.x;
|
|
1086
|
+
const dy = p.y - dab.y;
|
|
1087
|
+
const f = vectorDistortFalloff(Math.hypot(dx, dy), dab.radius) * s;
|
|
1088
|
+
if (f <= 0) return p;
|
|
1089
|
+
switch (mode) {
|
|
1090
|
+
case "smudge":
|
|
1091
|
+
return { x: p.x + (dab.dx ?? 0) * f, y: p.y + (dab.dy ?? 0) * f };
|
|
1092
|
+
case "twirl": {
|
|
1093
|
+
const a = (dab.angle ?? 15) * Math.PI / 180 * f;
|
|
1094
|
+
const c = Math.cos(a);
|
|
1095
|
+
const sn = Math.sin(a);
|
|
1096
|
+
return { x: dab.x + dx * c - dy * sn, y: dab.y + dx * sn + dy * c };
|
|
1097
|
+
}
|
|
1098
|
+
case "attract":
|
|
1099
|
+
return { x: p.x - dx * f * 0.5, y: p.y - dy * f * 0.5 };
|
|
1100
|
+
case "repel":
|
|
1101
|
+
return { x: p.x + dx * f * 0.5, y: p.y + dy * f * 0.5 };
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
754
1104
|
}
|
|
755
|
-
function
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
1105
|
+
function noise(x, y, seed) {
|
|
1106
|
+
let h = Math.imul(Math.round(x * 16) ^ 2654435769, 2246822507) ^ Math.imul(Math.round(y * 16) ^ 3266489909, 668265263) ^ Math.imul(seed | 0, 374761393);
|
|
1107
|
+
h ^= h >>> 15;
|
|
1108
|
+
h = Math.imul(h, 739982445);
|
|
1109
|
+
h ^= h >>> 12;
|
|
1110
|
+
return (h >>> 0) / 4294967295 * 2 - 1;
|
|
1111
|
+
}
|
|
1112
|
+
var reaches = (b, dab) => dab.x + dab.radius >= b.minX && dab.x - dab.radius <= b.maxX && dab.y + dab.radius >= b.minY && dab.y - dab.radius <= b.maxY;
|
|
1113
|
+
function roughen(path, dabs) {
|
|
1114
|
+
const step = Math.max(0.5, Math.min(...dabs.map((d) => d.radius)) / 6);
|
|
1115
|
+
const contours = pathToContours(path).map((c) => {
|
|
1116
|
+
const b = bezierPathBounds(contoursToPath([c]));
|
|
1117
|
+
if (!b || !dabs.some((d) => reaches(b, d))) return c;
|
|
1118
|
+
const ring = flattenChain(closeContour(c, c.closed), Math.min(0.25, step / 8));
|
|
1119
|
+
const pts = [];
|
|
1120
|
+
for (let i = 0; i < ring.length; i++) {
|
|
1121
|
+
const a = ring[i];
|
|
1122
|
+
const nxt = ring[i + 1] ?? (c.closed ? ring[0] : null);
|
|
1123
|
+
pts.push(a);
|
|
1124
|
+
if (!nxt) continue;
|
|
1125
|
+
const n2 = Math.floor(Math.hypot(nxt[0] - a[0], nxt[1] - a[1]) / step);
|
|
1126
|
+
for (let k = 1; k < n2; k++) pts.push([a[0] + (nxt[0] - a[0]) * k / n2, a[1] + (nxt[1] - a[1]) * k / n2]);
|
|
1127
|
+
}
|
|
1128
|
+
const out = pts.map((p, i) => {
|
|
1129
|
+
const prev = pts[i - 1] ?? (c.closed ? pts[pts.length - 1] : p);
|
|
1130
|
+
const next = pts[i + 1] ?? (c.closed ? pts[0] : p);
|
|
1131
|
+
const tx = next[0] - prev[0];
|
|
1132
|
+
const ty = next[1] - prev[1];
|
|
1133
|
+
const tl = Math.hypot(tx, ty) || 1;
|
|
1134
|
+
let off = 0;
|
|
1135
|
+
for (const dab of dabs) {
|
|
1136
|
+
const f = vectorDistortFalloff(Math.hypot(p[0] - dab.x, p[1] - dab.y), dab.radius) * Math.min(1, Math.max(0, dab.strength));
|
|
1137
|
+
if (f > 0) off += noise(p[0], p[1], dab.seed ?? 1) * f * dab.radius * 0.25 * (i % 2 ? 1 : -1);
|
|
1138
|
+
}
|
|
1139
|
+
return [p[0] - ty / tl * off, p[1] + tx / tl * off];
|
|
1140
|
+
});
|
|
1141
|
+
const segments = [];
|
|
1142
|
+
for (let i = 0; i + 1 < out.length; i++) segments.push([out[i], out[i + 1]]);
|
|
1143
|
+
if (c.closed && out.length > 1) segments.push([out[out.length - 1], out[0]]);
|
|
1144
|
+
return { segments, closed: c.closed };
|
|
1145
|
+
});
|
|
1146
|
+
return contoursToPath(contours);
|
|
1147
|
+
}
|
|
1148
|
+
function distortVectorPath(path, mode, dabs) {
|
|
1149
|
+
const b0 = bezierPathBounds(path);
|
|
1150
|
+
const live = dabs.filter((d) => d.radius > 0 && d.strength > 0);
|
|
1151
|
+
if (!b0 || !live.length) return path;
|
|
1152
|
+
if (mode === "roughen") {
|
|
1153
|
+
const reaching2 = live.filter((d) => reaches(b0, d));
|
|
1154
|
+
return reaching2.length ? roughen(path, reaching2) : path;
|
|
1155
|
+
}
|
|
1156
|
+
let grow = 0;
|
|
1157
|
+
const reaching = [];
|
|
1158
|
+
for (const d of live) {
|
|
1159
|
+
const b = { minX: b0.minX - grow, minY: b0.minY - grow, maxX: b0.maxX + grow, maxY: b0.maxY + grow };
|
|
1160
|
+
if (reaches(b, d)) {
|
|
1161
|
+
reaching.push(d);
|
|
1162
|
+
grow += Math.hypot(d.dx ?? 0, d.dy ?? 0) + d.radius * 0.5;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (!reaching.length) return path;
|
|
1166
|
+
const maps = reaching.map((d) => vectorDistortDabMap(mode, d));
|
|
1167
|
+
return mapBezierPath(path, (p) => maps.reduce((q, m) => m(q), p), 0.05);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// src/vector/gradientTool.ts
|
|
1171
|
+
var VECTOR_GRADIENT_STOP_GAP = 12;
|
|
1172
|
+
var VECTOR_GRADIENT_MIDPOINT_GAP = 9;
|
|
1173
|
+
var VECTOR_GRADIENT_OFF_BAR = 24;
|
|
1174
|
+
var STOP_HIT = 6;
|
|
1175
|
+
var MIDPOINT_HIT = 5;
|
|
1176
|
+
var HANDLE_HIT = 6;
|
|
1177
|
+
var BAR_HIT = 5;
|
|
1178
|
+
var fmt = (n2) => Math.round(n2 * 100) / 100;
|
|
1179
|
+
var apply = (m, p) => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] });
|
|
1180
|
+
function vectorGradientTargetPath(doc, selection, target, layer = null) {
|
|
1181
|
+
for (const id of selection) {
|
|
1182
|
+
const node = findVectorNode(doc.nodes, id);
|
|
1183
|
+
if (!node) continue;
|
|
1184
|
+
const path = collectPathNodes(node).find((p) => isVectorGradient(vectorTargetPaint(p, target, layer)));
|
|
1185
|
+
if (path) return path;
|
|
1186
|
+
}
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
function getVectorGradientAnnotator(doc, selection, viewport, target, layer = null) {
|
|
1190
|
+
const path = vectorGradientTargetPath(doc, selection, target, layer);
|
|
1191
|
+
if (!path) return null;
|
|
1192
|
+
const gradient = vectorTargetPaint(path, target, layer);
|
|
1193
|
+
const world = vectorWorldMatrix(doc.nodes, path.id) ?? IDENTITY_MATRIX;
|
|
1194
|
+
const screen = multiplyMatrices(world, [viewport.zoom, 0, 0, viewport.zoom, viewport.x, viewport.y]);
|
|
1195
|
+
const bounds = vectorPathLocalBounds(path);
|
|
1196
|
+
const h = vectorGradientHandles(gradient, bounds, screen);
|
|
1197
|
+
const v = { x: h.end.x - h.start.x, y: h.end.y - h.start.y };
|
|
1198
|
+
const len2 = Math.hypot(v.x, v.y);
|
|
1199
|
+
const normal = len2 > 1e-9 ? { x: -v.y / len2, y: v.x / len2 } : { x: 0, y: 1 };
|
|
1200
|
+
const at = (t, gap) => ({ x: h.start.x + v.x * t + normal.x * gap, y: h.start.y + v.y * t + normal.y * gap });
|
|
1201
|
+
const sorted = sortedVectorGradientStops(gradient);
|
|
1202
|
+
const stops = sorted.map((s, index) => ({ ...s, index, ...at(s.offset, VECTOR_GRADIENT_STOP_GAP) }));
|
|
1203
|
+
const midpoints = sorted.slice(0, -1).flatMap((s, index) => {
|
|
1204
|
+
const pos = vectorGradientMidpointPosition(sorted, index);
|
|
1205
|
+
return pos === null || sorted[index + 1].offset - s.offset < 1e-6 ? [] : [{ index, midpoint: s.midpoint ?? 0.5, ...at(pos, -VECTOR_GRADIENT_MIDPOINT_GAP) }];
|
|
1206
|
+
});
|
|
1207
|
+
let extent = null;
|
|
1208
|
+
if (isCentredVectorGradient(gradient.type)) {
|
|
1209
|
+
const frame = multiplyMatrices(vectorGradientMatrix(gradient, bounds), screen);
|
|
1210
|
+
const pts = gradient.type === "diamond" ? [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 0, y: -1 }] : Array.from({ length: 48 }, (_, k) => ({ x: Math.cos(k * Math.PI / 24), y: Math.sin(k * Math.PI / 24) }));
|
|
1211
|
+
extent = `M${pts.map((p) => apply(frame, p)).map((p) => `${fmt(p.x)} ${fmt(p.y)}`).join("L")}Z`;
|
|
1212
|
+
}
|
|
1213
|
+
return { nodeId: path.id, target, gradient, bounds, world, start: h.start, end: h.end, aspect: h.aspect, focal: h.focal, normal, stops, midpoints, extent };
|
|
1214
|
+
}
|
|
1215
|
+
function vectorGradientBarPosition(a, p) {
|
|
1216
|
+
const v = { x: a.end.x - a.start.x, y: a.end.y - a.start.y };
|
|
1217
|
+
const l2 = v.x * v.x + v.y * v.y;
|
|
1218
|
+
const d = { x: p.x - a.start.x, y: p.y - a.start.y };
|
|
1219
|
+
if (l2 < 1e-12) return { offset: 0, across: Math.hypot(d.x, d.y) };
|
|
1220
|
+
return { offset: (d.x * v.x + d.y * v.y) / l2, across: d.x * a.normal.x + d.y * a.normal.y };
|
|
1221
|
+
}
|
|
1222
|
+
function hitVectorGradientAnnotator(a, p) {
|
|
1223
|
+
const near = (q, r) => !!q && Math.hypot(q.x - p.x, q.y - p.y) <= r;
|
|
1224
|
+
for (let i = a.stops.length - 1; i >= 0; i--) if (near(a.stops[i], STOP_HIT)) return { kind: "stop", index: a.stops[i].index };
|
|
1225
|
+
for (const m of a.midpoints) if (near(m, MIDPOINT_HIT)) return { kind: "midpoint", index: m.index };
|
|
1226
|
+
const centred = isCentredVectorGradient(a.gradient.type);
|
|
1227
|
+
if (near(a.end, HANDLE_HIT)) return { kind: "handle", handle: centred ? "radius" : "end" };
|
|
1228
|
+
if (a.focal && near(a.focal, HANDLE_HIT) && !near(a.start, 1)) return { kind: "handle", handle: "focal" };
|
|
1229
|
+
if (near(a.start, HANDLE_HIT)) return { kind: "handle", handle: "start" };
|
|
1230
|
+
if (near(a.aspect, HANDLE_HIT)) return { kind: "handle", handle: "aspect" };
|
|
1231
|
+
const { offset, across } = vectorGradientBarPosition(a, p);
|
|
1232
|
+
if (offset >= 0 && offset <= 1 && across >= -BAR_HIT && across <= VECTOR_GRADIENT_STOP_GAP + STOP_HIT) return { kind: "bar", offset };
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// src/vector/blendGroup.ts
|
|
1237
|
+
var DEFAULT_VECTOR_BLEND_STEPS = 5;
|
|
1238
|
+
var DEFAULT_VECTOR_BLEND_DISTANCE = 20;
|
|
1239
|
+
var VECTOR_BLEND_MAX_STEPS = 1e3;
|
|
1240
|
+
var VECTOR_BLEND_MAX_SMOOTH_STEPS = 256;
|
|
1241
|
+
function isVectorBlendGroup(node) {
|
|
1242
|
+
return !!node && node.type === "group" && !!node.blend;
|
|
1243
|
+
}
|
|
1244
|
+
function vectorBlendKeys(group) {
|
|
1245
|
+
return group.children.filter((c) => !c.blendStep);
|
|
1246
|
+
}
|
|
1247
|
+
function resolveVectorBlendOptions(options) {
|
|
1248
|
+
return {
|
|
1249
|
+
spacing: options?.spacing ?? "steps",
|
|
1250
|
+
steps: options?.steps ?? DEFAULT_VECTOR_BLEND_STEPS,
|
|
1251
|
+
distance: options?.distance ?? DEFAULT_VECTOR_BLEND_DISTANCE,
|
|
1252
|
+
orientToSpine: options?.orientToSpine ?? false,
|
|
1253
|
+
linear: options?.linear ?? false
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
function paintColors(p) {
|
|
1257
|
+
if (typeof p === "string") return [p];
|
|
1258
|
+
if (p && typeof p === "object") {
|
|
1259
|
+
if (p.type === "mesh") return p.colors;
|
|
1260
|
+
if (p.type === "freeform") return p.points.map((q) => q.color);
|
|
1261
|
+
return p.stops.map((s) => s.color);
|
|
1262
|
+
}
|
|
1263
|
+
return [];
|
|
1264
|
+
}
|
|
1265
|
+
function vectorBlendColorDistance(a, b) {
|
|
1266
|
+
const pa = collectPathNodes(a)[0];
|
|
1267
|
+
const pb = collectPathNodes(b)[0];
|
|
1268
|
+
if (!pa || !pb) return 0;
|
|
1269
|
+
let d = 0;
|
|
1270
|
+
for (const key of ["fill", "stroke"]) {
|
|
1271
|
+
const ca = paintColors(pa[key]);
|
|
1272
|
+
const cb = paintColors(pb[key]);
|
|
1273
|
+
ca.forEach((c, i) => {
|
|
1274
|
+
const other = cb[i] ?? cb[0];
|
|
1275
|
+
if (other === void 0) return;
|
|
1276
|
+
const x = hexToRgba(c);
|
|
1277
|
+
const y = hexToRgba(other);
|
|
1278
|
+
if (!x || !y) return;
|
|
1279
|
+
d = Math.max(d, Math.abs(x.r - y.r), Math.abs(x.g - y.g), Math.abs(x.b - y.b), Math.abs(x.a - y.a));
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
return d;
|
|
1283
|
+
}
|
|
1284
|
+
var centreOf = (n2) => {
|
|
1285
|
+
const b = vectorNodeBounds(n2);
|
|
1286
|
+
return b ? [b.x + b.width / 2, b.y + b.height / 2] : [0, 0];
|
|
1287
|
+
};
|
|
1288
|
+
function vectorBlendStepCount(a, b, options, length) {
|
|
1289
|
+
const o = resolveVectorBlendOptions(options);
|
|
1290
|
+
if (o.spacing === "distance") {
|
|
1291
|
+
const d = Math.max(0.01, o.distance);
|
|
1292
|
+
return Math.min(VECTOR_BLEND_MAX_STEPS, Math.max(0, Math.round(length / d) - 1));
|
|
1293
|
+
}
|
|
1294
|
+
if (o.spacing === "smoothColor") {
|
|
1295
|
+
const cd = vectorBlendColorDistance(a, b);
|
|
1296
|
+
if (cd > 1 / 255) return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(cd * 255 / 2)));
|
|
1297
|
+
const ba = vectorNodeBounds(a);
|
|
1298
|
+
const bb = vectorNodeBounds(b);
|
|
1299
|
+
const dist4 = ba && bb ? Math.max(Math.abs(ba.x - bb.x), Math.abs(ba.y - bb.y), Math.abs(ba.x + ba.width - bb.x - bb.width), Math.abs(ba.y + ba.height - bb.y - bb.height)) : length;
|
|
1300
|
+
return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(dist4 / 2)));
|
|
1301
|
+
}
|
|
1302
|
+
return Math.min(VECTOR_BLEND_MAX_STEPS, Math.max(1, Math.round(Number.isFinite(o.steps) ? o.steps : DEFAULT_VECTOR_BLEND_STEPS)));
|
|
1303
|
+
}
|
|
1304
|
+
function vectorBlendRail(sp) {
|
|
1305
|
+
const pts = [];
|
|
1306
|
+
const angles = [];
|
|
1307
|
+
const push = (p, tangent) => {
|
|
1308
|
+
const last = pts[pts.length - 1];
|
|
1309
|
+
if (last && Math.hypot(p[0] - last[0], p[1] - last[1]) < 1e-9) return;
|
|
1310
|
+
pts.push(p);
|
|
1311
|
+
angles.push(Math.atan2(tangent[1], tangent[0]));
|
|
1312
|
+
};
|
|
1313
|
+
const count = subpathSegmentCount(sp);
|
|
1314
|
+
for (let i = 0; i < count; i++) {
|
|
1315
|
+
const seg = subpathSegment(sp, i);
|
|
1316
|
+
const samples = seg.length === 2 ? 1 : 32;
|
|
1317
|
+
if (i === 0) push(seg[0], segmentTangent(seg, 0));
|
|
1318
|
+
for (let k = 1; k <= samples; k++) push(segmentPoint(seg, k / samples), segmentTangent(seg, k / samples));
|
|
1319
|
+
}
|
|
1320
|
+
if (pts.length < 2) return null;
|
|
1321
|
+
const cum = [0];
|
|
1322
|
+
for (let i = 1; i < pts.length; i++) cum.push(cum[i - 1] + Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]));
|
|
1323
|
+
return { pts, cum, angles, total: cum[cum.length - 1], closed: sp.closed };
|
|
1324
|
+
}
|
|
1325
|
+
function vectorBlendRailAt(rail, s) {
|
|
1326
|
+
let len2 = Number.isFinite(s) ? s : 0;
|
|
1327
|
+
if (rail.closed && rail.total > 0) len2 = (len2 % rail.total + rail.total) % rail.total;
|
|
1328
|
+
len2 = Math.min(rail.total, Math.max(0, len2));
|
|
1329
|
+
let lo = 0;
|
|
1330
|
+
let hi = rail.cum.length - 1;
|
|
1331
|
+
while (hi - lo > 1) {
|
|
1332
|
+
const mid = lo + hi >> 1;
|
|
1333
|
+
if (rail.cum[mid] <= len2) lo = mid;
|
|
1334
|
+
else hi = mid;
|
|
1335
|
+
}
|
|
1336
|
+
const span = rail.cum[hi] - rail.cum[lo];
|
|
1337
|
+
const u = span > 1e-12 ? (len2 - rail.cum[lo]) / span : 0;
|
|
1338
|
+
const a = rail.pts[lo];
|
|
1339
|
+
const b = rail.pts[hi];
|
|
1340
|
+
return { point: [a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u], angle: Math.atan2(b[1] - a[1], b[0] - a[0]) };
|
|
1341
|
+
}
|
|
1342
|
+
function vectorBlendSpine(group) {
|
|
1343
|
+
const keys = vectorBlendKeys(group);
|
|
1344
|
+
if (keys.length < 2) return null;
|
|
1345
|
+
const stored = group.blend?.spine;
|
|
1346
|
+
if (stored && stored.anchors.length >= 2) {
|
|
1347
|
+
const rail2 = vectorBlendRail(stored);
|
|
1348
|
+
if (rail2) {
|
|
1349
|
+
const end = rail2.closed ? rail2.total * (1 - 1 / keys.length) : rail2.total;
|
|
1350
|
+
return { spine: stored, rail: rail2, stops: keys.map((_, i) => end * i / (keys.length - 1)), replaced: true };
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
const centres = keys.map(centreOf);
|
|
1354
|
+
const spine = { closed: false, anchors: centres.map((c) => ({ x: c[0], y: c[1] })) };
|
|
1355
|
+
const stops = [0];
|
|
1356
|
+
for (let i = 1; i < centres.length; i++) stops.push(stops[i - 1] + Math.hypot(centres[i][0] - centres[i - 1][0], centres[i][1] - centres[i - 1][1]));
|
|
1357
|
+
const rail = vectorBlendRail(spine) ?? { pts: centres, cum: stops, angles: centres.map(() => 0), total: stops[stops.length - 1], closed: false };
|
|
1358
|
+
return { spine, rail, stops, replaced: false };
|
|
1359
|
+
}
|
|
1360
|
+
function vectorBlendKeyTargets(group) {
|
|
1361
|
+
const s = vectorBlendSpine(group);
|
|
1362
|
+
return s ? s.stops.map((stop) => vectorBlendRailAt(s.rail, stop).point) : [];
|
|
1363
|
+
}
|
|
1364
|
+
var morphCache = /* @__PURE__ */ new WeakMap();
|
|
1365
|
+
function morphFor(a, b, blend) {
|
|
1366
|
+
const starts = [blend.starts?.[a.id] ?? null, blend.starts?.[b.id] ?? null];
|
|
1367
|
+
const key = JSON.stringify([!!blend.linear, starts]);
|
|
1368
|
+
let inner = morphCache.get(a);
|
|
1369
|
+
if (!inner) morphCache.set(a, inner = /* @__PURE__ */ new WeakMap());
|
|
1370
|
+
let byKey = inner.get(b);
|
|
1371
|
+
if (!byKey) inner.set(b, byKey = /* @__PURE__ */ new Map());
|
|
1372
|
+
let morph = byKey.get(key);
|
|
1373
|
+
if (!morph) byKey.set(key, morph = createVectorMorph(a, b, { linear: blend.linear, starts }));
|
|
1374
|
+
return morph;
|
|
1375
|
+
}
|
|
1376
|
+
var wrapAngle = (a) => Math.atan2(Math.sin(a), Math.cos(a));
|
|
1377
|
+
function asStep(node, id) {
|
|
1378
|
+
const rename = (n2, nid) => n2.type === "group" ? { ...n2, id: nid, children: n2.children.map((c, i) => rename(c, `${nid}.${i}`)) } : { ...n2, id: nid };
|
|
1379
|
+
return { ...rename(node, id), blendStep: true, locked: true };
|
|
1380
|
+
}
|
|
1381
|
+
function generateVectorBlendChildren(group) {
|
|
1382
|
+
const keys = vectorBlendKeys(group);
|
|
1383
|
+
const blend = group.blend;
|
|
1384
|
+
if (!blend || keys.length < 2) return keys;
|
|
1385
|
+
const spine = vectorBlendSpine(group);
|
|
1386
|
+
if (!spine) return keys;
|
|
1387
|
+
const out = [];
|
|
1388
|
+
let counter = 0;
|
|
763
1389
|
keys.forEach((a, i) => {
|
|
764
1390
|
out.push(a);
|
|
765
1391
|
const b = keys[i + 1];
|
|
@@ -2279,6 +2905,12 @@ function lineStyle(node) {
|
|
|
2279
2905
|
function renderLayers(node, defs, idPrefix) {
|
|
2280
2906
|
const out = [];
|
|
2281
2907
|
for (const l of vectorPaintLayerNodes(node)) {
|
|
2908
|
+
if (l.kind === "stroke" && isVectorBrushstrokeLayer(l.layer) && l.layer.visible !== false && (l.layer.opacity ?? 1) > 0) {
|
|
2909
|
+
const images = vectorBrushstrokeImages(node, l.layer);
|
|
2910
|
+
const blend2 = vectorBlendModeCss(l.layer.blendMode);
|
|
2911
|
+
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 });
|
|
2912
|
+
continue;
|
|
2913
|
+
}
|
|
2282
2914
|
if (!l.drawn) continue;
|
|
2283
2915
|
const role = l.kind;
|
|
2284
2916
|
const paint = vectorPaintToSvg(l.layer.paint, `${idPrefix}${role}-${node.id}-${l.index}`, { node: l.node, ...role === "stroke" ? { role } : {} });
|
|
@@ -2777,6 +3409,11 @@ function getVectorOverlayRenderData(state) {
|
|
|
2777
3409
|
const d = pts.map((q, i) => `${i ? "L" : "M"}${fmt2(q.x)} ${fmt2(q.y)}`).join("") + (pts.length === 1 ? `L${fmt2(pts[0].x)} ${fmt2(pts[0].y)}` : "");
|
|
2778
3410
|
toolStroke = { tool: it.tool, d, width: fmt2(it.width * z) };
|
|
2779
3411
|
}
|
|
3412
|
+
let brush = null;
|
|
3413
|
+
if (it?.type === "brush") {
|
|
3414
|
+
const c = toScreen(state, it);
|
|
3415
|
+
brush = { x: fmt2(c.x), y: fmt2(c.y), r: fmt2(it.radius * z) };
|
|
3416
|
+
}
|
|
2780
3417
|
let pencilString = null;
|
|
2781
3418
|
if (it?.type === "draw" && it.string) {
|
|
2782
3419
|
const a = toScreen(state, it.string[0]);
|
|
@@ -2858,6 +3495,7 @@ function getVectorOverlayRenderData(state) {
|
|
|
2858
3495
|
guides,
|
|
2859
3496
|
status,
|
|
2860
3497
|
toolStroke,
|
|
3498
|
+
brush,
|
|
2861
3499
|
pencilString,
|
|
2862
3500
|
cutPoint,
|
|
2863
3501
|
cornerWidgets,
|
|
@@ -3183,8 +3821,8 @@ function vectorPaintToHex(paint) {
|
|
|
3183
3821
|
if (typeof paint !== "string") return null;
|
|
3184
3822
|
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(paint.trim());
|
|
3185
3823
|
if (!m) return null;
|
|
3186
|
-
const
|
|
3187
|
-
return `#${
|
|
3824
|
+
const hex3 = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
|
|
3825
|
+
return `#${hex3.toLowerCase()}`;
|
|
3188
3826
|
}
|
|
3189
3827
|
var VECTOR_GRADIENT_TYPE_LABELS = { linear: "Linear", radial: "Radial", conic: "Conic", reflected: "Reflected", diamond: "Diamond" };
|
|
3190
3828
|
var VECTOR_GRADIENT_SPREAD_LABELS = { pad: "Pad", reflect: "Reflect", repeat: "Repeat" };
|
|
@@ -3297,7 +3935,11 @@ function getVectorAppearancePanel(state) {
|
|
|
3297
3935
|
blendMode: l.blendMode ?? "normal",
|
|
3298
3936
|
visible: l.visible !== false,
|
|
3299
3937
|
...kind === "stroke" ? { strokeWidth: l.strokeWidth ?? 1, strokeAlign: l.strokeAlign ?? "center" } : {},
|
|
3300
|
-
...(l
|
|
3938
|
+
...isVectorBrushstrokeLayer(l) ? (() => {
|
|
3939
|
+
const b = l.brush;
|
|
3940
|
+
const preset = vectorBrushstrokeBrush(b.brushId);
|
|
3941
|
+
return { brushstroke: { brushId: b.brushId, name: preset.name, size: b.size ?? preset.tip.size } };
|
|
3942
|
+
})() : (l.kind ?? "paint") !== "paint" ? { unknownKind: String(l.kind) } : {},
|
|
3301
3943
|
editing: edit.target === kind && (edit.layer === null ? index === count - 1 : edit.layer === index)
|
|
3302
3944
|
});
|
|
3303
3945
|
const fills = first ? vectorFills(first) : [];
|
|
@@ -3450,6 +4092,7 @@ var Page = class {
|
|
|
3450
4092
|
content = [];
|
|
3451
4093
|
states = /* @__PURE__ */ new Map();
|
|
3452
4094
|
shadings = /* @__PURE__ */ new Map();
|
|
4095
|
+
images = /* @__PURE__ */ new Map();
|
|
3453
4096
|
/** A graphics state with this fill / stroke alpha and blend mode, by name. */
|
|
3454
4097
|
gs(alpha, strokeAlpha, blend) {
|
|
3455
4098
|
const bm = blend ? BLEND[blend] : void 0;
|
|
@@ -3463,6 +4106,36 @@ var Page = class {
|
|
|
3463
4106
|
}
|
|
3464
4107
|
return name;
|
|
3465
4108
|
}
|
|
4109
|
+
/** An image (a PNG data URL) as an XObject with its alpha as a soft mask, by name; null when unreadable. */
|
|
4110
|
+
image(href) {
|
|
4111
|
+
const hit = this.images.get(href);
|
|
4112
|
+
if (hit) return hit;
|
|
4113
|
+
const parsed = parseDataUrl(href);
|
|
4114
|
+
if (!parsed || !isPng(parsed.bytes)) return null;
|
|
4115
|
+
let img;
|
|
4116
|
+
try {
|
|
4117
|
+
img = decodePng(parsed.bytes);
|
|
4118
|
+
} catch {
|
|
4119
|
+
return null;
|
|
4120
|
+
}
|
|
4121
|
+
const px = img.width * img.height;
|
|
4122
|
+
const rgb = new Uint8Array(px * 3);
|
|
4123
|
+
const alpha = new Uint8Array(px);
|
|
4124
|
+
for (let i = 0; i < px; i++) {
|
|
4125
|
+
rgb[i * 3] = img.data[i * 4];
|
|
4126
|
+
rgb[i * 3 + 1] = img.data[i * 4 + 1];
|
|
4127
|
+
rgb[i * 3 + 2] = img.data[i * 4 + 2];
|
|
4128
|
+
alpha[i] = img.data[i * 4 + 3];
|
|
4129
|
+
}
|
|
4130
|
+
const w = this.writer;
|
|
4131
|
+
const dims = `/Type /XObject /Subtype /Image /Width ${img.width} /Height ${img.height} /BitsPerComponent 8`;
|
|
4132
|
+
const mask = w.add(w.stream(`${dims} /ColorSpace /DeviceGray`, alpha, true));
|
|
4133
|
+
const id = w.add(w.stream(`${dims} /ColorSpace /DeviceRGB /SMask ${mask} 0 R`, rgb, true));
|
|
4134
|
+
const name = `Im${this.images.size + 1}`;
|
|
4135
|
+
this.images.set(href, name);
|
|
4136
|
+
w.imageIds.set(`${w.pageIndex}:${name}`, id);
|
|
4137
|
+
return name;
|
|
4138
|
+
}
|
|
3466
4139
|
/** Sets the fill colour (or prepares a paint server) for `paint`: returns its alpha, or a def to fill with by clipping. */
|
|
3467
4140
|
paint(paint, stroking) {
|
|
3468
4141
|
if (!paint || paint === "none") return null;
|
|
@@ -3608,6 +4281,14 @@ var Page = class {
|
|
|
3608
4281
|
for (const l of layers) {
|
|
3609
4282
|
const a = alpha * l.opacity;
|
|
3610
4283
|
const blend = l.blendMode ?? p.blendMode;
|
|
4284
|
+
if (l.kind === "brushstroke") {
|
|
4285
|
+
for (const im of l.images ?? []) {
|
|
4286
|
+
const name = this.image(im.href);
|
|
4287
|
+
if (!name) continue;
|
|
4288
|
+
this.content.push("q", `/${this.gs(a, a, blend)} gs`, `${n(im.width)} 0 0 ${n(-im.height)} ${n(im.x)} ${n(im.y + im.height)} cm`, `/${name} Do`, "Q");
|
|
4289
|
+
}
|
|
4290
|
+
continue;
|
|
4291
|
+
}
|
|
3611
4292
|
if (l.kind === "fill") {
|
|
3612
4293
|
if (l.fill !== "none") this.fill(p.d, l.fill, a, evenodd, blend);
|
|
3613
4294
|
} else if (l.stroke !== "none" && l.strokeWidth > 0) {
|
|
@@ -3625,6 +4306,7 @@ var PdfWriter = class {
|
|
|
3625
4306
|
objects = [];
|
|
3626
4307
|
gsDicts = /* @__PURE__ */ new Map();
|
|
3627
4308
|
shadingDicts = /* @__PURE__ */ new Map();
|
|
4309
|
+
imageIds = /* @__PURE__ */ new Map();
|
|
3628
4310
|
pageIndex = 0;
|
|
3629
4311
|
add(body) {
|
|
3630
4312
|
this.objects.push(body);
|
|
@@ -3710,7 +4392,8 @@ function vectorDocumentToPdf(doc, options = {}) {
|
|
|
3710
4392
|
const content = w.add(w.stream("", new TextEncoder().encode(page.content.join("\n")), compress));
|
|
3711
4393
|
const gsEntries = [...page.states.values()].map((name) => `/${name} ${w.add(w.gsDicts.get(`${k}:${name}`))} 0 R`);
|
|
3712
4394
|
const shEntries = [...page.shadings.values()].map((name) => `/${name} ${w.add(w.shadingDicts.get(`${k}:${name}`))} 0 R`);
|
|
3713
|
-
const
|
|
4395
|
+
const imEntries = [...page.images.values()].map((name) => `/${name} ${w.imageIds.get(`${k}:${name}`)} 0 R`);
|
|
4396
|
+
const resources = `<< ${gsEntries.length ? `/ExtGState << ${gsEntries.join(" ")} >> ` : ""}${shEntries.length ? `/Shading << ${shEntries.join(" ")} >> ` : ""}${imEntries.length ? `/XObject << ${imEntries.join(" ")} >>` : ""} >>`;
|
|
3714
4397
|
pageIds.push(w.add(`<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${n(W)} ${n(H)}] /Resources ${resources} /Contents ${content} 0 R >>`));
|
|
3715
4398
|
});
|
|
3716
4399
|
w.set(catalog, `<< /Type /Catalog /Pages ${pagesId} 0 R >>`);
|
|
@@ -3895,6 +4578,7 @@ function pathItems(node, notes) {
|
|
|
3895
4578
|
function nodeGroup(node, notes) {
|
|
3896
4579
|
if (node.visible === false) return null;
|
|
3897
4580
|
if (node.filters?.length) notes.push(`${label(node)}: filters (shadows, glows, blurs) are not part of Lottie (left out).`);
|
|
4581
|
+
if (node.type === "path" && node.strokes?.some((l) => l.kind === "brushstroke" && l.visible !== false)) notes.push(`${label(node)}: brushstrokes are raster images, not part of Lottie (left out).`);
|
|
3898
4582
|
const m = nodeMatrix(node);
|
|
3899
4583
|
let items = [];
|
|
3900
4584
|
const live = (node.effects ?? []).some((e) => e.enabled !== false && (e.type === "wiggle" || e.type === "envelope" || e.type === "contour" || e.type === "blockShadow" || e.type === "extrude" || e.type === "bevel"));
|
|
@@ -4030,8 +4714,8 @@ function subpathOf(prop, frame) {
|
|
|
4030
4714
|
}
|
|
4031
4715
|
function cssColor(c) {
|
|
4032
4716
|
const k = c.slice(0, 3).some((v) => v > 1) ? 255 : 1;
|
|
4033
|
-
const
|
|
4034
|
-
return `#${
|
|
4717
|
+
const hex3 = (v) => Math.round(Math.min(1, Math.max(0, v / k)) * 255).toString(16).padStart(2, "0");
|
|
4718
|
+
return `#${hex3(c[0] ?? 0)}${hex3(c[1] ?? 0)}${hex3(c[2] ?? 0)}`;
|
|
4035
4719
|
}
|
|
4036
4720
|
function geometryOf(it, frame, notes) {
|
|
4037
4721
|
switch (it.ty) {
|
|
@@ -4271,338 +4955,51 @@ function parseVectorLottie(input, options = {}) {
|
|
|
4271
4955
|
if (!parent || seen.has(parent)) return own;
|
|
4272
4956
|
seen.add(l);
|
|
4273
4957
|
return multiplyMatrices(own, worldOf2(parent, seen));
|
|
4274
|
-
};
|
|
4275
|
-
const out = [];
|
|
4276
|
-
for (const l of layers) {
|
|
4277
|
-
const ty = Number(l.ty);
|
|
4278
|
-
if (l.hd === true || frame < Number(l.ip ?? -Infinity) || frame >= Number(l.op ?? Infinity)) continue;
|
|
4279
|
-
if (l.td === 1 || l.tt) notes.push(`Layer "${l.nm ?? ""}": track mattes are not imported.`);
|
|
4280
|
-
if (l.ef) notes.push(`Layer "${l.nm ?? ""}": layer effects are not imported.`);
|
|
4281
|
-
if (l.ks && JSON.stringify(l.ks).includes('"x":"')) notes.push("Expressions are not evaluated (their stored values are used).");
|
|
4282
|
-
let children = [];
|
|
4283
|
-
if (ty === 4) children = itemsToNodes(Array.isArray(l.shapes) ? l.shapes : [], { paints: [], modifiers: [] }, s);
|
|
4284
|
-
else if (ty === 1) {
|
|
4285
|
-
const w = Number(l.sw ?? 0);
|
|
4286
|
-
const h = Number(l.sh ?? 0);
|
|
4287
|
-
const c = typeof l.sc === "string" ? l.sc : "#000000";
|
|
4288
|
-
children = [{ id: s.next(), type: "path", name: "Solid", subpaths: [rectSubpath({ x: 0, y: 0, width: w, height: h })], fill: c, stroke: null }];
|
|
4289
|
-
} else if (ty === 0 && typeof l.refId === "string" && depth < 16) {
|
|
4290
|
-
const asset = assets.get(l.refId);
|
|
4291
|
-
const local = frame - Number(l.st ?? 0);
|
|
4292
|
-
if (asset && Array.isArray(asset.layers)) children = layersToNodes(asset.layers, local, depth + 1);
|
|
4293
|
-
} else if (ty !== 3) {
|
|
4294
|
-
notes.push(`Layer "${l.nm ?? ""}" (type ${ty}) is not supported (skipped).`);
|
|
4295
|
-
continue;
|
|
4296
|
-
}
|
|
4297
|
-
if (!children.length) continue;
|
|
4298
|
-
const m = worldOf2(l);
|
|
4299
|
-
const opacity = transformOf(l.ks, frame, notes).opacity;
|
|
4300
|
-
let group = { id: s.next(), type: "group", ...typeof l.nm === "string" ? { name: l.nm } : {}, children };
|
|
4301
|
-
group = transformVectorNode(group, m);
|
|
4302
|
-
if (opacity < 1) group = { ...group, opacity };
|
|
4303
|
-
const masks = (Array.isArray(l.masksProperties) ? l.masksProperties : []).filter((mk) => (mk.mode ?? "a") === "a" && !mk.inv);
|
|
4304
|
-
if (Array.isArray(l.masksProperties) && masks.length !== l.masksProperties.length) notes.push(`Layer "${l.nm ?? ""}": only additive masks are imported.`);
|
|
4305
|
-
if (masks.length) {
|
|
4306
|
-
const subpaths = masks.map((mk) => subpathOf(mk.pt, frame)).filter((x) => !!x);
|
|
4307
|
-
const clip = transformVectorNode({ id: s.next(), type: "path", name: "Mask", subpaths, fill: "#000000", stroke: null }, m);
|
|
4308
|
-
const { transform: _t, opacity: _o, ...inner } = group;
|
|
4309
|
-
group = { id: s.next(), type: "group", name: `${group.name ?? "Layer"} mask`, mask: { type: "clip" }, children: [clip, { ...inner, id: s.next() }], ...group.opacity !== void 0 ? { opacity: group.opacity } : {} };
|
|
4310
|
-
}
|
|
4311
|
-
out.push(group);
|
|
4312
|
-
}
|
|
4313
|
-
return out.reverse();
|
|
4314
|
-
};
|
|
4315
|
-
const nodes = layersToNodes(json.layers, s.frame, 0);
|
|
4316
|
-
const width = Number(json.w ?? 512);
|
|
4317
|
-
const height = Number(json.h ?? 512);
|
|
4318
|
-
return { document: { width, height, nodes }, notes: [...new Set(notes)] };
|
|
4319
|
-
}
|
|
4320
|
-
|
|
4321
|
-
// src/vector/trace.ts
|
|
4322
|
-
var VECTOR_TRACE_MAX_PIXELS = 4e6;
|
|
4323
|
-
function signedArea(p) {
|
|
4324
|
-
let a = 0;
|
|
4325
|
-
for (let i = 0; i < p.length; i++) {
|
|
4326
|
-
const [x0, y0] = p[i];
|
|
4327
|
-
const [x1, y1] = p[(i + 1) % p.length];
|
|
4328
|
-
a += x0 * y1 - x1 * y0;
|
|
4329
|
-
}
|
|
4330
|
-
return a / 2;
|
|
4331
|
-
}
|
|
4332
|
-
function pointInPoly(p, poly) {
|
|
4333
|
-
let inside = false;
|
|
4334
|
-
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
4335
|
-
const [xi, yi] = poly[i];
|
|
4336
|
-
const [xj, yj] = poly[j];
|
|
4337
|
-
if (yi > p[1] !== yj > p[1] && p[0] < (xj - xi) * (p[1] - yi) / (yj - yi) + xi) inside = !inside;
|
|
4338
|
-
}
|
|
4339
|
-
return inside;
|
|
4340
|
-
}
|
|
4341
|
-
function vectorTraceContours(field, w, h, threshold) {
|
|
4342
|
-
const gw = w + 2;
|
|
4343
|
-
const gh = h + 2;
|
|
4344
|
-
const val = (i, j) => i === 0 || j === 0 || i > w || j > h ? -Infinity : field[(j - 1) * w + (i - 1)];
|
|
4345
|
-
const inside = (i, j) => val(i, j) >= threshold;
|
|
4346
|
-
const pos = (i, j) => [i - 0.5, j - 0.5];
|
|
4347
|
-
const cross2 = (a, b) => {
|
|
4348
|
-
const va = val(a[0], a[1]);
|
|
4349
|
-
const vb = val(b[0], b[1]);
|
|
4350
|
-
let t;
|
|
4351
|
-
if (Number.isFinite(va) && Number.isFinite(vb) && Math.abs(vb - va) > 1e-12) t = Math.min(1, Math.max(0, (threshold - va) / (vb - va)));
|
|
4352
|
-
else if (Number.isFinite(va)) t = va >= threshold ? 0.5 : 0;
|
|
4353
|
-
else t = vb >= threshold ? 0.5 : 1;
|
|
4354
|
-
const pa = pos(a[0], a[1]);
|
|
4355
|
-
const pb = pos(b[0], b[1]);
|
|
4356
|
-
return [pa[0] + (pb[0] - pa[0]) * t, pa[1] + (pb[1] - pa[1]) * t];
|
|
4357
|
-
};
|
|
4358
|
-
const hid = (i, j) => 2 * (j * gw + i);
|
|
4359
|
-
const vid = (i, j) => 2 * (j * gw + i) + 1;
|
|
4360
|
-
const next = /* @__PURE__ */ new Map();
|
|
4361
|
-
const point = /* @__PURE__ */ new Map();
|
|
4362
|
-
for (let j = 0; j < gh - 1; j++) {
|
|
4363
|
-
for (let i = 0; i < gw - 1; i++) {
|
|
4364
|
-
const tl = inside(i, j);
|
|
4365
|
-
const tr = inside(i + 1, j);
|
|
4366
|
-
const br = inside(i + 1, j + 1);
|
|
4367
|
-
const bl = inside(i, j + 1);
|
|
4368
|
-
const code = +tl << 3 | +tr << 2 | +br << 1 | +bl;
|
|
4369
|
-
if (code === 0 || code === 15) continue;
|
|
4370
|
-
const top = hid(i, j);
|
|
4371
|
-
const bottom = hid(i, j + 1);
|
|
4372
|
-
const left = vid(i, j);
|
|
4373
|
-
const right = vid(i + 1, j);
|
|
4374
|
-
const seg = (a, b) => next.set(a, b);
|
|
4375
|
-
const mean = () => (val(i, j) + val(i + 1, j) + val(i + 1, j + 1) + val(i, j + 1)) / 4;
|
|
4376
|
-
switch (code) {
|
|
4377
|
-
case 1:
|
|
4378
|
-
seg(left, bottom);
|
|
4379
|
-
break;
|
|
4380
|
-
case 2:
|
|
4381
|
-
seg(bottom, right);
|
|
4382
|
-
break;
|
|
4383
|
-
case 3:
|
|
4384
|
-
seg(left, right);
|
|
4385
|
-
break;
|
|
4386
|
-
case 4:
|
|
4387
|
-
seg(right, top);
|
|
4388
|
-
break;
|
|
4389
|
-
case 5:
|
|
4390
|
-
if (mean() >= threshold) {
|
|
4391
|
-
seg(left, top);
|
|
4392
|
-
seg(right, bottom);
|
|
4393
|
-
} else {
|
|
4394
|
-
seg(left, bottom);
|
|
4395
|
-
seg(right, top);
|
|
4396
|
-
}
|
|
4397
|
-
break;
|
|
4398
|
-
case 6:
|
|
4399
|
-
seg(bottom, top);
|
|
4400
|
-
break;
|
|
4401
|
-
case 7:
|
|
4402
|
-
seg(left, top);
|
|
4403
|
-
break;
|
|
4404
|
-
case 8:
|
|
4405
|
-
seg(top, left);
|
|
4406
|
-
break;
|
|
4407
|
-
case 9:
|
|
4408
|
-
seg(top, bottom);
|
|
4409
|
-
break;
|
|
4410
|
-
case 10:
|
|
4411
|
-
if (mean() >= threshold) {
|
|
4412
|
-
seg(top, right);
|
|
4413
|
-
seg(bottom, left);
|
|
4414
|
-
} else {
|
|
4415
|
-
seg(top, left);
|
|
4416
|
-
seg(bottom, right);
|
|
4417
|
-
}
|
|
4418
|
-
break;
|
|
4419
|
-
case 11:
|
|
4420
|
-
seg(top, right);
|
|
4421
|
-
break;
|
|
4422
|
-
case 12:
|
|
4423
|
-
seg(right, left);
|
|
4424
|
-
break;
|
|
4425
|
-
case 13:
|
|
4426
|
-
seg(right, bottom);
|
|
4427
|
-
break;
|
|
4428
|
-
case 14:
|
|
4429
|
-
seg(bottom, left);
|
|
4430
|
-
break;
|
|
4431
|
-
}
|
|
4432
|
-
const edges = [
|
|
4433
|
-
[top, [i, j], [i + 1, j]],
|
|
4434
|
-
[bottom, [i, j + 1], [i + 1, j + 1]],
|
|
4435
|
-
[left, [i, j], [i, j + 1]],
|
|
4436
|
-
[right, [i + 1, j], [i + 1, j + 1]]
|
|
4437
|
-
];
|
|
4438
|
-
for (const [e, a, b] of edges) if (inside(a[0], a[1]) !== inside(b[0], b[1]) && !point.has(e)) point.set(e, cross2(a, b));
|
|
4439
|
-
}
|
|
4440
|
-
}
|
|
4441
|
-
const out = [];
|
|
4442
|
-
for (const start of [...next.keys()].sort((a, b) => a - b)) {
|
|
4443
|
-
if (!next.has(start)) continue;
|
|
4444
|
-
const poly = [];
|
|
4445
|
-
let e = start;
|
|
4446
|
-
for (; ; ) {
|
|
4447
|
-
const n2 = next.get(e);
|
|
4448
|
-
if (n2 === void 0) break;
|
|
4449
|
-
next.delete(e);
|
|
4450
|
-
const p = point.get(e);
|
|
4451
|
-
if (p) poly.push(p);
|
|
4452
|
-
e = n2;
|
|
4453
|
-
if (e === start) break;
|
|
4454
|
-
}
|
|
4455
|
-
if (poly.length >= 3) out.push(poly);
|
|
4456
|
-
}
|
|
4457
|
-
return out;
|
|
4458
|
-
}
|
|
4459
|
-
function loopsOf(field, w, h, threshold, tolerance, minArea) {
|
|
4460
|
-
const loops = [];
|
|
4461
|
-
for (const raw of vectorTraceContours(field, w, h, threshold)) {
|
|
4462
|
-
const area = signedArea(raw);
|
|
4463
|
-
if (Math.abs(area) < Math.max(0, minArea)) continue;
|
|
4464
|
-
const simple = simplifyRing(raw, Math.max(0.01, tolerance));
|
|
4465
|
-
if (simple.length < 3) continue;
|
|
4466
|
-
loops.push({ raw, simple, area, depth: 0 });
|
|
4467
|
-
}
|
|
4468
|
-
for (const l of loops) l.depth = loops.filter((o) => o !== l && Math.abs(o.area) > Math.abs(l.area) && pointInPoly(l.simple[0], o.simple)).length;
|
|
4469
|
-
return loops.sort((a, b) => a.depth - b.depth || Math.abs(b.area) - Math.abs(a.area));
|
|
4470
|
-
}
|
|
4471
|
-
function loopSubpath(l, tolerance, roundness) {
|
|
4472
|
-
if (roundness <= 0) return { closed: true, anchors: l.simple.map(([x, y]) => ({ x, y })) };
|
|
4473
|
-
const cornerAngleDeg = 25 + roundness * 125;
|
|
4474
|
-
const cubics = fitCubics(l.raw, Math.max(0.05, tolerance), {
|
|
4475
|
-
closed: true,
|
|
4476
|
-
cornerAngleDeg
|
|
4477
|
-
});
|
|
4478
|
-
if (!cubics.length) return { closed: true, anchors: l.simple.map(([x, y]) => ({ x, y })) };
|
|
4479
|
-
const anchors = cubics.map((c, k) => {
|
|
4480
|
-
const prev = cubics[(k - 1 + cubics.length) % cubics.length];
|
|
4481
|
-
return {
|
|
4482
|
-
x: c.p0[0],
|
|
4483
|
-
y: c.p0[1],
|
|
4484
|
-
in: { x: prev.p2[0], y: prev.p2[1] },
|
|
4485
|
-
out: { x: c.p1[0], y: c.p1[1] }
|
|
4486
|
-
};
|
|
4487
|
-
});
|
|
4488
|
-
return { closed: true, anchors };
|
|
4489
|
-
}
|
|
4490
|
-
function traceField(field, w, h, threshold, o) {
|
|
4491
|
-
const tolerance = o.tolerance ?? 1;
|
|
4492
|
-
const roundness = Math.min(1, Math.max(0, o.cornerRoundness ?? 0.5));
|
|
4493
|
-
return loopsOf(field, w, h, threshold, tolerance, o.minArea ?? 10).map((l) => loopSubpath(l, tolerance, roundness));
|
|
4494
|
-
}
|
|
4495
|
-
function quantizeVectorTraceColors(pixels, count) {
|
|
4496
|
-
const n2 = pixels.width * pixels.height;
|
|
4497
|
-
const d = pixels.data;
|
|
4498
|
-
const opaque = [];
|
|
4499
|
-
for (let i = 0; i < n2; i++) if (d[i * 4 + 3] >= 128) opaque.push(i);
|
|
4500
|
-
const boxes = opaque.length ? [opaque] : [];
|
|
4501
|
-
const range = (box, c) => {
|
|
4502
|
-
let lo = 255;
|
|
4503
|
-
let hi = 0;
|
|
4504
|
-
for (const i of box) {
|
|
4505
|
-
const v = d[i * 4 + c];
|
|
4506
|
-
if (v < lo) lo = v;
|
|
4507
|
-
if (v > hi) hi = v;
|
|
4508
|
-
}
|
|
4509
|
-
return hi - lo;
|
|
4510
|
-
};
|
|
4511
|
-
while (boxes.length < count) {
|
|
4512
|
-
let best = -1;
|
|
4513
|
-
let bestRange = 0;
|
|
4514
|
-
let bestC = 0;
|
|
4515
|
-
boxes.forEach((b, k) => {
|
|
4516
|
-
if (b.length < 2) return;
|
|
4517
|
-
for (let c = 0; c < 3; c++) {
|
|
4518
|
-
const r = range(b, c);
|
|
4519
|
-
if (r > bestRange) {
|
|
4520
|
-
bestRange = r;
|
|
4521
|
-
best = k;
|
|
4522
|
-
bestC = c;
|
|
4523
|
-
}
|
|
4524
|
-
}
|
|
4525
|
-
});
|
|
4526
|
-
if (best < 0) break;
|
|
4527
|
-
const box = boxes[best].slice().sort((a, b) => d[a * 4 + bestC] - d[b * 4 + bestC]);
|
|
4528
|
-
const key = (k) => d[box[k] * 4 + bestC];
|
|
4529
|
-
const v = key(box.length >> 1);
|
|
4530
|
-
let mid = box.findIndex((_, k) => key(k) >= v);
|
|
4531
|
-
if (mid <= 0) mid = box.findIndex((_, k) => key(k) > v);
|
|
4532
|
-
boxes.splice(best, 1, box.slice(0, mid), box.slice(mid));
|
|
4533
|
-
}
|
|
4534
|
-
const palette = boxes.map((b) => {
|
|
4535
|
-
const s = [0, 0, 0];
|
|
4536
|
-
for (const i of b) for (let c = 0; c < 3; c++) s[c] += d[i * 4 + c];
|
|
4537
|
-
return [Math.round(s[0] / b.length), Math.round(s[1] / b.length), Math.round(s[2] / b.length)];
|
|
4538
|
-
});
|
|
4539
|
-
const index = new Int16Array(n2).fill(-1);
|
|
4540
|
-
const counts = palette.map(() => 0);
|
|
4541
|
-
for (const i of opaque) {
|
|
4542
|
-
let best = 0;
|
|
4543
|
-
let bestD = Infinity;
|
|
4544
|
-
palette.forEach((p, k) => {
|
|
4545
|
-
const dd = (d[i * 4] - p[0]) ** 2 + (d[i * 4 + 1] - p[1]) ** 2 + (d[i * 4 + 2] - p[2]) ** 2;
|
|
4546
|
-
if (dd < bestD) {
|
|
4547
|
-
bestD = dd;
|
|
4548
|
-
best = k;
|
|
4958
|
+
};
|
|
4959
|
+
const out = [];
|
|
4960
|
+
for (const l of layers) {
|
|
4961
|
+
const ty = Number(l.ty);
|
|
4962
|
+
if (l.hd === true || frame < Number(l.ip ?? -Infinity) || frame >= Number(l.op ?? Infinity)) continue;
|
|
4963
|
+
if (l.td === 1 || l.tt) notes.push(`Layer "${l.nm ?? ""}": track mattes are not imported.`);
|
|
4964
|
+
if (l.ef) notes.push(`Layer "${l.nm ?? ""}": layer effects are not imported.`);
|
|
4965
|
+
if (l.ks && JSON.stringify(l.ks).includes('"x":"')) notes.push("Expressions are not evaluated (their stored values are used).");
|
|
4966
|
+
let children = [];
|
|
4967
|
+
if (ty === 4) children = itemsToNodes(Array.isArray(l.shapes) ? l.shapes : [], { paints: [], modifiers: [] }, s);
|
|
4968
|
+
else if (ty === 1) {
|
|
4969
|
+
const w = Number(l.sw ?? 0);
|
|
4970
|
+
const h = Number(l.sh ?? 0);
|
|
4971
|
+
const c = typeof l.sc === "string" ? l.sc : "#000000";
|
|
4972
|
+
children = [{ id: s.next(), type: "path", name: "Solid", subpaths: [rectSubpath({ x: 0, y: 0, width: w, height: h })], fill: c, stroke: null }];
|
|
4973
|
+
} else if (ty === 0 && typeof l.refId === "string" && depth < 16) {
|
|
4974
|
+
const asset = assets.get(l.refId);
|
|
4975
|
+
const local = frame - Number(l.st ?? 0);
|
|
4976
|
+
if (asset && Array.isArray(asset.layers)) children = layersToNodes(asset.layers, local, depth + 1);
|
|
4977
|
+
} else if (ty !== 3) {
|
|
4978
|
+
notes.push(`Layer "${l.nm ?? ""}" (type ${ty}) is not supported (skipped).`);
|
|
4979
|
+
continue;
|
|
4549
4980
|
}
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
}
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
const field = new Float32Array(n2);
|
|
4564
|
-
const byAlpha = options.channel === "alpha";
|
|
4565
|
-
for (let i = 0; i < n2; i++) {
|
|
4566
|
-
const a = data[i * 4 + 3] / 255;
|
|
4567
|
-
field[i] = byAlpha ? a : a * (1 - (0.2126 * data[i * 4] + 0.7152 * data[i * 4 + 1] + 0.0722 * data[i * 4 + 2]) / 255);
|
|
4568
|
-
}
|
|
4569
|
-
const subpaths = traceField(field, w, h, Math.min(1, Math.max(0, options.threshold ?? 0.5)), options);
|
|
4570
|
-
return subpaths.length ? [
|
|
4571
|
-
{
|
|
4572
|
-
id: "trace-1",
|
|
4573
|
-
type: "path",
|
|
4574
|
-
name: "Trace",
|
|
4575
|
-
fill: options.color ?? "#000000",
|
|
4576
|
-
stroke: null,
|
|
4577
|
-
fillRule: "evenodd",
|
|
4578
|
-
subpaths
|
|
4981
|
+
if (!children.length) continue;
|
|
4982
|
+
const m = worldOf2(l);
|
|
4983
|
+
const opacity = transformOf(l.ks, frame, notes).opacity;
|
|
4984
|
+
let group = { id: s.next(), type: "group", ...typeof l.nm === "string" ? { name: l.nm } : {}, children };
|
|
4985
|
+
group = transformVectorNode(group, m);
|
|
4986
|
+
if (opacity < 1) group = { ...group, opacity };
|
|
4987
|
+
const masks = (Array.isArray(l.masksProperties) ? l.masksProperties : []).filter((mk) => (mk.mode ?? "a") === "a" && !mk.inv);
|
|
4988
|
+
if (Array.isArray(l.masksProperties) && masks.length !== l.masksProperties.length) notes.push(`Layer "${l.nm ?? ""}": only additive masks are imported.`);
|
|
4989
|
+
if (masks.length) {
|
|
4990
|
+
const subpaths = masks.map((mk) => subpathOf(mk.pt, frame)).filter((x) => !!x);
|
|
4991
|
+
const clip = transformVectorNode({ id: s.next(), type: "path", name: "Mask", subpaths, fill: "#000000", stroke: null }, m);
|
|
4992
|
+
const { transform: _t, opacity: _o, ...inner } = group;
|
|
4993
|
+
group = { id: s.next(), type: "group", name: `${group.name ?? "Layer"} mask`, mask: { type: "clip" }, children: [clip, { ...inner, id: s.next() }], ...group.opacity !== void 0 ? { opacity: group.opacity } : {} };
|
|
4579
4994
|
}
|
|
4580
|
-
|
|
4581
|
-
}
|
|
4582
|
-
const count = Math.min(32, Math.max(2, Math.round(options.colors ?? 6)));
|
|
4583
|
-
const q = quantizeVectorTraceColors(pixels, count);
|
|
4584
|
-
const order = q.palette.map((_, k) => k).filter((k) => q.counts[k] > 0).sort((a, b) => q.counts[b] - q.counts[a]);
|
|
4585
|
-
const rank = new Map(order.map((k, r) => [k, r]));
|
|
4586
|
-
const out = [];
|
|
4587
|
-
order.forEach((k, r) => {
|
|
4588
|
-
const field = new Uint8Array(n2);
|
|
4589
|
-
for (let i = 0; i < n2; i++) {
|
|
4590
|
-
const ix = q.index[i];
|
|
4591
|
-
field[i] = ix >= 0 && rank.get(ix) >= r ? 1 : 0;
|
|
4995
|
+
out.push(group);
|
|
4592
4996
|
}
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
fill: hex(q.palette[k]),
|
|
4600
|
-
stroke: null,
|
|
4601
|
-
fillRule: "evenodd",
|
|
4602
|
-
subpaths
|
|
4603
|
-
});
|
|
4604
|
-
});
|
|
4605
|
-
return out;
|
|
4997
|
+
return out.reverse();
|
|
4998
|
+
};
|
|
4999
|
+
const nodes = layersToNodes(json.layers, s.frame, 0);
|
|
5000
|
+
const width = Number(json.w ?? 512);
|
|
5001
|
+
const height = Number(json.h ?? 512);
|
|
5002
|
+
return { document: { width, height, nodes }, notes: [...new Set(notes)] };
|
|
4606
5003
|
}
|
|
4607
5004
|
|
|
4608
5005
|
// src/vector/constraints/auto.ts
|
|
@@ -6555,6 +6952,9 @@ var DEFAULT_VECTOR_PATH_EDIT = {
|
|
|
6555
6952
|
pencilSmoothing: 0,
|
|
6556
6953
|
recognizeShapes: false,
|
|
6557
6954
|
eraserSize: 10,
|
|
6955
|
+
distortMode: "smudge",
|
|
6956
|
+
distortSize: 20,
|
|
6957
|
+
distortStrength: 0.5,
|
|
6558
6958
|
pencilPressure: "pen",
|
|
6559
6959
|
fillGap: 0
|
|
6560
6960
|
};
|
|
@@ -6827,8 +7227,10 @@ var TOOL_LABELS = {
|
|
|
6827
7227
|
type: "Type",
|
|
6828
7228
|
typePath: "Type on a Path",
|
|
6829
7229
|
frame: "Frame",
|
|
6830
|
-
connector: "Connector"
|
|
7230
|
+
connector: "Connector",
|
|
7231
|
+
distort: "Distort Brush"
|
|
6831
7232
|
};
|
|
7233
|
+
var DISTORT_LABELS = { smudge: "Smudge", twirl: "Twirl", attract: "Attract", repel: "Repel", roughen: "Roughen" };
|
|
6832
7234
|
var TOOL_KEYS = {
|
|
6833
7235
|
v: "select",
|
|
6834
7236
|
a: "direct",
|
|
@@ -6851,7 +7253,8 @@ var TOOL_KEYS = {
|
|
|
6851
7253
|
t: "type",
|
|
6852
7254
|
q: "typePath",
|
|
6853
7255
|
f: "frame",
|
|
6854
|
-
x: "connector"
|
|
7256
|
+
x: "connector",
|
|
7257
|
+
d: "distort"
|
|
6855
7258
|
};
|
|
6856
7259
|
function vectorToolLabel(tool) {
|
|
6857
7260
|
return TOOL_LABELS[tool];
|
|
@@ -6878,7 +7281,8 @@ var VECTOR_TOOL_SHORTCUTS = {
|
|
|
6878
7281
|
type: "T",
|
|
6879
7282
|
typePath: "Q",
|
|
6880
7283
|
frame: "F",
|
|
6881
|
-
connector: "X"
|
|
7284
|
+
connector: "X",
|
|
7285
|
+
distort: "D"
|
|
6882
7286
|
};
|
|
6883
7287
|
var VECTOR_TOOL_GLYPHS = {
|
|
6884
7288
|
select: "\u2196",
|
|
@@ -6902,7 +7306,8 @@ var VECTOR_TOOL_GLYPHS = {
|
|
|
6902
7306
|
type: "T",
|
|
6903
7307
|
typePath: "\u{1D4AF}",
|
|
6904
7308
|
frame: "\u2B1A",
|
|
6905
|
-
connector: "\u2933"
|
|
7309
|
+
connector: "\u2933",
|
|
7310
|
+
distort: "\u2248"
|
|
6906
7311
|
};
|
|
6907
7312
|
var SHAPE_TOOLS = /* @__PURE__ */ new Set(["rect", "ellipse", "polygon", "star", "line", "frame"]);
|
|
6908
7313
|
var BOOLEAN_OPS = {
|
|
@@ -7433,6 +7838,9 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
7433
7838
|
const next = { ...this.editing, ...editing };
|
|
7434
7839
|
next.pencilSmoothing = Math.min(1, Math.max(0, Number.isFinite(next.pencilSmoothing) ? next.pencilSmoothing : 0));
|
|
7435
7840
|
if (!(next.eraserSize > 0)) next.eraserSize = this.editing.eraserSize;
|
|
7841
|
+
if (!VECTOR_DISTORT_BRUSH_MODES.includes(next.distortMode)) next.distortMode = this.editing.distortMode;
|
|
7842
|
+
if (!(next.distortSize > 0)) next.distortSize = this.editing.distortSize;
|
|
7843
|
+
next.distortStrength = Math.min(1, Math.max(0, Number.isFinite(next.distortStrength) ? next.distortStrength : this.editing.distortStrength));
|
|
7436
7844
|
this.editing = next;
|
|
7437
7845
|
this.notify();
|
|
7438
7846
|
}
|
|
@@ -7675,6 +8083,22 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
7675
8083
|
});
|
|
7676
8084
|
return next.id;
|
|
7677
8085
|
}
|
|
8086
|
+
/**
|
|
8087
|
+
* Replaces a generated group's art (its children) and provenance, in one undo step; used by
|
|
8088
|
+
* `regenerateVectorArt`. The group keeps its id, name, transform and place.
|
|
8089
|
+
*/
|
|
8090
|
+
replaceGeneratedArt(id, children, generation) {
|
|
8091
|
+
const node = this.getNode(id);
|
|
8092
|
+
if (!node || node.type !== "group" || !node.generation) return { ok: false, message: `"${id}" is not generated art.` };
|
|
8093
|
+
if (node.locked) return { ok: false, message: `"${id}" is locked; unlock it first.` };
|
|
8094
|
+
const inv = invertMatrix(this.worldOf(id));
|
|
8095
|
+
const placed = children.map((c) => this.freshIds(isIdentityMatrix(inv) ? c : transformVectorNode(c, inv)));
|
|
8096
|
+
this.change("Regenerate Art", () => {
|
|
8097
|
+
this.replaceNode(id, { ...node, generation, children: placed });
|
|
8098
|
+
this.anchors = [];
|
|
8099
|
+
});
|
|
8100
|
+
return { ok: true };
|
|
8101
|
+
}
|
|
7678
8102
|
/** Builds a path node for a shape with the current defaults (not added). */
|
|
7679
8103
|
createShapeNode(input) {
|
|
7680
8104
|
const b = { x: input.x, y: input.y, width: input.width, height: input.height };
|
|
@@ -8364,6 +8788,57 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
8364
8788
|
merge
|
|
8365
8789
|
);
|
|
8366
8790
|
}
|
|
8791
|
+
/**
|
|
8792
|
+
* Adds a brushstroke to the selected (or given) paths: a stroke layer that paints the path with
|
|
8793
|
+
* brush `brushId` from the paint editor's library (`vectorBrushstrokeBrushes`), `size` document
|
|
8794
|
+
* units across (default the brush's own), an optional `pressure` profile along the length, in
|
|
8795
|
+
* `color` (default the top stroke's paint, else black) at `opacity`. At the top of the stroke
|
|
8796
|
+
* stack or at `index`. One undo step.
|
|
8797
|
+
*/
|
|
8798
|
+
addBrushstroke(brush, options = {}) {
|
|
8799
|
+
const clean = sanitizeVectorBrushstroke(brush);
|
|
8800
|
+
if (!clean) return { ok: false, message: `Unknown brush "${brush?.brushId}": use one of ${vectorBrushstrokeBrushes().map((b) => b.id).join(", ")}.` };
|
|
8801
|
+
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)." };
|
|
8802
|
+
if (options.color !== void 0 && !parseVectorColor(options.color)) return { ok: false, message: `"${options.color}" is not a colour.` };
|
|
8803
|
+
if (options.opacity !== void 0 && !(options.opacity >= 0 && options.opacity <= 1)) return { ok: false, message: "opacity must be from 0 to 1." };
|
|
8804
|
+
return this.editStacks("stroke", options.ids, "Add Brushstroke", (stack, path) => {
|
|
8805
|
+
const top = [...stack].reverse().find((l) => (l.kind ?? "paint") === "paint");
|
|
8806
|
+
const paint = options.color ?? (typeof top?.paint === "string" && top.paint !== "none" ? top.paint : typeof path.stroke === "string" && path.stroke !== "none" ? path.stroke : "#000000");
|
|
8807
|
+
const layer = { kind: "brushstroke", paint, brush: clean, ...options.opacity !== void 0 && options.opacity < 1 ? { opacity: options.opacity } : {} };
|
|
8808
|
+
const at = options.index === void 0 ? stack.length : Math.min(stack.length, Math.max(0, Math.round(options.index)));
|
|
8809
|
+
return [...stack.slice(0, at), layer, ...stack.slice(at)];
|
|
8810
|
+
});
|
|
8811
|
+
}
|
|
8812
|
+
/**
|
|
8813
|
+
* Changes brushstroke layer `index` (in the stroke stack, 0 is the bottom) of the selected (or
|
|
8814
|
+
* given) paths: another brush, size, pressure profile (null removes it), colour or opacity.
|
|
8815
|
+
* Fails, changing nothing, when that layer is not a brushstroke. With `merge`, consecutive edits
|
|
8816
|
+
* fold into one undo step.
|
|
8817
|
+
*/
|
|
8818
|
+
setBrushstroke(index, patch, options = {}) {
|
|
8819
|
+
if (patch.brushId !== void 0 && !vectorBrushstrokeBrush(patch.brushId)) return { ok: false, message: `Unknown brush "${patch.brushId}": use one of ${vectorBrushstrokeBrushes().map((b) => b.id).join(", ")}.` };
|
|
8820
|
+
if (patch.size !== void 0 && patch.size !== null && !(patch.size > 0 && patch.size <= 500)) return { ok: false, message: "size must be above 0 and at most 500 (document units)." };
|
|
8821
|
+
if (patch.color !== void 0 && !parseVectorColor(patch.color)) return { ok: false, message: `"${patch.color}" is not a colour.` };
|
|
8822
|
+
if (patch.opacity !== void 0 && !(patch.opacity >= 0 && patch.opacity <= 1)) return { ok: false, message: "opacity must be from 0 to 1." };
|
|
8823
|
+
const merge = options.merge === true ? `brushstroke:${index}` : options.merge || void 0;
|
|
8824
|
+
return this.editStacks(
|
|
8825
|
+
"stroke",
|
|
8826
|
+
options.ids,
|
|
8827
|
+
"Edit Brushstroke",
|
|
8828
|
+
(stack, path) => {
|
|
8829
|
+
const layer = stack[index];
|
|
8830
|
+
if (!layer || layer.kind !== "brushstroke" || !layer.brush) return `Path "${path.id}" has no brushstroke at stroke ${index} (vector.getAppearance lists its strokes; 0 is the bottom).`;
|
|
8831
|
+
const brush = { ...layer.brush, ...patch.brushId ? { brushId: patch.brushId } : {} };
|
|
8832
|
+
if (patch.size === null) delete brush.size;
|
|
8833
|
+
else if (patch.size !== void 0) brush.size = patch.size;
|
|
8834
|
+
if (patch.pressure === null) delete brush.pressure;
|
|
8835
|
+
else if (patch.pressure !== void 0) brush.pressure = patch.pressure;
|
|
8836
|
+
const next = { ...layer, brush: sanitizeVectorBrushstroke(brush) ?? layer.brush, ...patch.color ? { paint: patch.color } : {}, ...patch.opacity !== void 0 ? { opacity: patch.opacity } : {} };
|
|
8837
|
+
return stack.map((l, i) => i === index ? next : l);
|
|
8838
|
+
},
|
|
8839
|
+
merge
|
|
8840
|
+
);
|
|
8841
|
+
}
|
|
8367
8842
|
/** Removes layer `index` of the fill or stroke stack of the selected (or given) paths. */
|
|
8368
8843
|
removePaintLayer(kind, index, options = {}) {
|
|
8369
8844
|
return this.editStacks(
|
|
@@ -9844,6 +10319,38 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
9844
10319
|
});
|
|
9845
10320
|
return { ok: true, id, ids: children.map((c) => c.id) };
|
|
9846
10321
|
}
|
|
10322
|
+
/**
|
|
10323
|
+
* Bitmap-to-vector stylise: turns RGBA `pixels` into stylised vector art (see
|
|
10324
|
+
* `stylizeVectorImage`: mosaic tiles, halftone dots, low-poly triangles or engraving lines) and
|
|
10325
|
+
* adds it as one group, placed like Image Trace (top-left at `x, y`, `width` wide, default one
|
|
10326
|
+
* unit per pixel). One undo step; selects the group. Returns its id and its paths' ids.
|
|
10327
|
+
*/
|
|
10328
|
+
stylizeImage(pixels, style, options = {}) {
|
|
10329
|
+
if (!VECTOR_STYLIZE_STYLES.includes(style)) return { ok: false, message: `Unknown style "${style}": use ${VECTOR_STYLIZE_STYLES.join(", ")}.` };
|
|
10330
|
+
if (options.cell !== void 0 && !(options.cell >= 1)) return { ok: false, message: "cell must be at least 1 pixel." };
|
|
10331
|
+
if (options.colors !== void 0 && !(Number.isInteger(options.colors) && options.colors >= 2 && options.colors <= 32)) return { ok: false, message: "colors must be a whole number from 2 to 32." };
|
|
10332
|
+
if (options.width !== void 0 && !(options.width > 0)) return { ok: false, message: "width must be above 0." };
|
|
10333
|
+
if (typeof options.color === "string" && !parseVectorColor(options.color)) return { ok: false, message: `"${options.color}" is not a colour.` };
|
|
10334
|
+
let made;
|
|
10335
|
+
try {
|
|
10336
|
+
made = stylizeVectorImage(pixels, style, options);
|
|
10337
|
+
} catch (e) {
|
|
10338
|
+
return { ok: false, message: e.message };
|
|
10339
|
+
}
|
|
10340
|
+
if (!made.length) return { ok: false, message: "Nothing to draw: the picture is empty or too light for this style (try a smaller cell, or mosaic)." };
|
|
10341
|
+
const k = (options.width ?? pixels.width) / pixels.width;
|
|
10342
|
+
const m = [k, 0, 0, k, options.x ?? 0, options.y ?? 0];
|
|
10343
|
+
const children = made.map((n2) => ({ ...transformVectorNode(n2, m), id: this.newId() }));
|
|
10344
|
+
const id = this.newId();
|
|
10345
|
+
const label2 = VECTOR_STYLIZE_LABELS[style];
|
|
10346
|
+
this.change(label2, () => {
|
|
10347
|
+
this.setDoc([...this.doc.nodes, { id, type: "group", name: options.name ?? label2, children }]);
|
|
10348
|
+
this.selection = [id];
|
|
10349
|
+
this.anchors = [];
|
|
10350
|
+
this.editingId = null;
|
|
10351
|
+
});
|
|
10352
|
+
return { ok: true, id, ids: children.map((c) => c.id) };
|
|
10353
|
+
}
|
|
9847
10354
|
// -----------------------------------------------------------------------------------------
|
|
9848
10355
|
// Gradient meshes and freeform gradients
|
|
9849
10356
|
/** The selected point of a mesh or freeform fill (Gradient tool), or null. */
|
|
@@ -11646,6 +12153,81 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
11646
12153
|
});
|
|
11647
12154
|
return out;
|
|
11648
12155
|
}
|
|
12156
|
+
/** Starts a Distort Brush stroke at `p` (document space). */
|
|
12157
|
+
downDistort(p) {
|
|
12158
|
+
const before = this.snapshot();
|
|
12159
|
+
const r = this.editing.distortSize;
|
|
12160
|
+
const drag = { kind: "distort", mode: this.editing.distortMode, base: this.doc, before, targets: /* @__PURE__ */ new Set(), dabs: [], last: p };
|
|
12161
|
+
this.addDistortDab(drag, p, true);
|
|
12162
|
+
this.drag = drag;
|
|
12163
|
+
this.interaction = { type: "brush", x: p.x, y: p.y, radius: r };
|
|
12164
|
+
this.notify();
|
|
12165
|
+
}
|
|
12166
|
+
/** Adds the paths the brush reaches at `p` to the stroke's targets, and (unless `targetsOnly`) a dab there. */
|
|
12167
|
+
addDistortDab(drag, p, targetsOnly = false) {
|
|
12168
|
+
const radius = this.editing.distortSize;
|
|
12169
|
+
const strength = this.editing.distortStrength;
|
|
12170
|
+
const base = this.doc;
|
|
12171
|
+
this.doc = drag.base;
|
|
12172
|
+
for (const n2 of this.strokeTargets([p], void 0, radius)) drag.targets.add(n2.id);
|
|
12173
|
+
this.doc = base;
|
|
12174
|
+
if (targetsOnly) return;
|
|
12175
|
+
if (drag.mode === "smudge") drag.dabs.push({ x: drag.last.x, y: drag.last.y, radius, strength, dx: p.x - drag.last.x, dy: p.y - drag.last.y });
|
|
12176
|
+
else drag.dabs.push({ x: p.x, y: p.y, radius, strength, angle: 15, seed: drag.dabs.length + 1 });
|
|
12177
|
+
}
|
|
12178
|
+
/** `base` with the target paths distorted by the dabs (document space dabs, mapped into each path's own space). */
|
|
12179
|
+
distortedDoc(base, targets, mode, dabs) {
|
|
12180
|
+
if (!dabs.length || !targets.size) return base;
|
|
12181
|
+
const nodes = mapTree(base.nodes, (n2) => {
|
|
12182
|
+
if (!targets.has(n2.id) || n2.type !== "path" || n2.locked) return n2;
|
|
12183
|
+
const world = vectorWorldMatrix(base.nodes, n2.id) ?? IDENTITY_MATRIX;
|
|
12184
|
+
const inv = invertMatrix(world);
|
|
12185
|
+
const k = 1 / Math.sqrt(Math.abs(world[0] * world[3] - world[1] * world[2]) || 1);
|
|
12186
|
+
const local = dabs.map((d) => {
|
|
12187
|
+
const c = applyMatrix(inv, d);
|
|
12188
|
+
const moved = d.dx !== void 0 || d.dy !== void 0 ? applyMatrix(inv, { x: d.x + (d.dx ?? 0), y: d.y + (d.dy ?? 0) }) : null;
|
|
12189
|
+
return { ...d, x: c.x, y: c.y, radius: d.radius * k, ...moved ? { dx: moved.x - c.x, dy: moved.y - c.y } : {} };
|
|
12190
|
+
});
|
|
12191
|
+
const path = subpathsToBezier(n2.subpaths);
|
|
12192
|
+
const out = distortVectorPath(path, mode, local);
|
|
12193
|
+
return out === path ? n2 : { ...n2, subpaths: bezierToSubpaths(out) };
|
|
12194
|
+
});
|
|
12195
|
+
return nodes === base.nodes ? base : { ...base, nodes };
|
|
12196
|
+
}
|
|
12197
|
+
/**
|
|
12198
|
+
* Distortion brush (CorelDRAW's Smudge, Twirl, Attract, Repel and Roughen tools): runs a round
|
|
12199
|
+
* brush of `radius` (default: the `distortSize` setting) and `strength` 0–1 along `points`
|
|
12200
|
+
* (document space). Smudge pushes the outlines along the stroke (needs two or more points),
|
|
12201
|
+
* twirl turns them about each point by `angle` degrees (default 15) at full strength, attract
|
|
12202
|
+
* pulls them toward each point, repel pushes them away, roughen spikes them. It acts on `ids`,
|
|
12203
|
+
* the selection, or every path the brush reaches. One undo step. Returns the changed ids.
|
|
12204
|
+
*/
|
|
12205
|
+
distortBrush(mode, points, options = {}) {
|
|
12206
|
+
if (!VECTOR_DISTORT_BRUSH_MODES.includes(mode)) return { ok: false, message: `Unknown brush "${mode}": use ${VECTOR_DISTORT_BRUSH_MODES.join(", ")}.` };
|
|
12207
|
+
if (!points.length || points.some((q) => !Number.isFinite(q?.x) || !Number.isFinite(q?.y))) return { ok: false, message: "points must be one or more { x, y } numbers." };
|
|
12208
|
+
if (mode === "smudge" && points.length < 2) return { ok: false, message: "Smudge needs two or more points: the stroke pushes the outlines along it." };
|
|
12209
|
+
const radius = options.radius ?? this.editing.distortSize;
|
|
12210
|
+
const strength = options.strength ?? this.editing.distortStrength;
|
|
12211
|
+
if (!(radius > 0)) return { ok: false, message: "radius must be more than 0." };
|
|
12212
|
+
if (!(strength >= 0 && strength <= 1)) return { ok: false, message: "strength must be from 0 to 1." };
|
|
12213
|
+
if (options.ids) {
|
|
12214
|
+
for (const id of options.ids) {
|
|
12215
|
+
const n2 = this.getNode(id);
|
|
12216
|
+
if (!n2) return { ok: false, message: `There is no node "${id}".` };
|
|
12217
|
+
if (n2.locked) return { ok: false, message: `"${id}" is locked; unlock it first.` };
|
|
12218
|
+
}
|
|
12219
|
+
}
|
|
12220
|
+
const targets = new Set(this.strokeTargets(points, options.ids, radius).map((n2) => n2.id));
|
|
12221
|
+
const dabs = mode === "smudge" ? points.slice(1).map((q, i) => ({ x: points[i].x, y: points[i].y, radius, strength, dx: q.x - points[i].x, dy: q.y - points[i].y })) : points.map((q, i) => ({ x: q.x, y: q.y, radius, strength, angle: options.angle ?? 15, seed: (options.seed ?? 1) + i }));
|
|
12222
|
+
const next = this.distortedDoc(this.doc, targets, mode, dabs);
|
|
12223
|
+
const changed = [...targets].filter((id) => findVectorNode(next.nodes, id) !== this.getNode(id));
|
|
12224
|
+
if (!changed.length) return { ok: false, message: "The brush reached no outline: put the points within radius of a path (or pass its id)." };
|
|
12225
|
+
this.change(DISTORT_LABELS[mode], () => {
|
|
12226
|
+
this.doc = next;
|
|
12227
|
+
this.anchors = [];
|
|
12228
|
+
});
|
|
12229
|
+
return { ok: true, ids: changed };
|
|
12230
|
+
}
|
|
11649
12231
|
/** A stroke's points in node `id`'s own space. */
|
|
11650
12232
|
localPoints(id, points) {
|
|
11651
12233
|
const inv = invertMatrix(this.worldOf(id));
|
|
@@ -12212,6 +12794,8 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
12212
12794
|
return;
|
|
12213
12795
|
case "connector":
|
|
12214
12796
|
return this.downConnector(p);
|
|
12797
|
+
case "distort":
|
|
12798
|
+
return this.downDistort(p);
|
|
12215
12799
|
default:
|
|
12216
12800
|
return this.downShape(p, input);
|
|
12217
12801
|
}
|
|
@@ -12634,6 +13218,16 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
12634
13218
|
}
|
|
12635
13219
|
case "builder":
|
|
12636
13220
|
return this.moveBuilder(drag, p);
|
|
13221
|
+
case "distort": {
|
|
13222
|
+
const r = this.editing.distortSize;
|
|
13223
|
+
if (Math.hypot(p.x - drag.last.x, p.y - drag.last.y) < r * 0.1) return;
|
|
13224
|
+
this.addDistortDab(drag, p);
|
|
13225
|
+
drag.last = p;
|
|
13226
|
+
this.doc = this.distortedDoc(drag.base, drag.targets, drag.mode, drag.dabs);
|
|
13227
|
+
this.interaction = { type: "brush", x: p.x, y: p.y, radius: r };
|
|
13228
|
+
this.notify(true);
|
|
13229
|
+
return;
|
|
13230
|
+
}
|
|
12637
13231
|
case "stroke": {
|
|
12638
13232
|
const last = drag.points[drag.points.length - 1];
|
|
12639
13233
|
if (Math.hypot(p.x - last.x, p.y - last.y) * this.viewport.zoom < 1.5) return;
|
|
@@ -12927,6 +13521,17 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
12927
13521
|
else this.erase(drag.points);
|
|
12928
13522
|
return;
|
|
12929
13523
|
}
|
|
13524
|
+
case "distort": {
|
|
13525
|
+
if (!drag.dabs.length && drag.mode !== "smudge") {
|
|
13526
|
+
this.addDistortDab(drag, drag.last);
|
|
13527
|
+
this.doc = this.distortedDoc(drag.base, drag.targets, drag.mode, drag.dabs);
|
|
13528
|
+
}
|
|
13529
|
+
if (this.doc === drag.base) {
|
|
13530
|
+
this.notify(true);
|
|
13531
|
+
return;
|
|
13532
|
+
}
|
|
13533
|
+
return this.commit(DISTORT_LABELS[drag.mode], drag.before);
|
|
13534
|
+
}
|
|
12930
13535
|
case "pull":
|
|
12931
13536
|
if (drag.closing) this.pen = null;
|
|
12932
13537
|
return this.commit(drag.label, drag.before);
|
|
@@ -12982,9 +13587,9 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
12982
13587
|
this.selection = [drag.nodeId];
|
|
12983
13588
|
return this.commit(`Draw ${inkShapeLabel(shape)}`, drag.before);
|
|
12984
13589
|
}
|
|
12985
|
-
const
|
|
13590
|
+
const fitted2 = fitCubics(drag.points, 1.5 / this.viewport.zoom, { cornerAngleDeg: 60 });
|
|
12986
13591
|
const anchors = [];
|
|
12987
|
-
for (const c of
|
|
13592
|
+
for (const c of fitted2) {
|
|
12988
13593
|
if (!anchors.length) anchors.push({ x: c.p0[0], y: c.p0[1] });
|
|
12989
13594
|
anchors[anchors.length - 1].out = { x: c.p1[0], y: c.p1[1] };
|
|
12990
13595
|
anchors.push({ x: c.p3[0], y: c.p3[1], in: { x: c.p2[0], y: c.p2[1] } });
|
|
@@ -14493,7 +15098,7 @@ var pickStyle = (params) => {
|
|
|
14493
15098
|
}
|
|
14494
15099
|
return out;
|
|
14495
15100
|
};
|
|
14496
|
-
var
|
|
15101
|
+
var check2 = (result) => {
|
|
14497
15102
|
if (!result.ok) fail(result.message ?? "The command failed.");
|
|
14498
15103
|
};
|
|
14499
15104
|
function createVectorCommands(controller, options = {}) {
|
|
@@ -14623,7 +15228,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
14623
15228
|
enabled: enabledFor,
|
|
14624
15229
|
run: (_ctx, params) => {
|
|
14625
15230
|
const targets = ids(params);
|
|
14626
|
-
|
|
15231
|
+
check2(controller.addPaintLayer(kind, layerPatchOf(params ?? {}, keys), { ids: targets, index: params?.index }));
|
|
14627
15232
|
return { nodes: pathsUnder(targets).map((id) => stackResult(id, kind)) };
|
|
14628
15233
|
}
|
|
14629
15234
|
},
|
|
@@ -14639,7 +15244,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
14639
15244
|
layerIndex(params.nodeId, kind, params.index);
|
|
14640
15245
|
const patch = layerPatchOf(params, keys);
|
|
14641
15246
|
if (!Object.keys(patch).length) fail(`Pass at least one of: ${keys.join(", ")}.`);
|
|
14642
|
-
|
|
15247
|
+
check2(controller.updatePaintLayer(kind, params.index, patch, { ids: [params.nodeId] }));
|
|
14643
15248
|
return stackResult(params.nodeId, kind);
|
|
14644
15249
|
}
|
|
14645
15250
|
},
|
|
@@ -14653,7 +15258,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
14653
15258
|
undoable: true,
|
|
14654
15259
|
run: (_ctx, params) => {
|
|
14655
15260
|
layerIndex(params.nodeId, kind, params.index);
|
|
14656
|
-
|
|
15261
|
+
check2(controller.removePaintLayer(kind, params.index, { ids: [params.nodeId] }));
|
|
14657
15262
|
return stackResult(params.nodeId, kind);
|
|
14658
15263
|
}
|
|
14659
15264
|
},
|
|
@@ -14672,7 +15277,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
14672
15277
|
undoable: true,
|
|
14673
15278
|
run: (_ctx, params) => {
|
|
14674
15279
|
layerIndex(params.nodeId, kind, params.index);
|
|
14675
|
-
|
|
15280
|
+
check2(controller.movePaintLayer(kind, params.index, params.to, { ids: [params.nodeId] }));
|
|
14676
15281
|
return stackResult(params.nodeId, kind);
|
|
14677
15282
|
}
|
|
14678
15283
|
}
|
|
@@ -14780,7 +15385,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
14780
15385
|
if (params?.type === void 0 && params?.invert === void 0 && params?.enabled === void 0) fail("Pass at least one of type, invert, enabled.");
|
|
14781
15386
|
const groups = maskTargets(params);
|
|
14782
15387
|
const patch = { ...params?.type ? { type: params.type } : {}, ...params?.invert !== void 0 ? { invert: params.invert } : {}, ...params?.enabled !== void 0 ? { enabled: params.enabled } : {} };
|
|
14783
|
-
|
|
15388
|
+
check2(controller.setMaskOptions(patch, { ids: groups }));
|
|
14784
15389
|
return { groups: groups.map(maskResult) };
|
|
14785
15390
|
}
|
|
14786
15391
|
},
|
|
@@ -14829,7 +15434,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
14829
15434
|
enabled: enabledFor,
|
|
14830
15435
|
run: (_ctx, params) => {
|
|
14831
15436
|
const targets = ids(params);
|
|
14832
|
-
|
|
15437
|
+
check2(controller.setBlendMode(params.blendMode, { ids: targets }));
|
|
14833
15438
|
return { nodes: targets.map((id) => ({ id, blendMode: controller.getNode(id)?.blendMode ?? "normal" })) };
|
|
14834
15439
|
}
|
|
14835
15440
|
},
|
|
@@ -14883,7 +15488,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
14883
15488
|
const filter = filterOf(params.nodeId, params.filterId);
|
|
14884
15489
|
checkFilterParams(filter.type, params.params);
|
|
14885
15490
|
if (params.params === void 0 && params.enabled === void 0) fail("Pass params or enabled.");
|
|
14886
|
-
|
|
15491
|
+
check2(controller.updateFilter(params.nodeId, params.filterId, { params: params.params, enabled: params.enabled }));
|
|
14887
15492
|
return filtersResult(params.nodeId);
|
|
14888
15493
|
}
|
|
14889
15494
|
},
|
|
@@ -14902,7 +15507,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
14902
15507
|
undoable: true,
|
|
14903
15508
|
run: (_ctx, params) => {
|
|
14904
15509
|
filterOf(params.nodeId, params.filterId);
|
|
14905
|
-
|
|
15510
|
+
check2(controller.removeFilter(params.nodeId, params.filterId));
|
|
14906
15511
|
return filtersResult(params.nodeId);
|
|
14907
15512
|
}
|
|
14908
15513
|
},
|
|
@@ -14925,7 +15530,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
14925
15530
|
undoable: true,
|
|
14926
15531
|
run: (_ctx, params) => {
|
|
14927
15532
|
filterOf(params.nodeId, params.filterId);
|
|
14928
|
-
|
|
15533
|
+
check2(controller.moveFilter(params.nodeId, params.filterId, params.index));
|
|
14929
15534
|
return filtersResult(params.nodeId);
|
|
14930
15535
|
}
|
|
14931
15536
|
}
|
|
@@ -15081,7 +15686,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15081
15686
|
},
|
|
15082
15687
|
undoable: true,
|
|
15083
15688
|
run: (_ctx, params) => {
|
|
15084
|
-
|
|
15689
|
+
check2(controller.setText(params.id, params.text, { start: params.start, end: params.end }));
|
|
15085
15690
|
return textSummary(params.id);
|
|
15086
15691
|
}
|
|
15087
15692
|
},
|
|
@@ -15102,7 +15707,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15102
15707
|
const patch = pick(params, [...Object.keys(FONT_PROPS), "fill"]);
|
|
15103
15708
|
if (!Object.keys(patch).length) fail(`Pass at least one of ${[...Object.keys(FONT_PROPS), "fill"].join(", ")}.`);
|
|
15104
15709
|
if (patch.fill !== void 0) patch.fill = checkPaint(patch.fill, "fill");
|
|
15105
|
-
|
|
15710
|
+
check2(controller.setTextStyle(params.id, patch, { start: params.start, end: params.end }));
|
|
15106
15711
|
return textSummary(params.id);
|
|
15107
15712
|
}
|
|
15108
15713
|
},
|
|
@@ -15117,7 +15722,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15117
15722
|
run: (_ctx, params) => {
|
|
15118
15723
|
const patch = pick(params, Object.keys(PARAGRAPH_PROPS));
|
|
15119
15724
|
if (!Object.keys(patch).length) fail(`Pass at least one of ${Object.keys(PARAGRAPH_PROPS).join(", ")}.`);
|
|
15120
|
-
|
|
15725
|
+
check2(controller.setParagraphStyle(params.id, patch));
|
|
15121
15726
|
return textSummary(params.id);
|
|
15122
15727
|
}
|
|
15123
15728
|
},
|
|
@@ -15146,7 +15751,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15146
15751
|
run: (_ctx, params) => {
|
|
15147
15752
|
const { id, ...patch } = params;
|
|
15148
15753
|
if (!Object.keys(patch).length) fail("Pass kind or a frame field to change.");
|
|
15149
|
-
|
|
15754
|
+
check2(controller.setTextFrame(id, patch));
|
|
15150
15755
|
return textSummary(id);
|
|
15151
15756
|
}
|
|
15152
15757
|
},
|
|
@@ -15178,7 +15783,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15178
15783
|
pathOptions: pick(params, Object.keys(PATH_TEXT_PROPS)),
|
|
15179
15784
|
...params.fill !== void 0 ? { fill: checkPaint(params.fill, "fill") } : {}
|
|
15180
15785
|
});
|
|
15181
|
-
|
|
15786
|
+
check2(r);
|
|
15182
15787
|
return textSummary(r.id);
|
|
15183
15788
|
}
|
|
15184
15789
|
},
|
|
@@ -15194,7 +15799,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15194
15799
|
enabled: enabledFor,
|
|
15195
15800
|
run: (_ctx, params) => {
|
|
15196
15801
|
const r = controller.createOutlines({ ids: ids(params) });
|
|
15197
|
-
|
|
15802
|
+
check2(r);
|
|
15198
15803
|
return { ids: r.ids };
|
|
15199
15804
|
}
|
|
15200
15805
|
}
|
|
@@ -15251,7 +15856,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15251
15856
|
enabled: enabledFor,
|
|
15252
15857
|
run: (_ctx, params) => {
|
|
15253
15858
|
const r = controller.makeSymbol({ ids: ids(params), ...params?.name ? { name: params.name } : {} });
|
|
15254
|
-
|
|
15859
|
+
check2(r);
|
|
15255
15860
|
return { symbolId: r.symbolId, instanceId: r.instanceId, symbol: symbolSummary(r.symbolId) };
|
|
15256
15861
|
}
|
|
15257
15862
|
},
|
|
@@ -15282,7 +15887,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15282
15887
|
...params.transform ? { transform: params.transform } : {},
|
|
15283
15888
|
...params.name ? { name: params.name } : {}
|
|
15284
15889
|
});
|
|
15285
|
-
|
|
15890
|
+
check2(r);
|
|
15286
15891
|
return { id: r.id };
|
|
15287
15892
|
}
|
|
15288
15893
|
},
|
|
@@ -15314,7 +15919,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15314
15919
|
};
|
|
15315
15920
|
if (!Object.keys(patch).length) fail("Pass at least one of scaleStrokes, fill, stroke, hidden.");
|
|
15316
15921
|
const targetIds = ids(params);
|
|
15317
|
-
|
|
15922
|
+
check2(controller.setInstanceOptions(patch, { ids: targetIds }));
|
|
15318
15923
|
const listed = list().nodes;
|
|
15319
15924
|
return { instances: targetIds.map((id) => listed.find((n2) => n2.id === id)) };
|
|
15320
15925
|
}
|
|
@@ -15329,7 +15934,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15329
15934
|
undoable: true,
|
|
15330
15935
|
run: (_ctx, params) => {
|
|
15331
15936
|
const targetIds = ids(params);
|
|
15332
|
-
|
|
15937
|
+
check2(controller.replaceSymbol(params.symbolId, { ids: targetIds }));
|
|
15333
15938
|
return { ids: targetIds };
|
|
15334
15939
|
}
|
|
15335
15940
|
},
|
|
@@ -15344,7 +15949,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15344
15949
|
enabled: enabledFor,
|
|
15345
15950
|
run: (_ctx, params) => {
|
|
15346
15951
|
const r = controller.detachInstances({ ids: ids(params) });
|
|
15347
|
-
|
|
15952
|
+
check2(r);
|
|
15348
15953
|
return { ids: r.ids };
|
|
15349
15954
|
}
|
|
15350
15955
|
},
|
|
@@ -15357,7 +15962,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15357
15962
|
params: { type: "object", properties: { symbolId: { type: "string", description: "The symbol (from vector.listSymbols)." }, name: { type: "string", minLength: 1, description: "Its new name." } }, required: ["symbolId", "name"], additionalProperties: false },
|
|
15358
15963
|
undoable: true,
|
|
15359
15964
|
run: (_ctx, params) => {
|
|
15360
|
-
|
|
15965
|
+
check2(controller.renameSymbol(params.symbolId, params.name));
|
|
15361
15966
|
return symbolSummary(params.symbolId);
|
|
15362
15967
|
}
|
|
15363
15968
|
},
|
|
@@ -15370,7 +15975,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15370
15975
|
params: { type: "object", properties: { symbolId: { type: "string", description: "The symbol (from vector.listSymbols)." }, detach: { type: "boolean", description: "Turn its instances into groups first (default false: fail when it is used)." } }, required: ["symbolId"], additionalProperties: false },
|
|
15371
15976
|
undoable: true,
|
|
15372
15977
|
run: (_ctx, params) => {
|
|
15373
|
-
|
|
15978
|
+
check2(controller.deleteSymbol(params.symbolId, { detach: params.detach }));
|
|
15374
15979
|
return { ok: true };
|
|
15375
15980
|
}
|
|
15376
15981
|
},
|
|
@@ -15382,7 +15987,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15382
15987
|
description: "Enters isolation mode on a symbol (`id` of the symbol, or of one of its instances): the document then holds the symbol's art in its own space (registration point at 0, 0), so every vector.* command edits the art and every instance follows. Call vector.exitSymbolEdit when done. Not an undo step itself. Returns { symbolId, nodes } (vector.list for the art).",
|
|
15383
15988
|
params: { type: "object", properties: { id: { type: "string", description: "A symbol id (vector.listSymbols) or an instance id (vector.list)." } }, required: ["id"], additionalProperties: false },
|
|
15384
15989
|
run: (_ctx, params) => {
|
|
15385
|
-
|
|
15990
|
+
check2(controller.editSymbol(params.id));
|
|
15386
15991
|
return { symbolId: controller.getState().symbolEdit.symbolId, nodes: list().nodes };
|
|
15387
15992
|
}
|
|
15388
15993
|
},
|
|
@@ -15473,7 +16078,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15473
16078
|
run: (_ctx, params) => {
|
|
15474
16079
|
const { ids: list2, ...patch } = params ?? {};
|
|
15475
16080
|
if (!Object.keys(patch).length) fail("Pass at least one of mode, center, rotation, copies, mirror.");
|
|
15476
|
-
|
|
16081
|
+
check2(controller.setSymmetry(patch, list2 ? { ids: list2 } : {}));
|
|
15477
16082
|
const ids2 = list2 ?? controller.getState().selection.filter((id) => controller.getNode(id)?.type === "group");
|
|
15478
16083
|
return { groups: ids2.filter((id) => controller.getNode(id)?.symmetry).map(symmetryResult) };
|
|
15479
16084
|
}
|
|
@@ -15489,7 +16094,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15489
16094
|
enabled: enabledFor,
|
|
15490
16095
|
run: (_ctx, params) => {
|
|
15491
16096
|
const r = controller.releaseSymmetry({ ...params?.ids ? { ids: params.ids } : {}, ...params?.expand ? { expand: true } : {} });
|
|
15492
|
-
|
|
16097
|
+
check2(r);
|
|
15493
16098
|
return { ids: r.ids ?? [] };
|
|
15494
16099
|
}
|
|
15495
16100
|
},
|
|
@@ -15501,7 +16106,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15501
16106
|
description: "Hands the canvas to the user drawing inside a top-level symmetry group: new pen, pencil, shape and text art goes into it, mirrored as it is drawn, until vector.exitSymmetry or Escape. Returns { id }.",
|
|
15502
16107
|
params: { type: "object", properties: { id: { type: "string", description: "The symmetry group." } }, required: ["id"], additionalProperties: false },
|
|
15503
16108
|
run: (_ctx, params) => {
|
|
15504
|
-
|
|
16109
|
+
check2(controller.enterSymmetry(params.id));
|
|
15505
16110
|
return { id: params.id };
|
|
15506
16111
|
}
|
|
15507
16112
|
},
|
|
@@ -15542,7 +16147,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15542
16147
|
const { ids: list2, ...patch } = params ?? {};
|
|
15543
16148
|
if (!Object.keys(patch).length) fail("Pass at least one of type, amount, color.");
|
|
15544
16149
|
const r = controller.setLens(patch, list2 ? { ids: list2 } : {});
|
|
15545
|
-
|
|
16150
|
+
check2(r);
|
|
15546
16151
|
return { lenses: r.lenses ?? [] };
|
|
15547
16152
|
}
|
|
15548
16153
|
},
|
|
@@ -15557,11 +16162,43 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15557
16162
|
enabled: enabledFor,
|
|
15558
16163
|
run: (_ctx, params) => {
|
|
15559
16164
|
const r = controller.setLens(null, params?.ids ? { ids: params.ids } : {});
|
|
15560
|
-
|
|
16165
|
+
check2(r);
|
|
15561
16166
|
return { ids: (r.lenses ?? []).map((l) => l.id) };
|
|
15562
16167
|
}
|
|
15563
16168
|
}
|
|
15564
16169
|
];
|
|
16170
|
+
const BRUSH_PROPS = {
|
|
16171
|
+
ids: { ...IDS, description: "Paths to distort (default: the selection, else every path the brush reaches)." },
|
|
16172
|
+
radius: { type: "number", exclusiveMinimum: 0, description: "Brush radius, document units (default: the distortSize setting, 20)." },
|
|
16173
|
+
strength: { type: "number", minimum: 0, maximum: 1, description: "Strength at the brush centre, 0\u20131, fading to 0 at the rim (default: the distortStrength setting, 0.5)." }
|
|
16174
|
+
};
|
|
16175
|
+
const brushRun = (mode) => (_ctx, params) => {
|
|
16176
|
+
const { points, ids: list2, ...rest } = params;
|
|
16177
|
+
const r = controller.distortBrush(
|
|
16178
|
+
mode,
|
|
16179
|
+
points.map(([x, y]) => ({ x, y })),
|
|
16180
|
+
{ ...rest, ...list2 ? { ids: list2 } : {} }
|
|
16181
|
+
);
|
|
16182
|
+
check2(r);
|
|
16183
|
+
return { ids: r.ids ?? [] };
|
|
16184
|
+
};
|
|
16185
|
+
const brush = (mode, label2, keywords, description, extra = {}, minPoints = 1) => ({
|
|
16186
|
+
...base,
|
|
16187
|
+
id: `${prefix}${mode}Brush`,
|
|
16188
|
+
label: label2,
|
|
16189
|
+
keywords: ["distort", "brush", "corel", ...keywords],
|
|
16190
|
+
description: `${description} One undo step. Returns { ids } of the changed paths; fails, changing nothing, when the brush reaches no outline.`,
|
|
16191
|
+
params: { type: "object", properties: { points: { ...POINTS, minItems: minPoints, description: minPoints > 1 ? "The brush stroke [[x, y], \u2026] in document units: at least two points." : "Where the brush dabs [[x, y], \u2026], document units: one dab per point." }, ...BRUSH_PROPS, ...extra }, required: ["points"], additionalProperties: false },
|
|
16192
|
+
undoable: true,
|
|
16193
|
+
run: brushRun(mode)
|
|
16194
|
+
});
|
|
16195
|
+
const distortCommands = () => [
|
|
16196
|
+
brush("smudge", "Smudge Brush\u2026", ["smudge", "push", "drag", "liquify"], "Smudges outlines along a stroke: what is under the brush is pushed the way it moves, most at its centre (CorelDRAW's Smudge).", {}, 2),
|
|
16197
|
+
brush("twirl", "Twirl Brush\u2026", ["twirl", "swirl", "spiral", "twist"], "Twirls the outlines under the brush about each point (CorelDRAW's Twirl).", { angle: { type: "number", description: "Degrees turned at the brush centre per point, clockwise on screen; negative turns the other way (default 15)." } }),
|
|
16198
|
+
brush("attract", "Attract Brush\u2026", ["attract", "pinch", "pull", "pucker"], "Pulls the outlines under the brush toward each point (CorelDRAW's Attract)."),
|
|
16199
|
+
brush("repel", "Repel Brush\u2026", ["repel", "bloat", "push out", "inflate"], "Pushes the outlines under the brush away from each point (CorelDRAW's Repel)."),
|
|
16200
|
+
brush("roughen", "Roughen Brush\u2026", ["roughen", "jagged", "spiky", "fur", "grass"], "Breaks the outlines under the brush into spikes (CorelDRAW's Roughen): use for fur, grass and torn edges.", { seed: { type: "integer", description: "Seed of the spikes (default 1): the same seed gives the same spikes." } })
|
|
16201
|
+
];
|
|
15565
16202
|
const PLANE = { type: "string", enum: [...VECTOR_PERSPECTIVE_PLANES], description: "A plane of the grid: floor, left and right (2 or 3 vanishing points), or floor, side and front (1 point)." };
|
|
15566
16203
|
const perspectiveResult = () => {
|
|
15567
16204
|
const grid = controller.getPerspective();
|
|
@@ -15597,7 +16234,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15597
16234
|
},
|
|
15598
16235
|
undoable: true,
|
|
15599
16236
|
run: (_ctx, params) => {
|
|
15600
|
-
|
|
16237
|
+
check2(controller.setPerspective(params));
|
|
15601
16238
|
return perspectiveResult();
|
|
15602
16239
|
}
|
|
15603
16240
|
},
|
|
@@ -15610,7 +16247,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15610
16247
|
params: { type: "object", properties: {}, additionalProperties: false },
|
|
15611
16248
|
undoable: true,
|
|
15612
16249
|
run: () => {
|
|
15613
|
-
|
|
16250
|
+
check2(controller.setPerspective(null));
|
|
15614
16251
|
return { perspective: null };
|
|
15615
16252
|
}
|
|
15616
16253
|
},
|
|
@@ -15622,7 +16259,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15622
16259
|
description: "Makes the Rectangle, Ellipse, Polygon and Star tools draw on a plane of the grid (their drag box is taken on the plane, so shapes recede with it), or flat again with plane null. Not an undo step. Returns the grid as vector.getPerspective does.",
|
|
15623
16260
|
params: { type: "object", properties: { plane: { ...PLANE, type: ["string", "null"], enum: [...VECTOR_PERSPECTIVE_PLANES, null], description: `${PLANE.description} null draws flat.` } }, required: ["plane"], additionalProperties: false },
|
|
15624
16261
|
run: (_ctx, params) => {
|
|
15625
|
-
|
|
16262
|
+
check2(controller.setPerspectivePlane(params.plane));
|
|
15626
16263
|
return perspectiveResult();
|
|
15627
16264
|
}
|
|
15628
16265
|
},
|
|
@@ -15637,11 +16274,90 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15637
16274
|
enabled: enabledFor,
|
|
15638
16275
|
run: (_ctx, params) => {
|
|
15639
16276
|
const r = controller.attachToPerspective(params.plane, params.ids ? { ids: params.ids } : {});
|
|
15640
|
-
|
|
16277
|
+
check2(r);
|
|
15641
16278
|
return { ids: r.ids ?? [] };
|
|
15642
16279
|
}
|
|
15643
16280
|
}
|
|
15644
16281
|
];
|
|
16282
|
+
const brushstrokeCommands = () => [
|
|
16283
|
+
{
|
|
16284
|
+
...base,
|
|
16285
|
+
id: `${prefix}listBrushes`,
|
|
16286
|
+
label: "Brushstroke Brushes",
|
|
16287
|
+
readOnly: true,
|
|
16288
|
+
keywords: ["brush", "brushstroke", "pencil", "pastel", "charcoal", "ink", "marker", "natural media"],
|
|
16289
|
+
description: "Lists the brushes a brushstroke can use (the paint editor's dry brushes): { brushes: [{ id, name, category, size }] } (size: the brush's own diameter in document units).",
|
|
16290
|
+
params: { type: "object", properties: {}, additionalProperties: false },
|
|
16291
|
+
run: () => ({ brushes: vectorBrushstrokeBrushes().map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size })) })
|
|
16292
|
+
},
|
|
16293
|
+
{
|
|
16294
|
+
...base,
|
|
16295
|
+
id: `${prefix}addBrushstroke`,
|
|
16296
|
+
label: "Add Brushstroke\u2026",
|
|
16297
|
+
keywords: ["brushstroke", "brush", "natural media", "pencil line", "chalk", "sketchy", "hand-drawn", "ink stroke"],
|
|
16298
|
+
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 `opacity`. 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).",
|
|
16299
|
+
params: {
|
|
16300
|
+
type: "object",
|
|
16301
|
+
properties: {
|
|
16302
|
+
ids: { ...IDS, description: "Paths or groups (default: the selection)." },
|
|
16303
|
+
brushId: { type: "string", description: 'Brush id from vector.listBrushes, e.g. "pencil-hb".' },
|
|
16304
|
+
size: { type: "number", exclusiveMinimum: 0, maximum: 500, description: "Brush diameter in document units (default: the brush's own)." },
|
|
16305
|
+
pressure: { type: "array", items: { type: "number", minimum: 0, maximum: 1 }, minItems: 1, maxItems: 64, description: "Pressure 0\u20131 along the length, spread evenly from start to end (default: full pressure)." },
|
|
16306
|
+
color: { type: "string", description: "CSS colour (default: the path's stroke colour, else black)." },
|
|
16307
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0\u20131 (default 1)." },
|
|
16308
|
+
index: { type: "integer", minimum: 0, description: "Position in the stroke stack, 0 the bottom (default: on top)." }
|
|
16309
|
+
},
|
|
16310
|
+
required: ["brushId"],
|
|
16311
|
+
additionalProperties: false
|
|
16312
|
+
},
|
|
16313
|
+
undoable: true,
|
|
16314
|
+
enabled: enabledFor,
|
|
16315
|
+
run: (_ctx, params) => {
|
|
16316
|
+
const targets = params.ids ?? controller.getState().selection;
|
|
16317
|
+
check2(
|
|
16318
|
+
controller.addBrushstroke(
|
|
16319
|
+
{ brushId: params.brushId, ...params.size !== void 0 ? { size: params.size } : {}, ...params.pressure ? { pressure: params.pressure } : {} },
|
|
16320
|
+
{ ...params.ids ? { ids: params.ids } : {}, ...params.index !== void 0 ? { index: params.index } : {}, ...params.color ? { color: params.color } : {}, ...params.opacity !== void 0 ? { opacity: params.opacity } : {} }
|
|
16321
|
+
)
|
|
16322
|
+
);
|
|
16323
|
+
const paths = [];
|
|
16324
|
+
for (const id of targets) {
|
|
16325
|
+
const n2 = controller.getNode(id);
|
|
16326
|
+
if (n2?.type === "path") paths.push(stackResult(id, "stroke"));
|
|
16327
|
+
}
|
|
16328
|
+
return { paths };
|
|
16329
|
+
}
|
|
16330
|
+
},
|
|
16331
|
+
{
|
|
16332
|
+
...base,
|
|
16333
|
+
id: `${prefix}setBrushstroke`,
|
|
16334
|
+
label: "Edit Brushstroke\u2026",
|
|
16335
|
+
keywords: ["brushstroke", "brush", "size", "pressure", "taper"],
|
|
16336
|
+
description: "Changes the brushstroke at stroke layer `index` of path `id` (vector.getAppearance lists strokes; 0 is the bottom): `brushId`, `size` (null: the brush's own), `pressure` (null: full pressure), `color`, `opacity`. Pass at least one. One undo step. Returns { id, strokes }.",
|
|
16337
|
+
params: {
|
|
16338
|
+
type: "object",
|
|
16339
|
+
properties: {
|
|
16340
|
+
id: { type: "string", description: "The path." },
|
|
16341
|
+
index: { type: "integer", minimum: 0, description: "Stroke layer index of the brushstroke." },
|
|
16342
|
+
brushId: { type: "string", description: "Another brush from vector.listBrushes." },
|
|
16343
|
+
size: { type: ["number", "null"], description: "Diameter in document units, or null for the brush's own." },
|
|
16344
|
+
pressure: { type: ["array", "null"], items: { type: "number", minimum: 0, maximum: 1 }, description: "Pressure profile 0\u20131 from start to end, or null for full pressure." },
|
|
16345
|
+
color: { type: "string", description: "CSS colour." },
|
|
16346
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0\u20131." }
|
|
16347
|
+
},
|
|
16348
|
+
required: ["id", "index"],
|
|
16349
|
+
additionalProperties: false
|
|
16350
|
+
},
|
|
16351
|
+
undoable: true,
|
|
16352
|
+
run: (_ctx, params) => {
|
|
16353
|
+
const { id, index, ...patch } = params;
|
|
16354
|
+
if (!Object.keys(patch).length) fail("Pass at least one of brushId, size, pressure, color, opacity.");
|
|
16355
|
+
layerIndex(id, "stroke", index);
|
|
16356
|
+
check2(controller.setBrushstroke(index, patch, { ids: [id] }));
|
|
16357
|
+
return stackResult(id, "stroke");
|
|
16358
|
+
}
|
|
16359
|
+
}
|
|
16360
|
+
];
|
|
15645
16361
|
const pdfCommands = () => [
|
|
15646
16362
|
{
|
|
15647
16363
|
...base,
|
|
@@ -15710,7 +16426,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15710
16426
|
undoable: true,
|
|
15711
16427
|
run: (_ctx, params) => {
|
|
15712
16428
|
const r = controller.importLottie(params.lottie, { ...params.frame !== void 0 ? { frame: params.frame } : {}, ...params.replace ? { replace: true } : {} });
|
|
15713
|
-
|
|
16429
|
+
check2(r);
|
|
15714
16430
|
return { ids: r.ids, notes: r.notes };
|
|
15715
16431
|
}
|
|
15716
16432
|
}
|
|
@@ -15754,7 +16470,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15754
16470
|
return fail(`The PNG could not be read: ${e.message}`);
|
|
15755
16471
|
}
|
|
15756
16472
|
const r = controller.traceImage(decoded, options2);
|
|
15757
|
-
|
|
16473
|
+
check2(r);
|
|
15758
16474
|
return {
|
|
15759
16475
|
id: r.id,
|
|
15760
16476
|
paths: r.ids.map((id) => {
|
|
@@ -15765,6 +16481,70 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15765
16481
|
}
|
|
15766
16482
|
}
|
|
15767
16483
|
];
|
|
16484
|
+
const IMAGE_PLACE = {
|
|
16485
|
+
image: { type: "string", description: "The picture as a data:image/png;base64,\u2026 URL (up to 4 million pixels)." },
|
|
16486
|
+
x: { type: "number", description: "Left edge in document units (default 0)." },
|
|
16487
|
+
y: { type: "number", description: "Top edge in document units (default 0)." },
|
|
16488
|
+
width: { type: "number", exclusiveMinimum: 0, description: "Width in document units (default: the image width in pixels)." },
|
|
16489
|
+
name: { type: "string", description: "Group name (default: the style's name)." }
|
|
16490
|
+
};
|
|
16491
|
+
const decodeImage = (image) => {
|
|
16492
|
+
const parsed = parseDataUrl(image);
|
|
16493
|
+
if (!parsed || !isPng(parsed.bytes)) fail("image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
16494
|
+
try {
|
|
16495
|
+
return decodePng(parsed.bytes, { maxPixels: VECTOR_TRACE_MAX_PIXELS });
|
|
16496
|
+
} catch (e) {
|
|
16497
|
+
return fail(`The PNG could not be read: ${e.message}`);
|
|
16498
|
+
}
|
|
16499
|
+
};
|
|
16500
|
+
const stylize = (style, label2, keywords, description, props) => ({
|
|
16501
|
+
...base,
|
|
16502
|
+
id: `${prefix}${style}Image`,
|
|
16503
|
+
label: label2,
|
|
16504
|
+
keywords: ["stylize", "stylise", "bitmap to vector", "artistic", "png to svg", ...keywords],
|
|
16505
|
+
description: `${description} Added as one group placed with its top-left at x, y and width wide; colours of one kind share a compound path. One undo step. Returns { id, paths: [{ id, name, fill, subpaths }] }.`,
|
|
16506
|
+
params: { type: "object", properties: { ...IMAGE_PLACE, ...props }, required: ["image"], additionalProperties: false },
|
|
16507
|
+
undoable: true,
|
|
16508
|
+
run: (_ctx, params) => {
|
|
16509
|
+
const { image, ...options2 } = params;
|
|
16510
|
+
const r = controller.stylizeImage(decodeImage(image), style, options2);
|
|
16511
|
+
check2(r);
|
|
16512
|
+
return {
|
|
16513
|
+
id: r.id,
|
|
16514
|
+
paths: r.ids.map((id) => {
|
|
16515
|
+
const n2 = controller.getNode(id);
|
|
16516
|
+
return { id, name: n2.name, fill: n2.fill, subpaths: n2.subpaths.length };
|
|
16517
|
+
})
|
|
16518
|
+
};
|
|
16519
|
+
}
|
|
16520
|
+
});
|
|
16521
|
+
const CELL = (dflt, what) => ({ type: "number", minimum: 1, description: `${what} in image pixels (default ${dflt}); larger draws fewer, bigger shapes.` });
|
|
16522
|
+
const PALETTE = (dflt) => ({ type: "integer", minimum: 2, maximum: 32, description: `Palette size (default ${dflt}).` });
|
|
16523
|
+
const stylizeCommands = () => [
|
|
16524
|
+
stylize("mosaic", "Mosaic from Image\u2026", ["mosaic", "tiles", "pixelate", "pixel art"], "Turns a PNG into mosaic tiles: a square or round tile per cell in the colour under it.", {
|
|
16525
|
+
cell: CELL(12, "Tile size"),
|
|
16526
|
+
shape: { type: "string", enum: ["square", "circle"], description: "Tile shape (default square)." },
|
|
16527
|
+
gap: { type: "number", minimum: 0, maximum: 0.9, description: "Grout between tiles as a fraction of the tile (default 0.1)." },
|
|
16528
|
+
colors: PALETTE(12)
|
|
16529
|
+
}),
|
|
16530
|
+
stylize("halftone", "Halftone from Image\u2026", ["halftone", "dots", "comic", "pop art", "screen print"], "Turns a PNG into halftone dots on a turned grid, each dot's area following how dark the picture is there.", {
|
|
16531
|
+
cell: CELL(8, "Dot spacing"),
|
|
16532
|
+
angle: { type: "number", description: "Screen angle in degrees (default 45)." },
|
|
16533
|
+
color: { type: ["string", "null"], description: "Ink colour (default black); null keeps the picture's colours, reduced to `colors`." },
|
|
16534
|
+
colors: PALETTE(12)
|
|
16535
|
+
}),
|
|
16536
|
+
stylize("lowPoly", "Low Poly from Image\u2026", ["low poly", "triangles", "polygon art", "geometric", "faceted"], "Turns a PNG into low-poly art: a jittered grid of triangles, each in the colour under it.", {
|
|
16537
|
+
cell: CELL(24, "Triangle size"),
|
|
16538
|
+
jitter: { type: "number", minimum: 0, maximum: 0.5, description: "How far the grid points wander, as a fraction of a cell (default 0.35)." },
|
|
16539
|
+
seed: { type: "integer", description: "Seed of the wandering (default 1): the same seed gives the same triangles." },
|
|
16540
|
+
colors: PALETTE(16)
|
|
16541
|
+
}),
|
|
16542
|
+
stylize("hatch", "Engraving from Image\u2026", ["engraving", "hatch", "hatching", "line art", "woodcut", "banknote"], "Turns a PNG into an engraving: parallel lines on a turned grid that swell where the picture is dark and break where it is light.", {
|
|
16543
|
+
cell: CELL(6, "Line spacing"),
|
|
16544
|
+
angle: { type: "number", description: "Line angle in degrees (default 45)." },
|
|
16545
|
+
color: { type: "string", description: "Ink colour (default black)." }
|
|
16546
|
+
})
|
|
16547
|
+
];
|
|
15768
16548
|
const shadingResult = (id) => ({ id, shading: controller.getShading(id) ?? fail(`"${id}" has no mesh or freeform fill.`) });
|
|
15769
16549
|
const COLOR = { type: "string", description: "A CSS colour." };
|
|
15770
16550
|
const FREEFORM_POINT = {
|
|
@@ -15813,7 +16593,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15813
16593
|
enabled: enabledFor,
|
|
15814
16594
|
run: (_ctx, params) => {
|
|
15815
16595
|
const list2 = ids(params);
|
|
15816
|
-
|
|
16596
|
+
check2(controller.setMeshGradient({ rows: params.rows, columns: params.columns, ...params.colors ? { colors: params.colors } : {}, ...params.corners ? { corners: params.corners } : {} }, { ids: list2 }));
|
|
15817
16597
|
return { meshes: list2.map(shadingResult) };
|
|
15818
16598
|
}
|
|
15819
16599
|
},
|
|
@@ -15840,7 +16620,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15840
16620
|
run: (_ctx, params) => {
|
|
15841
16621
|
const { id, index, ...patch } = params;
|
|
15842
16622
|
if (!Object.keys(patch).length) fail("Pass at least one of x / y, color, opacity.");
|
|
15843
|
-
|
|
16623
|
+
check2(controller.setMeshPoint(id, index, patch));
|
|
15844
16624
|
return shadingResult(id);
|
|
15845
16625
|
}
|
|
15846
16626
|
},
|
|
@@ -15865,7 +16645,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15865
16645
|
run: (_ctx, params) => {
|
|
15866
16646
|
const { ids: _i, ...input } = params ?? {};
|
|
15867
16647
|
const list2 = ids(params);
|
|
15868
|
-
|
|
16648
|
+
check2(controller.setFreeformGradient(input, { ids: list2 }));
|
|
15869
16649
|
return { gradients: list2.map(shadingResult) };
|
|
15870
16650
|
}
|
|
15871
16651
|
},
|
|
@@ -15885,7 +16665,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15885
16665
|
run: (_ctx, params) => {
|
|
15886
16666
|
const { id, ...point } = params;
|
|
15887
16667
|
const r = controller.addFreeformPoint(id, point);
|
|
15888
|
-
|
|
16668
|
+
check2(r);
|
|
15889
16669
|
return { ...shadingResult(id), index: r.index };
|
|
15890
16670
|
}
|
|
15891
16671
|
},
|
|
@@ -15913,7 +16693,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15913
16693
|
run: (_ctx, params) => {
|
|
15914
16694
|
const { id, index, ...patch } = params;
|
|
15915
16695
|
if (!Object.keys(patch).length) fail("Pass at least one of x / y, color, opacity, spread.");
|
|
15916
|
-
|
|
16696
|
+
check2(controller.setFreeformPoint(id, index, patch));
|
|
15917
16697
|
return shadingResult(id);
|
|
15918
16698
|
}
|
|
15919
16699
|
},
|
|
@@ -15926,7 +16706,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15926
16706
|
params: { type: "object", properties: { id: { type: "string", description: "The path." }, index: { type: "integer", minimum: 0, description: "Point index." } }, required: ["id", "index"], additionalProperties: false },
|
|
15927
16707
|
undoable: true,
|
|
15928
16708
|
run: (_ctx, params) => {
|
|
15929
|
-
|
|
16709
|
+
check2(controller.removeFreeformPoint(params.id, params.index));
|
|
15930
16710
|
return shadingResult(params.id);
|
|
15931
16711
|
}
|
|
15932
16712
|
},
|
|
@@ -15939,7 +16719,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15939
16719
|
params: { type: "object", properties: { id: { type: "string", description: "The path." }, lines: LINES }, required: ["id", "lines"], additionalProperties: false },
|
|
15940
16720
|
undoable: true,
|
|
15941
16721
|
run: (_ctx, params) => {
|
|
15942
|
-
|
|
16722
|
+
check2(controller.setFreeformLines(params.id, params.lines));
|
|
15943
16723
|
return shadingResult(params.id);
|
|
15944
16724
|
}
|
|
15945
16725
|
}
|
|
@@ -16115,7 +16895,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16115
16895
|
const { ids: list2, ...patch } = params ?? {};
|
|
16116
16896
|
if (!Object.keys(patch).length) fail("Pass at least one of start, end, route, radius, gap.");
|
|
16117
16897
|
const targets = ids({ ids: list2 });
|
|
16118
|
-
|
|
16898
|
+
check2(controller.setConnector(patch, { ids: targets }));
|
|
16119
16899
|
return { connectors: targets.map(connectorResult) };
|
|
16120
16900
|
}
|
|
16121
16901
|
}
|
|
@@ -16151,7 +16931,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16151
16931
|
undoable: true,
|
|
16152
16932
|
run: (_ctx, params) => {
|
|
16153
16933
|
const r = controller.addPage(params ?? {});
|
|
16154
|
-
|
|
16934
|
+
check2(r);
|
|
16155
16935
|
return { id: r.id, ...pagesResult() };
|
|
16156
16936
|
}
|
|
16157
16937
|
},
|
|
@@ -16172,7 +16952,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16172
16952
|
undoable: true,
|
|
16173
16953
|
run: (_ctx, params) => {
|
|
16174
16954
|
const r = controller.addMasterPage(params ?? {});
|
|
16175
|
-
|
|
16955
|
+
check2(r);
|
|
16176
16956
|
return { id: r.id, ...pagesResult() };
|
|
16177
16957
|
}
|
|
16178
16958
|
},
|
|
@@ -16185,7 +16965,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16185
16965
|
params: { type: "object", properties: { ids: { ...IDS, description: "Page ids (default: the selected pages)." }, masterId: { type: ["string", "null"], description: "Master page id, or null for none." } }, required: ["masterId"], additionalProperties: false },
|
|
16186
16966
|
undoable: true,
|
|
16187
16967
|
run: (_ctx, params) => {
|
|
16188
|
-
|
|
16968
|
+
check2(controller.setPageMaster(params.masterId, params.ids ? { ids: params.ids } : {}));
|
|
16189
16969
|
return pagesResult();
|
|
16190
16970
|
}
|
|
16191
16971
|
},
|
|
@@ -16198,7 +16978,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16198
16978
|
params: { type: "object", properties: { id: { type: "string", description: "The page." }, index: { type: "integer", minimum: 0, description: "New position, 0 for first." } }, required: ["id", "index"], additionalProperties: false },
|
|
16199
16979
|
undoable: true,
|
|
16200
16980
|
run: (_ctx, params) => {
|
|
16201
|
-
|
|
16981
|
+
check2(controller.movePage(params.id, params.index));
|
|
16202
16982
|
return pagesResult();
|
|
16203
16983
|
}
|
|
16204
16984
|
},
|
|
@@ -16220,7 +17000,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16220
17000
|
},
|
|
16221
17001
|
run: (_ctx, params) => {
|
|
16222
17002
|
const size = params.canvasWidth && params.canvasHeight ? { width: params.canvasWidth, height: params.canvasHeight } : void 0;
|
|
16223
|
-
|
|
17003
|
+
check2(controller.goToPage(params.id, size ? { size } : {}));
|
|
16224
17004
|
return { id: params.id };
|
|
16225
17005
|
}
|
|
16226
17006
|
}
|
|
@@ -16314,7 +17094,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16314
17094
|
const { ids: list2, ...patch } = params ?? {};
|
|
16315
17095
|
if (!Object.keys(patch).length) fail("Pass at least one of x, y, width, height, fill, clip.");
|
|
16316
17096
|
const before = new Set(controller.getFrames().map((f) => f.id));
|
|
16317
|
-
|
|
17097
|
+
check2(controller.setFrame(patch, list2 ? { ids: list2 } : {}));
|
|
16318
17098
|
const targets = list2 ?? controller.getFrames().filter((f) => before.has(f.id) && (controller.getState().selection.includes(f.id) || f.children.some((c) => controller.getState().selection.includes(c.id)))).map((f) => f.id);
|
|
16319
17099
|
return { frames: targets.map(frameResult) };
|
|
16320
17100
|
}
|
|
@@ -16338,7 +17118,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16338
17118
|
enabled: enabledFor,
|
|
16339
17119
|
run: (_ctx, params) => {
|
|
16340
17120
|
const list2 = ids(params);
|
|
16341
|
-
|
|
17121
|
+
check2(controller.setResizing({ ...params?.horizontal ? { horizontal: params.horizontal } : {}, ...params?.vertical ? { vertical: params.vertical } : {} }, { ids: list2 }));
|
|
16342
17122
|
return { nodes: list2.map((id) => ({ id, horizontal: controller.getNode(id)?.resizing?.horizontal ?? "left", vertical: controller.getNode(id)?.resizing?.vertical ?? "top" })) };
|
|
16343
17123
|
}
|
|
16344
17124
|
},
|
|
@@ -16397,7 +17177,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16397
17177
|
undoable: true,
|
|
16398
17178
|
run: (_ctx, params) => {
|
|
16399
17179
|
const r = controller.addConstraint(params);
|
|
16400
|
-
|
|
17180
|
+
check2(r);
|
|
16401
17181
|
return { id: r.id, constraint: controller.getConstraints().find((c) => c.id === r.id) };
|
|
16402
17182
|
}
|
|
16403
17183
|
},
|
|
@@ -16410,7 +17190,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16410
17190
|
params: { type: "object", properties: { ids: { type: "array", items: { type: "string" }, minItems: 1, description: "Constraint ids." } }, required: ["ids"], additionalProperties: false },
|
|
16411
17191
|
undoable: true,
|
|
16412
17192
|
run: (_ctx, params) => {
|
|
16413
|
-
|
|
17193
|
+
check2(controller.removeConstraints(params.ids));
|
|
16414
17194
|
return { ids: params.ids };
|
|
16415
17195
|
}
|
|
16416
17196
|
},
|
|
@@ -16431,7 +17211,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16431
17211
|
},
|
|
16432
17212
|
undoable: true,
|
|
16433
17213
|
run: (_ctx, params) => {
|
|
16434
|
-
|
|
17214
|
+
check2(controller.setConstraintValue(params.key, params.value));
|
|
16435
17215
|
return constraintsSummary();
|
|
16436
17216
|
}
|
|
16437
17217
|
},
|
|
@@ -16454,7 +17234,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16454
17234
|
undoable: true,
|
|
16455
17235
|
run: (_ctx, params) => {
|
|
16456
17236
|
const r = controller.autoConstrain(params ?? {});
|
|
16457
|
-
|
|
17237
|
+
check2(r);
|
|
16458
17238
|
return { ids: r.ids };
|
|
16459
17239
|
}
|
|
16460
17240
|
}
|
|
@@ -16544,7 +17324,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16544
17324
|
run: (_ctx, params) => {
|
|
16545
17325
|
const style = pickStyle(params);
|
|
16546
17326
|
if (!Object.keys(style).length) fail("Pass at least one style property.");
|
|
16547
|
-
|
|
17327
|
+
check2(controller.setStyle(style, ids(params)));
|
|
16548
17328
|
return { ok: true };
|
|
16549
17329
|
}
|
|
16550
17330
|
},
|
|
@@ -16561,7 +17341,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16561
17341
|
const { ids: _i, target, ...patch } = params ?? {};
|
|
16562
17342
|
if (patch.type !== void 0 && !VECTOR_GRADIENT_TYPES.includes(patch.type)) fail(`Unknown gradient type "${patch.type}".`);
|
|
16563
17343
|
if (patch.stops !== void 0 && !(Array.isArray(patch.stops) && patch.stops.length)) fail("stops needs at least one { offset, color }.");
|
|
16564
|
-
|
|
17344
|
+
check2(controller.setGradient(patch, { ids: ids(params), target: target ?? "fill" }));
|
|
16565
17345
|
const id = ids(params)[0];
|
|
16566
17346
|
const node = id ? controller.getNode(id) : void 0;
|
|
16567
17347
|
const path = node?.type === "path" ? node : void 0;
|
|
@@ -16582,6 +17362,8 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16582
17362
|
...lottieCommands(),
|
|
16583
17363
|
...pdfCommands(),
|
|
16584
17364
|
...symmetryCommands(),
|
|
17365
|
+
...brushstrokeCommands(),
|
|
17366
|
+
...stylizeCommands(),
|
|
16585
17367
|
...perspectiveCommands(),
|
|
16586
17368
|
...lensCommands(),
|
|
16587
17369
|
{
|
|
@@ -16604,9 +17386,9 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16604
17386
|
run: (_ctx, params) => {
|
|
16605
17387
|
const refs = params?.anchors ? anchorsOf(params.anchors) : void 0;
|
|
16606
17388
|
const target = { refs, ids: params?.ids ? ids(params) : void 0 };
|
|
16607
|
-
if (params?.radius !== void 0)
|
|
16608
|
-
else if (params?.kind)
|
|
16609
|
-
if (params?.radius === void 0 && params?.smoothing !== void 0)
|
|
17389
|
+
if (params?.radius !== void 0) check2(controller.setCornerRadius(params.radius, { ...target, kind: params.kind, ...params.smoothing !== void 0 ? { smoothing: params.smoothing } : {} }));
|
|
17390
|
+
else if (params?.kind) check2(controller.setCornerKind(params.kind, target));
|
|
17391
|
+
if (params?.radius === void 0 && params?.smoothing !== void 0) check2(controller.setCornerSmoothing(params.smoothing, target));
|
|
16610
17392
|
if (params?.radius === void 0 && !params?.kind && params?.smoothing === void 0) fail("Pass `radius`, `kind` and / or `smoothing`.");
|
|
16611
17393
|
return { ok: true };
|
|
16612
17394
|
}
|
|
@@ -16641,9 +17423,9 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16641
17423
|
undoable: true,
|
|
16642
17424
|
run: (_ctx, params) => {
|
|
16643
17425
|
const target = { ids: ids(params) };
|
|
16644
|
-
if (params?.preset)
|
|
16645
|
-
else if (params?.points !== void 0)
|
|
16646
|
-
else if (params?.taper || params?.wave)
|
|
17426
|
+
if (params?.preset) check2(controller.setWidthProfile(params.preset, target));
|
|
17427
|
+
else if (params?.points !== void 0) check2(controller.setWidthProfile(params.points ?? null, target));
|
|
17428
|
+
else if (params?.taper || params?.wave) check2(controller.setWidthProfile({ taper: params.taper, wave: params.wave }, target));
|
|
16647
17429
|
else fail("Pass `preset`, `points`, `taper` or `wave`.");
|
|
16648
17430
|
const node = controller.getNode(target.ids[0]);
|
|
16649
17431
|
return { ok: true, points: node?.type === "path" ? node.strokeWidthProfile ?? [] : [] };
|
|
@@ -16669,7 +17451,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16669
17451
|
undoable: true,
|
|
16670
17452
|
run: (_ctx, params) => {
|
|
16671
17453
|
if (!params?.id) fail("Pass the path `id`.");
|
|
16672
|
-
|
|
17454
|
+
check2(controller.setWidthPoint({ nodeId: params.id, index: params.index ?? null }, { t: params.t, left: params.left, right: params.right }));
|
|
16673
17455
|
const node = controller.getNode(params.id);
|
|
16674
17456
|
return { ok: true, points: node?.type === "path" ? node.strokeWidthProfile ?? [] : [] };
|
|
16675
17457
|
}
|
|
@@ -16707,7 +17489,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16707
17489
|
enabled: enabledFor,
|
|
16708
17490
|
undoable: true,
|
|
16709
17491
|
run: (_ctx, params) => {
|
|
16710
|
-
|
|
17492
|
+
check2(controller.distort(params.corners, { ids: ids(params), tolerance: params.tolerance }));
|
|
16711
17493
|
const frame = controller.getState().selectionFrame;
|
|
16712
17494
|
return { ok: true, bounds: frame ? { x: frame.x, y: frame.y, width: frame.width, height: frame.height } : null };
|
|
16713
17495
|
}
|
|
@@ -16730,7 +17512,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16730
17512
|
enabled: enabledFor,
|
|
16731
17513
|
undoable: true,
|
|
16732
17514
|
run: (_ctx, params) => {
|
|
16733
|
-
|
|
17515
|
+
check2(controller.skew(params.ax ?? 0, params.ay ?? 0, { ids: ids(params), origin: params.origin }));
|
|
16734
17516
|
return { ok: true };
|
|
16735
17517
|
}
|
|
16736
17518
|
},
|
|
@@ -16770,7 +17552,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16770
17552
|
if (params.expand) return { expanded: controller.expandAppearance({ ids: target }) };
|
|
16771
17553
|
if (params.topObject) {
|
|
16772
17554
|
const r = controller.makeEnvelopeWithTopObject({ ids: target, rows: params.mesh?.rows, columns: params.mesh?.columns });
|
|
16773
|
-
|
|
17555
|
+
check2(r);
|
|
16774
17556
|
return { nodeId: r.nodeId };
|
|
16775
17557
|
}
|
|
16776
17558
|
if (params.preset !== void 0 && !WARP_STYLES.includes(params.preset)) fail(`Unknown warp preset "${params.preset}".`);
|
|
@@ -16819,10 +17601,10 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16819
17601
|
undoable: true,
|
|
16820
17602
|
run: (_ctx, params) => {
|
|
16821
17603
|
const target = ids(params);
|
|
16822
|
-
if (params.dx || params.dy)
|
|
16823
|
-
if (params.sx !== void 0 || params.sy !== void 0)
|
|
16824
|
-
if (params.rotate)
|
|
16825
|
-
if (params.angle !== void 0)
|
|
17604
|
+
if (params.dx || params.dy) check2(controller.translate(params.dx ?? 0, params.dy ?? 0, { ids: target }));
|
|
17605
|
+
if (params.sx !== void 0 || params.sy !== void 0) check2(controller.scale(params.sx ?? 1, params.sy ?? params.sx ?? 1, { ids: target }));
|
|
17606
|
+
if (params.rotate) check2(controller.rotate(params.rotate, { ids: target }));
|
|
17607
|
+
if (params.angle !== void 0) check2(controller.setRotation(params.angle, { ids: target }));
|
|
16826
17608
|
return { ok: true };
|
|
16827
17609
|
}
|
|
16828
17610
|
},
|
|
@@ -16836,7 +17618,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16836
17618
|
enabled: enabledFor,
|
|
16837
17619
|
undoable: true,
|
|
16838
17620
|
run: (_ctx, params) => {
|
|
16839
|
-
|
|
17621
|
+
check2(controller.removeNodes(ids(params)));
|
|
16840
17622
|
return { ok: true };
|
|
16841
17623
|
}
|
|
16842
17624
|
},
|
|
@@ -16907,8 +17689,8 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16907
17689
|
},
|
|
16908
17690
|
undoable: true,
|
|
16909
17691
|
run: (_ctx, params) => {
|
|
16910
|
-
if (params.params !== void 0 || params.enabled !== void 0)
|
|
16911
|
-
if (params.index !== void 0)
|
|
17692
|
+
if (params.params !== void 0 || params.enabled !== void 0) check2(controller.updateEffect(params.nodeId, params.effectId, { params: params.params, enabled: params.enabled }));
|
|
17693
|
+
if (params.index !== void 0) check2(controller.moveEffect(params.nodeId, params.effectId, params.index));
|
|
16912
17694
|
return { effects: controller.getNode(params.nodeId)?.effects ?? [] };
|
|
16913
17695
|
}
|
|
16914
17696
|
},
|
|
@@ -16921,7 +17703,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
16921
17703
|
params: { type: "object", properties: { nodeId: { type: "string" }, effectId: { type: "string" } }, required: ["nodeId", "effectId"] },
|
|
16922
17704
|
undoable: true,
|
|
16923
17705
|
run: (_ctx, params) => {
|
|
16924
|
-
|
|
17706
|
+
check2(controller.removeEffect(params.nodeId, params.effectId));
|
|
16925
17707
|
return { ok: true };
|
|
16926
17708
|
}
|
|
16927
17709
|
},
|
|
@@ -17058,7 +17840,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17058
17840
|
undoable: true,
|
|
17059
17841
|
run: (_ctx, params) => {
|
|
17060
17842
|
const result = controller.shapeBuilder(params.points.map(([x, y]) => ({ x, y })), { ids: ids(params), erase: params.erase, tolerance: params.tolerance });
|
|
17061
|
-
|
|
17843
|
+
check2(result);
|
|
17062
17844
|
return { merged: result.merged, faces: result.faces, lines: result.lines, edges: result.edges };
|
|
17063
17845
|
}
|
|
17064
17846
|
},
|
|
@@ -17089,7 +17871,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17089
17871
|
},
|
|
17090
17872
|
undoable: true,
|
|
17091
17873
|
run: (_ctx, params) => {
|
|
17092
|
-
|
|
17874
|
+
check2(controller.align(params.edge, { ids: ids(params), to: params.to }));
|
|
17093
17875
|
return { ok: true };
|
|
17094
17876
|
}
|
|
17095
17877
|
},
|
|
@@ -17102,7 +17884,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17102
17884
|
params: { type: "object", properties: { direction: { type: "string", enum: ["horizontal", "vertical"] }, ids: IDS }, required: ["direction"] },
|
|
17103
17885
|
undoable: true,
|
|
17104
17886
|
run: (_ctx, params) => {
|
|
17105
|
-
|
|
17887
|
+
check2(controller.distribute(params.direction, { ids: ids(params) }));
|
|
17106
17888
|
return { ok: true };
|
|
17107
17889
|
}
|
|
17108
17890
|
},
|
|
@@ -17115,7 +17897,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17115
17897
|
params: { type: "object", properties: { order: { type: "string", enum: ["front", "forward", "backward", "back"] }, ids: IDS }, required: ["order"] },
|
|
17116
17898
|
undoable: true,
|
|
17117
17899
|
run: (_ctx, params) => {
|
|
17118
|
-
|
|
17900
|
+
check2(controller.arrange(params.order, { ids: ids(params) }));
|
|
17119
17901
|
return { ok: true };
|
|
17120
17902
|
}
|
|
17121
17903
|
},
|
|
@@ -17144,7 +17926,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17144
17926
|
enabled: enabledFor,
|
|
17145
17927
|
undoable: true,
|
|
17146
17928
|
run: (_ctx, params) => {
|
|
17147
|
-
|
|
17929
|
+
check2(controller.ungroup({ ids: ids(params) }));
|
|
17148
17930
|
return { selection: controller.getState().selection };
|
|
17149
17931
|
}
|
|
17150
17932
|
},
|
|
@@ -17157,7 +17939,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17157
17939
|
params: { type: "object", properties: { anchors: ANCHORS, keepShape: { type: "boolean" } } },
|
|
17158
17940
|
undoable: true,
|
|
17159
17941
|
run: (_ctx, params) => {
|
|
17160
|
-
|
|
17942
|
+
check2(controller.deleteAnchors(anchorsOf(params?.anchors), { keepShape: params?.keepShape }));
|
|
17161
17943
|
return { ok: true };
|
|
17162
17944
|
}
|
|
17163
17945
|
},
|
|
@@ -17174,7 +17956,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17174
17956
|
},
|
|
17175
17957
|
undoable: true,
|
|
17176
17958
|
run: (_ctx, params) => {
|
|
17177
|
-
|
|
17959
|
+
check2(controller.moveAnchor({ nodeId: params.nodeId, subpath: params.subpath, index: params.index }, { x: params.x, y: params.y }, { curvature: params.curvature }));
|
|
17178
17960
|
return { ok: true };
|
|
17179
17961
|
}
|
|
17180
17962
|
},
|
|
@@ -17198,7 +17980,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17198
17980
|
},
|
|
17199
17981
|
undoable: true,
|
|
17200
17982
|
run: (_ctx, params) => {
|
|
17201
|
-
|
|
17983
|
+
check2(controller.bendSegment({ nodeId: params.nodeId, subpath: params.subpath, segment: params.segment }, { x: params.x, y: params.y }, params.t));
|
|
17202
17984
|
return { ok: true };
|
|
17203
17985
|
}
|
|
17204
17986
|
},
|
|
@@ -17212,7 +17994,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17212
17994
|
params: { type: "object", properties: { ids: IDS, tolerance: { type: "number", minimum: 0 } } },
|
|
17213
17995
|
undoable: true,
|
|
17214
17996
|
run: (_ctx, params) => {
|
|
17215
|
-
|
|
17997
|
+
check2(controller.joinPaths({ ids: params?.ids ? ids(params) : void 0, tolerance: params?.tolerance }));
|
|
17216
17998
|
return { selection: controller.getState().selection };
|
|
17217
17999
|
}
|
|
17218
18000
|
},
|
|
@@ -17226,7 +18008,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17226
18008
|
params: { type: "object", properties: { axis: { type: "string", enum: ["horizontal", "vertical", "both"] }, anchors: ANCHORS } },
|
|
17227
18009
|
undoable: true,
|
|
17228
18010
|
run: (_ctx, params) => {
|
|
17229
|
-
|
|
18011
|
+
check2(controller.averageAnchors(params?.axis ?? "both", anchorsOf(params?.anchors)));
|
|
17230
18012
|
return { ok: true };
|
|
17231
18013
|
}
|
|
17232
18014
|
},
|
|
@@ -17240,7 +18022,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17240
18022
|
enabled: enabledFor,
|
|
17241
18023
|
undoable: true,
|
|
17242
18024
|
run: (_ctx, params) => {
|
|
17243
|
-
|
|
18025
|
+
check2(controller.removeRedundantPoints({ ids: params?.ids ? ids(params) : void 0, tolerance: params?.tolerance }));
|
|
17244
18026
|
return { ok: true };
|
|
17245
18027
|
}
|
|
17246
18028
|
},
|
|
@@ -17253,7 +18035,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17253
18035
|
params: { type: "object", properties: { ids: IDS, amount: { type: "number", minimum: 0, maximum: 1 }, anchors: ANCHORS } },
|
|
17254
18036
|
undoable: true,
|
|
17255
18037
|
run: (_ctx, params) => {
|
|
17256
|
-
|
|
18038
|
+
check2(controller.smoothPaths({ ids: params?.ids ? ids(params) : void 0, amount: params?.amount, refs: params?.anchors }));
|
|
17257
18039
|
return { ok: true };
|
|
17258
18040
|
}
|
|
17259
18041
|
},
|
|
@@ -17273,7 +18055,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17273
18055
|
if (!created) fail("Nothing to cut there (the end of an open path, or a locked path).");
|
|
17274
18056
|
return { ids: created };
|
|
17275
18057
|
}
|
|
17276
|
-
|
|
18058
|
+
check2(controller.cutAtAnchors(anchorsOf(params?.anchors)));
|
|
17277
18059
|
return { ids: controller.getState().selection };
|
|
17278
18060
|
}
|
|
17279
18061
|
},
|
|
@@ -17305,12 +18087,13 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17305
18087
|
return { ids: changed.filter((id) => controller.getNode(id)) };
|
|
17306
18088
|
}
|
|
17307
18089
|
},
|
|
18090
|
+
...distortCommands(),
|
|
17308
18091
|
{
|
|
17309
18092
|
...base,
|
|
17310
18093
|
id: `${prefix}setEditing`,
|
|
17311
18094
|
label: "Path Editing Options\u2026",
|
|
17312
18095
|
keywords: ["settings", "curvature", "pencil", "smoothing", "lazy mouse", "shapes", "eraser"],
|
|
17313
|
-
description: "Sets how editing behaves: `keepShapeOnDelete`, `curvatureMoves` (anchor drags bend like the Curvature tool), `pencilSmoothing` (lazy mouse, 0\u20131), `recognizeShapes` (pencil scribbles become lines, rectangles, ellipses, polygons), `eraserSize` and `pencilPressure` (`pen`: record pen pressure on pencil strokes as a width profile, `all`: any pointer reporting pressure, `off`).",
|
|
18096
|
+
description: "Sets how editing behaves: `keepShapeOnDelete`, `curvatureMoves` (anchor drags bend like the Curvature tool), `pencilSmoothing` (lazy mouse, 0\u20131), `recognizeShapes` (pencil scribbles become lines, rectangles, ellipses, polygons), `eraserSize`, the Distort Brush settings `distortMode` (smudge, twirl, attract, repel, roughen), `distortSize` (radius) and `distortStrength` (0\u20131), and `pencilPressure` (`pen`: record pen pressure on pencil strokes as a width profile, `all`: any pointer reporting pressure, `off`).",
|
|
17314
18097
|
params: {
|
|
17315
18098
|
type: "object",
|
|
17316
18099
|
properties: {
|
|
@@ -17319,12 +18102,15 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17319
18102
|
pencilSmoothing: { type: "number", minimum: 0, maximum: 1 },
|
|
17320
18103
|
recognizeShapes: { type: "boolean" },
|
|
17321
18104
|
eraserSize: { type: "number", exclusiveMinimum: 0 },
|
|
17322
|
-
pencilPressure: { type: "string", enum: ["pen", "all", "off"] }
|
|
18105
|
+
pencilPressure: { type: "string", enum: ["pen", "all", "off"] },
|
|
18106
|
+
distortMode: { type: "string", enum: [...VECTOR_DISTORT_BRUSH_MODES], description: "What the Distort Brush tool does when the user drags it." },
|
|
18107
|
+
distortSize: { type: "number", exclusiveMinimum: 0, description: "Distort Brush radius, document units." },
|
|
18108
|
+
distortStrength: { type: "number", minimum: 0, maximum: 1, description: "Distort Brush strength, 0\u20131." }
|
|
17323
18109
|
}
|
|
17324
18110
|
},
|
|
17325
18111
|
run: (_ctx, params) => {
|
|
17326
18112
|
const patch = {};
|
|
17327
|
-
for (const key of ["keepShapeOnDelete", "curvatureMoves", "pencilSmoothing", "recognizeShapes", "eraserSize", "pencilPressure"]) if (params?.[key] !== void 0) patch[key] = params[key];
|
|
18113
|
+
for (const key of ["keepShapeOnDelete", "curvatureMoves", "pencilSmoothing", "recognizeShapes", "eraserSize", "pencilPressure", "distortMode", "distortSize", "distortStrength"]) if (params?.[key] !== void 0) patch[key] = params[key];
|
|
17328
18114
|
controller.setEditing(patch);
|
|
17329
18115
|
return { editing: controller.getState().editing };
|
|
17330
18116
|
}
|
|
@@ -17360,13 +18146,227 @@ function createVectorCommands(controller, options = {}) {
|
|
|
17360
18146
|
params: { type: "object", properties: { svg: { type: "string" }, replace: { type: "boolean" } }, required: ["svg"] },
|
|
17361
18147
|
undoable: true,
|
|
17362
18148
|
run: (_ctx, params) => {
|
|
17363
|
-
|
|
18149
|
+
check2(params.replace ? controller.loadSvg(params.svg) : controller.importSvg(params.svg));
|
|
17364
18150
|
return { nodes: controller.getState().document.nodes.length, selection: controller.getState().selection };
|
|
17365
18151
|
}
|
|
17366
18152
|
}
|
|
17367
18153
|
];
|
|
17368
18154
|
}
|
|
17369
18155
|
|
|
18156
|
+
// src/vector/generate.ts
|
|
18157
|
+
var VECTOR_GENERATED_IMAGE_AS = ["color", "threshold", ...VECTOR_STYLIZE_STYLES];
|
|
18158
|
+
function vectorArtModels(models) {
|
|
18159
|
+
return models.filter((m) => m.media === "image" || m.media === "text");
|
|
18160
|
+
}
|
|
18161
|
+
var listModels = (o) => vectorArtModels(typeof o.models === "function" ? o.models() : o.models);
|
|
18162
|
+
function artNodes(art, as, colors) {
|
|
18163
|
+
if (!art || typeof art !== "object") return "The generator returned nothing usable (expected { svg }, { image } or { png }).";
|
|
18164
|
+
if ("svg" in art && typeof art.svg === "string") {
|
|
18165
|
+
let doc;
|
|
18166
|
+
try {
|
|
18167
|
+
doc = parseVectorSvg(art.svg);
|
|
18168
|
+
} catch (e) {
|
|
18169
|
+
return `The generated SVG could not be read: ${e.message}`;
|
|
18170
|
+
}
|
|
18171
|
+
if (!doc.nodes.length) return "The generated SVG has no shapes the editor can read.";
|
|
18172
|
+
const drawn = unionBounds(doc.nodes.map((n2) => vectorNodeBounds(n2)));
|
|
18173
|
+
const box = doc.width > 0 && doc.height > 0 ? { x: 0, y: 0, width: doc.width, height: doc.height } : drawn;
|
|
18174
|
+
return box ? { nodes: doc.nodes, box } : "The generated SVG is empty.";
|
|
18175
|
+
}
|
|
18176
|
+
let pixels = null;
|
|
18177
|
+
if ("image" in art) pixels = art.image;
|
|
18178
|
+
else if ("png" in art && typeof art.png === "string") {
|
|
18179
|
+
const parsed = parseDataUrl(art.png);
|
|
18180
|
+
if (!parsed || !isPng(parsed.bytes)) return "The generated image is not a PNG data URL.";
|
|
18181
|
+
try {
|
|
18182
|
+
pixels = decodePng(parsed.bytes, { maxPixels: VECTOR_TRACE_MAX_PIXELS });
|
|
18183
|
+
} catch (e) {
|
|
18184
|
+
return `The generated PNG could not be read: ${e.message}`;
|
|
18185
|
+
}
|
|
18186
|
+
}
|
|
18187
|
+
if (!pixels) return "The generator returned nothing usable (expected { svg }, { image } or { png }).";
|
|
18188
|
+
let nodes;
|
|
18189
|
+
try {
|
|
18190
|
+
nodes = as === "color" || as === "threshold" ? traceVectorImage(pixels, { mode: as, colors }) : stylizeVectorImage(pixels, as, { colors });
|
|
18191
|
+
} catch (e) {
|
|
18192
|
+
return e.message;
|
|
18193
|
+
}
|
|
18194
|
+
if (!nodes.length) return "The generated image had nothing to trace (try another `as`).";
|
|
18195
|
+
return { nodes, box: { x: 0, y: 0, width: pixels.width, height: pixels.height } };
|
|
18196
|
+
}
|
|
18197
|
+
function fitted(nodes, box, target) {
|
|
18198
|
+
const k = Math.min(target.width / (box.width || 1), target.height / (box.height || 1));
|
|
18199
|
+
const dx = target.x + (target.width - box.width * k) / 2 - box.x * k;
|
|
18200
|
+
const dy = target.y + (target.height - box.height * k) / 2 - box.y * k;
|
|
18201
|
+
return nodes.map((n2) => transformVectorNode(n2, [k, 0, 0, k, dx, dy]));
|
|
18202
|
+
}
|
|
18203
|
+
function ratioOf(w, h) {
|
|
18204
|
+
const g = (a2, b2) => b2 < 1 ? a2 : g(b2, a2 % b2);
|
|
18205
|
+
const a = Math.max(1, Math.round(w));
|
|
18206
|
+
const b = Math.max(1, Math.round(h));
|
|
18207
|
+
const d = g(a, b);
|
|
18208
|
+
return `${a / d}:${b / d}`;
|
|
18209
|
+
}
|
|
18210
|
+
async function produce(request, model, target, as, colors, options, idempotencyKey, signal) {
|
|
18211
|
+
const plan = planGenerationRequest(request, model, { strategy: "nearest" });
|
|
18212
|
+
if (!plan.ok) return `The request cannot be sent to "${model.id}": ${plan.errors.map((e) => `${e.field}: ${e.message}`).join("; ")}`;
|
|
18213
|
+
let art;
|
|
18214
|
+
try {
|
|
18215
|
+
art = await options.generate(plan.request, { model, plan, idempotencyKey, signal });
|
|
18216
|
+
} catch (e) {
|
|
18217
|
+
return `The generator failed: ${e.message}`;
|
|
18218
|
+
}
|
|
18219
|
+
const made = artNodes(art, as, colors);
|
|
18220
|
+
if (typeof made === "string") return made;
|
|
18221
|
+
return { nodes: fitted(made.nodes, made.box, target), plan };
|
|
18222
|
+
}
|
|
18223
|
+
var inFlight = /* @__PURE__ */ new WeakMap();
|
|
18224
|
+
function once(controller, key, args, fn) {
|
|
18225
|
+
let map = inFlight.get(controller);
|
|
18226
|
+
if (!map) inFlight.set(controller, map = /* @__PURE__ */ new Map());
|
|
18227
|
+
const sig = stableStringify(args);
|
|
18228
|
+
const hit = map.get(key);
|
|
18229
|
+
if (hit) return hit.args === sig ? hit.result : Promise.resolve({ ok: false, message: `idempotencyKey "${key}" was already used with different input: pass a new key for a new request.` });
|
|
18230
|
+
const result = fn().then((r) => {
|
|
18231
|
+
if (!r.ok) map.delete(key);
|
|
18232
|
+
return r;
|
|
18233
|
+
});
|
|
18234
|
+
map.set(key, { args: sig, result });
|
|
18235
|
+
if (map.size > 500) map.delete(map.keys().next().value);
|
|
18236
|
+
return result;
|
|
18237
|
+
}
|
|
18238
|
+
function generateVectorArt(controller, input, options) {
|
|
18239
|
+
const { signal, ...args } = input;
|
|
18240
|
+
return once(controller, input.idempotencyKey, args, async () => {
|
|
18241
|
+
if (typeof input.prompt !== "string" || !input.prompt.trim()) return { ok: false, message: "prompt must be some text describing the art." };
|
|
18242
|
+
if (!input.idempotencyKey) return { ok: false, message: "idempotencyKey is required (any unique string per request)." };
|
|
18243
|
+
const models = listModels(options);
|
|
18244
|
+
if (!models.length) return { ok: false, message: "No model can make vector art (an image model, or a text model that writes SVG)." };
|
|
18245
|
+
const model = input.model ? models.find((m) => m.id === input.model) : models[0];
|
|
18246
|
+
if (!model) return { ok: false, message: `Unknown model "${input.model}": use one of ${models.map((m) => m.id).join(", ")}.` };
|
|
18247
|
+
const as = input.as ?? "color";
|
|
18248
|
+
if (!VECTOR_GENERATED_IMAGE_AS.includes(as)) return { ok: false, message: `as must be one of ${VECTOR_GENERATED_IMAGE_AS.join(", ")}.` };
|
|
18249
|
+
const doc = controller.getState().document;
|
|
18250
|
+
const side = Math.min(doc.width, doc.height) * 0.6;
|
|
18251
|
+
const target = {
|
|
18252
|
+
x: input.x ?? (doc.width - (input.width ?? side)) / 2,
|
|
18253
|
+
y: input.y ?? (doc.height - (input.height ?? side)) / 2,
|
|
18254
|
+
width: input.width ?? side,
|
|
18255
|
+
height: input.height ?? input.width ?? side
|
|
18256
|
+
};
|
|
18257
|
+
if (!(target.width > 0 && target.height > 0)) return { ok: false, message: "width and height must be above 0." };
|
|
18258
|
+
const request = {
|
|
18259
|
+
prompt: input.prompt,
|
|
18260
|
+
mode: "generate",
|
|
18261
|
+
aspectRatio: input.aspectRatio ?? ratioOf(target.width, target.height),
|
|
18262
|
+
...input.negativePrompt ? { negativePrompt: input.negativePrompt } : {},
|
|
18263
|
+
...input.seed !== void 0 ? { seed: input.seed } : {}
|
|
18264
|
+
};
|
|
18265
|
+
const made = await produce(request, model, target, as, input.colors ?? 8, options, input.idempotencyKey, signal);
|
|
18266
|
+
if (typeof made === "string") return { ok: false, message: made };
|
|
18267
|
+
const provenance = createProvenance({ rawPrompt: input.prompt, model, plan: made.plan, meta: { as, colors: input.colors ?? 8, box: target } });
|
|
18268
|
+
const group = { id: "generated", type: "group", name: input.name ?? input.prompt.slice(0, 40), generation: provenance, children: made.nodes };
|
|
18269
|
+
const id = controller.addNode(group, { label: "Generate Art" });
|
|
18270
|
+
const added = controller.getNode(id);
|
|
18271
|
+
return { ok: true, id, ids: added.children.map((c) => c.id), adjustments: made.plan.adjustments, model: model.id };
|
|
18272
|
+
});
|
|
18273
|
+
}
|
|
18274
|
+
function regenerateVectorArt(controller, input, options) {
|
|
18275
|
+
const { signal, ...args } = input;
|
|
18276
|
+
return once(controller, input.idempotencyKey, args, async () => {
|
|
18277
|
+
const node = controller.getNode(input.id);
|
|
18278
|
+
const prov = node?.type === "group" ? node.generation : void 0;
|
|
18279
|
+
if (!node || node.type !== "group" || !prov) return { ok: false, message: `"${input.id}" is not generated art (generate some with generate_vector_art).` };
|
|
18280
|
+
if (node.locked) return { ok: false, message: `"${input.id}" is locked; unlock it first.` };
|
|
18281
|
+
const models = listModels(options);
|
|
18282
|
+
const model = models.find((m) => m.id === prov.modelId);
|
|
18283
|
+
if (!model) return { ok: false, message: `The model "${prov.modelId}" it was made with is not available.` };
|
|
18284
|
+
const meta = prov.meta ?? {};
|
|
18285
|
+
const box = vectorNodeBounds(node) ?? meta.box ?? { x: 0, y: 0, width: 100, height: 100 };
|
|
18286
|
+
const prompt = input.prompt ?? prov.rawPrompt;
|
|
18287
|
+
const request = { ...prov.request, prompt, seed: input.seed ?? Math.floor(Math.random() * 2 ** 31) };
|
|
18288
|
+
const made = await produce(request, model, box, meta.as ?? "color", meta.colors ?? 8, options, input.idempotencyKey, signal);
|
|
18289
|
+
if (typeof made === "string") return { ok: false, message: made };
|
|
18290
|
+
const provenance = createProvenance({ rawPrompt: prompt, model, plan: made.plan, meta: { ...meta, box } });
|
|
18291
|
+
const r = controller.replaceGeneratedArt(input.id, made.nodes, provenance);
|
|
18292
|
+
if (!r.ok) return { ok: false, message: r.message };
|
|
18293
|
+
const now = controller.getNode(input.id);
|
|
18294
|
+
return { ok: true, id: input.id, ids: now.children.map((c) => c.id), adjustments: made.plan.adjustments, model: model.id };
|
|
18295
|
+
});
|
|
18296
|
+
}
|
|
18297
|
+
var IMAGE_AS_DESCRIPTION = "How an image result becomes vectors: color (traced, a path per colour; default), threshold (one colour), or stylised as mosaic, halftone, lowPoly or hatch. Ignored for SVG results.";
|
|
18298
|
+
function createVectorGenerationToolProvider(controller, options) {
|
|
18299
|
+
const readOnly = () => {
|
|
18300
|
+
const r = typeof options.readOnly === "function" ? options.readOnly() : options.readOnly;
|
|
18301
|
+
return r ?? false;
|
|
18302
|
+
};
|
|
18303
|
+
const result = (r) => {
|
|
18304
|
+
if (!r.ok) return { isError: true, content: [{ type: "text", text: r.message ?? "Generation failed." }] };
|
|
18305
|
+
return { id: r.id, ids: r.ids, model: r.model, adjustments: r.adjustments };
|
|
18306
|
+
};
|
|
18307
|
+
const list = {
|
|
18308
|
+
name: "list_vector_art_models",
|
|
18309
|
+
title: "List vector art models",
|
|
18310
|
+
description: "Lists the models generate_vector_art can use: { models: [{ id, name, media, aspectRatios, seed }] }. Image models give pictures that are traced or stylised; text models write SVG.",
|
|
18311
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
18312
|
+
annotations: { title: "List vector art models", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
18313
|
+
handler: () => ({
|
|
18314
|
+
models: listModels(options).map((m) => ({ id: m.id, name: m.name ?? m.id, media: m.media, ...m.aspectRatios ? { aspectRatios: m.aspectRatios } : {}, seed: !!m.seed }))
|
|
18315
|
+
})
|
|
18316
|
+
};
|
|
18317
|
+
const generate = {
|
|
18318
|
+
name: "generate_vector_art",
|
|
18319
|
+
title: "Generate vector art",
|
|
18320
|
+
description: "Generates vector art from a text prompt with a model (list_vector_art_models) and adds it as one editable group fitted into a box on the artboard (one undo step). The request is adjusted to what the model supports. Costs money: pass a unique idempotencyKey; a retry with the same key and arguments returns the first result. Returns { id (the group), ids (its paths), model, adjustments }.",
|
|
18321
|
+
inputSchema: {
|
|
18322
|
+
type: "object",
|
|
18323
|
+
properties: {
|
|
18324
|
+
prompt: { type: "string", minLength: 1, description: 'What to draw, e.g. "a flat-style rocket, three colours".' },
|
|
18325
|
+
model: { type: "string", description: "Model id from list_vector_art_models (default: the first)." },
|
|
18326
|
+
negativePrompt: { type: "string", description: "What to avoid, for models that support it." },
|
|
18327
|
+
seed: { type: "integer", description: "Seed for repeatable results, for models that support it." },
|
|
18328
|
+
x: { type: "number", description: "Left of the box, document units (default: centred)." },
|
|
18329
|
+
y: { type: "number", description: "Top of the box, document units (default: centred)." },
|
|
18330
|
+
width: { type: "number", exclusiveMinimum: 0, description: "Box width, document units (default 60% of the artboard's smaller side)." },
|
|
18331
|
+
height: { type: "number", exclusiveMinimum: 0, description: "Box height, document units (default: the width). The art keeps its proportions inside." },
|
|
18332
|
+
as: { type: "string", enum: [...VECTOR_GENERATED_IMAGE_AS], description: IMAGE_AS_DESCRIPTION },
|
|
18333
|
+
colors: { type: "integer", minimum: 2, maximum: 32, description: "Palette size when tracing or stylising in colour (default 8)." },
|
|
18334
|
+
name: { type: "string", description: "Group name (default: the start of the prompt)." },
|
|
18335
|
+
idempotencyKey: { type: "string", minLength: 1, description: "A unique string per request; reuse it only to retry the same request." }
|
|
18336
|
+
},
|
|
18337
|
+
required: ["prompt", "idempotencyKey"],
|
|
18338
|
+
additionalProperties: false
|
|
18339
|
+
},
|
|
18340
|
+
annotations: { title: "Generate vector art", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
18341
|
+
handler: async (args, ctx) => result(await generateVectorArt(controller, { ...args, signal: ctx.signal }, options))
|
|
18342
|
+
};
|
|
18343
|
+
const regenerate = {
|
|
18344
|
+
name: "regenerate_vector_art",
|
|
18345
|
+
title: "Regenerate vector art",
|
|
18346
|
+
description: "Generates a generated group's art again (same model and settings, from its provenance) with a new seed (default random) and optionally a new prompt, replacing its paths in place (one undo step). Costs money: pass a unique idempotencyKey. Returns { id, ids, model, adjustments }.",
|
|
18347
|
+
inputSchema: {
|
|
18348
|
+
type: "object",
|
|
18349
|
+
properties: {
|
|
18350
|
+
id: { type: "string", description: "A group generate_vector_art made." },
|
|
18351
|
+
prompt: { type: "string", minLength: 1, description: "A new prompt (default: the one it was made with)." },
|
|
18352
|
+
seed: { type: "integer", description: "Seed (default: a random one)." },
|
|
18353
|
+
idempotencyKey: { type: "string", minLength: 1, description: "A unique string per request; reuse it only to retry the same request." }
|
|
18354
|
+
},
|
|
18355
|
+
required: ["id", "idempotencyKey"],
|
|
18356
|
+
additionalProperties: false
|
|
18357
|
+
},
|
|
18358
|
+
annotations: { title: "Regenerate vector art", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
18359
|
+
handler: async (args, ctx) => result(await regenerateVectorArt(controller, { ...args, signal: ctx.signal }, options))
|
|
18360
|
+
};
|
|
18361
|
+
return {
|
|
18362
|
+
id: options.id ?? "vector-generation",
|
|
18363
|
+
...options.namespace ? { namespace: options.namespace } : {},
|
|
18364
|
+
tools: () => readOnly() ? [list] : [list, generate, regenerate],
|
|
18365
|
+
// The write tools come and go with a read-only state that can change.
|
|
18366
|
+
...typeof options.readOnly === "function" ? { subscribe: (notify) => controller.subscribe(() => notify()) } : {}
|
|
18367
|
+
};
|
|
18368
|
+
}
|
|
18369
|
+
|
|
17370
18370
|
// src/vector/textPanel.ts
|
|
17371
18371
|
var VECTOR_TEXT_ALIGN_LABELS = { left: "Left", center: "Centre", right: "Right", justify: "Justify" };
|
|
17372
18372
|
var VECTOR_TEXT_ALIGN_OPTIONS = VECTOR_TEXT_ALIGNS.map((value) => ({ value, label: VECTOR_TEXT_ALIGN_LABELS[value] }));
|
|
@@ -17571,6 +18571,7 @@ export {
|
|
|
17571
18571
|
VECTOR_BLEND_MODE_MENU,
|
|
17572
18572
|
VECTOR_BLEND_SPACINGS,
|
|
17573
18573
|
VECTOR_BLEND_SPACING_LABELS,
|
|
18574
|
+
VECTOR_BRUSHSTROKE_SCALE,
|
|
17574
18575
|
VECTOR_BUILDER_HATCH_GAP,
|
|
17575
18576
|
VECTOR_CHARACTER_STYLE_KEYS,
|
|
17576
18577
|
VECTOR_CLIPBOARD_TYPE,
|
|
@@ -17586,6 +18587,7 @@ export {
|
|
|
17586
18587
|
VECTOR_CORNER_KIND_LABELS,
|
|
17587
18588
|
VECTOR_CORNER_WIDGET_INSET,
|
|
17588
18589
|
VECTOR_DIMENSIONAL_KINDS,
|
|
18590
|
+
VECTOR_DISTORT_BRUSH_MODES,
|
|
17589
18591
|
VECTOR_DISTORT_TOLERANCE,
|
|
17590
18592
|
VECTOR_EDIT_TOGGLES,
|
|
17591
18593
|
VECTOR_EFFECT_DEFAULTS,
|
|
@@ -17601,6 +18603,7 @@ export {
|
|
|
17601
18603
|
VECTOR_FILTER_PARAM_SPECS,
|
|
17602
18604
|
VECTOR_FILTER_TYPES,
|
|
17603
18605
|
VECTOR_FONT_WEIGHTS,
|
|
18606
|
+
VECTOR_GENERATED_IMAGE_AS,
|
|
17604
18607
|
VECTOR_GRADIENT_INTERPOLATIONS,
|
|
17605
18608
|
VECTOR_GRADIENT_INTERPOLATION_LABELS,
|
|
17606
18609
|
VECTOR_GRADIENT_MAX_PERIODS,
|
|
@@ -17642,6 +18645,9 @@ export {
|
|
|
17642
18645
|
VECTOR_STROKE_GRADIENT_MODE_LABELS,
|
|
17643
18646
|
VECTOR_STROKE_SETTING_KEYS,
|
|
17644
18647
|
VECTOR_STYLE_KEYS,
|
|
18648
|
+
VECTOR_STYLIZE_LABELS,
|
|
18649
|
+
VECTOR_STYLIZE_MAX_SHAPES,
|
|
18650
|
+
VECTOR_STYLIZE_STYLES,
|
|
17645
18651
|
VECTOR_SYMBOL_MAX_DEPTH,
|
|
17646
18652
|
VECTOR_SYMMETRY_MODES,
|
|
17647
18653
|
VECTOR_SYMMETRY_MODE_LABELS,
|
|
@@ -17716,6 +18722,7 @@ export {
|
|
|
17716
18722
|
createVectorEffect,
|
|
17717
18723
|
createVectorEffectsToolProvider,
|
|
17718
18724
|
createVectorFilter,
|
|
18725
|
+
createVectorGenerationToolProvider,
|
|
17719
18726
|
createVectorMeshGradient,
|
|
17720
18727
|
createVectorMorph,
|
|
17721
18728
|
cssFontFamily,
|
|
@@ -17725,6 +18732,7 @@ export {
|
|
|
17725
18732
|
decomposeRigid,
|
|
17726
18733
|
defaultVectorGradient,
|
|
17727
18734
|
distortVectorNode,
|
|
18735
|
+
distortVectorPath,
|
|
17728
18736
|
dragWarpMeshPoint as dragVectorMeshPoint,
|
|
17729
18737
|
effectPathLength,
|
|
17730
18738
|
effectPathVertexCount,
|
|
@@ -17751,6 +18759,7 @@ export {
|
|
|
17751
18759
|
fitVectorTextRuns,
|
|
17752
18760
|
foldVectorBooleanOperands,
|
|
17753
18761
|
formatVectorValue,
|
|
18762
|
+
generateVectorArt,
|
|
17754
18763
|
generateVectorBlendChildren,
|
|
17755
18764
|
generateWidthProfile,
|
|
17756
18765
|
getAnchor,
|
|
@@ -17820,6 +18829,7 @@ export {
|
|
|
17820
18829
|
isValidVectorParameterName,
|
|
17821
18830
|
isVectorBlendGroup,
|
|
17822
18831
|
isVectorBlendMode,
|
|
18832
|
+
isVectorBrushstrokeLayer,
|
|
17823
18833
|
isVectorConnector,
|
|
17824
18834
|
isVectorFilterType,
|
|
17825
18835
|
isVectorFrame,
|
|
@@ -17901,6 +18911,7 @@ export {
|
|
|
17901
18911
|
rectSubpath,
|
|
17902
18912
|
refineMorphContour,
|
|
17903
18913
|
refreshVectorBlends,
|
|
18914
|
+
regenerateVectorArt,
|
|
17904
18915
|
releaseVectorBlend,
|
|
17905
18916
|
removeAnchorKeepingShape,
|
|
17906
18917
|
removeAnchors,
|
|
@@ -17934,6 +18945,7 @@ export {
|
|
|
17934
18945
|
sampleVectorGradient,
|
|
17935
18946
|
sampleVectorGradientAt,
|
|
17936
18947
|
sampleVectorPaintDefAt,
|
|
18948
|
+
sanitizeVectorBrushstroke,
|
|
17937
18949
|
sanitizeVectorConnector,
|
|
17938
18950
|
sanitizeVectorEffects,
|
|
17939
18951
|
sanitizeVectorFills,
|
|
@@ -17977,6 +18989,7 @@ export {
|
|
|
17977
18989
|
strokePositionPoint,
|
|
17978
18990
|
strokeWidthProfileOf,
|
|
17979
18991
|
styleVectorText,
|
|
18992
|
+
stylizeVectorImage,
|
|
17980
18993
|
subpathEnclosesArea,
|
|
17981
18994
|
subpathPointAt,
|
|
17982
18995
|
subpathSegment,
|
|
@@ -18011,6 +19024,7 @@ export {
|
|
|
18011
19024
|
vectorAppearanceNodes,
|
|
18012
19025
|
vectorAppearanceOutline,
|
|
18013
19026
|
vectorAppearanceShapes,
|
|
19027
|
+
vectorArtModels,
|
|
18014
19028
|
vectorArtboards,
|
|
18015
19029
|
vectorBevelFaces,
|
|
18016
19030
|
vectorBlendColorDistance,
|
|
@@ -18025,6 +19039,10 @@ export {
|
|
|
18025
19039
|
vectorBoundsInside,
|
|
18026
19040
|
vectorBoxCorners,
|
|
18027
19041
|
vectorBoxMatrix,
|
|
19042
|
+
vectorBrushstrokeBrush,
|
|
19043
|
+
vectorBrushstrokeBrushes,
|
|
19044
|
+
vectorBrushstrokeImages,
|
|
19045
|
+
vectorBrushstrokeReach,
|
|
18028
19046
|
vectorBuilderErasedEdges,
|
|
18029
19047
|
vectorChildAppearanceShapes,
|
|
18030
19048
|
vectorConnectionSites,
|
|
@@ -18041,6 +19059,8 @@ export {
|
|
|
18041
19059
|
vectorCorners,
|
|
18042
19060
|
vectorCornersAttribute,
|
|
18043
19061
|
vectorDiamondPath,
|
|
19062
|
+
vectorDistortDabMap,
|
|
19063
|
+
vectorDistortFalloff,
|
|
18044
19064
|
vectorDistortQuad,
|
|
18045
19065
|
vectorDocumentToLottie,
|
|
18046
19066
|
vectorDocumentToPdf,
|
|
@@ -18083,6 +19103,8 @@ export {
|
|
|
18083
19103
|
vectorGradientTAt,
|
|
18084
19104
|
vectorGradientTargetPath,
|
|
18085
19105
|
vectorGradientUnitsMatrix,
|
|
19106
|
+
vectorHalftone,
|
|
19107
|
+
vectorHatch,
|
|
18086
19108
|
homographyFromPoints as vectorHomography,
|
|
18087
19109
|
homographyKeepsQuad as vectorHomographyKeepsQuad,
|
|
18088
19110
|
identityWarpMesh as vectorIdentityMesh,
|
|
@@ -18092,6 +19114,7 @@ export {
|
|
|
18092
19114
|
vectorLensColorMap,
|
|
18093
19115
|
vectorLensDrawn,
|
|
18094
19116
|
vectorLiveBooleanSteps,
|
|
19117
|
+
vectorLowPoly,
|
|
18095
19118
|
vectorMaskContains,
|
|
18096
19119
|
vectorMaskContentBounds,
|
|
18097
19120
|
vectorMaskContents,
|
|
@@ -18118,6 +19141,7 @@ export {
|
|
|
18118
19141
|
warpMeshParamAt as vectorMeshParamAt,
|
|
18119
19142
|
vectorMeshPointAt,
|
|
18120
19143
|
warpMeshRows as vectorMeshRows,
|
|
19144
|
+
vectorMosaic,
|
|
18121
19145
|
vectorNodeBounds,
|
|
18122
19146
|
vectorNodeEnvelope,
|
|
18123
19147
|
vectorNodeFilterSvg,
|