@weasel-js/renderer 1.10.0 → 1.11.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.
package/dist/index.js CHANGED
@@ -1,18 +1,18 @@
1
1
  export { createTilePattern } from './chunk-43YL7HZV.js';
2
- import { BATCH_ATTRIBUTE_LOCATIONS, BATCH_TEXTURE_SLOTS, registerProgram, BATCH_VERT_SRC, BATCH_FRAG_SRC, BATCH_FILL_UNIFORMS, BATCH_FILL_ATTRIBUTES, STRAIGHT_RGB_GLSL, GLImageCache, DrawBatch, getProgramSource, commandFill, commandPaint, uploadPremultiplied, cachedMarkerCommands, PAINT_MODE_PLAIN, PAINT_MODE_RADIAL, PAINT_MODE_CONIC, getMesh, quantizeStrokeScale, strokeMesh, axisAlignedClipRect, intersectClipRects, getPaintDrawKind, outlineMesh, outlineStrokeMesh, quantizeEmWidth } from './chunk-LFD47OV2.js';
3
- export { commandFill, commandPaint, commandStroke, fillInPoseFrame, fillToBoundsFrame, getImageBitmap, getPaintDrawKind, getProgramSource, imageStatus, isPaintKindKnown, isVectorImageSrc, paintDrawKindRegistry, programSourceRegistry, registerPaintDrawKind, registerPaintDrawKindLoader, registerProgram, releaseImage, releaseImageSource, resolveFillPattern, resolvePatternSpec, retryImage, setImageCacheBudget, subscribeImageReady, warmPaintKinds } from './chunk-LFD47OV2.js';
2
+ import { BATCH_ATTRIBUTE_LOCATIONS, BATCH_TEXTURE_SLOTS, registerProgram, quantizeStrokeScale, STRAIGHT_RGB_GLSL, BATCH_FILL_ATTRIBUTES, BATCH_FILL_UNIFORMS, BATCH_MATRICES_FRAG_SRC, BATCH_VERT_SRC, GLImageCache, DrawBatch, getProgramSource, paintDrawKindRegistry, commandFill, commandPaint, BATCH_FRAG_SRC, uploadPremultiplied, RAMP_SLOT, withMatrix, cachedMarkerCommands, BATCH_MATRIX_SLOTS, packSlot, WHITE_SLOT, PAINT_MODE_PLAIN, PAINT_MODE_RADIAL, PAINT_MODE_CONIC, getMesh, strokeMesh, axisAlignedClipRect, intersectClipRects, getPaintDrawKind, outlineMesh, outlineStrokeMesh, quantizeEmWidth } from './chunk-CUJEWF5G.js';
3
+ export { bitmapSrc, commandFill, commandPaint, commandStroke, fillInPoseFrame, fillToBoundsFrame, getImageBitmap, getPaintDrawKind, getProgramSource, imageStatus, isPaintKindKnown, isVectorImageSrc, paintDrawKindRegistry, programSourceRegistry, registerPaintDrawKind, registerPaintDrawKindLoader, registerProgram, releaseImage, releaseImageSource, resolveFillPattern, resolvePatternSpec, retryImage, setImageCacheBudget, subscribeImageReady, warmPaintKinds } from './chunk-CUJEWF5G.js';
4
4
  import { textureRegistry, getTexture } from './chunk-Q4SIHV7Q.js';
5
5
  export { registerTexture, textureRegistry, unregisterTexture } from './chunk-Q4SIHV7Q.js';
6
- import { DEFAULT_BAKE_BUDGET, markAllFontsNotUploaded, resetBakeBudget, GLYPH_COVERAGE_GLSL, syncColorPageTexture, colorPageTextureId, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey, GLYPH_MODE_R8, GLYPH_MODE_MSDF, glyphFieldScale } from '@weasel-js/font';
6
+ import { DEFAULT_BAKE_BUDGET, markAllFontsNotUploaded, glyphGeneration, resetBakeBudget, GLYPH_COVERAGE_GLSL, syncColorPageTexture, colorPageTextureId, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey, GLYPH_MODE_R8, GLYPH_MODE_MSDF, glyphFieldScale, SYNTH_BOLD_AMOUNT, sharperColorGlyph, syncSharpColorPageTexture, sharpColorPageTextureId, DYNAMIC_PAGE_SIZE } from '@weasel-js/font';
7
7
  export { canQueryLocalFonts, cssFontFamily, cssFontFamilyLoading, enableLocalFontOutlines, fontOutlineRegistry, fontRegistry, hasFontOutlines, isCanvasFont, listFontOutlines, loadFontOutlines, outlineStatus, registerCanvasFont, registerFont, registerFontOutlines, subscribeGlyphReady, unregisterCanvasFont, unregisterFontOutlines } from '@weasel-js/font';
8
- import { resolveGradientStops, sampleResolvedStops, resolveScreenLength, isSolidPaint, resolveColor, strokeHasInk } from '@weasel-js/paint';
9
- import { mat3 } from '@weasel-js/geom';
8
+ import { resolveGradientStops, sampleResolvedStops, resolveScreenLength, isSolidPaint, strokeHasInk, resolveColor, scaleVertexWidth } from '@weasel-js/paint';
9
+ import { mat3, DEFAULT_FLATTEN_TOLERANCE, resolveCornersWithSources, remapPerAnchor } from '@weasel-js/geom';
10
10
  export { mat3, viewToMat3 } from '@weasel-js/geom';
11
- import { tessellate } from '@weasel-js/geom/tessellate';
12
- export { tessellate } from '@weasel-js/geom/tessellate';
13
- import { resolveStrokeWidth, markerReach, metricKeepsRects, strokeSpaceOf, resolveMarkerSize } from '@weasel-js/stroke';
11
+ import { isDev } from '@weasel-js/registry';
12
+ import { resolveStrokeWidth, widestStrokeWidth, markerReach, strokeSpaceOf, resolveMarkerSize, metricKeepsRects } from '@weasel-js/stroke';
14
13
  export { tessellateStroke } from '@weasel-js/stroke';
15
14
  import { resolveTextStyle, resolveRuns, textPoseLayoutInput, cachedLayoutRuns, cachedPaintRuns, verticalAlignOffset, SYNTHETIC_ITALIC_RADIANS } from '@weasel-js/text';
15
+ export { tessellate } from '@weasel-js/geom/tessellate';
16
16
 
17
17
  // src/paintResources.ts
