@pixerate/editor 0.255.0 → 0.257.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 (129) hide show
  1. package/dist/agent/index.cjs +40 -38
  2. package/dist/agent/index.js +9 -7
  3. package/dist/animation/index.cjs +4 -2
  4. package/dist/animation/index.js +4 -2
  5. package/dist/animation-editor/index.cjs +63 -61
  6. package/dist/animation-editor/index.js +7 -5
  7. package/dist/brushes/colour.d.cts +11 -0
  8. package/dist/brushes/colour.d.ts +11 -0
  9. package/dist/brushes/index.cjs +51 -30
  10. package/dist/brushes/index.d.cts +1 -0
  11. package/dist/brushes/index.d.ts +1 -0
  12. package/dist/brushes/index.js +25 -4
  13. package/dist/brushes/resolve.d.cts +2 -0
  14. package/dist/brushes/resolve.d.ts +2 -0
  15. package/dist/brushes/schema.d.cts +1 -0
  16. package/dist/brushes/schema.d.ts +1 -0
  17. package/dist/brushes/tools.d.cts +7 -0
  18. package/dist/brushes/tools.d.ts +7 -0
  19. package/dist/brushes/types.d.cts +10 -2
  20. package/dist/brushes/types.d.ts +10 -2
  21. package/dist/brushes/ui.d.cts +14 -3
  22. package/dist/brushes/ui.d.ts +14 -3
  23. package/dist/{brushstrokeRender-HCOQUSWW.js → brushstrokeRender-JI4W6F2G.js} +2 -2
  24. package/dist/{brushstrokeRender-ZZOXI5O5.cjs → brushstrokeRender-MQ6PSDM5.cjs} +11 -11
  25. package/dist/canvas/index.cjs +20 -18
  26. package/dist/canvas/index.js +7 -5
  27. package/dist/chart/index.cjs +159 -157
  28. package/dist/chart/index.js +9 -7
  29. package/dist/{chunk-7272D3K2.js → chunk-2EUW2XZX.js} +27 -276
  30. package/dist/{chunk-L6Y7TSQ4.js → chunk-2VJ6OVJV.js} +1 -1
  31. package/dist/{chunk-6ZZKRH7R.js → chunk-47V3WXDG.js} +2 -2
  32. package/dist/{chunk-H7ET2ISK.cjs → chunk-4IMD2TLY.cjs} +52 -52
  33. package/dist/{chunk-VUKUTT2W.js → chunk-4M4E3N6X.js} +1 -1
  34. package/dist/{chunk-TBAVCLEQ.cjs → chunk-5Q5MLXAD.cjs} +23 -23
  35. package/dist/{chunk-MONXNVZK.js → chunk-6I23ZCGK.js} +5 -3
  36. package/dist/{chunk-GOBOCZZI.js → chunk-6NQPKNUZ.js} +1 -1
  37. package/dist/{chunk-WYGSR3IJ.js → chunk-AORZ5M5C.js} +1 -1
  38. package/dist/chunk-AVTPZO4Z.cjs +142 -0
  39. package/dist/{chunk-WKMUBATU.js → chunk-C2WEIEUB.js} +1 -1
  40. package/dist/{chunk-P3K2JCJA.js → chunk-D5BXSOSU.js} +3 -3
  41. package/dist/chunk-E2V5DA3W.js +36 -0
  42. package/dist/{chunk-WZOGDKCQ.js → chunk-F7CPSRLN.js} +0 -133
  43. package/dist/chunk-FJBQTZS4.js +17 -0
  44. package/dist/{chunk-SOSBOS5T.cjs → chunk-G23BTQA5.cjs} +22 -22
  45. package/dist/{chunk-EUIDTZNQ.cjs → chunk-I3Y2Q3PM.cjs} +59 -308
  46. package/dist/{chunk-YHUMGBZD.js → chunk-ICTPLVSR.js} +22 -4
  47. package/dist/{chunk-LRNUZKWV.cjs → chunk-IPUTIUGQ.cjs} +1 -1
  48. package/dist/{chunk-6GRUVGKU.js → chunk-ITURMC2M.js} +3 -3
  49. package/dist/{chunk-HYWRU5VX.js → chunk-IXQZHCOF.js} +1 -1
  50. package/dist/chunk-J5UQHWVL.js +142 -0
  51. package/dist/{chunk-YBAPT7NL.js → chunk-JC2SHA4A.js} +1 -23
  52. package/dist/chunk-LRTR4ASZ.cjs +17 -0
  53. package/dist/{chunk-HUAISCUX.cjs → chunk-M2HG5DJ2.cjs} +18 -18
  54. package/dist/{chunk-N76AMOKE.cjs → chunk-MDLEGVEN.cjs} +42 -42
  55. package/dist/chunk-MEGSQUQO.cjs +36 -0
  56. package/dist/chunk-MLLTWAXH.cjs +276 -0
  57. package/dist/{chunk-RHPSE2YR.js → chunk-MQ6MGIQM.js} +2 -2
  58. package/dist/{chunk-PGPFWC35.cjs → chunk-MQGQTKVW.cjs} +51 -33
  59. package/dist/{chunk-5YWWQ36H.cjs → chunk-O3ZVZSNJ.cjs} +9 -9
  60. package/dist/{chunk-GCTUOTCW.cjs → chunk-P3PZ63M3.cjs} +35 -35
  61. package/dist/{chunk-WRZ6C4TU.cjs → chunk-P6WYDBTH.cjs} +4 -4
  62. package/dist/{chunk-CJ6TBL2N.cjs → chunk-RHEMMGCN.cjs} +16 -16
  63. package/dist/{chunk-HIMGBZAM.cjs → chunk-RSIKDWOP.cjs} +201 -38
  64. package/dist/{chunk-SABJMH6M.cjs → chunk-RVGP2XLF.cjs} +2 -135
  65. package/dist/{chunk-JVRLGV3C.js → chunk-SMTUQWGY.js} +184 -21
  66. package/dist/{chunk-DMTEQ6SH.js → chunk-SSFMWJDK.js} +4 -4
  67. package/dist/{chunk-TFF2GTGN.cjs → chunk-SXQHWXEL.cjs} +2 -24
  68. package/dist/{chunk-IWEVV63O.cjs → chunk-UEYLANO3.cjs} +18 -16
  69. package/dist/chunk-VR6OGHU5.js +276 -0
  70. package/dist/{chunk-AF23QK5F.js → chunk-YQ4PQTBB.js} +1 -1
  71. package/dist/{chunk-B7V34GIH.cjs → chunk-YSGW7VV4.cjs} +88 -88
  72. package/dist/{chunk-3NS5C7FB.cjs → chunk-YYBW6DF2.cjs} +76 -76
  73. package/dist/{chunk-UFEDHACQ.js → chunk-Z7BX4TWO.js} +2 -2
  74. package/dist/{chunk-RUONPPMC.cjs → chunk-ZSE25IZA.cjs} +8 -8
  75. package/dist/color/index.cjs +7 -3
  76. package/dist/color/index.js +18 -14
  77. package/dist/export/index.cjs +8 -6
  78. package/dist/export/index.js +7 -5
  79. package/dist/extensions/index.cjs +11 -9
  80. package/dist/extensions/index.js +10 -8
  81. package/dist/gradient/index.cjs +33 -31
  82. package/dist/gradient/index.js +5 -3
  83. package/dist/image-editor/brush-presets.d.cts +4 -1
  84. package/dist/image-editor/brush-presets.d.ts +4 -1
  85. package/dist/image-editor/controller.d.cts +10 -5
  86. package/dist/image-editor/controller.d.ts +10 -5
  87. package/dist/image-editor/index.cjs +1090 -1073
  88. package/dist/image-editor/index.js +37 -20
  89. package/dist/index.cjs +17 -15
  90. package/dist/index.js +11 -9
  91. package/dist/paint-editor/brushes.d.cts +18 -1
  92. package/dist/paint-editor/brushes.d.ts +18 -1
  93. package/dist/paint-editor/collaboration-tools.d.cts +19 -0
  94. package/dist/paint-editor/collaboration-tools.d.ts +19 -0
  95. package/dist/paint-editor/collaboration.d.cts +180 -0
  96. package/dist/paint-editor/collaboration.d.ts +180 -0
  97. package/dist/paint-editor/controller.d.cts +83 -0
  98. package/dist/paint-editor/controller.d.ts +83 -0
  99. package/dist/paint-editor/engine.d.cts +7 -0
  100. package/dist/paint-editor/engine.d.ts +7 -0
  101. package/dist/paint-editor/index.cjs +2027 -1331
  102. package/dist/paint-editor/index.d.cts +2 -0
  103. package/dist/paint-editor/index.d.ts +2 -0
  104. package/dist/paint-editor/index.js +1671 -975
  105. package/dist/paint-editor/mark-settings.d.cts +1 -1
  106. package/dist/paint-editor/mark-settings.d.ts +1 -1
  107. package/dist/paint-editor/procedural.d.cts +4 -2
  108. package/dist/paint-editor/procedural.d.ts +4 -2
  109. package/dist/paint-editor/stamp-tip.d.cts +45 -0
  110. package/dist/paint-editor/stamp-tip.d.ts +45 -0
  111. package/dist/paint-editor/stroke.d.cts +2 -0
  112. package/dist/paint-editor/stroke.d.ts +2 -0
  113. package/dist/stickers/index.cjs +4 -2
  114. package/dist/stickers/index.js +6 -4
  115. package/dist/typography/index.cjs +3 -2
  116. package/dist/typography/index.js +2 -1
  117. package/dist/vector/index.cjs +971 -969
  118. package/dist/vector/index.js +8 -6
  119. package/dist/video-editor/index.cjs +4 -2
  120. package/dist/video-editor/index.js +4 -2
  121. package/package.json +1 -1
  122. package/skills/pixerate-brushes/SKILL.md +21 -11
  123. package/skills/pixerate-brushes-design/SKILL.md +6 -2
  124. package/skills/pixerate-brushes-design/evals/canvas-chalk.json +8 -8
  125. package/skills/pixerate-canvas/SKILL.md +3 -2
  126. package/skills/pixerate-image-editor/SKILL.md +1 -1
  127. package/skills/pixerate-paint-editor/SKILL.md +35 -47
  128. package/skills/pixerate-paint-editor/references/painting-alongside.md +19 -0
  129. package/skills/pixerate-vector/SKILL.md +1 -1
