@pixerate/editor 0.254.0 → 0.255.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 (73) 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 +59 -59
  4. package/dist/animation-editor/index.js +3 -3
  5. package/dist/brushes/index.cjs +31 -29
  6. package/dist/brushes/index.d.cts +1 -1
  7. package/dist/brushes/index.d.ts +1 -1
  8. package/dist/brushes/index.js +27 -25
  9. package/dist/brushes/library.d.cts +17 -0
  10. package/dist/brushes/library.d.ts +17 -0
  11. package/dist/{brushstrokeRender-5PYSKZIJ.js → brushstrokeRender-HCOQUSWW.js} +53 -18
  12. package/dist/{brushstrokeRender-7FLLSQEC.cjs → brushstrokeRender-ZZOXI5O5.cjs} +55 -20
  13. package/dist/canvas/brushTypes.d.cts +22 -3
  14. package/dist/canvas/brushTypes.d.ts +22 -3
  15. package/dist/canvas/brushstroke.d.cts +34 -12
  16. package/dist/canvas/brushstroke.d.ts +34 -12
  17. package/dist/canvas/brushstrokeRender.d.cts +14 -8
  18. package/dist/canvas/brushstrokeRender.d.ts +14 -8
  19. package/dist/canvas/changeFeed.d.cts +6 -0
  20. package/dist/canvas/changeFeed.d.ts +6 -0
  21. package/dist/canvas/drawController.d.cts +11 -3
  22. package/dist/canvas/drawController.d.ts +11 -3
  23. package/dist/canvas/freehand.d.cts +6 -1
  24. package/dist/canvas/freehand.d.ts +6 -1
  25. package/dist/canvas/index.cjs +123 -104
  26. package/dist/canvas/index.js +25 -6
  27. package/dist/{chunk-XVIK73LS.js → chunk-5P4JEOIV.js} +4 -4
  28. package/dist/{chunk-JPEHXIB5.cjs → chunk-5YWWQ36H.cjs} +66 -21
  29. package/dist/{chunk-CHAWTXBE.cjs → chunk-6WCQ35GI.cjs} +4 -4
  30. package/dist/{chunk-GYMMNCFT.js → chunk-AF23QK5F.js} +56 -11
  31. package/dist/{chunk-JFHBXNR2.cjs → chunk-B7V34GIH.cjs} +86 -86
  32. package/dist/{chunk-QC3OXRE4.js → chunk-DMTEQ6SH.js} +1 -1
  33. package/dist/{chunk-TLOGFIIV.cjs → chunk-GCTUOTCW.cjs} +35 -35
  34. package/dist/{chunk-34KLWUH4.cjs → chunk-HIMGBZAM.cjs} +1133 -597
  35. package/dist/{chunk-7P6VF3FG.js → chunk-HYWRU5VX.js} +1 -1
  36. package/dist/{chunk-QPHNVZHO.js → chunk-JVRLGV3C.js} +1118 -582
  37. package/dist/{chunk-LFMKWGFL.cjs → chunk-LRNUZKWV.cjs} +22 -8
  38. package/dist/chunk-PGPFWC35.cjs +517 -0
  39. package/dist/{chunk-QLSGTCYT.cjs → chunk-TBAVCLEQ.cjs} +23 -23
  40. package/dist/{chunk-2ZJ4DT3M.js → chunk-VUKUTT2W.js} +1 -1
  41. package/dist/{chunk-5JNA6LLY.js → chunk-WYGSR3IJ.js} +22 -8
  42. package/dist/chunk-YHUMGBZD.js +517 -0
  43. package/dist/commands/builtins.d.cts +1 -1
  44. package/dist/commands/builtins.d.ts +1 -1
  45. package/dist/commands/index.cjs +2 -2
  46. package/dist/commands/index.js +1 -1
  47. package/dist/image-editor/brush-presets.d.cts +32 -0
  48. package/dist/image-editor/brush-presets.d.ts +32 -0
  49. package/dist/image-editor/controller.d.cts +15 -0
  50. package/dist/image-editor/controller.d.ts +15 -0
  51. package/dist/image-editor/index.cjs +848 -775
  52. package/dist/image-editor/index.d.cts +1 -0
  53. package/dist/image-editor/index.d.ts +1 -0
  54. package/dist/image-editor/index.js +81 -8
  55. package/dist/image-editor/types.d.cts +7 -0
  56. package/dist/image-editor/types.d.ts +7 -0
  57. package/dist/paint-editor/index.cjs +80 -80
  58. package/dist/paint-editor/index.js +13 -13
  59. package/dist/vector/brushstroke.d.cts +21 -5
  60. package/dist/vector/brushstroke.d.ts +21 -5
  61. package/dist/vector/controller.d.cts +14 -1
  62. package/dist/vector/controller.d.ts +14 -1
  63. package/dist/vector/index.cjs +1220 -1199
  64. package/dist/vector/index.js +34 -13
  65. package/dist/vector/types.d.cts +7 -0
  66. package/dist/vector/types.d.ts +7 -0
  67. package/package.json +1 -1
  68. package/skills/pixerate-brushes/SKILL.md +9 -3
  69. package/skills/pixerate-canvas/SKILL.md +10 -5
  70. package/skills/pixerate-image-editor/SKILL.md +2 -1
  71. package/skills/pixerate-vector/SKILL.md +1 -1
  72. package/dist/chunk-HS6QDWDF.cjs +0 -1029
  73. package/dist/chunk-MTKY6K3Q.js +0 -1029
