@pixerate/editor 0.262.1 → 0.264.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 (89) hide show
  1. package/dist/animation-editor/index.cjs +60 -60
  2. package/dist/animation-editor/index.js +4 -4
  3. package/dist/brushes/index.cjs +158 -71
  4. package/dist/brushes/index.d.cts +3 -3
  5. package/dist/brushes/index.d.ts +3 -3
  6. package/dist/brushes/index.js +113 -26
  7. package/dist/brushes/library.d.cts +2 -0
  8. package/dist/brushes/library.d.ts +2 -0
  9. package/dist/brushes/profiles.d.cts +21 -2
  10. package/dist/brushes/profiles.d.ts +21 -2
  11. package/dist/brushes/ui.d.cts +57 -4
  12. package/dist/brushes/ui.d.ts +57 -4
  13. package/dist/{brushstrokeRender-25Q5BEY6.js → brushstrokeRender-3MOI7PH4.js} +3 -3
  14. package/dist/{brushstrokeRender-N6UPZSQS.cjs → brushstrokeRender-SMMPIJF2.cjs} +13 -13
  15. package/dist/canvas/index.cjs +15 -14
  16. package/dist/canvas/index.js +3 -2
  17. package/dist/canvas/stickers.d.cts +5 -1
  18. package/dist/canvas/stickers.d.ts +5 -1
  19. package/dist/{chunk-X2EXYY4J.js → chunk-4OJC4MCD.js} +9 -8
  20. package/dist/{chunk-J5UQHWVL.js → chunk-6JL2D5SG.js} +27 -15
  21. package/dist/{chunk-APX63ILU.js → chunk-ACFSFUQH.js} +31 -0
  22. package/dist/{chunk-TZOX7KWV.cjs → chunk-BY5EBP26.cjs} +35 -14
  23. package/dist/{chunk-CQVTGBQI.js → chunk-CECCP2V7.js} +1 -1
  24. package/dist/{chunk-F7CPSRLN.js → chunk-DOZ6F2K2.js} +12 -4
  25. package/dist/{chunk-5IW65VQ6.js → chunk-EE7SAYBF.js} +24 -3
  26. package/dist/{chunk-PYTJBVW5.cjs → chunk-F3KKR6DD.cjs} +37 -36
  27. package/dist/{chunk-YLUIFXSL.cjs → chunk-FQC5TDB6.cjs} +35 -35
  28. package/dist/{chunk-UVZ7RQOE.cjs → chunk-G5ADO47G.cjs} +1 -1
  29. package/dist/{chunk-D4TE5JNW.js → chunk-HAUDEVMC.js} +1 -1
  30. package/dist/{chunk-EHALWBBD.js → chunk-JEML4FSA.js} +1 -1
  31. package/dist/{chunk-43NT242D.cjs → chunk-KXNZNOEF.cjs} +86 -86
  32. package/dist/{chunk-JSDND7DF.cjs → chunk-OTN6VB5S.cjs} +32 -1
  33. package/dist/{chunk-AVTPZO4Z.cjs → chunk-PE6YJMQT.cjs} +37 -25
  34. package/dist/{chunk-QAR2N7HZ.js → chunk-ROBSFJ6F.js} +1 -1
  35. package/dist/{chunk-7AUQ2UJ3.cjs → chunk-USJX5SW4.cjs} +10 -10
  36. package/dist/{chunk-3TQW5MPW.cjs → chunk-V7MJ4HIN.cjs} +23 -23
  37. package/dist/{chunk-6NZL24W4.js → chunk-WWI6FLH2.js} +1 -1
  38. package/dist/{chunk-RVGP2XLF.cjs → chunk-XORJ7UZO.cjs} +21 -13
  39. package/dist/image-editor/controller.d.cts +3 -0
  40. package/dist/image-editor/controller.d.ts +3 -0
  41. package/dist/image-editor/index.cjs +792 -788
  42. package/dist/image-editor/index.js +10 -6
  43. package/dist/image-editor/stickers.d.cts +7 -2
  44. package/dist/image-editor/stickers.d.ts +7 -2
  45. package/dist/image-editor/types.d.cts +7 -0
  46. package/dist/image-editor/types.d.ts +7 -0
  47. package/dist/paint-editor/brush-library-tools.d.cts +8 -1
  48. package/dist/paint-editor/brush-library-tools.d.ts +8 -1
  49. package/dist/paint-editor/controller.d.cts +7 -2
  50. package/dist/paint-editor/controller.d.ts +7 -2
  51. package/dist/paint-editor/index.cjs +581 -524
  52. package/dist/paint-editor/index.d.cts +1 -1
  53. package/dist/paint-editor/index.d.ts +1 -1
  54. package/dist/paint-editor/index.js +105 -48
  55. package/dist/paint-editor/sticker-art.d.cts +20 -10
  56. package/dist/paint-editor/sticker-art.d.ts +20 -10
  57. package/dist/paint-editor/stickers.d.cts +8 -1
  58. package/dist/paint-editor/stickers.d.ts +8 -1
  59. package/dist/paint-editor/stroke.d.cts +2 -0
  60. package/dist/paint-editor/stroke.d.ts +2 -0
  61. package/dist/stickers/browser.d.cts +10 -3
  62. package/dist/stickers/browser.d.ts +10 -3
  63. package/dist/stickers/combine.d.cts +2 -0
  64. package/dist/stickers/combine.d.ts +2 -0
  65. package/dist/stickers/conformance.d.cts +1 -1
  66. package/dist/stickers/conformance.d.ts +1 -1
  67. package/dist/stickers/errors.d.cts +1 -1
  68. package/dist/stickers/errors.d.ts +1 -1
  69. package/dist/stickers/fake.d.cts +4 -2
  70. package/dist/stickers/fake.d.ts +4 -2
  71. package/dist/stickers/helpers.d.cts +16 -2
  72. package/dist/stickers/helpers.d.ts +16 -2
  73. package/dist/stickers/index.cjs +116 -29
  74. package/dist/stickers/index.js +106 -19
  75. package/dist/stickers/tools.d.cts +9 -2
  76. package/dist/stickers/tools.d.ts +9 -2
  77. package/dist/stickers/types.d.cts +32 -5
  78. package/dist/stickers/types.d.ts +32 -5
  79. package/dist/vector/controller.d.cts +13 -0
  80. package/dist/vector/controller.d.ts +13 -0
  81. package/dist/vector/index.cjs +1218 -1207
  82. package/dist/vector/index.js +19 -8
  83. package/dist/vector/stickers.d.cts +7 -2
  84. package/dist/vector/stickers.d.ts +7 -2
  85. package/package.json +1 -1
  86. package/skills/pixerate-brushes/SKILL.md +11 -3
  87. package/skills/pixerate-brushes-design/SKILL.md +1 -0
  88. package/skills/pixerate-stickers/SKILL.md +20 -9
  89. package/skills/pixerate-vector/SKILL.md +1 -1
