@pixerate/editor 0.137.0 → 0.138.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 (110) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.d.cts +3 -3
  3. package/dist/agent/index.d.ts +3 -3
  4. package/dist/agent/index.js +1 -1
  5. package/dist/animation-editor/index.cjs +57 -57
  6. package/dist/animation-editor/index.d.cts +3 -3
  7. package/dist/animation-editor/index.d.ts +3 -3
  8. package/dist/animation-editor/index.js +4 -4
  9. package/dist/{arrange-CKXK_hf7.d.cts → arrange-j_mLiFMO.d.cts} +1 -1
  10. package/dist/{arrange-BUREmgD9.d.ts → arrange-q5HQvnnB.d.ts} +1 -1
  11. package/dist/{bezier-CZ2AhjID.d.cts → bezier-CUaH8hcK.d.cts} +1 -1
  12. package/dist/{bezier-Dywv3Bw8.d.ts → bezier-DPmdGwgR.d.ts} +1 -1
  13. package/dist/{brushTypes-C7N3EBK7.d.cts → brushTypes-MX5UVs_m.d.cts} +27 -2
  14. package/dist/{brushTypes-C7N3EBK7.d.ts → brushTypes-MX5UVs_m.d.ts} +27 -2
  15. package/dist/{brushes-DJmUbIgz.d.ts → brushes-B1ABdDAs.d.ts} +2 -108
  16. package/dist/{brushes-CnvZSYYU.d.cts → brushes-DnIdJYxN.d.cts} +2 -108
  17. package/dist/{brushstrokeRender-IMFY3FF6.cjs → brushstrokeRender-PP6NTF6M.cjs} +52 -24
  18. package/dist/brushstrokeRender-PP6NTF6M.cjs.map +1 -0
  19. package/dist/{brushstrokeRender-2FAXA3XC.js → brushstrokeRender-QGEYJ4OZ.js} +40 -12
  20. package/dist/brushstrokeRender-QGEYJ4OZ.js.map +1 -0
  21. package/dist/{builtins-Cc-bH-3U.d.cts → builtins-BakDiKWf.d.cts} +3 -2
  22. package/dist/{builtins-ZU1GJnUA.d.ts → builtins-CE33vyv2.d.ts} +3 -2
  23. package/dist/canvas/index.cjs +16 -9
  24. package/dist/canvas/index.cjs.map +1 -1
  25. package/dist/canvas/index.d.cts +56 -11
  26. package/dist/canvas/index.d.ts +56 -11
  27. package/dist/canvas/index.js +10 -3
  28. package/dist/canvas/index.js.map +1 -1
  29. package/dist/{chunk-QBWEH3IM.cjs → chunk-3T5O225T.cjs} +391 -76
  30. package/dist/chunk-3T5O225T.cjs.map +1 -0
  31. package/dist/{chunk-PRSRYHQD.cjs → chunk-7MN75BXI.cjs} +36 -36
  32. package/dist/{chunk-PRSRYHQD.cjs.map → chunk-7MN75BXI.cjs.map} +1 -1
  33. package/dist/{chunk-EQQMGO42.js → chunk-C2J7GOMJ.js} +18 -5
  34. package/dist/chunk-C2J7GOMJ.js.map +1 -0
  35. package/dist/{chunk-2XS6LTUP.cjs → chunk-C46AZEBE.cjs} +104 -339
  36. package/dist/chunk-C46AZEBE.cjs.map +1 -0
  37. package/dist/{chunk-XN2NBLV5.cjs → chunk-CM3LJPR3.cjs} +82 -82
  38. package/dist/{chunk-XN2NBLV5.cjs.map → chunk-CM3LJPR3.cjs.map} +1 -1
  39. package/dist/{chunk-HUO3TI4C.js → chunk-CPSMARTX.js} +6 -241
  40. package/dist/chunk-CPSMARTX.js.map +1 -0
  41. package/dist/{chunk-KEENUAFN.cjs → chunk-DLLUOVQX.cjs} +18 -5
  42. package/dist/chunk-DLLUOVQX.cjs.map +1 -0
  43. package/dist/{chunk-LSXXAJG3.js → chunk-J7DSKTKB.js} +5 -4
  44. package/dist/chunk-J7DSKTKB.js.map +1 -0
  45. package/dist/{chunk-6S6362D2.js → chunk-OV7253FC.js} +2 -2
  46. package/dist/{chunk-5FSRTP6E.js → chunk-PQFVLOMR.js} +319 -4
  47. package/dist/chunk-PQFVLOMR.js.map +1 -0
  48. package/dist/{chunk-OIBIRXRG.js → chunk-PSPLRQL6.js} +2 -2
  49. package/dist/{chunk-SYT2TLQF.js → chunk-QBARQGSG.js} +2 -2
  50. package/dist/{chunk-5OOIUXNJ.cjs → chunk-SXOTFV3V.cjs} +6 -6
  51. package/dist/{chunk-5OOIUXNJ.cjs.map → chunk-SXOTFV3V.cjs.map} +1 -1
  52. package/dist/{chunk-DFSZO2B5.cjs → chunk-UFL5N4DK.cjs} +5 -4
  53. package/dist/chunk-UFL5N4DK.cjs.map +1 -0
  54. package/dist/{chunk-3D77LOBM.js → chunk-V45ZTW4R.js} +3 -3
  55. package/dist/{chunk-TLCTTQFF.cjs → chunk-VEHVXRJU.cjs} +17 -17
  56. package/dist/{chunk-TLCTTQFF.cjs.map → chunk-VEHVXRJU.cjs.map} +1 -1
  57. package/dist/commands/index.cjs +2 -2
  58. package/dist/commands/index.d.cts +3 -3
  59. package/dist/commands/index.d.ts +3 -3
  60. package/dist/commands/index.js +1 -1
  61. package/dist/{export-DwnZ_hvQ.d.ts → export-BwFRiItd.d.ts} +4 -4
  62. package/dist/{export-CyJdfP_Z.d.cts → export-DuN3dpCg.d.cts} +4 -4
  63. package/dist/{blending-NgF3qo3d.d.ts → grain-COgYMMHv.d.ts} +58 -1
  64. package/dist/{blending-D7jCmjO6.d.cts → grain-GCt_O2kv.d.cts} +58 -1
  65. package/dist/image-editor/index.cjs +695 -677
  66. package/dist/image-editor/index.cjs.map +1 -1
  67. package/dist/image-editor/index.d.cts +11 -11
  68. package/dist/image-editor/index.d.ts +11 -11
  69. package/dist/image-editor/index.js +23 -5
  70. package/dist/image-editor/index.js.map +1 -1
  71. package/dist/{opentype-a1KAW-qs.d.ts → opentype-T7bkbR2D.d.ts} +1 -1
  72. package/dist/{opentype-KR_jj6b_.d.cts → opentype-n99lqxEZ.d.cts} +1 -1
  73. package/dist/paint/index.cjs +21 -3
  74. package/dist/paint/index.cjs.map +1 -1
  75. package/dist/paint/index.d.cts +72 -7
  76. package/dist/paint/index.d.ts +72 -7
  77. package/dist/paint/index.js +20 -2
  78. package/dist/paint-editor/index.cjs +171 -171
  79. package/dist/paint-editor/index.cjs.map +1 -1
  80. package/dist/paint-editor/index.d.cts +75 -29
  81. package/dist/paint-editor/index.d.ts +75 -29
  82. package/dist/paint-editor/index.js +12 -12
  83. package/dist/{pathfinder-BHS4vQNm.d.cts → pathfinder-CQlldanv.d.cts} +2 -2
  84. package/dist/{pathfinder-BVvTIBMo.d.ts → pathfinder-D-V6JLSw.d.ts} +2 -2
  85. package/dist/{rand-Cw9VLrVC.d.cts → rand-D7nfeD1p.d.cts} +18 -2
  86. package/dist/{rand-8xbUb6ur.d.ts → rand-DJGxSq9d.d.ts} +18 -2
  87. package/dist/{svg-CSz3mzkw.d.ts → svg-CnDCpIy9.d.ts} +1 -1
  88. package/dist/{svg-DTb40ICU.d.cts → svg-DPJMUSNt.d.cts} +1 -1
  89. package/dist/vector/index.cjs +944 -944
  90. package/dist/vector/index.d.cts +10 -10
  91. package/dist/vector/index.d.ts +10 -10
  92. package/dist/vector/index.js +4 -4
  93. package/dist/{warp-DTOp0lON.d.cts → warp-C7rMWUgW.d.cts} +1 -1
  94. package/dist/{warp-pd7hrH4T.d.ts → warp-Doasjpyr.d.ts} +1 -1
  95. package/package.json +1 -1
  96. package/skills/pixerate-canvas/SKILL.md +12 -5
  97. package/dist/brushstrokeRender-2FAXA3XC.js.map +0 -1
  98. package/dist/brushstrokeRender-IMFY3FF6.cjs.map +0 -1
  99. package/dist/chunk-2XS6LTUP.cjs.map +0 -1
  100. package/dist/chunk-5FSRTP6E.js.map +0 -1
  101. package/dist/chunk-DFSZO2B5.cjs.map +0 -1
  102. package/dist/chunk-EQQMGO42.js.map +0 -1
  103. package/dist/chunk-HUO3TI4C.js.map +0 -1
  104. package/dist/chunk-KEENUAFN.cjs.map +0 -1
  105. package/dist/chunk-LSXXAJG3.js.map +0 -1
  106. package/dist/chunk-QBWEH3IM.cjs.map +0 -1
  107. /package/dist/{chunk-6S6362D2.js.map → chunk-OV7253FC.js.map} +0 -0
  108. /package/dist/{chunk-OIBIRXRG.js.map → chunk-PSPLRQL6.js.map} +0 -0
  109. /package/dist/{chunk-SYT2TLQF.js.map → chunk-QBARQGSG.js.map} +0 -0
  110. /package/dist/{chunk-3D77LOBM.js.map → chunk-V45ZTW4R.js.map} +0 -0
