@pixerate/editor 0.136.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.
Files changed (132) hide show
  1. package/dist/animation-editor/index.cjs +60 -55
  2. package/dist/animation-editor/index.cjs.map +1 -1
  3. package/dist/animation-editor/index.d.cts +4 -4
  4. package/dist/animation-editor/index.d.ts +4 -4
  5. package/dist/animation-editor/index.js +7 -2
  6. package/dist/animation-editor/index.js.map +1 -1
  7. package/dist/{bezier-CdccFsF8.d.cts → bezier-CZ2AhjID.d.cts} +1 -1
  8. package/dist/{bezier-DG9qNZQV.d.ts → bezier-Dywv3Bw8.d.ts} +1 -1
  9. package/dist/blending-D7jCmjO6.d.cts +1026 -0
  10. package/dist/blending-NgF3qo3d.d.ts +1026 -0
  11. package/dist/brushes-CnvZSYYU.d.cts +744 -0
  12. package/dist/brushes-DJmUbIgz.d.ts +744 -0
  13. package/dist/{brushstrokeRender-SII6MG4R.js → brushstrokeRender-2FAXA3XC.js} +13 -16
  14. package/dist/{brushstrokeRender-SII6MG4R.js.map → brushstrokeRender-2FAXA3XC.js.map} +1 -1
  15. package/dist/{brushstrokeRender-JNHVEUFG.cjs → brushstrokeRender-IMFY3FF6.cjs} +16 -19
  16. package/dist/brushstrokeRender-IMFY3FF6.cjs.map +1 -0
  17. package/dist/canvas/index.cjs +10 -10
  18. package/dist/canvas/index.cjs.map +1 -1
  19. package/dist/canvas/index.d.cts +7 -7
  20. package/dist/canvas/index.d.ts +7 -7
  21. package/dist/canvas/index.js +4 -4
  22. package/dist/{chunk-W4LNKTHT.cjs → chunk-2XS6LTUP.cjs} +63 -63
  23. package/dist/{chunk-W4LNKTHT.cjs.map → chunk-2XS6LTUP.cjs.map} +1 -1
  24. package/dist/{chunk-MAHQC3SK.js → chunk-3D77LOBM.js} +222 -135
  25. package/dist/chunk-3D77LOBM.js.map +1 -0
  26. package/dist/{chunk-BR7EKQUP.js → chunk-3G6J7UFV.js} +322 -2
  27. package/dist/chunk-3G6J7UFV.js.map +1 -0
  28. package/dist/{chunk-62XUO5LB.js → chunk-3X6IAJUQ.js} +2 -2
  29. package/dist/{chunk-EM3FMJWE.js → chunk-5FSRTP6E.js} +320 -3
  30. package/dist/chunk-5FSRTP6E.js.map +1 -0
  31. package/dist/{chunk-2R3UW57U.cjs → chunk-5OOIUXNJ.cjs} +333 -246
  32. package/dist/chunk-5OOIUXNJ.cjs.map +1 -0
  33. package/dist/{chunk-KZXLZCQ7.js → chunk-6S6362D2.js} +2 -2
  34. package/dist/{chunk-24ATT2PE.cjs → chunk-AEAAN4AD.cjs} +120 -120
  35. package/dist/{chunk-24ATT2PE.cjs.map → chunk-AEAAN4AD.cjs.map} +1 -1
  36. package/dist/{chunk-SVYE5HIM.js → chunk-EQQMGO42.js} +2 -2
  37. package/dist/{chunk-FHPVDTI2.cjs → chunk-F2Y4E4GU.cjs} +22 -22
  38. package/dist/{chunk-FHPVDTI2.cjs.map → chunk-F2Y4E4GU.cjs.map} +1 -1
  39. package/dist/{chunk-UYQUPUR6.js → chunk-HUO3TI4C.js} +3 -3
  40. package/dist/{chunk-DDSO4HS7.cjs → chunk-KEENUAFN.cjs} +2 -2
  41. package/dist/{chunk-DDSO4HS7.cjs.map → chunk-KEENUAFN.cjs.map} +1 -1
  42. package/dist/{chunk-2HC4DRF4.js → chunk-LLFHCSU2.js} +2 -2
  43. package/dist/{chunk-J5BA3BQ6.cjs → chunk-O7LUD7MH.cjs} +323 -3
  44. package/dist/chunk-O7LUD7MH.cjs.map +1 -0
  45. package/dist/{chunk-ILYFOJUN.js → chunk-OIBIRXRG.js} +3 -3
  46. package/dist/{chunk-B2CUCM7Y.cjs → chunk-PRSRYHQD.cjs} +36 -36
  47. package/dist/{chunk-B2CUCM7Y.cjs.map → chunk-PRSRYHQD.cjs.map} +1 -1
  48. package/dist/{chunk-6GFRKLLS.cjs → chunk-QBWEH3IM.cjs} +429 -112
  49. package/dist/chunk-QBWEH3IM.cjs.map +1 -0
  50. package/dist/{chunk-6QQR4LKE.js → chunk-SYT2TLQF.js} +35 -37
  51. package/dist/{chunk-6QQR4LKE.js.map → chunk-SYT2TLQF.js.map} +1 -1
  52. package/dist/{chunk-YT3JL7YM.cjs → chunk-TLCTTQFF.cjs} +53 -53
  53. package/dist/{chunk-YT3JL7YM.cjs.map → chunk-TLCTTQFF.cjs.map} +1 -1
  54. package/dist/{chunk-JLGNZMUV.cjs → chunk-XN2NBLV5.cjs} +313 -315
  55. package/dist/chunk-XN2NBLV5.cjs.map +1 -0
  56. package/dist/effects/index.d.cts +4 -4
  57. package/dist/effects/index.d.ts +4 -4
  58. package/dist/{export-Ds7zgGD8.d.ts → export-41rL6Cne.d.ts} +1 -1
  59. package/dist/{export-BOheWw2R.d.cts → export-CyJdfP_Z.d.cts} +5 -4
  60. package/dist/{export-CEYeRQXH.d.cts → export-D9mXp05Q.d.cts} +1 -1
  61. package/dist/{export-ByKKIA2S.d.ts → export-DwnZ_hvQ.d.ts} +5 -4
  62. package/dist/{helpers--ns3W2D7.d.ts → helpers-C-RgeyvH.d.ts} +18 -7
  63. package/dist/{helpers-BVkU4oUi.d.cts → helpers-v6GMazzg.d.cts} +18 -7
  64. package/dist/image-editor/index.cjs +984 -987
  65. package/dist/image-editor/index.cjs.map +1 -1
  66. package/dist/image-editor/index.d.cts +12 -11
  67. package/dist/image-editor/index.d.ts +12 -11
  68. package/dist/image-editor/index.js +92 -95
  69. package/dist/image-editor/index.js.map +1 -1
  70. package/dist/{opentype-yi-Kz4ND.d.cts → opentype-KR_jj6b_.d.cts} +1 -1
  71. package/dist/{opentype-DfoVYgmG.d.ts → opentype-a1KAW-qs.d.ts} +1 -1
  72. package/dist/paint/index.cjs +5 -8
  73. package/dist/paint/index.cjs.map +1 -1
  74. package/dist/paint/index.d.cts +4 -2
  75. package/dist/paint/index.d.ts +4 -2
  76. package/dist/paint/index.js +15 -18
  77. package/dist/paint-editor/index.cjs +403 -404
  78. package/dist/paint-editor/index.cjs.map +1 -1
  79. package/dist/paint-editor/index.d.cts +12 -748
  80. package/dist/paint-editor/index.d.ts +12 -748
  81. package/dist/paint-editor/index.js +77 -78
  82. package/dist/paint-editor/index.js.map +1 -1
  83. package/dist/{pathfinder-7JkMwYvC.d.cts → pathfinder-BHS4vQNm.d.cts} +1 -1
  84. package/dist/{pathfinder-GM3UWdBZ.d.ts → pathfinder-BVvTIBMo.d.ts} +1 -1
  85. package/dist/{pool-CkTxlLMC.d.ts → pool-CZQOrSXZ.d.ts} +1 -1
  86. package/dist/{pool-04qp8iW4.d.cts → pool-FjZZmZ80.d.cts} +1 -1
  87. package/dist/rand-8xbUb6ur.d.ts +1118 -0
  88. package/dist/rand-Cw9VLrVC.d.cts +1118 -0
  89. package/dist/raster/index.cjs +17 -19
  90. package/dist/raster/index.cjs.map +1 -1
  91. package/dist/raster/index.js +9 -11
  92. package/dist/raster/index.js.map +1 -1
  93. package/dist/{svg-rV6_HPmn.d.ts → svg-CSz3mzkw.d.ts} +2 -2
  94. package/dist/{svg-BDDKvWWY.d.cts → svg-DTb40ICU.d.cts} +2 -2
  95. package/dist/{tools-DPYerqeR.d.ts → tools-B0wXP5cF.d.ts} +1 -1
  96. package/dist/{tools-BAjSbTdY.d.cts → tools-Ca0kI1qo.d.cts} +1 -1
  97. package/dist/vector/index.cjs +1459 -1259
  98. package/dist/vector/index.cjs.map +1 -1
  99. package/dist/vector/index.d.cts +87 -11
  100. package/dist/vector/index.d.ts +87 -11
  101. package/dist/vector/index.js +213 -13
  102. package/dist/vector/index.js.map +1 -1
  103. package/dist/video-editor/index.d.cts +4 -4
  104. package/dist/video-editor/index.d.ts +4 -4
  105. package/dist/{warp-DQh9R7Ie.d.cts → warp-DTOp0lON.d.cts} +1 -1
  106. package/dist/{warp-XVL6SpSh.d.ts → warp-pd7hrH4T.d.ts} +1 -1
  107. package/package.json +1 -1
  108. package/skills/pixerate-vector/SKILL.md +8 -0
  109. package/dist/brushstrokeRender-JNHVEUFG.cjs.map +0 -1
  110. package/dist/chunk-2R3UW57U.cjs.map +0 -1
  111. package/dist/chunk-64PYNEHJ.js +0 -340
  112. package/dist/chunk-64PYNEHJ.js.map +0 -1
  113. package/dist/chunk-6GFRKLLS.cjs.map +0 -1
  114. package/dist/chunk-7QN76ESJ.js +0 -323
  115. package/dist/chunk-7QN76ESJ.js.map +0 -1
  116. package/dist/chunk-BR7EKQUP.js.map +0 -1
  117. package/dist/chunk-DCLJNVZS.cjs +0 -323
  118. package/dist/chunk-DCLJNVZS.cjs.map +0 -1
  119. package/dist/chunk-EM3FMJWE.js.map +0 -1
  120. package/dist/chunk-J5BA3BQ6.cjs.map +0 -1
  121. package/dist/chunk-JLGNZMUV.cjs.map +0 -1
  122. package/dist/chunk-MAHQC3SK.js.map +0 -1
  123. package/dist/chunk-Z6OUCS2K.cjs +0 -340
  124. package/dist/chunk-Z6OUCS2K.cjs.map +0 -1
  125. package/dist/rand-BszBST4s.d.ts +0 -2140
  126. package/dist/rand-DOH_aMvm.d.cts +0 -2140
  127. /package/dist/{chunk-62XUO5LB.js.map → chunk-3X6IAJUQ.js.map} +0 -0
  128. /package/dist/{chunk-KZXLZCQ7.js.map → chunk-6S6362D2.js.map} +0 -0
  129. /package/dist/{chunk-SVYE5HIM.js.map → chunk-EQQMGO42.js.map} +0 -0
  130. /package/dist/{chunk-UYQUPUR6.js.map → chunk-HUO3TI4C.js.map} +0 -0
  131. /package/dist/{chunk-2HC4DRF4.js.map → chunk-LLFHCSU2.js.map} +0 -0
  132. /package/dist/{chunk-ILYFOJUN.js.map → chunk-OIBIRXRG.js.map} +0 -0