@@ -8,8 +8,8 @@ import {
8
8
  createBrushLibraryToolProvider,
9
9
  createBrushLibraryTools,
10
10
  mergeBrushPatch
11
- } from "../chunk-X2EXYY4J.js";
12
- import "../chunk-J5UQHWVL.js";
11
+ } from "../chunk-4OJC4MCD.js";
12
+ import "../chunk-6JL2D5SG.js";
13
13
  import {
14
14
  BRUSH_FACET_SCHEMAS,
15
15
  BRUSH_FIELDS,
@@ -44,9 +44,11 @@ import {
44
44
  brushColourKind,
45
45
  brushToJSON,
46
46
  checkBrush,
47
+ checkBrushEdit,
47
48
  createBrushLibrary,
48
49
  degrade,
49
50
  getPath,
51
+ isBrushLibrary,
50
52
  listPresets,
51
53
  migrateBrushV1,
52
54
  normalizeBrushSpec,
@@ -57,11 +59,12 @@ import {
57
59
  resolveEngine,
58
60
  resolveWetMedium,
59
61
  setPath,
60
- snapshotBrushLibrary
61
- } from "../chunk-5IW65VQ6.js";
62
+ snapshotBrushLibrary,
63
+ withKeptIssues
64
+ } from "../chunk-EE7SAYBF.js";
62
65
  import {
63
66
  STAMP_KINDS
64
- } from "../chunk-APX63ILU.js";
67
+ } from "../chunk-ACFSFUQH.js";
65
68
  import {
66
69
  brushColourHex
67
70
  } from "../chunk-FJBQTZS4.js";
@@ -125,7 +128,11 @@ function brushEditorSections(brush, profile = PAINT_PROFILE) {
125
128
  const v = getPath(resolved, field.engine);
126
129
  return field.map ? field.map.toFacet(v) : v;
127
130
  };
128
- const warned = new Map(checkBrush(brush, profile).issues.filter((i) => i.severity === "warning").map((i) => [i.facet, i.reason]));
131
+ const issues = checkBrush(brush, profile).issues;
132
+ const warned = new Map(issues.filter((i) => i.severity === "warning").map((i) => [i.facet, i.reason]));
133
+ const refused = new Set(issues.filter((i) => i.severity === "error").map((i) => i.facet));
134
+ const full = (s) => s === "full";
135
+ const engineOk = !["kind", "tool", "tip.shape", "marks", "behaviour.surface"].some((f) => refused.has(f));
129
136
  const noteFor = (path) => [...warned].find(([facet]) => path === facet || path.startsWith(`${facet}.`))?.[1];