@@ -311,14 +311,39 @@ interface CanvasBrushstroke {
311
311
  /** Pointer Events pen tilt in degrees (−90..90) along the length. */
312
312
  tiltX?: number[];
313
313
  tiltY?: number[];
314
+ /**
315
+ * Character seed. A stroke with one renders the brush's character: its seeded size, angle,
316
+ * opacity and scatter jitter and the paper grain it catches (charcoal, pastel and pencil get
317
+ * their look from these). New strokes get one; older strokes without it render plain dabs at
318
+ * the stroke width, exactly as before.
319
+ */
320
+ seed?: number;
321
+ /**
322
+ * Brush diameter in path units. Missing: the preset's own size (tip settings: their `size`, else
323
+ * the stroke width) for strokes with a `seed`, the stroke width for older ones.
324
+ */
325
+ size?: number;
326
+ /** The paper the grain comes from (default the built-in sanded grain); `null` for no grain. */
327
+ paper?: CanvasBrushPaper | null;
328
+ }
329
+ /** Paper for a brushed stroke's grain (the paint engine's `BrushstrokePaper`). */
330
+ interface CanvasBrushPaper {
331
+ /** Grain pattern (default `sanded`: a fine, even tooth). */
332
+ pattern?: 'smooth' | 'cold-pressed' | 'hot-pressed' | 'rough' | 'vellum' | 'laid' | 'ingres' | 'sanded' | 'canvas' | 'linen' | 'gesso';
333
+ /** Grain seed (default 1). */
334
+ seed?: number;
335
+ /** Grain scale in canvas units, 1 the pattern's natural size (0.25..8, default 1). */
336
+ scale?: number;
337
+ /** Grain depth 0..1, times the brush's grain response (default 0.5). */
338
+ depth?: number;
314
339
  }
315
340
  /** A dry paint preset that can brush a canvas stroke. */
316
341
  interface CanvasBrushPreset {
317
342
  id: string;
318
343
  name: string;
319
344
  category: string;
320
- /** The preset's own tip diameter in pixels (the stroke width replaces it). */
345
+ /** The preset's own tip diameter in canvas units: a new brushed stroke's default size. */
321
346
  size: number;
322
347
  }
323
348
 
324
- export type { CanvasBrush as C, DirectionalDisplacementOptions as D, ExplodeNodeOptions as E, FanOutTrajectoryOptions as F, GraphDifferences as G, IncrementalLayoutOptions as I, NodeTransition as N, Point as P, Rect as R, Side as S, TrajectoryFunction as T, XYPosition as X, CanvasBrushPaint as a, CanvasBrushPreset as b, CanvasNode as c, CanvasNodeStyle as d, CanvasBrushstroke as e, CanvasEdge as f, CanvasLayoutOptions as g, PlaceBesideOptions as h, ReflowDisplacementOptions as i, CanvasClipboardPayload as j, Position as k, CanvasBrushPaintMapping as l, CanvasBrushPaintStop as m, CanvasBrushSettings as n, CanvasDockingCallbacks as o, CollapseNodeOptions as p, DisplacementDirection as q, DockStrategy as r, ExplosionSnapshot as s, TrajectoryPoint as t };
349
+ export type { CanvasBrush as C, DirectionalDisplacementOptions as D, ExplodeNodeOptions as E, FanOutTrajectoryOptions as F, GraphDifferences as G, IncrementalLayoutOptions as I, NodeTransition as N, Point as P, Rect as R, Side as S, TrajectoryFunction as T, XYPosition as X, CanvasBrushPaint as a, CanvasBrushPreset as b, CanvasNode as c, CanvasNodeStyle as d, CanvasBrushstroke as e, CanvasEdge as f, CanvasLayoutOptions as g, PlaceBesideOptions as h, ReflowDisplacementOptions as i, CanvasClipboardPayload as j, Position as k, CanvasBrushPaintMapping as l, CanvasBrushPaintStop as m, CanvasBrushPaper as n, CanvasBrushSettings as o, CanvasDockingCallbacks as p, CollapseNodeOptions as q, DisplacementDirection as r, DockStrategy as s, ExplosionSnapshot as t, TrajectoryPoint as u };
@@ -1,112 +1,6 @@
1
- import { D as Dab, C as CoverageMap, B as BrushSettings, S as StrokePoint, P as PaintTileTarget, a as PaintGradientSource, T as TileMedium, M as MixerSettings, b as MixingModel, c as BlenderSettings, d as SmudgeSettings, e as PaintSource } from './blending-NgF3qo3d.js';
1
+ import { D as Dab, C as CoverageMap, P as PaperGrain, B as BrushSettings, S as StrokePoint, a as PaintTileTarget, b as PaintGradientSource, T as TileMedium, M as MixerSettings, c as MixingModel, d as BlenderSettings, e as SmudgeSettings, f as PaintSource } from './grain-COgYMMHv.js';
2
2
  import { R as RasterRect, T as TiledSurface } from './surface-1MRncXpF.js';
3
3
 
