@pixerate/editor 0.258.0 → 0.260.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 (78) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.js +1 -1
  3. package/dist/animation-editor/index.cjs +61 -59
  4. package/dist/animation-editor/index.js +5 -3
  5. package/dist/brushes/contract.d.cts +24 -0
  6. package/dist/brushes/contract.d.ts +24 -0
  7. package/dist/brushes/index.cjs +146 -31
  8. package/dist/brushes/index.d.cts +1 -0
  9. package/dist/brushes/index.d.ts +1 -0
  10. package/dist/brushes/index.js +121 -6
  11. package/dist/{brushstrokeRender-JI4W6F2G.js → brushstrokeRender-25Q5BEY6.js} +17 -7
  12. package/dist/{brushstrokeRender-MQ6PSDM5.cjs → brushstrokeRender-N6UPZSQS.cjs} +31 -21
  13. package/dist/canvas/brushTypes.d.cts +5 -0
  14. package/dist/canvas/brushTypes.d.ts +5 -0
  15. package/dist/canvas/drawController.d.cts +15 -1
  16. package/dist/canvas/drawController.d.ts +15 -1
  17. package/dist/canvas/index.cjs +136 -102
  18. package/dist/canvas/index.js +35 -1
  19. package/dist/{chunk-3PPJRMIG.js → chunk-3O47TPGX.js} +197 -18
  20. package/dist/{chunk-5Q5MLXAD.cjs → chunk-3TQW5MPW.cjs} +23 -23
  21. package/dist/{chunk-JBBJE76O.cjs → chunk-3ZRNK3RO.cjs} +1138 -85
  22. package/dist/{chunk-YSGW7VV4.cjs → chunk-43NT242D.cjs} +1401 -228
  23. package/dist/{chunk-SMTUQWGY.js → chunk-5IW65VQ6.js} +1 -1
  24. package/dist/{chunk-YQ4PQTBB.js → chunk-6NZL24W4.js} +9 -1
  25. package/dist/{chunk-O3ZVZSNJ.cjs → chunk-7AUQ2UJ3.cjs} +137 -129
  26. package/dist/{chunk-4M4E3N6X.js → chunk-CQVTGBQI.js} +1 -1
  27. package/dist/{chunk-SSFMWJDK.js → chunk-D4TE5JNW.js} +1192 -19
  28. package/dist/{chunk-AORZ5M5C.js → chunk-EHALWBBD.js} +1 -1
  29. package/dist/{chunk-6WCQ35GI.cjs → chunk-NHA26O5X.cjs} +2 -2
  30. package/dist/{chunk-DPYAO5U6.cjs → chunk-PVJMTLPJ.cjs} +197 -18
  31. package/dist/{chunk-MQGQTKVW.cjs → chunk-PYTJBVW5.cjs} +29 -29
  32. package/dist/{chunk-IXQZHCOF.js → chunk-QAR2N7HZ.js} +1 -1
  33. package/dist/{chunk-5P4JEOIV.js → chunk-TMDDMJCR.js} +2 -2
  34. package/dist/{chunk-RSIKDWOP.cjs → chunk-TZOX7KWV.cjs} +15 -15
  35. package/dist/{chunk-IPUTIUGQ.cjs → chunk-UVZ7RQOE.cjs} +1 -1
  36. package/dist/{chunk-A7GJ6BXI.js → chunk-WJ4WQJCC.js} +1062 -9
  37. package/dist/{chunk-ICTPLVSR.js → chunk-X2EXYY4J.js} +1 -1
  38. package/dist/{chunk-WQCNC4DK.js → chunk-XC6DPRYI.js} +695 -4
  39. package/dist/{chunk-KBYB7OWB.cjs → chunk-YHDHFJUC.cjs} +776 -85
  40. package/dist/{chunk-P3PZ63M3.cjs → chunk-YLUIFXSL.cjs} +35 -35
  41. package/dist/commands/index.cjs +2 -2
  42. package/dist/commands/index.js +1 -1
  43. package/dist/image-editor/index.cjs +1385 -2484
  44. package/dist/image-editor/index.js +139 -1238
  45. package/dist/image-editor/selection-worker.cjs +3 -4
  46. package/dist/image-editor/selection-worker.js +1 -2
  47. package/dist/paint/index.cjs +3 -5
  48. package/dist/paint/index.js +40 -42
  49. package/dist/paint-editor/contract.d.cts +29 -0
  50. package/dist/paint-editor/contract.d.ts +29 -0
  51. package/dist/paint-editor/dab-stroke.d.cts +5 -0
  52. package/dist/paint-editor/dab-stroke.d.ts +5 -0
  53. package/dist/paint-editor/index.cjs +402 -258
  54. package/dist/paint-editor/index.d.cts +1 -0
  55. package/dist/paint-editor/index.d.ts +1 -0
  56. package/dist/paint-editor/index.js +197 -53
  57. package/dist/paint-editor/stamp-tip.d.cts +17 -0
  58. package/dist/paint-editor/stamp-tip.d.ts +17 -0
  59. package/dist/paint-editor/ui.d.cts +21 -1
  60. package/dist/paint-editor/ui.d.ts +21 -1
  61. package/dist/raster/index.cjs +2 -4
  62. package/dist/raster/index.js +6 -8
  63. package/dist/vector/brushstroke.d.cts +6 -0
  64. package/dist/vector/brushstroke.d.ts +6 -0
  65. package/dist/vector/controller.d.cts +11 -2
  66. package/dist/vector/controller.d.ts +11 -2
  67. package/dist/vector/index.cjs +990 -973
  68. package/dist/vector/index.js +28 -11
  69. package/package.json +1 -1
  70. package/skills/pixerate-brushes/SKILL.md +1 -1
  71. package/skills/pixerate-canvas/SKILL.md +3 -1
  72. package/skills/pixerate-vector/SKILL.md +2 -2
  73. package/dist/chunk-2VJAVXLI.js +0 -192
  74. package/dist/chunk-62Z5WLRA.js +0 -700
  75. package/dist/chunk-BOHDJJ2O.cjs +0 -192
  76. package/dist/chunk-NKGB64CW.cjs +0 -700
  77. package/dist/chunk-SS53NNZO.cjs +0 -1066
  78. package/dist/chunk-ZNA3IQZF.js +0 -1066
@@ -8,12 +8,10 @@
8
8
 
9
9
 
10
10
 
11
- var _chunkMQGQTKVWcjs = require('../chunk-MQGQTKVW.cjs');
11
+ var _chunkPYTJBVW5cjs = require('../chunk-PYTJBVW5.cjs');
12
12
  require('../chunk-AVTPZO4Z.cjs');
13
13
 
14
14
 
15
- var _chunkLRTR4ASZcjs = require('../chunk-LRTR4ASZ.cjs');
16
- require('../chunk-A2IS7EHE.cjs');
17
15
 
18
16
 
19
17
 
@@ -59,10 +57,15 @@ require('../chunk-A2IS7EHE.cjs');
59
57
 
60
58
 
61
59
 
62
- var _chunkRSIKDWOPcjs = require('../chunk-RSIKDWOP.cjs');
60
+
61
+ var _chunkTZOX7KWVcjs = require('../chunk-TZOX7KWV.cjs');
63
62
 
64
63
 
65
64
  var _chunkJSDND7DFcjs = require('../chunk-JSDND7DF.cjs');
65
+
66
+
67
+ var _chunkLRTR4ASZcjs = require('../chunk-LRTR4ASZ.cjs');
68
+ require('../chunk-A2IS7EHE.cjs');
66
69
  require('../chunk-U67EN7GG.cjs');