@@ -15,7 +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 { 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';
18
- export { BrushLibraryError, createBrushLibrary, parseBrushLibrary, type BrushFilter, type BrushLibrary, type BrushLibraryEntry, type BrushLibraryErrorCode, type BrushLibraryFile, type BrushLibraryOptions, type ImportBrushesResult, type SaveBrushResult, } from './library.cjs';
18
+ 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';
19
19
  export { BRUSH_PATCH_PROPERTIES, BRUSH_UNSET_PROPERTY, changedBrushPaths, mergeBrushPatch, UNSETTABLE_BRUSH_FACETS, type BrushPatch, type UnsettableBrushFacet } from './patch.cjs';
20
20
  export { brushEditorSections, brushLibraryGroups, setBrushEditorValue, type BrushEditorControl, type BrushEditorSection, type BrushEditorSectionId, type BrushLibraryGroup } from './ui.cjs';
21
21
  export { createBrushLibraryToolProvider, createBrushLibraryTools, type BrushLibraryToolProviderOptions, type BrushPreviewImage, type BrushPreviewRenderer } from './tools.cjs';
@@ -15,7 +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 { 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';
18
- export { BrushLibraryError, createBrushLibrary, parseBrushLibrary, type BrushFilter, type BrushLibrary, type BrushLibraryEntry, type BrushLibraryErrorCode, type BrushLibraryFile, type BrushLibraryOptions, type ImportBrushesResult, type SaveBrushResult, } from './library.js';
18
+ 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';
19
19
  export { BRUSH_PATCH_PROPERTIES, BRUSH_UNSET_PROPERTY, changedBrushPaths, mergeBrushPatch, UNSETTABLE_BRUSH_FACETS, type BrushPatch, type UnsettableBrushFacet } from './patch.js';
20
20
  export { brushEditorSections, brushLibraryGroups, setBrushEditorValue, type BrushEditorControl, type BrushEditorSection, type BrushEditorSectionId, type BrushLibraryGroup } from './ui.js';
21
21
  export { createBrushLibraryToolProvider, createBrushLibraryTools, type BrushLibraryToolProviderOptions, type BrushPreviewImage, type BrushPreviewRenderer } from './tools.js';
@@ -1,3 +1,14 @@
1
+ import {
2
+ BRUSH_PATCH_PROPERTIES,
3
+ BRUSH_SLIDERS,
4
+ BRUSH_UNSET_PROPERTY,
5
+ UNSETTABLE_BRUSH_FACETS,
6
+ brushEngineControls,
7
+ changedBrushPaths,
8
+ createBrushLibraryToolProvider,
9
+ createBrushLibraryTools,
10
+ mergeBrushPatch
11
+ } from "../chunk-YHUMGBZD.js";
1
12
  import {
2
13
  BRUSH_FACET_SCHEMAS,
3
14
  BRUSH_FIELDS,
@@ -6,54 +17,44 @@ import {
6
17
  BRUSH_KINDS,
7
18
  BRUSH_MARKS_KINDS,
8
19
  BRUSH_MEDIA,
9
- BRUSH_PATCH_PROPERTIES,
10
20
  BRUSH_PRESETS,
11
- BRUSH_SLIDERS,
21
+ BRUSH_ROTATIONS,
12
22
  BRUSH_SURFACES,
13
23
  BRUSH_TIP_SHAPES,
14
24
  BRUSH_TOOLS,
15
- BRUSH_UNSET_PROPERTY,
16
25
  BRUSH_WORD,
17
26
  BrushLibraryError,
18
27
  CANVAS_PROFILE,
28
+ DOT_ROTATIONS,
29
+ DOT_TEXTURE_ORDERS,
19
30
  IMAGE_PROFILE,
31
+ IMPASTO_TOOLS,
20
32
  MAX_BRUSH_PALETTE,
33
+ MAX_DOT_TEXTURES,
34
+ MAX_STICKER_STAMPS,
21
35
  PAINT_PROFILE,
22
- UNSETTABLE_BRUSH_FACETS,
36
+ PARTICLE_KINDS,
37
+ PARTICLE_RENDERS,
38
+ RIBBON_PAIRS,
23
39
  VECTOR_PROFILE,
24
40
  WET_BRUSH_TOOLS,
41
+ WET_MEDIA,
25
42
  brushColourKind,
26
- brushEngineControls,
27
- changedBrushPaths,
28
43
  checkBrush,
29
44
  createBrushLibrary,
30
- createBrushLibraryToolProvider,
31
- createBrushLibraryTools,
32
45
  degrade,
33
46
  getPath,
34
47
  listPresets,
35
- mergeBrushPatch,
36
48
  migrateBrushV1,
37
49
  normalizeBrushSpec,
38
- parseBrushLibrary,
50
+ parseBrushLibrary2 as parseBrushLibrary,
39
51
  resolveBrush,
40
52
  resolveCategory,
41
53
  resolveEngine,
42
54
  resolveWetMedium,
43
- setPath
44
- } from "../chunk-QPHNVZHO.js";
45
- import {
46
- BRUSH_ROTATIONS,
47
- DOT_ROTATIONS,
48
- DOT_TEXTURE_ORDERS,
49
- IMPASTO_TOOLS,
50
- MAX_DOT_TEXTURES,
51
- MAX_STICKER_STAMPS,
52
- PARTICLE_KINDS,
53
- PARTICLE_RENDERS,
54
- RIBBON_PAIRS,
55
- WET_MEDIA
56
- } from "../chunk-MTKY6K3Q.js";
55
+ setPath,
56
+ snapshotBrushLibrary
57
+ } from "../chunk-JVRLGV3C.js";
57
58
  import {
58
59
  STAMP_KINDS
59
60
  } from "../chunk-APX63ILU.js";
@@ -298,5 +299,6 @@ export {
298
299
  resolveCategory,
299
300
  resolveEngine,
300
301
  resolveWetMedium,
301
- setBrushEditorValue
302
+ setBrushEditorValue,
303
+ snapshotBrushLibrary
302
304
  };
@@ -102,3 +102,20 @@ export declare function parseBrushLibrary(raw: unknown, problems?: string[]): Br
102
102
  export declare function createBrushLibrary(options?: BrushLibraryOptions): BrushLibrary;
103
103
  /** "Closest: a, b. " for an unknown id (shared prefix or substring), or "" when nothing is near. */
104
104
  export declare function closest(id: string, ids: readonly string[], max?: number): string;
105
+ /** A synchronous copy of a library's brushes, for hosts whose reads are synchronous (renderers, controllers). */
106
+ export interface BrushLibrarySnapshot {
107
+ /** The brushes as last read: built-in presets, then saved brushes (empty until the first read). */
108
+ readonly entries: readonly BrushLibraryEntry[];
109
+ /** The saved brushes as last read. */
110
+ readonly user: readonly Brush[];
111
+ /** Resolves once the library has been read (again, after `refresh`). */
112
+ readonly ready: Promise<void>;
113
+ refresh(): Promise<void>;
114
+ /** Stops following the library. */
115
+ dispose(): void;
116
+ }
117
+ /**
118
+ * Follows `library`: reads it now and after every change, keeping the result for synchronous
119
+ * reads, and calls `onChange` after each read.
120
+ */
121
+ export declare function snapshotBrushLibrary(library: Pick<BrushLibrary, 'list' | 'subscribe'>, onChange?: () => void): BrushLibrarySnapshot;
@@ -102,3 +102,20 @@ export declare function parseBrushLibrary(raw: unknown, problems?: string[]): Br
102
102
  export declare function createBrushLibrary(options?: BrushLibraryOptions): BrushLibrary;
103
103
  /** "Closest: a, b. " for an unknown id (shared prefix or substring), or "" when nothing is near. */
104
104
  export declare function closest(id: string, ids: readonly string[], max?: number): string;
105
+ /** A synchronous copy of a library's brushes, for hosts whose reads are synchronous (renderers, controllers). */
106
+ export interface BrushLibrarySnapshot {
107
+ /** The brushes as last read: built-in presets, then saved brushes (empty until the first read). */
108
+ readonly entries: readonly BrushLibraryEntry[];
109
+ /** The saved brushes as last read. */
110
+ readonly user: readonly Brush[];
111
+ /** Resolves once the library has been read (again, after `refresh`). */
112
+ readonly ready: Promise<void>;
113
+ refresh(): Promise<void>;
114
+ /** Stops following the library. */
115
+ dispose(): void;
116
+ }
117
+ /**
118
+ * Follows `library`: reads it now and after every change, keeping the result for synchronous
119
+ * reads, and calls `onChange` after each read.
120
+ */
121
+ export declare function snapshotBrushLibrary(library: Pick<BrushLibrary, 'list' | 'subscribe'>, onChange?: () => void): BrushLibrarySnapshot;
@@ -1,12 +1,20 @@
1
1
  import {
2
2
  pathDataToPolyline
3
- } from "./chunk-5JNA6LLY.js";
3
+ } from "./chunk-WYGSR3IJ.js";
4
4
  import {
5
5
  parseCssColor
6
6
  } from "./chunk-3CI7FRMK.js";
7
7
  import {
8
- BUILTIN_BRUSHES
9
- } from "./chunk-MTKY6K3Q.js";
8
+ BRUSH_PRESETS,
9
+ BUILTIN_BRUSHES,
10
+ CANVAS_PROFILE,
11
+ checkBrush,
12
+ closest,
13
+ listPresets,
14
+ normalizeBrushSpec,
15
+ resolveBrush,
16
+ resolveCategory
17
+ } from "./chunk-JVRLGV3C.js";
10
18
  import "./chunk-APX63ILU.js";