4
- /**
5
- * Paper: a generated grain height map (0 = valley, 1 = peak) that tiles across the canvas, a paper
6
- * colour, grain depth, and the three properties wet media will read later (absorption,
7
- * permeability, roughness). Dry media catch the peaks: a light stroke only marks the high grain, a
8
- * firm one fills the valleys too. Everything is generated from a pattern and a seed, so a paper is
9
- * a few numbers in the document, never a bitmap, and no third-party texture is bundled.
10
- */
11
- /** Grain generators. Each is our own procedural pattern (no scanned textures). */
12
- type GrainPattern = 'smooth' | 'cold-pressed' | 'hot-pressed' | 'rough' | 'vellum' | 'laid' | 'ingres' | 'sanded' | 'canvas' | 'linen' | 'gesso';
13
- declare const GRAIN_PATTERNS: readonly GrainPattern[];
14
- /** Paper families, as a library groups them. */
15
- type PaperFamily = 'watercolor' | 'drawing' | 'pastel' | 'canvas' | 'panel' | 'smooth';
16
- declare const PAPER_FAMILIES: readonly PaperFamily[];
17
- /** How a height map is tuned for one kind of media. */
18
- interface GrainTuning {
19
- /** Height contrast around the middle, 0..3 (default 1). */
20
- contrast: number;
21
- /** Swap peaks and valleys (default false). */
22
- invert: boolean;
23
- }
24
- type PaperTiling = 'repeat' | 'mirror';
25
- interface PaperSettings {
26
- /** The library preset this paper started from (`custom` when it matches none). */
27
- preset: string;
28
- name: string;
29
- family: PaperFamily;
30
- pattern: GrainPattern;
31
- /** Grain seed: another seed gives another sheet of the same paper. */
32
- seed: number;
33
- /** Paper colour #rrggbb. */
34
- color: string;
35
- /** Grain scale: 1 is the pattern's natural size, 2 twice as coarse (0.25..8). */
36
- scale: number;
37
- /** How the grain tile repeats: `repeat` or `mirror` (alternate tiles flipped, so seams vanish). */
38
- tiling: PaperTiling;
39
- /** Grain depth 0..1: how much grain-responsive dry media skip the valleys (0: none). */
40
- depth: number;
41
- /** How much the grain shades the paper colour on screen and in exports, 0..1. */
42
- shading: number;
43
- /** The height map as dry media read it. */
44
- dry: GrainTuning;
45
- /** The height map as wet media will read it (stored now; wet media is a later phase). */
46
- wet: GrainTuning;
47
- /** Wet media: how fast the paper takes up water, 0..1. */
48
- absorption: number;
49
- /** Wet media: how far water spreads inside the paper, 0..1. */
50
- permeability: number;
51
- /** Wet media: how much the grain catches pigment, 0..1. */
52
- roughness: number;
53
- /** Metallic paper (gold or silver leaf, pearlescent card): the grain catches the light, 0..1 (default 0). */
54
- metallic?: number;
55
- }
56
- /** Side of the generated grain tile in pixels (at scale 1). */
57
- declare const GRAIN_TILE = 256;
58
- type PaperPreset = PaperSettings;
59
- /** The built-in paper library: generated papers in families. */
60
- declare const PAPER_LIBRARY: readonly PaperPreset[];
61
- declare const DEFAULT_PAPER: Readonly<PaperSettings>;
62
- /** A paper from the library by id, or `undefined`. */
63
- declare function paperPreset(id: string): PaperSettings | undefined;
64
- declare function clonePaper(p: PaperSettings): PaperSettings;
65
- /**
66
- * A paper with every field in range. Missing fields come from `base` (default: the library preset
67
- * named by `raw.preset`, else cold pressed).
68
- */
69
- declare function normalizePaper(raw?: Partial<PaperSettings> | Record<string, unknown>, base?: PaperSettings): PaperSettings;
70
- /** The raw height map of a pattern (`GRAIN_TILE` square, 0..1), generated once per pattern and seed. */
71
- declare function grainHeights(pattern: GrainPattern, seed: number, size?: number): Float32Array;
72
- /**
73
- * A paper's height field over the canvas (document pixels), tuned for one kind of media. Read it
74
- * with `at(x, y)`: bilinear, tiled by `paper.tiling`, at `paper.scale`.
75
- */
76
- declare class PaperGrain {
77
- readonly size = 256;
78
- private readonly heights;
79
- private readonly scale;
80
- private readonly mirror;
81
- readonly flat: boolean;
82
- constructor(paper: PaperSettings, media?: 'dry' | 'wet');
83
- /** Tile cell index for a coordinate in grain pixels (mirrored on odd tiles when mirroring). */
84
- private wrap;
85
- /** Height 0..1 at document position (x, y). */
86
- at(x: number, y: number): number;
87
- }
88
- /**
89
- * How much of a dry dab lands at grain height `h` (0..1): the media catches the peaks first, and
90
- * pressure pushes it into the valleys. `depth` is the paper's grain depth times the brush's grain
91
- * response; at 0 everything lands (1).
92
- */
93
- declare function grainCatch(h: number, depth: number, pressure: number): number;
94
- /** `#rrggbb` → [r, g, b] 0..255. */
95
- declare function hexToRgb8(hex: string): [number, number, number];
96
- /**
97
- * The paper as opaque RGBA over `rect` (document pixels): its colour, darkened in the valleys by
98
- * `shading`.
99
- */
100
- declare function renderPaper(paper: PaperSettings, rect: {
101
- x: number;
102
- y: number;
103
- width: number;
104
- height: number;
105
- }, grain?: PaperGrain, light?: {
106
- azimuth: number;
107
- elevation: number;
108
- }): Uint8ClampedArray;
109
-
110
4
  /**
111
5
  * The paint editor's dry-media stroke engine, on the shared brush engine (`@pixerate/editor/paint`):
112
6
  *
@@ -741,4 +635,4 @@ interface BrushLibraryFile {
741
635
  */
742
636
  declare function parseBrushLibrary(raw: unknown, problems?: string[]): PaintBrush[];
743
637
 
744
- export { type PaintBrushCategory as $, DEFAULT_WET_BRUSH as A, type BrushLibraryFile as B, type CoverageStroke as C, DEFAULT_BEHAVIOR as D, DryStroke as E, type DryStrokeOptions as F, GRAIN_PATTERNS as G, GRAIN_TILE as H, type ImpastoTool as I, type GrainPattern as J, type GrainTuning as K, IDENTITY_COPY as L, type MultiColorSettings as M, IMPASTO_TOOLS as N, type ImpastoSettings as O, type PaintBrush as P, type Linear2 as Q, MULTI_COLOR_MODES as R, type StrokeCopy as S, type MultiColorMode as T, PAINT_BRUSH_CATEGORIES as U, PAINT_BRUSH_ENGINES as V, type WetMediumKind as W, PAPER_FAMILIES as X, PAPER_LIBRARY as Y, PARTICLE_KINDS as Z, PARTICLE_RENDERS as _, type PenPoint as a, type PaintBrushEngine as a0, type PaperFamily as a1, type PaperTiling as a2, type ParticleSettings as a3, ParticleStroke as a4, type PenDab as a5, PenDabWalker as a6, PenSmoother as a7, type ProceduralStrokeOptions as a8, STAMP_KINDS as a9, normalizeStickerUpdate as aA, normalizeWetBrush as aB, paintToMultiColor as aC, paperPreset as aD, parseBrushLibrary as aE, penOrientation as aF, renderPaper as aG, renderStickers as aH, stampCoverage as aI, stickerRect as aJ, stickerSurface as aK, tiltToOrientation as aL, toStrokePoint as aM, usesPaintExchange as aN, type StickerSettings as aa, StickerSpray as ab, type StickerSprayInit as ac, WET_MEDIA as ad, WET_TOOLS as ae, type WetBrushSettings as af, brushTakesPaint as ag, brushToJSON as ah, clonePaper as ai, drawSticker as aj, grainCatch as ak, grainHeights as al, hexToRgb8 as am, isWetBrush as an, isWetCapable as ao, mixerTip as ap, multiColorToPaint as aq, normalizeBehavior as ar, normalizeBristle as as, normalizeDynamics as at, normalizeImpasto as au, normalizeMultiColor as av, normalizePaintBrush as aw, normalizePaper as ax, normalizeParticle as ay, normalizeStickerSettings as az, PaperGrain as b, type PaperSettings as c, type Sticker as d, type PaintBrushInput as e, type ParticleKind as f, type ParticleRender as g, type StampKind as h, type WetTool as i, BRUSH_ID as j, BRUSH_ROTATIONS as k, BUILTIN_BRUSHES as l, type BristleSettings as m, BristleStroke as n, type BrushBehavior as o, type BrushBehaviorInput as p, type BrushDynamics as q, type BrushRotation as r, DEFAULT_BRISTLE as s, DEFAULT_BRUSH_ID as t, DEFAULT_DYNAMICS as u, DEFAULT_IMPASTO as v, DEFAULT_MULTI_COLOR as w, DEFAULT_PAPER as x, DEFAULT_PARTICLE as y, DEFAULT_STICKER as z };
638
+ export { type StickerSettings as $, IDENTITY_COPY as A, type BrushLibraryFile as B, type CoverageStroke as C, DEFAULT_BEHAVIOR as D, IMPASTO_TOOLS as E, type ImpastoSettings as F, MULTI_COLOR_MODES as G, type MultiColorMode as H, type ImpastoTool as I, PAINT_BRUSH_CATEGORIES as J, PAINT_BRUSH_ENGINES as K, type Linear2 as L, type MultiColorSettings as M, PARTICLE_KINDS as N, PARTICLE_RENDERS as O, type PaintBrush as P, type PaintBrushCategory as Q, type PaintBrushEngine as R, type StrokeCopy as S, type ParticleSettings as T, ParticleStroke as U, type PenDab as V, type WetMediumKind as W, PenDabWalker as X, PenSmoother as Y, type ProceduralStrokeOptions as Z, STAMP_KINDS as _, type PenPoint as a, StickerSpray as a0, type StickerSprayInit as a1, WET_MEDIA as a2, WET_TOOLS as a3, type WetBrushSettings as a4, brushTakesPaint as a5, brushToJSON as a6, drawSticker as a7, isWetBrush as a8, isWetCapable as a9, mixerTip as aa, multiColorToPaint as ab, normalizeBehavior as ac, normalizeBristle as ad, normalizeDynamics as ae, normalizeImpasto as af, normalizeMultiColor as ag, normalizePaintBrush as ah, normalizeParticle as ai, normalizeStickerSettings as aj, normalizeStickerUpdate as ak, normalizeWetBrush as al, paintToMultiColor as am, parseBrushLibrary as an, penOrientation as ao, renderStickers as ap, stampCoverage as aq, stickerRect as ar, stickerSurface as as, tiltToOrientation as at, toStrokePoint as au, usesPaintExchange as av, type Sticker as b, type PaintBrushInput as c, type ParticleKind as d, type ParticleRender as e, type StampKind as f, type WetTool as g, BRUSH_ID as h, BRUSH_ROTATIONS as i, BUILTIN_BRUSHES as j, type BristleSettings as k, BristleStroke as l, type BrushBehavior as m, type BrushBehaviorInput as n, type BrushDynamics as o, type BrushRotation as p, DEFAULT_BRISTLE as q, DEFAULT_BRUSH_ID as r, DEFAULT_DYNAMICS as s, DEFAULT_IMPASTO as t, DEFAULT_MULTI_COLOR as u, DEFAULT_PARTICLE as v, DEFAULT_STICKER as w, DEFAULT_WET_BRUSH as x, DryStroke as y, type DryStrokeOptions as z };
@@ -1,112 +1,6 @@
1
- import { D as Dab, C as CoverageMap, B as BrushSettings, S as StrokePoint, P as PaintTileTarget, a as PaintGradientSource, T as TileMedium, M as MixerSettings, b as MixingModel, c as BlenderSettings, d as SmudgeSettings, e as PaintSource } from './blending-D7jCmjO6.cjs';
1
+ import { D as Dab, C as CoverageMap, P as PaperGrain, B as BrushSettings, S as StrokePoint, a as PaintTileTarget, b as PaintGradientSource, T as TileMedium, M as MixerSettings, c as MixingModel, d as BlenderSettings, e as SmudgeSettings, f as PaintSource } from './grain-GCt_O2kv.cjs';
2
2
  import { R as RasterRect, T as TiledSurface } from './surface-1MRncXpF.cjs';
