@weasel-js/renderer 1.9.4 → 1.10.1

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.
package/dist/index.d.ts CHANGED
@@ -1,14 +1,14 @@
1
- import { S as ShaderProgramHandle, a as ShaderUniform, b as ShaderProgram, F as FillPoseBox } from './paintKinds-B2vEy9A4.js';
2
- export { P as PaintBindContext, c as PaintDrawKind, d as PaintDrawKindLoader, e as PaintProgram, f as PaintResources, g as ProgramSource, h as ShaderCompileError, i as fillInPoseFrame, j as fillToBoundsFrame, k as getPaintDrawKind, l as getProgramSource, m as isPaintKindKnown, p as paintDrawKindRegistry, n as programSourceRegistry, r as registerPaintDrawKind, o as registerPaintDrawKindLoader, q as registerProgram, w as warmPaintKinds } from './paintKinds-B2vEy9A4.js';
1
+ import { S as ShaderProgramHandle, a as ShaderUniform, b as ShaderProgram, F as FillPoseBox } from './paintKinds-BmPAT51k.js';
2
+ export { P as PaintBindContext, c as PaintDrawKind, d as PaintDrawKindLoader, e as PaintProgram, f as PaintResources, g as ProgramSource, h as ShaderCompileError, i as ShaderStage, U as UniformArray, j as fillInPoseFrame, k as fillToBoundsFrame, l as getPaintDrawKind, m as getProgramSource, n as isPaintKindKnown, p as paintDrawKindRegistry, o as programSourceRegistry, r as registerPaintDrawKind, q as registerPaintDrawKindLoader, s as registerProgram, w as warmPaintKinds } from './paintKinds-BmPAT51k.js';
3
3
  import { Mesh } from '@weasel-js/geom/tessellate';
4
4
  export { Mesh, TessellateOptions, tessellate } from '@weasel-js/geom/tessellate';
5
- import { GradStop, ColorSpace, CommandFill, CommandStroke, Stroke, FillStyle, TextureHandle } from '@weasel-js/paint';
6
- export { CommandFill, CommandPaintUnits, CommandStroke, PosedFill, SolidFill, TextureHandle } from '@weasel-js/paint';
5
+ import { GradStop, ColorSpace, CommandFill, CommandStroke, Stroke, Fill, Paint, CommandPaint, TextureHandle } from '@weasel-js/paint';
6
+ export { CommandFill, CommandPaint, CommandPaintUnits, CommandStroke, PosedPaint, SolidPaint, TextureHandle } from '@weasel-js/paint';
7
7
  import { GlMat3, Path } from '@weasel-js/geom';
8
8
  export { GlMat3, View as ViewLike, mat3, viewToMat3 } from '@weasel-js/geom';
9
9
  import { ResolvedRun, TextAlign, TextDirection, TextStyle, TextVerticalAlign, TextPaint, TextPose, StyledRun } from '@weasel-js/text';
10
- import { D as DrawBatch } from './imageCache-oJVgxhkr.js';
11
- export { I as ImageNodeData, a as ImageRasterSize, b as ImageStatus, T as TextureEntry, g as getImageBitmap, i as imageStatus, c as isPatternSpec, d as isVectorImageSrc, r as registerTexture, e as releaseImage, f as resolveFillPattern, h as resolvePatternSpec, j as retryImage, s as subscribeImageReady, t as textureRegistry, u as unregisterTexture } from './imageCache-oJVgxhkr.js';
10
+ import { D as DrawBatch } from './imageCache-DucOxC7l.js';
11
+ export { I as ImageNodeData, a as ImageRasterSize, b as ImageStatus, T as TextureEntry, g as getImageBitmap, i as imageStatus, c as isVectorImageSrc, r as registerTexture, d as releaseImage, e as resolveFillPattern, f as resolvePatternSpec, h as retryImage, s as setImageCacheBudget, j as subscribeImageReady, t as textureRegistry, u as unregisterTexture } from './imageCache-DucOxC7l.js';
12
12
  export { StrokeOptions, tessellateStroke } from '@weasel-js/stroke';
13
13
  export { RegisterFontOptions, canQueryLocalFonts, cssFontFamily, cssFontFamilyLoading, enableLocalFontOutlines, fontOutlineRegistry, fontRegistry, hasFontOutlines, isCanvasFont, listFontOutlines, loadFontOutlines, outlineStatus, registerCanvasFont, registerFont, registerFontOutlines, subscribeGlyphReady, unregisterCanvasFont, unregisterFontOutlines } from '@weasel-js/font';
14
14
  import '@weasel-js/registry';
