@weasel-js/core 1.4.2 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +742 -0
  2. package/dist/{DrawCommand-Ch8W63oQ.d.ts → DrawCommand-CD-ug3d9.d.ts} +45 -1
  3. package/dist/{pointSnapToGrid-Dmthv94u.d.ts → autoPoseDescriptor-DF1SnnSx.d.ts} +18 -3
  4. package/dist/{stroke-Dq8nL4A2.d.ts → builtins-BXFBXegF.d.ts} +251 -102
  5. package/dist/{chunk-Y5KS66G7.js → chunk-2VXGHUVL.js} +4 -4
  6. package/dist/{chunk-Y5KS66G7.js.map → chunk-2VXGHUVL.js.map} +1 -1
  7. package/dist/{chunk-2KKYDDDD.js → chunk-4RJP2N2L.js} +43 -21
  8. package/dist/chunk-4RJP2N2L.js.map +1 -0
  9. package/dist/{chunk-TORKRNFY.js → chunk-BL65SHCX.js} +83 -8
  10. package/dist/chunk-BL65SHCX.js.map +1 -0
  11. package/dist/{chunk-MXFSHJOM.js → chunk-D4AAWR64.js} +4 -4
  12. package/dist/{chunk-MXFSHJOM.js.map → chunk-D4AAWR64.js.map} +1 -1
  13. package/dist/{chunk-ZB7UYJVG.js → chunk-DOBSZOPR.js} +3 -3
  14. package/dist/{chunk-ZB7UYJVG.js.map → chunk-DOBSZOPR.js.map} +1 -1
  15. package/dist/{chunk-BLJNRMKB.js → chunk-PRGBGMH3.js} +3 -3
  16. package/dist/{chunk-BLJNRMKB.js.map → chunk-PRGBGMH3.js.map} +1 -1
  17. package/dist/{chunk-KIVJXUYE.js → chunk-R3AWPTLZ.js} +2779 -1660
  18. package/dist/chunk-R3AWPTLZ.js.map +1 -0
  19. package/dist/{chunk-67KE7SDP.js → chunk-T3UQ3F6R.js} +4 -4
  20. package/dist/{chunk-67KE7SDP.js.map → chunk-T3UQ3F6R.js.map} +1 -1
  21. package/dist/{chunk-SBFC6J3C.js → chunk-WPM42WJP.js} +3 -3
  22. package/dist/{chunk-SBFC6J3C.js.map → chunk-WPM42WJP.js.map} +1 -1
  23. package/dist/clipboard.d.ts +2 -2
  24. package/dist/clipboard.js +3 -3
  25. package/dist/clone.d.ts +2 -2
  26. package/dist/clone.js +3 -3
  27. package/dist/{geometry-Dtt_k6Dq.d.ts → geometry-6fCNhAux.d.ts} +3 -3
  28. package/dist/{grid-nnXU4VjN.d.ts → grid-0Pbn5B2C.d.ts} +1 -14
  29. package/dist/index.d.ts +330 -288
  30. package/dist/index.js +9 -9
  31. package/dist/insert.d.ts +3 -3
  32. package/dist/insert.js +1 -1
  33. package/dist/move.d.ts +4 -4
  34. package/dist/move.js +3 -3
  35. package/dist/{options-CdFl510T.d.ts → options-DbYLImvq.d.ts} +1 -1
  36. package/dist/{registry-BwY_DxJM.d.ts → registry-BY-wI9gm.d.ts} +98 -8
  37. package/dist/renderer.d.ts +32 -4
  38. package/dist/renderer.js +9 -9
  39. package/dist/resize.d.ts +5 -4
  40. package/dist/resize.js +2 -2
  41. package/dist/routing.d.ts +6 -6
  42. package/dist/routing.js +1 -1
  43. package/dist/{types-XBcDEp3Y.d.ts → types-DEALFt5F.d.ts} +10 -6
  44. package/dist/{types-DIQAisSG.d.ts → types-bcc7jcUy.d.ts} +116 -15
  45. package/dist/{types-miXHGrZM.d.ts → types-ei3UMl9R.d.ts} +1 -1
  46. package/package.json +9 -9
  47. package/dist/chunk-2KKYDDDD.js.map +0 -1
  48. package/dist/chunk-KIVJXUYE.js.map +0 -1
  49. package/dist/chunk-TORKRNFY.js.map +0 -1