3
3
 
4
- /**
5
- * Paper: a generated grain height map (0 = valley, 1 = peak) that tiles across the canvas, a paper
6
- * colour, grain depth, and the three properties wet media will read later (absorption,
7
- * permeability, roughness). Dry media catch the peaks: a light stroke only marks the high grain, a
8
- * firm one fills the valleys too. Everything is generated from a pattern and a seed, so a paper is
9
- * a few numbers in the document, never a bitmap, and no third-party texture is bundled.
10
- */
11
- /** Grain generators. Each is our own procedural pattern (no scanned textures). */
12
- type GrainPattern = 'smooth' | 'cold-pressed' | 'hot-pressed' | 'rough' | 'vellum' | 'laid' | 'ingres' | 'sanded' | 'canvas' | 'linen' | 'gesso';
13
- declare const GRAIN_PATTERNS: readonly GrainPattern[];
14
- /** Paper families, as a library groups them. */
15
- type PaperFamily = 'watercolor' | 'drawing' | 'pastel' | 'canvas' | 'panel' | 'smooth';
16
- declare const PAPER_FAMILIES: readonly PaperFamily[];
17
- /** How a height map is tuned for one kind of media. */
18
- interface GrainTuning {
19
- /** Height contrast around the middle, 0..3 (default 1). */
20
- contrast: number;
21
- /** Swap peaks and valleys (default false). */
22
- invert: boolean;
23
- }
24
- type PaperTiling = 'repeat' | 'mirror';
25
- interface PaperSettings {
26
- /** The library preset this paper started from (`custom` when it matches none). */
27
- preset: string;
28
- name: string;
29
- family: PaperFamily;
30
- pattern: GrainPattern;
31
- /** Grain seed: another seed gives another sheet of the same paper. */
32
- seed: number;
33
- /** Paper colour #rrggbb. */
34
- color: string;
35
- /** Grain scale: 1 is the pattern's natural size, 2 twice as coarse (0.25..8). */
36
- scale: number;
37
- /** How the grain tile repeats: `repeat` or `mirror` (alternate tiles flipped, so seams vanish). */
38
- tiling: PaperTiling;
39
- /** Grain depth 0..1: how much grain-responsive dry media skip the valleys (0: none). */
40
- depth: number;
41
- /** How much the grain shades the paper colour on screen and in exports, 0..1. */
42
- shading: number;
43
- /** The height map as dry media read it. */
44
- dry: GrainTuning;
45
- /** The height map as wet media will read it (stored now; wet media is a later phase). */
46
- wet: GrainTuning;
47
- /** Wet media: how fast the paper takes up water, 0..1. */
48
- absorption: number;
49
- /** Wet media: how far water spreads inside the paper, 0..1. */
50
- permeability: number;
51
- /** Wet media: how much the grain catches pigment, 0..1. */
52
- roughness: number;
53
- /** Metallic paper (gold or silver leaf, pearlescent card): the grain catches the light, 0..1 (default 0). */
54
- metallic?: number;
55
- }
56
- /** Side of the generated grain tile in pixels (at scale 1). */
57
- declare const GRAIN_TILE = 256;
58
- type PaperPreset = PaperSettings;
59
- /** The built-in paper library: generated papers in families. */
60
- declare const PAPER_LIBRARY: readonly PaperPreset[];
61
- declare const DEFAULT_PAPER: Readonly<PaperSettings>;
62
- /** A paper from the library by id, or `undefined`. */
63
- declare function paperPreset(id: string): PaperSettings | undefined;
64
- declare function clonePaper(p: PaperSettings): PaperSettings;
65
- /**
66
- * A paper with every field in range. Missing fields come from `base` (default: the library preset
67
- * named by `raw.preset`, else cold pressed).
68
- */
69
- declare function normalizePaper(raw?: Partial<PaperSettings> | Record<string, unknown>, base?: PaperSettings): PaperSettings;
70
- /** The raw height map of a pattern (`GRAIN_TILE` square, 0..1), generated once per pattern and seed. */
71
- declare function grainHeights(pattern: GrainPattern, seed: number, size?: number): Float32Array;
72
- /**
73
- * A paper's height field over the canvas (document pixels), tuned for one kind of media. Read it
74
- * with `at(x, y)`: bilinear, tiled by `paper.tiling`, at `paper.scale`.
75
- */
76
- declare class PaperGrain {
77
- readonly size = 256;
78
- private readonly heights;
79
- private readonly scale;
80
- private readonly mirror;
81
- readonly flat: boolean;
82
- constructor(paper: PaperSettings, media?: 'dry' | 'wet');
83
- /** Tile cell index for a coordinate in grain pixels (mirrored on odd tiles when mirroring). */
84
- private wrap;
85
- /** Height 0..1 at document position (x, y). */
86
- at(x: number, y: number): number;
87
- }
88
- /**
89
- * How much of a dry dab lands at grain height `h` (0..1): the media catches the peaks first, and
90
- * pressure pushes it into the valleys. `depth` is the paper's grain depth times the brush's grain
91
- * response; at 0 everything lands (1).
92
- */
93
- declare function grainCatch(h: number, depth: number, pressure: number): number;
94
- /** `#rrggbb` → [r, g, b] 0..255. */
95
- declare function hexToRgb8(hex: string): [number, number, number];
96
- /**
97
- * The paper as opaque RGBA over `rect` (document pixels): its colour, darkened in the valleys by
98
- * `shading`.
99
- */
100
- declare function renderPaper(paper: PaperSettings, rect: {
101
- x: number;
102
- y: number;
103
- width: number;
104
- height: number;
105
- }, grain?: PaperGrain, light?: {
106
- azimuth: number;
107
- elevation: number;
108
- }): Uint8ClampedArray;
109
-
110
4
  /**
111
5
  * The paint editor's dry-media stroke engine, on the shared brush engine (`@pixerate/editor/paint`):
112
6
  *
@@ -741,4 +635,4 @@ interface BrushLibraryFile {
741
635
  */
742
636
  declare function parseBrushLibrary(raw: unknown, problems?: string[]): PaintBrush[];
743
637
 