@@ -20,15 +20,6 @@ import {
20
20
  orderQuad,
21
21
  solve8
22
22
  } from "../chunk-GX332W2C.js";
23
- import {
24
- bytesToBase64,
25
- decodePng,
26
- isPng,
27
- parseDataUrl
28
- } from "../chunk-7QN76ESJ.js";
29
- import {
30
- deflate
31
- } from "../chunk-6FCKOS2B.js";
32
23
  import {
33
24
  correspondMorphContours,
34
25
  createVectorMorph,
@@ -47,7 +38,7 @@ import {
47
38
  reverseMorphContour,
48
39
  subpathToMorphContour,
49
40
  turnMorphContour
50
- } from "../chunk-KZXLZCQ7.js";
41
+ } from "../chunk-6S6362D2.js";
51
42
  import {
52
43
  DEFAULT_VECTOR_FONT,
53
44
  IDENTITY_MATRIX,
@@ -56,6 +47,7 @@ import {
56
47
  VECTOR_BLEND_MODES,
57
48
  VECTOR_BLEND_MODE_LABELS,
58
49
  VECTOR_BLEND_SPACINGS,
50
+ VECTOR_BRUSHSTROKE_SCALE,
59
51
  VECTOR_CHARACTER_STYLE_KEYS,
60
52
  VECTOR_CONNECTOR_ROUTES,
61
53
  VECTOR_CONNECTOR_ROUTE_LABELS,
@@ -202,6 +194,7 @@ import {
202
194
  isUniformWidthProfile,
203
195
  isValidWarpMesh,
204
196
  isVectorBlendMode,
197
+ isVectorBrushstrokeLayer,
205
198
  isVectorConnector,
206
199
  isVectorFilterType,
207
200
  isVectorFrame,
@@ -292,6 +285,7 @@ import {
292
285
  sampleVectorGradient,
293
286
  sampleVectorGradientAt,
294
287
  sampleVectorPaintDefAt,
288
+ sanitizeVectorBrushstroke,
295
289
  sanitizeVectorConnector,
296
290
  sanitizeVectorEffects,
297
291
  sanitizeVectorFills,
@@ -360,6 +354,10 @@ import {
360
354
  vectorBlendModeFromCss,
361
355
  vectorBoundsInside,
362
356
  vectorBoxMatrix,
357
+ vectorBrushstrokeBrush,
358
+ vectorBrushstrokeBrushes,
359
+ vectorBrushstrokeImages,
360
+ vectorBrushstrokeReach,
363
361
  vectorChildAppearanceShapes,
364
362
  vectorConnectionSites,
365
363
  vectorConnectorTargetAt,
@@ -505,7 +503,8 @@ import {
505
503
  withVectorStrokes,
506
504
  withVectorTargetPaint,
507
505
  zigzagPaths
508
- } from "../chunk-MAHQC3SK.js";
506
+ } from "../chunk-3D77LOBM.js";
507
+ import "../chunk-HUO3TI4C.js";
509
508
  import {
510
509
  createProvenance
511
510
  } from "../chunk-A3BVTI3G.js";
@@ -540,6 +539,17 @@ import {
540
539
  resolveEffectValues
541
540
  } from "../chunk-QFSTQHZY.js";
542
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";
543
553
  import "../chunk-FPENXAEJ.js";
544
554
  import "../chunk-P6ODPZ4W.js";
545
555
  import {
@@ -2895,6 +2905,12 @@ function lineStyle(node) {
2895
2905
  function renderLayers(node, defs, idPrefix) {
2896
2906
  const out = [];
2897
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
+ }
2898
2914
  if (!l.drawn) continue;
2899
2915
  const role = l.kind;
2900
2916
  const paint = vectorPaintToSvg(l.layer.paint, `${idPrefix}${role}-${node.id}-${l.index}`, { node: l.node, ...role === "stroke" ? { role } : {} });
@@ -3919,7 +3935,11 @@ function getVectorAppearancePanel(state) {
3919
3935
  blendMode: l.blendMode ?? "normal",
3920
3936
  visible: l.visible !== false,
3921
3937
  ...kind === "stroke" ? { strokeWidth: l.strokeWidth ?? 1, strokeAlign: l.strokeAlign ?? "center" } : {},
3922
- ...(l.kind ?? "paint") !== "paint" ? { unknownKind: String(l.kind) } : {},
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) } : {},
3923
3943
  editing: edit.target === kind && (edit.layer === null ? index === count - 1 : edit.layer === index)
3924
3944
  });
3925
3945
  const fills = first ? vectorFills(first) : [];
@@ -4072,6 +4092,7 @@ var Page = class {
4072
4092
  content = [];
4073
4093
  states = /* @__PURE__ */ new Map();
4074
4094
  shadings = /* @__PURE__ */ new Map();
4095
+ images = /* @__PURE__ */ new Map();
4075
4096
  /** A graphics state with this fill / stroke alpha and blend mode, by name. */
4076
4097
  gs(alpha, strokeAlpha, blend) {
4077
4098
  const bm = blend ? BLEND[blend] : void 0;
@@ -4085,6 +4106,36 @@ var Page = class {
4085
4106
  }
4086
4107
  return name;
4087
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
+ }
4088
4139
  /** Sets the fill colour (or prepares a paint server) for `paint`: returns its alpha, or a def to fill with by clipping. */
4089
4140
  paint(paint, stroking) {
4090
4141
  if (!paint || paint === "none") return null;
@@ -4230,6 +4281,14 @@ var Page = class {
4230
4281
  for (const l of layers) {
4231
4282
  const a = alpha * l.opacity;
4232
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
+ }
4233
4292
  if (l.kind === "fill") {
4234
4293
  if (l.fill !== "none") this.fill(p.d, l.fill, a, evenodd, blend);
4235
4294
  } else if (l.stroke !== "none" && l.strokeWidth > 0) {
@@ -4247,6 +4306,7 @@ var PdfWriter = class {
4247
4306
  objects = [];
4248
4307
  gsDicts = /* @__PURE__ */ new Map();
4249
4308
  shadingDicts = /* @__PURE__ */ new Map();
4309
+ imageIds = /* @__PURE__ */ new Map();
4250
4310
  pageIndex = 0;
4251
4311
  add(body) {
4252
4312
  this.objects.push(body);
@@ -4332,7 +4392,8 @@ function vectorDocumentToPdf(doc, options = {}) {
4332
4392
  const content = w.add(w.stream("", new TextEncoder().encode(page.content.join("\n")), compress));
4333
4393
  const gsEntries = [...page.states.values()].map((name) => `/${name} ${w.add(w.gsDicts.get(`${k}:${name}`))} 0 R`);
4334
4394
  const shEntries = [...page.shadings.values()].map((name) => `/${name} ${w.add(w.shadingDicts.get(`${k}:${name}`))} 0 R`);
4335
- const resources = `<< ${gsEntries.length ? `/ExtGState << ${gsEntries.join(" ")} >> ` : ""}${shEntries.length ? `/Shading << ${shEntries.join(" ")} >>` : ""} >>`;
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(" ")} >>` : ""} >>`;
4336
4397
  pageIds.push(w.add(`<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${n(W)} ${n(H)}] /Resources ${resources} /Contents ${content} 0 R >>`));
4337
4398
  });
4338
4399
  w.set(catalog, `<< /Type /Catalog /Pages ${pagesId} 0 R >>`);
@@ -4517,6 +4578,7 @@ function pathItems(node, notes) {
4517
4578
  function nodeGroup(node, notes) {
4518
4579
  if (node.visible === false) return null;
4519
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).`);
4520
4582
  const m = nodeMatrix(node);
4521
4583
  let items = [];
4522
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"));
@@ -8726,6 +8788,57 @@ var VectorEditorController = class _VectorEditorController {
8726
8788
  merge
8727
8789
  );
8728
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
+ }
8729
8842
  /** Removes layer `index` of the fill or stroke stack of the selected (or given) paths. */
8730
8843
  removePaintLayer(kind, index, options = {}) {
8731
8844
  return this.editStacks(
@@ -16166,6 +16279,85 @@ function createVectorCommands(controller, options = {}) {
16166
16279
  }
16167
16280
  }
16168
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
+ ];
16169
16361
  const pdfCommands = () => [
16170
16362
  {
16171
16363
  ...base,
@@ -17170,6 +17362,7 @@ function createVectorCommands(controller, options = {}) {
17170
17362
  ...lottieCommands(),
17171
17363
  ...pdfCommands(),
17172
17364
  ...symmetryCommands(),
17365
+ ...brushstrokeCommands(),
17173
17366
  ...stylizeCommands(),
17174
17367
  ...perspectiveCommands(),
17175
17368
  ...lensCommands(),
@@ -18378,6 +18571,7 @@ export {
18378
18571
  VECTOR_BLEND_MODE_MENU,
18379
18572
  VECTOR_BLEND_SPACINGS,
18380
18573
  VECTOR_BLEND_SPACING_LABELS,
18574
+ VECTOR_BRUSHSTROKE_SCALE,
18381
18575
  VECTOR_BUILDER_HATCH_GAP,
18382
18576
  VECTOR_CHARACTER_STYLE_KEYS,
18383
18577
  VECTOR_CLIPBOARD_TYPE,
@@ -18635,6 +18829,7 @@ export {
18635
18829
  isValidVectorParameterName,
18636
18830
  isVectorBlendGroup,
18637
18831
  isVectorBlendMode,
18832
+ isVectorBrushstrokeLayer,
18638
18833
  isVectorConnector,
18639
18834
  isVectorFilterType,
18640
18835
  isVectorFrame,
@@ -18750,6 +18945,7 @@ export {
18750
18945
  sampleVectorGradient,
18751
18946
  sampleVectorGradientAt,
18752
18947
  sampleVectorPaintDefAt,
18948
+ sanitizeVectorBrushstroke,
18753
18949
  sanitizeVectorConnector,
18754
18950
  sanitizeVectorEffects,
18755
18951
  sanitizeVectorFills,
@@ -18843,6 +19039,10 @@ export {
18843
19039
  vectorBoundsInside,
18844
19040
  vectorBoxCorners,
18845
19041
  vectorBoxMatrix,
19042
+ vectorBrushstrokeBrush,
19043
+ vectorBrushstrokeBrushes,
19044
+ vectorBrushstrokeImages,
19045
+ vectorBrushstrokeReach,
18846
19046
  vectorBuilderErasedEdges,
18847
19047
  vectorChildAppearanceShapes,
18848
19048
  vectorConnectionSites,