@@ -116,6 +116,35 @@ type ShaderUniform = number | [number, number] | [number, number, number] | [num
116
116
  */
117
117
  declare function registerProgram(id: string, vert: string, frag: string): ShaderProgramHandle;
118
118
 
119
+ /**
120
+ * One full-screen pass over what a group has already drawn.
121
+ *
122
+ * The renderer runs a group's effects in order, each reading the previous
123
+ * one's output through `u_source` and writing a whole new buffer — so an
124
+ * effect is free to read neighbouring pixels, which is the entire point and
125
+ * the one thing `colorMatrix` can never do.
126
+ *
127
+ * `uniforms` are the effect's own; `u_source`, `u_resolution` and `u_texel`
128
+ * come from the renderer and must not be passed here.
129
+ */
130
+ interface Effect {
131
+ program: ShaderProgramHandle;
132
+ uniforms?: Record<string, ShaderUniform>;
133
+ }
134
+ /**
135
+ * Register a fragment shader as an effect. Sugar over `registerProgram` with
136
+ * the effect vertex shader, and the reason a consumer never imports the
137
+ * prelude: an effect that registers with the *custom-shader* vertex shader
138
+ * compiles, runs, and samples its source upside down.
139
+ *
140
+ * The fragment shader reads `v_uv` and `u_source`, and may declare
141
+ * `u_resolution` / `u_texel`. See `effectPrelude.ts` for the full contract,
142
+ * including the premultiplied-alpha requirement.
143
+ *
144
+ * @experimental
145
+ */
146
+ declare function registerEffect(id: string, frag: string): ShaderProgramHandle;
147
+
119
148
  /**
120
149
  * Solid-fill paint variant (subset of the full `FillStyle` union from
121
150
  * `@weasel-js/core`). Kept for back-compat with step-1/2 consumers and
@@ -172,6 +201,21 @@ interface GroupDrawCommand {
172
201
  * cannot escape an ancestor's clip. Max 7 nesting levels; the renderer
173
202
  * throws if exceeded. */
174
203
  clip?: Path;
204
+ /**
205
+ * Full-screen passes run over this group's own pixels, in order, before it
206
+ * is composited into its parent.
207
+ *
208
+ * Unlike every other field here, this does not accumulate down the group
209
+ * stack — it is a render-target boundary. The children draw into a buffer of
210
+ * their own, each effect reads the previous one's output, and the result is
211
+ * composited back under this group's `transform`, `alpha`, `colorMatrix` and
212
+ * whatever clip encloses it. So a blur here blurs this group and nothing
213
+ * around it, which is what a CSS `filter` on the canvas cannot do.
214
+ *
215
+ * An empty or absent list costs nothing: no buffer is allocated until a
216
+ * group asks for one.
217
+ */
218
+ effects?: readonly Effect[];
175
219
  children: DrawCommand[];
176
220
  }
177
221
  /**
@@ -285,4 +329,4 @@ interface ShaderDrawCommand {
285
329
  };
286
330
  }
287
331
 
288
- export { type DrawCommand as D, type GroupDrawCommand as G, type ImageDrawCommand as I, type Mat3 as M, type PathDrawCommand as P, SPRITE_STRIDE as S, type TextDrawCommand as T, type ShaderDrawCommand as a, type ShaderProgramHandle as b, type ShaderUniform as c, type SolidPaint as d, type SpritesDrawCommand as e, mat3 as m, registerProgram as r };
332
+ export { type DrawCommand as D, type Effect as E, type GroupDrawCommand as G, type ImageDrawCommand as I, type Mat3 as M, type PathDrawCommand as P, SPRITE_STRIDE as S, type TextDrawCommand as T, type ShaderDrawCommand as a, type ShaderProgramHandle as b, type ShaderUniform as c, type SolidPaint as d, type SpritesDrawCommand as e, registerProgram as f, mat3 as m, registerEffect as r };
@@ -1,6 +1,7 @@
1
- import { c as ResizeAnchor, R as ResizePose, B as BoundsConstraint, P as PointSnapBehavior, d as PointSnapFrame, M as ModifierState } from './types-miXHGrZM.js';
2
- import { B as Bounds, P as PoseProjection } from './geometry-Dtt_k6Dq.js';
1
+ import { c as ResizeAnchor, R as ResizePose, B as BoundsConstraint, P as PointSnapBehavior, d as PointSnapFrame, M as ModifierState } from './types-ei3UMl9R.js';
2
+ import { B as Bounds, P as PoseProjection } from './geometry-6fCNhAux.js';
3
3
  import { D as DebugSink } from './types-BHK2dkMu.js';
4
+ import { P as Path } from './path-JEV2c5If.js';
4
5
 
5
6
  /** Corner resize-handle: world-space center plus the anchor that pins the opposite corner during resize. */