744
- export { type PaintBrushCategory as $, DEFAULT_WET_BRUSH as A, type BrushLibraryFile as B, type CoverageStroke as C, DEFAULT_BEHAVIOR as D, DryStroke as E, type DryStrokeOptions as F, GRAIN_PATTERNS as G, GRAIN_TILE as H, type ImpastoTool as I, type GrainPattern as J, type GrainTuning as K, IDENTITY_COPY as L, type MultiColorSettings as M, IMPASTO_TOOLS as N, type ImpastoSettings as O, type PaintBrush as P, type Linear2 as Q, MULTI_COLOR_MODES as R, type StrokeCopy as S, type MultiColorMode as T, PAINT_BRUSH_CATEGORIES as U, PAINT_BRUSH_ENGINES as V, type WetMediumKind as W, PAPER_FAMILIES as X, PAPER_LIBRARY as Y, PARTICLE_KINDS as Z, PARTICLE_RENDERS as _, type PenPoint as a, type PaintBrushEngine as a0, type PaperFamily as a1, type PaperTiling as a2, type ParticleSettings as a3, ParticleStroke as a4, type PenDab as a5, PenDabWalker as a6, PenSmoother as a7, type ProceduralStrokeOptions as a8, STAMP_KINDS as a9, normalizeStickerUpdate as aA, normalizeWetBrush as aB, paintToMultiColor as aC, paperPreset as aD, parseBrushLibrary as aE, penOrientation as aF, renderPaper as aG, renderStickers as aH, stampCoverage as aI, stickerRect as aJ, stickerSurface as aK, tiltToOrientation as aL, toStrokePoint as aM, usesPaintExchange as aN, type StickerSettings as aa, StickerSpray as ab, type StickerSprayInit as ac, WET_MEDIA as ad, WET_TOOLS as ae, type WetBrushSettings as af, brushTakesPaint as ag, brushToJSON as ah, clonePaper as ai, drawSticker as aj, grainCatch as ak, grainHeights as al, hexToRgb8 as am, isWetBrush as an, isWetCapable as ao, mixerTip as ap, multiColorToPaint as aq, normalizeBehavior as ar, normalizeBristle as as, normalizeDynamics as at, normalizeImpasto as au, normalizeMultiColor as av, normalizePaintBrush as aw, normalizePaper as ax, normalizeParticle as ay, normalizeStickerSettings as az, PaperGrain as b, type PaperSettings as c, type Sticker as d, type PaintBrushInput as e, type ParticleKind as f, type ParticleRender as g, type StampKind as h, type WetTool as i, BRUSH_ID as j, BRUSH_ROTATIONS as k, BUILTIN_BRUSHES as l, type BristleSettings as m, BristleStroke as n, type BrushBehavior as o, type BrushBehaviorInput as p, type BrushDynamics as q, type BrushRotation as r, DEFAULT_BRISTLE as s, DEFAULT_BRUSH_ID as t, DEFAULT_DYNAMICS as u, DEFAULT_IMPASTO as v, DEFAULT_MULTI_COLOR as w, DEFAULT_PAPER as x, DEFAULT_PARTICLE as y, DEFAULT_STICKER as z };
638
+ export { type StickerSettings as $, IDENTITY_COPY as A, type BrushLibraryFile as B, type CoverageStroke as C, DEFAULT_BEHAVIOR as D, IMPASTO_TOOLS as E, type ImpastoSettings as F, MULTI_COLOR_MODES as G, type MultiColorMode as H, type ImpastoTool as I, PAINT_BRUSH_CATEGORIES as J, PAINT_BRUSH_ENGINES as K, type Linear2 as L, type MultiColorSettings as M, PARTICLE_KINDS as N, PARTICLE_RENDERS as O, type PaintBrush as P, type PaintBrushCategory as Q, type PaintBrushEngine as R, type StrokeCopy as S, type ParticleSettings as T, ParticleStroke as U, type PenDab as V, type WetMediumKind as W, PenDabWalker as X, PenSmoother as Y, type ProceduralStrokeOptions as Z, STAMP_KINDS as _, type PenPoint as a, StickerSpray as a0, type StickerSprayInit as a1, WET_MEDIA as a2, WET_TOOLS as a3, type WetBrushSettings as a4, brushTakesPaint as a5, brushToJSON as a6, drawSticker as a7, isWetBrush as a8, isWetCapable as a9, mixerTip as aa, multiColorToPaint as ab, normalizeBehavior as ac, normalizeBristle as ad, normalizeDynamics as ae, normalizeImpasto as af, normalizeMultiColor as ag, normalizePaintBrush as ah, normalizeParticle as ai, normalizeStickerSettings as aj, normalizeStickerUpdate as ak, normalizeWetBrush as al, paintToMultiColor as am, parseBrushLibrary as an, penOrientation as ao, renderStickers as ap, stampCoverage as aq, stickerRect as ar, stickerSurface as as, tiltToOrientation as at, toStrokePoint as au, usesPaintExchange as av, type Sticker as b, type PaintBrushInput as c, type ParticleKind as d, type ParticleRender as e, type StampKind as f, type WetTool as g, BRUSH_ID as h, BRUSH_ROTATIONS as i, BUILTIN_BRUSHES as j, type BristleSettings as k, BristleStroke as l, type BrushBehavior as m, type BrushBehaviorInput as n, type BrushDynamics as o, type BrushRotation as p, DEFAULT_BRISTLE as q, DEFAULT_BRUSH_ID as r, DEFAULT_DYNAMICS as s, DEFAULT_IMPASTO as t, DEFAULT_MULTI_COLOR as u, DEFAULT_PARTICLE as v, DEFAULT_STICKER as w, DEFAULT_WET_BRUSH as x, DryStroke as y, type DryStrokeOptions as z };
@@ -1,12 +1,12 @@
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 _chunkKEENUAFNcjs = require('./chunk-KEENUAFN.cjs');
3
+ var _chunkDLLUOVQXcjs = require('./chunk-DLLUOVQX.cjs');
4
4
 
5
5
 
6
6
  var _chunk2YQONY6Gcjs = require('./chunk-2YQONY6G.cjs');
7
7
 
8
8
 
9
- var _chunk2XS6LTUPcjs = require('./chunk-2XS6LTUP.cjs');
9
+ var _chunkC46AZEBEcjs = require('./chunk-C46AZEBE.cjs');
10
10
 
11
11
 
12
12
 
@@ -17,34 +17,34 @@ var _chunkIZ4WRXZAcjs = require('./chunk-IZ4WRXZA.cjs');
17
17
 
18
18
 
19
19
 
20
- var _chunkQBWEH3IMcjs = require('./chunk-QBWEH3IM.cjs');
20
+ var _chunk3T5O225Tcjs = require('./chunk-3T5O225T.cjs');
21
21
  require('./chunk-SYPFUL5B.cjs');
22
22
  require('./chunk-O7LUD7MH.cjs');
23
23
  require('./chunk-643B2HKC.cjs');
24
24
 
25
25
  // src/canvas/brushstrokeRender.ts
26
- var DRY = _chunk2XS6LTUPcjs.BUILTIN_BRUSHES.filter((b) => _chunkQBWEH3IMcjs.isBrushstrokeBrush.call(void 0, b));
26
+ var DRY = _chunkC46AZEBEcjs.BUILTIN_BRUSHES.filter((b) => _chunk3T5O225Tcjs.isBrushstrokeBrush.call(void 0, b));
27
27
  function listCanvasBrushes() {
28
28
  return DRY.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size }));
29
29
  }
