@weasel-js/renderer 1.10.1 → 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, STRAIGHT_RGB_GLSL, BATCH_FILL_ATTRIBUTES, BATCH_FILL_UNIFORMS, BATCH_MATRICES_FRAG_SRC, BATCH_VERT_SRC, GLImageCache, DrawBatch, getProgramSource, 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, quantizeStrokeScale, strokeMesh, axisAlignedClipRect, intersectClipRects, getPaintDrawKind, outlineMesh, outlineStrokeMesh, quantizeEmWidth } from './chunk-K7XCWVQH.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-K7XCWVQH.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() {
@@ -1127,6 +1127,22 @@ var GroupState = class {
1127
1127
  this.colorMatrixStack.pop();
1128
1128
  }
1129
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
+ }
1130
1146
 
1131
1147
  // src/effects/effectPrelude.ts
1132
1148
  var EFFECT_VERT_SRC = (
@@ -1259,14 +1275,11 @@ function endParityMask(ctx) {
1259
1275
  gl.clear(gl.STENCIL_BUFFER_BIT);
1260
1276
  gl.disable(gl.STENCIL_TEST);
1261
1277
  }
1262
-
1263
- // src/draw/customShader.ts
1264
1278
  var warnedUniforms = /* @__PURE__ */ new Set();
1265
1279
  function warnOnce(key, message) {
1266
1280
  if (warnedUniforms.has(key)) return;
1267
1281
  warnedUniforms.add(key);
1268
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
1269
- if (isDev) console.warn(message);
1282
+ if (isDev()) console.warn(message);
1270
1283
  }
1271
1284
  function warnOnceUniform(programId, name) {
1272
1285
  warnOnce(
@@ -1279,8 +1292,7 @@ function setUniform(gl, loc, value, textureCache, nextTexUnit) {
1279
1292
  const handle = value;
1280
1293
  const entry = getTexture(handle.id);
1281
1294
  if (!entry) {
1282
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
1283
- if (isDev) console.warn(`weasel setUniform: TextureHandle "${handle.id}" not registered`);
1295
+ if (isDev()) console.warn(`weasel setUniform: TextureHandle "${handle.id}" not registered`);
1284
1296
  return;
1285
1297
  }
1286
1298
  const unit = nextTexUnit.value++;
@@ -1297,8 +1309,7 @@ function setUniform(gl, loc, value, textureCache, nextTexUnit) {
1297
1309
  } else if (value.length === 16) {
1298
1310
  gl.uniformMatrix4fv(loc, false, value);
1299
1311
  } else {
1300
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
1301
- 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}`);
1302
1313
  }
1303
1314
  return;
1304
1315
  }
@@ -1315,8 +1326,7 @@ function setUniform(gl, loc, value, textureCache, nextTexUnit) {
1315
1326
  gl.uniform4fv(loc, arr);
1316
1327
  break;
1317
1328
  default: {
1318
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
1319
- 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`);
1320
1330
  }
1321
1331
  }
1322
1332
  return;
@@ -1360,14 +1370,13 @@ function isArrayValue(value) {
1360
1370
  return Array.isArray(value) || value instanceof Float32Array || value instanceof Int32Array || value instanceof Uint32Array;
1361
1371
  }
1362
1372
  function setUniformArray(gl, name, arr, value) {
1363
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
1364
1373
  const upload = ARRAY_UPLOADS[arr.type];
1365
1374
  if (!upload) {
1366
- 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`);
1367
1376
  return;
1368
1377
  }
1369
1378
  if (value.length % upload.n !== 0 || value.length > upload.n * arr.size) {
1370
- if (isDev) {
1379
+ if (isDev()) {
1371
1380
  throw new TypeError(
1372
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}`
1373
1382
  );
@@ -1562,7 +1571,8 @@ function returnToRenderTarget(ctx, width, height, scissorWasOn) {
1562
1571
  const target = ctx.renderTarget ?? null;
1563
1572
  gl.bindFramebuffer(gl.FRAMEBUFFER, target ? target.fbo : null);
1564
1573
  if (target) {
1565
- gl.viewport(0, 0, width, height);
1574
+ if (target.restore) target.restore();
1575
+ else gl.viewport(0, 0, width, height);
1566
1576
  } else {
1567
1577
  ctx.restoreTargetRect?.();
1568
1578
  if (scissorWasOn) gl.enable(gl.SCISSOR_TEST);
@@ -1603,14 +1613,15 @@ function drawFullscreenQuad(ctx, program, texture, width, height, uniforms, comp
1603
1613
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
1604
1614
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
1605
1615
  }
1616
+
1617
+ // src/draw/mesh.ts
1618
+ function flattenToleranceOf(ctx) {
1619
+ return worldFlattenTolerance(ctx.flattenTolerance, ctx.state.transform);
1620
+ }
1606
1621
  function fillMesh(ctx, path) {
1607
- if (ctx.flattenTolerance !== void 0) {
1608
- return tessellate(path, { flattenTolerance: ctx.flattenTolerance });
1609
- }
1610
- return getMesh(path);
1622
+ return getMesh(path, flattenToleranceOf(ctx));
1611
1623
  }
1612
1624
  function meshHandle(ctx, mesh) {
1613
- if (ctx.flattenTolerance !== void 0) return ctx.meshCache.uploadTransient(mesh);
1614
1625
  return ctx.meshCache.handleFor(mesh);
1615
1626
  }
1616
1627
  function fillMeshHandle(ctx, path) {
@@ -1740,22 +1751,21 @@ function flushBatch(ctx) {
1740
1751
  }
1741
1752
 
1742
1753
  // src/draw/clip.ts
1743
- function enterGroupClip(ctx, clip) {
1754
+ function enterGroupClip(ctx, clip, transform = ctx.state.transform) {
1744
1755
  const pending = ctx.clipStack.reduce((n, e) => n + (e.depth === 0 ? 1 : 0), 0);
1745
1756
  if (ctx.clipDepth + pending + 1 > 7) {
1746
1757
  throw new Error(
1747
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."
1748
1759
  );
1749
1760
  }
1750
- enterClip(ctx, clip);
1761
+ enterClip(ctx, clip, transform);
1751
1762
  }
1752
- function enterClip(ctx, path) {
1753
- const transform = ctx.state.transform;
1763
+ function enterClip(ctx, path, transform) {
1754
1764
  const rect = axisAlignedClipRect(path, transform);
1755
1765
  if (rect === null) {
1756
1766
  materializeClips(ctx);
1757
1767
  const depth = ctx.clipDepth + 1;
1758
- pushClip(ctx, path, depth);
1768
+ pushClip(ctx, path, depth, transform);
1759
1769
  ctx.clipDepth = depth;
1760
1770
  ctx.clipStack.push({ path, transform, rect: null, depth });
1761
1771
  } else {
@@ -1869,6 +1879,287 @@ function popClip(ctx, path, oldDepth, model = ctx.state.transform) {
1869
1879
  gl.colorMask(true, true, true, true);
1870
1880
  gl.stencilMask(255);
1871
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
+ }
1872
2163
  var WORLD = {};
1873
2164
  function resolveStrokeLengths(transform, cmd) {
1874
2165
  const stroke = cmd.stroke;
@@ -1903,6 +2194,36 @@ function isPxMarker(ref) {
1903
2194
  function resolvedMarker(ref, scale) {
1904
2195
  return { ...ref, size: resolveMarkerSize(ref, 0, quantizeStrokeScale(ref.size.px, scale)) };
1905
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
+ }
1906
2227
 
1907
2228
  // src/draw/batchRun.ts
1908
2229
  function slotForBitmap(staged, image, sampling) {
@@ -2104,9 +2425,13 @@ function stageRamps(ctx, stops, space, vertices) {
2104
2425
  rowV: ctx.gradRamps.rowV(ctx.gradRamps.upload(stops, space))
2105
2426
  };
2106
2427
  }
2107
- function stagedColor(ctx, paint) {
2428
+ function solidColor(paint) {
2108
2429
  const [r, g, b, a] = resolveColor(paint.color);
2109
- return foldColor(ctx, 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);
2110
2435
  }
2111
2436
  function pushRect(ctx, rect, fill) {
2112
2437
  const slot = stageSolid(ctx, 4);
@@ -2198,8 +2523,6 @@ function patternTileSpace(spaceInverse, origin, transform) {
2198
2523
  const unplace = mat3.invert(placement);
2199
2524
  return unplace && mat3.multiply(unplace, spaceInverse);
2200
2525
  }
2201
-
2202
- // src/draw/paintBind.ts
2203
2526
  function paintBindContext(ctx) {
2204
2527
  const cached = BIND_CONTEXTS.get(ctx);
2205
2528
  if (cached) return cached;
@@ -2239,8 +2562,7 @@ function bindPattern(ctx, prog, fill, model) {
2239
2562
  const tex = fill.texture;
2240
2563
  const entry = getTexture(tex.id);
2241
2564
  if (!entry) {
2242
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
2243
- if (isDev) console.warn(`weasel: pattern TextureHandle "${tex.id}" not registered`);
2565
+ if (isDev()) console.warn(`weasel: pattern TextureHandle "${tex.id}" not registered`);
2244
2566
  return null;
2245
2567
  }
2246
2568
  const inverse = gradientSpaceInverse(ctx, fill.units);
@@ -2310,7 +2632,8 @@ function bindGradient(ctx, prog, fill, model) {
2310
2632
  }
2311
2633
 
2312
2634
  // src/draw/pathPaint.ts
2313
- function drawPath(ctx, cmd) {
2635
+ function drawPath(ctx, given) {
2636
+ const cmd = withCornersResolved(given);
2314
2637
  if (cmd.fill?.paint) drawPathFill(ctx, cmd, cmd.fill.paint);
2315
2638
  if (cmd.stroke?.paint) drawPathStroke(ctx, cmd);
2316
2639
  }
@@ -2450,7 +2773,7 @@ function drawPathStroke(ctx, rawCmd) {
2450
2773
  drawPathStrokeUnclipped(ctx, cmd, spaces);
2451
2774
  }
2452
2775
  const width = resolveStrokeWidth(stroke.width ?? 1, 1);
2453
- 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);
2454
2777
  for (const marker of heads) drawPath(ctx, marker);
2455
2778
  }
2456
2779
  function drawPathStrokeUnclipped(ctx, cmd, spaces) {
@@ -2458,7 +2781,7 @@ function drawPathStrokeUnclipped(ctx, cmd, spaces) {
2458
2781
  const paint = stroke.paint;
2459
2782
  const isSolid2 = paint.kind === void 0 || paint.kind === "solid";
2460
2783
  const solid = paint;
2461
- const mesh = strokeMesh(cmd.path, stroke, ctx.flattenTolerance, spaces);
2784
+ const mesh = strokeMesh(cmd.path, stroke, flattenToleranceOf(ctx), spaces);
2462
2785
  if (mesh.indices.length === 0) return;
2463
2786
  const hasVColors = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
2464
2787
  const batched = batchPaint(ctx, paint, hasVColors);
@@ -2497,7 +2820,7 @@ var DOUBLED_VERTEX_WIDTHS = /* @__PURE__ */ new WeakMap();
2497
2820
  function doubledVertexWidths(widths) {
2498
2821
  let doubled = DOUBLED_VERTEX_WIDTHS.get(widths);
2499
2822
  if (doubled === void 0) {
2500
- doubled = widths.map((w) => w * 2);
2823
+ doubled = widths.map((w) => scaleVertexWidth(w, 2));
2501
2824
  DOUBLED_VERTEX_WIDTHS.set(widths, doubled);
2502
2825
  }
2503
2826
  return doubled;
@@ -2515,7 +2838,7 @@ function drawPathStrokeStenciled(ctx, cmd, align, spaces) {
2515
2838
  };
2516
2839
  const useVColor = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
2517
2840
  const fillHandle = fillMeshHandle(ctx, cmd.path);
2518
- const ribbonMesh = strokeMesh(cmd.path, widerStroke, ctx.flattenTolerance, spaces);
2841
+ const ribbonMesh = strokeMesh(cmd.path, widerStroke, flattenToleranceOf(ctx), spaces);
2519
2842
  if (ribbonMesh.indices.length === 0) return;
2520
2843
  const ribbonHandle = useVColor ? ctx.meshCache.uploadTransient(ribbonMesh) : ctx.meshCache.uploadRecurring(ribbonMesh);
2521
2844
  const gl = ctx.gl;
@@ -2625,19 +2948,38 @@ function mergeGlyphMeshes(group, dx, dy, meshFor) {
2625
2948
  }
2626
2949
  return { vertices, indices };
2627
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
2628
2960
  function drawColorGlyphs(ctx, group, dx, dy) {
2629
2961
  const fill = group.fill?.paint;
2630
2962
  if (fill === void 0 || group.quads.length === 0) return;
2631
2963
  if (!syncColorPageTexture(ctx.textureCache, group.page)) return;
2632
- const opacity = isSolidPaint(fill) ? resolveColor(fill.color)[3] : 1;
2633
- const atlasId = colorPageTextureId(group.page);
2964
+ const opacity = isSolidPaint(fill) ? solidColor(fill)[3] : 1;
2965
+ const baseId = colorPageTextureId(group.page);
2634
2966
  const batch = ctx.drawBatch;
2635
2967
  const m = ctx.state.transform;
2968
+ const devicePerCss = ctx.deviceWidth && ctx.widthCss ? ctx.deviceWidth / ctx.widthCss : 1;
2969
+ const toDevice = modelScale(m) * devicePerCss;
2970
+ let atlasId = baseId;
2636
2971
  let { slot, tint } = stageColorGlyphs(ctx, atlasId);
2637
2972
  const restage = () => {
2638
2973
  ({ slot, tint } = stageColorGlyphs(ctx, atlasId));
2639
2974
  };
2640
- 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
+ }
2641
2983
  if (batch.wouldOverflow(4)) {
2642
2984
  flushBatch(ctx);
2643
2985
  restage();
@@ -2668,6 +3010,31 @@ function drawColorGlyphs(ctx, group, dx, dy) {
2668
3010
  }
2669
3011
  }
2670
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
+ }
2671
3038
 
2672
3039
  // src/draw/scratchPass.ts
2673
3040
  var UNIT_QUAD = {
@@ -2745,11 +3112,6 @@ function bindScratchPaint(ctx, prog, painted) {
2745
3112
 
2746
3113
  // src/draw/text.ts
2747
3114
  var OUTLINE_MIN_SCREEN_PX = 48;
2748
- function modelScale(m) {
2749
- const sx = Math.hypot(m[0], m[1]);
2750
- const sy = Math.hypot(m[3], m[4]);
2751
- return Math.sqrt(sx * sy) || 1;
2752
- }
2753
3115
  function drawText(ctx, cmd) {
2754
3116
  const style = resolveTextStyle(cmd.style);
2755
3117
  const lineHeight = style.lineHeight;
@@ -2803,7 +3165,7 @@ function drawTextDecorations(ctx, decorations, dx, dy) {
2803
3165
  drawOutlineMesh(ctx, paint, ruleMesh(d.x0 + dx, d.y0 + dy, d.x1 + dx, d.y1 + dy));
2804
3166
  continue;
2805
3167
  }
2806
- 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];
2807
3169
  let cut;
2808
3170
  for (; ; ) {
2809
3171
  const slot = stageSolid(ctx, 4);
@@ -2831,7 +3193,6 @@ function ruleMesh(x0, y0, x1, y1) {
2831
3193
  indices: new Uint32Array([0, 1, 2, 0, 2, 3])
2832
3194
  };
2833
3195
  }
2834
- var SYNTH_BOLD_AMOUNT = 0.08;
2835
3196
  function drawTextGroup(ctx, group, dx, dy) {
2836
3197
  if (group.source === "canvas") {
2837
3198
  if (!syncDynamicPageTexture(ctx.textureCache, group.page)) return;
@@ -2857,7 +3218,7 @@ function drawTextGroup(ctx, group, dx, dy) {
2857
3218
  drawPaintedGlyphs(ctx, group, dx, dy, paintKind, atlasId, mode, bold, tanItalic, fieldScale);
2858
3219
  return;
2859
3220
  }
2860
- const color = isSolidPaint(fill) ? resolveColor(fill.color) : [0, 0, 0, 1];
3221
+ const color = isSolidPaint(fill) ? solidColor(fill) : [0, 0, 0, 1];
2861
3222
  const batch = ctx.drawBatch;
2862
3223
  const m = ctx.state.transform;
2863
3224
  let slot = stageGlyphs(ctx, atlasId, fieldScale);
@@ -2988,9 +3349,6 @@ function glyphScreenBounds(group, dx, dy, tanItalic, m) {
2988
3349
  return b;
2989
3350
  }
2990
3351
 
2991
- // src/DrawCommand.ts
2992
- var SPRITE_STRIDE = 9;
2993
-
2994
3352
  // src/draw/image.ts
2995
3353
  var MINIFIED = 2;
2996
3354
  function minifiedLimits(ctx) {
@@ -3189,6 +3547,7 @@ function ownsSurface(cmd) {
3189
3547
  return cmd.isolate === true || isOwnComposite(cmd.composite) || cmd.effects !== void 0 && cmd.effects.length > 0;
3190
3548
  }
3191
3549
  function drawGroup(ctx, cmd) {
3550
+ if (cmd.retain !== void 0 && drawRetainedGroup(ctx, cmd, ownsSurface(cmd), dispatch)) return;
3192
3551
  if (ownsSurface(cmd) && ctx.effectTargets && ctx.deviceWidth && ctx.deviceHeight) {
3193
3552
  if (cmd.clip) {
3194
3553
  ctx.state.push({ transform: cmd.transform });
@@ -3352,6 +3711,253 @@ var EffectTargets = class {
3352
3711
  }
3353
3712
  };
3354
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
+
3355
3961
  // src/shaders/extractUniformNames.ts
3356
3962
  var QUALIFIERS = /* @__PURE__ */ new Set([
3357
3963
  "lowp",
@@ -3583,6 +4189,7 @@ function compileCustomProgram(gl, src) {
3583
4189
  program.lookupAttributes(CUSTOM_ATTRIBUTES);
3584
4190
  return program;
3585
4191
  }
4192
+ var NO_RETAINED = { composited: 0, redrawn: 0, declined: 0, bytes: 0 };
3586
4193
  function now() {
3587
4194
  return typeof performance !== "undefined" ? performance.now() : Date.now();
3588
4195
  }
@@ -3593,7 +4200,7 @@ var GLYPH_PAINT_SOURCES = {
3593
4200
  };
3594
4201
  var WeaselRenderer = class {
3595
4202
  gl;
3596
- stats = { drawCalls: 0, ms: 0, spans: [] };
4203
+ stats = { drawCalls: 0, ms: 0, spans: [], retained: NO_RETAINED };
3597
4204
  /** Built on the first frame that draws with each, so a one-shot render of
3598
4205
  * plain shapes builds the batch program alone. */
3599
4206
  pathFill;
@@ -3630,6 +4237,10 @@ var WeaselRenderer = class {
3630
4237
  /** Offscreen buffers for group effects. Allocates nothing until a group
3631
4238
  * with effects asks, so a canvas without them pays no memory. */
3632
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;
3633
4244
  paintScratch;
3634
4245
  imageMinification;
3635
4246
  flattenTolerance;
@@ -3652,6 +4263,7 @@ var WeaselRenderer = class {
3652
4263
  if (!gl) throw new Error("WeaselRenderer: WebGL2 not available");
3653
4264
  this.gl = gl;
3654
4265
  this.effectTargets = new EffectTargets(this.gl);
4266
+ this.retained = new RetainedSurfaces(this.gl, opts.retainBudget ?? DEFAULT_RETAIN_BUDGET);
3655
4267
  this.paintScratch = new PaintScratch(this.gl);
3656
4268
  const attrs = typeof this.gl.getContextAttributes === "function" ? this.gl.getContextAttributes() : null;
3657
4269
  if (attrs && attrs.stencil === false) {
@@ -3723,15 +4335,16 @@ var WeaselRenderer = class {
3723
4335
  if (this.disposed) return;
3724
4336
  const src = getProgramSource(handle.id);
3725
4337
  if (!src) {
3726
- throw new Error(
3727
- `WeaselRenderer.registerProgram: program "${handle.id}" not found in source registry. Call the module-level registerProgram() first.`
3728
- );
4338
+ throw new Error(`WeaselRenderer.registerProgram: program "${handle.id}" not found in source registry. Call the module-level registerProgram() first.`);
3729
4339
  }
3730
4340
  this.installProgram(handle.id, src, compileCustomProgram(this.gl, src));
3731
4341
  }
3732
4342
  installProgram(id, src, program) {
3733
4343
  const previous = this.programRegistry.get(id);
3734
- if (previous) this.gl.deleteProgram(previous.handle);
4344
+ if (previous) {
4345
+ this.gl.deleteProgram(previous.handle);
4346
+ this.programEpoch++;
4347
+ }
3735
4348
  this.programRegistry.set(id, program);
3736
4349
  this.programSources.set(id, src);
3737
4350
  }
@@ -3803,28 +4416,28 @@ var WeaselRenderer = class {
3803
4416
  const oy = this.target?.origin.y ?? 0;
3804
4417
  const x = Math.round(ox * this.dpr);
3805
4418
  const y = Math.round(oy * this.dpr);
3806
- return {
3807
- x,
3808
- y,
3809
- width: Math.round((ox + this.widthCss) * this.dpr) - x,
3810
- height: Math.round((oy + this.heightCss) * this.dpr) - y
3811
- };
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 };
3812
4421
  }
3813
4422
  /** Viewport and scissor for this frame. Re-applied inside `render()` because
3814
4423
  * a co-tenant on the same context moves both between our frames. */
3815
4424
  applyTarget() {
3816
4425
  const gl = this.gl;
3817
- const { x, y, width, height } = this.deviceRect();
4426
+ const { x, y, width, height } = this.glRect();
4427
+ gl.viewport(x, y, width, height);
3818
4428
  if (!this.target) {
3819
4429
  gl.disable(gl.SCISSOR_TEST);
3820
- gl.viewport(0, 0, width, height);
3821
4430
  return;
3822
4431
  }
3823
- const glY = gl.drawingBufferHeight - y - height;
3824
- gl.viewport(x, glY, width, height);
3825
- gl.scissor(x, glY, width, height);
4432
+ gl.scissor(x, y, width, height);
3826
4433
  gl.enable(gl.SCISSOR_TEST);
3827
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
+ }
3828
4441
  /** Confine this renderer to a rect of its drawing buffer, or pass null to
3829
4442
  * give it the whole buffer back. Takes effect from the next `render()`. */
3830
4443
  setTarget(target) {
@@ -3846,6 +4459,7 @@ var WeaselRenderer = class {
3846
4459
  this.applyGlState();
3847
4460
  this.applyTarget();
3848
4461
  this.effectTargets.releaseAll();
4462
+ this.retained.releaseAll();
3849
4463
  this.paintScratch.release();
3850
4464
  this.paintResources.release();
3851
4465
  this.paintResources = createPaintResources();
@@ -3890,13 +4504,9 @@ var WeaselRenderer = class {
3890
4504
  this.meshCache.dispose();
3891
4505
  this.paintResources.release();
3892
4506
  for (const lazy of this.lazyPrograms()) lazy.release();
3893
- for (const prog of [this.batchFill, ...this.glyphPaintPrograms.values()]) {
3894
- gl.deleteProgram(prog.handle);
3895
- }
4507
+ for (const prog of [this.batchFill, ...this.glyphPaintPrograms.values()]) gl.deleteProgram(prog.handle);
3896
4508
  this.glyphPaintPrograms.clear();
3897
- for (const prog of this.programRegistry.values()) {
3898
- gl.deleteProgram(prog.handle);
3899
- }
4509
+ for (const prog of this.programRegistry.values()) gl.deleteProgram(prog.handle);
3900
4510
  this.programRegistry.clear();
3901
4511
  this.programSources.clear();
3902
4512
  this.textureCache.free();
@@ -3908,6 +4518,7 @@ var WeaselRenderer = class {
3908
4518
  if (this.whiteTexture) gl.deleteTexture(this.whiteTexture);
3909
4519
  this.whiteTexture = null;
3910
4520
  this.effectTargets.releaseAll();
4521
+ this.retained.releaseAll();
3911
4522
  this.paintScratch.release();
3912
4523
  }
3913
4524
  /**
@@ -3923,12 +4534,22 @@ var WeaselRenderer = class {
3923
4534
  const t0 = now();
3924
4535
  const counters = { drawCalls: 0 };
3925
4536
  const spanStats = [];
3926
- this.stats = { drawCalls: 0, ms: 0, spans: spanStats };
4537
+ this.stats = { drawCalls: 0, ms: 0, spans: spanStats, retained: NO_RETAINED };
3927
4538
  const gl = this.gl;
3928
4539
  this.meshCache.drainPendingDeletes();
3929
4540
  this.imageCache.beginFrame();
3930
4541
  this.textureCache.sweep(textureRegistry);
3931
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
+ ]);
3932
4553
  resetBakeBudget(this.bakeBudget);
3933
4554
  this.groupState.reset();
3934
4555
  this.drawBatch.reset();
@@ -3976,15 +4597,20 @@ var WeaselRenderer = class {
3976
4597
  heightCss: this.heightCss,
3977
4598
  clipDepth: 0,
3978
4599
  clipStack: [],
3979
- flattenTolerance: this.flattenTolerance,
4600
+ flattenTolerance: inCssPixels(this.flattenTolerance, this.dpr),
3980
4601
  textOutlineMinScreenSize: this.textOutlineMinScreenSize,
3981
4602
  viewMatrix,
3982
4603
  effectTargets: this.effectTargets,
4604
+ retained: this.retained,
3983
4605
  paintScratch: this.paintScratch,
3984
4606
  renderTarget: null,
3985
4607
  deviceWidth: device.width,
3986
4608
  deviceHeight: device.height,
3987
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
+ },
3988
4614
  paintResources: this.paintResources,
3989
4615
  stats: counters
3990
4616
  };
@@ -4003,6 +4629,14 @@ var WeaselRenderer = class {
4003
4629
  for (; next < commands.length; next++) dispatch(ctx, commands[next]);
4004
4630
  flushBatch(ctx);
4005
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
+ };
4006
4640
  this.stats.drawCalls = counters.drawCalls;
4007
4641
  this.stats.ms = now() - t0;
4008
4642
  }
@@ -4012,11 +4646,14 @@ var WeaselRenderer = class {
4012
4646
  lastFrameStats() {
4013
4647
  return this.stats;
4014
4648
  }
4015
- /** Change the curve-flattening tolerance after construction. `undefined`
4016
- * restores the cached `DEFAULT_FLATTEN_TOLERANCE` route. */
4649
+ /** Change `flattenTolerance` after construction; `undefined` restores the default. */
4017
4650
  setFlattenTolerance(tolerance) {
4018
4651
  this.flattenTolerance = tolerance;
4019
4652
  }
4653
+ /** Change `retainBudget` after construction; `undefined` restores the default. */
4654
+ setRetainBudget(bytes) {
4655
+ this.retained.setBudget(bytes ?? DEFAULT_RETAIN_BUDGET);
4656
+ }
4020
4657
  resize(dims) {
4021
4658
  this.widthCss = dims.width;
4022
4659
  this.heightCss = dims.height;
@@ -4129,119 +4766,6 @@ function frameRect(sheet, index) {
4129
4766
  h: frameHeight
4130
4767
  };
4131
4768
  }
4132
- var SLACK_PX = 1;
4133
- var DEFAULT_MITER_LIMIT = 4;
4134
- function cullDrawCommands(cmds, transform, rect) {
4135
- return cullList(cmds, transform, slackBox(rect));
4136
- }
4137
- function cullList(cmds, m, box) {
4138
- let out = null;
4139
- for (let i = 0; i < cmds.length; i++) {
4140
- const cmd = cmds[i];
4141
- const kept = cullOne(cmd, m, box);
4142
- if (kept === cmd) {
4143
- out?.push(cmd);
4144
- continue;
4145
- }
4146
- out ??= cmds.slice(0, i);
4147
- if (kept) out.push(kept);
4148
- }
4149
- return out ?? cmds;
4150
- }
4151
- function cullOne(cmd, m, box) {
4152
- switch (cmd.kind) {
4153
- case "group": {
4154
- if (cmd.effects && cmd.effects.length > 0) return cmd;
4155
- if (isOwnComposite(cmd.composite)) return cmd;
4156
- const inner = cmd.transform ? mat3.multiply(m, cmd.transform) : m;
4157
- const children = cullList(cmd.children, inner, box);
4158
- if (children === cmd.children) return cmd;
4159
- return children.length === 0 ? null : { ...cmd, children };
4160
- }
4161
- case "path":
4162
- return pathMisses(cmd.path, cmd.stroke, m, box) ? null : cmd;
4163
- case "image":
4164
- return quadMisses(cmd.x, cmd.y, cmd.x + cmd.w, cmd.y + cmd.h, m, box) ? null : cmd;
4165
- case "sprites":
4166
- return spritesMiss(cmd.sprites, m, box) ? null : cmd;
4167
- default:
4168
- return cmd;
4169
- }
4170
- }
4171
- function pathMisses(path, stroke, m, box) {
4172
- let minX, minY, maxX, maxY;
4173
- if (path.kind === "rect") {
4174
- minX = Math.min(path.x, path.x + path.width);
4175
- maxX = Math.max(path.x, path.x + path.width);
4176
- minY = Math.min(path.y, path.y + path.height);
4177
- maxY = Math.max(path.y, path.y + path.height);
4178
- } else if (path.kind === "polygon") {
4179
- const c = path.coords;
4180
- if (c.length < 2) return false;
4181
- minX = maxX = c[0];
4182
- minY = maxY = c[1];
4183
- for (let i = 2; i + 1 < c.length; i += 2) {
4184
- const x = c[i], y = c[i + 1];
4185
- if (x < minX) minX = x;
4186
- else if (x > maxX) maxX = x;
4187
- if (y < minY) minY = y;
4188
- else if (y > maxY) maxY = y;
4189
- }
4190
- } else {
4191
- return false;
4192
- }
4193
- const reach = stroke ? strokeReach(stroke, m) : 0;
4194
- return quadMisses(minX - reach, minY - reach, maxX + reach, maxY + reach, m, box);
4195
- }
4196
- function strokeReach(stroke, m) {
4197
- return strokeReachAt(stroke, mat3.meanScaleOf(m), mat3.minScaleOf(m));
4198
- }
4199
- function strokeReachAt(stroke, scale, least = scale) {
4200
- const at = least > 0 ? least : scale;
4201
- const base = resolveStrokeWidth(stroke.width ?? 1, typeof stroke.width === "object" ? at : scale);
4202
- let width = base;
4203
- if (stroke.vertexWidths) {
4204
- for (const v of stroke.vertexWidths) if (v > width) width = v;
4205
- }
4206
- const spike = Math.max(stroke.miterLimit ?? DEFAULT_MITER_LIMIT, Math.SQRT2);
4207
- const heads = markerReach(stroke);
4208
- return Math.max(Math.abs(width) * spike, heads.world, resolveScreenLength({ px: heads.px }, at));
4209
- }
4210
- function spritesMiss(sprites, m, box) {
4211
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
4212
- for (let i = 0; i + SPRITE_STRIDE <= sprites.length; i += SPRITE_STRIDE) {
4213
- const x0 = sprites[i], y0 = sprites[i + 1];
4214
- const x1 = x0 + sprites[i + 2], y1 = y0 + sprites[i + 3];
4215
- minX = Math.min(minX, x0, x1);
4216
- maxX = Math.max(maxX, x0, x1);
4217
- minY = Math.min(minY, y0, y1);
4218
- maxY = Math.max(maxY, y0, y1);
4219
- }
4220
- if (minX === Infinity) return false;
4221
- return quadMisses(minX, minY, maxX, maxY, m, box);
4222
- }
4223
- function boundsMissRect(bounds, transform, rect) {
4224
- const x0 = Math.min(bounds.x, bounds.x + bounds.width);
4225
- const x1 = Math.max(bounds.x, bounds.x + bounds.width);
4226
- const y0 = Math.min(bounds.y, bounds.y + bounds.height);
4227
- const y1 = Math.max(bounds.y, bounds.y + bounds.height);
4228
- return quadMisses(x0, y0, x1, y1, transform, slackBox(rect));
4229
- }
4230
- function slackBox(rect) {
4231
- return {
4232
- minX: rect.x - SLACK_PX,
4233
- minY: rect.y - SLACK_PX,
4234
- maxX: rect.x + rect.width + SLACK_PX,
4235
- maxY: rect.y + rect.height + SLACK_PX
4236
- };
4237
- }
4238
- function quadMisses(x0, y0, x1, y1, m, box) {
4239
- const [ax, ay] = mat3.apply(m, x0, y0);
4240
- const [bx, by] = mat3.apply(m, x1, y0);
4241
- const [cx, cy] = mat3.apply(m, x1, y1);
4242
- const [dx, dy] = mat3.apply(m, x0, y1);
4243
- 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;
4244
- }
4245
4769
 
4246
4770
  // src/effects/types.ts
4247
4771
  function registerEffect(id, frag) {
@@ -4367,6 +4891,6 @@ function isSolid(paint) {
4367
4891
  return paint == null || isSolidPaint(paint);
4368
4892
  }
4369
4893
 
4370
- 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 };
4371
4895
  //# sourceMappingURL=index.js.map
4372
4896
  //# sourceMappingURL=index.js.map