130
137
  const withNote = (c) => {
131
138
  const note = noteFor(c.path);
@@ -147,22 +154,23 @@ function brushEditorSections(brush, profile = PAINT_PROFILE) {
147
154
  const isTool = brush.kind === "tool";
148
155
  const shape = brush.tip.shape;
149
156
  const marks = brush.marks?.kind ?? "continuous";
157
+ const exchangeFull = full(brush.behaviour?.exchange?.reservoir ? profile.reservoir : profile.exchange);
150
158
  add("medium", "Medium", [
151
- profile.kinds.length > 1 && choice("kind", "Kind", options(profile.kinds, { mark: "Lays a material", tool: "Works on paint" })),
159
+ profile.kinds.length > 1 && profile.kinds.includes(brush.kind) && choice("kind", "Kind", options(profile.kinds, { mark: "Lays a material", tool: "Works on paint" })),
152
160
  !isTool && choice("medium", "Medium", [{ value: "", label: "Any" }, ...options(BRUSH_MEDIA, MEDIUM_LABELS)], ""),
153
161
  !isTool && withNote({ kind: "text", path: "variety", label: "Variety", ariaLabel: "Brush variety", value: brush.variety ?? "", placeholder: "pencil, oil, highlighter\u2026" }),
154
- isTool && profile.tools.length > 0 && choice("tool", "Tool", options(profile.tools, TOOL_LABELS))
162
+ isTool && !!brush.tool && profile.tools.includes(brush.tool) && choice("tool", "Tool", options(profile.tools, TOOL_LABELS))
155
163
  ]);
156
164
  const engineControls = brushEngineControls(resolved);
157
165
  const tipSliders = BRUSH_SLIDERS.filter((c) => c.path.startsWith("tip.") && c.path !== "tip.smoothing");
158
166
  add("shape", "Shape", [
159
- choice("tip.shape", "Tip", options(profile.tipShapes.filter((s) => !isTool || s !== "stamp"), SHAPE_LABELS)),
167
+ !refused.has("tip.shape") && choice("tip.shape", "Tip", options(profile.tipShapes.filter((s) => !isTool || s !== "stamp"), SHAPE_LABELS)),
160
168
  ...tipSliders.map((c) => slider(c)),
161
169
  engine === "dab" && choice("tip.blend", "Blend", BLEND_MODES.map((m) => ({ value: m.id, label: m.label })), "normal"),
162
- ...shape === "bristle" && engineControls ? engineControls.sliders.filter((c) => c.path.startsWith("bristle.")).map((c) => slider(c)) : [],
163
- shape === "stamp" && withNote({ kind: "stamps", path: "tip.stamps", label: "Stamps", ariaLabel: "Brush stamps", value: [...value("tip.stamps") ?? []], max: marks === "particles" ? MAX_DOT_TEXTURES : MAX_STICKER_STAMPS, options: STAMP_OPTIONS })
170
+ ...shape === "bristle" && engineOk && engineControls ? engineControls.sliders.filter((c) => c.path.startsWith("bristle.")).map((c) => slider(c)) : [],
171
+ shape === "stamp" && engineOk && withNote({ kind: "stamps", path: "tip.stamps", label: "Stamps", ariaLabel: "Brush stamps", value: [...value("tip.stamps") ?? []], max: marks === "particles" ? MAX_DOT_TEXTURES : MAX_STICKER_STAMPS, options: STAMP_OPTIONS })
164
172
  ]);
165
- if (!isTool && profile.marks.length > 1) {
173
+ if (!isTool && profile.marks.length > 1 && engineOk) {
166
174
  const particles = marks === "particles" && engineControls ? engineControls.sliders.filter((c) => c.path.startsWith("particle.") && c.path !== "particle.waveSpeed") : [];
167
175
  const objects = marks === "objects" && engineControls ? engineControls.sliders.filter((c) => c.path.startsWith("sticker.")) : [];
168
176
  const render = value("marks.render");
@@ -177,7 +185,7 @@ function brushEditorSections(brush, profile = PAINT_PROFILE) {
177
185
  ...objects.map((c) => slider(c))
178
186
  ]);
179
187
  }
180
- const feel = BRUSH_SLIDERS.filter((c) => ["tip.smoothing", "pressureSmoothing", "dynamics.minSize", "dynamics.tiltSize", "dynamics.velocitySize"].includes(c.path)).filter((c) => !(c.path === "dynamics.tiltSize" && profile.tilt === "none") && !(c.path === "dynamics.velocitySize" && profile.velocity === "none"));
188
+ const feel = BRUSH_SLIDERS.filter((c) => ["tip.smoothing", "pressureSmoothing", "dynamics.minSize", "dynamics.tiltSize", "dynamics.velocitySize"].includes(c.path)).filter((c) => !(c.path === "dynamics.tiltSize" && !full(profile.tilt)) && !(c.path === "dynamics.velocitySize" && !full(profile.velocity)));
181
189
  add("feel", "Feel", [
182
190
  ...feel.map((c) => slider(c)),
183
191
  toggle("dynamics.pressureSize", "Pressure \u2192 size"),
@@ -185,19 +193,19 @@ function brushEditorSections(brush, profile = PAINT_PROFILE) {
185
193
  choice("dynamics.rotation", "Rotation", options(BRUSH_ROTATIONS, ROTATION_LABELS))
186
194
  ]);
187
195
  const surfaces = profile.surfaces;
188
- const engineSliders = (engineControls?.sliders ?? []).filter((c) => !/^(bristle|particle|sticker)\./.test(c.path) && c.path !== "light.amount" && c.path !== "impasto.rim" && c.path !== "impasto.clarity");
189
- const exchangeOk = (path) => !path?.startsWith("behaviour.exchange") || (path.startsWith("behaviour.exchange.reservoir") ? profile.reservoir !== "none" : profile.exchange !== "none");
196
+ const engineSliders = (engineOk ? engineControls?.sliders ?? [] : []).filter((c) => !/^(bristle|particle|sticker)\./.test(c.path) && c.path !== "light.amount" && c.path !== "impasto.rim" && c.path !== "impasto.clarity");
197
+ const exchangeOk = (path) => !path?.startsWith("behaviour.exchange") || (path.startsWith("behaviour.exchange.reservoir") ? full(profile.reservoir) : exchangeFull);
190
198
  add("behaviour", engineControls?.title ?? "Behaviour", [
191
- !isTool && surfaces.length > 1 && choice("behaviour.surface", "Surface", options(surfaces, SURFACE_LABELS), "dry"),
192
- profile.grain !== "none" && slider({ path: "grain", label: "Grain", min: 0, max: 1, step: 0.01 }, "behaviour.grain"),
193
- engine === "wet" && !isTool && choice("behaviour.wet.medium", "Medium", options(WET_MEDIA, WET_MEDIUM_LABELS), "watercolor"),
194
- engine === "impasto" && choice("behaviour.thick.applicator", "Tool", options(IMPASTO_TOOLS, APPLICATOR_LABELS), "brush"),
199
+ !isTool && surfaces.length > 1 && !refused.has("behaviour.surface") && choice("behaviour.surface", "Surface", options(surfaces, SURFACE_LABELS), "dry"),
200
+ full(profile.grain) && slider({ path: "grain", label: "Grain", min: 0, max: 1, step: 0.01 }, "behaviour.grain"),
201
+ engine === "wet" && engineOk && !isTool && choice("behaviour.wet.medium", "Medium", options(WET_MEDIA, WET_MEDIUM_LABELS), "watercolor"),
202
+ engine === "impasto" && engineOk && choice("behaviour.thick.applicator", "Tool", options(IMPASTO_TOOLS, APPLICATOR_LABELS), "brush"),
195
203
  ...engineSliders.map((c) => exchangeOk(facetOf(c.path)) ? slider(c) : null),
196
- engineControls?.mixingPath && fieldOf("behaviour.mixing") && choice("behaviour.mixing", "Mixing", options(["pigment", "rgb"], { pigment: "Pigment", rgb: "RGB" })),
197
- engineControls?.dirty && profile.exchange !== "none" && fieldOf("behaviour.exchange.dirty") && toggle("behaviour.exchange.dirty", "Dirty brush"),
198
- engine === "mixer" && toggle("behaviour.exchange.reservoir.refill", "Load each stroke")
204
+ engineOk && engineControls?.mixingPath && (engine === "wet" || exchangeFull) && fieldOf("behaviour.mixing") && choice("behaviour.mixing", "Mixing", options(["pigment", "rgb"], { pigment: "Pigment", rgb: "RGB" })),
205
+ engineOk && engineControls?.dirty && exchangeFull && fieldOf("behaviour.exchange.dirty") && toggle("behaviour.exchange.dirty", "Dirty brush"),
206
+ engine === "mixer" && engineOk && full(profile.reservoir) && toggle("behaviour.exchange.reservoir.refill", "Load each stroke")
199
207
  ]);
200
- if (!isTool && profile.light !== "none" && engineControls?.light) {
208
+ if (!isTool && full(profile.light) && engineOk && engineControls?.light) {
201
209
  add("light", "Light", [
202
210
  slider({ path: "light.amount", label: "Light", min: 0, max: 1, step: 0.01 }),
203
211
  toggle("light.pressure", "Light follows pressure"),
@@ -206,13 +214,13 @@ function brushEditorSections(brush, profile = PAINT_PROFILE) {
206
214
  toggle("light.glow", "Suggest a glow")
207
215
  ]);
208
216
  }
209
- if (profile.motion !== "none" && marks === "particles") add("motion", "Motion", [slider({ path: "particle.waveSpeed", label: "Wave speed", min: 0, max: 1, step: 0.01 })]);
217
+ if (full(profile.motion) && engineOk && marks === "particles") add("motion", "Motion", [slider({ path: "particle.waveSpeed", label: "Wave speed", min: 0, max: 1, step: 0.01 })]);
210
218
  if (!isTool) {
211
219
  const own = brush.colour?.default;
212
220
  const hex = (c) => typeof c === "string" ? brushColourHex(c, "light") ?? null : null;
213
221
  const pair = isDualColor(own) ? own : null;
214
222
  add("colour", "Colour", [
215
- withNote({ kind: "colour", path: "colour.default", label: "Default colour", ariaLabel: "Brush default colour", value: pair ? brushColourHex(pair, "light") ?? null : hex(own), dark: pair ? brushColourHex(pair, "dark") ?? null : null, gradient: own !== void 0 && typeof own === "object" && !pair }),
223
+ !refused.has("colour.default") && withNote({ kind: "colour", path: "colour.default", label: "Default colour", ariaLabel: "Brush default colour", value: pair ? brushColourHex(pair, "light") ?? null : hex(own), dark: pair ? brushColourHex(pair, "dark") ?? null : null, gradient: own !== void 0 && typeof own === "object" && !pair }),
216
224
  withNote({
217
225
  kind: "palette",
218
226
  path: "colour.palette",
@@ -226,6 +234,78 @@ function brushEditorSections(brush, profile = PAINT_PROFILE) {
226
234
  }
227
235
  return sections;
228
236
  }
237
+ var MARKS_WORDS = { particles: "particle marks", objects: "sticker marks", continuous: "continuous marks" };
238
+ function hiddenLabel(issue, b) {
239
+ switch (issue.facet) {
240
+ case "kind":
241
+ case "tool":
242
+ return b.kind === "tool" ? `the ${(TOOL_LABELS[b.tool] ?? b.tool ?? "tool").toLowerCase()} tool` : "its marks";
243
+ case "tip.shape":
244
+ return `a ${b.tip.shape} tip`;
245
+ case "marks":
246
+ return MARKS_WORDS[b.marks?.kind ?? "continuous"] ?? "its marks";
247
+ case "behaviour.surface":
248
+ return `${b.behaviour?.surface ?? "dry"} behaviour`;
249
+ case "behaviour.exchange.reservoir":
250
+ return "a Mixer Brush reservoir";
251
+ case "behaviour.exchange":
252
+ return "paint pick-up";
253
+ case "behaviour.grain":
254
+ return "paper grain";
255
+ case "light":
256
+ return "light";
257
+ case "motion":
258
+ return "motion";
259
+ case "dynamics.velocity":
260
+ return "velocity dynamics";
261
+ case "dynamics.tilt":
262
+ return "tilt dynamics";
263
+ case "colour.default":
264
+ return `a ${brushColourKind(b.colour.default)} default colour`;
265
+ default:
266
+ return null;
267
+ }
268
+ }
269
+ var sentence = (words) => words.length < 2 ? words[0] ?? "" : `${words.slice(0, -1).join(", ")} and ${words.at(-1)}`;
270
+ function brushEditorScope(brush, profile = PAINT_PROFILE) {
271
+ const hidden = [];
272
+ for (const issue of checkBrush(brush, profile).issues) {
273
+ const label = hiddenLabel(issue, brush);
274
+ if (label && !hidden.some((h) => h.label === label)) hidden.push({ facet: issue.facet, label });
275
+ }
276
+ return { hidden, note: hidden.length ? `Also has ${sentence(hidden.map((h) => h.label))}, which ${profile.name} can't use.` : null };
277
+ }
278
+ function brushIdFromName(name) {
279
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "brush";
280
+ }
281
+ function newBrushFor(profile, from, name = "New brush") {
282
+ const id = brushIdFromName(name);
283
+ const base = { id, name, kind: profile.kinds.includes("mark") ? "mark" : profile.kinds[0], ...profile.kinds.includes("mark") ? {} : { tool: profile.tools[0] }, tip: { shape: profile.tipShapes.includes("round") ? "round" : profile.tipShapes[0] } };
284
+ if (from) {
285
+ const stripped = degrade(from, profile, { ignored: true });
286
+ if (!stripped.issues.length) return { ...stripped.brush, id, name };
287
+ }
288
+ return base;
289
+ }
290
+ async function saveBrushEdit(library, options2) {
291
+ const { original, profile } = options2;
292
+ const check = checkBrushEdit(original, options2.brush, profile);
293
+ if (!check.ok) {
294
+ const errors = check.issues.filter((i) => i.severity === "error").map((i) => `${i.facet}: ${i.reason} ${i.fix}`);
295
+ throw new BrushLibraryError("invalid", `${profile.name} can't use this brush: ${errors.join(" ")} Nothing was saved.`);
296
+ }
297
+ const inPlace = !!original && options2.name === void 0 && !library.isBuiltin(original.id) && original.id === options2.brush.id && !!await library.get(original.id);
298
+ let brush = options2.brush;
299
+ if (!inPlace) {
300
+ const name = ((options2.name ?? (original ? `${original.name} copy` : brush.name)).trim() || "Brush").slice(0, 80);
301
+ const stem = brushIdFromName(name).slice(0, 56);
302
+ let id = stem;
303
+ for (let n = 2; library.isBuiltin(id) || await library.get(id); n++) id = `${stem}-${n}`;
304
+ brush = { ...brush, id, name };
305
+ }
306
+ const saved = await library.save(brush, { overwrite: inPlace });
307
+ return { ...saved, kept: check.kept };
308
+ }
229
309
  function setBrushEditorValue(brush, path, value) {
230
310
  const next = JSON.parse(JSON.stringify(brush));
231
311
  if (path === "kind") {
@@ -418,23 +498,30 @@ export {
418
498
  brushColourHex,
419
499
  brushColourKind,
420
500
  brushContracts,
501
+ brushEditorScope,
421
502
  brushEditorSections,
503
+ brushIdFromName,
422
504
  brushLibraryGroups,
423
505
  changedBrushPaths,
424
506
  checkBrush,
507
+ checkBrushEdit,
425
508
  createBrushLibrary,
426
509
  createBrushLibraryToolProvider,
427
510
  createBrushLibraryTools,
428
511
  degrade,
512
+ isBrushLibrary,
429
513
  listPresets,
430
514
  mergeBrushPatch,
431
515
  migrateBrushV1,
516
+ newBrushFor,
432
517
  normalizeBrushSpec,
433
518
  parseBrushLibrary,
434
519
  resolveBrush,
435
520
  resolveCategory,
436
521
  resolveEngine,
437
522
  resolveWetMedium,
523
+ saveBrushEdit,
438
524
  setBrushEditorValue,
439
- snapshotBrushLibrary
525
+ snapshotBrushLibrary,
526
+ withKeptIssues
440
527
  };
@@ -91,6 +91,8 @@ export interface BrushLibrary {
91
91
  /** Called after every change. Returns an unsubscribe function. */
92
92
  subscribe(listener: () => void): () => void;
93
93
  }
94
+ /** Whether `library` is a whole `BrushLibrary` (it can save), not only a list to read: hosts offer brush editing on one. */
95
+ export declare function isBrushLibrary(library: unknown): library is BrushLibrary;
94
96
  /**
95
97
  * Brushes read from a library file: `{ format: 'pixerate-brushes', version: 1 | 2, brushes }` or
96
98
  * a bare array. Version 1 brushes are migrated (`migrateBrushV1`); every brush is normalised.
@@ -91,6 +91,8 @@ export interface BrushLibrary {
91
91
  /** Called after every change. Returns an unsubscribe function. */
92
92
  subscribe(listener: () => void): () => void;
93
93
  }
94
+ /** Whether `library` is a whole `BrushLibrary` (it can save), not only a list to read: hosts offer brush editing on one. */
95
+ export declare function isBrushLibrary(library: unknown): library is BrushLibrary;
94
96
  /**
95
97
  * Brushes read from a library file: `{ format: 'pixerate-brushes', version: 1 | 2, brushes }` or
96
98
  * a bare array. Version 1 brushes are migrated (`migrateBrushV1`); every brush is normalised.
@@ -66,6 +66,21 @@ export declare const BRUSH_HOST_PROFILES: Readonly<Record<string, BrushHostProfi
66
66
  export declare function brushColourKind(paint: BrushPaint): BrushColourKind;
67
67
  /** What a brush needs from a host, and what the host does about it. */
68
68
  export declare function checkBrush(brush: Brush, profile: BrushHostProfile): BrushCheck;
69
+ export interface BrushEditCheck extends BrushCheck {
70
+ /**
71
+ * Errors the brush had before the edit: facets this host can't use, kept unchanged for the
72
+ * hosts that can (a light paint brush edited in Canvas keeps its light). Not in `issues`.
73
+ */
74
+ kept: BrushIssue[];
75
+ }
76
+ /**
77
+ * A check of an edit: errors the original brush already had (`before`, its check for the same
78
+ * host) are kept rather than refused, so a host edits what it renders and leaves the rest alone.
79
+ * Without `before` (a new brush) every error refuses it.
80
+ */
81
+ export declare function withKeptIssues(check: BrushCheck, before: BrushCheck | null): BrushEditCheck;
82
+ /** `checkBrush` for an edit of `original` (`null`: a new brush), keeping facets it already had that the host can't use. */
83
+ export declare function checkBrushEdit(original: Brush | null, brush: Brush, profile: BrushHostProfile): BrushEditCheck;
69
84
  /** The presets (default: the built-in ones) a host can render, in library order. */
70
85
  export declare function listPresets(profile: BrushHostProfile, presets?: readonly Brush[]): Brush[];
71
86
  export interface DegradedBrush {
@@ -80,6 +95,10 @@ export interface DegradedBrush {
80
95
  * The brush the host can render: facets it refuses are removed (light, motion, a reservoir, a
81
96
  * wet or thick surface, particle or object marks, bristle and stamp tips become round, an
82
97
  * unsupported default colour), each reported in `dropped`. A tool the host lacks can't be
83
- * stripped and stays in `issues`.
98
+ * stripped and stays in `issues`. `ignored: true` also removes what the host paints without
99
+ * (its warnings: velocity on canvas, grain in the image editor, palette colours it skips), for
100
+ * a brush made only of what the host uses.
84
101
  */
85
- export declare function degrade(brush: Brush, profile: BrushHostProfile): DegradedBrush;
102
+ export declare function degrade(brush: Brush, profile: BrushHostProfile, options?: {
103
+ ignored?: boolean;
104
+ }): DegradedBrush;
@@ -66,6 +66,21 @@ export declare const BRUSH_HOST_PROFILES: Readonly<Record<string, BrushHostProfi
66
66
  export declare function brushColourKind(paint: BrushPaint): BrushColourKind;
67
67
  /** What a brush needs from a host, and what the host does about it. */
68
68
  export declare function checkBrush(brush: Brush, profile: BrushHostProfile): BrushCheck;
69
+ export interface BrushEditCheck extends BrushCheck {
70
+ /**
71
+ * Errors the brush had before the edit: facets this host can't use, kept unchanged for the
72
+ * hosts that can (a light paint brush edited in Canvas keeps its light). Not in `issues`.
73
+ */
74
+ kept: BrushIssue[];
75
+ }
76
+ /**
77
+ * A check of an edit: errors the original brush already had (`before`, its check for the same
78
+ * host) are kept rather than refused, so a host edits what it renders and leaves the rest alone.
79
+ * Without `before` (a new brush) every error refuses it.
80
+ */
81
+ export declare function withKeptIssues(check: BrushCheck, before: BrushCheck | null): BrushEditCheck;
82
+ /** `checkBrush` for an edit of `original` (`null`: a new brush), keeping facets it already had that the host can't use. */
83
+ export declare function checkBrushEdit(original: Brush | null, brush: Brush, profile: BrushHostProfile): BrushEditCheck;
69
84
  /** The presets (default: the built-in ones) a host can render, in library order. */
70
85
  export declare function listPresets(profile: BrushHostProfile, presets?: readonly Brush[]): Brush[];
71
86
  export interface DegradedBrush {
@@ -80,6 +95,10 @@ export interface DegradedBrush {
80
95
  * The brush the host can render: facets it refuses are removed (light, motion, a reservoir, a
81
96
  * wet or thick surface, particle or object marks, bristle and stamp tips become round, an
82
97
  * unsupported default colour), each reported in `dropped`. A tool the host lacks can't be
83
- * stripped and stays in `issues`.
98
+ * stripped and stays in `issues`. `ignored: true` also removes what the host paints without
99
+ * (its warnings: velocity on canvas, grain in the image editor, palette colours it skips), for
100
+ * a brush made only of what the host uses.
84
101
  */
85
- export declare function degrade(brush: Brush, profile: BrushHostProfile): DegradedBrush;
102
+ export declare function degrade(brush: Brush, profile: BrushHostProfile, options?: {
103
+ ignored?: boolean;
104
+ }): DegradedBrush;
@@ -8,8 +8,8 @@
8
8
  * Sliders reuse the paint editor's tables (`BRUSH_SLIDERS`, `brushEngineControls`), mapped from
9
9
  * engine paths to facet paths through `BRUSH_FIELDS`, so the panels and the editor agree.
10
10
  */
11
- import type { BrushLibraryEntry } from './library.cjs';
12
- import { type BrushHostProfile } from './profiles.cjs';
11
+ import { type BrushLibrary, type BrushLibraryEntry, type SaveBrushResult } from './library.cjs';
12
+ import { type BrushHostProfile, type BrushIssue } from './profiles.cjs';
13
13
  import { type Brush } from './types.cjs';
14
14
  export type BrushEditorSectionId = 'medium' | 'shape' | 'marks' | 'feel' | 'behaviour' | 'light' | 'motion' | 'colour';
15
15
  interface ControlBase {
@@ -79,10 +79,63 @@ export interface BrushEditorSection {
79
79
  }
80
80
  /**
81
81
  * Sections of controls for a brush, in order, as the host `profile` (default the paint editor)
82
- * can render them: sections and controls it refuses are left out, ones it paints without carry
83
- * a note. Values are the brush's own, else the defaults of the engine it resolves to.
82
+ * can render them: only the sections, controls and options the host uses are listed. Facets of
83
+ * the brush the host can't use (a paint brush's light opened in Canvas) get no control, so an
84
+ * edit keeps them as they are; `brushEditorScope` names them. Values are the brush's own, else
85
+ * the defaults of the engine it resolves to.
84
86
  */
85
87
  export declare function brushEditorSections(brush: Brush, profile?: BrushHostProfile): BrushEditorSection[];
88
+ /** What a brush has that the host can't use: one facet, in words. */
89
+ export interface BrushEditorHiddenFacet {
90
+ facet: string;
91
+ /** In words, for a sentence: `light`, `wet behaviour`, `a bristle tip`. */
92
+ label: string;
93
+ }
94
+ export interface BrushEditorScope {
95
+ /** Facets the brush has that the host can't use (refused or painted without): they get no control and an edit keeps them. */
96
+ hidden: BrushEditorHiddenFacet[];
97
+ /** A short note naming them ("Also has light and wet behaviour, which Canvas can't use."), or `null`. */
98
+ note: string | null;
99
+ }
100
+ /**
101
+ * What the editor leaves out of a brush for this host: the facets the host refuses or paints
102
+ * without, which `brushEditorSections` gives no control, so saving an edit keeps them unchanged
103
+ * for the hosts that use them. `note` names them for the editor to show.
104
+ */
105
+ export declare function brushEditorScope(brush: Brush, profile?: BrushHostProfile): BrushEditorScope;
106
+ /** A brush id from a name: lowercase letters, digits and dashes (`'brush'` when nothing is left). */
107
+ export declare function brushIdFromName(name: string): string;
108
+ /**
109
+ * A new brush for a host to start editing: `from` (the brush in use) with every facet the host
110
+ * can't use removed (`degrade` with `ignored`), else a plain round brush. It passes `checkBrush`
111
+ * for that host, and the editor then offers only what the host uses, so it stays that way.
112
+ */
113
+ export declare function newBrushFor(profile: BrushHostProfile, from?: Brush | null, name?: string): Brush;
114
+ export interface SaveBrushEditOptions {
115
+ /** The brush as it was before editing (`null`: a new brush). Facets of it the host can't use are kept. */
116
+ original: Brush | null;
117
+ /** The edited brush. */
118
+ brush: Brush;
119
+ /** The host the edit was made in. */
120
+ profile: BrushHostProfile;
121
+ /**
122
+ * Save as a new brush with this name (its id made from the name, `-2`… when taken). Without
123
+ * it the original is changed in place, or a copy is saved when it is built in or new.
124
+ */
125
+ name?: string;
126
+ }
127
+ /** What `saveBrushEdit` stored, plus the facets kept for other hosts. */
128
+ export interface SaveBrushEditResult extends SaveBrushResult {
129
+ /** Facets this host can't use that the brush keeps (see `checkBrushEdit`). */
130
+ kept: BrushIssue[];
131
+ }
132
+ /**
133
+ * Saves a brush edited in a host's editor to the shared library, by the rule the library tools
134
+ * follow too: the edit may not add what the host can't use (a new brush must pass `checkBrush`),
135
+ * and what the original had for other hosts is kept. Built-in presets are never changed: the
136
+ * edit is saved as a copy (`<name> copy`). Refusals are `BrushLibraryError`s with the facet and fix.
137
+ */
138
+ export declare function saveBrushEdit(library: Pick<BrushLibrary, 'get' | 'save' | 'isBuiltin'>, options: SaveBrushEditOptions): Promise<SaveBrushEditResult>;
86
139
  /**
87
140
  * The brush with one control's value set (`undefined` removes it), normalised. Changing the kind,
88
141
  * the marks or the tip shape starts that facet afresh. Returns the brush and what was clamped or
@@ -8,8 +8,8 @@
8
8
  * Sliders reuse the paint editor's tables (`BRUSH_SLIDERS`, `brushEngineControls`), mapped from
9
9
  * engine paths to facet paths through `BRUSH_FIELDS`, so the panels and the editor agree.
10
10
  */
11
- import type { BrushLibraryEntry } from './library.js';
12
- import { type BrushHostProfile } from './profiles.js';
11
+ import { type BrushLibrary, type BrushLibraryEntry, type SaveBrushResult } from './library.js';
12
+ import { type BrushHostProfile, type BrushIssue } from './profiles.js';
13
13
  import { type Brush } from './types.js';
14
14
  export type BrushEditorSectionId = 'medium' | 'shape' | 'marks' | 'feel' | 'behaviour' | 'light' | 'motion' | 'colour';
15
15
  interface ControlBase {
@@ -79,10 +79,63 @@ export interface BrushEditorSection {
79
79
  }
80
80
  /**
81
81
  * Sections of controls for a brush, in order, as the host `profile` (default the paint editor)
82
- * can render them: sections and controls it refuses are left out, ones it paints without carry
83
- * a note. Values are the brush's own, else the defaults of the engine it resolves to.
82
+ * can render them: only the sections, controls and options the host uses are listed. Facets of
83
+ * the brush the host can't use (a paint brush's light opened in Canvas) get no control, so an
84
+ * edit keeps them as they are; `brushEditorScope` names them. Values are the brush's own, else
85
+ * the defaults of the engine it resolves to.
84
86
  */
85
87
  export declare function brushEditorSections(brush: Brush, profile?: BrushHostProfile): BrushEditorSection[];
88
+ /** What a brush has that the host can't use: one facet, in words. */
89
+ export interface BrushEditorHiddenFacet {
90
+ facet: string;
91
+ /** In words, for a sentence: `light`, `wet behaviour`, `a bristle tip`. */
92
+ label: string;
93
+ }
94
+ export interface BrushEditorScope {
95
+ /** Facets the brush has that the host can't use (refused or painted without): they get no control and an edit keeps them. */
96
+ hidden: BrushEditorHiddenFacet[];
97
+ /** A short note naming them ("Also has light and wet behaviour, which Canvas can't use."), or `null`. */
98
+ note: string | null;
99
+ }
100
+ /**
101
+ * What the editor leaves out of a brush for this host: the facets the host refuses or paints
102
+ * without, which `brushEditorSections` gives no control, so saving an edit keeps them unchanged
103
+ * for the hosts that use them. `note` names them for the editor to show.
104
+ */
105
+ export declare function brushEditorScope(brush: Brush, profile?: BrushHostProfile): BrushEditorScope;
106
+ /** A brush id from a name: lowercase letters, digits and dashes (`'brush'` when nothing is left). */
107
+ export declare function brushIdFromName(name: string): string;
108
+ /**
109
+ * A new brush for a host to start editing: `from` (the brush in use) with every facet the host
110
+ * can't use removed (`degrade` with `ignored`), else a plain round brush. It passes `checkBrush`
111
+ * for that host, and the editor then offers only what the host uses, so it stays that way.
112
+ */
113
+ export declare function newBrushFor(profile: BrushHostProfile, from?: Brush | null, name?: string): Brush;
114
+ export interface SaveBrushEditOptions {
115
+ /** The brush as it was before editing (`null`: a new brush). Facets of it the host can't use are kept. */
116
+ original: Brush | null;
117
+ /** The edited brush. */
118
+ brush: Brush;
119
+ /** The host the edit was made in. */
120
+ profile: BrushHostProfile;
121
+ /**
122
+ * Save as a new brush with this name (its id made from the name, `-2`… when taken). Without
123
+ * it the original is changed in place, or a copy is saved when it is built in or new.
124
+ */
125
+ name?: string;
126
+ }
127
+ /** What `saveBrushEdit` stored, plus the facets kept for other hosts. */
128
+ export interface SaveBrushEditResult extends SaveBrushResult {
129
+ /** Facets this host can't use that the brush keeps (see `checkBrushEdit`). */
130
+ kept: BrushIssue[];
131
+ }
132
+ /**
133
+ * Saves a brush edited in a host's editor to the shared library, by the rule the library tools
134
+ * follow too: the edit may not add what the host can't use (a new brush must pass `checkBrush`),
135
+ * and what the original had for other hosts is kept. Built-in presets are never changed: the
136
+ * edit is saved as a copy (`<name> copy`). Refusals are `BrushLibraryError`s with the facet and fix.
137
+ */
138
+ export declare function saveBrushEdit(library: Pick<BrushLibrary, 'get' | 'save' | 'isBuiltin'>, options: SaveBrushEditOptions): Promise<SaveBrushEditResult>;
86
139
  /**
87
140
  * The brush with one control's value set (`undefined` removes it), normalised. Changing the kind,
88
141
  * the marks or the tip shape starts that facet afresh. Returns the brush and what was clamped or
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pathDataToPolyline
3
- } from "./chunk-EHALWBBD.js";
3
+ } from "./chunk-JEML4FSA.js";
4
4
  import {
5
5
  BRUSH_PRESETS,
6
6
  BUILTIN_BRUSHES,
@@ -11,8 +11,8 @@ import {
11
11
  normalizeBrushSpec,
12
12
  resolveBrush,
13
13
  resolveCategory
14
- } from "./chunk-5IW65VQ6.js";
15
- import "./chunk-APX63ILU.js";
14
+ } from "./chunk-EE7SAYBF.js";
15
+ import "./chunk-ACFSFUQH.js";
16
16
  import {
17
17
  formatNumber,
18
18
  getShapeSvg,
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkUVZ7RQOEcjs = require('./chunk-UVZ7RQOE.cjs');
3
+ var _chunkG5ADO47Gcjs = require('./chunk-G5ADO47G.cjs');
4
4
 
5
5
 
6
6
 
@@ -11,8 +11,8 @@ var _chunkUVZ7RQOEcjs = require('./chunk-UVZ7RQOE.cjs');
11
11
 
12
12
 
13
13
 
14
- var _chunkTZOX7KWVcjs = require('./chunk-TZOX7KWV.cjs');
15
- require('./chunk-JSDND7DF.cjs');
14
+ var _chunkBY5EBP26cjs = require('./chunk-BY5EBP26.cjs');
15
+ require('./chunk-OTN6VB5S.cjs');
16
16
 
17
17
 
18
18
 
@@ -45,17 +45,17 @@ require('./chunk-5UEFBM2M.cjs');
45
45
  require('./chunk-IPJ3TEYR.cjs');
46
46
 
47
47
  // src/canvas/brushstrokeRender.ts
48
- var PRESETS = _chunkTZOX7KWVcjs.listPresets.call(void 0, _chunkTZOX7KWVcjs.CANVAS_PROFILE).map((spec) => _chunkTZOX7KWVcjs.BUILTIN_BRUSHES.find((b) => b.id === spec.id));
48
+ var PRESETS = _chunkBY5EBP26cjs.listPresets.call(void 0, _chunkBY5EBP26cjs.CANVAS_PROFILE).map((spec) => _chunkBY5EBP26cjs.BUILTIN_BRUSHES.find((b) => b.id === spec.id));
49
49
  var presetIds = () => PRESETS.map((b) => b.id).join(", ");
50
- var refusal = (brush) => _chunkTZOX7KWVcjs.checkBrush.call(void 0, brush, _chunkTZOX7KWVcjs.CANVAS_PROFILE).issues.filter((i) => i.severity === "error").map((i) => `${i.reason} ${i.fix}`).join(" ");
50
+ var refusal = (brush) => _chunkBY5EBP26cjs.checkBrush.call(void 0, brush, _chunkBY5EBP26cjs.CANVAS_PROFILE).issues.filter((i) => i.severity === "error").map((i) => `${i.reason} ${i.fix}`).join(" ");
51
51
  function readCanvasBrushSpec(spec) {
52
52
  let brush;
53
53
  try {
54
- brush = _chunkTZOX7KWVcjs.normalizeBrushSpec.call(void 0, spec);
54
+ brush = _chunkBY5EBP26cjs.normalizeBrushSpec.call(void 0, spec);
55
55
  } catch (e) {
56
56
  throw new Error(`Not a brush: ${e.message}`);
57
57
  }
58
- if (!_chunkTZOX7KWVcjs.checkBrush.call(void 0, brush, _chunkTZOX7KWVcjs.CANVAS_PROFILE).ok) throw new Error(`Brush '${brush.id}' (${brush.name}) can't brush a canvas stroke: ${refusal(brush)}`);
58
+ if (!_chunkBY5EBP26cjs.checkBrush.call(void 0, brush, _chunkBY5EBP26cjs.CANVAS_PROFILE).ok) throw new Error(`Brush '${brush.id}' (${brush.name}) can't brush a canvas stroke: ${refusal(brush)}`);
59
59
  return brush;
60
60
  }
61
61
  function listCanvasBrushes(user = []) {
@@ -63,12 +63,12 @@ function listCanvasBrushes(user = []) {
63
63
  const d = _optionalChain([spec, 'access', _2 => _2.colour, 'optionalAccess', _3 => _3.default]);
64
64
  return typeof d === "string" || _chunkMLLTWAXHcjs.isDualColor.call(void 0, d) ? { defaultColor: d } : {};
65
65
  };
66
- const out = PRESETS.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size, builtin: true, ...colourOf(_chunkTZOX7KWVcjs.BRUSH_PRESETS.find((p) => p.id === b.id)) }));
66
+ const out = PRESETS.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size, builtin: true, ...colourOf(_chunkBY5EBP26cjs.BRUSH_PRESETS.find((p) => p.id === b.id)) }));
67
67
  for (const raw of user) {
68
68
  try {
69
69
  const brush = readCanvasBrushSpec(raw);
70
70
  if (out.some((b) => b.id === brush.id)) continue;
71
- out.push({ id: brush.id, name: brush.name, category: _chunkTZOX7KWVcjs.resolveCategory.call(void 0, brush), size: _chunkTZOX7KWVcjs.resolveBrush.call(void 0, brush).tip.size, builtin: false, spec: brush, ...colourOf(brush) });
71
+ out.push({ id: brush.id, name: brush.name, category: _chunkBY5EBP26cjs.resolveCategory.call(void 0, brush), size: _chunkBY5EBP26cjs.resolveBrush.call(void 0, brush).tip.size, builtin: false, spec: brush, ...colourOf(brush) });
72
72
  } catch (e2) {
73
73
  }
74
74
  }
@@ -79,15 +79,15 @@ function resolveCanvasBrush(brush, width, spec) {
79
79
  const sized = (b) => given === void 0 ? b : { ...b, tip: { ...b.tip, size: given } };
80
80
  if (typeof brush === "string") {
81
81
  if (spec !== void 0 && spec !== null) {
82
- const saved = _chunkTZOX7KWVcjs.resolveBrush.call(void 0, readCanvasBrushSpec(spec));
82
+ const saved = _chunkBY5EBP26cjs.resolveBrush.call(void 0, readCanvasBrushSpec(spec));
83
83
  if (!_chunkPXDIAYB6cjs.isBrushstrokeBrush.call(void 0, saved)) throw new Error(`Brush '${saved.id}' (${saved.name}) can't brush a canvas stroke: it is not a dry dab brush.`);
84
84
  return sized(saved);
85
85
  }
86
86
  const preset = PRESETS.find((b) => b.id === brush);
87
87
  if (preset) return sized(preset);
88
- const other = _chunkTZOX7KWVcjs.BRUSH_PRESETS.find((b) => b.id === brush);
88
+ const other = _chunkBY5EBP26cjs.BRUSH_PRESETS.find((b) => b.id === brush);
89
89
  if (other) throw new Error(`Brush '${brush}' (${other.name}) can't brush a canvas stroke: ${refusal(other)} Use one of: ${presetIds()}, or a saved brush the canvas can paint.`);
90
- throw new Error(`Unknown brush '${brush}'. ${_chunkTZOX7KWVcjs.closest.call(void 0, brush, PRESETS.map((b) => b.id))}Use one of the dry presets (${presetIds()}), a saved brush from the brush library, or tip settings ({ hardness, spacing, flow, \u2026 }).`);
90
+ throw new Error(`Unknown brush '${brush}'. ${_chunkBY5EBP26cjs.closest.call(void 0, brush, PRESETS.map((b) => b.id))}Use one of the dry presets (${presetIds()}), a saved brush from the brush library, or tip settings ({ hardness, spacing, flow, \u2026 }).`);
91
91
  }
92
92
  if (!brush || typeof brush !== "object" || Array.isArray(brush)) {
93
93
  throw new Error("A brush is a preset or saved brush id (see listCanvasBrushes / canvas.listBrushes) or an object of tip settings.");
@@ -123,7 +123,7 @@ function renderShapeBrushstrokeNow(shape, scale, place = {}) {
123
123
  const textured = typeof seed === "number" && Number.isFinite(seed);
124
124
  const size = _nullishCoalesce(shape.brush.size, () => ( (textured ? void 0 : _nullishCoalesce(shape.strokeWidth, () => ( 3)))));
125
125
  const brush = resolveCanvasBrush(shape.brush.brush, size, shape.brush.spec);
126
- const points = _chunkUVZ7RQOEcjs.pathDataToPolyline.call(void 0, shape.d);
126
+ const points = _chunkG5ADO47Gcjs.pathDataToPolyline.call(void 0, shape.d);
127
127
  if (!points.length) return null;
128
128
  const { pressure, tiltX, tiltY } = shape.brush;
129
129
  const input = {