30
30
  function resolveCanvasBrush(brush, width) {
31
- const size = Math.max(1, Number.isFinite(width) ? width : 1);
31
+ const given = width !== void 0 && Number.isFinite(width) ? Math.max(1, width) : void 0;
32
32
  if (typeof brush === "string") {
33
- const preset = _chunk2XS6LTUPcjs.BUILTIN_BRUSHES.find((b) => b.id === brush);
33
+ const preset = _chunkC46AZEBEcjs.BUILTIN_BRUSHES.find((b) => b.id === brush);
34
34
  const ids = DRY.map((b) => b.id).join(", ");
35
35
  if (!preset) throw new Error(`Unknown brush '${brush}'. Use one of the dry paint presets (${ids}) or tip settings ({ hardness, spacing, flow, \u2026 }).`);
36
- if (!_chunkQBWEH3IMcjs.isBrushstrokeBrush.call(void 0, preset)) {
36
+ if (!_chunk3T5O225Tcjs.isBrushstrokeBrush.call(void 0, preset)) {
37
37
  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}.`);
38
38
  }
39
- return { ...preset, tip: { ...preset.tip, size } };
39
+ return given === void 0 ? preset : { ...preset, tip: { ...preset.tip, size: given } };
40
40
  }
41
41
  if (!brush || typeof brush !== "object" || Array.isArray(brush)) {
42
42
  throw new Error("A brush is a dry paint preset id (see listCanvasBrushes / canvas.listBrushes) or an object of tip settings.");
43
43
  }
44
- return { size, ...brush };
44
+ return { size: _nullishCoalesce(given, () => ( 3)), ...brush };
45
45
  }
46
46
  function resolveCanvasPaint(paint) {
47
- const source = _chunkQBWEH3IMcjs.parsePaintSource.call(void 0, paint, "paint");
47
+ const source = _chunk3T5O225Tcjs.parsePaintSource.call(void 0, paint, "paint");
48
48
  if (source.type === "gradient" && (source.map.by === "velocity" || source.map.by === "time")) {
49
49
  throw new Error(`paint.map.by "${source.map.by}" needs timed samples, which canvas strokes do not record; use distance, pressure, tilt, direction, across-tip or random.`);
50
50
  }
@@ -60,48 +60,76 @@ function shapePaint(shape) {
60
60
  }
61
61
  function strokeColor(shape) {
62
62
  const gradient = _chunkIZ4WRXZAcjs.resolveCanvasGradient.call(void 0, _nullishCoalesce(shape.strokeGradient, () => ( shape.stroke)));
63
- const css = gradient ? _nullishCoalesce(_optionalChain([gradient, 'access', _ => _.stops, 'access', _2 => _2[0], 'optionalAccess', _3 => _3.color]), () => ( "#000")) : typeof shape.stroke === "string" && shape.stroke !== "none" ? shape.stroke : "#000";
63
+ const css = gradient ? _nullishCoalesce(_optionalChain([gradient, 'access', _2 => _2.stops, 'access', _3 => _3[0], 'optionalAccess', _4 => _4.color]), () => ( "#000")) : typeof shape.stroke === "string" && shape.stroke !== "none" ? shape.stroke : "#000";
64
64
  const parsed = _nullishCoalesce(_chunk2YQONY6Gcjs.parseCssColor.call(void 0, css), () => ( { hex: "#000000", alpha: 1 }));
65
65
  const n = parseInt(parsed.hex.slice(1), 16);
66
66
  return [n >> 16 & 255, n >> 8 & 255, n & 255, Math.round(parsed.alpha * 255)];
67
67
  }
68
- var cache = new (0, _chunkQBWEH3IMcjs.BrushstrokeCache)(128);
68
+ var cache = new (0, _chunk3T5O225Tcjs.BrushstrokeCache)(128);
69
69
  var urls = /* @__PURE__ */ new WeakMap();
70
- function renderShapeBrushstrokeNow(shape, scale) {
71
- const brush = resolveCanvasBrush(shape.brush.brush, _nullishCoalesce(shape.strokeWidth, () => ( 3)));
72
- const points = _chunkKEENUAFNcjs.pathDataToPolyline.call(void 0, shape.d);
70
+ function renderShapeBrushstrokeNow(shape, scale, place = {}) {
71
+ const seed = shape.brush.seed;
72
+ const textured = typeof seed === "number" && Number.isFinite(seed);
73
+ const size = _nullishCoalesce(shape.brush.size, () => ( (textured ? void 0 : _nullishCoalesce(shape.strokeWidth, () => ( 3)))));
74
+ const brush = resolveCanvasBrush(shape.brush.brush, size);
75
+ const points = _chunkDLLUOVQXcjs.pathDataToPolyline.call(void 0, shape.d);
73
76
  if (!points.length) return null;
74
77
  const { pressure, tiltX, tiltY } = shape.brush;
75
78
  const input = {
76
79
  points: points.map(([x, y]) => ({ x, y })),
77
- ..._optionalChain([pressure, 'optionalAccess', _4 => _4.length]) ? { pressure } : {},
78
- ..._optionalChain([tiltX, 'optionalAccess', _5 => _5.length]) ? { tiltX } : {},
79
- ..._optionalChain([tiltY, 'optionalAccess', _6 => _6.length]) ? { tiltY } : {}
80
+ ..._optionalChain([pressure, 'optionalAccess', _5 => _5.length]) ? { pressure } : {},
81
+ ..._optionalChain([tiltX, 'optionalAccess', _6 => _6.length]) ? { tiltX } : {},
82
+ ..._optionalChain([tiltY, 'optionalAccess', _7 => _7.length]) ? { tiltY } : {}
80
83
  };
81
84
  const paint = shapePaint(shape);
82
- const stroke = cache.render(input, brush, { scale, color: strokeColor(shape), ...paint ? { paint } : {} });
85
+ const character = textured ? {
86
+ character: {
87
+ seed,
88
+ ...shape.brush.paper !== void 0 ? { paper: shape.brush.paper } : {},
89
+ // Whole canvas units: the grain stays put, and tiny drags do not re-render.
90
+ origin: { x: Math.round(_nullishCoalesce(_optionalChain([place, 'access', _8 => _8.origin, 'optionalAccess', _9 => _9.x]), () => ( 0))), y: Math.round(_nullishCoalesce(_optionalChain([place, 'access', _10 => _10.origin, 'optionalAccess', _11 => _11.y]), () => ( 0))) },
91
+ unit: place.unit && place.unit > 0 ? Math.round(place.unit * 1e3) / 1e3 : 1
92
+ }
93
+ } : {};
94
+ const stroke = cache.render(input, brush, { scale, color: strokeColor(shape), ...paint ? { paint } : {}, ...character });
83
95
  if (!stroke.bounds) return null;
84
96
  let href = urls.get(stroke);
85
97
  if (href === void 0) {
86
- href = _chunkQBWEH3IMcjs.brushstrokeDataUrl.call(void 0, stroke);
98
+ href = _chunk3T5O225Tcjs.brushstrokeDataUrl.call(void 0, stroke);
87
99
  urls.set(stroke, href);
88
100
  }
89
101
  return { href, ...stroke.bounds, scale: stroke.scale };
90
102
  }
91
103
  var escapeAttr = (s) => s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
92
- function brushedShapeSvgNow(shape, size, scale) {
104
+ function brushedShapeSvgNow(shape, size, scale, place = {}) {
93
105
  const f = (n) => _chunkIZ4WRXZAcjs.formatNumber.call(void 0, n, 3);
94
- const image = renderShapeBrushstrokeNow(shape, scale);
106
+ const image = renderShapeBrushstrokeNow(shape, scale, place);
95
107
  const svg = _chunkIZ4WRXZAcjs.getShapeSvg.call(void 0, shape, size);
96
108
  const body = image ? `<image href="${escapeAttr(image.href)}" x="${f(image.x)}" y="${f(image.y)}" width="${f(image.width)}" height="${f(image.height)}" preserveAspectRatio="none"${shape.opacity !== void 0 ? ` opacity="${f(shape.opacity)}"` : ""}/>` : "";
97
109
  const content = svg.transform && body ? `<g transform="${svg.transform}">${body}</g>` : body;
98
110
  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>`;
99
111
  }
112
+ function brushSwatchNow(brush, options) {
113
+ const w = options.width && options.width > 8 ? options.width : 96;
114
+ const h = options.height && options.height > 4 ? options.height : 24;
115
+ const own = typeof brush === "string" ? resolveCanvasBrush(brush, void 0) : resolveCanvasBrush(brush, brush.size);
116
+ const tip = "tip" in own && own.tip ? _nullishCoalesce(own.tip.size, () => ( 3)) : _nullishCoalesce(own.size, () => ( 3));
117
+ const size = Math.max(1.5, Math.min(tip, h * 0.55));
118
+ const pad = size / 2 + 2;
119
+ 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]);
120
+ const d = pts.map(([x, y], i) => `${i ? "L" : "M"}${x.toFixed(2)} ${y.toFixed(2)}`).join(" ");
121
+ const image = renderShapeBrushstrokeNow(
122
+ { kind: "path", d, stroke: _nullishCoalesce(options.color, () => ( "#1f2937")), strokeWidth: size, brush: { brush, seed: 7, size, pressure: [0.35, 1, 0.5] } },
123
+ options.scale && options.scale > 0 ? options.scale : 2
124
+ );
125
+ return _nullishCoalesce(_optionalChain([image, 'optionalAccess', _12 => _12.href]), () => ( ""));
126
+ }
127
+
100
128
 
101
129
 
102
130
 
103
131
 
104
132
 
105
133
 