@@ -75,7 +75,13 @@ var DEFAULT_PARTICLE = {
75
75
  dotRotation: "random"
76
76
  };
77
77
  function normalizeParticle(raw = {}, base = DEFAULT_PARTICLE) {
78
- const textures = Array.isArray(raw.dotTextures) ? raw.dotTextures.filter((t) => _chunkJSDND7DFcjs.STAMP_KINDS.includes(t)).slice(0, MAX_DOT_TEXTURES) : [..._nullishCoalesce(base.dotTextures, () => ( []))];
78
+ const texture = (t) => {
79
+ if (typeof t !== "string") return null;
80
+ const bare = t.startsWith("editor:") ? t.slice(7) : t;
81
+ if (_chunkJSDND7DFcjs.STAMP_KINDS.includes(bare)) return bare;
82
+ return /^[^:\s]+:\S+$/.test(t) && t.length <= 200 ? t : null;
83
+ };
84
+ const textures = Array.isArray(raw.dotTextures) ? raw.dotTextures.map(texture).filter((t) => t !== null).slice(0, MAX_DOT_TEXTURES) : [..._nullishCoalesce(base.dotTextures, () => ( []))];
79
85
  return {
80
86
  kind: oneOf(raw.kind, PARTICLE_KINDS, base.kind),
81
87
  render: oneOf(raw.render, PARTICLE_RENDERS, base.render),
@@ -204,6 +210,20 @@ function normalizeBehavior(raw = {}, base = DEFAULT_BEHAVIOR) {
204
210
  multicolor: normalizeMultiColor(_nullishCoalesce(raw.multicolor, () => ( {})), base.multicolor)
205
211
  };
206
212
  }
213
+ var DEFAULT_SPRAY = { density: 0.5, size: 0.12 };
214
+ function readStamp(raw, base) {
215
+ if (raw === null) return null;
216
+ if (typeof raw !== "string") return base;
217
+ if (_chunkJSDND7DFcjs.STAMP_KINDS.includes(raw)) return `editor:${raw}`;
218
+ return /^[^:\s]+:\S+$/.test(raw) && raw.length <= 200 ? raw : base;
219
+ }
220
+ function readSpray(raw, base) {
221
+ if (raw === null) return null;
222
+ if (!raw || typeof raw !== "object") return base;
223
+ const r = raw;
224
+ const b = _nullishCoalesce(base, () => ( DEFAULT_SPRAY));
225
+ return { density: unit2(r.density, b.density), size: range(r.size, 0.02, 1, b.size) };
226
+ }
207
227
  function isWetCapable(brush) {
208
228
  return brush.engine === "dab" || brush.engine === "mixer" || brush.engine === "wet" || brush.engine === "impasto";
209
229
  }
@@ -305,6 +325,8 @@ function normalizePaintBrush(raw = {}, base) {
305
325
  bristle: normalizeBristle(_nullishCoalesce(raw.bristle, () => ( {})), _nullishCoalesce(b.bristle, () => ( DEFAULT_BRISTLE))),
306
326
  sticker: normalizeStickerSettings(_nullishCoalesce(raw.sticker, () => ( {})), _nullishCoalesce(b.sticker, () => ( DEFAULT_STICKER))),
307
327
  light: normalizeLight(_nullishCoalesce(raw.light, () => ( {})), _nullishCoalesce(b.light, () => ( DEFAULT_LIGHT))),
328
+ stamp: readStamp(raw.stamp, _nullishCoalesce(b.stamp, () => ( null))),
329
+ spray: readSpray(raw.spray, _nullishCoalesce(b.spray, () => ( null))),
308
330
  paint,
309
331
  ...raw.builtin ? { builtin: true } : {}
310
332
  };
@@ -330,6 +352,8 @@ var BASE_BRUSH = {
330
352
  bristle: { ...DEFAULT_BRISTLE },
331
353
  sticker: { ...DEFAULT_STICKER, stamps: [...DEFAULT_STICKER.stamps] },
332
354
  light: { ...DEFAULT_LIGHT },
355
+ stamp: null,
356
+ spray: null,
333
357
  paint: null
334
358
  };
335
359
  var builtin = (init) => ({ ...normalizePaintBrush(init), builtin: true });
@@ -959,6 +983,53 @@ var BUILTIN_BRUSHES = [
959
983
  engine: "wet",
960
984
  tip: { size: 40, hardness: 0.5, spacing: 0.12, opacity: 1, flow: 1, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
961
985
  wet: { tool: "blot", strength: 0.7 }
986
+ }),
987
+ // Pens, crayon, chalk and the spray can (their facets: `@pixerate/editor/brushes` presets).
988
+ builtin({
989
+ id: "ballpoint",
990
+ name: "Ballpoint",
991
+ category: "ink",
992
+ tip: { size: 3, hardness: 0.95, spacing: 0.05, opacity: 0.9, smoothing: 0.35, pressureOpacity: true },
993
+ grain: 0.15,
994
+ pressureSmoothing: 0.3,
995
+ dynamics: { minSize: 0.7, opacityJitter: 0.08 }
996
+ }),
997
+ builtin({
998
+ id: "gel-pen",
999
+ name: "Gel pen",
1000
+ category: "ink",
1001
+ tip: { size: 4, spacing: 0.04, smoothing: 0.3 },
1002
+ pressureSmoothing: 0.3,
1003
+ dynamics: { minSize: 0.8 }
1004
+ }),
1005
+ builtin({
1006
+ id: "fineliner",
1007
+ name: "Fineliner",
1008
+ category: "ink",
1009
+ tip: { size: 2, spacing: 0.05, smoothing: 0.4, pressureSize: false }
1010
+ }),
1011
+ builtin({
1012
+ id: "crayon",
1013
+ name: "Crayon",
1014
+ category: "pastel",
1015
+ tip: { size: 14, hardness: 0.7, spacing: 0.06, opacity: 0.9, flow: 0.85, smoothing: 0.2 },
1016
+ grain: 0.9,
1017
+ dynamics: { minSize: 0.6, tiltSize: 0.6, sizeJitter: 0.1, opacityJitter: 0.1 }
1018
+ }),
1019
+ builtin({
1020
+ id: "chalk",
1021
+ name: "Chalk",
1022
+ category: "pastel",
1023
+ tip: { size: 18, hardness: 0.6, spacing: 0.05, opacity: 0.95, flow: 0.8, smoothing: 0.2 },
1024
+ grain: 1,
1025
+ dynamics: { minSize: 0.5, tiltSize: 0.8, sizeJitter: 0.12, scatter: 0.05 }
1026
+ }),
1027
+ builtin({
1028
+ id: "spray-can",
1029
+ name: "Spray can",
1030
+ category: "airbrush",
1031
+ tip: { size: 60, hardness: 0.9, spacing: 0.08, opacity: 0.9, smoothing: 0.2, pressureSize: false, pressureOpacity: true },
1032
+ spray: { density: 0.7, size: 0.04 }
962
1033
  })
963
1034
  ];