67
70
  require('../chunk-WOFXZ3XU.cjs');
68
71
 
@@ -109,20 +112,20 @@ var ARIA = {
109
112
  "light.pressure": "Light follows pressure"
110
113
  };
111
114
  var aria = (path, label) => _nullishCoalesce(ARIA[path], () => ( `Brush ${label.toLowerCase()}`));
112
- function brushEditorSections(brush, profile = _chunkRSIKDWOPcjs.PAINT_PROFILE) {
113
- const resolved = _chunkRSIKDWOPcjs.resolveBrush.call(void 0, brush);
114
- const engine = _chunkRSIKDWOPcjs.resolveEngine.call(void 0, brush);
115
- const fieldOf = (facet) => _chunkRSIKDWOPcjs.BRUSH_FIELDS.find((f) => f.facet === facet && f.engines.includes(engine));
116
- const facetOf = (enginePath) => _optionalChain([(_nullishCoalesce(_chunkRSIKDWOPcjs.BRUSH_FIELDS.find((f) => f.engine === enginePath && f.engines.includes(engine)), () => ( _chunkRSIKDWOPcjs.BRUSH_FIELDS.find((f) => f.engine === enginePath)))), 'optionalAccess', _ => _.facet]);
115
+ function brushEditorSections(brush, profile = _chunkTZOX7KWVcjs.PAINT_PROFILE) {
116
+ const resolved = _chunkTZOX7KWVcjs.resolveBrush.call(void 0, brush);
117
+ const engine = _chunkTZOX7KWVcjs.resolveEngine.call(void 0, brush);
118
+ const fieldOf = (facet) => _chunkTZOX7KWVcjs.BRUSH_FIELDS.find((f) => f.facet === facet && f.engines.includes(engine));
119
+ const facetOf = (enginePath) => _optionalChain([(_nullishCoalesce(_chunkTZOX7KWVcjs.BRUSH_FIELDS.find((f) => f.engine === enginePath && f.engines.includes(engine)), () => ( _chunkTZOX7KWVcjs.BRUSH_FIELDS.find((f) => f.engine === enginePath)))), 'optionalAccess', _ => _.facet]);
117
120
  const value = (path) => {
118
- const own = _chunkRSIKDWOPcjs.getPath.call(void 0, brush, path);
121
+ const own = _chunkTZOX7KWVcjs.getPath.call(void 0, brush, path);
119
122
  if (own !== void 0) return own;
120
123
  const field = fieldOf(path);
121
124
  if (!field) return void 0;
122
- const v = _chunkRSIKDWOPcjs.getPath.call(void 0, resolved, field.engine);
125
+ const v = _chunkTZOX7KWVcjs.getPath.call(void 0, resolved, field.engine);
123
126
  return field.map ? field.map.toFacet(v) : v;
124
127
  };
125
- const warned = new Map(_chunkRSIKDWOPcjs.checkBrush.call(void 0, brush, profile).issues.filter((i) => i.severity === "warning").map((i) => [i.facet, i.reason]));
128
+ const warned = new Map(_chunkTZOX7KWVcjs.checkBrush.call(void 0, brush, profile).issues.filter((i) => i.severity === "warning").map((i) => [i.facet, i.reason]));
126
129
  const noteFor = (path) => _optionalChain([[...warned], 'access', _2 => _2.find, 'call', _3 => _3(([facet]) => path === facet || path.startsWith(`${facet}.`)), 'optionalAccess', _4 => _4[1]]);
127
130
  const withNote = (c) => {
128
131
  const note = noteFor(c.path);
@@ -146,18 +149,18 @@ function brushEditorSections(brush, profile = _chunkRSIKDWOPcjs.PAINT_PROFILE) {
146
149
  const marks = _nullishCoalesce(_optionalChain([brush, 'access', _7 => _7.marks, 'optionalAccess', _8 => _8.kind]), () => ( "continuous"));
147
150
  add("medium", "Medium", [
148
151
  profile.kinds.length > 1 && choice("kind", "Kind", options(profile.kinds, { mark: "Lays a material", tool: "Works on paint" })),
149
- !isTool && choice("medium", "Medium", [{ value: "", label: "Any" }, ...options(_chunkRSIKDWOPcjs.BRUSH_MEDIA, MEDIUM_LABELS)], ""),
152
+ !isTool && choice("medium", "Medium", [{ value: "", label: "Any" }, ...options(_chunkTZOX7KWVcjs.BRUSH_MEDIA, MEDIUM_LABELS)], ""),
150
153
  !isTool && withNote({ kind: "text", path: "variety", label: "Variety", ariaLabel: "Brush variety", value: _nullishCoalesce(brush.variety, () => ( "")), placeholder: "pencil, oil, highlighter\u2026" }),
151
154
  isTool && profile.tools.length > 0 && choice("tool", "Tool", options(profile.tools, TOOL_LABELS))
152
155
  ]);
153
- const engineControls = _chunkMQGQTKVWcjs.brushEngineControls.call(void 0, resolved);
154
- const tipSliders = _chunkMQGQTKVWcjs.BRUSH_SLIDERS.filter((c) => c.path.startsWith("tip.") && c.path !== "tip.smoothing");
156
+ const engineControls = _chunkPYTJBVW5cjs.brushEngineControls.call(void 0, resolved);
157
+ const tipSliders = _chunkPYTJBVW5cjs.BRUSH_SLIDERS.filter((c) => c.path.startsWith("tip.") && c.path !== "tip.smoothing");
155
158
  add("shape", "Shape", [
156
159
  choice("tip.shape", "Tip", options(profile.tipShapes.filter((s) => !isTool || s !== "stamp"), SHAPE_LABELS)),
157
160
  ...tipSliders.map((c) => slider(c)),
158
161
  engine === "dab" && choice("tip.blend", "Blend", _chunk2VX55NFMcjs.BLEND_MODES.map((m) => ({ value: m.id, label: m.label })), "normal"),
159
162
  ...shape === "bristle" && engineControls ? engineControls.sliders.filter((c) => c.path.startsWith("bristle.")).map((c) => slider(c)) : [],
160
- shape === "stamp" && withNote({ kind: "stamps", path: "tip.stamps", label: "Stamps", ariaLabel: "Brush stamps", value: [..._nullishCoalesce(value("tip.stamps"), () => ( []))], max: marks === "particles" ? _chunkRSIKDWOPcjs.MAX_DOT_TEXTURES : _chunkRSIKDWOPcjs.MAX_STICKER_STAMPS, options: STAMP_OPTIONS })
163
+ shape === "stamp" && withNote({ kind: "stamps", path: "tip.stamps", label: "Stamps", ariaLabel: "Brush stamps", value: [..._nullishCoalesce(value("tip.stamps"), () => ( []))], max: marks === "particles" ? _chunkTZOX7KWVcjs.MAX_DOT_TEXTURES : _chunkTZOX7KWVcjs.MAX_STICKER_STAMPS, options: STAMP_OPTIONS })
161
164
  ]);
162
165
  if (!isTool && profile.marks.length > 1) {
163
166
  const particles = marks === "particles" && engineControls ? engineControls.sliders.filter((c) => c.path.startsWith("particle.") && c.path !== "particle.waveSpeed") : [];
@@ -165,21 +168,21 @@ function brushEditorSections(brush, profile = _chunkRSIKDWOPcjs.PAINT_PROFILE) {
165
168
  const render = value("marks.render");
166
169
  add("marks", "Marks", [
167
170
  choice("marks.kind", "Laid as", options(profile.marks, MARKS_LABELS), "continuous"),
168
- marks === "particles" && choice("marks.physics", "Particles", options(_chunkRSIKDWOPcjs.PARTICLE_KINDS, PHYSICS_LABELS)),
169
- marks === "particles" && choice("marks.render", "Drawn as", options(_chunkRSIKDWOPcjs.PARTICLE_RENDERS, RENDER_LABELS)),
170
- marks === "particles" && render === "ribbon" && choice("marks.ribbonPairs", "Ribbon joins", options(_chunkRSIKDWOPcjs.RIBBON_PAIRS, { adjacent: "Neighbours", "first-last": "Outermost" })),
171
- marks === "particles" && shape === "stamp" && choice("marks.stampOrder", "Texture order", options(_chunkRSIKDWOPcjs.DOT_TEXTURE_ORDERS, { sequence: "In turn", random: "Random" })),
172
- marks === "particles" && shape === "stamp" && choice("marks.stampRotation", "Texture turn", options(_chunkRSIKDWOPcjs.DOT_ROTATIONS, { follow: "Follow motion", random: "Random", fixed: "Tip angle" })),
171
+ marks === "particles" && choice("marks.physics", "Particles", options(_chunkTZOX7KWVcjs.PARTICLE_KINDS, PHYSICS_LABELS)),
172
+ marks === "particles" && choice("marks.render", "Drawn as", options(_chunkTZOX7KWVcjs.PARTICLE_RENDERS, RENDER_LABELS)),
173
+ marks === "particles" && render === "ribbon" && choice("marks.ribbonPairs", "Ribbon joins", options(_chunkTZOX7KWVcjs.RIBBON_PAIRS, { adjacent: "Neighbours", "first-last": "Outermost" })),
174
+ marks === "particles" && shape === "stamp" && choice("marks.stampOrder", "Texture order", options(_chunkTZOX7KWVcjs.DOT_TEXTURE_ORDERS, { sequence: "In turn", random: "Random" })),
175
+ marks === "particles" && shape === "stamp" && choice("marks.stampRotation", "Texture turn", options(_chunkTZOX7KWVcjs.DOT_ROTATIONS, { follow: "Follow motion", random: "Random", fixed: "Tip angle" })),
173
176
  ...particles.map((c) => slider(c)),
174
177
  ...objects.map((c) => slider(c))
175
178
  ]);
176
179
  }
177
- const feel = _chunkMQGQTKVWcjs.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"));
180
+ const feel = _chunkPYTJBVW5cjs.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"));
178
181
  add("feel", "Feel", [
179
182
  ...feel.map((c) => slider(c)),
180
183
  toggle("dynamics.pressureSize", "Pressure \u2192 size"),
181
184
  toggle("dynamics.pressureOpacity", "Pressure \u2192 opacity"),
182
- choice("dynamics.rotation", "Rotation", options(_chunkRSIKDWOPcjs.BRUSH_ROTATIONS, ROTATION_LABELS))
185
+ choice("dynamics.rotation", "Rotation", options(_chunkTZOX7KWVcjs.BRUSH_ROTATIONS, ROTATION_LABELS))
183
186
  ]);
184
187
  const surfaces = profile.surfaces;
185
188
  const engineSliders = (_nullishCoalesce(_optionalChain([engineControls, 'optionalAccess', _9 => _9.sliders]), () => ( []))).filter((c) => !/^(bristle|particle|sticker)\./.test(c.path) && c.path !== "light.amount" && c.path !== "impasto.rim" && c.path !== "impasto.clarity");
@@ -187,8 +190,8 @@ function brushEditorSections(brush, profile = _chunkRSIKDWOPcjs.PAINT_PROFILE) {
187
190
  add("behaviour", _nullishCoalesce(_optionalChain([engineControls, 'optionalAccess', _12 => _12.title]), () => ( "Behaviour")), [
188
191
  !isTool && surfaces.length > 1 && choice("behaviour.surface", "Surface", options(surfaces, SURFACE_LABELS), "dry"),
189
192
  profile.grain !== "none" && slider({ path: "grain", label: "Grain", min: 0, max: 1, step: 0.01 }, "behaviour.grain"),
190
- engine === "wet" && !isTool && choice("behaviour.wet.medium", "Medium", options(_chunkRSIKDWOPcjs.WET_MEDIA, WET_MEDIUM_LABELS), "watercolor"),
191
- engine === "impasto" && choice("behaviour.thick.applicator", "Tool", options(_chunkRSIKDWOPcjs.IMPASTO_TOOLS, APPLICATOR_LABELS), "brush"),
193
+ engine === "wet" && !isTool && choice("behaviour.wet.medium", "Medium", options(_chunkTZOX7KWVcjs.WET_MEDIA, WET_MEDIUM_LABELS), "watercolor"),
194
+ engine === "impasto" && choice("behaviour.thick.applicator", "Tool", options(_chunkTZOX7KWVcjs.IMPASTO_TOOLS, APPLICATOR_LABELS), "brush"),
192
195
  ...engineSliders.map((c) => exchangeOk(facetOf(c.path)) ? slider(c) : null),
193
196
  _optionalChain([engineControls, 'optionalAccess', _13 => _13.mixingPath]) && fieldOf("behaviour.mixing") && choice("behaviour.mixing", "Mixing", options(["pigment", "rgb"], { pigment: "Pigment", rgb: "RGB" })),
194
197
  _optionalChain([engineControls, 'optionalAccess', _14 => _14.dirty]) && profile.exchange !== "none" && fieldOf("behaviour.exchange.dirty") && toggle("behaviour.exchange.dirty", "Dirty brush"),
@@ -216,7 +219,7 @@ function brushEditorSections(brush, profile = _chunkRSIKDWOPcjs.PAINT_PROFILE) {
216
219
  label: "Palette",
217
220
  ariaLabel: "Brush palette",
218
221
  value: (_nullishCoalesce(_optionalChain([brush, 'access', _21 => _21.colour, 'optionalAccess', _22 => _22.palette]), () => ( []))).flatMap((c) => _chunkMLLTWAXHcjs.isDualColor.call(void 0, c) ? [{ light: _chunkLRTR4ASZcjs.brushColourHex.call(void 0, c, "light"), dark: _chunkLRTR4ASZcjs.brushColourHex.call(void 0, c, "dark") }] : hex(c) ? [hex(c)] : []),
219
- max: _chunkRSIKDWOPcjs.MAX_BRUSH_PALETTE
222
+ max: _chunkTZOX7KWVcjs.MAX_BRUSH_PALETTE
220
223
  }),
221
224
  toggle("colour.own", "Always its own colour")
222
225
  ]);
@@ -245,13 +248,13 @@ function setBrushEditorValue(brush, path, value) {
245
248
  if (value !== "bristle") delete next.tip.bristle;
246
249
  } else if (value === void 0 || value === "") {
247
250
  const keys = path.split(".");
248
- const parent = _chunkRSIKDWOPcjs.getPath.call(void 0, next, keys.slice(0, -1).join("."));
251
+ const parent = _chunkTZOX7KWVcjs.getPath.call(void 0, next, keys.slice(0, -1).join("."));
249
252
  if (keys.length === 1) delete next[keys[0]];
250
253
  else if (parent) delete parent[keys[keys.length - 1]];
251
- } else _chunkRSIKDWOPcjs.setPath.call(void 0, next, path, value);
254
+ } else _chunkTZOX7KWVcjs.setPath.call(void 0, next, path, value);
252
255
  const problems = [];
253
256
  try {
254
- return { brush: _chunkRSIKDWOPcjs.normalizeBrushSpec.call(void 0, JSON.parse(JSON.stringify(next)), problems), problems };
257
+ return { brush: _chunkTZOX7KWVcjs.normalizeBrushSpec.call(void 0, JSON.parse(JSON.stringify(next)), problems), problems };
255
258
  } catch (e) {
256
259
  return { brush, problems: [e.message] };
257
260
  }
@@ -267,7 +270,7 @@ function brushLibraryGroups(entries, filter = {}) {
267
270
  const text = words([b.id, b.name, _nullishCoalesce(b.variety, () => ( "")), _nullishCoalesce(b.medium, () => ( "")), _nullishCoalesce(b.tool, () => ( "")), ..._nullishCoalesce(b.tags, () => ( []))].join(" "));
268
271
  if (!wanted.every((w) => text.some((t) => t.startsWith(w)))) continue;
269
272
  }
270
- if (filter.profile && !_chunkRSIKDWOPcjs.checkBrush.call(void 0, b, filter.profile).ok) continue;
273
+ if (filter.profile && !_chunkTZOX7KWVcjs.checkBrush.call(void 0, b, filter.profile).ok) continue;
271
274
  const id = b.kind === "tool" ? "tools" : _nullishCoalesce(b.medium, () => ( "other"));
272
275
  let g = groups.get(id);
273
276
  if (!g) groups.set(id, g = { id, title: id === "tools" ? "Tools" : id === "other" ? "Other" : MEDIUM_LABELS[id], varieties: [] });
@@ -280,6 +283,118 @@ function brushLibraryGroups(entries, filter = {}) {
280
283
  return [...groups.values()].sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
281
284
  }
282
285
 
286
+ // src/brushes/contract.ts
287
+ var BRUSH_CONTRACT_FORMAT_VERSION = 1;
288
+ var SUPPORT = { type: "string", enum: ["full", "ignored", "none"], description: "full: rendered; ignored: accepted, painted without it (a warning); none: refused (an error)." };
289
+ var BRUSH_PROFILE_JSON_SCHEMA = {
290
+ type: "object",
291
+ description: "What a host can render (`BrushHostProfile`).",
292
+ properties: {
293
+ id: { type: "string", description: "paint, image, canvas, vector\u2026" },
294
+ name: { type: "string", description: "Display name, used in messages." },
295
+ kinds: { type: "array", items: { type: "string", enum: [..._chunkTZOX7KWVcjs.BRUSH_KINDS] }, description: "Brush kinds it has." },
296
+ tools: { type: "array", items: { type: "string", enum: [..._chunkTZOX7KWVcjs.BRUSH_TOOLS] }, description: "Tools it has (kind tool)." },
297
+ tipShapes: { type: "array", items: { type: "string", enum: [..._chunkTZOX7KWVcjs.BRUSH_TIP_SHAPES] }, description: "Tip shapes it renders." },
298
+ marks: { type: "array", items: { type: "string", enum: [..._chunkTZOX7KWVcjs.BRUSH_MARKS_KINDS] }, description: "Marks kinds it renders." },
299
+ surfaces: { type: "array", items: { type: "string", enum: [..._chunkTZOX7KWVcjs.BRUSH_SURFACES] }, description: "Surfaces it renders." },
300
+ exchange: { ...SUPPORT, description: "Picking paint up and mixing it in." },
301
+ reservoir: { ...SUPPORT, description: "The Mixer Brush reservoir." },
302
+ grain: { ...SUPPORT, description: "Paper grain response." },
303
+ light: { ...SUPPORT, description: "Light paint." },
304
+ motion: { ...SUPPORT, description: "Motion after painting." },
305
+ colours: { type: "array", items: { type: "string", enum: ["solid", "dual", "gradient", "effect"] }, description: "Colour kinds a default colour may be." },
306
+ velocity: { ...SUPPORT, description: "Velocity dynamics." },
307
+ tilt: { ...SUPPORT, description: "Tilt dynamics." }
308
+ },
309
+ required: ["id", "name", "kinds", "tools", "tipShapes", "marks", "surfaces", "exchange", "reservoir", "grain", "light", "motion", "colours", "velocity", "tilt"],
310
+ additionalProperties: false
311
+ };
312
+ var BRUSH_LIBRARY_FILE_JSON_SCHEMA = {
313
+ type: "object",
314
+ description: "A brush library file, as export_brushes writes it. Readers also accept version 1 files (flat engine brushes, migrated) and a bare array of brushes.",
315
+ properties: {
316
+ format: { type: "string", const: "pixerate-brushes", description: 'Always "pixerate-brushes".' },
317
+ version: { type: "integer", const: 2, description: "Always 2." },
318
+ brushes: { type: "array", items: { $ref: "brush.schema.json" }, description: "The brushes, each a facet brush (brush.schema.json)." }
319
+ },
320
+ required: ["format", "version", "brushes"],
321
+ additionalProperties: false
322
+ };
323
+ function migrationInputs() {
324
+ const pick = (id) => _chunkTZOX7KWVcjs.brushToJSON.call(void 0, _chunkTZOX7KWVcjs.BUILTIN_BRUSHES.find((b) => b.id === id));
325
+ return [
326
+ { id: "plain", name: "Plain" },
327
+ pick("pencil-6b"),
328
+ { ...pick("soft-pastel"), id: "my-pastel", name: "My pastel", category: "custom", grain: 0.2, tip: { ...pick("soft-pastel").tip, size: 50 } },
329
+ pick("mixer-brush"),
330
+ pick("watercolor-wash"),
331
+ pick("impasto-brush"),
332
+ pick("hair-strands"),
333
+ pick("bristle-round"),
334
+ pick("sticker-stars"),
335
+ pick("rainbow-marker"),
336
+ pick("blender"),
337
+ pick("add-water"),
338
+ pick("spray-can"),
339
+ // Fields an engine never reads are dropped on migration.
340
+ { ...pick("ink-pen"), id: "ink-with-mixer", name: "Ink with mixer settings", mixer: { wet: 0.9, load: 0.1, mix: 0.9, loadEachStroke: false, cleanEachStroke: false, mixing: "pigment" } }
341
+ ];
342
+ }
343
+ function editorSectionFixtures() {
344
+ const ids = ["highlighter", "chalk", "watercolor-wash", "hair-strands", "mixer-brush", "blender", "spray-can"];
345
+ return ids.flatMap(
346
+ (id) => Object.keys(_chunkTZOX7KWVcjs.BRUSH_HOST_PROFILES).map((host) => {
347
+ const brush = _chunkTZOX7KWVcjs.BRUSH_PRESETS.find((b) => b.id === id);
348
+ return { brush: id, host, sections: brushEditorSections(brush, _chunkTZOX7KWVcjs.BRUSH_HOST_PROFILES[host]) };
349
+ })
350
+ );
351
+ }
352
+ function toolDefinitions() {
353
+ const library = _chunkTZOX7KWVcjs.createBrushLibrary.call(void 0, );
354
+ const toolsFor = (host) => _chunkPYTJBVW5cjs.createBrushLibraryTools.call(void 0, library, { profile: _chunkTZOX7KWVcjs.BRUSH_HOST_PROFILES[host], renderPreview: () => ({ data: "", mimeType: "image/png" }) });
355
+ return {
356
+ tools: toolsFor("paint").map((t) => ({ name: t.name, title: t.title, description: t.description, inputSchema: t.inputSchema, annotations: t.annotations })),
357
+ hosts: Object.fromEntries(Object.keys(_chunkTZOX7KWVcjs.BRUSH_HOST_PROFILES).map((host) => [host, toolsFor(host).map((t) => t.name)]))
358
+ };
359
+ }
360
+ function brushContracts() {
361
+ const presets = _chunkTZOX7KWVcjs.BRUSH_PRESETS.map((b) => JSON.parse(JSON.stringify(b)));
362
+ const resolved = _chunkTZOX7KWVcjs.BRUSH_PRESETS.map((b) => ({ id: b.id, engine: _chunkTZOX7KWVcjs.resolveEngine.call(void 0, b), category: _chunkTZOX7KWVcjs.resolveCategory.call(void 0, b), brush: _chunkTZOX7KWVcjs.brushToJSON.call(void 0, _chunkTZOX7KWVcjs.resolveBrush.call(void 0, b)) }));
363
+ const profiles = Object.values(_chunkTZOX7KWVcjs.BRUSH_HOST_PROFILES);
364
+ const checks = _chunkTZOX7KWVcjs.BRUSH_PRESETS.map((b) => ({
365
+ id: b.id,
366
+ hosts: Object.fromEntries(profiles.map((p) => {
367
+ const c = _chunkTZOX7KWVcjs.checkBrush.call(void 0, b, p);
368
+ return [p.id, { ok: c.ok, issues: c.issues.map(({ facet, severity }) => ({ facet, severity })) }];
369
+ }))
370
+ }));
371
+ const migrations = migrationInputs().map((input) => ({ input, output: _chunkTZOX7KWVcjs.migrateBrushV1.call(void 0, input), resolved: _chunkTZOX7KWVcjs.brushToJSON.call(void 0, _chunkTZOX7KWVcjs.resolveBrush.call(void 0, _chunkTZOX7KWVcjs.migrateBrushV1.call(void 0, input))), engine: _chunkTZOX7KWVcjs.normalizePaintBrush.call(void 0, input).engine }));
372
+ const files = {
373
+ "brush.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "brush.schema.json", ..._chunkTZOX7KWVcjs.BRUSH_JSON_SCHEMA },
374
+ "profile.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "profile.schema.json", ...BRUSH_PROFILE_JSON_SCHEMA },
375
+ "library-file.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "library-file.schema.json", ...BRUSH_LIBRARY_FILE_JSON_SCHEMA },
376
+ "presets.json": presets,
377
+ "resolved.json": resolved,
378
+ "profiles.json": profiles,
379
+ "checks.json": checks,
380
+ "migrations.json": migrations,
381
+ "tools.json": toolDefinitions(),
382
+ "editor-sections.json": editorSectionFixtures()
383
+ };
384
+ files["manifest.json"] = {
385
+ domain: "brushes",
386
+ formatVersion: BRUSH_CONTRACT_FORMAT_VERSION,
387
+ limits: { maxPalette: _chunkTZOX7KWVcjs.MAX_BRUSH_PALETTE },
388
+ counts: { presets: presets.length, profiles: profiles.length, migrations: migrations.length, editorSections: files["editor-sections.json"].length },
389
+ files: Object.keys(files).sort()
390
+ };
391
+ return files;
392
+ }
393
+
394
+
395
+
396
+
397
+
283
398
 
284
399
 
285
400
 
@@ -322,4 +437,4 @@ function brushLibraryGroups(entries, filter = {}) {
322
437
 
323
438
 
324
439
 
325
- exports.BRUSH_FACET_SCHEMAS = _chunkRSIKDWOPcjs.BRUSH_FACET_SCHEMAS; exports.BRUSH_HOST_PROFILES = _chunkRSIKDWOPcjs.BRUSH_HOST_PROFILES; exports.BRUSH_JSON_SCHEMA = _chunkRSIKDWOPcjs.BRUSH_JSON_SCHEMA; exports.BRUSH_KINDS = _chunkRSIKDWOPcjs.BRUSH_KINDS; exports.BRUSH_MARKS_KINDS = _chunkRSIKDWOPcjs.BRUSH_MARKS_KINDS; exports.BRUSH_MEDIA = _chunkRSIKDWOPcjs.BRUSH_MEDIA; exports.BRUSH_PATCH_PROPERTIES = _chunkMQGQTKVWcjs.BRUSH_PATCH_PROPERTIES; exports.BRUSH_PRESETS = _chunkRSIKDWOPcjs.BRUSH_PRESETS; exports.BRUSH_SURFACES = _chunkRSIKDWOPcjs.BRUSH_SURFACES; exports.BRUSH_TIP_SHAPES = _chunkRSIKDWOPcjs.BRUSH_TIP_SHAPES; exports.BRUSH_TOOLS = _chunkRSIKDWOPcjs.BRUSH_TOOLS; exports.BRUSH_UNSET_PROPERTY = _chunkMQGQTKVWcjs.BRUSH_UNSET_PROPERTY; exports.BRUSH_WORD = _chunkRSIKDWOPcjs.BRUSH_WORD; exports.BrushLibraryError = _chunkRSIKDWOPcjs.BrushLibraryError; exports.CANVAS_PROFILE = _chunkRSIKDWOPcjs.CANVAS_PROFILE; exports.IMAGE_PROFILE = _chunkRSIKDWOPcjs.IMAGE_PROFILE; exports.MAX_BRUSH_PALETTE = _chunkRSIKDWOPcjs.MAX_BRUSH_PALETTE; exports.PAINT_PROFILE = _chunkRSIKDWOPcjs.PAINT_PROFILE; exports.UNSETTABLE_BRUSH_FACETS = _chunkMQGQTKVWcjs.UNSETTABLE_BRUSH_FACETS; exports.VECTOR_PROFILE = _chunkRSIKDWOPcjs.VECTOR_PROFILE; exports.WET_BRUSH_TOOLS = _chunkRSIKDWOPcjs.WET_BRUSH_TOOLS; exports.brushColourHex = _chunkLRTR4ASZcjs.brushColourHex; exports.brushColourKind = _chunkRSIKDWOPcjs.brushColourKind; exports.brushEditorSections = brushEditorSections; exports.brushLibraryGroups = brushLibraryGroups; exports.changedBrushPaths = _chunkMQGQTKVWcjs.changedBrushPaths; exports.checkBrush = _chunkRSIKDWOPcjs.checkBrush; exports.createBrushLibrary = _chunkRSIKDWOPcjs.createBrushLibrary; exports.createBrushLibraryToolProvider = _chunkMQGQTKVWcjs.createBrushLibraryToolProvider; exports.createBrushLibraryTools = _chunkMQGQTKVWcjs.createBrushLibraryTools; exports.degrade = _chunkRSIKDWOPcjs.degrade; exports.listPresets = _chunkRSIKDWOPcjs.listPresets; exports.mergeBrushPatch = _chunkMQGQTKVWcjs.mergeBrushPatch; exports.migrateBrushV1 = _chunkRSIKDWOPcjs.migrateBrushV1; exports.normalizeBrushSpec = _chunkRSIKDWOPcjs.normalizeBrushSpec; exports.parseBrushLibrary = _chunkRSIKDWOPcjs.parseBrushLibrary2; exports.resolveBrush = _chunkRSIKDWOPcjs.resolveBrush; exports.resolveCategory = _chunkRSIKDWOPcjs.resolveCategory; exports.resolveEngine = _chunkRSIKDWOPcjs.resolveEngine; exports.resolveWetMedium = _chunkRSIKDWOPcjs.resolveWetMedium; exports.setBrushEditorValue = setBrushEditorValue; exports.snapshotBrushLibrary = _chunkRSIKDWOPcjs.snapshotBrushLibrary;
440
+ exports.BRUSH_CONTRACT_FORMAT_VERSION = BRUSH_CONTRACT_FORMAT_VERSION; exports.BRUSH_FACET_SCHEMAS = _chunkTZOX7KWVcjs.BRUSH_FACET_SCHEMAS; exports.BRUSH_HOST_PROFILES = _chunkTZOX7KWVcjs.BRUSH_HOST_PROFILES; exports.BRUSH_JSON_SCHEMA = _chunkTZOX7KWVcjs.BRUSH_JSON_SCHEMA; exports.BRUSH_KINDS = _chunkTZOX7KWVcjs.BRUSH_KINDS; exports.BRUSH_LIBRARY_FILE_JSON_SCHEMA = BRUSH_LIBRARY_FILE_JSON_SCHEMA; exports.BRUSH_MARKS_KINDS = _chunkTZOX7KWVcjs.BRUSH_MARKS_KINDS; exports.BRUSH_MEDIA = _chunkTZOX7KWVcjs.BRUSH_MEDIA; exports.BRUSH_PATCH_PROPERTIES = _chunkPYTJBVW5cjs.BRUSH_PATCH_PROPERTIES; exports.BRUSH_PRESETS = _chunkTZOX7KWVcjs.BRUSH_PRESETS; exports.BRUSH_PROFILE_JSON_SCHEMA = BRUSH_PROFILE_JSON_SCHEMA; exports.BRUSH_SURFACES = _chunkTZOX7KWVcjs.BRUSH_SURFACES; exports.BRUSH_TIP_SHAPES = _chunkTZOX7KWVcjs.BRUSH_TIP_SHAPES; exports.BRUSH_TOOLS = _chunkTZOX7KWVcjs.BRUSH_TOOLS; exports.BRUSH_UNSET_PROPERTY = _chunkPYTJBVW5cjs.BRUSH_UNSET_PROPERTY; exports.BRUSH_WORD = _chunkTZOX7KWVcjs.BRUSH_WORD; exports.BrushLibraryError = _chunkTZOX7KWVcjs.BrushLibraryError; exports.CANVAS_PROFILE = _chunkTZOX7KWVcjs.CANVAS_PROFILE; exports.IMAGE_PROFILE = _chunkTZOX7KWVcjs.IMAGE_PROFILE; exports.MAX_BRUSH_PALETTE = _chunkTZOX7KWVcjs.MAX_BRUSH_PALETTE; exports.PAINT_PROFILE = _chunkTZOX7KWVcjs.PAINT_PROFILE; exports.UNSETTABLE_BRUSH_FACETS = _chunkPYTJBVW5cjs.UNSETTABLE_BRUSH_FACETS; exports.VECTOR_PROFILE = _chunkTZOX7KWVcjs.VECTOR_PROFILE; exports.WET_BRUSH_TOOLS = _chunkTZOX7KWVcjs.WET_BRUSH_TOOLS; exports.brushColourHex = _chunkLRTR4ASZcjs.brushColourHex; exports.brushColourKind = _chunkTZOX7KWVcjs.brushColourKind; exports.brushContracts = brushContracts; exports.brushEditorSections = brushEditorSections; exports.brushLibraryGroups = brushLibraryGroups; exports.changedBrushPaths = _chunkPYTJBVW5cjs.changedBrushPaths; exports.checkBrush = _chunkTZOX7KWVcjs.checkBrush; exports.createBrushLibrary = _chunkTZOX7KWVcjs.createBrushLibrary; exports.createBrushLibraryToolProvider = _chunkPYTJBVW5cjs.createBrushLibraryToolProvider; exports.createBrushLibraryTools = _chunkPYTJBVW5cjs.createBrushLibraryTools; exports.degrade = _chunkTZOX7KWVcjs.degrade; exports.listPresets = _chunkTZOX7KWVcjs.listPresets; exports.mergeBrushPatch = _chunkPYTJBVW5cjs.mergeBrushPatch; exports.migrateBrushV1 = _chunkTZOX7KWVcjs.migrateBrushV1; exports.normalizeBrushSpec = _chunkTZOX7KWVcjs.normalizeBrushSpec; exports.parseBrushLibrary = _chunkTZOX7KWVcjs.parseBrushLibrary2; exports.resolveBrush = _chunkTZOX7KWVcjs.resolveBrush; exports.resolveCategory = _chunkTZOX7KWVcjs.resolveCategory; exports.resolveEngine = _chunkTZOX7KWVcjs.resolveEngine; exports.resolveWetMedium = _chunkTZOX7KWVcjs.resolveWetMedium; exports.setBrushEditorValue = setBrushEditorValue; exports.snapshotBrushLibrary = _chunkTZOX7KWVcjs.snapshotBrushLibrary;
@@ -15,6 +15,7 @@ export { resolveBrush, resolveCategory, resolveEngine, resolveWetMedium, type Re
15
15
  export { migrateBrushV1 } from './migrate.cjs';
16
16
  export { BRUSH_PRESETS } from './presets.cjs';
17
17
  export { brushColourHex } from './colour.cjs';
18
+ export { BRUSH_CONTRACT_FORMAT_VERSION, BRUSH_LIBRARY_FILE_JSON_SCHEMA, BRUSH_PROFILE_JSON_SCHEMA, brushContracts } from './contract.cjs';
18
19
  export { BRUSH_HOST_PROFILES, brushColourKind, CANVAS_PROFILE, checkBrush, degrade, IMAGE_PROFILE, listPresets, PAINT_PROFILE, VECTOR_PROFILE, type BrushCheck, type BrushColourKind, type BrushHostProfile, type BrushIssue, type BrushSupport, type DegradedBrush } from './profiles.cjs';
19
20
  export { BrushLibraryError, createBrushLibrary, parseBrushLibrary, snapshotBrushLibrary, type BrushFilter, type BrushLibrary, type BrushLibraryEntry, type BrushLibraryErrorCode, type BrushLibraryFile, type BrushLibrarySnapshot, type BrushLibraryOptions, type ImportBrushesResult, type SaveBrushResult, } from './library.cjs';
20
21
  export { BRUSH_PATCH_PROPERTIES, BRUSH_UNSET_PROPERTY, changedBrushPaths, mergeBrushPatch, UNSETTABLE_BRUSH_FACETS, type BrushPatch, type UnsettableBrushFacet } from './patch.cjs';
@@ -15,6 +15,7 @@ export { resolveBrush, resolveCategory, resolveEngine, resolveWetMedium, type Re
15
15
  export { migrateBrushV1 } from './migrate.js';
16
16
  export { BRUSH_PRESETS } from './presets.js';
17
17
  export { brushColourHex } from './colour.js';
18
+ export { BRUSH_CONTRACT_FORMAT_VERSION, BRUSH_LIBRARY_FILE_JSON_SCHEMA, BRUSH_PROFILE_JSON_SCHEMA, brushContracts } from './contract.js';
18
19
  export { BRUSH_HOST_PROFILES, brushColourKind, CANVAS_PROFILE, checkBrush, degrade, IMAGE_PROFILE, listPresets, PAINT_PROFILE, VECTOR_PROFILE, type BrushCheck, type BrushColourKind, type BrushHostProfile, type BrushIssue, type BrushSupport, type DegradedBrush } from './profiles.js';
19
20
  export { BrushLibraryError, createBrushLibrary, parseBrushLibrary, snapshotBrushLibrary, type BrushFilter, type BrushLibrary, type BrushLibraryEntry, type BrushLibraryErrorCode, type BrushLibraryFile, type BrushLibrarySnapshot, type BrushLibraryOptions, type ImportBrushesResult, type SaveBrushResult, } from './library.js';
20
21
  export { BRUSH_PATCH_PROPERTIES, BRUSH_UNSET_PROPERTY, changedBrushPaths, mergeBrushPatch, UNSETTABLE_BRUSH_FACETS, type BrushPatch, type UnsettableBrushFacet } from './patch.js';
@@ -8,12 +8,8 @@ import {
8
8
  createBrushLibraryToolProvider,
9
9
  createBrushLibraryTools,
10
10
  mergeBrushPatch
11
- } from "../chunk-ICTPLVSR.js";
11
+ } from "../chunk-X2EXYY4J.js";
12
12
  import "../chunk-J5UQHWVL.js";
13
- import {
14
- brushColourHex
15
- } from "../chunk-FJBQTZS4.js";
16
- import "../chunk-3CI7FRMK.js";
17
13
  import {
18
14
  BRUSH_FACET_SCHEMAS,
19
15
  BRUSH_FIELDS,
@@ -28,6 +24,7 @@ import {
28
24
  BRUSH_TIP_SHAPES,
29
25
  BRUSH_TOOLS,
30
26
  BRUSH_WORD,
27
+ BUILTIN_BRUSHES,
31
28
  BrushLibraryError,
32
29
  CANVAS_PROFILE,
33
30
  DOT_ROTATIONS,
@@ -45,6 +42,7 @@ import {
45
42
  WET_BRUSH_TOOLS,
46
43
  WET_MEDIA,
47
44
  brushColourKind,
45
+ brushToJSON,
48
46
  checkBrush,
49
47
  createBrushLibrary,
50
48
  degrade,
@@ -52,6 +50,7 @@ import {
52
50
  listPresets,
53
51
  migrateBrushV1,
54
52
  normalizeBrushSpec,
53
+ normalizePaintBrush,
55
54
  parseBrushLibrary2 as parseBrushLibrary,
56
55
  resolveBrush,
57
56
  resolveCategory,
@@ -59,10 +58,14 @@ import {
59
58
  resolveWetMedium,
60
59
  setPath,
61
60
  snapshotBrushLibrary
62
- } from "../chunk-SMTUQWGY.js";
61
+ } from "../chunk-5IW65VQ6.js";
63
62
  import {
64
63
  STAMP_KINDS
65
64
  } from "../chunk-APX63ILU.js";
65
+ import {
66
+ brushColourHex
67
+ } from "../chunk-FJBQTZS4.js";
68
+ import "../chunk-3CI7FRMK.js";
66
69
  import "../chunk-SZTI6EER.js";
67
70
  import "../chunk-V2ZCJRDM.js";
68
71
  import {
@@ -279,15 +282,126 @@ function brushLibraryGroups(entries, filter = {}) {
279
282
  const order = ["dry", "ink", "paint", "spray", "other", "tools"];
280
283
  return [...groups.values()].sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
281
284
  }
285
+
286
+ // src/brushes/contract.ts
287
+ var BRUSH_CONTRACT_FORMAT_VERSION = 1;
288
+ var SUPPORT = { type: "string", enum: ["full", "ignored", "none"], description: "full: rendered; ignored: accepted, painted without it (a warning); none: refused (an error)." };
289
+ var BRUSH_PROFILE_JSON_SCHEMA = {
290
+ type: "object",
291
+ description: "What a host can render (`BrushHostProfile`).",
292
+ properties: {
293
+ id: { type: "string", description: "paint, image, canvas, vector\u2026" },
294
+ name: { type: "string", description: "Display name, used in messages." },
295
+ kinds: { type: "array", items: { type: "string", enum: [...BRUSH_KINDS] }, description: "Brush kinds it has." },
296
+ tools: { type: "array", items: { type: "string", enum: [...BRUSH_TOOLS] }, description: "Tools it has (kind tool)." },
297
+ tipShapes: { type: "array", items: { type: "string", enum: [...BRUSH_TIP_SHAPES] }, description: "Tip shapes it renders." },
298
+ marks: { type: "array", items: { type: "string", enum: [...BRUSH_MARKS_KINDS] }, description: "Marks kinds it renders." },
299
+ surfaces: { type: "array", items: { type: "string", enum: [...BRUSH_SURFACES] }, description: "Surfaces it renders." },
300
+ exchange: { ...SUPPORT, description: "Picking paint up and mixing it in." },
301
+ reservoir: { ...SUPPORT, description: "The Mixer Brush reservoir." },
302
+ grain: { ...SUPPORT, description: "Paper grain response." },
303
+ light: { ...SUPPORT, description: "Light paint." },
304
+ motion: { ...SUPPORT, description: "Motion after painting." },
305
+ colours: { type: "array", items: { type: "string", enum: ["solid", "dual", "gradient", "effect"] }, description: "Colour kinds a default colour may be." },
306
+ velocity: { ...SUPPORT, description: "Velocity dynamics." },
307
+ tilt: { ...SUPPORT, description: "Tilt dynamics." }
308
+ },
309
+ required: ["id", "name", "kinds", "tools", "tipShapes", "marks", "surfaces", "exchange", "reservoir", "grain", "light", "motion", "colours", "velocity", "tilt"],
310
+ additionalProperties: false
311
+ };
312
+ var BRUSH_LIBRARY_FILE_JSON_SCHEMA = {
313
+ type: "object",
314
+ description: "A brush library file, as export_brushes writes it. Readers also accept version 1 files (flat engine brushes, migrated) and a bare array of brushes.",
315
+ properties: {
316
+ format: { type: "string", const: "pixerate-brushes", description: 'Always "pixerate-brushes".' },
317
+ version: { type: "integer", const: 2, description: "Always 2." },
318
+ brushes: { type: "array", items: { $ref: "brush.schema.json" }, description: "The brushes, each a facet brush (brush.schema.json)." }
319
+ },
320
+ required: ["format", "version", "brushes"],
321
+ additionalProperties: false
322
+ };
323
+ function migrationInputs() {
324
+ const pick = (id) => brushToJSON(BUILTIN_BRUSHES.find((b) => b.id === id));
325
+ return [
326
+ { id: "plain", name: "Plain" },
327
+ pick("pencil-6b"),
328
+ { ...pick("soft-pastel"), id: "my-pastel", name: "My pastel", category: "custom", grain: 0.2, tip: { ...pick("soft-pastel").tip, size: 50 } },
329
+ pick("mixer-brush"),
330
+ pick("watercolor-wash"),
331
+ pick("impasto-brush"),
332
+ pick("hair-strands"),
333
+ pick("bristle-round"),
334
+ pick("sticker-stars"),
335
+ pick("rainbow-marker"),
336
+ pick("blender"),
337
+ pick("add-water"),
338
+ pick("spray-can"),
339
+ // Fields an engine never reads are dropped on migration.
340
+ { ...pick("ink-pen"), id: "ink-with-mixer", name: "Ink with mixer settings", mixer: { wet: 0.9, load: 0.1, mix: 0.9, loadEachStroke: false, cleanEachStroke: false, mixing: "pigment" } }
341
+ ];
342
+ }
343
+ function editorSectionFixtures() {
344
+ const ids = ["highlighter", "chalk", "watercolor-wash", "hair-strands", "mixer-brush", "blender", "spray-can"];
345
+ return ids.flatMap(
346
+ (id) => Object.keys(BRUSH_HOST_PROFILES).map((host) => {
347
+ const brush = BRUSH_PRESETS.find((b) => b.id === id);
348
+ return { brush: id, host, sections: brushEditorSections(brush, BRUSH_HOST_PROFILES[host]) };
349
+ })
350
+ );
351
+ }
352
+ function toolDefinitions() {
353
+ const library = createBrushLibrary();
354
+ const toolsFor = (host) => createBrushLibraryTools(library, { profile: BRUSH_HOST_PROFILES[host], renderPreview: () => ({ data: "", mimeType: "image/png" }) });
355
+ return {
356
+ tools: toolsFor("paint").map((t) => ({ name: t.name, title: t.title, description: t.description, inputSchema: t.inputSchema, annotations: t.annotations })),
357
+ hosts: Object.fromEntries(Object.keys(BRUSH_HOST_PROFILES).map((host) => [host, toolsFor(host).map((t) => t.name)]))
358
+ };
359
+ }
360
+ function brushContracts() {
361
+ const presets = BRUSH_PRESETS.map((b) => JSON.parse(JSON.stringify(b)));
362
+ const resolved = BRUSH_PRESETS.map((b) => ({ id: b.id, engine: resolveEngine(b), category: resolveCategory(b), brush: brushToJSON(resolveBrush(b)) }));
363
+ const profiles = Object.values(BRUSH_HOST_PROFILES);
364
+ const checks = BRUSH_PRESETS.map((b) => ({
365
+ id: b.id,
366
+ hosts: Object.fromEntries(profiles.map((p) => {
367
+ const c = checkBrush(b, p);
368
+ return [p.id, { ok: c.ok, issues: c.issues.map(({ facet, severity }) => ({ facet, severity })) }];
369
+ }))
370
+ }));
371
+ const migrations = migrationInputs().map((input) => ({ input, output: migrateBrushV1(input), resolved: brushToJSON(resolveBrush(migrateBrushV1(input))), engine: normalizePaintBrush(input).engine }));
372
+ const files = {
373
+ "brush.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "brush.schema.json", ...BRUSH_JSON_SCHEMA },
374
+ "profile.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "profile.schema.json", ...BRUSH_PROFILE_JSON_SCHEMA },
375
+ "library-file.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "library-file.schema.json", ...BRUSH_LIBRARY_FILE_JSON_SCHEMA },
376
+ "presets.json": presets,
377
+ "resolved.json": resolved,
378
+ "profiles.json": profiles,
379
+ "checks.json": checks,
380
+ "migrations.json": migrations,
381
+ "tools.json": toolDefinitions(),
382
+ "editor-sections.json": editorSectionFixtures()
383
+ };
384
+ files["manifest.json"] = {
385
+ domain: "brushes",
386
+ formatVersion: BRUSH_CONTRACT_FORMAT_VERSION,
387
+ limits: { maxPalette: MAX_BRUSH_PALETTE },
388
+ counts: { presets: presets.length, profiles: profiles.length, migrations: migrations.length, editorSections: files["editor-sections.json"].length },
389
+ files: Object.keys(files).sort()
390
+ };
391
+ return files;
392
+ }
282
393
  export {
394
+ BRUSH_CONTRACT_FORMAT_VERSION,
283
395
  BRUSH_FACET_SCHEMAS,
284
396
  BRUSH_HOST_PROFILES,
285
397
  BRUSH_JSON_SCHEMA,
286
398
  BRUSH_KINDS,
399
+ BRUSH_LIBRARY_FILE_JSON_SCHEMA,
287
400
  BRUSH_MARKS_KINDS,
288
401
  BRUSH_MEDIA,
289
402
  BRUSH_PATCH_PROPERTIES,
290
403
  BRUSH_PRESETS,
404
+ BRUSH_PROFILE_JSON_SCHEMA,
291
405
  BRUSH_SURFACES,
292
406
  BRUSH_TIP_SHAPES,
293
407
  BRUSH_TOOLS,
@@ -303,6 +417,7 @@ export {
303
417
  WET_BRUSH_TOOLS,
304
418
  brushColourHex,
305
419
  brushColourKind,
420
+ brushContracts,
306
421
  brushEditorSections,
307
422
  brushLibraryGroups,
308
423
  changedBrushPaths,
@@ -1,9 +1,6 @@
1
1
  import {
2
2
  pathDataToPolyline
3
- } from "./chunk-AORZ5M5C.js";
4
- import {
5
- parseCssColor
6
- } from "./chunk-3CI7FRMK.js";
3
+ } from "./chunk-EHALWBBD.js";
7
4
  import {
8
5
  BRUSH_PRESETS,
9
6
  BUILTIN_BRUSHES,
@@ -14,13 +11,16 @@ import {
14
11
  normalizeBrushSpec,
15
12
  resolveBrush,
16
13
  resolveCategory
17
- } from "./chunk-SMTUQWGY.js";
14
+ } from "./chunk-5IW65VQ6.js";
18
15
  import "./chunk-APX63ILU.js";
19
16
  import {
20
17
  formatNumber,
21
18
  getShapeSvg,
22
19
  resolveCanvasGradient
23
20
  } from "./chunk-PWBZH3FK.js";
21
+ import {
22
+ parseCssColor
23
+ } from "./chunk-3CI7FRMK.js";
24
24
  import "./chunk-SZTI6EER.js";
25
25
  import "./chunk-V2ZCJRDM.js";
26
26
  import {
@@ -37,6 +37,12 @@ import "./chunk-OTTT5N3K.js";
37
37
  import "./chunk-IRSTHLQQ.js";
38
38
  import "./chunk-HVVR5PZW.js";
39
39
  import "./chunk-QGHEDC7B.js";
40
+ import {
41
+ isDualColor
42
+ } from "./chunk-VR6OGHU5.js";
43
+ import "./chunk-JC2SHA4A.js";
44
+ import "./chunk-MHLU6GRJ.js";
45
+ import "./chunk-6ONMQ6WF.js";
40
46
 
41
47
  // src/canvas/brushstrokeRender.ts
42
48
  var PRESETS = listPresets(CANVAS_PROFILE).map((spec) => BUILTIN_BRUSHES.find((b) => b.id === spec.id));
@@ -53,12 +59,16 @@ function readCanvasBrushSpec(spec) {
53
59
  return brush;
54
60
  }
55
61
  function listCanvasBrushes(user = []) {
56
- const out = PRESETS.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size, builtin: true }));
62
+ const colourOf = (spec) => {
63
+ const d = spec.colour?.default;
64
+ return typeof d === "string" || isDualColor(d) ? { defaultColor: d } : {};
65
+ };
66
+ const out = PRESETS.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size, builtin: true, ...colourOf(BRUSH_PRESETS.find((p) => p.id === b.id)) }));
57
67
  for (const raw of user) {
58
68
  try {
59
69
  const brush = readCanvasBrushSpec(raw);
60
70
  if (out.some((b) => b.id === brush.id)) continue;
61
- out.push({ id: brush.id, name: brush.name, category: resolveCategory(brush), size: resolveBrush(brush).tip.size, builtin: false, spec: brush });
71
+ out.push({ id: brush.id, name: brush.name, category: resolveCategory(brush), size: resolveBrush(brush).tip.size, builtin: false, spec: brush, ...colourOf(brush) });
62
72
  } catch {
63
73
  }
64
74
  }