18
18
  function createPaintResources() {
@@ -229,7 +229,6 @@ uniform mat4 u_colorMatrix;
229
229
  uniform vec4 u_colorBias;
230
230
  uniform float u_opacity;
231
231
  uniform int u_gradKind;
232
- uniform vec2 u_gradP0;
233
232
  uniform vec2 u_gradDir;
234
233
  uniform float u_gradLen;
235
234
  uniform float u_gradRadius;
@@ -240,12 +239,11 @@ const float PI = 3.14159265358979323846;
240
239
  vec4 shadePaint(vec2 world) {
241
240
  float t;
242
241
  if (u_gradKind == 0) {
243
- vec2 d = world - u_gradP0;
244
- t = dot(d, u_gradDir) / max(u_gradLen, 0.0001);
242
+ t = dot(world, u_gradDir) / max(u_gradLen, 0.0001);
245
243
  } else if (u_gradKind == 1) {
246
- t = length(world - u_gradP0) / max(u_gradRadius, 0.0001);
244
+ t = length(world) / max(u_gradRadius, 0.0001);
247
245
  } else {
248
- float a = atan(world.y - u_gradP0.y, world.x - u_gradP0.x) - u_gradAngle;
246
+ float a = atan(world.y, world.x) - u_gradAngle;
249
247
  t = fract(a / (2.0 * PI));
250
248
  }
251
249
  t = clamp(t, 0.0, 1.0);
@@ -284,7 +282,6 @@ var GRAD_FILL_UNIFORMS = [
284
282
  "u_colorMatrix",
285
283
  "u_colorBias",
286
284
  "u_gradKind",
287
- "u_gradP0",
288
285
  "u_gradDir",
289
286
  "u_gradLen",
290
287
  "u_gradRadius",
@@ -355,6 +352,72 @@ var PATTERN_FILL_UNIFORMS = [
355
352
  "u_colorBias"
356
353
  ];
357
354
  var PATTERN_FILL_ATTRIBUTES = ["a_position"];
355
+
356
+ // src/shaders/builtinPrograms.ts
357
+ var LazyProgram = class {
358
+ constructor(gl, spec) {
359
+ this.gl = gl;
360
+ this.spec = spec;
361
+ }
362
+ gl;
363
+ spec;
364
+ program = null;
365
+ get() {
366
+ if (!this.program) {
367
+ const { vert, frag, uniforms, attributes } = this.spec;
368
+ const program = new ShaderProgram(this.gl, vert, frag);
369
+ program.lookupUniforms(uniforms);
370
+ program.lookupAttributes(attributes);
371
+ this.program = program;
372
+ }
373
+ return this.program;
374
+ }
375
+ /** Delete the program if it was built. It builds again on the next `get`. */
376
+ release() {
377
+ if (this.program) this.gl.deleteProgram(this.program.handle);
378
+ this.program = null;
379
+ }
380
+ /** Forget a program its lost context already took with it. */
381
+ abandon() {
382
+ this.program = null;
383
+ }
384
+ };
385
+ var PATH_FILL_SPEC = {
386
+ vert: VERT_SRC,
387
+ frag: FRAG_SRC,
388
+ uniforms: PATH_FILL_UNIFORMS,
389
+ attributes: PATH_FILL_ATTRIBUTES
390
+ };
391
+ var PATH_FILL_VCOLOR_SPEC = {
392
+ vert: VCOLOR_VERT_SRC,
393
+ frag: VCOLOR_FRAG_SRC,
394
+ uniforms: PATH_FILL_UNIFORMS,
395
+ attributes: PATH_FILL_VCOLOR_ATTRIBUTES
396
+ };
397
+ var GRAD_FILL_SPEC = {
398
+ vert: GRAD_VERT_SRC,
399
+ frag: GRAD_FRAG_SRC,
400
+ uniforms: GRAD_FILL_UNIFORMS,
401
+ attributes: GRAD_FILL_ATTRIBUTES
402
+ };
403
+ var BATCH_FILL_MATRICES_SPEC = {
404
+ vert: BATCH_VERT_SRC,
405
+ frag: BATCH_MATRICES_FRAG_SRC,
406
+ uniforms: BATCH_FILL_UNIFORMS,
407
+ attributes: BATCH_FILL_ATTRIBUTES
408
+ };
409
+ var PATTERN_FILL_SPEC = {
410
+ vert: PATTERN_VERT_SRC,
411
+ frag: PATTERN_FRAG_SRC,
412
+ uniforms: PATTERN_FILL_UNIFORMS,
413
+ attributes: PATTERN_FILL_ATTRIBUTES
414
+ };
415
+ function createBatchFill(gl) {
416
+ const program = new ShaderProgram(gl, BATCH_VERT_SRC, BATCH_FRAG_SRC);
417
+ program.lookupUniforms(BATCH_FILL_UNIFORMS);
418
+ program.lookupAttributes(BATCH_FILL_ATTRIBUTES);
419
+ return program;
420
+ }
358
421
  var L = BATCH_ATTRIBUTE_LOCATIONS;
359
422
  var GLYPH_PAINT_VERT_SRC = (
360
423
  /* glsl */
@@ -1064,6 +1127,22 @@ var GroupState = class {
1064
1127
  this.colorMatrixStack.pop();
1065
1128
  }
1066
1129
  };
1130
+ var DEFAULT_FLATTEN_TOLERANCE_PX = DEFAULT_FLATTEN_TOLERANCE;
1131
+ function worldFlattenTolerance(tolerance, transform) {
1132
+ if (typeof tolerance === "number") return tolerance;
1133
+ const px = tolerance?.px ?? DEFAULT_FLATTEN_TOLERANCE_PX;
1134
+ const [a, b, , c, d] = transform;
1135
+ const sum = a * a + b * b + c * c + d * d;
1136
+ const det = a * d - b * c;
1137
+ const stretch = Math.sqrt((sum + Math.sqrt(Math.max(0, sum * sum - 4 * det * det))) / 2);
1138
+ const world = px / stretch;
1139
+ if (!(world > 0) || !Number.isFinite(world)) return DEFAULT_FLATTEN_TOLERANCE;
1140
+ return 2 ** Math.floor(Math.log2(world));
1141
+ }
1142
+ function inCssPixels(tolerance, dpr) {
1143
+ if (typeof tolerance === "number") return tolerance;
1144
+ return { px: (tolerance?.px ?? DEFAULT_FLATTEN_TOLERANCE_PX) / dpr };
1145
+ }
1067
1146
 
1068
1147
  // src/effects/effectPrelude.ts
1069
1148
  var EFFECT_VERT_SRC = (
@@ -1196,14 +1275,11 @@ function endParityMask(ctx) {
1196
1275
  gl.clear(gl.STENCIL_BUFFER_BIT);
1197
1276
  gl.disable(gl.STENCIL_TEST);
1198
1277
  }
1199
-
1200
- // src/draw/customShader.ts
1201
1278
  var warnedUniforms = /* @__PURE__ */ new Set();
1202
1279
  function warnOnce(key, message) {
1203
1280
  if (warnedUniforms.has(key)) return;
1204
1281
  warnedUniforms.add(key);
1205
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
1206
- if (isDev) console.warn(message);
1282
+ if (isDev()) console.warn(message);
1207
1283
  }
1208
1284
  function warnOnceUniform(programId, name) {
1209
1285
  warnOnce(
@@ -1216,8 +1292,7 @@ function setUniform(gl, loc, value, textureCache, nextTexUnit) {
1216
1292
  const handle = value;
1217
1293
  const entry = getTexture(handle.id);
1218
1294
  if (!entry) {
1219
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
1220
- if (isDev) console.warn(`weasel setUniform: TextureHandle "${handle.id}" not registered`);
1295
+ if (isDev()) console.warn(`weasel setUniform: TextureHandle "${handle.id}" not registered`);
1221
1296
  return;
1222
1297
  }
1223
1298
  const unit = nextTexUnit.value++;
@@ -1234,8 +1309,7 @@ function setUniform(gl, loc, value, textureCache, nextTexUnit) {
1234
1309
  } else if (value.length === 16) {
1235
1310
  gl.uniformMatrix4fv(loc, false, value);
1236
1311
  } else {
1237
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
1238
- if (isDev) throw new TypeError(`weasel setUniform: Float32Array must be length 9 (mat3) or 16 (mat4), got ${value.length}`);
1312
+ if (isDev()) throw new TypeError(`weasel setUniform: Float32Array must be length 9 (mat3) or 16 (mat4), got ${value.length}`);
1239
1313
  }
1240
1314
  return;
1241
1315
  }
@@ -1252,8 +1326,7 @@ function setUniform(gl, loc, value, textureCache, nextTexUnit) {
1252
1326
  gl.uniform4fv(loc, arr);
1253
1327
  break;
1254
1328
  default: {
1255
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
1256
- if (isDev) throw new TypeError(`weasel setUniform: array length ${arr.length} not supported`);
1329
+ if (isDev()) throw new TypeError(`weasel setUniform: array length ${arr.length} not supported`);
1257
1330
  }
1258
1331
  }
1259
1332
  return;
@@ -1297,14 +1370,13 @@ function isArrayValue(value) {
1297
1370
  return Array.isArray(value) || value instanceof Float32Array || value instanceof Int32Array || value instanceof Uint32Array;
1298
1371
  }
1299
1372
  function setUniformArray(gl, name, arr, value) {
1300
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
1301
1373
  const upload = ARRAY_UPLOADS[arr.type];
1302
1374
  if (!upload) {
1303
- if (isDev) console.warn(`weasel setUniform: array uniform "${name}" has type ${arr.type}, which cannot be written as a whole`);
1375
+ if (isDev()) console.warn(`weasel setUniform: array uniform "${name}" has type ${arr.type}, which cannot be written as a whole`);
1304
1376
  return;
1305
1377
  }
1306
1378
  if (value.length % upload.n !== 0 || value.length > upload.n * arr.size) {
1307
- if (isDev) {
1379
+ if (isDev()) {
1308
1380
  throw new TypeError(
1309
1381
  `weasel setUniform: "${name}" is ${arr.type}[${arr.size}], so its value must hold a multiple of ${upload.n} numbers, at most ${upload.n * arr.size}; got ${value.length}`
1310
1382
  );
@@ -1499,7 +1571,8 @@ function returnToRenderTarget(ctx, width, height, scissorWasOn) {
1499
1571
  const target = ctx.renderTarget ?? null;
1500
1572
  gl.bindFramebuffer(gl.FRAMEBUFFER, target ? target.fbo : null);
1501
1573
  if (target) {
1502
- gl.viewport(0, 0, width, height);
1574
+ if (target.restore) target.restore();
1575
+ else gl.viewport(0, 0, width, height);
1503
1576
  } else {
1504
1577
  ctx.restoreTargetRect?.();
1505
1578
  if (scissorWasOn) gl.enable(gl.SCISSOR_TEST);
@@ -1540,19 +1613,97 @@ function drawFullscreenQuad(ctx, program, texture, width, height, uniforms, comp
1540
1613
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
1541
1614
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
1542
1615
  }
1616
+
1617
+ // src/draw/mesh.ts
1618
+ function flattenToleranceOf(ctx) {
1619
+ return worldFlattenTolerance(ctx.flattenTolerance, ctx.state.transform);
1620
+ }
1543
1621
  function fillMesh(ctx, path) {
1544
- if (ctx.flattenTolerance !== void 0) {
1545
- return tessellate(path, { flattenTolerance: ctx.flattenTolerance });
1546
- }
1547
- return getMesh(path);
1622
+ return getMesh(path, flattenToleranceOf(ctx));
1548
1623
  }
1549
1624
  function meshHandle(ctx, mesh) {
1550
- if (ctx.flattenTolerance !== void 0) return ctx.meshCache.uploadTransient(mesh);
1551
1625
  return ctx.meshCache.handleFor(mesh);
1552
1626
  }
1553
1627
  function fillMeshHandle(ctx, path) {
1554
1628
  return meshHandle(ctx, fillMesh(ctx, path));
1555
1629
  }
1630
+
1631
+ // src/draw/foldColor.ts
1632
+ function isIdentityColorMatrix(cm) {
1633
+ return cm === IDENTITY_COLOR_MATRIX || sameValues(IDENTITY_COLOR_MATRIX, cm);
1634
+ }
1635
+ var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
1636
+ function foldColor(ctx, r, g, b, a) {
1637
+ const cm = ctx.state.colorMatrix;
1638
+ const alpha = ctx.state.alpha;
1639
+ if (isIdentityColorMatrix(cm)) return [r, g, b, a * alpha];
1640
+ return [
1641
+ clamp01(cm[0] * r + cm[1] * g + cm[2] * b + cm[3] * a + cm[4]),
1642
+ clamp01(cm[5] * r + cm[6] * g + cm[7] * b + cm[8] * a + cm[9]),
1643
+ clamp01(cm[10] * r + cm[11] * g + cm[12] * b + cm[13] * a + cm[14]),
1644
+ clamp01(cm[15] * r + cm[16] * g + cm[17] * b + cm[18] * a + cm[19]) * alpha
1645
+ ];
1646
+ }
1647
+ var NO_TINT = [1, 1, 1, 1];
1648
+ var DIAGONAL = [0, 6, 12, 18];
1649
+ function textureTint(ctx) {
1650
+ const cm = ctx.state.colorMatrix;
1651
+ const alpha = ctx.state.alpha;
1652
+ if (isIdentityColorMatrix(cm)) return alpha === 1 ? NO_TINT : [1, 1, 1, alpha];
1653
+ for (let i = 0; i < 20; i++) {
1654
+ const v = cm[i];
1655
+ if (DIAGONAL.includes(i) ? v < 0 || v > 1 : v !== 0) return null;
1656
+ }
1657
+ return [cm[0], cm[6], cm[12], cm[18] * alpha];
1658
+ }
1659
+
1660
+ // src/draw/batchMatrices.ts
1661
+ function matrixIndex(matrices, colorMatrix, alpha) {
1662
+ for (let i = 0; i < matrices.length; i++) {
1663
+ const m = matrices[i];
1664
+ if (m.alpha === alpha && (m.colorMatrix === colorMatrix || sameValues(m.colorMatrix, colorMatrix))) return i;
1665
+ }
1666
+ return matrices.length < (BATCH_MATRIX_SLOTS ) ? matrices.length : -1;
1667
+ }
1668
+ var MATRICES = new Float32Array(16 * BATCH_MATRIX_SLOTS);
1669
+ var BIASES = new Float32Array(4 * BATCH_MATRIX_SLOTS);
1670
+ var ALPHAS = new Float32Array(BATCH_MATRIX_SLOTS);
1671
+ var UPLOADED = /* @__PURE__ */ new WeakMap();
1672
+ function holds(held, matrices) {
1673
+ if (held.length < matrices.length) return false;
1674
+ for (let i = 0; i < matrices.length; i++) {
1675
+ const a = held[i];
1676
+ const b = matrices[i];
1677
+ if (a.alpha !== b.alpha) return false;
1678
+ if (a.colorMatrix !== b.colorMatrix && !sameValues(a.colorMatrix, b.colorMatrix)) return false;
1679
+ }
1680
+ return true;
1681
+ }
1682
+ function uploadBatchMatrices(ctx, prog, matrices) {
1683
+ let byProgram = UPLOADED.get(ctx);
1684
+ if (!byProgram) {
1685
+ byProgram = /* @__PURE__ */ new WeakMap();
1686
+ UPLOADED.set(ctx, byProgram);
1687
+ }
1688
+ const held = byProgram.get(prog);
1689
+ if (held !== void 0 && holds(held, matrices)) return;
1690
+ const n = matrices.length;
1691
+ for (let i = 0; i < n; i++) {
1692
+ const cm = matrices[i].colorMatrix;
1693
+ for (let row = 0; row < 4; row++) {
1694
+ for (let col = 0; col < 4; col++) MATRICES[16 * i + col * 4 + row] = cm[row * 5 + col];
1695
+ BIASES[4 * i + row] = cm[row * 5 + 4];
1696
+ }
1697
+ ALPHAS[i] = matrices[i].alpha;
1698
+ }
1699
+ const gl = ctx.gl;
1700
+ gl.uniformMatrix4fv(prog.uniform("u_colorMatrix"), false, MATRICES.subarray(0, 16 * n));
1701
+ gl.uniform4fv(prog.uniform("u_colorBias"), BIASES.subarray(0, 4 * n));
1702
+ gl.uniform1fv(prog.uniform("u_alpha"), ALPHAS.subarray(0, n));
1703
+ byProgram.set(prog, matrices.slice());
1704
+ }
1705
+
1706
+ // src/draw/flushBatch.ts
1556
1707
  var BATCH_MODEL = mat3.identity();
1557
1708
  var SAMPLER_UNITS = new Int32Array(
1558
1709
  Array.from({ length: BATCH_TEXTURE_SLOTS }, (_, i) => i)
@@ -1566,14 +1717,12 @@ function flushBatch(ctx) {
1566
1717
  return;
1567
1718
  }
1568
1719
  const gl = ctx.gl;
1569
- const prog = ctx.batchFill;
1720
+ const prog = staged.matrices.length > 1 ? ctx.batchFillMatrices ?? ctx.batchFill : ctx.batchFill;
1570
1721
  gl.useProgram(prog.handle);
1571
1722
  const indexCount = batch.uploadAndBind();
1572
1723
  setProjAndModel(ctx, prog, BATCH_MODEL);
1573
1724
  setColorUniform(ctx, prog, 1, 1, 1, 1);
1574
- setAlphaUniform(ctx, prog, staged.alpha);
1575
- gl.uniform1f(prog.uniform("u_synthBold"), staged.synthBold);
1576
- setColorMatrixUniforms(ctx, prog, staged.colorMatrix);
1725
+ uploadBatchMatrices(ctx, prog, staged.matrices);
1577
1726
  gl.activeTexture(gl.TEXTURE0);
1578
1727
  gl.bindTexture(gl.TEXTURE_2D, ctx.whiteTexture);
1579
1728
  for (let i = 0; i < staged.textures.length; i++) {
@@ -1584,16 +1733,13 @@ function flushBatch(ctx) {
1584
1733
  FIELD_SCALES[2 * (i + 1) + 1] = entry.fieldScale[1];
1585
1734
  continue;
1586
1735
  }
1587
- if (entry.kind === "ramps") {
1588
- ctx.gradRamps.bind(i + 1);
1589
- continue;
1590
- }
1591
1736
  ctx.imageCache.bind(entry.image, i + 1);
1592
1737
  ctx.imageCache.setMagFilter(
1593
1738
  entry.image,
1594
1739
  entry.sampling === "nearest" ? gl.NEAREST : gl.LINEAR
1595
1740
  );
1596
1741
  }
1742
+ if (staged.ramps) ctx.gradRamps.bind(RAMP_SLOT);
1597
1743
  gl.uniform1iv(prog.uniform("u_samplers"), SAMPLER_UNITS);
1598
1744
  gl.uniform2fv(prog.uniform("u_fieldScale"), FIELD_SCALES);
1599
1745
  applyClipTest(ctx, staged.clipDepth);
@@ -1605,22 +1751,21 @@ function flushBatch(ctx) {
1605
1751
  }
1606
1752
 
1607
1753
  // src/draw/clip.ts
1608
- function enterGroupClip(ctx, clip) {
1754
+ function enterGroupClip(ctx, clip, transform = ctx.state.transform) {
1609
1755
  const pending = ctx.clipStack.reduce((n, e) => n + (e.depth === 0 ? 1 : 0), 0);
1610
1756
  if (ctx.clipDepth + pending + 1 > 7) {
1611
1757
  throw new Error(
1612
1758
  "weasel: clip nesting depth exceeded (max 7). You can't nest more than 7 levels of clipped containers in a single draw tree. Flatten the hierarchy or compose poses outside the scene graph."
1613
1759
  );
1614
1760
  }
1615
- enterClip(ctx, clip);
1761
+ enterClip(ctx, clip, transform);
1616
1762
  }
1617
- function enterClip(ctx, path) {
1618
- const transform = ctx.state.transform;
1763
+ function enterClip(ctx, path, transform) {
1619
1764
  const rect = axisAlignedClipRect(path, transform);
1620
1765
  if (rect === null) {
1621
1766
  materializeClips(ctx);
1622
1767
  const depth = ctx.clipDepth + 1;
1623
- pushClip(ctx, path, depth);
1768
+ pushClip(ctx, path, depth, transform);
1624
1769
  ctx.clipDepth = depth;
1625
1770
  ctx.clipStack.push({ path, transform, rect: null, depth });
1626
1771
  } else {
@@ -1734,6 +1879,287 @@ function popClip(ctx, path, oldDepth, model = ctx.state.transform) {
1734
1879
  gl.colorMask(true, true, true, true);
1735
1880
  gl.stencilMask(255);
1736
1881
  }
1882
+
1883
+ // src/DrawCommand.ts
1884
+ var SPRITE_STRIDE = 9;
1885
+ var CULL_SLACK_PX = 1;
1886
+ var DEFAULT_MITER_LIMIT = 4;
1887
+ function cullDrawCommands(cmds, transform, rect) {
1888
+ return cullList(cmds, transform, slackBox(rect));
1889
+ }
1890
+ function cullList(cmds, m, box) {
1891
+ let out = null;
1892
+ for (let i = 0; i < cmds.length; i++) {
1893
+ const cmd = cmds[i];
1894
+ const kept = cullOne(cmd, m, box);
1895
+ if (kept === cmd) {
1896
+ out?.push(cmd);
1897
+ continue;
1898
+ }
1899
+ out ??= cmds.slice(0, i);
1900
+ if (kept) out.push(kept);
1901
+ }
1902
+ return out ?? cmds;
1903
+ }
1904
+ function cullOne(cmd, m, box) {
1905
+ switch (cmd.kind) {
1906
+ case "group": {
1907
+ if (cmd.effects && cmd.effects.length > 0) return cmd;
1908
+ if (isOwnComposite(cmd.composite)) return cmd;
1909
+ const inner = cmd.transform ? mat3.multiply(m, cmd.transform) : m;
1910
+ const children = cullList(cmd.children, inner, box);
1911
+ if (children === cmd.children) return cmd;
1912
+ return children.length === 0 ? null : { ...cmd, children };
1913
+ }
1914
+ case "path":
1915
+ case "image":
1916
+ case "sprites":
1917
+ return boxMisses(paintedBox(cmd, m), box) ? null : cmd;
1918
+ default:
1919
+ return cmd;
1920
+ }
1921
+ }
1922
+ function paintedBox(cmd, m) {
1923
+ switch (cmd.kind) {
1924
+ case "path":
1925
+ return pathBox(cmd.path, cmd.stroke, m);
1926
+ case "image":
1927
+ return screenBox(cmd.x, cmd.y, cmd.x + cmd.w, cmd.y + cmd.h, m);
1928
+ case "sprites":
1929
+ return spritesBox(cmd.sprites, m);
1930
+ }
1931
+ }
1932
+ function pathBox(path, stroke, m) {
1933
+ let minX, minY, maxX, maxY;
1934
+ if (path.kind === "rect") {
1935
+ minX = Math.min(path.x, path.x + path.width);
1936
+ maxX = Math.max(path.x, path.x + path.width);
1937
+ minY = Math.min(path.y, path.y + path.height);
1938
+ maxY = Math.max(path.y, path.y + path.height);
1939
+ } else if (path.kind === "polygon") {
1940
+ const c = path.coords;
1941
+ if (c.length < 2) return null;
1942
+ minX = maxX = c[0];
1943
+ minY = maxY = c[1];
1944
+ for (let i = 2; i + 1 < c.length; i += 2) {
1945
+ const x = c[i], y = c[i + 1];
1946
+ if (x < minX) minX = x;
1947
+ else if (x > maxX) maxX = x;
1948
+ if (y < minY) minY = y;
1949
+ else if (y > maxY) maxY = y;
1950
+ }
1951
+ } else {
1952
+ return null;
1953
+ }
1954
+ const reach = stroke ? strokeReach(stroke, m) : 0;
1955
+ return screenBox(minX - reach, minY - reach, maxX + reach, maxY + reach, m);
1956
+ }
1957
+ function strokeReach(stroke, m) {
1958
+ return strokeReachAt(stroke, mat3.meanScaleOf(m), mat3.minScaleOf(m));
1959
+ }
1960
+ function strokeReachAt(stroke, scale, least = scale) {
1961
+ const at = least > 0 ? least : scale;
1962
+ const base = resolveStrokeWidth(stroke.width ?? 1, typeof stroke.width === "object" ? at : scale);
1963
+ const width = widestStrokeWidth(base, stroke.vertexWidths);
1964
+ const spike = Math.max(stroke.miterLimit ?? DEFAULT_MITER_LIMIT, Math.SQRT2);
1965
+ const heads = markerReach(stroke);
1966
+ return Math.max(Math.abs(width) * spike, heads.world, resolveScreenLength({ px: heads.px }, at));
1967
+ }
1968
+ function spritesBox(sprites, m) {
1969
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1970
+ for (let i = 0; i + SPRITE_STRIDE <= sprites.length; i += SPRITE_STRIDE) {
1971
+ const x0 = sprites[i], y0 = sprites[i + 1];
1972
+ const x1 = x0 + sprites[i + 2], y1 = y0 + sprites[i + 3];
1973
+ minX = Math.min(minX, x0, x1);
1974
+ maxX = Math.max(maxX, x0, x1);
1975
+ minY = Math.min(minY, y0, y1);
1976
+ maxY = Math.max(maxY, y0, y1);
1977
+ }
1978
+ if (minX === Infinity) return null;
1979
+ return screenBox(minX, minY, maxX, maxY, m);
1980
+ }
1981
+ function boundsMissRect(bounds, transform, rect) {
1982
+ const x0 = Math.min(bounds.x, bounds.x + bounds.width);
1983
+ const x1 = Math.max(bounds.x, bounds.x + bounds.width);
1984
+ const y0 = Math.min(bounds.y, bounds.y + bounds.height);
1985
+ const y1 = Math.max(bounds.y, bounds.y + bounds.height);
1986
+ return boxMisses(screenBox(x0, y0, x1, y1, transform), slackBox(rect));
1987
+ }
1988
+ function slackBox(rect) {
1989
+ return {
1990
+ minX: rect.x - CULL_SLACK_PX,
1991
+ minY: rect.y - CULL_SLACK_PX,
1992
+ maxX: rect.x + rect.width + CULL_SLACK_PX,
1993
+ maxY: rect.y + rect.height + CULL_SLACK_PX
1994
+ };
1995
+ }
1996
+ function screenBox(x0, y0, x1, y1, m) {
1997
+ const [ax, ay] = mat3.apply(m, x0, y0);
1998
+ const [bx, by] = mat3.apply(m, x1, y0);
1999
+ const [cx, cy] = mat3.apply(m, x1, y1);
2000
+ const [dx, dy] = mat3.apply(m, x0, y1);
2001
+ return {
2002
+ minX: Math.min(ax, bx, cx, dx),
2003
+ minY: Math.min(ay, by, cy, dy),
2004
+ maxX: Math.max(ax, bx, cx, dx),
2005
+ maxY: Math.max(ay, by, cy, dy)
2006
+ };
2007
+ }
2008
+ function boxMisses(b, box) {
2009
+ return b !== null && (b.maxX < box.minX || b.minX > box.maxX || b.maxY < box.minY || b.minY > box.maxY);
2010
+ }
2011
+
2012
+ // src/retain/contentBounds.ts
2013
+ var EMPTY = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
2014
+ var ANYWHERE = { minX: -Infinity, minY: -Infinity, maxX: Infinity, maxY: Infinity };
2015
+ function contentBounds(children, transform, clips, scale, width, height) {
2016
+ let box = listBox(children, transform);
2017
+ for (const clip of clips) box = clipped(box, pathBox(clip.path, void 0, clip.transform));
2018
+ const x0 = Math.max(0, Math.floor((box.minX - CULL_SLACK_PX) * scale));
2019
+ const y0 = Math.max(0, Math.floor((box.minY - CULL_SLACK_PX) * scale));
2020
+ const x1 = Math.min(width, Math.ceil((box.maxX + CULL_SLACK_PX) * scale));
2021
+ const y1 = Math.min(height, Math.ceil((box.maxY + CULL_SLACK_PX) * scale));
2022
+ if ([x0, y0, x1, y1].some(Number.isNaN)) return { x: 0, y: 0, width, height };
2023
+ if (x1 <= x0 || y1 <= y0) return { x: 0, y: 0, width: 0, height: 0 };
2024
+ return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
2025
+ }
2026
+ function listBox(cmds, m) {
2027
+ let acc = EMPTY;
2028
+ for (const cmd of cmds) {
2029
+ acc = union(acc, commandBox(cmd, m));
2030
+ if (acc === ANYWHERE) break;
2031
+ }
2032
+ return acc;
2033
+ }
2034
+ function commandBox(cmd, m) {
2035
+ switch (cmd.kind) {
2036
+ case "group": {
2037
+ const inner = cmd.transform ? mat3.multiply(m, cmd.transform) : m;
2038
+ const box = cmd.effects && cmd.effects.length > 0 ? ANYWHERE : listBox(cmd.children, inner);
2039
+ return cmd.clip ? clipped(box, pathBox(cmd.clip, void 0, inner)) : box;
2040
+ }
2041
+ case "path":
2042
+ case "image":
2043
+ case "sprites":
2044
+ return paintedBox(cmd, m) ?? ANYWHERE;
2045
+ default:
2046
+ return ANYWHERE;
2047
+ }
2048
+ }
2049
+ function union(a, b) {
2050
+ if (a === EMPTY || b === ANYWHERE) return b;
2051
+ if (b === EMPTY || a === ANYWHERE) return a;
2052
+ return {
2053
+ minX: Math.min(a.minX, b.minX),
2054
+ minY: Math.min(a.minY, b.minY),
2055
+ maxX: Math.max(a.maxX, b.maxX),
2056
+ maxY: Math.max(a.maxY, b.maxY)
2057
+ };
2058
+ }
2059
+ function clipped(a, clip) {
2060
+ if (!clip || a === EMPTY) return a;
2061
+ const box = {
2062
+ minX: Math.max(a.minX, clip.minX),
2063
+ minY: Math.max(a.minY, clip.minY),
2064
+ maxX: Math.min(a.maxX, clip.maxX),
2065
+ maxY: Math.min(a.maxY, clip.maxY)
2066
+ };
2067
+ return box.minX > box.maxX || box.minY > box.maxY ? EMPTY : box;
2068
+ }
2069
+
2070
+ // src/draw/retained.ts
2071
+ var COMPOSITE_AS_DRAWN = { alpha: 1, colorMatrix: IDENTITY_COLOR_MATRIX };
2072
+ var ownPixelsOnly = /* @__PURE__ */ new WeakMap();
2073
+ function readsOnlyOwnPixels(children) {
2074
+ const known = ownPixelsOnly.get(children);
2075
+ if (known !== void 0) return known;
2076
+ let ok = true;
2077
+ for (const child of children) {
2078
+ if (child.kind !== "group") continue;
2079
+ if (isOwnComposite(child.composite) || !readsOnlyOwnPixels(child.children)) {
2080
+ ok = false;
2081
+ break;
2082
+ }
2083
+ }
2084
+ ownPixelsOnly.set(children, ok);
2085
+ return ok;
2086
+ }
2087
+ function drawRetainedGroup(ctx, cmd, ownsSurface2, dispatch2) {
2088
+ const store = ctx.retained;
2089
+ const width = ctx.deviceWidth;
2090
+ const height = ctx.deviceHeight;
2091
+ if (!store || !width || !height) return false;
2092
+ if (ownsSurface2 || ctx.renderTarget || !readsOnlyOwnPixels(cmd.children)) {
2093
+ store.decline();
2094
+ return false;
2095
+ }
2096
+ const id = cmd.retain;
2097
+ ctx.state.push({ transform: cmd.transform, alpha: cmd.alpha, colorMatrix: cmd.colorMatrix });
2098
+ const clips = ctx.clipStack.map(({ path, transform }) => ({ path, transform }));
2099
+ if (cmd.clip) clips.push({ path: cmd.clip, transform: ctx.state.transform });
2100
+ const key = {
2101
+ children: cmd.children,
2102
+ transform: ctx.state.transform,
2103
+ alpha: ctx.state.alpha,
2104
+ colorMatrix: ctx.state.colorMatrix,
2105
+ viewMatrix: ctx.viewMatrix,
2106
+ clips
2107
+ };
2108
+ let pixels = store.lookup(id, width, height, key);
2109
+ if (!pixels) {
2110
+ const rect2 = contentBounds(cmd.children, ctx.state.transform, clips, width / ctx.widthCss, width, height);
2111
+ const draw = store.reserve(id, width, height, rect2);
2112
+ if (!draw) {
2113
+ ctx.state.pop();
2114
+ return false;
2115
+ }
2116
+ flushBatch(ctx);
2117
+ if (draw.fbo) drawInto(ctx, cmd.children, clips, draw.fbo, rect2, dispatch2);
2118
+ pixels = draw.commit(key);
2119
+ }
2120
+ ctx.state.pop();
2121
+ const { texture, rect } = pixels;
2122
+ if (!texture) return true;
2123
+ flushBatch(ctx);
2124
+ const composite = ctx.ensureProgram?.(compositeProgramId()) ?? null;
2125
+ if (composite && ctx.viewportWithin) {
2126
+ applyClipTest(ctx, 0);
2127
+ ctx.viewportWithin(rect);
2128
+ drawFullscreenQuad(ctx, composite, texture, rect.width, rect.height, void 0, COMPOSITE_AS_DRAWN);
2129
+ ctx.restoreTargetRect?.();
2130
+ }
2131
+ return true;
2132
+ }
2133
+ function drawInto(ctx, children, clips, fbo, rect, dispatch2) {
2134
+ const gl = ctx.gl;
2135
+ const width = ctx.deviceWidth;
2136
+ const height = ctx.deviceHeight;
2137
+ const parentClipDepth = ctx.clipDepth;
2138
+ const parentClipStack = ctx.clipStack;
2139
+ const parentBatchClip = ctx.drawBatch.clip;
2140
+ const scissorWasOn = gl.isEnabled(gl.SCISSOR_TEST);
2141
+ ctx.clipDepth = 0;
2142
+ ctx.clipStack = [];
2143
+ ctx.drawBatch.clip = null;
2144
+ const restore = () => {
2145
+ gl.viewport(-rect.x, rect.y + rect.height - height, width, height);
2146
+ gl.scissor(0, 0, rect.width, rect.height);
2147
+ gl.enable(gl.SCISSOR_TEST);
2148
+ };
2149
+ ctx.renderTarget = { fbo, restore };
2150
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
2151
+ restore();
2152
+ gl.stencilMask(255);
2153
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
2154
+ for (const clip of clips) enterGroupClip(ctx, clip.path, clip.transform);
2155
+ for (const child of children) dispatch2(ctx, child);
2156
+ flushBatch(ctx);
2157
+ ctx.clipDepth = parentClipDepth;
2158
+ ctx.clipStack = parentClipStack;
2159
+ ctx.drawBatch.clip = parentBatchClip;
2160
+ ctx.renderTarget = null;
2161
+ returnToRenderTarget(ctx, width, height, scissorWasOn);
2162
+ }
1737
2163
  var WORLD = {};
1738
2164
  function resolveStrokeLengths(transform, cmd) {
1739
2165
  const stroke = cmd.stroke;
@@ -1768,37 +2194,72 @@ function isPxMarker(ref) {
1768
2194
  function resolvedMarker(ref, scale) {
1769
2195
  return { ...ref, size: resolveMarkerSize(ref, 0, quantizeStrokeScale(ref.size.px, scale)) };
1770
2196
  }
2197
+ var REMAPPED = /* @__PURE__ */ new WeakMap();
2198
+ function remapped(values, sources, stride) {
2199
+ let bySources = REMAPPED.get(values);
2200
+ if (bySources === void 0) {
2201
+ bySources = /* @__PURE__ */ new WeakMap();
2202
+ REMAPPED.set(values, bySources);
2203
+ }
2204
+ let out = bySources.get(sources);
2205
+ if (out === void 0) {
2206
+ out = remapPerAnchor(values, sources, stride);
2207
+ bySources.set(sources, out);
2208
+ }
2209
+ return out;
2210
+ }
2211
+ function withCornersResolved(cmd) {
2212
+ if (cmd.path.kind !== "polygon") return cmd;
2213
+ const { path, anchorSources: src } = resolveCornersWithSources(cmd.path);
2214
+ if (path === cmd.path) return cmd;
2215
+ const out = { ...cmd, path };
2216
+ if (src && cmd.vertexColors) out.vertexColors = remapped(cmd.vertexColors, src, 4);
2217
+ if (src && cmd.stroke && (cmd.stroke.vertexColors || cmd.stroke.vertexWidths)) {
2218
+ const { vertexColors, vertexWidths } = cmd.stroke;
2219
+ out.stroke = {
2220
+ ...cmd.stroke,
2221
+ ...vertexColors ? { vertexColors: remapped(vertexColors, src, 4) } : {},
2222
+ ...vertexWidths ? { vertexWidths: remapped(vertexWidths, src, 1) } : {}
2223
+ };
2224
+ }
2225
+ return out;
2226
+ }
1771
2227
 
1772
- // src/draw/foldColor.ts
1773
- function isIdentityColorMatrix(cm) {
1774
- return cm === IDENTITY_COLOR_MATRIX || sameValues(IDENTITY_COLOR_MATRIX, cm);
2228
+ // src/draw/batchRun.ts
2229
+ function slotForBitmap(staged, image, sampling) {
2230
+ for (let i = 0; i < staged.textures.length; i++) {
2231
+ const entry = staged.textures[i];
2232
+ if (entry.kind !== "bitmap" || entry.image !== image) continue;
2233
+ return entry.sampling === sampling ? i + 1 : -1;
2234
+ }
2235
+ return nextFreeSlot(staged);
1775
2236
  }
1776
- var colorFolding = { enabled: true };
1777
- var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
1778
- function foldColor(ctx, staged, r, g, b, a) {
1779
- if (!staged.foldsAlpha) return [r, g, b, a];
1780
- const cm = ctx.state.colorMatrix;
1781
- const alpha = ctx.state.alpha;
1782
- if (isIdentityColorMatrix(cm)) return [r, g, b, a * alpha];
1783
- return [
1784
- clamp01(cm[0] * r + cm[1] * g + cm[2] * b + cm[3] * a + cm[4]),
1785
- clamp01(cm[5] * r + cm[6] * g + cm[7] * b + cm[8] * a + cm[9]),
1786
- clamp01(cm[10] * r + cm[11] * g + cm[12] * b + cm[13] * a + cm[14]),
1787
- clamp01(cm[15] * r + cm[16] * g + cm[17] * b + cm[18] * a + cm[19]) * alpha
1788
- ];
2237
+ function slotForRamps(staged) {
2238
+ if (staged.ramps) return RAMP_SLOT;
2239
+ return nextFreeSlot(staged) < 0 ? -1 : RAMP_SLOT;
1789
2240
  }
1790
- var NO_TINT = [1, 1, 1, 1];
1791
- var DIAGONAL = [0, 6, 12, 18];
1792
- function textureTint(cm) {
1793
- if (isIdentityColorMatrix(cm)) return NO_TINT;
1794
- for (let i = 0; i < 20; i++) {
1795
- const v = cm[i];
1796
- if (DIAGONAL.includes(i) ? v < 0 || v > 1 : v !== 0) return null;
2241
+ function slotForAtlas(staged, id) {
2242
+ for (let i = 0; i < staged.textures.length; i++) {
2243
+ const entry = staged.textures[i];
2244
+ if (entry.kind === "atlas" && entry.id === id) return i + 1;
1797
2245
  }
1798
- return [cm[0], cm[6], cm[12], cm[18]];
2246
+ return nextFreeSlot(staged);
2247
+ }
2248
+ function nextFreeSlot(staged) {
2249
+ const taken = staged.textures.length + (staged.ramps ? 1 : 0);
2250
+ return taken + 1 < BATCH_TEXTURE_SLOTS ? staged.textures.length + 1 : -1;
2251
+ }
2252
+ function runTakes(ctx, staged, colorMatrix, alpha) {
2253
+ return staged.clipDepth === runClipDepth(ctx) && matrixIndex(staged.matrices, colorMatrix, alpha) >= 0;
1799
2254
  }
1800
- function stagedTint(staged, tint) {
1801
- return staged.foldsAlpha && tint !== null ? tint : NO_TINT;
2255
+ function openRun(ctx) {
2256
+ ctx.batchState ??= { clipDepth: runClipDepth(ctx), matrices: [], textures: [], ramps: false };
2257
+ return ctx.batchState;
2258
+ }
2259
+ function takeMatrix(staged, colorMatrix, alpha) {
2260
+ const index = matrixIndex(staged.matrices, colorMatrix, alpha);
2261
+ if (index === staged.matrices.length) staged.matrices.push({ colorMatrix, alpha });
2262
+ return index;
1802
2263
  }
1803
2264
  function batchPaint(ctx, fill, hasVColors) {
1804
2265
  if (hasVColors) return void 0;
@@ -1887,39 +2348,6 @@ function spaceToDevice(ctx, units) {
1887
2348
  }
1888
2349
 
1889
2350
  // src/draw/batch.ts
1890
- function stagedStateIsLive(ctx, staged, image, sampling, synthBold = 0, folds = false) {
1891
- if (staged.clipDepth !== (ctx.clipStack.length === 0 ? ctx.clipDepth : runClipDepth(ctx))) return false;
1892
- if (staged.synthBold !== synthBold) return false;
1893
- if (!staged.foldsAlpha && staged.alpha !== ctx.state.alpha) return false;
1894
- if (image !== void 0 && slotForBitmap(staged, image, sampling) < 0) return false;
1895
- const colorMatrix = ctx.state.colorMatrix;
1896
- if (staged.colorMatrix === colorMatrix || sameValues(staged.colorMatrix, colorMatrix)) return true;
1897
- return folds && staged.foldsAlpha;
1898
- }
1899
- function slotForBitmap(staged, image, sampling) {
1900
- for (let i = 0; i < staged.textures.length; i++) {
1901
- const entry = staged.textures[i];
1902
- if (entry.kind !== "bitmap" || entry.image !== image) continue;
1903
- return entry.sampling === sampling ? i + 1 : -1;
1904
- }
1905
- return nextFreeSlot(staged);
1906
- }
1907
- function slotForRamps(staged) {
1908
- for (let i = 0; i < staged.textures.length; i++) {
1909
- if (staged.textures[i].kind === "ramps") return i + 1;
1910
- }
1911
- return nextFreeSlot(staged);
1912
- }
1913
- function slotForAtlas(staged, id) {
1914
- for (let i = 0; i < staged.textures.length; i++) {
1915
- const entry = staged.textures[i];
1916
- if (entry.kind === "atlas" && entry.id === id) return i + 1;
1917
- }
1918
- return nextFreeSlot(staged);
1919
- }
1920
- function nextFreeSlot(staged) {
1921
- return staged.textures.length + 1 < BATCH_TEXTURE_SLOTS ? staged.textures.length + 1 : -1;
1922
- }
1923
2351
  var MAX_BATCHED_MESH_VERTICES = 96;
1924
2352
  var MAX_BATCHED_MESH_VERTICES_IN_RUN = 1024;
1925
2353
  var meshBatching = {
@@ -1932,79 +2360,82 @@ function canBatchMesh(ctx, mesh) {
1932
2360
  return mesh.vertices.length >> 1 <= cap;
1933
2361
  }
1934
2362
  function stageSolid(ctx, vertices) {
1935
- if (ctx.batchState !== void 0 && !stagedStateIsLive(ctx, ctx.batchState, void 0, void 0, 0, colorFolding.enabled)) {
1936
- flushBatch(ctx);
1937
- }
2363
+ const colorMatrix = IDENTITY_COLOR_MATRIX ;
2364
+ const alpha = 1 ;
2365
+ const open = ctx.batchState;
2366
+ if (open !== void 0 && !runTakes(ctx, open, colorMatrix, alpha)) flushBatch(ctx);
1938
2367
  if (ctx.drawBatch.wouldOverflow(vertices)) flushBatch(ctx);
1939
- return openRun(ctx, colorFolding.enabled);
1940
- }
1941
- function openRun(ctx, folds = false) {
1942
- if (ctx.batchState === void 0) {
1943
- const colorMatrix = folds ? IDENTITY_COLOR_MATRIX : ctx.state.colorMatrix;
1944
- const foldsAlpha = isIdentityColorMatrix(colorMatrix);
1945
- ctx.batchState = {
1946
- alpha: foldsAlpha ? 1 : ctx.state.alpha,
1947
- colorMatrix,
1948
- clipDepth: runClipDepth(ctx),
1949
- synthBold: 0,
1950
- foldsAlpha,
1951
- textures: []
1952
- };
1953
- }
1954
- return ctx.batchState;
2368
+ const matrix = takeMatrix(openRun(ctx), colorMatrix, alpha);
2369
+ return packSlot(withMatrix(WHITE_SLOT, matrix), PAINT_MODE_PLAIN);
2370
+ }
2371
+ var UNTINTED = [1, 1, 1, 1];
2372
+ function texturedUnder(ctx, alphaInEntry) {
2373
+ const tint = textureTint(ctx);
2374
+ if (tint !== null) return { colorMatrix: IDENTITY_COLOR_MATRIX, alpha: 1, tint, slot: 0 };
2375
+ const colorMatrix = ctx.state.colorMatrix;
2376
+ const alpha = ctx.state.alpha;
2377
+ return alphaInEntry ? { colorMatrix, alpha, tint: UNTINTED, slot: 0 } : { colorMatrix, alpha: 1, tint: [1, 1, 1, alpha], slot: 0 };
1955
2378
  }
1956
2379
  function stageImage(ctx, image, sampling) {
1957
- const tint = textureTint(ctx.state.colorMatrix) ;
1958
- if (ctx.batchState !== void 0 && !stagedStateIsLive(ctx, ctx.batchState, image, sampling, 0, tint !== null)) {
2380
+ const under = texturedUnder(ctx, false);
2381
+ const open = ctx.batchState;
2382
+ if (open !== void 0 && (!runTakes(ctx, open, under.colorMatrix, under.alpha) || slotForBitmap(open, image, sampling) < 0)) {
1959
2383
  flushBatch(ctx);
1960
2384
  }
1961
2385
  if (ctx.drawBatch.wouldOverflow(4)) flushBatch(ctx);
1962
- const staged = openRun(ctx, tint !== null);
2386
+ const staged = openRun(ctx);
1963
2387
  let slot = slotForBitmap(staged, image, sampling);
1964
2388
  if (slot > staged.textures.length) {
1965
2389
  staged.textures.push({ kind: "bitmap", image, sampling });
1966
2390
  slot = staged.textures.length;
1967
2391
  }
1968
- return { staged, slot, tint: stagedTint(staged, tint) };
2392
+ under.slot = withMatrix(slot, takeMatrix(staged, under.colorMatrix, under.alpha));
2393
+ return under;
1969
2394
  }
1970
- function stageGlyphs(ctx, atlasId, fieldScale, synthBold) {
1971
- if (ctx.batchState !== void 0 && (!stagedStateIsLive(ctx, ctx.batchState, void 0, void 0, synthBold, colorFolding.enabled) || slotForAtlas(ctx.batchState, atlasId) < 0)) {
2395
+ function stageGlyphs(ctx, atlasId, fieldScale) {
2396
+ const colorMatrix = IDENTITY_COLOR_MATRIX ;
2397
+ const alpha = 1 ;
2398
+ const open = ctx.batchState;
2399
+ if (open !== void 0 && (!runTakes(ctx, open, colorMatrix, alpha) || slotForAtlas(open, atlasId) < 0)) {
1972
2400
  flushBatch(ctx);
1973
2401
  }
1974
2402
  if (ctx.drawBatch.wouldOverflow(4)) flushBatch(ctx);
1975
- const staged = openRun(ctx, colorFolding.enabled);
1976
- staged.synthBold = synthBold;
2403
+ const staged = openRun(ctx);
1977
2404
  let slot = slotForAtlas(staged, atlasId);
1978
2405
  if (slot > staged.textures.length) {
1979
2406
  staged.textures.push({ kind: "atlas", id: atlasId, fieldScale });
1980
2407
  slot = staged.textures.length;
1981
2408
  }
1982
- return { staged, slot };
2409
+ return withMatrix(slot, takeMatrix(staged, colorMatrix, alpha));
1983
2410
  }
1984
2411
  function stageRamps(ctx, stops, space, vertices) {
1985
2412
  if (ctx.gradRamps.wouldReshape(stops, space)) flushBatch(ctx);
1986
- if (ctx.batchState !== void 0 && (!stagedStateIsLive(ctx, ctx.batchState) || slotForRamps(ctx.batchState) < 0)) {
2413
+ const under = texturedUnder(ctx, true);
2414
+ const open = ctx.batchState;
2415
+ if (open !== void 0 && (!runTakes(ctx, open, under.colorMatrix, under.alpha) || slotForRamps(open) < 0)) {
1987
2416
  flushBatch(ctx);
1988
2417
  }
1989
2418
  if (ctx.drawBatch.wouldOverflow(vertices)) flushBatch(ctx);
1990
2419
  const staged = openRun(ctx);
1991
- let slot = slotForRamps(staged);
1992
- if (slot > staged.textures.length) {
1993
- staged.textures.push({ kind: "ramps" });
1994
- slot = staged.textures.length;
1995
- }
1996
- return { staged, slot, rowV: ctx.gradRamps.rowV(ctx.gradRamps.upload(stops, space)) };
1997
- }
1998
- function stagedAlpha(ctx, staged, opacity) {
1999
- return (opacity ?? 1) * (staged.foldsAlpha ? ctx.state.alpha : 1);
2420
+ const slot = slotForRamps(staged);
2421
+ staged.ramps = true;
2422
+ return {
2423
+ slot: withMatrix(slot, takeMatrix(staged, under.colorMatrix, under.alpha)),
2424
+ tint: under.tint,
2425
+ rowV: ctx.gradRamps.rowV(ctx.gradRamps.upload(stops, space))
2426
+ };
2000
2427
  }
2001
- function stagedColor(ctx, staged, paint) {
2428
+ function solidColor(paint) {
2002
2429
  const [r, g, b, a] = resolveColor(paint.color);
2003
- return foldColor(ctx, staged, r, g, b, a * (paint.opacity ?? 1));
2430
+ return [r, g, b, a * (paint.opacity ?? 1)];
2431
+ }
2432
+ function stagedColor(ctx, paint) {
2433
+ const [r, g, b, a] = solidColor(paint);
2434
+ return foldColor(ctx, r, g, b, a);
2004
2435
  }
2005
2436
  function pushRect(ctx, rect, fill) {
2006
- const staged = stageSolid(ctx, 4);
2007
- const [r, g, b, a] = stagedColor(ctx, staged, fill);
2437
+ const slot = stageSolid(ctx, 4);
2438
+ const [r, g, b, a] = stagedColor(ctx, fill);
2008
2439
  if (!ctx.drawBatch.pushRect(
2009
2440
  rect.x,
2010
2441
  rect.y,
@@ -2014,18 +2445,19 @@ function pushRect(ctx, rect, fill) {
2014
2445
  r,
2015
2446
  g,
2016
2447
  b,
2017
- a
2448
+ a,
2449
+ slot
2018
2450
  )) retryUncut(ctx, rect, fill, pushRect);
2019
2451
  }
2020
2452
  function pushMesh(ctx, mesh, paint) {
2021
- const staged = stageSolid(ctx, mesh.vertices.length >> 1);
2022
- const [r, g, b, a] = stagedColor(ctx, staged, paint);
2023
- if (!ctx.drawBatch.pushMesh(mesh, ctx.state.transform, r, g, b, a)) {
2453
+ const slot = stageSolid(ctx, mesh.vertices.length >> 1);
2454
+ const [r, g, b, a] = stagedColor(ctx, paint);
2455
+ if (!ctx.drawBatch.pushMesh(mesh, ctx.state.transform, r, g, b, a, slot)) {
2024
2456
  retryUncut(ctx, mesh, paint, pushMesh);
2025
2457
  }
2026
2458
  }
2027
2459
  function pushRampRect(ctx, rect, paint) {
2028
- const { staged, slot, rowV } = stageRamps(ctx, paint.stops, paint.space, 4);
2460
+ const { slot, tint, rowV } = stageRamps(ctx, paint.stops, paint.space, 4);
2029
2461
  const { uv, post } = rampVertices(paint, rowV);
2030
2462
  if (!ctx.drawBatch.pushGradientRect(
2031
2463
  rect.x,
@@ -2037,14 +2469,14 @@ function pushRampRect(ctx, rect, paint) {
2037
2469
  post,
2038
2470
  slot,
2039
2471
  paint.mode,
2040
- 1,
2041
- 1,
2042
- 1,
2043
- stagedAlpha(ctx, staged, paint.opacity)
2472
+ tint[0],
2473
+ tint[1],
2474
+ tint[2],
2475
+ (paint.opacity ?? 1) * tint[3]
2044
2476
  )) retryUncut(ctx, rect, paint, pushRampRect);
2045
2477
  }
2046
2478
  function pushRampMesh(ctx, mesh, paint) {
2047
- const { staged, slot, rowV } = stageRamps(ctx, paint.stops, paint.space, mesh.vertices.length >> 1);
2479
+ const { slot, tint, rowV } = stageRamps(ctx, paint.stops, paint.space, mesh.vertices.length >> 1);
2048
2480
  const { uv, post } = rampVertices(paint, rowV);
2049
2481
  if (!ctx.drawBatch.pushGradientMesh(
2050
2482
  mesh,
@@ -2053,10 +2485,10 @@ function pushRampMesh(ctx, mesh, paint) {
2053
2485
  post,
2054
2486
  slot,
2055
2487
  paint.mode,
2056
- 1,
2057
- 1,
2058
- 1,
2059
- stagedAlpha(ctx, staged, paint.opacity)
2488
+ tint[0],
2489
+ tint[1],
2490
+ tint[2],
2491
+ (paint.opacity ?? 1) * tint[3]
2060
2492
  )) retryUncut(ctx, mesh, paint, pushRampMesh);
2061
2493
  }
2062
2494
  function tryStageFill(ctx, mesh, paint) {
@@ -2070,7 +2502,9 @@ function tryStageFill(ctx, mesh, paint) {
2070
2502
  }
2071
2503
  var NO_FIELD = [0, 0];
2072
2504
  function stageColorGlyphs(ctx, atlasId) {
2073
- if (ctx.batchState !== void 0 && (!stagedStateIsLive(ctx, ctx.batchState) || slotForAtlas(ctx.batchState, atlasId) < 0)) {
2505
+ const under = texturedUnder(ctx, false);
2506
+ const open = ctx.batchState;
2507
+ if (open !== void 0 && (!runTakes(ctx, open, under.colorMatrix, under.alpha) || slotForAtlas(open, atlasId) < 0)) {
2074
2508
  flushBatch(ctx);
2075
2509
  }
2076
2510
  if (ctx.drawBatch.wouldOverflow(4)) flushBatch(ctx);
@@ -2080,7 +2514,8 @@ function stageColorGlyphs(ctx, atlasId) {
2080
2514
  staged.textures.push({ kind: "atlas", id: atlasId, fieldScale: NO_FIELD });
2081
2515
  slot = staged.textures.length;
2082
2516
  }
2083
- return { staged, slot };
2517
+ under.slot = withMatrix(slot, takeMatrix(staged, under.colorMatrix, under.alpha));
2518
+ return under;
2084
2519
  }
2085
2520
  function patternTileSpace(spaceInverse, origin, transform) {
2086
2521
  const [a, b, c, d] = transform ?? [1, 0, 0, 1];
@@ -2088,8 +2523,6 @@ function patternTileSpace(spaceInverse, origin, transform) {
2088
2523
  const unplace = mat3.invert(placement);
2089
2524
  return unplace && mat3.multiply(unplace, spaceInverse);
2090
2525
  }
2091
-
2092
- // src/draw/paintBind.ts
2093
2526
  function paintBindContext(ctx) {
2094
2527
  const cached = BIND_CONTEXTS.get(ctx);
2095
2528
  if (cached) return cached;
@@ -2129,8 +2562,7 @@ function bindPattern(ctx, prog, fill, model) {
2129
2562
  const tex = fill.texture;
2130
2563
  const entry = getTexture(tex.id);
2131
2564
  if (!entry) {
2132
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
2133
- if (isDev) console.warn(`weasel: pattern TextureHandle "${tex.id}" not registered`);
2565
+ if (isDev()) console.warn(`weasel: pattern TextureHandle "${tex.id}" not registered`);
2134
2566
  return null;
2135
2567
  }
2136
2568
  const inverse = gradientSpaceInverse(ctx, fill.units);
@@ -2142,8 +2574,10 @@ function bindPattern(ctx, prog, fill, model) {
2142
2574
  gl.useProgram(prog.handle);
2143
2575
  setProjAndModel(ctx, prog, model);
2144
2576
  setColorMatrixUniforms(ctx, prog);
2145
- gl.uniformMatrix3fv(prog.uniform("u_worldInv"), false, mat3.toUniform(tileSpace));
2146
2577
  const [tw, th] = textureSize(entry.source);
2578
+ tileSpace[6] -= Math.floor(tileSpace[6] / tw) * tw;
2579
+ tileSpace[7] -= Math.floor(tileSpace[7] / th) * th;
2580
+ gl.uniformMatrix3fv(prog.uniform("u_worldInv"), false, mat3.toUniform(tileSpace));
2147
2581
  gl.uniform2f(prog.uniform("u_tileSize"), tw, th);
2148
2582
  ctx.textureCache.bind(tex.id, 0);
2149
2583
  gl.uniform1i(prog.uniform("u_sampler"), 0);
@@ -2159,11 +2593,13 @@ function textureSize(source) {
2159
2593
  function bindGradient(ctx, prog, fill, model) {
2160
2594
  const inverse = gradientSpaceInverse(ctx, fill.units);
2161
2595
  if (!inverse) return null;
2596
+ const about = fill.kind === "linear-gradient" ? fill.from : fill.center;
2597
+ const aboutStart = mat3.multiply(mat3.translated(mat3.identity(), -about.x, -about.y), inverse);
2162
2598
  const gl = ctx.gl;
2163
2599
  const row = ctx.gradRamps.upload(fill.stops, fill.interpolate ?? "rgb");
2164
2600
  gl.useProgram(prog.handle);
2165
2601
  setProjAndModel(ctx, prog, model);
2166
- gl.uniformMatrix3fv(prog.uniform("u_worldInv"), false, mat3.toUniform(inverse));
2602
+ gl.uniformMatrix3fv(prog.uniform("u_worldInv"), false, mat3.toUniform(aboutStart));
2167
2603
  setColorMatrixUniforms(ctx, prog);
2168
2604
  ctx.gradRamps.bind(0);
2169
2605
  gl.uniform1i(prog.uniform("u_ramp"), 0);
@@ -2175,21 +2611,18 @@ function bindGradient(ctx, prog, fill, model) {
2175
2611
  const dx = fill.to.x - fill.from.x;
2176
2612
  const dy = fill.to.y - fill.from.y;
2177
2613
  const len = Math.hypot(dx, dy) || 1;
2178
- gl.uniform2f(prog.uniform("u_gradP0"), fill.from.x, fill.from.y);
2179
2614
  gl.uniform2f(prog.uniform("u_gradDir"), dx / len, dy / len);
2180
2615
  gl.uniform1f(prog.uniform("u_gradLen"), len);
2181
2616
  gl.uniform1f(prog.uniform("u_gradRadius"), 0);
2182
2617
  gl.uniform1f(prog.uniform("u_gradAngle"), 0);
2183
2618
  } else if (fill.kind === "radial-gradient") {
2184
2619
  gl.uniform1i(prog.uniform("u_gradKind"), 1);
2185
- gl.uniform2f(prog.uniform("u_gradP0"), fill.center.x, fill.center.y);
2186
2620
  gl.uniform2f(prog.uniform("u_gradDir"), 0, 0);
2187
2621
  gl.uniform1f(prog.uniform("u_gradLen"), 0);
2188
2622
  gl.uniform1f(prog.uniform("u_gradRadius"), fill.radius);
2189
2623
  gl.uniform1f(prog.uniform("u_gradAngle"), 0);
2190
2624
  } else {
2191
2625
  gl.uniform1i(prog.uniform("u_gradKind"), 2);
2192
- gl.uniform2f(prog.uniform("u_gradP0"), fill.center.x, fill.center.y);
2193
2626
  gl.uniform2f(prog.uniform("u_gradDir"), 0, 0);
2194
2627
  gl.uniform1f(prog.uniform("u_gradLen"), 0);
2195
2628
  gl.uniform1f(prog.uniform("u_gradRadius"), 0);
@@ -2199,7 +2632,8 @@ function bindGradient(ctx, prog, fill, model) {
2199
2632
  }
2200
2633
 
2201
2634
  // src/draw/pathPaint.ts
2202
- function drawPath(ctx, cmd) {
2635
+ function drawPath(ctx, given) {
2636
+ const cmd = withCornersResolved(given);
2203
2637
  if (cmd.fill?.paint) drawPathFill(ctx, cmd, cmd.fill.paint);
2204
2638
  if (cmd.stroke?.paint) drawPathStroke(ctx, cmd);
2205
2639
  }
@@ -2339,7 +2773,7 @@ function drawPathStroke(ctx, rawCmd) {
2339
2773
  drawPathStrokeUnclipped(ctx, cmd, spaces);
2340
2774
  }
2341
2775
  const width = resolveStrokeWidth(stroke.width ?? 1, 1);
2342
- const heads = cachedMarkerCommands(cmd.path, rawCmd.stroke, stroke, width, ctx.flattenTolerance, spaces);
2776
+ const heads = cachedMarkerCommands(cmd.path, rawCmd.stroke, stroke, width, flattenToleranceOf(ctx), spaces);
2343
2777
  for (const marker of heads) drawPath(ctx, marker);
2344
2778
  }
2345
2779
  function drawPathStrokeUnclipped(ctx, cmd, spaces) {
@@ -2347,7 +2781,7 @@ function drawPathStrokeUnclipped(ctx, cmd, spaces) {
2347
2781
  const paint = stroke.paint;
2348
2782
  const isSolid2 = paint.kind === void 0 || paint.kind === "solid";
2349
2783
  const solid = paint;
2350
- const mesh = strokeMesh(cmd.path, stroke, ctx.flattenTolerance, spaces);
2784
+ const mesh = strokeMesh(cmd.path, stroke, flattenToleranceOf(ctx), spaces);
2351
2785
  if (mesh.indices.length === 0) return;
2352
2786
  const hasVColors = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
2353
2787
  const batched = batchPaint(ctx, paint, hasVColors);
@@ -2386,7 +2820,7 @@ var DOUBLED_VERTEX_WIDTHS = /* @__PURE__ */ new WeakMap();
2386
2820
  function doubledVertexWidths(widths) {
2387
2821
  let doubled = DOUBLED_VERTEX_WIDTHS.get(widths);
2388
2822
  if (doubled === void 0) {
2389
- doubled = widths.map((w) => w * 2);
2823
+ doubled = widths.map((w) => scaleVertexWidth(w, 2));
2390
2824
  DOUBLED_VERTEX_WIDTHS.set(widths, doubled);
2391
2825
  }
2392
2826
  return doubled;
@@ -2404,7 +2838,7 @@ function drawPathStrokeStenciled(ctx, cmd, align, spaces) {
2404
2838
  };
2405
2839
  const useVColor = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
2406
2840
  const fillHandle = fillMeshHandle(ctx, cmd.path);
2407
- const ribbonMesh = strokeMesh(cmd.path, widerStroke, ctx.flattenTolerance, spaces);
2841
+ const ribbonMesh = strokeMesh(cmd.path, widerStroke, flattenToleranceOf(ctx), spaces);
2408
2842
  if (ribbonMesh.indices.length === 0) return;
2409
2843
  const ribbonHandle = useVColor ? ctx.meshCache.uploadTransient(ribbonMesh) : ctx.meshCache.uploadRecurring(ribbonMesh);
2410
2844
  const gl = ctx.gl;
@@ -2514,21 +2948,38 @@ function mergeGlyphMeshes(group, dx, dy, meshFor) {
2514
2948
  }
2515
2949
  return { vertices, indices };
2516
2950
  }
2951
+
2952
+ // src/draw/modelScale.ts
2953
+ function modelScale(m) {
2954
+ const sx = Math.hypot(m[0], m[1]);
2955
+ const sy = Math.hypot(m[3], m[4]);
2956
+ return Math.sqrt(sx * sy) || 1;
2957
+ }
2958
+
2959
+ // src/draw/colorGlyphs.ts
2517
2960
  function drawColorGlyphs(ctx, group, dx, dy) {
2518
2961
  const fill = group.fill?.paint;
2519
2962
  if (fill === void 0 || group.quads.length === 0) return;
2520
2963
  if (!syncColorPageTexture(ctx.textureCache, group.page)) return;
2521
- const opacity = isSolidPaint(fill) ? resolveColor(fill.color)[3] : 1;
2522
- const atlasId = colorPageTextureId(group.page);
2964
+ const opacity = isSolidPaint(fill) ? solidColor(fill)[3] : 1;
2965
+ const baseId = colorPageTextureId(group.page);
2523
2966
  const batch = ctx.drawBatch;
2524
2967
  const m = ctx.state.transform;
2525
- let { staged, slot } = stageColorGlyphs(ctx, atlasId);
2526
- let alpha = opacity * (staged.foldsAlpha ? ctx.state.alpha : 1);
2968
+ const devicePerCss = ctx.deviceWidth && ctx.widthCss ? ctx.deviceWidth / ctx.widthCss : 1;
2969
+ const toDevice = modelScale(m) * devicePerCss;
2970
+ let atlasId = baseId;
2971
+ let { slot, tint } = stageColorGlyphs(ctx, atlasId);
2527
2972
  const restage = () => {
2528
- ({ staged, slot } = stageColorGlyphs(ctx, atlasId));
2529
- alpha = opacity * (staged.foldsAlpha ? ctx.state.alpha : 1);
2973
+ ({ slot, tint } = stageColorGlyphs(ctx, atlasId));
2530
2974
  };
2531
- for (const q of group.quads) {
2975
+ for (const base of group.quads) {
2976
+ const sharp = sharper(ctx, base, toDevice);
2977
+ const q = sharp?.quad ?? base;
2978
+ const id = sharp?.atlasId ?? baseId;
2979
+ if (id !== atlasId) {
2980
+ atlasId = id;
2981
+ restage();
2982
+ }
2532
2983
  if (batch.wouldOverflow(4)) {
2533
2984
  flushBatch(ctx);
2534
2985
  restage();
@@ -2544,8 +2995,11 @@ function drawColorGlyphs(ctx, group, dx, dy) {
2544
2995
  q.v0,
2545
2996
  q.u1,
2546
2997
  q.v1,
2547
- alpha,
2548
- slot
2998
+ opacity * tint[3],
2999
+ slot,
3000
+ tint[0],
3001
+ tint[1],
3002
+ tint[2]
2549
3003
  )) {
2550
3004
  cut = enterUncut(ctx);
2551
3005
  restage();
@@ -2556,6 +3010,31 @@ function drawColorGlyphs(ctx, group, dx, dy) {
2556
3010
  }
2557
3011
  }
2558
3012
  }
3013
+ function sharper(ctx, q, toDevice) {
3014
+ const char = q.colorGlyph;
3015
+ if (!char || char.width === 0) return null;
3016
+ const texel = (q.x1 - q.x0) / char.width;
3017
+ const sharp = sharperColorGlyph(char, texel * toDevice);
3018
+ if (!sharp || !syncSharpColorPageTexture(ctx.textureCache, sharp.glyph.page)) return null;
3019
+ const g = sharp.glyph;
3020
+ const k = 2 ** sharp.level;
3021
+ const x0 = q.x0 + (g.xoffset - char.xoffset) * texel;
3022
+ const y0 = q.y0 + (g.yoffset - char.yoffset) * texel;
3023
+ return {
3024
+ quad: {
3025
+ x0,
3026
+ y0,
3027
+ x1: x0 + g.width / k * texel,
3028
+ y1: y0 + g.height / k * texel,
3029
+ u0: g.x / DYNAMIC_PAGE_SIZE,
3030
+ v0: g.y / DYNAMIC_PAGE_SIZE,
3031
+ u1: (g.x + g.width) / DYNAMIC_PAGE_SIZE,
3032
+ v1: (g.y + g.height) / DYNAMIC_PAGE_SIZE,
3033
+ baselineY: q.baselineY
3034
+ },
3035
+ atlasId: sharpColorPageTextureId(g.page)
3036
+ };
3037
+ }
2559
3038
 
2560
3039
  // src/draw/scratchPass.ts
2561
3040
  var UNIT_QUAD = {
@@ -2633,11 +3112,6 @@ function bindScratchPaint(ctx, prog, painted) {
2633
3112
 
2634
3113
  // src/draw/text.ts
2635
3114
  var OUTLINE_MIN_SCREEN_PX = 48;
2636
- function modelScale(m) {
2637
- const sx = Math.hypot(m[0], m[1]);
2638
- const sy = Math.hypot(m[3], m[4]);
2639
- return Math.sqrt(sx * sy) || 1;
2640
- }
2641
3115
  function drawText(ctx, cmd) {
2642
3116
  const style = resolveTextStyle(cmd.style);
2643
3117
  const lineHeight = style.lineHeight;
@@ -2691,17 +3165,22 @@ function drawTextDecorations(ctx, decorations, dx, dy) {
2691
3165
  drawOutlineMesh(ctx, paint, ruleMesh(d.x0 + dx, d.y0 + dy, d.x1 + dx, d.y1 + dy));
2692
3166
  continue;
2693
3167
  }
2694
- const [r, g, b, a] = isSolidPaint(paint) ? resolveColor(paint.color) : [0, 0, 0, 1];
3168
+ const [r, g, b, a] = isSolidPaint(paint) ? solidColor(paint) : [0, 0, 0, 1];
2695
3169
  let cut;
2696
3170
  for (; ; ) {
2697
- const staged = stageSolid(ctx, 4);
3171
+ const slot = stageSolid(ctx, 4);
3172
+ const rgba = foldColor(ctx, r, g, b, a);
2698
3173
  if (ctx.drawBatch.pushRect(
2699
3174
  d.x0 + dx,
2700
3175
  d.y0 + dy,
2701
3176
  d.x1 - d.x0,
2702
3177
  d.y1 - d.y0,
2703
3178
  ctx.state.transform,
2704
- ...foldColor(ctx, staged, r, g, b, a)
3179
+ rgba[0],
3180
+ rgba[1],
3181
+ rgba[2],
3182
+ rgba[3],
3183
+ slot
2705
3184
  )) break;
2706
3185
  cut = enterUncut(ctx);
2707
3186
  }
@@ -2714,7 +3193,6 @@ function ruleMesh(x0, y0, x1, y1) {
2714
3193
  indices: new Uint32Array([0, 1, 2, 0, 2, 3])
2715
3194
  };
2716
3195
  }
2717
- var SYNTH_BOLD_AMOUNT = 0.08;
2718
3196
  function drawTextGroup(ctx, group, dx, dy) {
2719
3197
  if (group.source === "canvas") {
2720
3198
  if (!syncDynamicPageTexture(ctx.textureCache, group.page)) return;
@@ -2740,16 +3218,15 @@ function drawTextGroup(ctx, group, dx, dy) {
2740
3218
  drawPaintedGlyphs(ctx, group, dx, dy, paintKind, atlasId, mode, bold, tanItalic, fieldScale);
2741
3219
  return;
2742
3220
  }
2743
- const color = isSolidPaint(fill) ? resolveColor(fill.color) : [0, 0, 0, 1];
3221
+ const color = isSolidPaint(fill) ? solidColor(fill) : [0, 0, 0, 1];
2744
3222
  const batch = ctx.drawBatch;
2745
3223
  const m = ctx.state.transform;
2746
- let { staged, slot } = stageGlyphs(ctx, atlasId, fieldScale, bold);
2747
- let rgba = foldColor(ctx, staged, color[0], color[1], color[2], color[3]);
3224
+ let slot = stageGlyphs(ctx, atlasId, fieldScale);
3225
+ const rgba = foldColor(ctx, color[0], color[1], color[2], color[3]);
2748
3226
  for (const q of group.quads) {
2749
3227
  if (batch.wouldOverflow(4)) {
2750
3228
  flushBatch(ctx);
2751
- ({ staged, slot } = stageGlyphs(ctx, atlasId, fieldScale, bold));
2752
- rgba = foldColor(ctx, staged, color[0], color[1], color[2], color[3]);
3229
+ slot = stageGlyphs(ctx, atlasId, fieldScale);
2753
3230
  }
2754
3231
  let cut;
2755
3232
  while (!batch.pushGlyph(
@@ -2769,16 +3246,15 @@ function drawTextGroup(ctx, group, dx, dy) {
2769
3246
  rgba[2],
2770
3247
  rgba[3],
2771
3248
  slot,
2772
- mode
3249
+ mode,
3250
+ bold
2773
3251
  )) {
2774
3252
  cut = enterUncut(ctx);
2775
- ({ staged, slot } = stageGlyphs(ctx, atlasId, fieldScale, bold));
2776
- rgba = foldColor(ctx, staged, color[0], color[1], color[2], color[3]);
3253
+ slot = stageGlyphs(ctx, atlasId, fieldScale);
2777
3254
  }
2778
3255
  if (cut !== void 0) {
2779
3256
  leaveUncut(ctx, cut);
2780
- ({ staged, slot } = stageGlyphs(ctx, atlasId, fieldScale, bold));
2781
- rgba = foldColor(ctx, staged, color[0], color[1], color[2], color[3]);
3257
+ slot = stageGlyphs(ctx, atlasId, fieldScale);
2782
3258
  }
2783
3259
  }
2784
3260
  }
@@ -2835,7 +3311,8 @@ function drawPaintedGlyphs(ctx, group, dx, dy, kind, atlasId, mode, bold, tanIta
2835
3311
  1,
2836
3312
  1,
2837
3313
  GLYPH_PAINT_ATLAS_UNIT,
2838
- mode
3314
+ mode,
3315
+ 0
2839
3316
  );
2840
3317
  }
2841
3318
  drawStagedGlyphs(ctx);
@@ -2872,9 +3349,6 @@ function glyphScreenBounds(group, dx, dy, tanItalic, m) {
2872
3349
  return b;
2873
3350
  }
2874
3351
 
2875
- // src/DrawCommand.ts
2876
- var SPRITE_STRIDE = 9;
2877
-
2878
3352
  // src/draw/image.ts
2879
3353
  var MINIFIED = 2;
2880
3354
  function minifiedLimits(ctx) {
@@ -2891,7 +3365,7 @@ function drawImage(ctx, cmd) {
2891
3365
  const sh = cmd.source?.h ?? cmd.image.height;
2892
3366
  if (isMinified(minifiedLimits(ctx), sw, sh, cmd.w, cmd.h)) ctx.imageCache.buildMipmaps(cmd.image);
2893
3367
  }
2894
- const { staged, slot, tint } = stageImage(ctx, cmd.image, cmd.sampling ?? "linear");
3368
+ const { slot, tint } = stageImage(ctx, cmd.image, cmd.sampling ?? "linear");
2895
3369
  const src = cmd.source;
2896
3370
  let u0 = 0, v0 = 0, u1 = 1, v1 = 1;
2897
3371
  if (src) {
@@ -2921,10 +3395,8 @@ function drawImage(ctx, cmd) {
2921
3395
  v0,
2922
3396
  u1,
2923
3397
  v1,
2924
- // Group alpha rides the vertices only where it rode the solid colors too:
2925
- // with a real color matrix in the run it is `u_alpha`, and folding it here
2926
- // as well would apply it twice.
2927
- (cmd.opacity ?? 1) * (staged.foldsAlpha ? ctx.state.alpha : 1) * tint[3],
3398
+ // Group alpha rides here with the opacity, in the tint's fourth channel.
3399
+ (cmd.opacity ?? 1) * tint[3],
2928
3400
  slot,
2929
3401
  tint[0],
2930
3402
  tint[1],
@@ -2945,17 +3417,15 @@ function drawSprites(ctx, cmd) {
2945
3417
  }
2946
3418
  }
2947
3419
  const sampling = cmd.sampling ?? "linear";
2948
- let { staged, slot, tint } = stageImage(ctx, cmd.image, sampling);
3420
+ let { slot, tint } = stageImage(ctx, cmd.image, sampling);
2949
3421
  const batch = ctx.drawBatch;
2950
3422
  const m = ctx.state.transform;
2951
- let groupAlpha = (staged.foldsAlpha ? ctx.state.alpha : 1) * tint[3];
2952
3423
  const tw = cmd.image.width;
2953
3424
  const th = cmd.image.height;
2954
3425
  for (let s = 0, i = 0; s < count; s++, i += SPRITE_STRIDE) {
2955
3426
  if (batch.wouldOverflow(4)) {
2956
3427
  flushBatch(ctx);
2957
- ({ staged, slot, tint } = stageImage(ctx, cmd.image, sampling));
2958
- groupAlpha = (staged.foldsAlpha ? ctx.state.alpha : 1) * tint[3];
3428
+ ({ slot, tint } = stageImage(ctx, cmd.image, sampling));
2959
3429
  }
2960
3430
  const sx = data[i + 4], sy = data[i + 5], sw = data[i + 6], sh = data[i + 7];
2961
3431
  let cut;
@@ -2969,20 +3439,18 @@ function drawSprites(ctx, cmd) {
2969
3439
  sy / th,
2970
3440
  (sx + sw) / tw,
2971
3441
  (sy + sh) / th,
2972
- data[i + 8] * groupAlpha,
3442
+ data[i + 8] * tint[3],
2973
3443
  slot,
2974
3444
  tint[0],
2975
3445
  tint[1],
2976
3446
  tint[2]
2977
3447
  )) {
2978
3448
  cut = enterUncut(ctx);
2979
- ({ staged, slot, tint } = stageImage(ctx, cmd.image, sampling));
2980
- groupAlpha = (staged.foldsAlpha ? ctx.state.alpha : 1) * tint[3];
3449
+ ({ slot, tint } = stageImage(ctx, cmd.image, sampling));
2981
3450
  }
2982
3451
  if (cut !== void 0) {
2983
3452
  leaveUncut(ctx, cut);
2984
- ({ staged, slot, tint } = stageImage(ctx, cmd.image, sampling));
2985
- groupAlpha = (staged.foldsAlpha ? ctx.state.alpha : 1) * tint[3];
3453
+ ({ slot, tint } = stageImage(ctx, cmd.image, sampling));
2986
3454
  }
2987
3455
  }
2988
3456
  }
@@ -3079,6 +3547,7 @@ function ownsSurface(cmd) {
3079
3547
  return cmd.isolate === true || isOwnComposite(cmd.composite) || cmd.effects !== void 0 && cmd.effects.length > 0;
3080
3548
  }
3081
3549
  function drawGroup(ctx, cmd) {
3550
+ if (cmd.retain !== void 0 && drawRetainedGroup(ctx, cmd, ownsSurface(cmd), dispatch)) return;
3082
3551
  if (ownsSurface(cmd) && ctx.effectTargets && ctx.deviceWidth && ctx.deviceHeight) {
3083
3552
  if (cmd.clip) {
3084
3553
  ctx.state.push({ transform: cmd.transform });
@@ -3242,6 +3711,253 @@ var EffectTargets = class {
3242
3711
  }
3243
3712
  };
3244
3713
 
3714
+ // src/retain/retainedKey.ts
3715
+ function keepKey(key) {
3716
+ return {
3717
+ ...key,
3718
+ transform: key.transform.slice(),
3719
+ colorMatrix: key.colorMatrix.slice(),
3720
+ viewMatrix: key.viewMatrix?.slice(),
3721
+ clips: key.clips.map((c) => ({ path: c.path, transform: c.transform.slice() }))
3722
+ };
3723
+ }
3724
+ function sameKey(a, b) {
3725
+ return a.children === b.children && a.alpha === b.alpha && sameFloats(a.transform, b.transform) && sameFloats(a.colorMatrix, b.colorMatrix) && (a.viewMatrix === void 0 ? b.viewMatrix === void 0 : b.viewMatrix !== void 0 && sameFloats(a.viewMatrix, b.viewMatrix)) && sameClips(a.clips, b.clips);
3726
+ }
3727
+ function sameClips(a, b) {
3728
+ if (a.length !== b.length) return false;
3729
+ for (let i = 0; i < a.length; i++) {
3730
+ if (a[i].path !== b[i].path || !sameFloats(a[i].transform, b[i].transform)) return false;
3731
+ }
3732
+ return true;
3733
+ }
3734
+ function sameFloats(a, b) {
3735
+ if (a.length !== b.length) return false;
3736
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
3737
+ return true;
3738
+ }
3739
+ function sameStamp(a, b) {
3740
+ if (a.length !== b.length) return false;
3741
+ for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
3742
+ return true;
3743
+ }
3744
+
3745
+ // src/retain/surfaceGl.ts
3746
+ function createSurfaceTexture(gl, width, height) {
3747
+ const texture = gl.createTexture();
3748
+ gl.bindTexture(gl.TEXTURE_2D, texture);
3749
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
3750
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
3751
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
3752
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
3753
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
3754
+ gl.bindTexture(gl.TEXTURE_2D, null);
3755
+ const fbo = gl.createFramebuffer();
3756
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
3757
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
3758
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
3759
+ return { texture, fbo };
3760
+ }
3761
+ function deleteSurfaceTexture(gl, surface) {
3762
+ gl.deleteFramebuffer(surface.fbo);
3763
+ gl.deleteTexture(surface.texture);
3764
+ }
3765
+ function createScratch(gl, width, height, samples) {
3766
+ const storage = (format) => {
3767
+ const rb = gl.createRenderbuffer();
3768
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
3769
+ if (samples > 0) {
3770
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, format, width, height);
3771
+ } else {
3772
+ gl.renderbufferStorage(gl.RENDERBUFFER, format, width, height);
3773
+ }
3774
+ return rb;
3775
+ };
3776
+ const color = storage(gl.RGBA8);
3777
+ const stencil = storage(gl.DEPTH24_STENCIL8);
3778
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
3779
+ const fbo = gl.createFramebuffer();
3780
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
3781
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, color);
3782
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, stencil);
3783
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
3784
+ return { fbo, color, stencil, width, height };
3785
+ }
3786
+ function deleteScratch(gl, scratch) {
3787
+ gl.deleteFramebuffer(scratch.fbo);
3788
+ gl.deleteRenderbuffer(scratch.color);
3789
+ gl.deleteRenderbuffer(scratch.stencil);
3790
+ }
3791
+ function resolveInto(gl, scratch, target, width, height) {
3792
+ const scissor = gl.isEnabled(gl.SCISSOR_TEST);
3793
+ gl.disable(gl.SCISSOR_TEST);
3794
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, scratch.fbo);
3795
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, target.fbo);
3796
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
3797
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
3798
+ if (scissor) gl.enable(gl.SCISSOR_TEST);
3799
+ }
3800
+
3801
+ // src/retain/RetainedSurfaces.ts
3802
+ var RetainedSurfaces = class {
3803
+ gl;
3804
+ budget;
3805
+ samples;
3806
+ surfaces = /* @__PURE__ */ new Map();
3807
+ /** Shared: retained draws never nest. */
3808
+ scratch = null;
3809
+ width = 0;
3810
+ height = 0;
3811
+ frame = 0;
3812
+ stamp = [];
3813
+ composited = 0;
3814
+ redrawn = 0;
3815
+ declined = 0;
3816
+ constructor(gl, budget) {
3817
+ this.gl = gl;
3818
+ this.budget = budget;
3819
+ const samples = gl.getParameter(gl.SAMPLES);
3820
+ this.samples = typeof samples === "number" && samples > 1 ? samples : 0;
3821
+ }
3822
+ /** GPU memory held now, scratch buffer included. */
3823
+ get bytes() {
3824
+ return this.keptBytes() + (this.scratch ? this.scratchBytes(this.scratch.width, this.scratch.height) : 0);
3825
+ }
3826
+ /** Change the budget. Lowered below what the surfaces hold, every surface
3827
+ * goes, and groups are drawn again within the new one. */
3828
+ setBudget(budget) {
3829
+ this.budget = budget;
3830
+ if (this.keptBytes() > budget) this.releaseAll();
3831
+ }
3832
+ /** Open a frame. `stamp` holds what every surface depends on beyond its
3833
+ * key — resource generations the renderer watches — compared entry by
3834
+ * entry. */
3835
+ beginFrame(stamp) {
3836
+ this.frame++;
3837
+ this.stamp = stamp;
3838
+ this.composited = 0;
3839
+ this.redrawn = 0;
3840
+ this.declined = 0;
3841
+ }
3842
+ /** `id`'s pixels if they were drawn under `key`, in a drawing buffer
3843
+ * `width × height` device pixels. */
3844
+ lookup(id, width, height, key) {
3845
+ this.fitTo(width, height);
3846
+ const surface = this.surfaces.get(id);
3847
+ if (!surface?.key || !sameKey(surface.key, key) || !sameStamp(surface.stamp, this.stamp)) return null;
3848
+ surface.frame = this.frame;
3849
+ this.composited++;
3850
+ return pixelsOf(surface);
3851
+ }
3852
+ /** Somewhere to draw `id` this frame, covering `rect` of a drawing buffer
3853
+ * `width × height` device pixels, or `null` when that would take the total
3854
+ * past the budget. */
3855
+ reserve(id, width, height, rect) {
3856
+ this.fitTo(width, height);
3857
+ const existing = this.surfaces.get(id);
3858
+ const empty = rect.width === 0 || rect.height === 0;
3859
+ const held = this.scratch;
3860
+ const scratchW = Math.max(held?.width ?? 0, empty ? 0 : rect.width);
3861
+ const scratchH = Math.max(held?.height ?? 0, empty ? 0 : rect.height);
3862
+ const after = this.keptBytes() + rectBytes(rect) - (existing ? rectBytes(existing.rect) : 0);
3863
+ if (after > this.budget) {
3864
+ this.declined++;
3865
+ return null;
3866
+ }
3867
+ const surface = existing ?? { gl: null, rect, key: null, stamp: [], frame: this.frame };
3868
+ this.surfaces.set(id, surface);
3869
+ this.fitSurface(surface, rect);
3870
+ surface.frame = this.frame;
3871
+ this.redrawn++;
3872
+ const scratch = surface.gl ? this.scratchFor(scratchW, scratchH) : null;
3873
+ return {
3874
+ fbo: scratch?.fbo ?? null,
3875
+ commit: (key) => {
3876
+ if (scratch && surface.gl) resolveInto(this.gl, scratch, surface.gl, surface.rect.width, surface.rect.height);
3877
+ surface.key = keepKey(key);
3878
+ surface.stamp = this.stamp;
3879
+ return pixelsOf(surface);
3880
+ }
3881
+ };
3882
+ }
3883
+ /** Count a group drawn straight through for want of a surface. */
3884
+ decline() {
3885
+ this.declined++;
3886
+ }
3887
+ /** Free every surface no group asked for this frame, and the scratch
3888
+ * buffer once this frame redrew nothing or it is larger than every surface
3889
+ * left needs. */
3890
+ endFrame() {
3891
+ let needW = 0;
3892
+ let needH = 0;
3893
+ for (const [id, surface] of this.surfaces) {
3894
+ if (surface.frame === this.frame) {
3895
+ needW = Math.max(needW, surface.rect.width);
3896
+ needH = Math.max(needH, surface.rect.height);
3897
+ continue;
3898
+ }
3899
+ this.deleteSurface(surface);
3900
+ this.surfaces.delete(id);
3901
+ }
3902
+ const oversized = this.scratch && (this.scratch.width > needW || this.scratch.height > needH);
3903
+ if (this.redrawn === 0 || oversized) this.dropScratch();
3904
+ }
3905
+ /** Drop everything: on dispose, and on context loss, where the handles are
3906
+ * already gone and only the bookkeeping has to go. */
3907
+ releaseAll() {
3908
+ for (const surface of this.surfaces.values()) this.deleteSurface(surface);
3909
+ this.surfaces.clear();
3910
+ this.dropScratch();
3911
+ }
3912
+ fitTo(width, height) {
3913
+ if (width === this.width && height === this.height) return;
3914
+ this.releaseAll();
3915
+ this.width = width;
3916
+ this.height = height;
3917
+ }
3918
+ keptBytes() {
3919
+ let n = 0;
3920
+ for (const surface of this.surfaces.values()) n += rectBytes(surface.rect);
3921
+ return n;
3922
+ }
3923
+ scratchBytes(width, height) {
3924
+ return width * height * Math.max(1, this.samples) * 8;
3925
+ }
3926
+ /** The scratch buffer at `width × height`, reallocated if it is not. */
3927
+ scratchFor(width, height) {
3928
+ const held = this.scratch;
3929
+ if (held && held.width === width && held.height === height) return held;
3930
+ this.dropScratch();
3931
+ return this.scratch = createScratch(this.gl, width, height, this.samples);
3932
+ }
3933
+ /** Give `surface` a texture the size of `rect`, or none for an empty one,
3934
+ * keeping one that is already that size. */
3935
+ fitSurface(surface, rect) {
3936
+ const was = surface.rect;
3937
+ surface.rect = { ...rect };
3938
+ if (surface.gl && was.width === rect.width && was.height === rect.height) return;
3939
+ this.deleteSurface(surface);
3940
+ if (rect.width > 0 && rect.height > 0) surface.gl = createSurfaceTexture(this.gl, rect.width, rect.height);
3941
+ }
3942
+ deleteSurface(surface) {
3943
+ if (surface.gl) deleteSurfaceTexture(this.gl, surface.gl);
3944
+ surface.gl = null;
3945
+ }
3946
+ dropScratch() {
3947
+ if (this.scratch) deleteScratch(this.gl, this.scratch);
3948
+ this.scratch = null;
3949
+ }
3950
+ };
3951
+ function pixelsOf(surface) {
3952
+ return { texture: surface.gl?.texture ?? null, rect: surface.rect };
3953
+ }
3954
+ function rectBytes(rect) {
3955
+ return rect.width * rect.height * 4;
3956
+ }
3957
+
3958
+ // src/rendererTypes.ts
3959
+ var DEFAULT_RETAIN_BUDGET = 256 * 1024 * 1024;
3960
+
3245
3961
  // src/shaders/extractUniformNames.ts
3246
3962
  var QUALIFIERS = /* @__PURE__ */ new Set([
3247
3963
  "lowp",
@@ -3473,6 +4189,7 @@ function compileCustomProgram(gl, src) {
3473
4189
  program.lookupAttributes(CUSTOM_ATTRIBUTES);
3474
4190
  return program;
3475
4191
  }
4192
+ var NO_RETAINED = { composited: 0, redrawn: 0, declined: 0, bytes: 0 };
3476
4193
  function now() {
3477
4194
  return typeof performance !== "undefined" ? performance.now() : Date.now();
3478
4195
  }
@@ -3483,10 +4200,13 @@ var GLYPH_PAINT_SOURCES = {
3483
4200
  };
3484
4201
  var WeaselRenderer = class {
3485
4202
  gl;
3486
- stats = { drawCalls: 0, ms: 0, spans: [] };
4203
+ stats = { drawCalls: 0, ms: 0, spans: [], retained: NO_RETAINED };
4204
+ /** Built on the first frame that draws with each, so a one-shot render of
4205
+ * plain shapes builds the batch program alone. */
3487
4206
  pathFill;
3488
4207
  pathFillVColor;
3489
4208
  batchFill;
4209
+ batchFillMatrices;
3490
4210
  gradFill;
3491
4211
  patternFill;
3492
4212
  /** Compiled on first use: only atlas text under a texture paint needs one. */
@@ -3517,6 +4237,10 @@ var WeaselRenderer = class {
3517
4237
  /** Offscreen buffers for group effects. Allocates nothing until a group
3518
4238
  * with effects asks, so a canvas without them pays no memory. */
3519
4239
  effectTargets;
4240
+ retained;
4241
+ /** Advanced when a program is compiled again over one already drawn with,
4242
+ * which leaves every retained surface drawn by the old one stale. */
4243
+ programEpoch = 0;
3520
4244
  paintScratch;
3521
4245
  imageMinification;
3522
4246
  flattenTolerance;
@@ -3539,6 +4263,7 @@ var WeaselRenderer = class {
3539
4263
  if (!gl) throw new Error("WeaselRenderer: WebGL2 not available");
3540
4264
  this.gl = gl;
3541
4265
  this.effectTargets = new EffectTargets(this.gl);
4266
+ this.retained = new RetainedSurfaces(this.gl, opts.retainBudget ?? DEFAULT_RETAIN_BUDGET);
3542
4267
  this.paintScratch = new PaintScratch(this.gl);
3543
4268
  const attrs = typeof this.gl.getContextAttributes === "function" ? this.gl.getContextAttributes() : null;
3544
4269
  if (attrs && attrs.stencil === false) {
@@ -3566,21 +4291,12 @@ var WeaselRenderer = class {
3566
4291
  }
3567
4292
  this.applyGlState();
3568
4293
  this.applyTarget();
3569
- this.pathFill = new ShaderProgram(this.gl, VERT_SRC, FRAG_SRC);
3570
- this.pathFill.lookupUniforms(PATH_FILL_UNIFORMS);
3571
- this.pathFill.lookupAttributes(PATH_FILL_ATTRIBUTES);
3572
- this.pathFillVColor = new ShaderProgram(this.gl, VCOLOR_VERT_SRC, VCOLOR_FRAG_SRC);
3573
- this.pathFillVColor.lookupUniforms(PATH_FILL_UNIFORMS);
3574
- this.pathFillVColor.lookupAttributes(PATH_FILL_VCOLOR_ATTRIBUTES);
3575
- this.batchFill = new ShaderProgram(this.gl, BATCH_VERT_SRC, BATCH_FRAG_SRC);
3576
- this.batchFill.lookupUniforms(BATCH_FILL_UNIFORMS);
3577
- this.batchFill.lookupAttributes(BATCH_FILL_ATTRIBUTES);
3578
- this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
3579
- this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
3580
- this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
3581
- this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
3582
- this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
3583
- this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
4294
+ this.pathFill = new LazyProgram(this.gl, PATH_FILL_SPEC);
4295
+ this.pathFillVColor = new LazyProgram(this.gl, PATH_FILL_VCOLOR_SPEC);
4296
+ this.gradFill = new LazyProgram(this.gl, GRAD_FILL_SPEC);
4297
+ this.patternFill = new LazyProgram(this.gl, PATTERN_FILL_SPEC);
4298
+ this.batchFillMatrices = new LazyProgram(this.gl, BATCH_FILL_MATRICES_SPEC);
4299
+ this.batchFill = createBatchFill(this.gl);
3584
4300
  this.meshCache = new GLMeshCache(this.gl);
3585
4301
  this.textureCache = new GLTextureCache(this.gl);
3586
4302
  this.imageCache = new GLImageCache(this.gl, this.imageMinification);
@@ -3589,6 +4305,9 @@ var WeaselRenderer = class {
3589
4305
  this.drawBatch = new DrawBatch(this.gl, this.batchFill);
3590
4306
  this.whiteTexture = createWhiteTexture(this.gl);
3591
4307
  }
4308
+ lazyPrograms() {
4309
+ return [this.pathFill, this.pathFillVColor, this.gradFill, this.patternFill, this.batchFillMatrices];
4310
+ }
3592
4311
  uploadQuadGeometry() {
3593
4312
  const gl = this.gl;
3594
4313
  this.quadVbo = gl.createBuffer();
@@ -3616,15 +4335,16 @@ var WeaselRenderer = class {
3616
4335
  if (this.disposed) return;
3617
4336
  const src = getProgramSource(handle.id);
3618
4337
  if (!src) {
3619
- throw new Error(
3620
- `WeaselRenderer.registerProgram: program "${handle.id}" not found in source registry. Call the module-level registerProgram() first.`
3621
- );
4338
+ throw new Error(`WeaselRenderer.registerProgram: program "${handle.id}" not found in source registry. Call the module-level registerProgram() first.`);
3622
4339
  }
3623
4340
  this.installProgram(handle.id, src, compileCustomProgram(this.gl, src));
3624
4341
  }
3625
4342
  installProgram(id, src, program) {
3626
4343
  const previous = this.programRegistry.get(id);
3627
- if (previous) this.gl.deleteProgram(previous.handle);
4344
+ if (previous) {
4345
+ this.gl.deleteProgram(previous.handle);
4346
+ this.programEpoch++;
4347
+ }
3628
4348
  this.programRegistry.set(id, program);
3629
4349
  this.programSources.set(id, src);
3630
4350
  }
@@ -3696,28 +4416,28 @@ var WeaselRenderer = class {
3696
4416
  const oy = this.target?.origin.y ?? 0;
3697
4417
  const x = Math.round(ox * this.dpr);
3698
4418
  const y = Math.round(oy * this.dpr);
3699
- return {
3700
- x,
3701
- y,
3702
- width: Math.round((ox + this.widthCss) * this.dpr) - x,
3703
- height: Math.round((oy + this.heightCss) * this.dpr) - y
3704
- };
4419
+ const width = Math.round((ox + this.widthCss) * this.dpr) - x;
4420
+ return { x, y, width, height: Math.round((oy + this.heightCss) * this.dpr) - y };
3705
4421
  }
3706
4422
  /** Viewport and scissor for this frame. Re-applied inside `render()` because
3707
4423
  * a co-tenant on the same context moves both between our frames. */
3708
4424
  applyTarget() {
3709
4425
  const gl = this.gl;
3710
- const { x, y, width, height } = this.deviceRect();
4426
+ const { x, y, width, height } = this.glRect();
4427
+ gl.viewport(x, y, width, height);
3711
4428
  if (!this.target) {
3712
4429
  gl.disable(gl.SCISSOR_TEST);
3713
- gl.viewport(0, 0, width, height);
3714
4430
  return;
3715
4431
  }
3716
- const glY = gl.drawingBufferHeight - y - height;
3717
- gl.viewport(x, glY, width, height);
3718
- gl.scissor(x, glY, width, height);
4432
+ gl.scissor(x, y, width, height);
3719
4433
  gl.enable(gl.SCISSOR_TEST);
3720
4434
  }
4435
+ /** This renderer's rect as GL addresses it, from the buffer's bottom left. */
4436
+ glRect() {
4437
+ const { x, y, width, height } = this.deviceRect();
4438
+ if (!this.target) return { x: 0, y: 0, width, height };
4439
+ return { x, y: this.gl.drawingBufferHeight - y - height, width, height };
4440
+ }
3721
4441
  /** Confine this renderer to a rect of its drawing buffer, or pass null to
3722
4442
  * give it the whole buffer back. Takes effect from the next `render()`. */
3723
4443
  setTarget(target) {
@@ -3739,24 +4459,12 @@ var WeaselRenderer = class {
3739
4459
  this.applyGlState();
3740
4460
  this.applyTarget();
3741
4461
  this.effectTargets.releaseAll();
4462
+ this.retained.releaseAll();
3742
4463
  this.paintScratch.release();
3743
4464
  this.paintResources.release();
3744
4465
  this.paintResources = createPaintResources();
3745
- this.pathFill = new ShaderProgram(this.gl, VERT_SRC, FRAG_SRC);
3746
- this.pathFill.lookupUniforms(PATH_FILL_UNIFORMS);
3747
- this.pathFill.lookupAttributes(PATH_FILL_ATTRIBUTES);
3748
- this.pathFillVColor = new ShaderProgram(this.gl, VCOLOR_VERT_SRC, VCOLOR_FRAG_SRC);
3749
- this.pathFillVColor.lookupUniforms(PATH_FILL_UNIFORMS);
3750
- this.pathFillVColor.lookupAttributes(PATH_FILL_VCOLOR_ATTRIBUTES);
3751
- this.batchFill = new ShaderProgram(this.gl, BATCH_VERT_SRC, BATCH_FRAG_SRC);
3752
- this.batchFill.lookupUniforms(BATCH_FILL_UNIFORMS);
3753
- this.batchFill.lookupAttributes(BATCH_FILL_ATTRIBUTES);
3754
- this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
3755
- this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
3756
- this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
3757
- this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
3758
- this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
3759
- this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
4466
+ for (const lazy of this.lazyPrograms()) lazy.abandon();
4467
+ this.batchFill = createBatchFill(this.gl);
3760
4468
  this.glyphPaintPrograms.clear();
3761
4469
  this.meshCache = new GLMeshCache(this.gl);
3762
4470
  this.textureCache = new GLTextureCache(this.gl);
@@ -3795,20 +4503,10 @@ var WeaselRenderer = class {
3795
4503
  }
3796
4504
  this.meshCache.dispose();
3797
4505
  this.paintResources.release();
3798
- for (const prog of [
3799
- this.pathFill,
3800
- this.pathFillVColor,
3801
- this.batchFill,
3802
- this.gradFill,
3803
- this.patternFill,
3804
- ...this.glyphPaintPrograms.values()
3805
- ]) {
3806
- gl.deleteProgram(prog.handle);
3807
- }
4506
+ for (const lazy of this.lazyPrograms()) lazy.release();
4507
+ for (const prog of [this.batchFill, ...this.glyphPaintPrograms.values()]) gl.deleteProgram(prog.handle);
3808
4508
  this.glyphPaintPrograms.clear();
3809
- for (const prog of this.programRegistry.values()) {
3810
- gl.deleteProgram(prog.handle);
3811
- }
4509
+ for (const prog of this.programRegistry.values()) gl.deleteProgram(prog.handle);
3812
4510
  this.programRegistry.clear();
3813
4511
  this.programSources.clear();
3814
4512
  this.textureCache.free();
@@ -3820,6 +4518,7 @@ var WeaselRenderer = class {
3820
4518
  if (this.whiteTexture) gl.deleteTexture(this.whiteTexture);
3821
4519
  this.whiteTexture = null;
3822
4520
  this.effectTargets.releaseAll();
4521
+ this.retained.releaseAll();
3823
4522
  this.paintScratch.release();
3824
4523
  }
3825
4524
  /**
@@ -3835,12 +4534,22 @@ var WeaselRenderer = class {
3835
4534
  const t0 = now();
3836
4535
  const counters = { drawCalls: 0 };
3837
4536
  const spanStats = [];
3838
- this.stats = { drawCalls: 0, ms: 0, spans: spanStats };
4537
+ this.stats = { drawCalls: 0, ms: 0, spans: spanStats, retained: NO_RETAINED };
3839
4538
  const gl = this.gl;
3840
4539
  this.meshCache.drainPendingDeletes();
3841
4540
  this.imageCache.beginFrame();
3842
4541
  this.textureCache.sweep(textureRegistry);
3843
4542
  this.refreshPrograms();
4543
+ this.retained.beginFrame([
4544
+ glyphGeneration(),
4545
+ textureRegistry.getVersion(),
4546
+ paintDrawKindRegistry.getVersion(),
4547
+ this.programEpoch,
4548
+ this.flattenTolerance,
4549
+ this.widthCss,
4550
+ this.heightCss,
4551
+ this.dpr
4552
+ ]);
3844
4553
  resetBakeBudget(this.bakeBudget);
3845
4554
  this.groupState.reset();
3846
4555
  this.drawBatch.reset();
@@ -3853,13 +4562,25 @@ var WeaselRenderer = class {
3853
4562
  gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
3854
4563
  }
3855
4564
  const device = this.deviceRect();
4565
+ const { pathFill, pathFillVColor, gradFill, patternFill, batchFillMatrices } = this;
3856
4566
  const ctx = {
3857
4567
  gl,
3858
- pathFill: this.pathFill,
3859
- pathFillVColor: this.pathFillVColor,
4568
+ get pathFill() {
4569
+ return pathFill.get();
4570
+ },
4571
+ get pathFillVColor() {
4572
+ return pathFillVColor.get();
4573
+ },
3860
4574
  batchFill: this.batchFill,
3861
- gradFill: this.gradFill,
3862
- patternFill: this.patternFill,
4575
+ get batchFillMatrices() {
4576
+ return batchFillMatrices.get();
4577
+ },
4578
+ get gradFill() {
4579
+ return gradFill.get();
4580
+ },
4581
+ get patternFill() {
4582
+ return patternFill.get();
4583
+ },
3863
4584
  meshCache: this.meshCache,
3864
4585
  textureCache: this.textureCache,
3865
4586
  imageCache: this.imageCache,
@@ -3876,15 +4597,20 @@ var WeaselRenderer = class {
3876
4597
  heightCss: this.heightCss,
3877
4598
  clipDepth: 0,
3878
4599
  clipStack: [],
3879
- flattenTolerance: this.flattenTolerance,
4600
+ flattenTolerance: inCssPixels(this.flattenTolerance, this.dpr),
3880
4601
  textOutlineMinScreenSize: this.textOutlineMinScreenSize,
3881
4602
  viewMatrix,
3882
4603
  effectTargets: this.effectTargets,
4604
+ retained: this.retained,
3883
4605
  paintScratch: this.paintScratch,
3884
4606
  renderTarget: null,
3885
4607
  deviceWidth: device.width,
3886
4608
  deviceHeight: device.height,
3887
4609
  restoreTargetRect: () => this.applyTarget(),
4610
+ viewportWithin: (rect) => {
4611
+ const own = this.glRect();
4612
+ gl.viewport(own.x + rect.x, own.y + own.height - rect.y - rect.height, rect.width, rect.height);
4613
+ },
3888
4614
  paintResources: this.paintResources,
3889
4615
  stats: counters
3890
4616
  };
@@ -3903,6 +4629,14 @@ var WeaselRenderer = class {
3903
4629
  for (; next < commands.length; next++) dispatch(ctx, commands[next]);
3904
4630
  flushBatch(ctx);
3905
4631
  this.meshCache.freeTransient();
4632
+ const retained = this.retained;
4633
+ retained.endFrame();
4634
+ this.stats.retained = {
4635
+ composited: retained.composited,
4636
+ redrawn: retained.redrawn,
4637
+ declined: retained.declined,
4638
+ bytes: retained.bytes
4639
+ };
3906
4640
  this.stats.drawCalls = counters.drawCalls;
3907
4641
  this.stats.ms = now() - t0;
3908
4642
  }
@@ -3912,11 +4646,14 @@ var WeaselRenderer = class {
3912
4646
  lastFrameStats() {
3913
4647
  return this.stats;
3914
4648
  }
3915
- /** Change the curve-flattening tolerance after construction. `undefined`
3916
- * restores the cached `DEFAULT_FLATTEN_TOLERANCE` route. */
4649
+ /** Change `flattenTolerance` after construction; `undefined` restores the default. */
3917
4650
  setFlattenTolerance(tolerance) {
3918
4651
  this.flattenTolerance = tolerance;
3919
4652
  }
4653
+ /** Change `retainBudget` after construction; `undefined` restores the default. */
4654
+ setRetainBudget(bytes) {
4655
+ this.retained.setBudget(bytes ?? DEFAULT_RETAIN_BUDGET);
4656
+ }
3920
4657
  resize(dims) {
3921
4658
  this.widthCss = dims.width;
3922
4659
  this.heightCss = dims.height;
@@ -3933,11 +4670,11 @@ var WeaselRenderer = class {
3933
4670
  }
3934
4671
  /** @internal */
3935
4672
  _pathFill() {
3936
- return this.pathFill;
4673
+ return this.pathFill.get();
3937
4674
  }
3938
4675
  /** @internal */
3939
4676
  _pathFillVColor() {
3940
- return this.pathFillVColor;
4677
+ return this.pathFillVColor.get();
3941
4678
  }
3942
4679
  /** @internal */
3943
4680
  _drawBatch() {
@@ -3948,12 +4685,16 @@ var WeaselRenderer = class {
3948
4685
  return this.batchFill;
3949
4686
  }
3950
4687
  /** @internal */
4688
+ _batchFillMatrices() {
4689
+ return this.batchFillMatrices.get();
4690
+ }
4691
+ /** @internal */
3951
4692
  _gradFill() {
3952
- return this.gradFill;
4693
+ return this.gradFill.get();
3953
4694
  }
3954
4695
  /** @internal */
3955
4696
  _patternFill() {
3956
- return this.patternFill;
4697
+ return this.patternFill.get();
3957
4698
  }
3958
4699
  /** @internal */
3959
4700
  _meshCache() {
@@ -4025,119 +4766,6 @@ function frameRect(sheet, index) {
4025
4766
  h: frameHeight
4026
4767
  };
4027
4768
  }
4028
- var SLACK_PX = 1;
4029
- var DEFAULT_MITER_LIMIT = 4;
4030
- function cullDrawCommands(cmds, transform, rect) {
4031
- return cullList(cmds, transform, slackBox(rect));
4032
- }
4033
- function cullList(cmds, m, box) {
4034
- let out = null;
4035
- for (let i = 0; i < cmds.length; i++) {
4036
- const cmd = cmds[i];
4037
- const kept = cullOne(cmd, m, box);
4038
- if (kept === cmd) {
4039
- out?.push(cmd);
4040
- continue;
4041
- }
4042
- out ??= cmds.slice(0, i);
4043
- if (kept) out.push(kept);
4044
- }
4045
- return out ?? cmds;
4046
- }
4047
- function cullOne(cmd, m, box) {
4048
- switch (cmd.kind) {
4049
- case "group": {
4050
- if (cmd.effects && cmd.effects.length > 0) return cmd;
4051
- if (isOwnComposite(cmd.composite)) return cmd;
4052
- const inner = cmd.transform ? mat3.multiply(m, cmd.transform) : m;
4053
- const children = cullList(cmd.children, inner, box);
4054
- if (children === cmd.children) return cmd;
4055
- return children.length === 0 ? null : { ...cmd, children };
4056
- }
4057
- case "path":
4058
- return pathMisses(cmd.path, cmd.stroke, m, box) ? null : cmd;
4059
- case "image":
4060
- return quadMisses(cmd.x, cmd.y, cmd.x + cmd.w, cmd.y + cmd.h, m, box) ? null : cmd;
4061
- case "sprites":
4062
- return spritesMiss(cmd.sprites, m, box) ? null : cmd;
4063
- default:
4064
- return cmd;
4065
- }
4066
- }
4067
- function pathMisses(path, stroke, m, box) {
4068
- let minX, minY, maxX, maxY;
4069
- if (path.kind === "rect") {
4070
- minX = Math.min(path.x, path.x + path.width);
4071
- maxX = Math.max(path.x, path.x + path.width);
4072
- minY = Math.min(path.y, path.y + path.height);
4073
- maxY = Math.max(path.y, path.y + path.height);
4074
- } else if (path.kind === "polygon") {
4075
- const c = path.coords;
4076
- if (c.length < 2) return false;
4077
- minX = maxX = c[0];
4078
- minY = maxY = c[1];
4079
- for (let i = 2; i + 1 < c.length; i += 2) {
4080
- const x = c[i], y = c[i + 1];
4081
- if (x < minX) minX = x;
4082
- else if (x > maxX) maxX = x;
4083
- if (y < minY) minY = y;
4084
- else if (y > maxY) maxY = y;
4085
- }
4086
- } else {
4087
- return false;
4088
- }
4089
- const reach = stroke ? strokeReach(stroke, m) : 0;
4090
- return quadMisses(minX - reach, minY - reach, maxX + reach, maxY + reach, m, box);
4091
- }
4092
- function strokeReach(stroke, m) {
4093
- return strokeReachAt(stroke, mat3.meanScaleOf(m), mat3.minScaleOf(m));
4094
- }
4095
- function strokeReachAt(stroke, scale, least = scale) {
4096
- const at = least > 0 ? least : scale;
4097
- const base = resolveStrokeWidth(stroke.width ?? 1, typeof stroke.width === "object" ? at : scale);
4098
- let width = base;
4099
- if (stroke.vertexWidths) {
4100
- for (const v of stroke.vertexWidths) if (v > width) width = v;
4101
- }
4102
- const spike = Math.max(stroke.miterLimit ?? DEFAULT_MITER_LIMIT, Math.SQRT2);
4103
- const heads = markerReach(stroke);
4104
- return Math.max(Math.abs(width) * spike, heads.world, resolveScreenLength({ px: heads.px }, at));
4105
- }
4106
- function spritesMiss(sprites, m, box) {
4107
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
4108
- for (let i = 0; i + SPRITE_STRIDE <= sprites.length; i += SPRITE_STRIDE) {
4109
- const x0 = sprites[i], y0 = sprites[i + 1];
4110
- const x1 = x0 + sprites[i + 2], y1 = y0 + sprites[i + 3];
4111
- minX = Math.min(minX, x0, x1);
4112
- maxX = Math.max(maxX, x0, x1);
4113
- minY = Math.min(minY, y0, y1);
4114
- maxY = Math.max(maxY, y0, y1);
4115
- }
4116
- if (minX === Infinity) return false;
4117
- return quadMisses(minX, minY, maxX, maxY, m, box);
4118
- }
4119
- function boundsMissRect(bounds, transform, rect) {
4120
- const x0 = Math.min(bounds.x, bounds.x + bounds.width);
4121
- const x1 = Math.max(bounds.x, bounds.x + bounds.width);
4122
- const y0 = Math.min(bounds.y, bounds.y + bounds.height);
4123
- const y1 = Math.max(bounds.y, bounds.y + bounds.height);
4124
- return quadMisses(x0, y0, x1, y1, transform, slackBox(rect));
4125
- }
4126
- function slackBox(rect) {
4127
- return {
4128
- minX: rect.x - SLACK_PX,
4129
- minY: rect.y - SLACK_PX,
4130
- maxX: rect.x + rect.width + SLACK_PX,
4131
- maxY: rect.y + rect.height + SLACK_PX
4132
- };
4133
- }
4134
- function quadMisses(x0, y0, x1, y1, m, box) {
4135
- const [ax, ay] = mat3.apply(m, x0, y0);
4136
- const [bx, by] = mat3.apply(m, x1, y0);
4137
- const [cx, cy] = mat3.apply(m, x1, y1);
4138
- const [dx, dy] = mat3.apply(m, x0, y1);
4139
- return Math.max(ax, bx, cx, dx) < box.minX || Math.min(ax, bx, cx, dx) > box.maxX || Math.max(ay, by, cy, dy) < box.minY || Math.min(ay, by, cy, dy) > box.maxY;
4140
- }
4141
4769
 
4142
4770
  // src/effects/types.ts
4143
4771
  function registerEffect(id, frag) {
@@ -4263,6 +4891,6 @@ function isSolid(paint) {
4263
4891
  return paint == null || isSolidPaint(paint);
4264
4892
  }
4265
4893
 
4266
- export { EFFECT_KIT_UNIFORMS, EFFECT_VERT_SRC, IDENTITY_COLOR_MATRIX, OUTLINE_MIN_SCREEN_PX, SPRITE_STRIDE, ShaderCompileError, WeaselRenderer, blur, boundsMissRect, buildGradientRamp, cullDrawCommands, frameRect, registerEffect, strokeReachAt, textCommand, textCommandFromPose, textCommandFromRuns, vignette };
4894
+ export { DEFAULT_FLATTEN_TOLERANCE_PX, DEFAULT_RETAIN_BUDGET, EFFECT_KIT_UNIFORMS, EFFECT_VERT_SRC, IDENTITY_COLOR_MATRIX, OUTLINE_MIN_SCREEN_PX, SPRITE_STRIDE, ShaderCompileError, WeaselRenderer, blur, boundsMissRect, buildGradientRamp, cullDrawCommands, frameRect, registerEffect, resolveStrokeLengths, strokeReachAt, textCommand, textCommandFromPose, textCommandFromRuns, vignette };
4267
4895
  //# sourceMappingURL=index.js.map
4268
4896
  //# sourceMappingURL=index.js.map