6
7
  interface CornerHandle {
@@ -137,4 +138,18 @@ declare function pointSnapToGrid<TPose extends ResizePose>(args: {
137
138
  bypassKey?: keyof ModifierState;
138
139
  }): PointSnapBehavior<TPose>;
139
140
 
140
- export { CORNER_ANCHORS as C, type UseResizeOptions as U, type CornerAnchor as a, type CornerHandle as b, cornerPoint as c, cornerResizeHandles as d, type CornerEdge as e, fixedCornerOf as f, hitCornerHandle as h, pointSnapToGrid as p };
141
+ /** True for Path-shaped poses (`{kind: 'polygon' | 'rect'}`). Useful for
142
+ * callers that need to fork between `pathPoseDescriptor` and
143
+ * `RECT_POSE_DESCRIPTOR` without forcing the consumer to wire `geometry`
144
+ * explicitly. */
145
+ declare function isPathLike(p: unknown): p is Path;
146
+ /** Per-call dispatch: if the pose looks like a Path, route to
147
+ * `pathPoseDescriptor`; otherwise treat as a plain rect pose. Avoids forcing
148
+ * demos with Path TPose to wire `geometry={pathPoseDescriptor}` explicitly.
149
+ * `getRotation` surfaces a `pose.rotation` field on non-Path poses so demos
150
+ * using rect-with-rotation shapes (e.g. `RotatedPose`) don't have to wire
151
+ * `geometry={ROTATED_POSE_DESCRIPTOR}` just to get rotated selection chrome
152
+ * and rotation-aware corner hit-tests. */
153
+ declare const AUTO_POSE_DESCRIPTOR: PoseProjection<unknown>;
154
+
155
+ export { AUTO_POSE_DESCRIPTOR as A, CORNER_ANCHORS as C, type UseResizeOptions as U, type CornerAnchor as a, type CornerHandle as b, cornerPoint as c, cornerResizeHandles as d, type CornerEdge as e, fixedCornerOf as f, hitCornerHandle as h, isPathLike as i, pointSnapToGrid as p };
@@ -1,5 +1,5 @@
1
1
  import { GradStop, Stroke } from '@weasel-js/paint';
2
- import { M as Mat3, b as ShaderProgramHandle, D as DrawCommand } from './DrawCommand-Ch8W63oQ.js';
2
+ import { M as Mat3, b as ShaderProgramHandle, D as DrawCommand, E as Effect } from './DrawCommand-CD-ug3d9.js';
3
3
  import { P as Path } from './path-JEV2c5If.js';
4
4
 
5
5
  /**
@@ -218,6 +218,23 @@ declare class GLImageCache {
218
218
  private readonly gl;
219
219
  private readonly minification;
220
220
  private readonly map;
221
+ /**
222
+ * MAG_FILTER each texture currently carries, so a redundant write can be
223
+ * skipped.
224
+ *
225
+ * The batch sets this per flush — the same bitmap can be drawn at both
226
+ * filters in one frame, and the value has to be right at the draw rather than
227
+ * at upload. Filtering is state on the *texture object*, though, not on the
228
+ * unit, so re-asserting a value it already has is a write to a live texture
229
+ * for no reason. On a large sheet that is not free: a consumer's wall samples
230
+ * a 5652px-square atlas, 122MB resident, and measured `sampling: 'nearest'`
231
+ * costing up to 8x `'linear'` there — where the linear pass re-asserts the
232
+ * upload default and the nearest pass changes state on every draw.
233
+ *
234
+ * Whether that is the cause is unproven (see `docs/TODO.md`), but the write
235
+ * was redundant either way.
236
+ */
237
+ private readonly magFilters;
221
238
  /** `minification` selects the MIN_FILTER strategy for uploaded textures.
222
239
  * `'linear'` (default) is the screen path's existing behavior. `'mipmap'`
223
240
  * generates mipmaps and filters LINEAR_MIPMAP_LINEAR — required for
@@ -227,13 +244,21 @@ declare class GLImageCache {
227
244
  constructor(gl: WebGL2RenderingContext, minification?: ImageMinification);
228
245
  upload(key: object, source: TexSource, repetition?: PatternRepetition): WebGLTexture;
229
246
  bind(key: object, unit: number): void;
247
+ /** Set `key`'s MAG_FILTER, skipping the call when it already holds that
248
+ * value. Its texture must be bound to the active unit — callers pair this
249
+ * with `bind`. */
250
+ setMagFilter(key: object, filter: number): void;
230
251
  }
231
252
 
232
253
  /**
233
- * CPU gradient-ramp builder + GL 1×256 RGBA texture cache.
254
+ * CPU gradient-ramp builder + the GL texture every baked ramp lives in.
234
255
  *
235
- * Each unique stop list is uploaded once. Key = JSON.stringify(stops).
236
- * The cache is GL-context-bound; discard and recreate on context loss.
256
+ * Each unique stop list is baked once into a 256-texel strip and written to its
257
+ * own **row** of one RGBA texture, keyed by `JSON.stringify(stops)`. One
258
+ * texture rather than one per ramp is what lets a gradient take a batch texture
259
+ * slot the way a bitmap or a font atlas does: every gradient in a frame samples
260
+ * the same unit, so a run does not break per gradient. The atlas is
261
+ * GL-context-bound; discard and recreate on context loss.
237
262
  *
238
263
  * Output convention §2: texels are stored as straight RGBA. The fragment
239
264
  * shader applies premultiplication before writing outColor.
@@ -242,25 +267,66 @@ declare class GLImageCache {
242
267
  /** Bake gradient stops into a 256-entry RGBA lookup strip, which the shader
243
268
  * samples instead of evaluating stops per fragment. */