964
1035
  var DEFAULT_BRUSH_ID = "pencil-6b";
@@ -998,12 +1069,16 @@ var EDITOR_STAMPS = {
998
1069
  toEngine: (v) => v.flatMap((ref) => {
999
1070
  try {
1000
1071
  const stamp = _chunkJSDND7DFcjs.editorStampOf.call(void 0, ref);
1001
- return stamp ? [stamp] : [];
1072
+ return stamp ? [stamp] : ref.includes(":") ? [ref] : [];
1002
1073
  } catch (e3) {
1003
1074
  return [];
1004
1075
  }
1005
1076
  }),
1006
- toFacet: (v) => v.map((stamp) => `editor:${stamp}`)
1077
+ toFacet: (v) => v.map((stamp) => stamp.includes(":") ? stamp : `editor:${stamp}`)
1078
+ };
1079
+ var ONE_STAMP = {
1080
+ toEngine: (v) => _nullishCoalesce(v[0], () => ( null)),
1081
+ toFacet: (v) => v ? [v] : void 0
1007
1082
  };
1008
1083
  var f = (facet, engine, engines = ALL, extra = {}) => ({ facet, engine, engines, ...extra });
1009
1084
  var each = (facetPrefix, enginePrefix, keys, engines) => keys.map((k) => f(`${facetPrefix}.${k}`, `${enginePrefix}.${k}`, engines));