106
- exports.brushedShapeSvgNow = brushedShapeSvgNow; exports.listCanvasBrushes = listCanvasBrushes; exports.renderShapeBrushstrokeNow = renderShapeBrushstrokeNow; exports.resolveCanvasBrush = resolveCanvasBrush; exports.resolveCanvasPaint = resolveCanvasPaint;
107
- //# sourceMappingURL=brushstrokeRender-IMFY3FF6.cjs.map
134
+ exports.brushSwatchNow = brushSwatchNow; exports.brushedShapeSvgNow = brushedShapeSvgNow; exports.listCanvasBrushes = listCanvasBrushes; exports.renderShapeBrushstrokeNow = renderShapeBrushstrokeNow; exports.resolveCanvasBrush = resolveCanvasBrush; exports.resolveCanvasPaint = resolveCanvasPaint;
135
+ //# sourceMappingURL=brushstrokeRender-PP6NTF6M.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/brushstrokeRender-PP6NTF6M.cjs","../src/canvas/brushstrokeRender.ts"],"names":[],"mappings":"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","file":"/home/runner/work/editor/editor/packages/core/dist/brushstrokeRender-PP6NTF6M.cjs","sourcesContent":[null,"/**\n * The lazily loaded half of brushed freehand strokes (`./brushstroke.ts` imports it on first use):\n * the paint engine's dry brushstroke renderer, the paint presets and a CSS colour parser. Keep\n * every import of the paint engine in this file, so the canvas entry never reaches it statically.\n */\nimport { BrushstrokeCache, brushstrokeDataUrl, isBrushstrokeBrush, type BrushstrokeBrush, type BrushstrokeImage } from '../paint/brushstroke.js';\nimport { parsePaintSource, type PaintSource } from '../paint/gradient-paint.js';\nimport { BUILTIN_BRUSHES } from '../paint-editor/brushes.js';\nimport { parseCssColor } from '../image-editor/layers/css-color.js';\nimport { resolveCanvasGradient } from './gradient.js';\nimport { formatNumber, getShapeSvg, type ShapeSize } from './shapeGeometry.js';\nimport { pathDataToPolyline, type BrushedPathShape, type CanvasBrushSwatchOptions, type CanvasBrush, type CanvasBrushPaint, type CanvasBrushPreset, type CanvasBrushstrokeImage } from './brushstroke.js';\n\nconst DRY = BUILTIN_BRUSHES.filter((b) => isBrushstrokeBrush(b));\n\nexport function listCanvasBrushes(): CanvasBrushPreset[] {\n\treturn DRY.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size }));\n}\n\n/**\n * The paint engine brush for a canvas brush at diameter `width` (undefined: the preset's own size,\n * or the settings' `size`, else 3); throws with the usable ids.\n */\nexport function resolveCanvasBrush(brush: CanvasBrush, width: number | undefined): BrushstrokeBrush {\n\tconst given = width !== undefined && Number.isFinite(width) ? Math.max(1, width) : undefined;\n\tif (typeof brush === 'string') {\n\t\tconst preset = BUILTIN_BRUSHES.find((b) => b.id === brush);\n\t\tconst ids = DRY.map((b) => b.id).join(', ');\n\t\tif (!preset) throw new Error(`Unknown brush '${brush}'. Use one of the dry paint presets (${ids}) or tip settings ({ hardness, spacing, flow, … }).`);\n\t\tif (!isBrushstrokeBrush(preset)) {\n\t\t\tthrow 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}.`);\n\t\t}\n\t\treturn given === undefined ? preset : { ...preset, tip: { ...preset.tip, size: given } };\n\t}\n\tif (!brush || typeof brush !== 'object' || Array.isArray(brush)) {\n\t\tthrow new Error('A brush is a dry paint preset id (see listCanvasBrushes / canvas.listBrushes) or an object of tip settings.');\n\t}\n\treturn { size: given ?? 3, ...brush };\n}\n\n/**\n * The paint engine's `PaintSource` for a brushed stroke's `paint`; throws with what is wrong.\n * Mappings that need timed samples (`velocity`, `time`) are refused: canvas strokes carry no timing.\n */\nexport function resolveCanvasPaint(paint: CanvasBrushPaint | unknown): PaintSource {\n\tconst source = parsePaintSource(paint, 'paint');\n\tif (source.type === 'gradient' && (source.map.by === 'velocity' || source.map.by === 'time')) {\n\t\tthrow new Error(`paint.map.by \"${source.map.by}\" needs timed samples, which canvas strokes do not record; use distance, pressure, tilt, direction, across-tip or random.`);\n\t}\n\treturn source;\n}\n\n/** A stored stroke's paint, or null when it has none or it is not valid paint (the stroke colour is used then). */\nfunction shapePaint(shape: BrushedPathShape): PaintSource | null {\n\tif (shape.brush.paint === undefined || shape.brush.paint === null) return null;\n\ttry {\n\t\treturn resolveCanvasPaint(shape.brush.paint);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * RGBA of a shape's stroke paint: with `brush.paint` its alpha and the colour of `paint` stops.\n * A canvas gradient stroke without `brush.paint` uses its first stop.\n */\nfunction strokeColor(shape: BrushedPathShape): [number, number, number, number] {\n\tconst gradient = resolveCanvasGradient(shape.strokeGradient ?? shape.stroke);\n\tconst css = gradient ? (gradient.stops[0]?.color ?? '#000') : typeof shape.stroke === 'string' && shape.stroke !== 'none' ? shape.stroke : '#000';\n\tconst parsed = parseCssColor(css) ?? { hex: '#000000', alpha: 1 };\n\tconst n = parseInt(parsed.hex.slice(1), 16);\n\treturn [(n >> 16) & 255, (n >> 8) & 255, n & 255, Math.round(parsed.alpha * 255)];\n}\n\nconst cache = new BrushstrokeCache(128);\nconst urls = new WeakMap<BrushstrokeImage, string>();\n\nexport function renderShapeBrushstrokeNow(shape: BrushedPathShape, scale: number, place: { origin?: { x: number; y: number }; unit?: number } = {}): CanvasBrushstrokeImage | null {\n\t// Strokes with a character seed (new ones) default to the preset's size and render its jitter\n\t// and grain; older strokes keep the stroke width and plain dabs, so they look as they always did.\n\tconst seed = shape.brush.seed;\n\tconst textured = typeof seed === 'number' && Number.isFinite(seed);\n\tconst size = shape.brush.size ?? (textured ? undefined : (shape.strokeWidth ?? 3));\n\tconst brush = resolveCanvasBrush(shape.brush.brush, size);\n\tconst points = pathDataToPolyline(shape.d);\n\tif (!points.length) return null;\n\tconst { pressure, tiltX, tiltY } = shape.brush;\n\tconst input = {\n\t\tpoints: points.map(([x, y]) => ({ x, y })),\n\t\t...(pressure?.length ? { pressure } : {}),\n\t\t...(tiltX?.length ? { tiltX } : {}),\n\t\t...(tiltY?.length ? { tiltY } : {})\n\t};\n\tconst paint = shapePaint(shape);\n\tconst character = textured\n\t\t? {\n\t\t\t\tcharacter: {\n\t\t\t\t\tseed,\n\t\t\t\t\t...(shape.brush.paper !== undefined ? { paper: shape.brush.paper } : {}),\n\t\t\t\t\t// Whole canvas units: the grain stays put, and tiny drags do not re-render.\n\t\t\t\t\torigin: { x: Math.round(place.origin?.x ?? 0), y: Math.round(place.origin?.y ?? 0) },\n\t\t\t\t\tunit: place.unit && place.unit > 0 ? Math.round(place.unit * 1000) / 1000 : 1\n\t\t\t\t}\n\t\t\t}\n\t\t: {};\n\tconst stroke = cache.render(input, brush, { scale, color: strokeColor(shape), ...(paint ? { paint } : {}), ...character });\n\tif (!stroke.bounds) return null;\n\tlet href = urls.get(stroke);\n\tif (href === undefined) {\n\t\thref = brushstrokeDataUrl(stroke);\n\t\turls.set(stroke, href);\n\t}\n\treturn { href, ...stroke.bounds, scale: stroke.scale };\n}\n\nconst escapeAttr = (s: string) => s.replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;');\n\nexport function brushedShapeSvgNow(shape: BrushedPathShape, size: ShapeSize, scale: number, place: { origin?: { x: number; y: number }; unit?: number } = {}): string {\n\tconst f = (n: number) => formatNumber(n, 3);\n\tconst image = renderShapeBrushstrokeNow(shape, scale, place);\n\tconst svg = getShapeSvg(shape, size);\n\tconst body = image\n\t\t? `<image href=\"${escapeAttr(image.href)}\" x=\"${f(image.x)}\" y=\"${f(image.y)}\" width=\"${f(image.width)}\" height=\"${f(image.height)}\" preserveAspectRatio=\"none\"${shape.opacity !== undefined ? ` opacity=\"${f(shape.opacity)}\"` : ''}/>`\n\t\t: '';\n\tconst content = svg.transform && body ? `<g transform=\"${svg.transform}\">${body}</g>` : body;\n\treturn `<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>`;\n}\n\nexport function brushSwatchNow(brush: CanvasBrush, options: CanvasBrushSwatchOptions): string {\n\tconst w = options.width && options.width > 8 ? options.width : 96;\n\tconst h = options.height && options.height > 4 ? options.height : 24;\n\tconst own = typeof brush === 'string' ? resolveCanvasBrush(brush, undefined) : resolveCanvasBrush(brush, brush.size);\n\tconst tip = 'tip' in own && own.tip ? own.tip.size ?? 3 : (own as { size?: number }).size ?? 3;\n\tconst size = Math.max(1.5, Math.min(tip, h * 0.55));\n\tconst pad = size / 2 + 2;\n\t// A gentle S across the swatch, swelling in the middle.\n\tconst 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] as const);\n\tconst d = pts.map(([x, y], i) => `${i ? 'L' : 'M'}${x.toFixed(2)} ${y.toFixed(2)}`).join(' ');\n\tconst image = renderShapeBrushstrokeNow(\n\t\t{ kind: 'path', d, stroke: options.color ?? '#1f2937', strokeWidth: size, brush: { brush, seed: 7, size, pressure: [0.35, 1, 0.5] } },\n\t\toptions.scale && options.scale > 0 ? options.scale : 2\n\t);\n\treturn image?.href ?? '';\n}\n"]}
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  pathDataToPolyline
3
- } from "./chunk-EQQMGO42.js";
3
+ } from "./chunk-C2J7GOMJ.js";
4
4
  import {
5
5
  parseCssColor
6
6
  } from "./chunk-CTTF4KYV.js";