11
19
  import {
12
20
  formatNumber,
@@ -14,6 +22,7 @@ import {
14
22
  resolveCanvasGradient
15
23
  } from "./chunk-PWBZH3FK.js";
16
24
  import "./chunk-SZTI6EER.js";
25
+ import "./chunk-V2ZCJRDM.js";
17
26
  import {
18
27
  BrushstrokeCache,
19
28
  brushstrokeDataUrl,
@@ -30,23 +39,48 @@ import "./chunk-HVVR5PZW.js";
30
39
  import "./chunk-QGHEDC7B.js";
31
40
 
32
41
  // src/canvas/brushstrokeRender.ts
33
- var DRY = BUILTIN_BRUSHES.filter((b) => isBrushstrokeBrush(b) && b.light.amount === 0);
34
- function listCanvasBrushes() {
35
- return DRY.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size }));
42
+ var PRESETS = listPresets(CANVAS_PROFILE).map((spec) => BUILTIN_BRUSHES.find((b) => b.id === spec.id));
43
+ var presetIds = () => PRESETS.map((b) => b.id).join(", ");
44
+ var refusal = (brush) => checkBrush(brush, CANVAS_PROFILE).issues.filter((i) => i.severity === "error").map((i) => `${i.reason} ${i.fix}`).join(" ");
45
+ function readCanvasBrushSpec(spec) {
46
+ let brush;
47
+ try {
48
+ brush = normalizeBrushSpec(spec);
49
+ } catch (e) {
50
+ throw new Error(`Not a brush: ${e.message}`);
51
+ }
52
+ if (!checkBrush(brush, CANVAS_PROFILE).ok) throw new Error(`Brush '${brush.id}' (${brush.name}) can't brush a canvas stroke: ${refusal(brush)}`);
53
+ return brush;
54
+ }
55
+ function listCanvasBrushes(user = []) {
56
+ const out = PRESETS.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size, builtin: true }));
57
+ for (const raw of user) {
58
+ try {
59
+ const brush = readCanvasBrushSpec(raw);
60
+ 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 });
62
+ } catch {
63
+ }
64
+ }
65
+ return out;
36
66
  }