244
269
  declare function buildGradientRamp(stops: GradStop[]): Uint8ClampedArray;
245
- declare class GradientRampCache {
270
+ declare class GradientRampAtlas {
246
271
  private readonly gl;
247
- private readonly map;
272
+ /** Row per stop list, in least-recently-used order: a hit re-inserts, so the
273
+ * first entry is the row a full atlas recycles. */
274
+ private readonly rowByKey;
275
+ private texture;
276
+ private rows;
277
+ /** Mirror of the texture, so growth can respecify it without re-baking every
278
+ * ramp it already holds. */
279
+ private pixels;
248
280
  private totalQueries;
249
281
  private cacheHits;
250
282
  constructor(gl: WebGL2RenderingContext);
251
- upload(stops: GradStop[]): string;
252
- bind(key: string, unit: number): void;
283
+ /** Rows the atlas currently holds — its texture height. */
284
+ get height(): number;
285
+ /**
286
+ * Bake `stops` into a row and return it, reusing the row an identical stop
287
+ * list already holds.
288
+ *
289
+ * **A returned row outlives the frame only while the atlas has room.** Past
290
+ * `RAMP_ATLAS_MAX_ROWS` an upload recycles the least recently used row, which
291
+ * rewrites texels a row handed out earlier still names. Nothing that defers
292
+ * its draw past this call may hold a row across another `upload` without
293
+ * arranging to be flushed first.
294
+ */
295
+ upload(stops: GradStop[]): number;
296
+ /**
297
+ * Whether uploading `stops` would move where existing rows sit — by growing
298
+ * the atlas, which changes every row's `v`, or by recycling one, which
299
+ * rewrites its texels.
300
+ *
301
+ * Anything holding a row past this call asks first and gets itself out of
302
+ * the way, because neither can be undone once it has happened.
303
+ */
304
+ wouldReshape(stops: GradStop[]): boolean;
305
+ /**
306
+ * The `v` a row is sampled at — its center.
307
+ *
308
+ * The center is load-bearing: the atlas filters LINEAR, so a `v` anywhere
309
+ * else blends the ramp beside it into this one. At the center the neighbor's
310
+ * weight is exactly zero.
311
+ */
312
+ rowV(row: number): number;
313
+ bind(unit: number): void;
253
314
  hitRate(): number;
254
315
  resetStats(): void;
255
316
  /**
256
- * Delete every uploaded GL ramp texture and clear the map. Called by
257
- * `WeaselRenderer.dispose()`. Only the GL textures are owned resources —
258
- * `buildGradientRamp`'s CPU-side `Uint8ClampedArray` is transient per
259
- * `upload()` call and already gone by the time a ramp is cached. The
260
- * cache is unusable but refillable afterward (stats are left as-is; call
317
+ * Delete the atlas texture and forget every row in it. Called by
318
+ * `WeaselRenderer.dispose()`. The CPU mirror goes with it, so the atlas is
319
+ * unusable but refillable afterward (stats are left as-is; call
261
320
  * `resetStats()` separately if desired).
262
321
  */
263
322
  free(): void;
323
+ /** The row the next ramp is written to: a free one, a taller atlas, or the
324
+ * least recently used row of a full one. */
325
+ private claimRow;
326
+ /** Grow to `rows`, keeping every row at the index it already had — a row
327
+ * index is a stable name, and callers hold them. */
328
+ private resize;
329
+ private writeRow;
264
330
  }
265
331
 
266
332
  /** Row-major 4×5 color matrix identity. */
@@ -279,6 +345,22 @@ declare class GroupState {
279
345
  get alpha(): number;
280
346
  get colorMatrix(): Float32Array;
281
347
  push(frame: GroupFrame): void;
348
+ /**
349
+ * Push a frame whose children paint into a surface of their own.
350
+ *
351
+ * The transform still accumulates — the children draw where they would have
352
+ * drawn. Alpha and colour do not: they describe how the finished surface
353
+ * joins the frame, and applying them on the way in as well would fade the
354
+ * pixels an effect is about to read, then fade them again on the way out.
355
+ *
356
+ * Returns the pair the caller must apply at composite time — what `push`
357
+ * would have left on the stack — so the composition rules live here and not
358
+ * in the dispatcher.
359
+ */
360
+ pushIsolated(frame: GroupFrame): {
361
+ alpha: number;
362
+ colorMatrix: Float32Array;
363
+ };
282
364
  /** Drop every pushed frame, leaving the root. A frame that throws part-way
283
365
  * down the tree never pops, and the next frame would draw under leftovers. */
284
366
  reset(): void;
@@ -286,33 +368,72 @@ declare class GroupState {
286
368
  }
287
369
 
288
370
  /**
289
- * Growable vertex staging for consecutive solid-fill geometry.
371
+ * Growable vertex staging for a run of solid-fill geometry, image quads and
372
+ * glyphs.
290
373
  *
291
374
  * Geometry only: `draw.ts` owns when a run starts, what breaks it, and the
292
- * uniforms the flush draws under. Colors ride the vertices (the batch program
293
- * is `pathFillVColor` with `u_color` left at white) because shapes in a run
294
- * differ in color and a merged draw has one set of uniforms — and so does the
295
- * model transform, applied here rather than uploaded, so that shapes under
296
- * different transforms still share a draw.
375
+ * uniforms the flush draws under.
376
+ *
377
+ * **One batch for all three, because a page interleaves them.** A grid of
378
+ * thumbnails is a ground rect under an atlas quad under a caption, per cell,
379
+ * and while each kind staged separately every one had to drain the others
380
+ * before it could stage — so a shape that batches perfectly in any one half
381
+ * alone paid a flush per command. Everything a vertex needs to say which it is
382
+ * fits in the same vertex: solids carry the UV of a 1x1 white texel and are
383
+ * their own color, quads carry their atlas UV and a white color, glyphs carry
384
+ * a font atlas UV, their text color, and a paint mode saying the texel is a
385
+ * distance field rather than a color. Gradients join as a fourth off the ramp
386
+ * atlas — a linear one without a mode of its own, since (ramp position, row) is
387
+ * what the plain mode already samples, and a radial or conic one with a mode
388
+ * that says its UV is a gradient-space coordinate to take a `length` or an
389
+ * `atan` of. See `shaders/batchFill.ts`.
390
+ *
391
+ * Colors ride the vertices because shapes in a run differ in color and a merged
392
+ * draw has one set of uniforms — and so does the model transform, applied here
393
+ * rather than uploaded, so shapes under different transforms still share a
394
+ * draw. The texture rides them too, as a slot index into the units the flush
395
+ * binds: slot 0 is the white texel every solid samples, and `draw.ts` hands out
396
+ * the rest per bitmap. A run holds as many bitmaps as there are slots, so an
397
+ * atlas is what makes a wall of one sheet coalesce and a handful of loose
398
+ * bitmaps no longer breaks a run per command.
297
399
  */
298
400
 
299
- declare class SolidBatch {
401
+ /**
402
+ * A gradient vertex's `a_uv`, each channel affine in the coordinates the
403
+ * geometry arrives in: `u = ux*x + uy*y + u0`, and the same for `v`.
404
+ *
405
+ * One shape for all three gradients. A linear one's `v` row is the constant
406
+ * atlas row and its `u` row is the ramp position; a radial or conic one's two
407
+ * rows are the gradient-space coordinate, and the row rides `a_post` instead.
408
+ */
409
+ interface GradientUV {
410
+ ux: number;
411
+ uy: number;
412
+ u0: number;
413
+ vx: number;
414
+ vy: number;
415
+ v0: number;
416
+ }
417
+ declare class DrawBatch {
300
418
  private readonly gl;
301
419
  private readonly aPos;
302
420
  private readonly aColor;
303
- /** Cycled per flush, and created on first use so a renderer that flushes
304
- * rarely allocates as few slots as it flushes. */
305
- private readonly ring;
306
- private next;
307
- /** The same, for flushes past a slot's capacity; these sets grow to fit. */
421
+ private readonly aUv;
422
+ private readonly aPost;
423
+ private readonly aSlot;
424
+ /** One ring per tier, cycled per flush. Slots are created on first use, so a
425
+ * renderer that flushes rarely allocates as few as it flushes. */
426
+ private readonly rings;
427
+ private readonly nextInRing;
428
+ /** The same, for flushes past the largest tier; these sets grow to fit. */
308
429
  private readonly largeRing;
309
430
  private nextLarge;
310
431
  private verts;
311
432
  private idx;
312
433
  private nVerts;
313
434
  private nIdx;
314
- /** Whether the staged run is rects alone, so its indices are the canonical
315
- * quad pattern and a slot already holding that pattern needs no upload. */
435
+ /** Whether the staged run is quads alone, so its indices are the canonical
436
+ * pattern and a slot already holding that pattern needs no upload. */
316
437
  private pureRects;
317
438
  constructor(gl: WebGL2RenderingContext, prog: ShaderProgram);
318
439
  get length(): number;
@@ -324,6 +445,62 @@ declare class SolidBatch {
324
445
  * cover it and the batch draws at `u_model` identity.
325
446
  */
326
447
  pushRect(x: number, y: number, w: number, h: number, m: Mat3, r: number, g: number, b: number, a: number): void;
448
+ /**
449
+ * Append one image quad: the destination rect `(x, y, w, h)` mapped through
450
+ * `m`, sampling `(u0, v0)`-`(u1, v1)`, every corner carrying `post` as the
451
+ * after-the-color-matrix alpha factor.
452
+ *
453
+ * Corners wind top-left, top-right, bottom-right, bottom-left with UVs
454
+ * following — the same winding `pushRect` uses, which is what lets a run of
455
+ * mixed rects and quads keep the canonical index pattern. The flips a command
456
+ * asks for are already in the `u` / `v` the caller passes.
457
+ *
458
+ * `slot` is the texture unit the corners sample, which `draw.ts` assigns per
459
+ * bitmap within the run.
460
+ */
461
+ pushQuad(x: number, y: number, w: number, h: number, m: Mat3, u0: number, v0: number, u1: number, v1: number, post: number, slot: number): void;
462
+ /**
463
+ * Append one glyph quad: the box `(x0, y0)`-`(x1, y1)` mapped through `m`,
464
+ * sampling `(u0, v0)`-`(u1, v1)` of the font atlas at `slot`, painted in
465
+ * `rgba`.
466
+ *
467
+ * `mode` says which channels of that atlas carry the distance field, and
468
+ * rides the vertices packed into the slot — so a run off a baked MSDF atlas
469
+ * and one off the runtime canvas bake still share a draw. The synthetic-bold
470
+ * threshold does not: it is `u_synthBold`, and `draw.ts` breaks the run when
471
+ * it changes.
472
+ *
473
+ * **A synthetic oblique is sheared here rather than in the shader.** The old
474
+ * text program carried the baseline per vertex and skewed against
475
+ * `u_synthItalic`; the batch places its own corners, so the shear is one
476
+ * multiply while they are being placed, and `tanItalic` is 0 for an upright
477
+ * face. It has to happen before `m`, which is where the shader had it too.
478
+ *
479
+ * Corners wind top-left, top-right, bottom-right, bottom-left — `pushQuad`'s
480
+ * winding, which is what lets a run of mixed rects, image quads and glyphs
481
+ * keep the canonical index pattern.
482
+ */
483
+ pushGlyph(x0: number, y0: number, x1: number, y1: number, baselineY: number, tanItalic: number, m: Mat3, u0: number, v0: number, u1: number, v1: number, r: number, g: number, b: number, a: number, slot: number, mode: number): void;
484
+ /**
485
+ * Append one rect filled by a gradient: the corners of `(x, y, w, h)` mapped
486
+ * through `m`, sampling the ramp atlas at `slot`.
487
+ *
488
+ * `uv` gives each channel of `a_uv` as an affine function of the coordinates
489
+ * the corners arrive in, which is what lets a gradient ride the vertices at
490
+ * all: a linear one's ramp position is affine in position outright, and a
491
+ * radial or conic one's gradient-space *coordinate* is, even though the ramp
492
+ * position it yields is not. Either way the rasterizer's interpolation across
493
+ * a triangle is exact. `mode` says which of the two the fragment shader is
494
+ * looking at, and `post` carries the atlas row for the modes that read it —
495
+ * see `shaders/batchFill.ts`.
496
+ *
497
+ * Values outside 0..1 are the sampler's business; the atlas clamps to the
498
+ * edge texel, which is what the gradient shader's own `clamp` did.
499
+ */
500
+ pushGradientRect(x: number, y: number, w: number, h: number, m: Mat3, uv: GradientUV, post: number, slot: number, mode: number, r: number, g: number, b: number, a: number): void;
501
+ /** `pushMesh` for a mesh filled by a gradient — see `pushGradientRect` for
502
+ * what `uv`, `post` and `mode` carry. */
503
+ pushGradientMesh(mesh: Mesh, m: Mat3, uv: GradientUV, post: number, slot: number, mode: number, r: number, g: number, b: number, a: number): void;
327
504
  /**
328
505
  * Append a tessellated mesh through `m`, all vertices carrying `rgba`. The
329
506
  * mesh's own indices are rebased onto the staged vertices, which is why the
@@ -335,6 +512,9 @@ declare class SolidBatch {
335
512
  uploadAndBind(): number;
336
513
  reset(): void;
337
514
  dispose(): void;
515
+ private writeVertex;
516
+ /** Two triangles over the four corners just written. */
517
+ private pushQuadIndices;
338
518
  /** Grow the CPU arrays so `vertices` / `indices` more fit. */
339
519
  private reserve;
340
520
  private nextRingSlot;
@@ -343,67 +523,6 @@ declare class SolidBatch {
343
523
  private deleteSet;
344
524
  }
345
525
 
346
- /**
347
- * Growable vertex staging for consecutive image quads.
348
- *
349
- * Geometry only: `draw.ts` owns when a run starts, what breaks it, and the
350
- * uniforms the flush draws under. The counterpart to `SolidBatch`, and the same
351
- * two tricks — the model transform is applied here rather than uploaded, so
352
- * quads under different group transforms still share a draw, and opacity rides
353
- * the vertices, so quads under different opacities do too. What it cannot
354
- * absorb is the texture: a batch samples one, which is why an atlas is what
355
- * makes a large run coalesce at all.
356
- *
357
- * Every run is quads, so a slot's index buffer is written once at creation and
358
- * never again: the pattern for N quads is a prefix of the pattern for any
359
- * larger N, so the pattern for a slot's capacity serves every flush it takes.
360
- * `SolidBatch` carries meshes, whose indices are rebased per flush, and has to
361
- * re-upload; this does not.
362
- */
363
-
364
- declare class ImageBatch {
365
- private readonly gl;
366
- private readonly aPos;
367
- private readonly aUv;
368
- private readonly aOpacity;
369
- /** One ring per tier, cycled per flush. Slots are created on first use, so a
370
- * renderer that flushes rarely allocates as few as it flushes. */
371
- private readonly rings;
372
- private readonly nextInRing;
373
- /** For flushes past the largest tier; these sets grow to fit. */
374
- private readonly largeRing;
375
- private nextLarge;
376
- private verts;
377
- private nQuads;
378
- constructor(gl: WebGL2RenderingContext, prog: ShaderProgram);
379
- /** Indices staged — what a caller passes to `drawElements`. */
380
- get length(): number;
381
- get quads(): number;
382
- /** Whether staging one more quad would put the run past the per-flush cap. */
383
- wouldOverflow(): boolean;
384
- /**
385
- * Append one image quad: the destination rect `(x, y, w, h)` mapped through
386
- * `m`, sampling `(u0, v0)`-`(u1, v1)`, every corner carrying `opacity`.
387
- *
388
- * An affine maps a rect to a parallelogram, so two triangles still cover it
389
- * and the batch draws at `u_model` identity. Corners wind top-left,
390
- * top-right, bottom-right, bottom-left, with UVs following — the flips a
391
- * command asks for are already in the `u`/`v` the caller passes.
392
- */
393
- pushQuad(x: number, y: number, w: number, h: number, m: Mat3, u0: number, v0: number, u1: number, v1: number, opacity: number): void;
394
- /** Upload the staged geometry into the next set of buffers and bind its VAO.
395
- * Returns the index count for the caller's `drawElements`. */
396
- uploadAndBind(): number;
397
- reset(): void;
398
- dispose(): void;
399
- /** Grow the CPU arrays so one more quad fits. */
400
- private reserve;
401
- private nextRingSlot;
402
- private nextLargeSlot;
403
- private createSet;
404
- private deleteSet;
405
- }
406
-
407
526
  /** How to construct a `WeaselRenderer`: the GL context or canvas to draw
408
527
  * into, the output size, and the quality knobs that separate screen
409
528
  * rendering from print. */
@@ -470,27 +589,30 @@ declare class WeaselRenderer {
470
589
  private readonly gl;
471
590
  private pathFill;
472
591
  private pathFillVColor;
473
- private textSdf;
474
- private textSdfR8;
475
592
  private imageFill;
476
- private imageFillVOpacity;
593
+ private batchFill;
477
594
  private gradFill;
478
595
  private patternFill;
479
596
  private meshCache;
480
597
  private textureCache;
481
598
  private imageCache;
482
- private gradRampCache;
599
+ private gradRamps;
483
600
  private programRegistry;
484
601
  private quadVbo;
485
602
  private quadIbo;
486
- private solidBatch;
487
- private imageBatch;
603
+ private drawBatch;
604
+ /** 1x1 white, so a batch flush sampling no image still samples something —
605
+ * see `shaders/batchFill.ts`. */
606
+ private whiteTexture;
488
607
  private readonly groupState;
489
608
  private widthCss;
490
609
  private heightCss;
491
610
  private dpr;
492
611
  private canvas;
493
612
  private target;
613
+ /** Offscreen buffers for group effects. Allocates nothing until a group
614
+ * with effects asks, so a canvas without them pays no memory. */
615
+ private readonly effectTargets;
494
616
  private readonly imageMinification;
495
617
  private readonly flattenTolerance?;
496
618
  private readonly bakeBudget;
@@ -541,7 +663,7 @@ declare class WeaselRenderer {
541
663
  * Scope: built-in shader programs, any consumer-registered programs, the
542
664
  * shared quad/rect geometry, any in-flight transient meshes, and the
543
665
  * enumerable Map-keyed caches (`GLTextureCache` atlas/image textures,
544
- * `GradientRampCache` ramp textures) ARE freed.
666
+ * `GradientRampAtlas`'s ramp texture) ARE freed.
545
667
  *
546
668
  * NOT freed: `GLImageCache` (bitmap/pattern textures) and `GLMeshCache`'s
547
669
  * persistent per-Path mesh cache are keyed by `WeakMap`, not enumerable,
@@ -577,18 +699,15 @@ declare class WeaselRenderer {
577
699
  /** @internal */ _gl(): WebGL2RenderingContext;
578
700
  /** @internal */ _pathFill(): ShaderProgram;
579
701
  /** @internal */ _pathFillVColor(): ShaderProgram;
580
- /** @internal */ _solidBatch(): SolidBatch;
581
- /** @internal */ _imageBatch(): ImageBatch;
582
- /** @internal */ _textSdf(): ShaderProgram;
583
- /** @internal */ _textSdfR8(): ShaderProgram;
702
+ /** @internal */ _drawBatch(): DrawBatch;
584
703
  /** @internal */ _imageFill(): ShaderProgram;
585
- /** @internal */ _imageFillVOpacity(): ShaderProgram;
704
+ /** @internal */ _batchFill(): ShaderProgram;
586
705
  /** @internal */ _gradFill(): ShaderProgram;
587
706
  /** @internal */ _patternFill(): ShaderProgram;
588
707
  /** @internal */ _meshCache(): GLMeshCache;
589
708
  /** @internal */ _textureCache(): GLTextureCache;
590
709
  /** @internal */ _imageCache(): GLImageCache;
591
- /** @internal */ _gradRampCache(): GradientRampCache;
710
+ /** @internal */ _gradRamps(): GradientRampAtlas;
592
711
  /** @internal */ _groupState(): GroupState;
593
712
  /** @internal */ _widthCss(): number;
594
713
  /** @internal */ _heightCss(): number;
@@ -688,4 +807,34 @@ declare function resolveStrokeWidth(width: number | {
688
807
  */
689
808
  declare function tessellateStroke(path: Path, stroke: Stroke, opts?: StrokeOptions): Mesh;
690
809
 
691
- export { IDENTITY_COLOR_MATRIX as I, type Mesh as M, type RenderTarget as R, ShaderCompileError as S, type View as V, WeaselRenderer as W, type ImageMinification as a, type SpriteSheet as b, type StrokeOptions as c, type WeaselRendererOptions as d, buildGradientRamp as e, frameRect as f, ShaderProgram as g, resolveStrokeWidth as r, tessellateStroke as t, viewToMat3 as v };
810
+ /**
811
+ * Effects that ship with the kit.
812
+ *
813
+ * Each is a function returning `Effect[]`, not a single `Effect`, because a
814
+ * separable kernel is genuinely two passes and hiding that behind one entry
815
+ * would make the cost invisible at the callsite:
816
+ *
817
+ * effects={[...blur({ radius: 4 }), ...vignette({ amount: 0.6 })]}
818
+ */
819
+
820
+ /**
821
+ * Gaussian blur, as a horizontal pass followed by a vertical one.
822
+ *
823
+ * `radius` is in device pixels and is the distance of the outermost tap, not a
824
+ * standard deviation — 0 is a copy, and the falloff is the same nine-tap
825
+ * kernel at every radius, so a large one is a wide blur rather than a better
826
+ * one. Two passes at O(9) beat one at O(81) and look the same.
827
+ */
828
+ declare function blur({ radius }: {
829
+ radius: number;
830
+ }): Effect[];
831
+ /**
832
+ * Darken toward the corners. `amount` is how dark the corner gets (0..1) and
833
+ * `feather` how much of the radius the falloff occupies.
834
+ */
835
+ declare function vignette({ amount, feather }: {
836
+ amount: number;
837
+ feather?: number;
838
+ }): Effect[];
839
+
840
+ export { IDENTITY_COLOR_MATRIX as I, type Mesh as M, type RenderTarget as R, ShaderCompileError as S, type View as V, WeaselRenderer as W, type ImageMinification as a, type SpriteSheet as b, type StrokeOptions as c, type WeaselRendererOptions as d, blur as e, buildGradientRamp as f, frameRect as g, vignette as h, ShaderProgram as i, resolveStrokeWidth as r, tessellateStroke as t, viewToMat3 as v };
@@ -1,5 +1,5 @@
1
- import { RECT_ORIGIN_PROJECTION } from './chunk-SBFC6J3C.js';
2
- import { registerOpFactory, captureSlot, slotFromIndex, resolveSlot } from './chunk-2KKYDDDD.js';
1
+ import { RECT_ORIGIN_PROJECTION } from './chunk-WPM42WJP.js';
2
+ import { registerOpFactory, captureSlot, slotFromIndex, resolveSlot } from './chunk-4RJP2N2L.js';
3
3
 
4
4
  // src/core/ops/transform.ts
5
5
  function createTransformOp(args) {
@@ -142,5 +142,5 @@ function snap(strategy, opts = {}) {
142
142
  }
143
143
 
144
144
  export { createReparentOp, createTransformOp, deleteScratch, getScratch, scratchKey, setScratch, snap };
145
- //# sourceMappingURL=chunk-Y5KS66G7.js.map
146
- //# sourceMappingURL=chunk-Y5KS66G7.js.map
145
+ //# sourceMappingURL=chunk-2VXGHUVL.js.map
146
+ //# sourceMappingURL=chunk-2VXGHUVL.js.map