7
7
  import {
8
8
  BUILTIN_BRUSHES
9
- } from "./chunk-HUO3TI4C.js";
9
+ } from "./chunk-CPSMARTX.js";
10
10
  import {
11
11
  formatNumber,
12
12
  getShapeSvg,
@@ -17,7 +17,7 @@ import {
17
17
  brushstrokeDataUrl,
18
18
  isBrushstrokeBrush,
19
19
  parsePaintSource
20
- } from "./chunk-5FSRTP6E.js";
20
+ } from "./chunk-PQFVLOMR.js";
21
21
  import "./chunk-63AZAUSN.js";
22
22
  import "./chunk-3G6J7UFV.js";
23
23
  import "./chunk-6FCKOS2B.js";
@@ -28,7 +28,7 @@ function listCanvasBrushes() {
28
28
  return DRY.map((b) => ({ id: b.id, name: b.name, category: b.category, size: b.tip.size }));
29
29
  }
30
30
  function resolveCanvasBrush(brush, width) {
31
- const size = Math.max(1, Number.isFinite(width) ? width : 1);
31
+ const given = width !== void 0 && Number.isFinite(width) ? Math.max(1, width) : void 0;
32
32
  if (typeof brush === "string") {
33
33
  const preset = BUILTIN_BRUSHES.find((b) => b.id === brush);
34
34
  const ids = DRY.map((b) => b.id).join(", ");
@@ -36,12 +36,12 @@ function resolveCanvasBrush(brush, width) {
36
36
  if (!isBrushstrokeBrush(preset)) {
37
37
  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}.`);
38
38
  }
39
- return { ...preset, tip: { ...preset.tip, size } };
39
+ return given === void 0 ? preset : { ...preset, tip: { ...preset.tip, size: given } };
40
40
  }
41
41
  if (!brush || typeof brush !== "object" || Array.isArray(brush)) {
42
42
  throw new Error("A brush is a dry paint preset id (see listCanvasBrushes / canvas.listBrushes) or an object of tip settings.");
43
43
  }
44
- return { size, ...brush };
44
+ return { size: given ?? 3, ...brush };
45
45
  }
46
46
  function resolveCanvasPaint(paint) {
47
47
  const source = parsePaintSource(paint, "paint");
@@ -67,8 +67,11 @@ function strokeColor(shape) {
67
67
  }
68
68
  var cache = new BrushstrokeCache(128);
69
69
  var urls = /* @__PURE__ */ new WeakMap();
70
- function renderShapeBrushstrokeNow(shape, scale) {
71
- const brush = resolveCanvasBrush(shape.brush.brush, shape.strokeWidth ?? 3);
70
+ function renderShapeBrushstrokeNow(shape, scale, place = {}) {
71
+ const seed = shape.brush.seed;
72
+ const textured = typeof seed === "number" && Number.isFinite(seed);
73
+ const size = shape.brush.size ?? (textured ? void 0 : shape.strokeWidth ?? 3);
74
+ const brush = resolveCanvasBrush(shape.brush.brush, size);
72
75
  const points = pathDataToPolyline(shape.d);
73
76
  if (!points.length) return null;
74
77
  const { pressure, tiltX, tiltY } = shape.brush;
@@ -79,7 +82,16 @@ function renderShapeBrushstrokeNow(shape, scale) {
79
82
  ...tiltY?.length ? { tiltY } : {}
80
83
  };
81
84
  const paint = shapePaint(shape);
82
- const stroke = cache.render(input, brush, { scale, color: strokeColor(shape), ...paint ? { paint } : {} });
85
+ const character = textured ? {
86
+ character: {
87
+ seed,
88
+ ...shape.brush.paper !== void 0 ? { paper: shape.brush.paper } : {},
89
+ // Whole canvas units: the grain stays put, and tiny drags do not re-render.
90
+ origin: { x: Math.round(place.origin?.x ?? 0), y: Math.round(place.origin?.y ?? 0) },
91
+ unit: place.unit && place.unit > 0 ? Math.round(place.unit * 1e3) / 1e3 : 1
92
+ }
93
+ } : {};
94
+ const stroke = cache.render(input, brush, { scale, color: strokeColor(shape), ...paint ? { paint } : {}, ...character });
83
95
  if (!stroke.bounds) return null;
84
96
  let href = urls.get(stroke);
85
97
  if (href === void 0) {
@@ -89,19 +101,35 @@ function renderShapeBrushstrokeNow(shape, scale) {
89
101
  return { href, ...stroke.bounds, scale: stroke.scale };
90
102
  }
91
103
  var escapeAttr = (s) => s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
92
- function brushedShapeSvgNow(shape, size, scale) {
104
+ function brushedShapeSvgNow(shape, size, scale, place = {}) {
93
105
  const f = (n) => formatNumber(n, 3);
94
- const image = renderShapeBrushstrokeNow(shape, scale);
106
+ const image = renderShapeBrushstrokeNow(shape, scale, place);
95
107
  const svg = getShapeSvg(shape, size);
96
108
  const body = image ? `<image href="${escapeAttr(image.href)}" x="${f(image.x)}" y="${f(image.y)}" width="${f(image.width)}" height="${f(image.height)}" preserveAspectRatio="none"${shape.opacity !== void 0 ? ` opacity="${f(shape.opacity)}"` : ""}/>` : "";
97
109
  const content = svg.transform && body ? `<g transform="${svg.transform}">${body}</g>` : body;
98
110
  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>`;
99
111
  }
112
+ function brushSwatchNow(brush, options) {
113
+ const w = options.width && options.width > 8 ? options.width : 96;
114
+ const h = options.height && options.height > 4 ? options.height : 24;
115
+ const own = typeof brush === "string" ? resolveCanvasBrush(brush, void 0) : resolveCanvasBrush(brush, brush.size);
116
+ const tip = "tip" in own && own.tip ? own.tip.size ?? 3 : own.size ?? 3;
117
+ const size = Math.max(1.5, Math.min(tip, h * 0.55));
118
+ const pad = size / 2 + 2;
119
+ 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]);
120
+ const d = pts.map(([x, y], i) => `${i ? "L" : "M"}${x.toFixed(2)} ${y.toFixed(2)}`).join(" ");
121
+ const image = renderShapeBrushstrokeNow(
122
+ { kind: "path", d, stroke: options.color ?? "#1f2937", strokeWidth: size, brush: { brush, seed: 7, size, pressure: [0.35, 1, 0.5] } },
123
+ options.scale && options.scale > 0 ? options.scale : 2
124
+ );
125
+ return image?.href ?? "";
126
+ }
100
127
  export {
128
+ brushSwatchNow,
101
129
  brushedShapeSvgNow,
102
130
  listCanvasBrushes,
103
131
  renderShapeBrushstrokeNow,
104
132
  resolveCanvasBrush,
105
133
  resolveCanvasPaint
106
134
  };
107
- //# sourceMappingURL=brushstrokeRender-2FAXA3XC.js.map
135
+ //# sourceMappingURL=brushstrokeRender-QGEYJ4OZ.js.map