37
- function resolveCanvasBrush(brush, width) {
67
+ function resolveCanvasBrush(brush, width, spec) {
38
68
  const given = width !== void 0 && Number.isFinite(width) ? Math.max(1, width) : void 0;
69
+ const sized = (b) => given === void 0 ? b : { ...b, tip: { ...b.tip, size: given } };
39
70
  if (typeof brush === "string") {
40
- const preset = BUILTIN_BRUSHES.find((b) => b.id === brush);
41
- const ids = DRY.map((b) => b.id).join(", ");
42
- if (!preset) throw new Error(`Unknown brush '${brush}'. Use one of the dry paint presets (${ids}) or tip settings ({ hardness, spacing, flow, \u2026 }).`);
43
- if (!isBrushstrokeBrush(preset)) {
44
- throw new Error(`Brush '${brush}' (${preset.name}) is not a dry brush: brushed canvas strokes use dry media only (wet paint needs an area to flow in; mixer, blender, smudge and eraser brushes need paint under them). Use one of: ${ids}.`);
71
+ if (spec !== void 0 && spec !== null) {
72
+ const saved = resolveBrush(readCanvasBrushSpec(spec));
73
+ if (!isBrushstrokeBrush(saved)) throw new Error(`Brush '${saved.id}' (${saved.name}) can't brush a canvas stroke: it is not a dry dab brush.`);
74
+ return sized(saved);
45
75
  }
46
- return given === void 0 ? preset : { ...preset, tip: { ...preset.tip, size: given } };
76
+ const preset = PRESETS.find((b) => b.id === brush);
77
+ if (preset) return sized(preset);
78
+ const other = BRUSH_PRESETS.find((b) => b.id === brush);
79
+ if (other) throw new Error(`Brush '${brush}' (${other.name}) can't brush a canvas stroke: ${refusal(other)} Use one of: ${presetIds()}, or a saved brush the canvas can paint.`);
80
+ throw new Error(`Unknown brush '${brush}'. ${closest(brush, PRESETS.map((b) => b.id))}Use one of the dry presets (${presetIds()}), a saved brush from the brush library, or tip settings ({ hardness, spacing, flow, \u2026 }).`);
47
81
  }
48
82
  if (!brush || typeof brush !== "object" || Array.isArray(brush)) {
49
- throw new Error("A brush is a dry paint preset id (see listCanvasBrushes / canvas.listBrushes) or an object of tip settings.");
83
+ throw new Error("A brush is a preset or saved brush id (see listCanvasBrushes / canvas.listBrushes) or an object of tip settings.");
50
84
  }
51
85
  return { size: given ?? 3, ...brush };
52
86
  }
@@ -78,7 +112,7 @@ function renderShapeBrushstrokeNow(shape, scale, place = {}) {
78
112
  const seed = shape.brush.seed;
79
113
  const textured = typeof seed === "number" && Number.isFinite(seed);
80
114
  const size = shape.brush.size ?? (textured ? void 0 : shape.strokeWidth ?? 3);
81
- const brush = resolveCanvasBrush(shape.brush.brush, size);
115
+ const brush = resolveCanvasBrush(shape.brush.brush, size, shape.brush.spec);
82
116
  const points = pathDataToPolyline(shape.d);
83
117
  if (!points.length) return null;
84
118
  const { pressure, tiltX, tiltY } = shape.brush;
@@ -116,17 +150,17 @@ function brushedShapeSvgNow(shape, size, scale, place = {}) {
116
150
  const content = svg.transform && body ? `<g transform="${svg.transform}">${body}</g>` : body;
117
151
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${f(size.width)}" height="${f(size.height)}" viewBox="0 0 ${f(size.width)} ${f(size.height)}">${content}</svg>`;
118
152
  }
119
- function brushSwatchNow(brush, options) {
153
+ function brushSwatchNow(brush, options, spec) {
120
154
  const w = options.width && options.width > 8 ? options.width : 96;
121
155
  const h = options.height && options.height > 4 ? options.height : 24;
122
- const own = typeof brush === "string" ? resolveCanvasBrush(brush, void 0) : resolveCanvasBrush(brush, brush.size);
156
+ const own = typeof brush === "string" ? resolveCanvasBrush(brush, void 0, spec) : resolveCanvasBrush(brush, brush.size);
123
157
  const tip = "tip" in own && own.tip ? own.tip.size ?? 3 : own.size ?? 3;
124
158
  const size = Math.max(1.5, Math.min(tip, h * 0.55));
125
159
  const pad = size / 2 + 2;
126
160
  const pts = Array.from({ length: 9 }, (_, i) => [pad + (w - 2 * pad) * i / 8, h / 2 + Math.sin(i / 8 * Math.PI * 2) * (h / 2 - pad) * 0.6]);
127
161
  const d = pts.map(([x, y], i) => `${i ? "L" : "M"}${x.toFixed(2)} ${y.toFixed(2)}`).join(" ");
128
162
  const image = renderShapeBrushstrokeNow(
129
- { kind: "path", d, stroke: options.color ?? "#1f2937", strokeWidth: size, brush: { brush, seed: 7, size, pressure: [0.35, 1, 0.5] } },
163
+ { kind: "path", d, stroke: options.color ?? "#1f2937", strokeWidth: size, brush: { brush, ...spec ? { spec } : {}, seed: 7, size, pressure: [0.35, 1, 0.5] } },
130
164
  options.scale && options.scale > 0 ? options.scale : 2
131
165
  );
132
166
  return image?.href ?? "";
@@ -135,6 +169,7 @@ export {
135
169
  brushSwatchNow,
136
170
  brushedShapeSvgNow,
137
171
  listCanvasBrushes,
172
+ readCanvasBrushSpec,
138
173
  renderShapeBrushstrokeNow,
139
174
  resolveCanvasBrush,
140
175
  resolveCanvasPaint
@@ -1,12 +1,20 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkLFMKWGFLcjs = require('./chunk-LFMKWGFL.cjs');
3
+ var _chunkLRNUZKWVcjs = require('./chunk-LRNUZKWV.cjs');
4
4
 
5
5
 
6
6
  var _chunkA2IS7EHEcjs = require('./chunk-A2IS7EHE.cjs');
7
7
 
8
8
 
9
- var _chunkHS6QDWDFcjs = require('./chunk-HS6QDWDF.cjs');
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+ var _chunkHIMGBZAMcjs = require('./chunk-HIMGBZAM.cjs');
10
18
  require('./chunk-JSDND7DF.cjs');
11
19
 
12
20
 
@@ -14,6 +22,7 @@ require('./chunk-JSDND7DF.cjs');
14
22
 
15
23
  var _chunkNM3G4VQTcjs = require('./chunk-NM3G4VQT.cjs');
16
24
  require('./chunk-U67EN7GG.cjs');
25
+ require('./chunk-WOFXZ3XU.cjs');
17
26
 
18
27
 
19
28
 
@@ -30,23 +39,48 @@ require('./chunk-474IQG74.cjs');
30
39
  require('./chunk-XFM3OCHU.cjs');
31
40
 
32
41
  // src/canvas/brushstrokeRender.ts
33
- var DRY = _chunkHS6QDWDFcjs.BUILTIN_BRUSHES.filter((b) => _chunkPXDIAYB6cjs.isBrushstrokeBrush.call(void 0, b) && b.light.amount === 0);
34
- function listCanvasBrushes() {
35
- return DRY.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size }));
42
+ var PRESETS = _chunkHIMGBZAMcjs.listPresets.call(void 0, _chunkHIMGBZAMcjs.CANVAS_PROFILE).map((spec) => _chunkHIMGBZAMcjs.BUILTIN_BRUSHES.find((b) => b.id === spec.id));
43
+ var presetIds = () => PRESETS.map((b) => b.id).join(", ");
44
+ var refusal = (brush) => _chunkHIMGBZAMcjs.checkBrush.call(void 0, brush, _chunkHIMGBZAMcjs.CANVAS_PROFILE).issues.filter((i) => i.severity === "error").map((i) => `${i.reason} ${i.fix}`).join(" ");
45
+ function readCanvasBrushSpec(spec) {
46
+ let brush;
47
+ try {
48
+ brush = _chunkHIMGBZAMcjs.normalizeBrushSpec.call(void 0, spec);
49
+ } catch (e) {
50
+ throw new Error(`Not a brush: ${e.message}`);
51
+ }
52
+ if (!_chunkHIMGBZAMcjs.checkBrush.call(void 0, brush, _chunkHIMGBZAMcjs.CANVAS_PROFILE).ok) throw new Error(`Brush '${brush.id}' (${brush.name}) can't brush a canvas stroke: ${refusal(brush)}`);
53
+ return brush;
54
+ }
55
+ function listCanvasBrushes(user = []) {
56
+ const out = PRESETS.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size, builtin: true }));
57
+ for (const raw of user) {
58
+ try {
59
+ const brush = readCanvasBrushSpec(raw);
60
+ if (out.some((b) => b.id === brush.id)) continue;
61
+ out.push({ id: brush.id, name: brush.name, category: _chunkHIMGBZAMcjs.resolveCategory.call(void 0, brush), size: _chunkHIMGBZAMcjs.resolveBrush.call(void 0, brush).tip.size, builtin: false, spec: brush });
62
+ } catch (e2) {
63
+ }
64
+ }
65
+ return out;
36
66
  }
37
- function resolveCanvasBrush(brush, width) {
67
+ function resolveCanvasBrush(brush, width, spec) {
38
68
  const given = width !== void 0 && Number.isFinite(width) ? Math.max(1, width) : void 0;
69
+ const sized = (b) => given === void 0 ? b : { ...b, tip: { ...b.tip, size: given } };
39
70
  if (typeof brush === "string") {
40
- const preset = _chunkHS6QDWDFcjs.BUILTIN_BRUSHES.find((b) => b.id === brush);
41
- const ids = DRY.map((b) => b.id).join(", ");
42
- if (!preset) throw new Error(`Unknown brush '${brush}'. Use one of the dry paint presets (${ids}) or tip settings ({ hardness, spacing, flow, \u2026 }).`);
43
- if (!_chunkPXDIAYB6cjs.isBrushstrokeBrush.call(void 0, preset)) {
44
- throw new Error(`Brush '${brush}' (${preset.name}) is not a dry brush: brushed canvas strokes use dry media only (wet paint needs an area to flow in; mixer, blender, smudge and eraser brushes need paint under them). Use one of: ${ids}.`);
71
+ if (spec !== void 0 && spec !== null) {
72
+ const saved = _chunkHIMGBZAMcjs.resolveBrush.call(void 0, readCanvasBrushSpec(spec));
73
+ if (!_chunkPXDIAYB6cjs.isBrushstrokeBrush.call(void 0, saved)) throw new Error(`Brush '${saved.id}' (${saved.name}) can't brush a canvas stroke: it is not a dry dab brush.`);
74
+ return sized(saved);
45
75
  }
46
- return given === void 0 ? preset : { ...preset, tip: { ...preset.tip, size: given } };
76
+ const preset = PRESETS.find((b) => b.id === brush);
77
+ if (preset) return sized(preset);
78
+ const other = _chunkHIMGBZAMcjs.BRUSH_PRESETS.find((b) => b.id === brush);
79
+ if (other) throw new Error(`Brush '${brush}' (${other.name}) can't brush a canvas stroke: ${refusal(other)} Use one of: ${presetIds()}, or a saved brush the canvas can paint.`);
80
+ throw new Error(`Unknown brush '${brush}'. ${_chunkHIMGBZAMcjs.closest.call(void 0, brush, PRESETS.map((b) => b.id))}Use one of the dry presets (${presetIds()}), a saved brush from the brush library, or tip settings ({ hardness, spacing, flow, \u2026 }).`);
47
81
  }
48
82
  if (!brush || typeof brush !== "object" || Array.isArray(brush)) {
49
- throw new Error("A brush is a dry paint preset id (see listCanvasBrushes / canvas.listBrushes) or an object of tip settings.");
83
+ throw new Error("A brush is a preset or saved brush id (see listCanvasBrushes / canvas.listBrushes) or an object of tip settings.");
50
84
  }
51
85
  return { size: _nullishCoalesce(given, () => ( 3)), ...brush };
52
86
  }
@@ -61,7 +95,7 @@ function shapePaint(shape) {
61
95
  if (shape.brush.paint === void 0 || shape.brush.paint === null) return null;
62
96
  try {
63
97
  return resolveCanvasPaint(shape.brush.paint);
64
- } catch (e) {
98
+ } catch (e3) {
65
99
  return null;
66
100
  }
67
101
  }
@@ -78,8 +112,8 @@ function renderShapeBrushstrokeNow(shape, scale, place = {}) {
78
112
  const seed = shape.brush.seed;
79
113
  const textured = typeof seed === "number" && Number.isFinite(seed);
80
114
  const size = _nullishCoalesce(shape.brush.size, () => ( (textured ? void 0 : _nullishCoalesce(shape.strokeWidth, () => ( 3)))));
81
- const brush = resolveCanvasBrush(shape.brush.brush, size);
82
- const points = _chunkLFMKWGFLcjs.pathDataToPolyline.call(void 0, shape.d);
115
+ const brush = resolveCanvasBrush(shape.brush.brush, size, shape.brush.spec);
116
+ const points = _chunkLRNUZKWVcjs.pathDataToPolyline.call(void 0, shape.d);
83
117
  if (!points.length) return null;
84
118
  const { pressure, tiltX, tiltY } = shape.brush;
85
119
  const input = {
@@ -116,17 +150,17 @@ function brushedShapeSvgNow(shape, size, scale, place = {}) {
116
150
  const content = svg.transform && body ? `<g transform="${svg.transform}">${body}</g>` : body;
117
151
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${f(size.width)}" height="${f(size.height)}" viewBox="0 0 ${f(size.width)} ${f(size.height)}">${content}</svg>`;
118
152
  }
119
- function brushSwatchNow(brush, options) {
153
+ function brushSwatchNow(brush, options, spec) {
120
154
  const w = options.width && options.width > 8 ? options.width : 96;
121
155
  const h = options.height && options.height > 4 ? options.height : 24;
122
- const own = typeof brush === "string" ? resolveCanvasBrush(brush, void 0) : resolveCanvasBrush(brush, brush.size);
156
+ const own = typeof brush === "string" ? resolveCanvasBrush(brush, void 0, spec) : resolveCanvasBrush(brush, brush.size);
123
157
  const tip = "tip" in own && own.tip ? _nullishCoalesce(own.tip.size, () => ( 3)) : _nullishCoalesce(own.size, () => ( 3));
124
158
  const size = Math.max(1.5, Math.min(tip, h * 0.55));
125
159
  const pad = size / 2 + 2;
126
160
  const pts = Array.from({ length: 9 }, (_, i) => [pad + (w - 2 * pad) * i / 8, h / 2 + Math.sin(i / 8 * Math.PI * 2) * (h / 2 - pad) * 0.6]);
127
161
  const d = pts.map(([x, y], i) => `${i ? "L" : "M"}${x.toFixed(2)} ${y.toFixed(2)}`).join(" ");
128
162
  const image = renderShapeBrushstrokeNow(
129
- { kind: "path", d, stroke: _nullishCoalesce(options.color, () => ( "#1f2937")), strokeWidth: size, brush: { brush, seed: 7, size, pressure: [0.35, 1, 0.5] } },
163
+ { kind: "path", d, stroke: _nullishCoalesce(options.color, () => ( "#1f2937")), strokeWidth: size, brush: { brush, ...spec ? { spec } : {}, seed: 7, size, pressure: [0.35, 1, 0.5] } },
130
164
  options.scale && options.scale > 0 ? options.scale : 2
131
165
  );
132
166
  return _nullishCoalesce(_optionalChain([image, 'optionalAccess', _12 => _12.href]), () => ( ""));
@@ -138,4 +172,5 @@ function brushSwatchNow(brush, options) {
138
172
 
139
173
 
140
174
 
141
- exports.brushSwatchNow = brushSwatchNow; exports.brushedShapeSvgNow = brushedShapeSvgNow; exports.listCanvasBrushes = listCanvasBrushes; exports.renderShapeBrushstrokeNow = renderShapeBrushstrokeNow; exports.resolveCanvasBrush = resolveCanvasBrush; exports.resolveCanvasPaint = resolveCanvasPaint;
175
+
176
+ exports.brushSwatchNow = brushSwatchNow; exports.brushedShapeSvgNow = brushedShapeSvgNow; exports.listCanvasBrushes = listCanvasBrushes; exports.readCanvasBrushSpec = readCanvasBrushSpec; exports.renderShapeBrushstrokeNow = renderShapeBrushstrokeNow; exports.resolveCanvasBrush = resolveCanvasBrush; exports.resolveCanvasPaint = resolveCanvasPaint;
@@ -24,11 +24,24 @@ export interface CanvasBrushSettings {
24
24
  pressureOpacity?: boolean;
25
25
  }
26
26
  /**
27
- * A brush for a freehand stroke: the id of a dry paint preset (`ink-pen`, `pencil-hb`, `marker`, …;
28
- * `listCanvasBrushes` lists them) or tip settings. The stroke width sets the brush size unless
29
- * tip settings give one.
27
+ * A brush for a freehand stroke: the id of a dry paint preset (`ink-pen`, `pencil-hb`, `marker`, …)
28
+ * or of a saved brush in the host's brush library (`listCanvasBrushes` lists both), or tip
29
+ * settings. The stroke width sets the brush size unless tip settings give one.
30
30
  */
31
31
  export type CanvasBrush = string | CanvasBrushSettings;
32
+ /**
33
+ * A saved brush from a brush library, as the facet brush of `@pixerate/editor/brushes` (plain
34
+ * JSON, described loosely so these types stay import-free). A stroke drawn with a saved brush
35
+ * keeps a copy, so the document renders the same without the library.
36
+ */
37
+ export type CanvasBrushSpec = {
38
+ id: string;
39
+ name: string;
40
+ kind: string;
41
+ tip: {
42
+ shape: string;
43
+ } & Record<string, unknown>;
44
+ } & Record<string, unknown>;
32
45
  /** A gradient stop of brushed stroke paint (the paint engine's `PaintGradientStop`). */
33
46
  export interface CanvasBrushPaintStop {
34
47
  /** 0..1 along the gradient. */
@@ -88,6 +101,8 @@ export type CanvasBrushPaint = {
88
101
  */
89
102
  export interface CanvasBrushstroke {
90
103
  brush: CanvasBrush;
104
+ /** The saved brush `brush` names, embedded (strokes drawn with a library brush that is not a preset). */
105
+ spec?: CanvasBrushSpec;
91
106
  /** Gradient paint (or one colour) instead of the stroke colour. */
92
107
  paint?: CanvasBrushPaint;
93
108
  /** Pressure 0..1 along the length (default full pressure). */
@@ -126,6 +141,10 @@ export interface CanvasBrushPreset {
126
141
  id: string;
127
142
  name: string;
128
143
  category: string;
144
+ /** A built-in preset (false: a saved brush from the brush library). */
145
+ builtin?: boolean;
146
+ /** A saved brush's facets: set `brushSpec` to it with `brush` when picking it. */
147
+ spec?: CanvasBrushSpec;
129
148
  /** The preset's own tip diameter in canvas units: a new brushed stroke's default size. */
130
149
  size: number;
131
150
  }
@@ -24,11 +24,24 @@ export interface CanvasBrushSettings {
24
24
  pressureOpacity?: boolean;
25
25
  }
26
26
  /**
27
- * A brush for a freehand stroke: the id of a dry paint preset (`ink-pen`, `pencil-hb`, `marker`, …;
28
- * `listCanvasBrushes` lists them) or tip settings. The stroke width sets the brush size unless
29
- * tip settings give one.
27
+ * A brush for a freehand stroke: the id of a dry paint preset (`ink-pen`, `pencil-hb`, `marker`, …)
28
+ * or of a saved brush in the host's brush library (`listCanvasBrushes` lists both), or tip
29
+ * settings. The stroke width sets the brush size unless tip settings give one.
30
30
  */
31
31
  export type CanvasBrush = string | CanvasBrushSettings;
32
+ /**
33
+ * A saved brush from a brush library, as the facet brush of `@pixerate/editor/brushes` (plain
34
+ * JSON, described loosely so these types stay import-free). A stroke drawn with a saved brush
35
+ * keeps a copy, so the document renders the same without the library.
36
+ */
37
+ export type CanvasBrushSpec = {
38
+ id: string;
39
+ name: string;
40
+ kind: string;
41
+ tip: {
42
+ shape: string;
43
+ } & Record<string, unknown>;
44
+ } & Record<string, unknown>;
32
45
  /** A gradient stop of brushed stroke paint (the paint engine's `PaintGradientStop`). */
33
46
  export interface CanvasBrushPaintStop {
34
47
  /** 0..1 along the gradient. */
@@ -88,6 +101,8 @@ export type CanvasBrushPaint = {
88
101
  */
89
102
  export interface CanvasBrushstroke {
90
103
  brush: CanvasBrush;
104
+ /** The saved brush `brush` names, embedded (strokes drawn with a library brush that is not a preset). */
105
+ spec?: CanvasBrushSpec;
91
106
  /** Gradient paint (or one colour) instead of the stroke colour. */
92
107
  paint?: CanvasBrushPaint;
93
108
  /** Pressure 0..1 along the length (default full pressure). */
@@ -126,6 +141,10 @@ export interface CanvasBrushPreset {
126
141
  id: string;
127
142
  name: string;
128
143
  category: string;
144
+ /** A built-in preset (false: a saved brush from the brush library). */
145
+ builtin?: boolean;
146
+ /** A saved brush's facets: set `brushSpec` to it with `brush` when picking it. */
147
+ spec?: CanvasBrushSpec;
129
148
  /** The preset's own tip diameter in canvas units: a new brushed stroke's default size. */
130
149
  size: number;
131
150
  }
@@ -8,8 +8,10 @@
8
8
  import { brushstrokeScaleBucket } from '../paint/brushstroke-scale.cjs';
9
9
  import { type PathShape, type ShapeDescription, type ShapeSize } from './shapeGeometry.cjs';
10
10
  import type { Point } from './types.cjs';
11
- import type { CanvasBrush, CanvasBrushPaint, CanvasBrushPreset, CanvasBrushstroke } from './brushTypes.cjs';
12
- export type { CanvasBrush, CanvasBrushPaint, CanvasBrushPaintMapping, CanvasBrushPaintStop, CanvasBrushPaper, CanvasBrushPreset, CanvasBrushSettings, CanvasBrushstroke } from './brushTypes.cjs';
11
+ import type { CanvasBrush, CanvasBrushPaint, CanvasBrushPreset, CanvasBrushSpec, CanvasBrushstroke } from './brushTypes.cjs';
12
+ import type { BrushLibrary } from '../brushes/library.cjs';
13
+ import type { BrushPreviewRenderer } from '../brushes/tools.cjs';
14
+ export type { CanvasBrush, CanvasBrushPaint, CanvasBrushPaintMapping, CanvasBrushPaintStop, CanvasBrushPaper, CanvasBrushPreset, CanvasBrushSettings, CanvasBrushSpec, CanvasBrushstroke } from './brushTypes.cjs';
13
15
  export { brushstrokeScaleBucket };
14
16
  /** A brushed stroke's image, placed in the path's coordinates (the shape's view box). */
15
17
  export interface CanvasBrushstrokeImage {
@@ -40,9 +42,12 @@ export declare function pathDataToPolyline(d: string, step?: number): Point[];
40
42
  export declare function profileAlongPolyline(points: readonly Point[], values: readonly number[], count?: number): number[];
41
43
  /** The renderer chunk (`./brushstrokeRender.ts`). */
42
44
  export interface CanvasBrushstrokeRenderer {
43
- listCanvasBrushes(): CanvasBrushPreset[];
44
- /** Throws for an unknown or non-dry brush; the result is the paint engine's brush. */
45
- resolveCanvasBrush(brush: CanvasBrush, width: number): unknown;
45
+ /** The presets, then the saved brushes given (facet brushes) the canvas profile admits. */
46
+ listCanvasBrushes(user?: readonly unknown[]): CanvasBrushPreset[];
47
+ /** Throws for an unknown brush or one the canvas profile refuses; the result is the paint engine's brush. */
48
+ resolveCanvasBrush(brush: CanvasBrush, width: number | undefined, spec?: unknown): unknown;
49
+ /** A saved brush checked against the canvas profile (throws with the reason and fix). */
50
+ readCanvasBrushSpec(spec: unknown): unknown;
46
51
  /** Throws for paint that is not a colour or gradient paint a canvas stroke can use; the result is the paint engine's `PaintSource`. */
47
52
  resolveCanvasPaint(paint: unknown): unknown;
48
53
  renderShapeBrushstrokeNow(shape: BrushedPathShape, scale: number, place?: {
@@ -52,7 +57,7 @@ export interface CanvasBrushstrokeRenderer {
52
57
  };
53
58
  unit?: number;
54
59
  }): CanvasBrushstrokeImage | null;
55
- brushSwatchNow(brush: CanvasBrush, options: CanvasBrushSwatchOptions): string;
60
+ brushSwatchNow(brush: CanvasBrush, options: CanvasBrushSwatchOptions, spec?: CanvasBrushSpec): string;
56
61
  brushedShapeSvgNow(shape: BrushedPathShape, size: ShapeSize, scale: number, place?: {
57
62
  origin?: {
58
63
  x: number;
@@ -66,13 +71,24 @@ export interface CanvasBrushstrokeRenderer {
66
71
  * colour parser). Called for you by the functions below; call it early to warm up.
67
72
  */
68
73
  export declare function loadCanvasBrushstrokeRenderer(): Promise<CanvasBrushstrokeRenderer>;
69
- /** The dry paint presets a freehand stroke can use (wet, mixer, blender, smudge and eraser presets are left out). */
70
- export declare function listCanvasBrushes(): Promise<CanvasBrushPreset[]>;
71
74
  /**
72
- * Checks a brush: an unknown preset id, a wet (or other non-dry) preset, or settings that are not
73
- * an object throw an error saying which presets can be used.
75
+ * The brushes a freehand stroke can use: the presets `CANVAS_PROFILE` admits (dry dab brushes; wet,
76
+ * mixer, blender, smudge, eraser and light presets are left out), then the saved brushes of
77
+ * `library` it admits.
74
78
  */
75
- export declare function validateCanvasBrush(brush: CanvasBrush): Promise<void>;
79
+ export declare function listCanvasBrushes(library?: Pick<BrushLibrary, 'list'>): Promise<CanvasBrushPreset[]>;
80
+ /**
81
+ * The saved brush a stroke should embed for `brush`: the library's brush when `brush` names a saved
82
+ * one (checked against the canvas profile), else undefined (a preset or tip settings). Throws
83
+ * saying why a saved brush can't brush a stroke.
84
+ */
85
+ export declare function resolveCanvasBrushSpec(brush: CanvasBrush, library?: Pick<BrushLibrary, 'get' | 'isBuiltin'>): Promise<CanvasBrushSpec | undefined>;
86
+ /**
87
+ * Checks a brush: an unknown id, a brush the canvas profile refuses (wet, a tool, light…), or
88
+ * settings that are not an object throw an error saying why and which brushes can be used.
89
+ * `spec` is the saved brush `brush` names (`resolveCanvasBrushSpec`).
90
+ */
91
+ export declare function validateCanvasBrush(brush: CanvasBrush, spec?: CanvasBrushSpec): Promise<void>;
76
92
  /**
77
93
  * Checks a brushed stroke's `paint` (a colour or gradient paint): bad stops, an unknown mapping,
78
94
  * or a mapping that needs timed samples (`velocity`, `time`) throw an error saying what to use.
@@ -119,4 +135,10 @@ export interface CanvasBrushSwatchOptions {
119
135
  * and grain), its size capped to fit, as a PNG data URL ('' when nothing renders). Throws for an
120
136
  * unknown or non-dry brush.
121
137
  */
122
- export declare function renderCanvasBrushSwatch(brush: CanvasBrush, options?: CanvasBrushSwatchOptions): Promise<string>;
138
+ export declare function renderCanvasBrushSwatch(brush: CanvasBrush, options?: CanvasBrushSwatchOptions, spec?: CanvasBrushSpec): Promise<string>;
139
+ /**
140
+ * `preview_brush` for brushed strokes (canvas and vector): the brush library tools' renderer
141
+ * (`createBrushLibraryToolProvider(library, { profile: CANVAS_PROFILE, renderPreview: renderCanvasBrushPreview })`),
142
+ * a swatch of the brush as a canvas stroke paints it. Loads the renderer on first use.
143
+ */
144
+ export declare const renderCanvasBrushPreview: BrushPreviewRenderer;