@@ -31,29 +31,16 @@ interface MeshResources {
31
31
  * Caches GL-side buffers + VAO per `Mesh` identity. Upload happens lazily
32
32
  * on first `handleFor(mesh)` call.
33
33
  *
34
- * **GC-aware cleanup (deferred-delete queue):** when a Mesh becomes
35
- * unreachable, the WeakMap entry is dropped automatically — but the
36
- * underlying GL resources (VAO, VBO, IBO) would leak forever without an
37
- * explicit `gl.delete*` call. We register each Mesh with a
34
+ * **GC-aware cleanup (deferred-delete queue):** we register each Mesh with a
38
35
  * `FinalizationRegistry`; when the Mesh is reclaimed, the finalizer pushes
39
- * the resources onto an internal queue. The renderer drains this queue at
40
- * the start of each `render()` call (via `drainPendingDeletes()`), when GL
41
- * is in a known state — no VAO bound, no draw mid-flight. Deleting from the
42
- * finalizer directly was racy (it could fire between two `gl.bindVertexArray`
43
- * calls inside `dispatch()`, sometimes corrupting the next draw); deferring
44
- * the actual delete to a known-safe point keeps the use-after-free risk to
45
- * zero.
46
- *
47
- * Caveats:
48
- * - Finalizer timing is non-deterministic; resources may live for a frame
49
- * or two beyond their Mesh, but they will be reclaimed.
50
- * - If the GL context is lost between finalizer and drain, the drain
51
- * no-ops safely (`gl.isContextLost()` makes deletes no-ops, and
52
- * `gl.deleteBuffer(null)` is allowed).
36
+ * its GL resources (VAO, VBO, IBO) onto an internal queue. The renderer
37
+ * drains this queue at the start of each `render()` call (via
38
+ * `drainPendingDeletes()`), when GL is in a known state — no VAO bound, no
39
+ * draw mid-flight. Finalizer timing is non-deterministic; resources may live
40
+ * for a frame or two beyond their Mesh.
53
41
  *
54
42
  * The cache is GL-context-bound; if the context is lost and re-created, the
55
- * renderer should construct a new GLMeshCache. (Context loss handling lives
56
- * in `WeaselRenderer`.)
43
+ * renderer should construct a new GLMeshCache.
57
44
  */
58
45
  declare class GLMeshCache {
59
46
  private readonly gl;
@@ -70,11 +57,9 @@ declare class GLMeshCache {
70
57
  constructor(gl: WebGL2RenderingContext);
71
58
  handleFor(mesh: Mesh): GLMeshHandle;
72
59
  /**
73
- * Upload a Mesh that the caller knows is single-use (e.g. the per-frame
74
- * stroke ribbon from `tessellateStroke`). Returns a handle backed by fresh
75
- * GL resources that the renderer will free deterministically at the end
76
- * of the current frame via `freeTransient()`. Bypasses the WeakMap cache
77
- * and the FinalizationRegistry, so transient meshes never wait on GC.
60
+ * Upload a Mesh that the caller knows is single-use. Returns a handle backed
61
+ * by fresh GL resources that the renderer frees at the end of the current
62
+ * frame via `freeTransient()`, so transient meshes never wait on GC.
78
63
  */
79
64
  uploadTransient(mesh: Mesh): GLMeshHandle;
80
65
  /**
@@ -112,19 +97,19 @@ declare class GLMeshCache {
112
97
  private upload;
113
98
  }
114
99
 
100
+ /** A texture source the browser decodes. */
101
+ type ImageTexSource = ImageBitmap | ImageData | HTMLCanvasElement | HTMLImageElement;
102
+
115
103
  /**
116
- * GL texture upload + cache for MSDF font atlases (and future images).
104
+ * GL texture upload + cache, keyed by a string id (the font family name or
105
+ * image id). The cache is GL-context-bound; discard and create a new one on
106
+ * context loss.
117
107
  *
118
- * Textures are keyed by a string id (the font family name or image id).
119
- * Pixels are uploaded once per id; a later upload of the same id reuses the
120
- * texture and only re-applies its wrap mode when that changed.
121
- * The cache is GL-context-bound; discard and create a new one on context loss.
122
- *
123
- * Atlas format: RGBA UNSIGNED_BYTE, linear filtering, no mipmaps.
124
- * Mipmap generation is deliberately skipped — MSDF works correctly with
125
- * linear filtering, and mipmap resampling corrupts the multi-channel SDF signal.
108
+ * Atlas format: RGBA UNSIGNED_BYTE, linear filtering, no mipmaps: mipmap
109
+ * resampling corrupts the multi-channel SDF signal.
126
110
  */
127
- type TexSource$1 = HTMLImageElement | ImageBitmap | ImageData | HTMLCanvasElement;
111
+
112
+ type TexSource$1 = ImageTexSource;
128
113
  /** How a texture samples outside `0..1`. Pattern tiles need `'repeat'`;
129
114
  * everything else clamps. WebGL2 allows `REPEAT` on NPOT textures, so a
130
115
  * tile of any size tiles correctly. */
@@ -146,10 +131,12 @@ declare class GLTextureCache {
146
131
  /** Uploads the pixels once per id. `wrap` is re-applied whenever it differs
147
132
  * from what the id was last uploaded under: the same registered image can
148
133
  * be a clamped shader texture in one draw and a repeating pattern tile in
149
- * the next, and first-upload-wins gave the second one the first one's mode. */
134
+ * the next. */
150
135
  upload(id: string, source: TexSource$1, wrap?: TextureWrap): string;
136
+ private ensure;
151
137
  /** `upload` for an id from the texture registry, whose image can be replaced
152
- * under the same id: a changed source sends its pixels again. */
138
+ * under the same id: a changed source sends its pixels again. A registered
139
+ * texture is an image, so its texels are premultiplied. */
153
140
  uploadRegistered(id: string, source: TexSource$1, wrap?: TextureWrap): string;
154
141
  /**
155
142
  * Delete the texture of every registry-backed id the registry has dropped.
@@ -164,38 +151,40 @@ declare class GLTextureCache {
164
151
  uploadR8(id: string, width: number, height: number, data: Uint8Array): void;
165
152
  /** Patch a rect of an existing R8 texture with tightly-packed w×h bytes. */
166
153
  subImageR8(id: string, x: number, y: number, w: number, h: number, data: Uint8Array): void;
154
+ /** Create a texture from premultiplied RGBA bytes that are a color: a page
155
+ * of color glyphs. Mipmapped, as a font atlas must never be, because one is
156
+ * drawn well below the size it was baked at. No-op if `id` already exists. */
157
+ uploadRGBA(id: string, width: number, height: number, data: Uint8Array): void;
158
+ /** Patch a rect of an existing RGBA texture with tightly-packed w×h texels. */
159
+ subImageRGBA(id: string, x: number, y: number, w: number, h: number, data: Uint8Array): void;
167
160
  bind(id: string, unit: number): void;
168
161
  /**
169
- * Delete every uploaded GL texture and clear the map. Called by
170
- * `WeaselRenderer.dispose()`. The cache is unusable but refillable
171
- * afterward — a subsequent `upload()` for a previously-seen id re-creates
172
- * the texture rather than restoring the deleted one.
162
+ * Delete every uploaded GL texture and clear the map. The cache is
163
+ * refillable afterward.
173
164
  */
174
165
  free(): void;
175
166
  }
176
167
 
177
168
  /**
178
- * GL texture upload cache for ImageBitmap objects.
169
+ * GL texture upload cache for ImageBitmap objects, keyed by source identity
170
+ * (WeakMap) so GC can reclaim unreferenced bitmaps. A collected source's
171
+ * texture is queued and deleted at the renderer's next frame; GC timing is
172
+ * not prompt, so a source's owner that knows it will never be drawn again
173
+ * frees it at once with {@link releaseImageSource}, and `dispose` frees them
174
+ * all.
179
175
  *
180
- * Key: ImageBitmap (or pattern source object) identity (WeakMap) — lets GC
181
- * reclaim unreferenced bitmaps. A collected source's texture is queued and
182
- * deleted at the renderer's next frame, the way `GLMeshCache` frees a mesh's
183
- * buffers; GC timing is not prompt, so a source's owner that knows it will
184
- * never be drawn again frees it at once with {@link releaseImageSource}, and
185
- * `dispose` frees them all.
186
- *
187
- * Wrapping is set once at upload time per the `repetition` parameter:
188
- * - undefined / 'no-repeat' → CLAMP_TO_EDGE
189
- * - 'repeat' → REPEAT (both axes)
190
- * - 'repeat-x' → REPEAT on S, CLAMP on T
191
- * - 'repeat-y' → CLAMP on S, REPEAT on T
192
- *
193
- * Convention §2: texels stored straight; shader premultiplies.
176
+ * Wrapping is set once at upload time. Texels are stored premultiplied,
177
+ * whatever the source: see `uploadPremultiplied`.
194
178
  */
179
+
195
180
  type PatternRepetition = 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat';
196
- /** MIN_FILTER strategy for uploaded textures. */
197
- type ImageMinification = 'linear' | 'mipmap';
198
- type TexSource = ImageBitmap | ImageData | HTMLCanvasElement | HTMLImageElement;
181
+ /**
182
+ * MIN_FILTER strategy for uploaded textures: `'linear'` never mipmaps,
183
+ * `'mipmap'` does at upload, and `'auto'` uploads linear and builds a
184
+ * texture's mipmaps the first time a frame draws it at half size or smaller.
185
+ */
186
+ type ImageMinification = 'linear' | 'mipmap' | 'auto';
187
+ type TexSource = ImageTexSource;
199
188
  /** Free `key`'s texture in every live renderer. For an owner that has replaced
200
189
  * a source and will never draw the old one again — a later draw of it simply
201
190
  * re-uploads. */
@@ -211,32 +200,30 @@ declare class GLImageCache {
211
200
  private readonly textures;
212
201
  /**
213
202
  * MAG_FILTER each texture currently carries, so a redundant write can be
214
- * skipped.
215
- *
216
- * The batch sets this per flush — the same bitmap can be drawn at both
217
- * filters in one frame, and the value has to be right at the draw rather than
218
- * at upload. Filtering is state on the *texture object*, though, not on the
219
- * unit, so re-asserting a value it already has is a write to a live texture
220
- * for no reason. On a large sheet that is not free: a consumer's wall samples
221
- * a 5652px-square atlas, 122MB resident, and measured `sampling: 'nearest'`
222
- * costing up to 8x `'linear'` there — where the linear pass re-asserts the
223
- * upload default and the nearest pass changes state on every draw.
224
- *
225
- * Whether that is the cause is unproven (see `docs/TODO.md`), but the write
226
- * was redundant either way.
203
+ * skipped. The batch sets this per flush — the same bitmap can be drawn at
204
+ * both filters in one frame — but filtering is state on the *texture
205
+ * object*, not on the unit, so re-asserting a value it already has is a
206
+ * write to a live texture for no reason.
227
207
  */
228
208
  private readonly magFilters;
229
209
  private readonly finalizer;
230
210
  private readonly pendingDeletes;
211
+ /** Under `'auto'`, the sources whose mipmaps are built. */
212
+ private readonly mipped;
213
+ private frame;
214
+ private readonly lastDrawn;
231
215
  /** `minification` selects the MIN_FILTER strategy for uploaded textures.
232
- * `'linear'` (default) is the screen path's existing behavior. `'mipmap'`
233
- * generates mipmaps and filters LINEAR_MIPMAP_LINEAR — required for
234
- * quality minification when a large source bitmap is drawn small (the
235
- * headless print/export path); bilinear-only minification undersamples
236
- * and produces moiré. */
216
+ * `'mipmap'` generates mipmaps and filters LINEAR_MIPMAP_LINEAR: when a
217
+ * large source bitmap is drawn small, bilinear-only minification
218
+ * undersamples and produces moiré. `'auto'` pays for that only on the
219
+ * textures a frame draws small, when it first does. */
237
220
  constructor(gl: WebGL2RenderingContext, minification?: ImageMinification);
238
- /** Delete the textures of sources collected since the last call. The
239
- * renderer calls this at the top of each `render()`. */
221
+ /** Open a frame: what {@link drewInLatestFrame} answers about from here on.
222
+ * The renderer calls this at the top of each `render()`. */
223
+ beginFrame(): void;
224
+ /** Whether `key` was uploaded or bound since the last {@link beginFrame}. */
225
+ drewInLatestFrame(key: object): boolean;
226
+ /** Delete the textures of sources collected since the last call. */
240
227
  drainPendingDeletes(): void;
241
228
  /** Delete `key`'s texture, if it has one. */
242
229
  release(key: object): void;
@@ -247,6 +234,12 @@ declare class GLImageCache {
247
234
  * its textures died with it. */
248
235
  abandon(): void;
249
236
  upload(key: object, source: TexSource, repetition?: PatternRepetition): WebGLTexture;
237
+ /** Whether `key` would gain from {@link buildMipmaps}: only under `'auto'`,
238
+ * and only until they are built. */
239
+ lacksMipmaps(key: object): boolean;
240
+ /** Build `key`'s mipmaps and filter minification through them from here
241
+ * on. Its texture must be uploaded. Leaves no texture bound. */
242
+ buildMipmaps(key: object): void;
250
243
  bind(key: object, unit: number): void;
251
244
  /** Set `key`'s MAG_FILTER, skipping the call when it already holds that
252
245
  * value. Its texture must be bound to the active unit — callers pair this
@@ -264,8 +257,8 @@ declare class GLImageCache {
264
257
  * the same unit, so a run does not break per gradient. The atlas is
265
258
  * GL-context-bound; discard and recreate on context loss.
266
259
  *
267
- * Output convention §2: texels are stored as straight RGBA. The fragment
268
- * shader applies premultiplication before writing outColor.
260
+ * Texels are stored as straight RGBA. The fragment shader applies
261
+ * premultiplication before writing outColor.
269
262
  */
270
263
 
271
264
  /** Bake gradient stops into a 256-entry RGBA lookup strip, which the shader
@@ -319,10 +312,8 @@ declare class GradientRampAtlas {
319
312
  hitRate(): number;
320
313
  resetStats(): void;
321
314
  /**
322
- * Delete the atlas texture and forget every row in it. Called by
323
- * `WeaselRenderer.dispose()`. The CPU mirror goes with it, so the atlas is
324
- * unusable but refillable afterward (stats are left as-is; call
325
- * `resetStats()` separately if desired).
315
+ * Delete the atlas texture and forget every row in it. The CPU mirror goes
316
+ * with it, so the atlas is refillable afterward. Stats are left as-is.
326
317
  */
327
318
  free(): void;
328
319
  /** The row the next ramp is written to: a free one, a taller atlas, or the
@@ -351,16 +342,13 @@ declare class GroupState {
351
342
  get colorMatrix(): Float32Array;
352
343
  push(frame: GroupFrame): void;
353
344
  /**
354
- * Push a frame whose children paint into a surface of their own.
355
- *
356
- * The transform still accumulates — the children draw where they would have
357
- * drawn. Alpha and colour do not: they describe how the finished surface
358
- * joins the frame, and applying them on the way in as well would fade the
359
- * pixels an effect is about to read, then fade them again on the way out.
345
+ * Push a frame whose children paint into a surface of their own. The
346
+ * transform still accumulates. Alpha and color do not: applying them on the
347
+ * way in would fade the pixels an effect is about to read, then fade them
348
+ * again on the way out.
360
349
  *
361
350
  * Returns the pair the caller must apply at composite time — what `push`
362
- * would have left on the stack — so the composition rules live here and not
363
- * in the dispatcher.
351
+ * would have left on the stack.
364
352
  */
365
353
  pushIsolated(frame: GroupFrame): {
366
354
  alpha: number;
@@ -373,14 +361,10 @@ declare class GroupState {
373
361
  }
374
362
 
375
363
  /**
376
- * One full-screen pass over what a group has already drawn.
364
+ * One full-screen pass over what a group has already drawn. A group's effects
365
+ * run in order, each reading the previous one's output through `u_source`.
377
366
  *
378
- * The renderer runs a group's effects in order, each reading the previous
379
- * one's output through `u_source` and writing a whole new buffer — so an
380
- * effect is free to read neighbouring pixels, which is the entire point and
381
- * the one thing `colorMatrix` can never do.
382
- *
383
- * `uniforms` are the effect's own; `u_source`, `u_resolution` and `u_texel`
367
+ * `uniforms` are the effect's own; `u_source`, `u_resolution`, and `u_texel`
384
368
  * come from the renderer and must not be passed here.
385
369
  */
386
370
  interface Effect {
@@ -388,31 +372,34 @@ interface Effect {
388
372
  uniforms?: Record<string, ShaderUniform>;
389
373
  }
390
374
  /**
391
- * Register a fragment shader as an effect. Sugar over `registerProgram` with
392
- * the effect vertex shader, and the reason a consumer never imports the
393
- * prelude: an effect that registers with the *custom-shader* vertex shader
394
- * compiles, runs, and samples its source upside down.
375
+ * Register a fragment shader as an effect. An effect registered through
376
+ * `registerProgram` with the *custom-shader* vertex shader compiles, runs, and
377
+ * samples its source upside down.
395
378
  *
396
- * The fragment shader reads `v_uv` and `u_source`, and may declare
397
- * `u_resolution` / `u_texel`. See `effectPrelude.ts` for the full contract,
398
- * including the premultiplied-alpha requirement.
379
+ * The fragment shader reads `v_uv` and `u_source`, may declare `u_resolution` /
380
+ * `u_texel`, and must write premultiplied alpha. See `effectPrelude.ts`.
399
381
  *
400
382
  * @experimental
401
383
  */
402
384
  declare function registerEffect(id: string, frag: string): ShaderProgramHandle;
403
385
 
404
386
  /**
405
- * Solid-fill paint variant (subset of the full `FillStyle` union from
406
- * `@weasel-js/paint`). Kept for back-compat with step-1/2 consumers and
407
- * because some code reads `fill.color` directly. Through step 4, fills can
408
- * be any `FillStyle` variant — solid, pattern, or gradient.
387
+ * How an isolated group's finished surface joins what is already drawn.
388
+ *
389
+ * Every color the renderer writes is premultiplied, so each Porter-Duff
390
+ * operator is `source * Fa + destination * Fb` with both factors read off one
391
+ * alpha or the other, which is a blend function and needs no second pass.
409
392
  */
410
- interface SolidPaint {
411
- fill?: 'solid';
412
- /** Any CSS color string accepted by `parseColor`: hex, `rgb()`/`rgba()`, `hsl()`/`hsla()`, named, or `transparent`. */
413
- color: string;
414
- opacity?: number;
393
+ /** A Porter-Duff operator, named as canvas 2D's `globalCompositeOperation` names it. */
394
+ type CompositeOp = 'source-over' | 'source-in' | 'source-out' | 'source-atop' | 'destination-over' | 'destination-in' | 'destination-out' | 'destination-atop' | 'xor' | 'lighter' | 'copy';
395
+ /** What one side of the blend is scaled by. */
396
+ type BlendFactor = 'zero' | 'one' | 'source-alpha' | 'one-minus-source-alpha' | 'destination-alpha' | 'one-minus-destination-alpha';
397
+ /** An operator with no name: `source * source + destination * destination`, on premultiplied color. */
398
+ interface BlendFactors {
399
+ source: BlendFactor;
400
+ destination: BlendFactor;
415
401
  }
402
+
416
403
  /** DrawCommand variants implemented through step 6. */
417
404
  type DrawCommand = PathDrawCommand | GroupDrawCommand | TextDrawCommand | ImageDrawCommand | SpritesDrawCommand | ShaderDrawCommand;
418
405
  /** Draw a path, filled and/or stroked. The workhorse command: every shape the
@@ -421,22 +408,20 @@ interface PathDrawCommand {
421
408
  kind: 'path';
422
409
  path: Path;
423
410
  /** Solid, pattern, or gradient (linear/radial/conic), resolved for the
424
- * renderer: `commandFill` makes one from a node's `FillStyle`. */
411
+ * renderer: `commandPaint` makes one from a node's `Paint`. */
425
412
  fill?: CommandFill;
426
413
  /** Stroke spec. Its `paint` takes what `fill` does. */
427
414
  stroke?: CommandStroke;
428
415
  /**
429
416
  * Optional flat RGBA-per-path-anchor color array (length =
430
- * `4 × countPathAnchors(path)`, floats in 0..1). The renderer
431
- * arc-length-interpolates these per-anchor colors across the
432
- * flattened/triangulated mesh between consecutive anchors using the
433
- * mesh's `anchorA` / `anchorB` / `anchorT` parameterization.
417
+ * `4 × countPathAnchors(path)`, floats in 0..1), arc-length-interpolated
418
+ * across the mesh between consecutive anchors.
434
419
  *
435
420
  * **`fill` must also be set when using `vertexColors`.** The renderer
436
421
  * only enters the per-vertex shader path when the command has a fill
437
- * (the fill provides the opacity uniform; the vertex colors override
438
- * the fill's color). Pass any solid `fill` (e.g. `{ color: '#fff' }`)
439
- * as the placeholder; the per-vertex colors win in the shader.
422
+ * (the fill provides the opacity uniform). Pass any solid `fill` (e.g.
423
+ * `{ paint: { color: '#fff' } }`) as the placeholder; the per-vertex colors
424
+ * win in the shader.
440
425
  */
441
426
  vertexColors?: number[];
442
427
  }
@@ -473,16 +458,31 @@ interface GroupDrawCommand {
473
458
  * group asks for one.
474
459
  */
475
460
  effects?: readonly Effect[];
461
+ /**
462
+ * Draw the children into a buffer of their own and composite it once, the
463
+ * render-target boundary `effects` makes, with no pass run over it. `alpha`
464
+ * and `colorMatrix` then apply to the finished surface, so overlapping
465
+ * children do not show through each other as they do when each is faded on
466
+ * its own. Costs a device-resolution buffer and a full-screen composite.
467
+ */
468
+ isolate?: boolean;
469
+ /**
470
+ * How the group's finished surface joins what is already drawn. Default
471
+ * `source-over`. Any other operator isolates the group, and its composite
472
+ * covers the whole target inside the enclosing clip, so an operator that
473
+ * scales the destination by the source's alpha (`source-in`,
474
+ * `destination-in`, `destination-atop`, `copy`) also clears everything the
475
+ * group left empty. Operators reading the destination's alpha need a target
476
+ * that has one: a context created with `alpha: false` reads as opaque.
477
+ */
478
+ composite?: CompositeOp | BlendFactors;
476
479
  children: DrawCommand[];
477
480
  }
478
481
  /**
479
482
  * Text draw command. Renders one or more runs at (`x`, `y`) in screen
480
- * space, optionally word-wrapping at `maxWidth`. The renderer resolves
481
- * each run's `(fontFamily, fontWeight, fontStyle)` to an MSDF atlas via
482
- * `resolveFontVariant` and bucket-draws by atlas + color group.
483
- *
484
- * `style` carries node-level defaults (`lineHeight`, anti-alias width)
485
- * that don't belong on individual runs.
483
+ * space, optionally word-wrapping at `maxWidth`. `style` carries node-level
484
+ * defaults (`lineHeight`, anti-alias width) that don't belong on individual
485
+ * runs.
486
486
  */
487
487
  interface TextDrawCommand {
488
488
  kind: 'text';
@@ -510,11 +510,15 @@ interface TextDrawCommand {
510
510
  }
511
511
  /**
512
512
  * Image draw command — renders `image` at screen-space rect (x, y, w, h).
513
- * The image is stretched to fit; no tiling. Use a pattern FillStyle on a path
513
+ * The image is stretched to fit; no tiling. Use a pattern Paint on a path
514
514
  * for tiling.
515
515
  */
516
516
  interface ImageDrawCommand {
517
517
  kind: 'image';
518
+ /** A bitmap holding premultiplied pixels, which is what `createImageBitmap`
519
+ * makes unless told otherwise. One made with `premultiplyAlpha: 'none'`
520
+ * draws too bright wherever it is translucent: a browser uploads a bitmap
521
+ * as it stands, and nothing reports which kind it is. */
518
522
  image: ImageBitmap;
519
523
  x: number;
520
524
  y: number;
@@ -547,15 +551,12 @@ declare const SPRITE_STRIDE = 9;
547
551
  * Draw many quads sampling one bitmap — an atlas, a sprite sheet, a wall of
548
552
  * thumbnails. The same picture as a run of `ImageDrawCommand`s the renderer
549
553
  * would coalesce anyway, handed over already packed so it never walks a
550
- * command object per quad.
551
- *
552
- * Reach for it past a few thousand sprites. Below that a plain run of image
553
- * commands merges into the same single draw and reads better; the packed form
554
- * exists because at 20,000 the object walk is about half the frame.
554
+ * command object per quad. Reach for it past a few thousand sprites: at
555
+ * 20,000 the object walk is about half the frame.
555
556
  *
556
557
  * The sprites are one run: they share a texture, a filter, and whatever group
557
- * transform, alpha, color matrix and clip are live, exactly as a merged run of
558
- * image commands would. Anything varying per sprite is in the array.
558
+ * transform, alpha, color matrix and clip are live. Anything varying per
559
+ * sprite is in the array.
559
560
  */
560
561
  interface SpritesDrawCommand {
561
562
  kind: 'sprites';
@@ -581,7 +582,9 @@ interface SpritesDrawCommand {
581
582
  * The kit automatically sets `u_bounds`, `u_view`, and `u_proj` — do not
582
583
  * declare those in `uniforms`.
583
584
  *
584
- * @experimental API may change before v2.
585
+ * `program` is the handle the module-level `registerProgram()` returned. Each
586
+ * renderer compiles it the first time it draws one, so a layer emits this
587
+ * command with nothing set up on the canvas or the renderer beforehand.
585
588
  */
586
589
  interface ShaderDrawCommand {
587
590
  kind: 'shader';
@@ -626,7 +629,8 @@ interface RenderStats {
626
629
  interface WeaselRendererOptions {
627
630
  /** A WebGL2 context to draw with. Wins over `canvas`. */
628
631
  gl?: WebGL2RenderingContext;
629
- /** A canvas to open a stencil-backed WebGL2 context on, when `gl` is absent. */
632
+ /** A canvas to open a stencil-backed WebGL2 context on, when `gl` is absent.
633
+ * The renderer sets its drawing buffer and never its CSS size. */
630
634
  canvas?: HTMLCanvasElement;
631
635
  /** Drawing size in CSS pixels. */
632
636
  width: number;
@@ -634,10 +638,10 @@ interface WeaselRendererOptions {
634
638
  height: number;
635
639
  /** Device pixels per CSS pixel. */
636
640
  dpr: number;
637
- /** MIN_FILTER strategy for image/pattern textures (`GLImageCache`).
638
- * Default `'linear'` — the existing screen behavior. The headless
639
- * `renderSceneToPixels` path passes `'mipmap'` for print-quality
640
- * minification. Explicitly passing `'linear'` is always valid. */
641
+ /** MIN_FILTER strategy for image textures (`GLImageCache`). Default
642
+ * `'linear'`. `'mipmap'` builds mipmaps for every image at upload, as
643
+ * the headless `renderSceneToPixels` path does; `'auto'` builds them
644
+ * for an image the first time a frame draws it at half size or smaller. */
641
645
  imageMinification?: ImageMinification;
642
646
  /** Flatness tolerance for curve tessellation, in WORLD units (see
643
647
  * `TessellateOptions.flattenTolerance`). When set, path fills are
@@ -690,11 +694,14 @@ interface RenderTarget {
690
694
  declare class WeaselRenderer {
691
695
  private readonly gl;
692
696
  private stats;
693
- private pathFill;
694
- private pathFillVColor;
697
+ /** Built on the first frame that draws with each, so a one-shot render of
698
+ * plain shapes builds the batch program alone. */
699
+ private readonly pathFill;
700
+ private readonly pathFillVColor;
695
701
  private batchFill;
696
- private gradFill;
697
- private patternFill;
702
+ private readonly batchFillMatrices;
703
+ private readonly gradFill;
704
+ private readonly patternFill;
698
705
  /** Compiled on first use: only atlas text under a texture paint needs one. */
699
706
  private glyphPaintPrograms;
700
707
  private meshCache;
@@ -738,16 +745,16 @@ declare class WeaselRenderer {
738
745
  * generation: a restore releases it and opens the next. */
739
746
  private paintResources;
740
747
  constructor(opts: WeaselRendererOptions);
748
+ private lazyPrograms;
741
749
  private uploadQuadGeometry;
742
750
  /**
743
751
  * Compile a consumer-registered shader program against this renderer's GL context.
744
752
  *
745
- * Call once per renderer after the module-level `registerProgram()`.
746
- * Throws `ShaderCompileError` if compilation fails.
753
+ * Optional: a program compiles on the first frame that draws with it. Call
754
+ * this after the module-level `registerProgram()` to pay for the compile
755
+ * before that frame, or to catch the `ShaderCompileError` it throws.
747
756
  *
748
757
  * In dev mode, calling again with the same handle replaces the compiled program.
749
- *
750
- * @experimental
751
758
  */
752
759
  registerProgram(handle: ShaderProgramHandle): void;
753
760
  private installProgram;
@@ -767,11 +774,11 @@ declare class WeaselRenderer {
767
774
  * Applied per `render()` rather than once at construction because a
768
775
  * co-tenant sharing this context moves all of it between our frames. */
769
776
  private applyGlState;
770
- /** Viewport and scissor for this frame. Re-applied inside `render()` because
771
- * a co-tenant on the same context moves both between our frames. */
772
777
  /** This renderer's rect in device pixels, top-left origin. A tile rounds its
773
778
  * edges rather than its size, so two that share a CSS edge share a device one. */
774
779
  private deviceRect;
780
+ /** Viewport and scissor for this frame. Re-applied inside `render()` because
781
+ * a co-tenant on the same context moves both between our frames. */
775
782
  private applyTarget;
776
783
  /** Confine this renderer to a rect of its drawing buffer, or pass null to
777
784
  * give it the whole buffer back. Takes effect from the next `render()`. */
@@ -781,14 +788,9 @@ declare class WeaselRenderer {
781
788
  private onContextLost;
782
789
  private onContextRestored;
783
790
  /** Free every GL resource this renderer created and detach its
784
- * context-loss listeners. Idempotent.
785
- *
786
- * That covers the built-in and consumer-registered programs (their shaders
787
- * go with them), the shared quad geometry, the batch, the effect targets,
788
- * and every cache: atlas and gradient-ramp textures, bitmap and pattern
789
- * textures, and meshes — transient, persistent, and those whose Path was
790
- * already collected. The context itself is left alone, so a caller-owned
791
- * context can host renderer after renderer without accumulating anything.
791
+ * context-loss listeners. Idempotent. The context itself is left alone, so
792
+ * a caller-owned context can host renderer after renderer without
793
+ * accumulating anything.
792
794
  *
793
795
  * A disposed renderer ignores further `render()` and `registerProgram()`
794
796
  * calls. */
@@ -819,6 +821,7 @@ declare class WeaselRenderer {
819
821
  /** @internal */ _pathFillVColor(): ShaderProgram;
820
822
  /** @internal */ _drawBatch(): DrawBatch;
821
823
  /** @internal */ _batchFill(): ShaderProgram;
824
+ /** @internal */ _batchFillMatrices(): ShaderProgram;
822
825
  /** @internal */ _gradFill(): ShaderProgram;
823
826
  /** @internal */ _patternFill(): ShaderProgram;
824
827
  /** @internal */ _meshCache(): GLMeshCache;
@@ -881,14 +884,15 @@ interface CullRect {
881
884
  * caps included), images and sprites by their quads, and every bound is taken
882
885
  * through the accumulated transform as the AABB of its corners, so rotation
883
886
  * only enlarges it. Text and shader commands are always kept, and so is a
884
- * group with `effects` — an effect may move pixels. Clips are ignored, which
887
+ * group with `effects` — an effect may move pixels — or its own `composite`,
888
+ * which changes the destination even where the group drew nothing. Clips are ignored, which
885
889
  * can only keep more. A group whose children all go is dropped.
886
890
  *
887
891
  * Returns `cmds` itself when nothing was dropped.
888
892
  */
889
893
  declare function cullDrawCommands(cmds: DrawCommand[], transform: GlMat3, rect: CullRect): DrawCommand[];
890
894
  /**
891
- * {@link strokeReach} at a known world-to-screen scale — what a painter
895
+ * How far a stroke reaches past its geometry at a known world-to-screen scale — what a painter
892
896
  * bounding its own output before it paints can know. `least` is the scale
893
897
  * along the axis the transform shrinks most, where it differs from `scale`.
894
898
  *
@@ -957,12 +961,9 @@ declare function vignette({ amount, feather }: {
957
961
  /**
958
962
  * Kit-supplied vertex shader for a full-screen effect pass.
959
963
  *
960
- * Distinct from `CUSTOM_VERT_SRC` on purpose. That one places its quad through
961
- * `u_bounds` and `u_proj`, so its `v_uv` runs top-down in *screen* space; an
962
- * effect samples a texture the scene was just rendered into, whose v axis runs
963
- * the other way. Passing `a_position` straight to `gl_Position` and `a_uv`
964
- * straight out keeps the two aligned — `QUAD_VERTICES` already carries
965
- * `a_uv == (a_position + 1) / 2` on both axes.
964
+ * Distinct from `CUSTOM_VERT_SRC` on purpose. That one's `v_uv` runs top-down
965
+ * in *screen* space; an effect samples a texture the scene was just rendered
966
+ * into, whose v axis runs the other way.
966
967
  *
967
968
  * PUBLIC CONTRACT — what every effect fragment shader may read:
968
969
  *
@@ -971,11 +972,10 @@ declare function vignette({ amount, feather }: {
971
972
  * uniform vec2 u_resolution source size in device pixels
972
973
  * uniform vec2 u_texel 1.0 / u_resolution — one texel step
973
974
  *
974
- * PREMULTIPLIED ALPHA: `u_source` holds premultiplied RGBA, because that is
975
- * what the scene blends into it. An effect that weights neighbouring samples
976
- * (a blur) is doing the right thing on premultiplied values; one that
977
- * manipulates rgb against a must premultiply its own output the same way the
978
- * custom-shader prelude requires.
975
+ * PREMULTIPLIED ALPHA: `u_source` holds premultiplied RGBA. An effect that
976
+ * weights neighboring samples (a blur) is doing the right thing on
977
+ * premultiplied values; one that manipulates rgb against a must premultiply
978
+ * its own output.
979
979
  */
980
980
  declare const EFFECT_VERT_SRC = "#version 300 es\nin vec2 a_position;\nin vec2 a_uv;\nout vec2 v_uv;\n\nvoid main() {\n v_uv = a_uv;\n gl_Position = vec4(a_position, 0.0, 1.0);\n}\n";
981
981
  /** Uniforms the renderer sets on every effect pass. An effect must not
@@ -988,26 +988,26 @@ declare const EFFECT_KIT_UNIFORMS: readonly ["u_source", "u_resolution", "u_texe
988
988
  */
989
989
 
990
990
  /**
991
- * `fill` as a draw command's fill: `'bounds'` units mapped onto `box`, and a
992
- * tile spec swapped for its texture. `null` when a spec cannot rasterize.
991
+ * `paint` as a draw command's paint: `'bounds'` units mapped onto `box`, and
992
+ * a tile kind swapped for its texture. `null` when a tile cannot rasterize.
993
993
  */
994
- declare function commandFill(fill: FillStyle, box: FillPoseBox): CommandFill | null;
994
+ declare function commandPaint(paint: Paint, box: FillPoseBox): CommandPaint | null;
995
+ /**
996
+ * `fill` as a draw command's fill, its paint resolved by {@link commandPaint}.
997
+ * `null` when the fill carries no paint, or its paint cannot be resolved.
998
+ */
999
+ declare function commandFill(fill: Fill, box: FillPoseBox): CommandFill | null;
995
1000
  /**
996
1001
  * `stroke` as a draw command's stroke, its paint resolved as
997
- * {@link commandFill} resolves a fill. `null` when the stroke carries no
1002
+ * {@link commandPaint} resolves one. `null` when the stroke carries no
998
1003
  * paint, or its paint cannot be resolved.
999
1004
  */
1000
1005
  declare function commandStroke(stroke: Stroke, box: FillPoseBox): CommandStroke | null;
1001
1006
 
1002
1007
  /**
1003
1008
  * Tile-pattern primitive — a small helper for building repeating texture
1004
- * tiles from a draw callback. The result plugs into a `FillStyle` of
1005
- * `{ fill: 'pattern', pattern }` (see `@weasel-js/paint`).
1006
- *
1007
- * GL-backed: the tile is rendered to an `OffscreenCanvas` (with a 2D fallback
1008
- * to a regular `<canvas>` for environments without `OffscreenCanvas`),
1009
- * converted to an `ImageBitmap`, and registered with `registerTexture()`.
1010
- * The returned `TextureHandle` is what `FillStyle.pattern` accepts.
1009
+ * tiles from a draw callback. The result plugs into a `Paint` of
1010
+ * `{ kind: 'texture', texture }` (see `@weasel-js/paint`).
1011
1011
  *
1012
1012
  * For ready-to-use named patterns (hatch, crosshatch, dots, chunks),
1013
1013
  * see the `@weasel-js/renderer/patterns-builtin` subpath.
@@ -1017,25 +1017,22 @@ declare function commandStroke(stroke: Stroke, box: FillPoseBox): CommandStroke
1017
1017
  interface TilePatternOpts {
1018
1018
  /** Edge length (in pixels) of the square offscreen tile. */
1019
1019
  size: number;
1020
- /** FillStyle one tile at integer-pixel coordinates `(0, 0) .. (size, size)`. */
1020
+ /** Paint one tile at integer-pixel coordinates `(0, 0) .. (size, size)`. */
1021
1021
  draw: (oc: CanvasRenderingContext2D, size: number) => void;
1022
1022
  }
1023
1023
  /**
1024
- * Render a single tile to an offscreen canvas and register it as a GL
1025
- * texture, returning a `TextureHandle` you can wrap in a `FillStyle` of
1026
- * `{ fill: 'pattern', pattern }`. Returns `null` only when an
1027
- * `OffscreenCanvas`/2D context can't be acquired (very rare; same fallback
1028
- * semantics as the prior `CanvasPattern`-based implementation).
1024
+ * Render a single tile and register it as a GL texture, returning a
1025
+ * `TextureHandle` you can wrap in a `Paint` of
1026
+ * `{ kind: 'texture', texture }`. Returns `null` only when an
1027
+ * `OffscreenCanvas`/2D context can't be acquired.
1029
1028
  */
1030
1029
  declare function createTilePattern(opts: TilePatternOpts): TextureHandle | null;
1031
1030
 
1032
1031
  /**
1033
1032
  * Build a `TextDrawCommand`. `textCommandFromPose` is the command for a text
1034
- * node — what `kit:text` and `createTextLayer` both emit. `textCommand` takes
1035
- * a plain string and wraps it in one run; `textCommandFromRuns` takes the
1036
- * styled runs directly, for text that has no pose. All three
1037
- * resolve through `resolveTextStyle` + `resolveRuns`, so every emitter of a
1038
- * text command derives `align` and per-run resolution the same way — a
1033
+ * node; `textCommand` takes a plain string and wraps it in one run;
1034
+ * `textCommandFromRuns` takes the styled runs directly, for text that has no
1035
+ * pose. All three resolve through `resolveTextStyle` + `resolveRuns` — a
1039
1036
  * caller that assembles the command by hand is one field away from a
1040
1037
  * silently different result.
1041
1038
  *
@@ -1067,4 +1064,4 @@ declare function textCommand(x: number, y: number, text: string, style?: TextSty
1067
1064
  */
1068
1065
  declare function textCommandFromPose(pose: TextPose, scale?: number): TextDrawCommand;
1069
1066
 
1070
- export { type CullRect, type DrawCommand, EFFECT_KIT_UNIFORMS, EFFECT_VERT_SRC, type Effect, FillPoseBox, type GroupDrawCommand, IDENTITY_COLOR_MATRIX, type ImageDrawCommand, type ImageMinification, OUTLINE_MIN_SCREEN_PX, type PathDrawCommand, type RenderOptions, type RenderSpan, type RenderSpanStats, type RenderStats, type RenderTarget, SPRITE_STRIDE, type ShaderDrawCommand, ShaderProgram, ShaderProgramHandle, ShaderUniform, type SolidPaint, type SpriteSheet, type SpritesDrawCommand, type TextDrawCommand, type TilePatternOpts, WeaselRenderer, type WeaselRendererOptions, blur, boundsMissRect, buildGradientRamp, commandFill, commandStroke, createTilePattern, cullDrawCommands, frameRect, registerEffect, releaseImageSource, strokeReachAt, textCommand, textCommandFromPose, textCommandFromRuns, vignette };
1067
+ export { type BlendFactor, type BlendFactors, type CompositeOp, type CullRect, type DrawCommand, EFFECT_KIT_UNIFORMS, EFFECT_VERT_SRC, type Effect, FillPoseBox, type GroupDrawCommand, IDENTITY_COLOR_MATRIX, type ImageDrawCommand, type ImageMinification, OUTLINE_MIN_SCREEN_PX, type PathDrawCommand, type RenderOptions, type RenderSpan, type RenderSpanStats, type RenderStats, type RenderTarget, SPRITE_STRIDE, type ShaderDrawCommand, ShaderProgram, ShaderProgramHandle, ShaderUniform, type SpriteSheet, type SpritesDrawCommand, type TextDrawCommand, type TilePatternOpts, WeaselRenderer, type WeaselRendererOptions, blur, boundsMissRect, buildGradientRamp, commandFill, commandPaint, commandStroke, createTilePattern, cullDrawCommands, frameRect, registerEffect, releaseImageSource, strokeReachAt, textCommand, textCommandFromPose, textCommandFromRuns, vignette };