@weasel-js/renderer 1.9.4 → 1.10.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.
@@ -1,4 +1,4 @@
1
- import { TextureHandle, FillStyle, PosedFill, GradientUnits, GradStop, ColorSpace } from '@weasel-js/paint';
1
+ import { TextureHandle, Paint, PosedPaint, PaintUnits, GradStop, ColorSpace } from '@weasel-js/paint';
2
2
  import { Reflection } from '@weasel-js/registry';
3
3
  import { GlMat3 } from '@weasel-js/geom';
4
4
 
@@ -10,13 +10,14 @@ import { GlMat3 } from '@weasel-js/geom';
10
10
  * Designed so test code can stub `getShaderParameter` / `getProgramParameter`
11
11
  * via the recorder Proxy without further special-casing.
12
12
  */
13
- type Stage = 'vertex' | 'fragment' | 'link';
13
+ /** The step of building a program that a `ShaderCompileError` came from. */
14
+ type ShaderStage = 'vertex' | 'fragment' | 'link';
14
15
  /** Thrown when a shader fails to compile or link, carrying which stage failed
15
16
  * and the driver's log. */
16
17
  declare class ShaderCompileError extends Error {
17
- readonly stage: Stage;
18
+ readonly stage: ShaderStage;
18
19
  readonly log: string;
19
- constructor(stage: Stage, log: string);
20
+ constructor(stage: ShaderStage, log: string);
20
21
  }
21
22
  /** An array uniform a program can be written as a whole: its element type as
22
23
  * declared, its declared length, and the location of slot 0. */
@@ -48,17 +49,10 @@ declare class ShaderProgram {
48
49
  }
49
50
 
50
51
  /**
51
- * registerProgram — public API for registering custom shader programs.
52
- *
53
- * Stores raw GLSL source strings in a module-level registry. GL compilation
54
- * happens on each WeaselRenderer via WeaselRenderer.registerProgram(), which
55
- * calls getProgramSource() and compiles the result. This keeps registerProgram
56
- * GL-context-agnostic — identical pattern to registerFont storing ImageBitmap.
57
- *
58
- * Module-level state = source strings only; compiled GL
59
- * programs live on each renderer's programRegistry (Map<id, ShaderProgram>).
60
- *
61
- * Lifecycle: program sources live for the module lifetime. No unregister in v1.
52
+ * Public API for registering custom shader programs. Module-level state is
53
+ * source strings only; each WeaselRenderer compiles them into its own
54
+ * programRegistry, which keeps registration GL-context-agnostic. Program
55
+ * sources live for the module lifetime.
62
56
  */
63
57
 
64
58
  /** Opaque handle to a compiled custom shader program. */
@@ -123,22 +117,18 @@ declare function getProgramSource(id: string): ProgramSource | null;
123
117
  * to compile, the renderer logs the error once and keeps drawing the old one.
124
118
  * - Prod mode: calling with an existing id throws.
125
119
  *
126
- * Actual GL compilation and `ShaderCompileError` throwing happen in
127
- * `WeaselRenderer.registerProgram()`, not here.
128
- *
129
- * @experimental API may break before v2.
120
+ * Nothing compiles here. A renderer compiles the source the first time it
121
+ * draws with it and reports a failure to the console, drawing nothing for
122
+ * that command; `WeaselRenderer.registerProgram()` compiles ahead of that and
123
+ * throws `ShaderCompileError`.
130
124
  */
131
125
  declare function registerProgram(id: string, vert: string, frag: string): ShaderProgramHandle;
132
126
 
133
127
  /**
134
128
  * Resolve a `units: 'bounds'` gradient into the frame its node is painted in.
135
- *
136
129
  * Node painters bake the pose into their geometry (`pathInPoseFrame`) rather
137
130
  * than emitting a per-node transform, so by the time the renderer sees a
138
131
  * scene node there is no node-local space left for a gradient to refer to.
139
- * This is the paint-side counterpart: it maps box-relative gradient geometry
140
- * onto the same box the path was just projected into, so the two arrive in
141
- * the renderer in one frame.
142
132
  */
143
133
 
144
134
  /** The box a `'bounds'` gradient's normalized geometry is mapped onto —
@@ -160,20 +150,16 @@ interface FillPoseBox {
160
150
  * so a non-square box cannot make it the ellipse SVG would — this keeps the
161
151
  * radius proportionate instead.
162
152
  */
163
- declare function fillInPoseFrame(fill: FillStyle, box: FillPoseBox): PosedFill;
153
+ declare function fillInPoseFrame(fill: Paint, box: FillPoseBox): PosedPaint;
164
154
  /**
165
155
  * Inverse of {@link fillInPoseFrame}: express a gradient already positioned
166
156
  * in `box`'s frame as `0..1` fractions of that box (`units: 'bounds'`), so
167
157
  * it follows the node when moved or resized.
168
158
  *
169
- * The importer's counterpart — SVG `userSpaceOnUse` coordinates arrive in
170
- * page space, and normalizing them here makes an imported gradient behave
171
- * exactly like one drawn in the app.
172
- *
173
159
  * A zero-width or zero-height box has no frame to normalize against; the
174
160
  * fill is returned untouched rather than divided by zero.
175
161
  */
176
- declare function fillToBoundsFrame(fill: FillStyle, box: FillPoseBox): FillStyle;
162
+ declare function fillToBoundsFrame(fill: Paint, box: FillPoseBox): Paint;
177
163
 
178
164
  /**
179
165
  * The renderer's half of a paint kind: what it takes to draw one.
@@ -181,9 +167,7 @@ declare function fillToBoundsFrame(fill: FillStyle, box: FillPoseBox): FillStyle
181
167
  * Solids, patterns and the three gradients are branches inside the GL
182
168
  * renderer and never come through here. A kind beyond those registers its
183
169
  * bind slot, its frame conversions and its shaders, and the renderer consults
184
- * this registry for any kind it does not recognize. What an editor or a
185
- * serializer needs of a kind is not the renderer's to know; whoever owns
186
- * those registers it beside this.
170
+ * this registry for any kind it does not recognize.
187
171
  */
188
172
 
189
173
  /** A compiled GL program. A paint kind gets one from
@@ -227,14 +211,14 @@ interface PaintBindContext {
227
211
  * an axis. Nothing the paint draws there means anything, so `bind` should
228
212
  * return `null` and draw nothing.
229
213
  */
230
- spaceInverse(units: GradientUnits | undefined): GlMat3 | null;
214
+ spaceInverse(units: PaintUnits | undefined): GlMat3 | null;
231
215
  /**
232
216
  * The transform from the space `units` names to device pixels of the target
233
217
  * this draw lands in — screen pixels times the pixel ratio on screen, output
234
218
  * pixels in a headless render. It is what a paint baking a texture measures
235
219
  * its resolution against. `null` where `spaceInverse` is.
236
220
  */
237
- spaceToDevice(units: GradientUnits | undefined): GlMat3 | null;
221
+ spaceToDevice(units: PaintUnits | undefined): GlMat3 | null;
238
222
  /** The largest texture side this context accepts, `MAX_TEXTURE_SIZE`. */
239
223
  readonly maxTextureSize: number;
240
224
  /** The lifetime GPU state made for this draw belongs to. Stable across the
@@ -275,11 +259,11 @@ interface PaintDrawKind {
275
259
  * `v_world` through `spaceInverse`, not from where `a_position` happens to
276
260
  * lie.
277
261
  */
278
- bind?(ctx: PaintBindContext, fill: FillStyle): PaintProgram | null;
262
+ bind?(ctx: PaintBindContext, fill: Paint): PaintProgram | null;
279
263
  /** Bounds frame → the frame the node is painted in. */
280
- inPoseFrame?(fill: FillStyle, box: FillPoseBox): FillStyle;
264
+ inPoseFrame?(fill: Paint, box: FillPoseBox): Paint;
281
265
  /** The inverse. Required whenever `inPoseFrame` is supplied. */
282
- toBoundsFrame?(fill: FillStyle, box: FillPoseBox): FillStyle;
266
+ toBoundsFrame?(fill: Paint, box: FillPoseBox): Paint;
283
267
  /**
284
268
  * Shader programs `bind` looks up through `ctx.program`, keyed by program
285
269
  * id; registering the kind registers them. Declaring them here rather than
@@ -327,4 +311,4 @@ declare function warmPaintKinds(kinds?: readonly string[]): Promise<void>;
327
311
  /** @internal Test helper — do not call from product code. */
328
312
  declare function _resetPaintDrawKindsForTests(): void;
329
313
 