@@ -1047,6 +1122,9 @@ var BRUSH_FIELDS = [
1047
1122
  f("tip.stamps", "particle.dotTextures", ["particle"], { shapes: ["stamp"], map: EDITOR_STAMPS }),
1048
1123
  f("motion.waveSpeed", "particle.waveSpeed", ["particle"]),
1049
1124
  f("tip.stamps", "sticker.stamps", ["sticker"], { shapes: ["stamp"] }),
1125
+ f("tip.stamps", "stamp", ["dab"], { shapes: ["stamp"], map: ONE_STAMP }),
1126
+ f("marks.density", "spray.density", ["dab"], { marks: ["scatter"] }),
1127
+ f("marks.size", "spray.size", ["dab"], { marks: ["scatter"] }),
1050
1128
  ...each("marks", "sticker", ["density", "sizeJitter", "rotationJitter", "scatter"], ["sticker"]),
1051
1129
  f("marks.colourJitter", "sticker.colorJitter", ["sticker"]),
1052
1130
  ...each("tip.bristle", "bristle", ["count", "stiffness", "splay", "thickness", "load", "jitter"], ["bristle"])
@@ -1086,6 +1164,8 @@ var VARIETY_CATEGORY = {
1086
1164
  pastel: "pastel",
1087
1165
  "soft-pastel": "pastel",
1088
1166
  "oil-pastel": "pastel",
1167
+ crayon: "pastel",
1168
+ chalk: "pastel",
1089
1169
  charcoal: "charcoal",
1090
1170
  airbrush: "airbrush",
1091
1171
  marker: "marker"
@@ -1118,17 +1198,18 @@ function resolveBrush(brush, options = {}) {
1118
1198
  const engine = resolveEngine(brush);
1119
1199
  const input = { id: brush.id, name: brush.name, category: resolveCategory(brush, engine), engine };
1120
1200
  for (const field of BRUSH_FIELDS) {
1121
- if (!field.engines.includes(engine) || field.shapes && !field.shapes.includes(brush.tip.shape)) continue;
1201
+ if (!field.engines.includes(engine) || field.shapes && !field.shapes.includes(brush.tip.shape) || field.marks && !field.marks.includes(_nullishCoalesce(_optionalChain([brush, 'access', _16 => _16.marks, 'optionalAccess', _17 => _17.kind]), () => ( "continuous")))) continue;
1122
1202
  const v = getPath(brush, field.facet);
1123
1203
  if (v !== void 0) setPath(input, field.engine, field.map ? field.map.toEngine(v) : copy(v));
1124
1204
  }
1205
+ if (engine === "dab" && _optionalChain([brush, 'access', _18 => _18.marks, 'optionalAccess', _19 => _19.kind]) === "scatter") input.spray = { ..._nullishCoalesce(input.spray, () => ( {})) };
1125
1206
  if (engine === "wet") {
1126
1207
  setPath(input, "wet.tool", brush.kind === "tool" ? WET_TOOL_OF[brush.tool] : "paint");
1127
1208
  const medium = resolveWetMedium(brush);
1128
1209
  if (medium) setPath(input, "wet.medium", medium);
1129
1210
  }
1130
1211
  if (engine === "blender" || engine === "smudge") input.loadsColor = false;
1131
- const paint = _optionalChain([brush, 'access', _16 => _16.colour, 'optionalAccess', _17 => _17.default]);
1212
+ const paint = _optionalChain([brush, 'access', _20 => _20.colour, 'optionalAccess', _21 => _21.default]);
1132
1213
  if (paint && typeof paint === "object" && "type" in paint && paint.type === "gradient") input.paint = copy(paint);
1133
1214
  return normalizePaintBrush({ ...input, ...options.builtin ? { builtin: true } : {} });
1134
1215
  }
@@ -1147,7 +1228,7 @@ var CATEGORY_MEDIUM = {
1147
1228
  };
1148
1229
  function tipShape(b) {
1149
1230
  if (b.engine === "bristle") return "bristle";
1150
- if (b.engine === "sticker" || b.engine === "particle" && b.particle.dotTextures.length > 0) return "stamp";
1231
+ if (b.engine === "sticker" || b.engine === "particle" && b.particle.dotTextures.length > 0 || b.engine === "dab" && b.stamp) return "stamp";
1151
1232
  if (b.tip.roundness >= 1) return "round";
1152
1233
  return Math.abs(Math.abs(b.tip.angle % 180) - 45) < 1e-9 || Math.abs(Math.abs(b.tip.angle % 180) - 135) < 1e-9 ? "chisel" : "flat";
1153
1234
  }
@@ -1173,7 +1254,7 @@ function skeleton(b) {
1173
1254
  return { ...brush, marks: { kind: "objects" } };
1174
1255
  default: {
1175
1256
  const [medium, variety] = _nullishCoalesce(CATEGORY_MEDIUM[b.category], () => ( []));
1176
- return { ...brush, ...medium ? { medium } : {}, ...variety ? { variety } : {} };
1257
+ return { ...brush, ...medium ? { medium } : {}, ...variety ? { variety } : {}, ...b.engine === "dab" && b.spray ? { marks: { kind: "scatter" } } : {} };
1177
1258
  }
1178
1259
  }
1179
1260
  }
@@ -1183,7 +1264,7 @@ function migrateBrushV1(raw) {
1183
1264
  const brush = skeleton(b);
1184
1265
  const defaults = resolveBrush(brush);
1185
1266
  for (const field of BRUSH_FIELDS) {
1186
- if (!field.engines.includes(b.engine) || field.shapes && !field.shapes.includes(brush.tip.shape)) continue;
1267
+ if (!field.engines.includes(b.engine) || field.shapes && !field.shapes.includes(brush.tip.shape) || field.marks && !field.marks.includes(_nullishCoalesce(_optionalChain([brush, 'access', _22 => _22.marks, 'optionalAccess', _23 => _23.kind]), () => ( "continuous")))) continue;
1187
1268
  const v = getPath(b, field.engine);
1188
1269
  if (!equal(v, getPath(defaults, field.engine))) setPath(brush, field.facet, field.map ? field.map.toFacet(v) : copy2(v));
1189
1270
  }
@@ -1196,7 +1277,7 @@ var BRUSH_KINDS = ["mark", "tool"];
1196
1277
  var BRUSH_MEDIA = ["paint", "ink", "dry", "spray"];
1197
1278
  var BRUSH_TOOLS = ["eraser", "blender", "smudge", "water", "dryer", "blow", "blot", "wet-blend", "wet-drag"];
1198
1279
  var BRUSH_TIP_SHAPES = ["round", "flat", "chisel", "bristle", "stamp"];
1199
- var BRUSH_MARKS_KINDS = ["continuous", "particles", "objects"];
1280
+ var BRUSH_MARKS_KINDS = ["continuous", "scatter", "particles", "objects"];
1200
1281
  var BRUSH_SURFACES = ["dry", "wet", "thick"];
1201
1282
 
1202
1283
  // src/brushes/schema.ts
@@ -1284,6 +1365,15 @@ var PARTICLES = object(
1284
1365
  },
1285
1366
  ["kind"]
1286
1367
  );
1368
+ var SCATTER = object(
1369
+ "A spray (a spray can): each dab lays a seeded scatter of droplets across the tip; jitter and size come from dynamics and the tip. Missing fields take their defaults.",
1370
+ {
1371
+ kind: { type: "string", const: "scatter", description: 'Always "scatter".' },
1372
+ density: unit3("Droplets per dab: 1 + density \xD7 31 (default 0.5)."),
1373
+ size: num(0.02, 1, "Droplet size as a fraction of the tip size (default 0.12).")
1374
+ },
1375
+ ["kind"]
1376
+ );
1287
1377
  var OBJECTS = object(
1288
1378
  "Each mark stays an editable object (a sticker) on a sticker layer; needs a stamp tip. Missing fields take their defaults.",
1289
1379
  {
@@ -1423,7 +1513,7 @@ var BRUSH_JSON_SCHEMA = object(
1423
1513
  "Tools: eraser removes paint; blender mixes the colours under the tip; smudge drags paint along; water lays clear water (lifts dried paint with behaviour.wet.rewet); dryer dries wet paint; blow pushes water along; blot takes wet paint up; wet-blend evens out wet pigment; wet-drag drags wet paint."
1424
1514
  ),
1425
1515
  tip: TIP,
1426
- marks: { description: "How the tip is laid along the path (default continuous).", oneOf: [CONTINUOUS, PARTICLES, OBJECTS] },
1516
+ marks: { description: "How the tip is laid along the path (default continuous): continuous dabs, a scatter of spray droplets, particles, or objects (editable stickers).", oneOf: [CONTINUOUS, SCATTER, PARTICLES, OBJECTS] },
1427
1517
  dynamics: DYNAMICS,
1428
1518
  behaviour: BEHAVIOUR,
1429
1519
  light: LIGHT,
@@ -1434,7 +1524,7 @@ var BRUSH_JSON_SCHEMA = object(
1434
1524
  );
1435
1525
  var BRUSH_FACET_SCHEMAS = {
1436
1526
  tip: TIP,
1437
- marks: { continuous: CONTINUOUS, particles: PARTICLES, objects: OBJECTS },
1527
+ marks: { continuous: CONTINUOUS, scatter: SCATTER, particles: PARTICLES, objects: OBJECTS },
1438
1528
  dynamics: DYNAMICS,
1439
1529
  behaviour: BEHAVIOUR,
1440
1530
  light: LIGHT,
@@ -1510,7 +1600,7 @@ function readObject(value, schema, path, problems) {
1510
1600
  const out = {};
1511
1601
  for (const [key, v] of Object.entries(value)) {
1512
1602
  if (v === void 0) continue;
1513
- const child = _optionalChain([schema, 'access', _18 => _18.properties, 'optionalAccess', _19 => _19[key]]);
1603
+ const child = _optionalChain([schema, 'access', _24 => _24.properties, 'optionalAccess', _25 => _25[key]]);
1514
1604
  if (!child) {
1515
1605
  problems.push(`${path}.${key} is not a brush field; dropped.`);
1516
1606
  continue;
@@ -1562,7 +1652,7 @@ function normalizeBrushSpec(raw, problems = []) {
1562
1652
  if (rawMarks !== void 0) {
1563
1653
  const k = isObject(rawMarks) ? rawMarks.kind : void 0;
1564
1654
  const schema = BRUSH_FACET_SCHEMAS.marks[k];
1565
- if (!schema) problems.push(`marks.kind ${JSON.stringify(k)} is not valid; use continuous, particles or objects. marks dropped.`);
1655
+ if (!schema) problems.push(`marks.kind ${JSON.stringify(k)} is not valid; use continuous, scatter, particles or objects. marks dropped.`);
1566
1656
  else brush.marks = readObject(rawMarks, schema, "brush.marks", problems);
1567
1657
  }
1568
1658
  if (rawColour !== void 0) {
@@ -1600,7 +1690,15 @@ function normalizeBrushSpec(raw, problems = []) {
1600
1690
  delete brush.marks;
1601
1691
  }
1602
1692
  }
1603
- if (_optionalChain([brush, 'access', _20 => _20.marks, 'optionalAccess', _21 => _21.kind]) === "objects" && brush.tip.shape !== "stamp") {
1693
+ if (_optionalChain([brush, 'access', _26 => _26.marks, 'optionalAccess', _27 => _27.kind]) === "scatter") {
1694
+ const surface = _optionalChain([brush, 'access', _28 => _28.behaviour, 'optionalAccess', _29 => _29.surface]);
1695
+ const why = brush.tip.shape === "bristle" ? "bristle tips lay their own marks" : surface === "wet" || surface === "thick" ? `${surface} paint is not sprayed` : _optionalChain([brush, 'access', _30 => _30.behaviour, 'optionalAccess', _31 => _31.exchange]) ? "a spray does not pick paint up" : null;
1696
+ if (why) {
1697
+ drop("marks", why);
1698
+ delete brush.marks;
1699
+ }
1700
+ }
1701
+ if (_optionalChain([brush, 'access', _32 => _32.marks, 'optionalAccess', _33 => _33.kind]) === "objects" && brush.tip.shape !== "stamp") {
1604
1702
  problems.push("marks objects lay stamps: tip.shape set to stamp.");
1605
1703
  brush.tip.shape = "stamp";
1606
1704
  }
@@ -1613,15 +1711,7 @@ function normalizeBrushSpec(raw, problems = []) {
1613
1711
  delete brush.tip.stamps;
1614
1712
  }
1615
1713
  if (brush.tip.stamps) {
1616
- const particles = _optionalChain([brush, 'access', _22 => _22.marks, 'optionalAccess', _23 => _23.kind]) === "particles";
1617
- const stamps = brush.tip.stamps.flatMap((s) => {
1618
- const ref = _chunkJSDND7DFcjs.normalizeStickerRef.call(void 0, s);
1619
- if (particles && !_chunkJSDND7DFcjs.editorStampOf.call(void 0, ref)) {
1620
- problems.push(`brush.tip.stamps "${ref}" dropped: particle marks lay only the editor's stamps (editor:star, editor:dot\u2026).`);
1621
- return [];
1622
- }
1623
- return [ref];
1624
- });
1714
+ const stamps = brush.tip.stamps.map((s) => _chunkJSDND7DFcjs.normalizeStickerRef.call(void 0, s));
1625
1715
  if (stamps.length) brush.tip.stamps = stamps;
1626
1716
  else {
1627
1717
  drop("tip.stamps", "none of the stamps can be laid");
@@ -1658,7 +1748,7 @@ function normalizeBrushSpec(raw, problems = []) {
1658
1748
  if (b.exchange && kind === "tool" && !wetTool) {
1659
1749
  drop("behaviour.exchange", `the ${brush.tool} does not pick paint up`);
1660
1750
  delete b.exchange;
1661
- } else if (b.exchange && (_optionalChain([brush, 'access', _24 => _24.marks, 'optionalAccess', _25 => _25.kind]) === "particles" || _optionalChain([brush, 'access', _26 => _26.marks, 'optionalAccess', _27 => _27.kind]) === "objects" || brush.tip.shape === "bristle")) {
1751
+ } else if (b.exchange && (_optionalChain([brush, 'access', _34 => _34.marks, 'optionalAccess', _35 => _35.kind]) === "particles" || _optionalChain([brush, 'access', _36 => _36.marks, 'optionalAccess', _37 => _37.kind]) === "objects" || brush.tip.shape === "bristle")) {
1662
1752
  drop("behaviour.exchange", "particle, object and bristle marks do not pick paint up");
1663
1753
  delete b.exchange;
1664
1754
  }
@@ -2123,7 +2213,8 @@ var BRUSH_PRESETS = [
2123
2213
  variety: "neon",
2124
2214
  tip: { shape: "round", size: 10, hardness: 0.9, spacing: 0.04 },
2125
2215
  dynamics: { smoothing: 0.45, pressureSize: false },
2126
- light: { amount: 1 }
2216
+ light: { amount: 1 },
2217
+ colour: { palette: ["#f0abfc", "#22d3ee", "#a3e635", "#fb7185", "#fde047"] }
2127
2218
  },
2128
2219
  {
2129
2220
  id: "light-airbrush",
@@ -2340,6 +2431,77 @@ var BRUSH_PRESETS = [
2340
2431
  tip: { shape: "round", size: 40, hardness: 0.5, spacing: 0.12 },
2341
2432
  dynamics: { smoothing: 0.2, pressureSize: false, pressureOpacity: true },
2342
2433
  behaviour: { strength: 0.7 }
2434
+ },
2435
+ // --- Pens, crayon, chalk and the spray can -------------------------------------------
2436
+ // A ballpoint: a fine, hard, even line that lightens with a light touch.
2437
+ {
2438
+ id: "ballpoint",
2439
+ name: "Ballpoint",
2440
+ kind: "mark",
2441
+ medium: "ink",
2442
+ variety: "ballpoint",
2443
+ tip: { shape: "round", size: 3, hardness: 0.95, spacing: 0.05, opacity: 0.9 },
2444
+ dynamics: { smoothing: 0.35, pressureSmoothing: 0.3, pressureOpacity: true, minSize: 0.7, opacityJitter: 0.08 },
2445
+ behaviour: { grain: 0.15 },
2446
+ colour: { default: "#1e3a8a", palette: ["#1e3a8a", "#111827", "#b91c1c", "#166534"] }
2447
+ },
2448
+ // A gel pen: an even, opaque, saturated line.
2449
+ {
2450
+ id: "gel-pen",
2451
+ name: "Gel pen",
2452
+ kind: "mark",
2453
+ medium: "ink",
2454
+ variety: "gel",
2455
+ tip: { shape: "round", size: 4, spacing: 0.04 },
2456
+ dynamics: { smoothing: 0.3, pressureSmoothing: 0.3, minSize: 0.8 },
2457
+ colour: { default: "#111827", palette: ["#111827", "#7c3aed", "#db2777", "#0891b2", "#16a34a", "#f59e0b"] }
2458
+ },
2459
+ // A fineliner: one width, whatever the pressure.
2460
+ {
2461
+ id: "fineliner",
2462
+ name: "Fineliner",
2463
+ kind: "mark",
2464
+ medium: "ink",
2465
+ variety: "fineliner",
2466
+ tip: { shape: "round", size: 2, spacing: 0.05 },
2467
+ dynamics: { smoothing: 0.4, pressureSize: false },
2468
+ colour: { default: "#111827" }
2469
+ },
2470
+ // A wax crayon: waxy, catching the paper's peaks, a little uneven.
2471
+ {
2472
+ id: "crayon",
2473
+ name: "Crayon",
2474
+ kind: "mark",
2475
+ medium: "dry",
2476
+ variety: "crayon",
2477
+ tip: { shape: "round", size: 14, hardness: 0.7, spacing: 0.06, opacity: 0.9, flow: 0.85 },
2478
+ dynamics: { smoothing: 0.2, minSize: 0.6, tiltSize: 0.6, sizeJitter: 0.1, opacityJitter: 0.1 },
2479
+ behaviour: { grain: 0.9 },
2480
+ colour: { default: "#dc2626", palette: ["#dc2626", "#f59e0b", "#facc15", "#16a34a", "#2563eb", "#7c3aed"] }
2481
+ },
2482
+ // Chalk: broad, powdery and grainy. Its default colour is light on dark paper and dark on light.
2483
+ {
2484
+ id: "chalk",
2485
+ name: "Chalk",
2486
+ kind: "mark",
2487
+ medium: "dry",
2488
+ variety: "chalk",
2489
+ tip: { shape: "round", size: 18, hardness: 0.6, spacing: 0.05, opacity: 0.95, flow: 0.8 },
2490
+ dynamics: { smoothing: 0.2, minSize: 0.5, tiltSize: 0.8, sizeJitter: 0.12, scatter: 0.05 },
2491
+ behaviour: { grain: 1 },
2492
+ colour: { default: { light: "#475569", dark: "#f8fafc" }, palette: [{ light: "#475569", dark: "#f8fafc" }, "#fca5a5", "#fde68a", "#93c5fd"] }
2493
+ },
2494
+ // A spray can: a mist of hard droplets, denser the slower it passes.
2495
+ {
2496
+ id: "spray-can",
2497
+ name: "Spray can",
2498
+ kind: "mark",
2499
+ medium: "spray",
2500
+ variety: "spraycan",
2501
+ tip: { shape: "round", size: 60, hardness: 0.9, spacing: 0.08, opacity: 0.9 },
2502
+ marks: { kind: "scatter", density: 0.7, size: 0.04 },
2503
+ dynamics: { smoothing: 0.2, pressureSize: false, pressureOpacity: true },
2504
+ colour: { palette: ["#ef4444", "#f97316", "#facc15", "#22c55e", "#3b82f6", "#a855f7", "#111827", "#f8fafc"] }
2343
2505
  }
2344
2506
  ];
2345
2507
 
@@ -2420,26 +2582,26 @@ function checkBrush(brush, profile) {
2420
2582
  add("tool", "error", `${host} has no ${brush.tool} tool.`, profile.tools.length ? `Use one of: ${list(profile.tools)}.` : `${host} has no tools; use a mark brush.`);
2421
2583
  }
2422
2584
  if (!profile.tipShapes.includes(brush.tip.shape)) add("tip.shape", "error", `${host} can't lay a ${brush.tip.shape} tip.`, `Set tip.shape to one of: ${list(profile.tipShapes)}.`);
2423
- const marks = _nullishCoalesce(_optionalChain([brush, 'access', _28 => _28.marks, 'optionalAccess', _29 => _29.kind]), () => ( "continuous"));
2585
+ const marks = _nullishCoalesce(_optionalChain([brush, 'access', _38 => _38.marks, 'optionalAccess', _39 => _39.kind]), () => ( "continuous"));
2424
2586
  if (!profile.marks.includes(marks)) add("marks", "error", `${host} can't lay ${marks} marks.`, `Use marks ${list(profile.marks)} (remove marks for continuous dabs).`);
2425
- const surface = _nullishCoalesce(_optionalChain([brush, 'access', _30 => _30.behaviour, 'optionalAccess', _31 => _31.surface]), () => ( "dry"));
2587
+ const surface = _nullishCoalesce(_optionalChain([brush, 'access', _40 => _40.behaviour, 'optionalAccess', _41 => _41.surface]), () => ( "dry"));
2426
2588
  if (brush.kind === "mark" && !profile.surfaces.includes(surface)) add("behaviour.surface", "error", `${host} has no ${surface} surface.`, `Set behaviour.surface to ${list(profile.surfaces)}.`);
2427
- const ex = _optionalChain([brush, 'access', _32 => _32.behaviour, 'optionalAccess', _33 => _33.exchange]);
2428
- feature(profile.reservoir, !!_optionalChain([ex, 'optionalAccess', _34 => _34.reservoir]), "behaviour.exchange.reservoir", "a Mixer Brush reservoir", "Remove behaviour.exchange.reservoir.");
2429
- feature(profile.exchange, !_optionalChain([ex, 'optionalAccess', _35 => _35.reservoir]) && (nonZero(_optionalChain([ex, 'optionalAccess', _36 => _36.pickup])) || !!_optionalChain([ex, 'optionalAccess', _37 => _37.dirty])), "behaviour.exchange", "picking paint up", "Remove behaviour.exchange.");
2430
- feature(profile.grain, nonZero(_optionalChain([brush, 'access', _38 => _38.behaviour, 'optionalAccess', _39 => _39.grain])), "behaviour.grain", "paper grain", "Set behaviour.grain to 0.");
2431
- feature(profile.light, nonZero(_optionalChain([brush, 'access', _40 => _40.light, 'optionalAccess', _41 => _41.amount])), "light", "light paint (glow and spill)", "Remove light (or set light.amount to 0).");
2589
+ const ex = _optionalChain([brush, 'access', _42 => _42.behaviour, 'optionalAccess', _43 => _43.exchange]);
2590
+ feature(profile.reservoir, !!_optionalChain([ex, 'optionalAccess', _44 => _44.reservoir]), "behaviour.exchange.reservoir", "a Mixer Brush reservoir", "Remove behaviour.exchange.reservoir.");
2591
+ feature(profile.exchange, !_optionalChain([ex, 'optionalAccess', _45 => _45.reservoir]) && (nonZero(_optionalChain([ex, 'optionalAccess', _46 => _46.pickup])) || !!_optionalChain([ex, 'optionalAccess', _47 => _47.dirty])), "behaviour.exchange", "picking paint up", "Remove behaviour.exchange.");
2592
+ feature(profile.grain, nonZero(_optionalChain([brush, 'access', _48 => _48.behaviour, 'optionalAccess', _49 => _49.grain])), "behaviour.grain", "paper grain", "Set behaviour.grain to 0.");
2593
+ feature(profile.light, nonZero(_optionalChain([brush, 'access', _50 => _50.light, 'optionalAccess', _51 => _51.amount])), "light", "light paint (glow and spill)", "Remove light (or set light.amount to 0).");
2432
2594
  const m = brush.motion;
2433
2595
  feature(profile.motion, !!m && (!!m.live || nonZero(m.waveSpeed) || m.fps !== void 0), "motion", "animation", "Remove motion.");
2434
2596
  const d = _nullishCoalesce(brush.dynamics, () => ( {}));
2435
2597
  feature(profile.velocity, nonZero(d.velocitySize) || nonZero(d.velocityOpacity), "dynamics.velocity", "velocity dynamics (its strokes carry no timing)", "Set dynamics.velocitySize and velocityOpacity to 0.");
2436
2598
  feature(profile.tilt, nonZero(d.tiltSize) || nonZero(d.tiltRoundness) || nonZero(d.tiltOpacity), "dynamics.tilt", "tilt dynamics", "Set dynamics.tiltSize, tiltRoundness and tiltOpacity to 0.");
2437
2599
  const colour = brush.colour;
2438
- if (_optionalChain([colour, 'optionalAccess', _42 => _42.default]) !== void 0) {
2600
+ if (_optionalChain([colour, 'optionalAccess', _52 => _52.default]) !== void 0) {
2439
2601
  const kind = brushColourKind(colour.default);
2440
2602
  if (!profile.colours.includes(kind)) add("colour.default", "error", `${host} can't paint ${kind} colours.`, `Use a ${list(profile.colours)} colour, or remove colour.default.`);
2441
2603
  }
2442
- const foreign = [...new Set((_nullishCoalesce(_optionalChain([colour, 'optionalAccess', _43 => _43.palette]), () => ( []))).map(brushColourKind).filter((k) => !profile.colours.includes(k)))];
2604
+ const foreign = [...new Set((_nullishCoalesce(_optionalChain([colour, 'optionalAccess', _53 => _53.palette]), () => ( []))).map(brushColourKind).filter((k) => !profile.colours.includes(k)))];
2443
2605
  if (foreign.length) add("colour.palette", "warning", `${host} skips the palette's ${list(foreign)} colours.`, `Nothing to do; or keep to ${list(profile.colours)} colours.`);
2444
2606
  return { ok: !issues.some((i) => i.severity === "error"), issues };
2445
2607
  }
@@ -2501,7 +2663,7 @@ function degrade(brush, profile) {
2501
2663
  }
2502
2664
  dropped.push(issue);
2503
2665
  }
2504
- if (_optionalChain([b, 'access', _44 => _44.behaviour, 'optionalAccess', _45 => _45.exchange]) && !Object.keys(b.behaviour.exchange).length) delete b.behaviour.exchange;
2666
+ if (_optionalChain([b, 'access', _54 => _54.behaviour, 'optionalAccess', _55 => _55.exchange]) && !Object.keys(b.behaviour.exchange).length) delete b.behaviour.exchange;
2505
2667
  const after = checkBrush(b, profile).issues.filter((i) => i.severity === "error");
2506
2668
  return { brush: b, dropped, issues: after };
2507
2669
  }
@@ -2531,7 +2693,7 @@ function parseBrushLibrary2(raw, problems = []) {
2531
2693
  if (file && file.version !== void 0 && file.version !== 1 && file.version !== 2) {
2532
2694
  throw new BrushLibraryError("invalid", `Brush library version ${JSON.stringify(file.version)} is not supported; this library reads versions 1 and 2.`);
2533
2695
  }
2534
- const v1File = _optionalChain([file, 'optionalAccess', _46 => _46.version]) === 1;
2696
+ const v1File = _optionalChain([file, 'optionalAccess', _56 => _56.version]) === 1;
2535
2697
  const out = [];
2536
2698
  items.forEach((item, i) => {
2537
2699
  const at = `brushes[${i}]`;
@@ -2714,7 +2876,7 @@ function snapshotBrushLibrary(library, onChange) {
2714
2876
  (list2) => {
2715
2877
  entries = list2;
2716
2878
  user = list2.filter((e) => !e.builtin).map((e) => e.brush);
2717
- _optionalChain([onChange, 'optionalCall', _47 => _47()]);
2879
+ _optionalChain([onChange, 'optionalCall', _57 => _57()]);
2718
2880
  },
2719
2881
  () => void 0
2720
2882
  );
@@ -2817,4 +2979,5 @@ function snapshotBrushLibrary(library, onChange) {
2817
2979
 
2818
2980
 
2819
2981
 
2820
- exports.MAX_STICKER_STAMPS = MAX_STICKER_STAMPS; exports.DEFAULT_STICKER = DEFAULT_STICKER; exports.normalizeStickerSettings = normalizeStickerSettings; exports.PARTICLE_KINDS = PARTICLE_KINDS; exports.PARTICLE_RENDERS = PARTICLE_RENDERS; exports.RIBBON_PAIRS = RIBBON_PAIRS; exports.DOT_TEXTURE_ORDERS = DOT_TEXTURE_ORDERS; exports.DOT_ROTATIONS = DOT_ROTATIONS; exports.MAX_DOT_TEXTURES = MAX_DOT_TEXTURES; exports.DEFAULT_PARTICLE = DEFAULT_PARTICLE; exports.normalizeParticle = normalizeParticle; exports.DEFAULT_BRISTLE = DEFAULT_BRISTLE; exports.normalizeBristle = normalizeBristle; exports.PAINT_BRUSH_ENGINES = PAINT_BRUSH_ENGINES; exports.PAINT_BRUSH_CATEGORIES = PAINT_BRUSH_CATEGORIES; exports.WET_TOOLS = WET_TOOLS; exports.WET_MEDIA = WET_MEDIA; exports.DEFAULT_WET_BRUSH = DEFAULT_WET_BRUSH; exports.normalizeWetBrush = normalizeWetBrush; exports.IMPASTO_TOOLS = IMPASTO_TOOLS; exports.DEFAULT_IMPASTO = DEFAULT_IMPASTO; exports.DEFAULT_LIGHT = DEFAULT_LIGHT; exports.normalizeLight = normalizeLight; exports.normalizeImpasto = normalizeImpasto; exports.MULTI_COLOR_MODES = MULTI_COLOR_MODES; exports.DEFAULT_MULTI_COLOR = DEFAULT_MULTI_COLOR; exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR; exports.BRUSH_ROTATIONS = BRUSH_ROTATIONS; exports.DEFAULT_DYNAMICS = DEFAULT_DYNAMICS; exports.normalizeDynamics = normalizeDynamics; exports.BRUSH_ID = BRUSH_ID; exports.normalizeMultiColor = normalizeMultiColor; exports.normalizeBehavior = normalizeBehavior; exports.isWetCapable = isWetCapable; exports.isWetBrush = isWetBrush; exports.usesPaintExchange = usesPaintExchange; exports.brushTakesPaint = brushTakesPaint; exports.paintToMultiColor = paintToMultiColor; exports.multiColorToPaint = multiColorToPaint; exports.normalizePaintBrush = normalizePaintBrush; exports.BUILTIN_BRUSHES = BUILTIN_BRUSHES; exports.DEFAULT_BRUSH_ID = DEFAULT_BRUSH_ID; exports.brushToJSON = brushToJSON; exports.parseBrushLibrary = parseBrushLibrary; exports.BRUSH_FIELDS = BRUSH_FIELDS; exports.getPath = getPath; exports.setPath = setPath; exports.resolveEngine = resolveEngine; exports.resolveWetMedium = resolveWetMedium; exports.resolveCategory = resolveCategory; exports.resolveBrush = resolveBrush; exports.migrateBrushV1 = migrateBrushV1; exports.BRUSH_KINDS = BRUSH_KINDS; exports.BRUSH_MEDIA = BRUSH_MEDIA; exports.BRUSH_TOOLS = BRUSH_TOOLS; exports.BRUSH_TIP_SHAPES = BRUSH_TIP_SHAPES; exports.BRUSH_MARKS_KINDS = BRUSH_MARKS_KINDS; exports.BRUSH_SURFACES = BRUSH_SURFACES; exports.MAX_BRUSH_PALETTE = MAX_BRUSH_PALETTE; exports.BRUSH_WORD = BRUSH_WORD; exports.BRUSH_JSON_SCHEMA = BRUSH_JSON_SCHEMA; exports.BRUSH_FACET_SCHEMAS = BRUSH_FACET_SCHEMAS; exports.WET_BRUSH_TOOLS = WET_BRUSH_TOOLS; exports.normalizeBrushSpec = normalizeBrushSpec; exports.BRUSH_PRESETS = BRUSH_PRESETS; exports.PAINT_PROFILE = PAINT_PROFILE; exports.IMAGE_PROFILE = IMAGE_PROFILE; exports.CANVAS_PROFILE = CANVAS_PROFILE; exports.VECTOR_PROFILE = VECTOR_PROFILE; exports.BRUSH_HOST_PROFILES = BRUSH_HOST_PROFILES; exports.brushColourKind = brushColourKind; exports.checkBrush = checkBrush; exports.listPresets = listPresets; exports.degrade = degrade; exports.BrushLibraryError = BrushLibraryError; exports.parseBrushLibrary2 = parseBrushLibrary2; exports.createBrushLibrary = createBrushLibrary; exports.closest = closest; exports.snapshotBrushLibrary = snapshotBrushLibrary;
2982
+
2983
+ exports.MAX_STICKER_STAMPS = MAX_STICKER_STAMPS; exports.DEFAULT_STICKER = DEFAULT_STICKER; exports.normalizeStickerSettings = normalizeStickerSettings; exports.PARTICLE_KINDS = PARTICLE_KINDS; exports.PARTICLE_RENDERS = PARTICLE_RENDERS; exports.RIBBON_PAIRS = RIBBON_PAIRS; exports.DOT_TEXTURE_ORDERS = DOT_TEXTURE_ORDERS; exports.DOT_ROTATIONS = DOT_ROTATIONS; exports.MAX_DOT_TEXTURES = MAX_DOT_TEXTURES; exports.DEFAULT_PARTICLE = DEFAULT_PARTICLE; exports.normalizeParticle = normalizeParticle; exports.DEFAULT_BRISTLE = DEFAULT_BRISTLE; exports.normalizeBristle = normalizeBristle; exports.PAINT_BRUSH_ENGINES = PAINT_BRUSH_ENGINES; exports.PAINT_BRUSH_CATEGORIES = PAINT_BRUSH_CATEGORIES; exports.WET_TOOLS = WET_TOOLS; exports.WET_MEDIA = WET_MEDIA; exports.DEFAULT_WET_BRUSH = DEFAULT_WET_BRUSH; exports.normalizeWetBrush = normalizeWetBrush; exports.IMPASTO_TOOLS = IMPASTO_TOOLS; exports.DEFAULT_IMPASTO = DEFAULT_IMPASTO; exports.DEFAULT_LIGHT = DEFAULT_LIGHT; exports.normalizeLight = normalizeLight; exports.normalizeImpasto = normalizeImpasto; exports.MULTI_COLOR_MODES = MULTI_COLOR_MODES; exports.DEFAULT_MULTI_COLOR = DEFAULT_MULTI_COLOR; exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR; exports.BRUSH_ROTATIONS = BRUSH_ROTATIONS; exports.DEFAULT_DYNAMICS = DEFAULT_DYNAMICS; exports.normalizeDynamics = normalizeDynamics; exports.BRUSH_ID = BRUSH_ID; exports.normalizeMultiColor = normalizeMultiColor; exports.normalizeBehavior = normalizeBehavior; exports.DEFAULT_SPRAY = DEFAULT_SPRAY; exports.isWetCapable = isWetCapable; exports.isWetBrush = isWetBrush; exports.usesPaintExchange = usesPaintExchange; exports.brushTakesPaint = brushTakesPaint; exports.paintToMultiColor = paintToMultiColor; exports.multiColorToPaint = multiColorToPaint; exports.normalizePaintBrush = normalizePaintBrush; exports.BUILTIN_BRUSHES = BUILTIN_BRUSHES; exports.DEFAULT_BRUSH_ID = DEFAULT_BRUSH_ID; exports.brushToJSON = brushToJSON; exports.parseBrushLibrary = parseBrushLibrary; exports.BRUSH_FIELDS = BRUSH_FIELDS; exports.getPath = getPath; exports.setPath = setPath; exports.resolveEngine = resolveEngine; exports.resolveWetMedium = resolveWetMedium; exports.resolveCategory = resolveCategory; exports.resolveBrush = resolveBrush; exports.migrateBrushV1 = migrateBrushV1; exports.BRUSH_KINDS = BRUSH_KINDS; exports.BRUSH_MEDIA = BRUSH_MEDIA; exports.BRUSH_TOOLS = BRUSH_TOOLS; exports.BRUSH_TIP_SHAPES = BRUSH_TIP_SHAPES; exports.BRUSH_MARKS_KINDS = BRUSH_MARKS_KINDS; exports.BRUSH_SURFACES = BRUSH_SURFACES; exports.MAX_BRUSH_PALETTE = MAX_BRUSH_PALETTE; exports.BRUSH_WORD = BRUSH_WORD; exports.BRUSH_JSON_SCHEMA = BRUSH_JSON_SCHEMA; exports.BRUSH_FACET_SCHEMAS = BRUSH_FACET_SCHEMAS; exports.WET_BRUSH_TOOLS = WET_BRUSH_TOOLS; exports.normalizeBrushSpec = normalizeBrushSpec; exports.BRUSH_PRESETS = BRUSH_PRESETS; exports.PAINT_PROFILE = PAINT_PROFILE; exports.IMAGE_PROFILE = IMAGE_PROFILE; exports.CANVAS_PROFILE = CANVAS_PROFILE; exports.VECTOR_PROFILE = VECTOR_PROFILE; exports.BRUSH_HOST_PROFILES = BRUSH_HOST_PROFILES; exports.brushColourKind = brushColourKind; exports.checkBrush = checkBrush; exports.listPresets = listPresets; exports.degrade = degrade; exports.BrushLibraryError = BrushLibraryError; exports.parseBrushLibrary2 = parseBrushLibrary2; exports.createBrushLibrary = createBrushLibrary; exports.closest = closest; exports.snapshotBrushLibrary = snapshotBrushLibrary;
@@ -4,10 +4,6 @@
4
4
 
5
5
 
6
6
 
7
-
8
-
9
-
10
-
11
7
  var _chunkJSDND7DFcjs = require('./chunk-JSDND7DF.cjs');
12
8
 
13
9
 
@@ -16,129 +12,6 @@ var _chunkU67EN7GGcjs = require('./chunk-U67EN7GG.cjs');
16
12
 
17
13
  var _chunkW5M3J7PVcjs = require('./chunk-W5M3J7PV.cjs');
18
14
 
19
- // src/stickers/tools.ts
20
- var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
21
- var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
22
- function stickerForAgent(provider, s) {
23
- const { id, thumbnail: _thumbnail, ...rest } = s;
24
- return { ref: _chunkJSDND7DFcjs.stickerRefOf.call(void 0, provider, id), ...rest };
25
- }
26
- async function knownRefs(provider, cap = 500) {
27
- const refs = [];
28
- let after;
29
- while (refs.length < cap) {
30
- const page = await provider.list({ limit: _chunkJSDND7DFcjs.MAX_STICKER_PAGE, ...after ? { after } : {} });
31
- refs.push(...page.items.map((s) => _chunkJSDND7DFcjs.stickerRefOf.call(void 0, provider, s.id)));
32
- if (!page.next || !page.items.length) break;
33
- after = page.next;
34
- }
35
- return refs;
36
- }
37
- async function describeStickerRef(provider, ref) {
38
- _chunkJSDND7DFcjs.parseStickerRef.call(void 0, ref);
39
- const id = _chunkJSDND7DFcjs.stickerIdFor.call(void 0, provider, ref);
40
- const found = id === null ? void 0 : await provider.describe(id);
41
- if (found) return found;
42
- const near = _chunkJSDND7DFcjs.closestStickerRefs.call(void 0, ref, await knownRefs(provider));
43
- const owner = _chunkJSDND7DFcjs.parseStickerRef.call(void 0, ref).provider;
44
- const where = id === null && !provider.namespaced ? ` The sticker library is "${provider.id}", not "${owner}".` : "";
45
- throw new (0, _chunkU67EN7GGcjs.StickerError)("unknown_sticker", `No sticker "${ref}".${where} Closest: ${near.join(", ") || "none"}; find_stickers lists them.`);
46
- }
47
- function createStickerTools(options) {
48
- const find = {
49
- name: "find_stickers",
50
- title: "Find stickers",
51
- description: "Searches the sticker library (the editor's own stamps unless the host plugged in its own): words in the name, tags, category or description, filtered by category, tags and commercial licence, a small page at a time. Returns each sticker's ref (provider:id, for the tools that spray or place stickers), name, category, tags, natural width and height, formats, whether it is tintable (recoloured to the paint colour) and its licence, plus total and next (pass as after for the next page) and the categories. Changes nothing.",
52
- inputSchema: {
53
- type: "object",
54
- properties: {
55
- query: { type: "string", maxLength: 200, description: "Words to match (default: list everything)." },
56
- category: { type: "string", description: "Only this category (the result lists them)." },
57
- tags: { type: "array", maxItems: 10, items: { type: "string", description: "A tag." }, description: "Only stickers with every one of these tags." },
58
- commercial: { type: "boolean", description: "true: only stickers whose licence allows commercial use." },
59
- limit: { type: "integer", minimum: 1, maximum: _chunkJSDND7DFcjs.MAX_STICKER_PAGE, description: "Page size (default 24)." },
60
- after: { type: "string", description: "next from the previous page." }
61
- },
62
- additionalProperties: false
63
- },
64
- annotations: { title: "Find stickers", ...READ },
65
- handler: async ({ query, category, tags, commercial, limit, after }) => {
66
- const p = options.provider();
67
- const categories = await _asyncNullishCoalesce(await _optionalChain([p, 'access', _2 => _2.categories, 'optionalCall', _3 => _3()]), async () => ( null));
68
- if (category !== void 0 && categories && !categories.some((c) => c.id === category)) {
69
- throw new (0, _chunkU67EN7GGcjs.StickerError)("invalid_argument", `No category "${category}" in "${p.name}"; categories: ${categories.map((c) => c.id).join(", ")}.`);
70
- }
71
- const o = { ...category !== void 0 ? { category } : {}, ...tags ? { tags } : {}, ...commercial ? { licence: { commercial: true } } : {}, ...limit !== void 0 ? { limit } : {}, ...after !== void 0 ? { after } : {} };
72
- const q = typeof query === "string" ? query.trim() : "";
73
- if (q && !p.capabilities.supportsSearch) throw new (0, _chunkU67EN7GGcjs.StickerError)("invalid_argument", `"${p.name}" can't search; leave query out and filter by category instead.`);
74
- const page = q ? await p.search(q, o) : await p.list(o);
75
- return { provider: p.id, items: page.items.map((s) => stickerForAgent(p, s)), total: page.total, ...page.next ? { next: page.next } : {}, ...categories ? { categories } : {} };
76
- }
77
- };
78
- const describe = {
79
- name: "describe_sticker",
80
- title: "Describe a sticker",
81
- description: "Everything about one sticker by ref (from find_stickers): name, category, tags, natural size, formats, tintable, licence (with any attribution it asks for), dominant colours, variants and its outline as SVG path data (silhouette). An unknown ref is refused with the closest refs. Changes nothing.",
82
- inputSchema: { type: "object", properties: { ref: { type: "string", description: "Sticker ref provider:id from find_stickers (e.g. editor:star)." } }, required: ["ref"], additionalProperties: false },
83
- annotations: { title: "Describe a sticker", ...READ },
84
- handler: async ({ ref }) => {
85
- const p = options.provider();
86
- const d = await describeStickerRef(p, ref);
87
- const { thumbnail: _thumbnail, id, ...rest } = d;
88
- return { ref: _chunkJSDND7DFcjs.stickerRefOf.call(void 0, p, id), ...rest };
89
- }
90
- };
91
- return options.insert ? [find, describe, createInsertStickerTool(options.provider, options.insert)] : [find, describe];
92
- }
93
- async function loadForHost(provider, ref, d, host, colour, size) {
94
- const id = _chunkJSDND7DFcjs.stickerIdFor.call(void 0, provider, ref);
95
- const format = d.formats.includes(host.format) ? host.format : _nullishCoalesce(d.formats[0], () => ( host.format));
96
- return provider.load(id, { format, ...format === "png" ? { size: Math.max(8, Math.min(_nullishCoalesce(provider.capabilities.maxSize, () => ( 1024)), Math.round(size))) } : {}, ...colour && d.tintable ? { colour } : {} });
97
- }
98
- async function insertSticker(provider, host, options) {
99
- const d = await describeStickerRef(provider, options.ref);
100
- if (options.color !== void 0 && !d.tintable) throw new (0, _chunkU67EN7GGcjs.StickerError)("invalid_argument", `"${options.ref}" is not tintable (its art has its own colours); leave color out, or pick a tintable sticker (find_stickers lists tintable).`);
101
- if (options.width !== void 0 && !(options.width > 0)) throw new (0, _chunkU67EN7GGcjs.StickerError)("invalid_argument", `width must be a positive size, got ${options.width}.`);
102
- const w = _nullishCoalesce(options.width, () => ( d.width));
103
- const h = w * d.height / d.width;
104
- const centre = host.defaultCentre();
105
- const asset = await loadForHost(provider, options.ref, d, host, options.color, Math.max(w, h));
106
- const ref = _chunkJSDND7DFcjs.stickerRefOf.call(void 0, provider, d.id);
107
- const made = await host.insert({ ref, description: d, asset, x: _nullishCoalesce(options.x, () => ( centre.x)), y: _nullishCoalesce(options.y, () => ( centre.y)), width: w, height: h, ...options.color ? { colour: options.color.toLowerCase() } : {} });
108
- return { ...made, ref, width: w, height: h, licence: d.licence };
109
- }
110
- function createInsertStickerTool(provider, host) {
111
- return {
112
- name: "insert_sticker",
113
- title: "Insert a sticker",
114
- description: `Puts one sticker from the sticker library (ref from find_stickers) into the document as a new ${host.noun}, centred at x, y and width wide (its natural aspect kept; default its natural size), tinted to color when the sticker is tintable. Its art is loaded first; an unknown ref is refused with the closest ones. One undo step. Returns the new ${host.noun}'s id (for the editor's own tools, such as effects on it), the ref and its licence (credit any attribution it asks for).`,
115
- inputSchema: {
116
- type: "object",
117
- properties: {
118
- ref: { type: "string", minLength: 1, description: "Sticker ref provider:id from find_stickers (e.g. editor:star)." },
119
- x: { type: "number", description: "Centre x in document units (default the middle of the view or document)." },
120
- y: { type: "number", description: "Centre y in document units (default the middle of the view or document)." },
121
- width: { type: "number", exclusiveMinimum: 0, maximum: 1e4, description: "Width in document units (default the natural width); the height follows the aspect." },
122
- color: { type: "string", pattern: "^#[0-9a-fA-F]{6}$", description: "Tint #rrggbb for tintable stickers (default the sticker's own colour); refused for stickers that are not tintable." }
123
- },
124
- required: ["ref"],
125
- additionalProperties: false
126
- },
127
- annotations: { title: "Insert a sticker", ...WRITE },
128
- handler: ({ ref, x, y, width, color }) => insertSticker(provider(), host, { ref, x, y, width, color })
129
- };
130
- }
131
- function createStickerToolProvider(options) {
132
- return {
133
- id: _nullishCoalesce(options.id, () => ( "stickers")),
134
- namespace: options.namespace,
135
- title: "Stickers",
136
- description: "Find and describe stickers from the sticker library, by ref (provider:id), for the editors that spray or place them.",
137
- skill: "pixerate-stickers",
138
- tools: createStickerTools(options)
139
- };
140
- }
141
-
142
15
  // src/stickers/editor.ts
143
16
  var BOX = 100;
144
17
  var GRID = 128;
@@ -382,7 +255,7 @@ var StickerBrowser = (_class = class {
382
255
  this.next = void 0;
383
256
  this.set({ loading: true, error: null, items: [], total: 0, hasMore: false, provider: p.name });
384
257
  try {
385
- const categories = await _asyncNullishCoalesce(await _optionalChain([p, 'access', _4 => _4.categories, 'optionalCall', _5 => _5()]), async () => ( []));
258
+ const categories = await _asyncNullishCoalesce(await _optionalChain([p, 'access', _2 => _2.categories, 'optionalCall', _3 => _3()]), async () => ( []));
386
259
  if (run !== this.run) return;
387
260
  this.set({ categories });
388
261
  await this.page(run, p, true);
@@ -427,10 +300,4 @@ var StickerBrowser = (_class = class {
427
300
 
428
301
 
429
302
 
430
-
431
-
432
-
433
-
434
-
435
-
436
- exports.stickerForAgent = stickerForAgent; exports.describeStickerRef = describeStickerRef; exports.createStickerTools = createStickerTools; exports.insertSticker = insertSticker; exports.createInsertStickerTool = createInsertStickerTool; exports.createStickerToolProvider = createStickerToolProvider; exports.stampOutline = stampOutline; exports.stampPixels = stampPixels; exports.editorStickers = editorStickers; exports.StickerBrowser = StickerBrowser;
303
+ exports.stampOutline = stampOutline; exports.stampPixels = stampPixels; exports.editorStickers = editorStickers; exports.StickerBrowser = StickerBrowser;