330
- export { type FillPoseBox as F, type PaintBindContext as P, type ShaderProgramHandle as S, _resetPaintDrawKindsForTests as _, type ShaderUniform as a, ShaderProgram as b, type PaintDrawKind as c, type PaintDrawKindLoader as d, type PaintProgram as e, type PaintResources as f, type ProgramSource as g, ShaderCompileError as h, fillInPoseFrame as i, fillToBoundsFrame as j, getPaintDrawKind as k, getProgramSource as l, isPaintKindKnown as m, programSourceRegistry as n, registerPaintDrawKindLoader as o, paintDrawKindRegistry as p, registerProgram as q, registerPaintDrawKind as r, _resetProgramRegistryForTests as s, warmPaintKinds as w };
314
+ export { type FillPoseBox as F, type PaintBindContext as P, type ShaderProgramHandle as S, type UniformArray as U, _resetPaintDrawKindsForTests as _, type ShaderUniform as a, ShaderProgram as b, type PaintDrawKind as c, type PaintDrawKindLoader as d, type PaintProgram as e, type PaintResources as f, type ProgramSource as g, ShaderCompileError as h, type ShaderStage as i, fillInPoseFrame as j, fillToBoundsFrame as k, getPaintDrawKind as l, getProgramSource as m, isPaintKindKnown as n, programSourceRegistry as o, paintDrawKindRegistry as p, registerPaintDrawKindLoader as q, registerPaintDrawKind as r, registerProgram as s, _resetProgramRegistryForTests as t, warmPaintKinds as w };
@@ -56,20 +56,14 @@ declare function drawTileShapes(oc: CanvasRenderingContext2D, geometry: TileGeom
56
56
 
57
57
  /**
58
58
  * Built-in pattern factories — generic line/dot/scatter textures. Each
59
- * returns a `TextureHandle` you can wrap in a `FillStyle` of
60
- * `{ fill: 'pattern', pattern }` and pass to anything that accepts a `FillStyle`
61
- * (`FillStyle`-aware layers, path fills, etc.).
62
- * Imported from a subpath so consumers that ship their own catalog can
63
- * avoid the bundle cost.
59
+ * returns a `TextureHandle` you can wrap in a `Paint` of
60
+ * `{ kind: 'texture', texture }`. Imported from a subpath so consumers that
61
+ * ship their own catalog can avoid the bundle cost.
64
62
  *
65
- * All factories require an explicit `color` (and `bg` for `chunks`). No
66
- * defaults are baked in — the caller's design system is the source of truth
67
- * for palette decisions.
63
+ * All factories require an explicit `color`; no defaults are baked in.
68
64
  *
69
- * Each is a thin wrapper over `tileGeometry`, which describes the tile as
70
- * shapes so `@weasel-js/svg` can emit the same tile as a `<pattern>`.
71
- * Prefer naming a tile in a `TilePatternSpec` over calling these directly —
72
- * a spec serializes, a `TextureHandle` does not.
65
+ * Prefer a tile paint (`{ kind: 'hatch', color }`) over calling these directly —
66
+ * it serializes, a `TextureHandle` does not.
73
67
  */
74
68
 
75
69
  /** Diagonal hatch (forward slash). */
@@ -98,10 +92,8 @@ interface DotsParams {
98
92
  declare function dots(params: DotsParams): TextureHandle | null;
99
93
  /**
100
94
  * Random scatter of small ellipses on top of a solid background. Driven by a
101
- * seeded PRNG so the same `seed` produces a deterministic tile.
102
- *
103
- * Note: the `bg` field is optional — omit it for a transparent background
104
- * (the scatter shapes draw directly onto whatever sits beneath the overlay).
95
+ * seeded PRNG so the same `seed` produces a deterministic tile. Omit `bg` for
96
+ * a transparent background.
105
97
  */
106
98
  interface ChunksParams {
107
99
  color: string;
@@ -1,6 +1,6 @@
1
- import { createTilePattern } from './chunk-2VOBNWBN.js';
2
- import { tileGeometry, drawTileShapes, tileSize } from './chunk-ORBXHIRB.js';
3
- export { drawTileShapes, tileGeometry, tileSize } from './chunk-ORBXHIRB.js';
1
+ import { createTilePattern } from './chunk-43YL7HZV.js';
2
+ import { tileGeometry, drawTileShapes, tileSize } from './chunk-Q4SIHV7Q.js';
3
+ export { drawTileShapes, tileGeometry, tileSize } from './chunk-Q4SIHV7Q.js';
4
4
 
5
5
  // src/patterns/patterns-builtin.ts
6
6
  function build(spec) {
@@ -11,16 +11,16 @@ function build(spec) {
11
11
  });
12
12
  }
13
13
  function hatch(params) {
14
- return build({ tile: "hatch", ...params });
14
+ return build({ kind: "hatch", ...params });
15
15
  }
16
16
  function crosshatch(params) {
17
- return build({ tile: "crosshatch", ...params });
17
+ return build({ kind: "crosshatch", ...params });
18
18
  }
19
19
  function dots(params) {
20
- return build({ tile: "dots", ...params });
20
+ return build({ kind: "dots", ...params });
21
21
  }
22
22
  function chunks(params) {
23
- return build({ tile: "chunks", ...params });
23
+ return build({ kind: "chunks", ...params });
24
24
  }
25
25
 
26
26
  export { chunks, crosshatch, dots, hatch };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/patterns/patterns-builtin.ts"],"names":[],"mappings":";;;;;AA0BA,SAAS,MAAM,IAAA,EAA6C;AAC1D,EAAA,MAAM,QAAA,GAAW,aAAa,IAAI,CAAA;AAClC,EAAA,OAAO,iBAAA,CAAkB;AAAA,IACvB,IAAA,EAAM,SAAS,IAAI,CAAA;AAAA,IACnB,IAAA,EAAM,CAAC,EAAA,KAAO,cAAA,CAAe,IAAI,QAAQ;AAAA,GAC1C,CAAA;AACH;AAUO,SAAS,MAAM,MAAA,EAA2C;AAC/D,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,GAAG,QAAQ,CAAA;AAC3C;AAUO,SAAS,WAAW,MAAA,EAAgD;AACzE,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,YAAA,EAAc,GAAG,QAAQ,CAAA;AAChD;AAUO,SAAS,KAAK,MAAA,EAA0C;AAC7D,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,GAAG,QAAQ,CAAA;AAC1C;AAoBO,SAAS,OAAO,MAAA,EAA4C;AACjE,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,QAAA,EAAU,GAAG,QAAQ,CAAA;AAC5C","file":"patterns-builtin.js","sourcesContent":["/**\n * Built-in pattern factories — generic line/dot/scatter textures. Each\n * returns a `TextureHandle` you can wrap in a `FillStyle` of\n * `{ fill: 'pattern', pattern }` and pass to anything that accepts a `FillStyle`\n * (`FillStyle`-aware layers, path fills, etc.).\n * Imported from a subpath so consumers that ship their own catalog can\n * avoid the bundle cost.\n *\n * All factories require an explicit `color` (and `bg` for `chunks`). No\n * defaults are baked in — the caller's design system is the source of truth\n * for palette decisions.\n *\n * Each is a thin wrapper over `tileGeometry`, which describes the tile as\n * shapes so `@weasel-js/svg` can emit the same tile as a `<pattern>`.\n * Prefer naming a tile in a `TilePatternSpec` over calling these directly —\n * a spec serializes, a `TextureHandle` does not.\n */\n\nexport { tileGeometry, tileSize, drawTileShapes } from './tileGeometry';\nexport type { TileShape, TileGeometry } from './tileGeometry';\n\nimport { createTilePattern } from './';\nimport { tileGeometry, drawTileShapes, tileSize } from './tileGeometry';\nimport type { TilePatternSpec } from '@weasel-js/paint';\nimport type { TextureHandle } from '../textures/registerTexture';\n\nfunction build(spec: TilePatternSpec): TextureHandle | null {\n const geometry = tileGeometry(spec);\n return createTilePattern({\n size: tileSize(spec),\n draw: (oc) => drawTileShapes(oc, geometry),\n });\n}\n\n/** Diagonal hatch (forward slash). */\nexport interface HatchParams {\n color: string;\n size?: number;\n lineWidth?: number;\n}\n\n/** Build a diagonal-hatch pattern texture. */\nexport function hatch(params: HatchParams): TextureHandle | null {\n return build({ tile: 'hatch', ...params });\n}\n\n/** Diagonal hatch in both directions. */\nexport interface CrosshatchParams {\n color: string;\n size?: number;\n lineWidth?: number;\n}\n\n/** Build a crosshatch pattern texture. */\nexport function crosshatch(params: CrosshatchParams): TextureHandle | null {\n return build({ tile: 'crosshatch', ...params });\n}\n\n/** Regular grid of filled dots. */\nexport interface DotsParams {\n color: string;\n size?: number;\n radius?: number;\n}\n\n/** Build a dot-grid pattern texture. */\nexport function dots(params: DotsParams): TextureHandle | null {\n return build({ tile: 'dots', ...params });\n}\n\n/**\n * Random scatter of small ellipses on top of a solid background. Driven by a\n * seeded PRNG so the same `seed` produces a deterministic tile.\n *\n * Note: the `bg` field is optional — omit it for a transparent background\n * (the scatter shapes draw directly onto whatever sits beneath the overlay).\n */\nexport interface ChunksParams {\n color: string;\n bg?: string;\n size?: number;\n /** Coverage: `chunks * chunkSize²` per `size²` tile. Range ~[0, 1]. */\n density?: number;\n chunkSize?: number;\n seed?: number;\n}\n\n/** Build a seeded random-scatter pattern texture. */\nexport function chunks(params: ChunksParams): TextureHandle | null {\n return build({ tile: 'chunks', ...params });\n}\n"]}
1
+ {"version":3,"sources":["../src/patterns/patterns-builtin.ts"],"names":[],"mappings":";;;;;AAoBA,SAAS,MAAM,IAAA,EAA6C;AAC1D,EAAA,MAAM,QAAA,GAAW,aAAa,IAAI,CAAA;AAClC,EAAA,OAAO,iBAAA,CAAkB;AAAA,IACvB,IAAA,EAAM,SAAS,IAAI,CAAA;AAAA,IACnB,IAAA,EAAM,CAAC,EAAA,KAAO,cAAA,CAAe,IAAI,QAAQ;AAAA,GAC1C,CAAA;AACH;AAUO,SAAS,MAAM,MAAA,EAA2C;AAC/D,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,GAAG,QAAQ,CAAA;AAC3C;AAUO,SAAS,WAAW,MAAA,EAAgD;AACzE,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,YAAA,EAAc,GAAG,QAAQ,CAAA;AAChD;AAUO,SAAS,KAAK,MAAA,EAA0C;AAC7D,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,GAAG,QAAQ,CAAA;AAC1C;AAkBO,SAAS,OAAO,MAAA,EAA4C;AACjE,EAAA,OAAO,MAAM,EAAE,IAAA,EAAM,QAAA,EAAU,GAAG,QAAQ,CAAA;AAC5C","file":"patterns-builtin.js","sourcesContent":["/**\n * Built-in pattern factories — generic line/dot/scatter textures. Each\n * returns a `TextureHandle` you can wrap in a `Paint` of\n * `{ kind: 'texture', texture }`. Imported from a subpath so consumers that\n * ship their own catalog can avoid the bundle cost.\n *\n * All factories require an explicit `color`; no defaults are baked in.\n *\n * Prefer a tile paint (`{ kind: 'hatch', color }`) over calling these directly —\n * it serializes, a `TextureHandle` does not.\n */\n\nexport { tileGeometry, tileSize, drawTileShapes } from './tileGeometry';\nexport type { TileShape, TileGeometry } from './tileGeometry';\n\nimport { createTilePattern } from './';\nimport { tileGeometry, drawTileShapes, tileSize } from './tileGeometry';\nimport type { TilePatternSpec } from '@weasel-js/paint';\nimport type { TextureHandle } from '../textures/registerTexture';\n\nfunction build(spec: TilePatternSpec): TextureHandle | null {\n const geometry = tileGeometry(spec);\n return createTilePattern({\n size: tileSize(spec),\n draw: (oc) => drawTileShapes(oc, geometry),\n });\n}\n\n/** Diagonal hatch (forward slash). */\nexport interface HatchParams {\n color: string;\n size?: number;\n lineWidth?: number;\n}\n\n/** Build a diagonal-hatch pattern texture. */\nexport function hatch(params: HatchParams): TextureHandle | null {\n return build({ kind: 'hatch', ...params });\n}\n\n/** Diagonal hatch in both directions. */\nexport interface CrosshatchParams {\n color: string;\n size?: number;\n lineWidth?: number;\n}\n\n/** Build a crosshatch pattern texture. */\nexport function crosshatch(params: CrosshatchParams): TextureHandle | null {\n return build({ kind: 'crosshatch', ...params });\n}\n\n/** Regular grid of filled dots. */\nexport interface DotsParams {\n color: string;\n size?: number;\n radius?: number;\n}\n\n/** Build a dot-grid pattern texture. */\nexport function dots(params: DotsParams): TextureHandle | null {\n return build({ kind: 'dots', ...params });\n}\n\n/**\n * Random scatter of small ellipses on top of a solid background. Driven by a\n * seeded PRNG so the same `seed` produces a deterministic tile. Omit `bg` for\n * a transparent background.\n */\nexport interface ChunksParams {\n color: string;\n bg?: string;\n size?: number;\n /** Coverage: `chunks * chunkSize²` per `size²` tile. Range ~[0, 1]. */\n density?: number;\n chunkSize?: number;\n seed?: number;\n}\n\n/** Build a seeded random-scatter pattern texture. */\nexport function chunks(params: ChunksParams): TextureHandle | null {\n return build({ kind: 'chunks', ...params });\n}\n"]}
@@ -1,21 +1,15 @@
1
1
  import { Path } from '@weasel-js/geom';
2
2
  import { Mesh } from '@weasel-js/geom/tessellate';
3
- export { F as FLOATS_PER_VERTEX, _ as __setImageLoaderForTests, k as _resetImageCacheForTests, l as _resetPatternSpecCacheForTests, m as _resetTextureRegistryForTests } from './imageCache-oJVgxhkr.js';
4
- export { _ as _resetPaintDrawKindsForTests, s as _resetProgramRegistryForTests } from './paintKinds-B2vEy9A4.js';
3
+ export { F as FLOATS_PER_VERTEX, _ as __setImageLoaderForTests, k as _resetImageCacheForTests, l as _resetPatternSpecCacheForTests, m as _resetTextureRegistryForTests } from './imageCache-DYcWftY8.js';
4
+ export { _ as _resetPaintDrawKindsForTests, t as _resetProgramRegistryForTests } from './paintKinds-BmPAT51k.js';
5
5
  import '@weasel-js/paint';
6
6
  import '@weasel-js/registry';
7
7
 
8
8
  /**
9
- * Return the tessellated Mesh for `path`, computing and caching on first call.
10
- * Cached by Path identity (WeakMap) — different Path objects with the same
11
- * coords are distinct cache entries.
12
- *
13
- * A rect path the batch stages as a quad never reaches this cache. One used
14
- * as a clip, or filled with a pattern or vertex colors, does.
15
- *
16
- * Also bypassed whenever `DrawContext.flattenTolerance` is set: `fillMeshHandle`
17
- * (see `../draw`) routes custom-tolerance fills through a fresh transient
18
- * tessellation instead of this cache.
9
+ * The tessellated Mesh for `path`, cached by Path identity — different Path
10
+ * objects with the same coords are distinct cache entries. A mesh built under
11
+ * another default open-fill rule is rebuilt. Bypassed whenever
12
+ * `DrawContext.flattenTolerance` is set.
19
13
  */
20
14
  declare function getMesh(path: Path): Mesh;
21
15
  /** Test helper. Do not call from product code. */
@@ -39,66 +33,31 @@ declare function _resetCacheForTests(): void;
39
33
  declare function _resetMarkerCommandCacheForTests(): void;
40
34
 
41
35
  /**
42
- * Tessellated glyph outlines, in em space.
43
- *
44
- * This is the half of the outline text tier that is expensive: parsing `d`
45
- * into a path and running earcut over it costs far more than the per-instance
46
- * transform that follows. It is also the half that does not vary — em space
47
- * is unit-scale and baseline-relative, so the same glyph of the same face is
48
- * the same triangles at every size, position and zoom. Tessellate once, keep
49
- * it, and pay only a scale-and-translate per occurrence.
50
- *
51
- * That is why the renderer transforms cached vertices on the CPU and appends
52
- * them into one shared buffer rather than giving each glyph its own model
53
- * matrix: a per-glyph matrix would mean a draw call per glyph, throwing away
54
- * the batching the atlas tier already has, in exchange for saving an
55
- * arithmetic pass over a few hundred vertices.
56
- *
57
- * ### Flatten tolerance
36
+ * Tessellated glyph outlines, in em space. Em space is unit-scale and
37
+ * baseline-relative, so the same glyph of the same face is the same triangles
38
+ * at every size, position, and zoom: tessellate once and pay only a
39
+ * scale-and-translate per occurrence.
58
40
  *
59
- * Tessellating once means committing to a curve tolerance once, and em space
60
- * makes that a clean tradeoff rather than a guess: an error of `T` em shows
61
- * up as `T × fontSize × viewScale` screen pixels. At 1/4096 em, a glyph would
62
- * have to reach ~4000 screen pixels tall before the flattening error covered
63
- * a single pixel — well past any size a document is read at, and far enough
64
- * past the tier's own threshold that the two never interact.
41
+ * Flatten tolerance: an error of `T` em shows up as `T × fontSize × viewScale`
42
+ * screen pixels. At 1/4096 em, a glyph would have to reach ~4000 screen pixels
43
+ * tall before the flattening error covered a single pixel.
65
44
  *
66
- * ### Bound
67
- *
68
- * A plain `Map` with a cap and wholesale eviction. Glyph tessellations are
69
- * small (a few hundred vertices) and the working set is a charset, not a
70
- * document — the cap exists so a page that cycles through many faces cannot
71
- * grow without limit, not because pressure is expected. Wholesale rather than
72
- * LRU because the refill cost is bounded by what is actually on screen, and
73
- * an LRU's bookkeeping would cost more than the misses it avoids.
45
+ * Eviction is wholesale rather than LRU because the refill cost is bounded by
46
+ * what is actually on screen.
74
47
  */
75
48
 
76
49
  /** Test helper. Do not call from product code. */
77
50
  declare function _resetOutlineMeshCacheForTests(): void;
78
51
 
79
52
  /**
80
- * Tessellated glyph *outlines' strokes*, in em space.
81
- *
82
- * The sibling of `outlineMeshCache`, and cached for the same reason: parsing
83
- * `d` and tessellating a ribbon costs far more than the per-instance
84
- * transform that follows, and em space is size- and position-independent, so
85
- * one tessellation serves every occurrence of that glyph.
86
- *
87
- * ### Why the width is in the key
88
- *
89
- * A fill mesh is the same triangles at every size. A stroke's is not — the
90
- * ribbon's width relative to the glyph decides the geometry, so the em-space
91
- * mesh depends on `strokeWidth / fontSize`. That ratio is a property of the
92
- * run's style, not of the frame, so it is stable across frames and makes a
93
- * perfectly good cache key; what it is *not* is free, so a document that
94
- * animates stroke width or font size will miss every frame. That is the same
95
- * bargain `tessellate` makes everywhere else, and it is the reason the width
96
- * is quantized before it reaches the key: neighbouring sub-quantum widths
97
- * would otherwise each get their own entry and evict each other.
53
+ * Tessellated glyph *outlines' strokes*, in em space: the sibling of
54
+ * `outlineMeshCache`.
98
55
  *
99
- * Joins, caps, miter limit, alignment and dash all change the ribbon too, so
100
- * they are in the key as well — assembled by the caller, which already has
101
- * the `Stroke` in hand.
56
+ * A stroke's em-space mesh depends on `strokeWidth / fontSize`, so the width is
57
+ * in the key, and a document that animates stroke width or font size will miss
58
+ * every frame. The width is quantized before it reaches the key: neighboring
59
+ * sub-quantum widths would otherwise each get their own entry and evict each
60
+ * other.
102
61
  */
103
62
 
104
63
  /** Test helper. Do not call from product code. */
@@ -1,4 +1,4 @@
1
- export { FLOATS_PER_VERTEX, __setImageLoaderForTests, _resetCacheForTests, _resetImageCacheForTests, _resetMarkerCommandCacheForTests, _resetOutlineMeshCacheForTests, _resetOutlineStrokeMeshCacheForTests, _resetPaintDrawKindsForTests, _resetPatternSpecCacheForTests, _resetProgramRegistryForTests, _resetStrokeMeshCacheForTests, getMesh } from './chunk-VHQJ56XQ.js';
2
- export { _resetTextureRegistryForTests } from './chunk-ORBXHIRB.js';
1
+ export { FLOATS_PER_VERTEX, __setImageLoaderForTests, _resetCacheForTests, _resetImageCacheForTests, _resetMarkerCommandCacheForTests, _resetOutlineMeshCacheForTests, _resetOutlineStrokeMeshCacheForTests, _resetPaintDrawKindsForTests, _resetPatternSpecCacheForTests, _resetProgramRegistryForTests, _resetStrokeMeshCacheForTests, getMesh } from './chunk-LFD47OV2.js';
2
+ export { _resetTextureRegistryForTests } from './chunk-Q4SIHV7Q.js';
3
3
  //# sourceMappingURL=test-seams.js.map
4
4
  //# sourceMappingURL=test-seams.js.map
@@ -4,11 +4,8 @@
4
4
  * Records every method call as { name, args, result } so tests can assert
5
5
  * against the call sequence. Returns synthetic handles for object creation
6
6
  * (createShader/Program/Buffer/etc.); reports truthy for *Parameter status
7
- * queries (i.e. assumes shader/program compilation succeeded).
8
- *
9
- * NOT a renderer correctness check — pixel-level correctness is verified
10
- * end-to-end by the smoke test in dev/smoke.spec.ts. This recorder catches
11
- * "did the dispatcher emit the right call sequence" bugs only.
7
+ * queries (i.e. assumes shader/program compilation succeeded). NOT a renderer
8
+ * correctness check: it sees the call sequence, never a pixel.
12
9
  */
13
10
  interface GLCall {
14
11
  readonly name: string;
@@ -21,11 +21,14 @@ var GL_CONSTANTS = {
21
21
  STENCIL_TEST: 2960,
22
22
  CULL_FACE: 2884,
23
23
  UNPACK_FLIP_Y_WEBGL: 37440,
24
+ UNPACK_PREMULTIPLY_ALPHA_WEBGL: 37441,
24
25
  SCISSOR_TEST: 3089,
25
26
  MAX_TEXTURE_SIZE: 3379,
26
27
  // Blend factors
27
28
  SRC_ALPHA: 770,
28
29
  ONE_MINUS_SRC_ALPHA: 771,
30
+ DST_ALPHA: 772,
31
+ ONE_MINUS_DST_ALPHA: 773,
29
32
  ZERO: 0,
30
33
  ONE: 1,
31
34
  // Buffer bits
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/glRecorder.ts"],"names":[],"mappings":"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","file":"glRecorder.js","sourcesContent":["/**\n * Proxy-based recording WebGL2 context for unit tests.\n *\n * Records every method call as { name, args, result } so tests can assert\n * against the call sequence. Returns synthetic handles for object creation\n * (createShader/Program/Buffer/etc.); reports truthy for *Parameter status\n * queries (i.e. assumes shader/program compilation succeeded).\n *\n * NOT a renderer correctness check — pixel-level correctness is verified\n * end-to-end by the smoke test in dev/smoke.spec.ts. This recorder catches\n * \"did the dispatcher emit the right call sequence\" bugs only.\n */\n\nexport interface GLCall {\n readonly name: string;\n readonly args: readonly unknown[];\n readonly result: unknown;\n}\n\n/** What the recorded context reports about itself. */\nexport interface GLRecorderOptions {\n /** Reported as `gl.drawingBufferWidth`. Default 800. */\n drawingBufferWidth?: number;\n /** Reported as `gl.drawingBufferHeight`. Default 600. */\n drawingBufferHeight?: number;\n /** Returned by `gl.getContextAttributes()`. Default `{ stencil: true }`. */\n contextAttributes?: Partial<WebGLContextAttributes>;\n}\n\n/** A stand-in context and the calls made on it, oldest first. */\nexport interface GLRecorder {\n readonly gl: WebGL2RenderingContext;\n readonly calls: GLCall[];\n reset(): void;\n}\n\nconst GL_CONSTANTS: Readonly<Record<string, number>> = {\n // Shader / program\n VERTEX_SHADER: 0x8B31,\n FRAGMENT_SHADER: 0x8B30,\n COMPILE_STATUS: 0x8B81,\n LINK_STATUS: 0x8B82,\n // Buffer targets\n ARRAY_BUFFER: 0x8892,\n ELEMENT_ARRAY_BUFFER: 0x8893,\n STATIC_DRAW: 0x88E4,\n // Types\n FLOAT: 0x1406,\n UNSIGNED_INT: 0x1405,\n // Primitives\n TRIANGLES: 0x0004,\n // Errors / state\n NO_ERROR: 0,\n DEPTH_TEST: 0x0B71,\n BLEND: 0x0BE2,\n STENCIL_TEST: 0x0B90,\n CULL_FACE: 0x0B44,\n UNPACK_FLIP_Y_WEBGL: 0x9240,\n SCISSOR_TEST: 0x0C11,\n MAX_TEXTURE_SIZE: 0x0D33,\n // Blend factors\n SRC_ALPHA: 0x0302,\n ONE_MINUS_SRC_ALPHA: 0x0303,\n ZERO: 0,\n ONE: 1,\n // Buffer bits\n COLOR_BUFFER_BIT: 0x00004000,\n STENCIL_BUFFER_BIT: 0x00000400,\n DEPTH_BUFFER_BIT: 0x00000100,\n // Stencil ops\n KEEP: 0x1E00,\n REPLACE: 0x1E01,\n INVERT: 0x150A,\n // Compare funcs\n ALWAYS: 0x0207,\n NEVER: 0x0200,\n EQUAL: 0x0202,\n NOTEQUAL: 0x0205,\n // Color masks\n FRONT_AND_BACK: 0x0408,\n // Misc constants used in step 1\n COLOR_WRITEMASK: 0x0C23,\n // Textures (step 3)\n TEXTURE_2D: 0x0DE1,\n TEXTURE0: 0x84C0,\n RGBA: 0x1908,\n UNSIGNED_BYTE: 0x1401,\n TEXTURE_MIN_FILTER: 0x2801,\n TEXTURE_MAG_FILTER: 0x2800,\n LINEAR: 0x2601,\n NEAREST: 0x2600,\n TEXTURE_WRAP_S: 0x2802,\n TEXTURE_WRAP_T: 0x2803,\n CLAMP_TO_EDGE: 0x812F,\n // Wrap modes (step 4)\n REPEAT: 0x2901,\n MIRRORED_REPEAT: 0x8370,\n // Mipmap minification filter\n LINEAR_MIPMAP_LINEAR: 0x2703,\n // Framebuffers and renderbuffers (offscreen effect passes). Every one of\n // these has to be here before a test names it: an absent ALL-CAPS property\n // reads as 0 through the proxy, so `expect(args[0]).toBe(gl.FRAMEBUFFER)`\n // would compare 0 against 0 and pass over a renderer that bound nothing.\n FRAMEBUFFER: 0x8D40,\n RENDERBUFFER: 0x8D41,\n FRAMEBUFFER_BINDING: 0x8CA6,\n RENDERBUFFER_BINDING: 0x8CA7,\n COLOR_ATTACHMENT0: 0x8CE0,\n DEPTH_STENCIL_ATTACHMENT: 0x821A,\n DEPTH24_STENCIL8: 0x88F0,\n RGBA8: 0x8058,\n FRAMEBUFFER_COMPLETE: 0x8CD5,\n};\n\nfunction syntheticHandle(name: string, seq: number): { __id: string } {\n return { __id: `${name}_${seq}` };\n}\n\n/**\n * Copy typed-array arguments as they are recorded.\n *\n * A caller is entitled to reuse the array it uploads from — the text ring\n * packs every draw through one scratch buffer — so storing the reference\n * records a value that later frames overwrite. A test reading it back then\n * compares two frames and sees the same numbers twice, which passes.\n */\nfunction snapshot(value: unknown): unknown {\n return ArrayBuffer.isView(value) && !(value instanceof DataView)\n ? (value as unknown as { slice(): unknown }).slice()\n : value;\n}\n\n/** A WebGL2 stand-in that records every call and draws nothing. */\nexport function makeGLRecorder(options: GLRecorderOptions = {}): GLRecorder {\n const calls: GLCall[] = [];\n const {\n drawingBufferWidth = 800,\n drawingBufferHeight = 600,\n contextAttributes = { stencil: true },\n } = options;\n\n // Read as data, not called. The Proxy below hands back a recording function\n // for every lowercase property, so without this `drawingBufferHeight` is a\n // function and every y-flip computed from it is NaN — silently.\n const DATA_PROPERTIES: Readonly<Record<string, unknown>> = {\n drawingBufferWidth,\n drawingBufferHeight,\n };\n\n const handler = (name: string) => (...args: unknown[]) => {\n let result: unknown = undefined;\n switch (name) {\n case 'createShader':\n case 'createProgram':\n case 'createBuffer':\n case 'createVertexArray':\n case 'createTexture':\n case 'createFramebuffer':\n case 'createRenderbuffer':\n result = syntheticHandle(name, calls.length);\n break;\n case 'getUniformLocation':\n case 'getAttribLocation':\n result = calls.length;\n break;\n case 'getShaderParameter':\n case 'getProgramParameter':\n result = true;\n break;\n case 'getShaderInfoLog':\n case 'getProgramInfoLog':\n result = '';\n break;\n case 'getContextAttributes':\n result = { ...contextAttributes };\n break;\n case 'getError':\n result = 0;\n break;\n default:\n result = undefined;\n }\n calls.push({ name, args: args.map(snapshot), result });\n return result;\n };\n\n const gl = new Proxy(\n {},\n {\n get(_, prop: string | symbol) {\n if (typeof prop !== 'string') return undefined;\n if (prop in DATA_PROPERTIES) return DATA_PROPERTIES[prop];\n if (prop in GL_CONSTANTS) return GL_CONSTANTS[prop];\n if (/^[A-Z_0-9]+$/.test(prop)) return 0; // unknown all-caps constant → 0\n return handler(prop);\n },\n },\n ) as unknown as WebGL2RenderingContext;\n\n return {\n gl,\n calls,\n reset() {\n calls.length = 0;\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/testing/glRecorder.ts"],"names":[],"mappings":"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","file":"glRecorder.js","sourcesContent":["/**\n * Proxy-based recording WebGL2 context for unit tests.\n *\n * Records every method call as { name, args, result } so tests can assert\n * against the call sequence. Returns synthetic handles for object creation\n * (createShader/Program/Buffer/etc.); reports truthy for *Parameter status\n * queries (i.e. assumes shader/program compilation succeeded). NOT a renderer\n * correctness check: it sees the call sequence, never a pixel.\n */\n\nexport interface GLCall {\n readonly name: string;\n readonly args: readonly unknown[];\n readonly result: unknown;\n}\n\n/** What the recorded context reports about itself. */\nexport interface GLRecorderOptions {\n /** Reported as `gl.drawingBufferWidth`. Default 800. */\n drawingBufferWidth?: number;\n /** Reported as `gl.drawingBufferHeight`. Default 600. */\n drawingBufferHeight?: number;\n /** Returned by `gl.getContextAttributes()`. Default `{ stencil: true }`. */\n contextAttributes?: Partial<WebGLContextAttributes>;\n}\n\n/** A stand-in context and the calls made on it, oldest first. */\nexport interface GLRecorder {\n readonly gl: WebGL2RenderingContext;\n readonly calls: GLCall[];\n reset(): void;\n}\n\nconst GL_CONSTANTS: Readonly<Record<string, number>> = {\n // Shader / program\n VERTEX_SHADER: 0x8B31,\n FRAGMENT_SHADER: 0x8B30,\n COMPILE_STATUS: 0x8B81,\n LINK_STATUS: 0x8B82,\n // Buffer targets\n ARRAY_BUFFER: 0x8892,\n ELEMENT_ARRAY_BUFFER: 0x8893,\n STATIC_DRAW: 0x88E4,\n // Types\n FLOAT: 0x1406,\n UNSIGNED_INT: 0x1405,\n // Primitives\n TRIANGLES: 0x0004,\n // Errors / state\n NO_ERROR: 0,\n DEPTH_TEST: 0x0B71,\n BLEND: 0x0BE2,\n STENCIL_TEST: 0x0B90,\n CULL_FACE: 0x0B44,\n UNPACK_FLIP_Y_WEBGL: 0x9240,\n UNPACK_PREMULTIPLY_ALPHA_WEBGL: 0x9241,\n SCISSOR_TEST: 0x0C11,\n MAX_TEXTURE_SIZE: 0x0D33,\n // Blend factors\n SRC_ALPHA: 0x0302,\n ONE_MINUS_SRC_ALPHA: 0x0303,\n DST_ALPHA: 0x0304,\n ONE_MINUS_DST_ALPHA: 0x0305,\n ZERO: 0,\n ONE: 1,\n // Buffer bits\n COLOR_BUFFER_BIT: 0x00004000,\n STENCIL_BUFFER_BIT: 0x00000400,\n DEPTH_BUFFER_BIT: 0x00000100,\n // Stencil ops\n KEEP: 0x1E00,\n REPLACE: 0x1E01,\n INVERT: 0x150A,\n // Compare funcs\n ALWAYS: 0x0207,\n NEVER: 0x0200,\n EQUAL: 0x0202,\n NOTEQUAL: 0x0205,\n // Color masks\n FRONT_AND_BACK: 0x0408,\n // Misc constants used in step 1\n COLOR_WRITEMASK: 0x0C23,\n // Textures (step 3)\n TEXTURE_2D: 0x0DE1,\n TEXTURE0: 0x84C0,\n RGBA: 0x1908,\n UNSIGNED_BYTE: 0x1401,\n TEXTURE_MIN_FILTER: 0x2801,\n TEXTURE_MAG_FILTER: 0x2800,\n LINEAR: 0x2601,\n NEAREST: 0x2600,\n TEXTURE_WRAP_S: 0x2802,\n TEXTURE_WRAP_T: 0x2803,\n CLAMP_TO_EDGE: 0x812F,\n // Wrap modes (step 4)\n REPEAT: 0x2901,\n MIRRORED_REPEAT: 0x8370,\n // Mipmap minification filter\n LINEAR_MIPMAP_LINEAR: 0x2703,\n // Framebuffers and renderbuffers (offscreen effect passes). Every one of\n // these has to be here before a test names it: an absent ALL-CAPS property\n // reads as 0 through the proxy, so `expect(args[0]).toBe(gl.FRAMEBUFFER)`\n // would compare 0 against 0 and pass over a renderer that bound nothing.\n FRAMEBUFFER: 0x8D40,\n RENDERBUFFER: 0x8D41,\n FRAMEBUFFER_BINDING: 0x8CA6,\n RENDERBUFFER_BINDING: 0x8CA7,\n COLOR_ATTACHMENT0: 0x8CE0,\n DEPTH_STENCIL_ATTACHMENT: 0x821A,\n DEPTH24_STENCIL8: 0x88F0,\n RGBA8: 0x8058,\n FRAMEBUFFER_COMPLETE: 0x8CD5,\n};\n\nfunction syntheticHandle(name: string, seq: number): { __id: string } {\n return { __id: `${name}_${seq}` };\n}\n\n/**\n * Copy typed-array arguments as they are recorded.\n *\n * A caller is entitled to reuse the array it uploads from — the text ring\n * packs every draw through one scratch buffer — so storing the reference\n * records a value that later frames overwrite. A test reading it back then\n * compares two frames and sees the same numbers twice, which passes.\n */\nfunction snapshot(value: unknown): unknown {\n return ArrayBuffer.isView(value) && !(value instanceof DataView)\n ? (value as unknown as { slice(): unknown }).slice()\n : value;\n}\n\n/** A WebGL2 stand-in that records every call and draws nothing. */\nexport function makeGLRecorder(options: GLRecorderOptions = {}): GLRecorder {\n const calls: GLCall[] = [];\n const {\n drawingBufferWidth = 800,\n drawingBufferHeight = 600,\n contextAttributes = { stencil: true },\n } = options;\n\n // Read as data, not called. The Proxy below hands back a recording function\n // for every lowercase property, so without this `drawingBufferHeight` is a\n // function and every y-flip computed from it is NaN — silently.\n const DATA_PROPERTIES: Readonly<Record<string, unknown>> = {\n drawingBufferWidth,\n drawingBufferHeight,\n };\n\n const handler = (name: string) => (...args: unknown[]) => {\n let result: unknown = undefined;\n switch (name) {\n case 'createShader':\n case 'createProgram':\n case 'createBuffer':\n case 'createVertexArray':\n case 'createTexture':\n case 'createFramebuffer':\n case 'createRenderbuffer':\n result = syntheticHandle(name, calls.length);\n break;\n case 'getUniformLocation':\n case 'getAttribLocation':\n result = calls.length;\n break;\n case 'getShaderParameter':\n case 'getProgramParameter':\n result = true;\n break;\n case 'getShaderInfoLog':\n case 'getProgramInfoLog':\n result = '';\n break;\n case 'getContextAttributes':\n result = { ...contextAttributes };\n break;\n case 'getError':\n result = 0;\n break;\n default:\n result = undefined;\n }\n calls.push({ name, args: args.map(snapshot), result });\n return result;\n };\n\n const gl = new Proxy(\n {},\n {\n get(_, prop: string | symbol) {\n if (typeof prop !== 'string') return undefined;\n if (prop in DATA_PROPERTIES) return DATA_PROPERTIES[prop];\n if (prop in GL_CONSTANTS) return GL_CONSTANTS[prop];\n if (/^[A-Z_0-9]+$/.test(prop)) return 0; // unknown all-caps constant → 0\n return handler(prop);\n },\n },\n ) as unknown as WebGL2RenderingContext;\n\n return {\n gl,\n calls,\n reset() {\n calls.length = 0;\n },\n };\n}\n"]}
@@ -5,12 +5,10 @@ import { GLRecorder } from './glRecorder.js';
5
5
  * floats-per-vertex taken from the `vertexAttribPointer` recorded for that same
6
6
  * buffer object.
7
7
  *
8
- * Pairing an upload with the pointer call next to it used to work because every
9
- * draw minted its own VAO and buffers. The text paths now take a slot from a
10
- * ring that configured its attributes once, so a frame after the first records
11
- * no pointer calls at all — hence a tracker that remembers what it has learned
12
- * rather than a function over one frame's calls. Ask it for `uploads()` after
13
- * each frame and it carries the strides across `recorder.reset()`.
8
+ * A ring slot configures its attributes once, so a frame after the first
9
+ * records no pointer calls at all — hence a tracker that remembers what it has
10
+ * learned. Ask it for `uploads()` after each frame and it carries the strides
11
+ * across `recorder.reset()`.
14
12
  */
15
13
 
16
14
  /** One vertex buffer upload. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/vertexUploads.ts"],"names":[],"mappings":";AAeA,IAAM,YAAA,GAAe,KAAA;AAwBrB,SAAS,QAAQ,IAAA,EAAmC;AAClD,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,KAAS,YAAA,GAAe,IAAA,CAAK,KAAK,CAAC,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA;AACpE,EAAA,IAAI,EAAE,IAAA,YAAgB,YAAA,CAAA,EAAe,OAAO,IAAA;AAC5C,EAAA,IAAI,IAAA,CAAK,IAAA,KAAS,eAAA,EAAiB,OAAO,IAAA;AAC1C,EAAA,MAAM,SAAA,GAAY,OAAO,IAAA,CAAK,IAAA,CAAK,CAAC,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,GAAI,CAAA;AACpE,EAAA,MAAM,MAAA,GAAS,OAAO,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,GAAI,IAAA,CAAK,MAAA,GAAS,SAAA;AAC/E,EAAA,OAAO,IAAA,CAAK,QAAA,CAAS,SAAA,EAAW,SAAA,GAAY,MAAM,CAAA;AACpD;AAGO,SAAS,kBAAkB,QAAA,EAAqC;AAErE,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAqB;AAEzC,EAAA,OAAO;AAAA,IACL,OAAA,GAA0B;AACxB,MAAA,MAAM,MAAsB,EAAC;AAC7B,MAAA,MAAM,UAAqD,EAAC;AAC5D,MAAA,IAAI,KAAA,GAAiB,IAAA;AAIrB,MAAA,KAAA,MAAW,IAAA,IAAQ,SAAS,KAAA,EAAO;AACjC,QAAA,IAAI,IAAA,CAAK,SAAS,YAAA,EAAc;AAC9B,UAAA,IAAI,IAAA,CAAK,KAAK,CAAC,CAAA,KAAM,cAAc,KAAA,GAAQ,IAAA,CAAK,KAAK,CAAC,CAAA;AACtD,UAAA;AAAA,QACF;AACA,QAAA,IAAI,IAAA,CAAK,SAAS,qBAAA,EAAuB;AAEvC,UAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA;AACzB,UAAA,OAAA,CAAQ,IAAI,KAAA,EAAO,KAAA,KAAU,CAAA,GAAI,CAAA,GAAI,QAAQ,CAAC,CAAA;AAC9C,UAAA;AAAA,QACF;AACA,QAAA,IAAI,IAAA,CAAK,IAAA,KAAS,YAAA,IAAgB,IAAA,CAAK,SAAS,eAAA,EAAiB;AACjE,QAAA,MAAM,IAAA,GAAO,QAAQ,IAAI,CAAA;AACzB,QAAA,IAAI,MAAM,OAAA,CAAQ,IAAA,CAAK,EAAE,MAAA,EAAQ,KAAA,EAAO,MAAM,CAAA;AAAA,MAChD;AAEA,MAAA,KAAA,MAAW,EAAE,MAAA,EAAQ,IAAA,EAAK,IAAK,OAAA,EAAS;AACtC,QAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA;AACjC,QAAA,IAAI,WAAW,MAAA,EAAW;AACxB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WAGF;AAAA,QACF;AACA,QAAA,GAAA,CAAI,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAA,EAAM,CAAA;AAAA,MAC3B;AACA,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,GACF;AACF","file":"vertexUploads.js","sourcesContent":["/**\n * Read vertex geometry back out of a `GLRecorder`, with each upload's\n * floats-per-vertex taken from the `vertexAttribPointer` recorded for that same\n * buffer object.\n *\n * Pairing an upload with the pointer call next to it used to work because every\n * draw minted its own VAO and buffers. The text paths now take a slot from a\n * ring that configured its attributes once, so a frame after the first records\n * no pointer calls at all — hence a tracker that remembers what it has learned\n * rather than a function over one frame's calls. Ask it for `uploads()` after\n * each frame and it carries the strides across `recorder.reset()`.\n */\n\nimport type { GLCall, GLRecorder } from './glRecorder';\n\nconst ARRAY_BUFFER = 0x8892;\n\n/** One vertex buffer upload. */\nexport interface VertexUpload {\n /** Floats per vertex. */\n stride: number;\n data: Float32Array;\n}\n\n/** Reads vertex uploads out of a recorder, remembering each buffer's stride. */\nexport interface UploadTracker {\n /** Every vertex upload recorded since the last `recorder.reset()`. */\n uploads(): VertexUpload[];\n}\n\n/**\n * The floats an upload actually sent.\n *\n * `bufferSubData(target, offset, src, srcOffset, length)` hands GL a *window*\n * into a scratch array the batch reuses across frames, so the recorded\n * argument is the whole scratch — the tail of it being whatever the last, and\n * possibly longer, run left there. Reading it whole compares live vertices\n * against stale ones.\n */\nfunction payload(call: GLCall): Float32Array | null {\n const data = call.name === 'bufferData' ? call.args[1] : call.args[2];\n if (!(data instanceof Float32Array)) return null;\n if (call.name !== 'bufferSubData') return data;\n const srcOffset = typeof call.args[3] === 'number' ? call.args[3] : 0;\n const length = typeof call.args[4] === 'number' ? call.args[4] : data.length - srcOffset;\n return data.subarray(srcOffset, srcOffset + length);\n}\n\n/** A tracker over `recorder`; keep one across frames. */\nexport function makeUploadTracker(recorder: GLRecorder): UploadTracker {\n // Keyed on the recorder's synthetic buffer handle, which is a stable object.\n const strides = new Map<unknown, number>();\n\n return {\n uploads(): VertexUpload[] {\n const out: VertexUpload[] = [];\n const pending: { buffer: unknown; data: Float32Array }[] = [];\n let bound: unknown = null;\n\n // Two passes: a buffer's attribute layout can be recorded either before\n // its first upload (the ring) or after it (every other draw path).\n for (const call of recorder.calls) {\n if (call.name === 'bindBuffer') {\n if (call.args[0] === ARRAY_BUFFER) bound = call.args[1];\n continue;\n }\n if (call.name === 'vertexAttribPointer') {\n // Byte stride; 0 means tightly packed, which for a_position is a vec2.\n const bytes = call.args[4] as number;\n strides.set(bound, bytes === 0 ? 2 : bytes / 4);\n continue;\n }\n if (call.name !== 'bufferData' && call.name !== 'bufferSubData') continue;\n const data = payload(call);\n if (data) pending.push({ buffer: bound, data });\n }\n\n for (const { buffer, data } of pending) {\n const stride = strides.get(buffer);\n if (stride === undefined) {\n throw new Error(\n 'vertexUploads: a vertex buffer was written with no vertexAttribPointer ' +\n 'ever recorded for it — call uploads() on the frame that creates the ' +\n 'buffer, before resetting the recorder.',\n );\n }\n out.push({ stride, data });\n }\n return out;\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/testing/vertexUploads.ts"],"names":[],"mappings":";AAaA,IAAM,YAAA,GAAe,KAAA;AAqBrB,SAAS,QAAQ,IAAA,EAAmC;AAClD,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,KAAS,YAAA,GAAe,IAAA,CAAK,KAAK,CAAC,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA;AACpE,EAAA,IAAI,EAAE,IAAA,YAAgB,YAAA,CAAA,EAAe,OAAO,IAAA;AAC5C,EAAA,IAAI,IAAA,CAAK,IAAA,KAAS,eAAA,EAAiB,OAAO,IAAA;AAC1C,EAAA,MAAM,SAAA,GAAY,OAAO,IAAA,CAAK,IAAA,CAAK,CAAC,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,GAAI,CAAA;AACpE,EAAA,MAAM,MAAA,GAAS,OAAO,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,GAAI,IAAA,CAAK,MAAA,GAAS,SAAA;AAC/E,EAAA,OAAO,IAAA,CAAK,QAAA,CAAS,SAAA,EAAW,SAAA,GAAY,MAAM,CAAA;AACpD;AAGO,SAAS,kBAAkB,QAAA,EAAqC;AAErE,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAqB;AAEzC,EAAA,OAAO;AAAA,IACL,OAAA,GAA0B;AACxB,MAAA,MAAM,MAAsB,EAAC;AAC7B,MAAA,MAAM,UAAqD,EAAC;AAC5D,MAAA,IAAI,KAAA,GAAiB,IAAA;AAIrB,MAAA,KAAA,MAAW,IAAA,IAAQ,SAAS,KAAA,EAAO;AACjC,QAAA,IAAI,IAAA,CAAK,SAAS,YAAA,EAAc;AAC9B,UAAA,IAAI,IAAA,CAAK,KAAK,CAAC,CAAA,KAAM,cAAc,KAAA,GAAQ,IAAA,CAAK,KAAK,CAAC,CAAA;AACtD,UAAA;AAAA,QACF;AACA,QAAA,IAAI,IAAA,CAAK,SAAS,qBAAA,EAAuB;AAEvC,UAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA;AACzB,UAAA,OAAA,CAAQ,IAAI,KAAA,EAAO,KAAA,KAAU,CAAA,GAAI,CAAA,GAAI,QAAQ,CAAC,CAAA;AAC9C,UAAA;AAAA,QACF;AACA,QAAA,IAAI,IAAA,CAAK,IAAA,KAAS,YAAA,IAAgB,IAAA,CAAK,SAAS,eAAA,EAAiB;AACjE,QAAA,MAAM,IAAA,GAAO,QAAQ,IAAI,CAAA;AACzB,QAAA,IAAI,MAAM,OAAA,CAAQ,IAAA,CAAK,EAAE,MAAA,EAAQ,KAAA,EAAO,MAAM,CAAA;AAAA,MAChD;AAEA,MAAA,KAAA,MAAW,EAAE,MAAA,EAAQ,IAAA,EAAK,IAAK,OAAA,EAAS;AACtC,QAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA;AACjC,QAAA,IAAI,WAAW,MAAA,EAAW;AACxB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WAGF;AAAA,QACF;AACA,QAAA,GAAA,CAAI,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAA,EAAM,CAAA;AAAA,MAC3B;AACA,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,GACF;AACF","file":"vertexUploads.js","sourcesContent":["/**\n * Read vertex geometry back out of a `GLRecorder`, with each upload's\n * floats-per-vertex taken from the `vertexAttribPointer` recorded for that same\n * buffer object.\n *\n * A ring slot configures its attributes once, so a frame after the first\n * records no pointer calls at all — hence a tracker that remembers what it has\n * learned. Ask it for `uploads()` after each frame and it carries the strides\n * across `recorder.reset()`.\n */\n\nimport type { GLCall, GLRecorder } from './glRecorder';\n\nconst ARRAY_BUFFER = 0x8892;\n\n/** One vertex buffer upload. */\nexport interface VertexUpload {\n /** Floats per vertex. */\n stride: number;\n data: Float32Array;\n}\n\n/** Reads vertex uploads out of a recorder, remembering each buffer's stride. */\nexport interface UploadTracker {\n /** Every vertex upload recorded since the last `recorder.reset()`. */\n uploads(): VertexUpload[];\n}\n\n/**\n * The floats an upload actually sent. `bufferSubData(target, offset, src,\n * srcOffset, length)` hands GL a *window* into a scratch array the batch\n * reuses across frames, so reading the recorded argument whole compares live\n * vertices against stale ones.\n */\nfunction payload(call: GLCall): Float32Array | null {\n const data = call.name === 'bufferData' ? call.args[1] : call.args[2];\n if (!(data instanceof Float32Array)) return null;\n if (call.name !== 'bufferSubData') return data;\n const srcOffset = typeof call.args[3] === 'number' ? call.args[3] : 0;\n const length = typeof call.args[4] === 'number' ? call.args[4] : data.length - srcOffset;\n return data.subarray(srcOffset, srcOffset + length);\n}\n\n/** A tracker over `recorder`; keep one across frames. */\nexport function makeUploadTracker(recorder: GLRecorder): UploadTracker {\n // Keyed on the recorder's synthetic buffer handle, which is a stable object.\n const strides = new Map<unknown, number>();\n\n return {\n uploads(): VertexUpload[] {\n const out: VertexUpload[] = [];\n const pending: { buffer: unknown; data: Float32Array }[] = [];\n let bound: unknown = null;\n\n // Two passes: a buffer's attribute layout can be recorded either before\n // its first upload (the ring) or after it (every other draw path).\n for (const call of recorder.calls) {\n if (call.name === 'bindBuffer') {\n if (call.args[0] === ARRAY_BUFFER) bound = call.args[1];\n continue;\n }\n if (call.name === 'vertexAttribPointer') {\n // Byte stride; 0 means tightly packed, which for a_position is a vec2.\n const bytes = call.args[4] as number;\n strides.set(bound, bytes === 0 ? 2 : bytes / 4);\n continue;\n }\n if (call.name !== 'bufferData' && call.name !== 'bufferSubData') continue;\n const data = payload(call);\n if (data) pending.push({ buffer: bound, data });\n }\n\n for (const { buffer, data } of pending) {\n const stride = strides.get(buffer);\n if (stride === undefined) {\n throw new Error(\n 'vertexUploads: a vertex buffer was written with no vertexAttribPointer ' +\n 'ever recorded for it — call uploads() on the frame that creates the ' +\n 'buffer, before resetting the recorder.',\n );\n }\n out.push({ stride, data });\n }\n return out;\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weasel-js/renderer",
3
- "version": "1.9.4",
3
+ "version": "1.10.0",
4
4
  "description": "A WebGL2 renderer for draw commands: batched path fills and strokes, MSDF and outline text, images and sprites, gradients, patterns, registered shader programs, and full-screen effects. No scene graph, no React.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,14 +36,14 @@
36
36
  "./package.json": "./package.json"
37
37
  },
38
38
  "dependencies": {
39
- "@weasel-js/geom": "1.9.4",
40
- "@weasel-js/paint": "1.9.4",
41
- "@weasel-js/registry": "1.9.4",
42
- "@weasel-js/stroke": "1.9.4",
43
- "@weasel-js/text": "1.9.4"
39
+ "@weasel-js/geom": "1.10.0",
40
+ "@weasel-js/paint": "1.10.0",
41
+ "@weasel-js/registry": "1.10.0",
42
+ "@weasel-js/stroke": "1.10.0",
43
+ "@weasel-js/text": "1.10.0"
44
44
  },
45
45
  "peerDependencies": {
46
- "@weasel-js/font": "1.9.4"
46
+ "@weasel-js/font": "1.10.0"
47
47
  },
48
48
  "author": "orochi235",
49
49
  "homepage": "https://orochi235.github.io/weasel/",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/patterns/index.ts"],"names":[],"mappings":";;;AAiCO,SAAS,kBAAkB,IAAA,EAA6C;AAC7E,EAAA,MAAM,MAAA,GAAS,cAAc,IAAI,CAAA;AACjC,EAAA,OAAO,MAAA,GAAS,eAAA,CAAgB,MAAM,CAAA,GAAI,IAAA;AAC5C","file":"chunk-2VOBNWBN.js","sourcesContent":["/**\n * Tile-pattern primitive — a small helper for building repeating texture\n * tiles from a draw callback. The result plugs into a `FillStyle` of\n * `{ fill: 'pattern', pattern }` (see `@weasel-js/paint`).\n *\n * GL-backed: the tile is rendered to an `OffscreenCanvas` (with a 2D fallback\n * to a regular `<canvas>` for environments without `OffscreenCanvas`),\n * converted to an `ImageBitmap`, and registered with `registerTexture()`.\n * The returned `TextureHandle` is what `FillStyle.pattern` accepts.\n *\n * For ready-to-use named patterns (hatch, crosshatch, dots, chunks),\n * see the `@weasel-js/renderer/patterns-builtin` subpath.\n */\n\nimport { registerTexture } from '../textures/registerTexture';\nimport type { TextureHandle } from '../textures/registerTexture';\nimport { rasterizeTile } from './rasterizeTile';\n\n/** Options for `createTilePattern`. */\nexport interface TilePatternOpts {\n /** Edge length (in pixels) of the square offscreen tile. */\n size: number;\n /** FillStyle one tile at integer-pixel coordinates `(0, 0) .. (size, size)`. */\n draw: (oc: CanvasRenderingContext2D, size: number) => void;\n}\n\n/**\n * Render a single tile to an offscreen canvas and register it as a GL\n * texture, returning a `TextureHandle` you can wrap in a `FillStyle` of\n * `{ fill: 'pattern', pattern }`. Returns `null` only when an\n * `OffscreenCanvas`/2D context can't be acquired (very rare; same fallback\n * semantics as the prior `CanvasPattern`-based implementation).\n */\nexport function createTilePattern(opts: TilePatternOpts): TextureHandle | null {\n const bitmap = rasterizeTile(opts);\n return bitmap ? registerTexture(bitmap) : null;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/textures/registerTexture.ts","../src/patterns/tileGeometry.ts","../src/patterns/rasterizeTile.ts"],"names":[],"mappings":"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","file":"chunk-ORBXHIRB.js","sourcesContent":["/**\n * registerTexture — accepts an image source, assigns an opaque id, stores in\n * a module-level registry. Actual GL upload happens lazily at draw time in\n * drawShader() via GLTextureCache.upload (which is idempotent).\n *\n * Lifecycle: a texture lives until `unregisterTexture`. Each renderer frees\n * its own GL copy at the top of its next frame.\n *\n * Convention §9: this registry stores image data only — no per-renderer state.\n * Each WeaselRenderer's GLTextureCache does its own dedup by id and source.\n */\n\nimport type { TextureHandle } from '@weasel-js/paint';\nimport { createReflectable, type Reflection } from '@weasel-js/registry';\n\nexport type { TextureHandle };\n\n/** A registered texture's image source, uploaded to GL lazily at first draw. */\nexport interface TextureEntry {\n source: HTMLImageElement | ImageBitmap;\n}\n\nlet counter = 0;\nconst registry = createReflectable<TextureEntry>();\n\n/** Every registered texture, keyed by its handle id. */\nexport const textureRegistry: Reflection<TextureEntry> = registry.reflection;\n\n/** @internal Test helper — do not call from product code. */\nexport function _resetTextureRegistryForTests(): void {\n registry.clear();\n counter = 0;\n}\n\n/**\n * Whoever mints the ids under one prefix and can rebuild any of them.\n * @internal\n */\nexport interface TextureIdOwner {\n /** Register `id` again if it can be rebuilt; do nothing otherwise. */\n revive(id: string): void;\n /** `id` was looked up and found: something is about to draw it. */\n touched?(id: string): void;\n}\n\nconst owners: { prefix: string; owner: TextureIdOwner }[] = [];\n\n/**\n * Claim every texture id starting with `prefix`. Returns the release.\n * @internal\n */\nexport function ownTextureIds(prefix: string, owner: TextureIdOwner): () => void {\n const claim = { prefix, owner };\n owners.push(claim);\n return () => {\n const i = owners.indexOf(claim);\n if (i !== -1) owners.splice(i, 1);\n };\n}\n\nfunction ownerOf(id: string): TextureIdOwner | undefined {\n for (let i = 0; i < owners.length; i++) {\n if (id.startsWith(owners[i].prefix)) return owners[i].owner;\n }\n return undefined;\n}\n\n/**\n * The entry for `id`, as a draw looks it up. An owned id that was\n * unregistered is rebuilt by its owner first, so a handle held past its\n * texture's eviction still draws.\n */\nexport function getTexture(id: string): TextureEntry | null {\n const owner = owners.length > 0 ? ownerOf(id) : undefined;\n const hit = registry.get(id);\n if (hit) {\n owner?.touched?.(id);\n return hit;\n }\n if (!owner) return null;\n owner.revive(id);\n return registry.get(id) ?? null;\n}\n\n/**\n * Register an image for use as a shader texture uniform.\n *\n * Returns a TextureHandle whose `id` can be passed as a ShaderUniform value.\n * The handle is valid for the lifetime of the renderer it will be used with.\n *\n * @param image The image to register. HTMLImageElement must be fully loaded.\n * @returns Opaque TextureHandle.\n */\nexport function registerTexture(\n image: HTMLImageElement | ImageBitmap,\n): TextureHandle {\n const id = `tex_${++counter}`;\n registry.set(id, { source: image });\n return { id };\n}\n\n/**\n * Register an image under an id its caller chose.\n * @internal\n */\nexport function registerTextureAs(\n id: string,\n image: HTMLImageElement | ImageBitmap,\n): TextureHandle {\n registry.set(id, { source: image });\n return { id };\n}\n\n/**\n * Drop a registered texture. Every renderer that uploaded it frees its GL\n * copy at the top of its next frame; a draw command still holding the handle\n * then draws nothing and warns. The image itself is the caller's to release.\n *\n * @returns Whether the handle was registered.\n */\nexport function unregisterTexture(handle: TextureHandle): boolean {\n return registry.delete(handle.id);\n}\n","/**\n * The shapes a built-in tile is made of, as data.\n *\n * One description, two renderers: `patterns-builtin` strokes/fills these to\n * an `OffscreenCanvas` to build the GL texture, and `@weasel-js/svg` maps\n * them to `<pattern>` children. Duplicating the tile drawing in the\n * serializer would let the raster and vector forms drift apart silently.\n */\n\nimport type { TilePatternSpec } from '@weasel-js/paint';\n\n/** One primitive within a pattern tile, in tile-local coordinates. */\nexport type TileShape =\n | { kind: 'line'; x1: number; y1: number; x2: number; y2: number; width: number; color: string }\n | { kind: 'circle'; cx: number; cy: number; r: number; color: string }\n | { kind: 'ellipse'; cx: number; cy: number; rx: number; ry: number; rotation: number; color: string }\n | { kind: 'rect'; x: number; y: number; width: number; height: number; color: string };\n\n/** A built-in tile's shapes plus the edge length they're drawn within. */\nexport interface TileGeometry {\n size: number;\n shapes: TileShape[];\n}\n\n/** Defaults per tile kind, matching each factory's signature. */\nexport function tileSize(spec: TilePatternSpec): number {\n if (spec.size !== undefined) return spec.size;\n switch (spec.tile) {\n case 'hatch': return 5;\n case 'crosshatch': return 6;\n case 'dots': return 6;\n case 'chunks': return 48;\n default: return 8;\n }\n}\n\n/** Seeded PRNG (mulberry32) — `chunks` places its scatter with this, so the\n * same `seed` yields the same tile in both the raster and vector paths. */\nfunction mulberry32(seed: number): () => number {\n let s = seed | 0;\n return () => {\n s = (s + 0x6D2B79F5) | 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/** Resolve a pattern spec to the shapes that make up one tile. Consumers that\n * want to draw a pattern swatch themselves — a picker, an SVG export — read\n * this rather than rasterizing a texture. */\nexport function tileGeometry(spec: TilePatternSpec): TileGeometry {\n const s = tileSize(spec);\n const { color } = spec;\n\n switch (spec.tile) {\n case 'hatch': {\n const width = spec.lineWidth ?? 1;\n return { size: s, shapes: forwardDiagonals(s, width, color) };\n }\n case 'crosshatch': {\n const width = spec.lineWidth ?? 0.8;\n return {\n size: s,\n shapes: [...forwardDiagonals(s, width, color), ...backwardDiagonals(s, width, color)],\n };\n }\n case 'dots': {\n const r = spec.radius ?? 1;\n return { size: s, shapes: [{ kind: 'circle', cx: s / 2, cy: s / 2, r, color }] };\n }\n case 'chunks': {\n const density = spec.density ?? 0.35;\n const chunkSize = spec.chunkSize ?? 3;\n const rand = mulberry32(spec.seed ?? 42);\n const count = Math.round((s * s * density) / (chunkSize * chunkSize));\n const shapes: TileShape[] = [];\n if (spec.bg !== undefined) {\n shapes.push({ kind: 'rect', x: 0, y: 0, width: s, height: s, color: spec.bg });\n }\n for (let i = 0; i < count; i++) {\n const cx = rand() * s;\n const cy = rand() * s;\n const w = chunkSize * (0.5 + rand());\n const h = chunkSize * (0.5 + rand());\n const rotation = rand() * Math.PI;\n shapes.push({ kind: 'ellipse', cx, cy, rx: w / 2, ry: h / 2, rotation, color });\n }\n return { size: s, shapes };\n }\n default:\n return { size: s, shapes: [] };\n }\n}\n\n/** The forward (`/`) diagonal, plus the two corner stubs that make it tile\n * seamlessly across the edges. */\nfunction forwardDiagonals(s: number, width: number, color: string): TileShape[] {\n return [\n { kind: 'line', x1: -1, y1: s + 1, x2: s + 1, y2: -1, width, color },\n { kind: 'line', x1: -1, y1: 1, x2: 1, y2: -1, width, color },\n { kind: 'line', x1: s - 1, y1: s + 1, x2: s + 1, y2: s - 1, width, color },\n ];\n}\n\n/** The backward (`\\`) diagonal and its two stubs. */\nfunction backwardDiagonals(s: number, width: number, color: string): TileShape[] {\n return [\n { kind: 'line', x1: -1, y1: -1, x2: s + 1, y2: s + 1, width, color },\n { kind: 'line', x1: s - 1, y1: -1, x2: s + 1, y2: 1, width, color },\n { kind: 'line', x1: -1, y1: s - 1, x2: 1, y2: s + 1, width, color },\n ];\n}\n\n/** Draw a tile's shapes to a 2D context — the raster half of the pair. */\nexport function drawTileShapes(oc: CanvasRenderingContext2D, geometry: TileGeometry): void {\n for (const shape of geometry.shapes) {\n if (shape.kind === 'line') {\n oc.strokeStyle = shape.color;\n oc.lineWidth = shape.width;\n oc.beginPath();\n oc.moveTo(shape.x1, shape.y1);\n oc.lineTo(shape.x2, shape.y2);\n oc.stroke();\n continue;\n }\n oc.fillStyle = shape.color;\n if (shape.kind === 'rect') {\n oc.fillRect(shape.x, shape.y, shape.width, shape.height);\n continue;\n }\n oc.beginPath();\n if (shape.kind === 'circle') {\n oc.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2);\n } else {\n oc.ellipse(shape.cx, shape.cy, shape.rx, shape.ry, shape.rotation, 0, Math.PI * 2);\n }\n oc.fill();\n }\n}\n","import type { TilePatternOpts } from './';\n\n/** Draw one tile and hand back its pixels, or `null` where no\n * `OffscreenCanvas` 2D context can be had. */\nexport function rasterizeTile(opts: TilePatternOpts): ImageBitmap | null {\n // Prefer OffscreenCanvas; fall back to <canvas> for environments without it.\n let off: OffscreenCanvas | HTMLCanvasElement;\n try {\n off = new OffscreenCanvas(opts.size, opts.size);\n } catch {\n if (typeof document === 'undefined') return null;\n const c = document.createElement('canvas');\n c.width = opts.size;\n c.height = opts.size;\n off = c;\n }\n const oc = off.getContext('2d') as CanvasRenderingContext2D | null;\n if (!oc) return null;\n opts.draw(oc, opts.size);\n\n // Only an OffscreenCanvas can hand over a bitmap synchronously.\n if (typeof (off as OffscreenCanvas).transferToImageBitmap !== 'function') return null;\n return (off as OffscreenCanvas).transferToImageBitmap();\n}\n"]}