@vectojs/core 1.20.0 → 1.21.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.
@@ -964,6 +964,38 @@ var Entity = (_class2 = class {
964
964
  *
965
965
  * @returns The {@link A11yAttributes} for this entity's shadow node.
966
966
  */
967
+ /**
968
+ * Describe this entity's own debug surface for DevTools.
969
+ *
970
+ * Returns `null` by default, meaning "nothing beyond the generic `Entity`
971
+ * fields the inspector already shows". Override in a component to expose the
972
+ * state that makes it inspectable — see {@link DevtoolsDescriptor}.
973
+ *
974
+ * Called only while a panel is inspecting this entity, never per frame, so it
975
+ * may compute values that would be too expensive to track continuously.
976
+ *
977
+ * @returns A descriptor, or `null` to opt out.
978
+ */
979
+ getDevtoolsDescriptor() {
980
+ return null;
981
+ }
982
+ /**
983
+ * Which of a child's properties this entity computes during layout.
984
+ *
985
+ * Returns an empty array by default, meaning "this entity does not position its
986
+ * children". A container that lays out children — `Stack`, `Table`, `Tabs` —
987
+ * overrides it so tooling can mark those values as parent-owned: editing `x` on
988
+ * a `Stack` child is reverted by the next layout, and knowing that in advance is
989
+ * the difference between a confusing tool and a correct one.
990
+ *
991
+ * @param child - The child being asked about. Containers whose control depends
992
+ * on the child (a `Table` cell versus its header) can answer per child.
993
+ * @returns Property names this entity overwrites on that child.
994
+ */
995
+ getLayoutControlledProperties(child) {
996
+ void child;
997
+ return [];
998
+ }
967
999
  getA11yAttributes() {
968
1000
  return {};
969
1001
  }
@@ -962,6 +962,38 @@ var Entity = class {
962
962
  *
963
963
  * @returns The {@link A11yAttributes} for this entity's shadow node.
964
964
  */
965
+ /**
966
+ * Describe this entity's own debug surface for DevTools.
967
+ *
968
+ * Returns `null` by default, meaning "nothing beyond the generic `Entity`
969
+ * fields the inspector already shows". Override in a component to expose the
970
+ * state that makes it inspectable — see {@link DevtoolsDescriptor}.
971
+ *
972
+ * Called only while a panel is inspecting this entity, never per frame, so it
973
+ * may compute values that would be too expensive to track continuously.
974
+ *
975
+ * @returns A descriptor, or `null` to opt out.
976
+ */
977
+ getDevtoolsDescriptor() {
978
+ return null;
979
+ }
980
+ /**
981
+ * Which of a child's properties this entity computes during layout.
982
+ *
983
+ * Returns an empty array by default, meaning "this entity does not position its
984
+ * children". A container that lays out children — `Stack`, `Table`, `Tabs` —
985
+ * overrides it so tooling can mark those values as parent-owned: editing `x` on
986
+ * a `Stack` child is reverted by the next layout, and knowing that in advance is
987
+ * the difference between a confusing tool and a correct one.
988
+ *
989
+ * @param child - The child being asked about. Containers whose control depends
990
+ * on the child (a `Table` cell versus its header) can answer per child.
991
+ * @returns Property names this entity overwrites on that child.
992
+ */
993
+ getLayoutControlledProperties(child) {
994
+ void child;
995
+ return [];
996
+ }
965
997
  getA11yAttributes() {
966
998
  return {};
967
999
  }
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4;const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5;const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
2
2
 
3
3
  // src/renderer/CanvasRenderer.ts
4
4
  var TWO_PI = Math.PI * 2;
@@ -212,6 +212,11 @@ var CanvasRenderer = (_class = class _CanvasRenderer {
212
212
  this.ctx.drawImage(source, dx, dy, dw, dh);
213
213
  }
214
214
  /** @inheritdoc */
215
+ drawImageRect(source, sx, sy, sw, sh, dx, dy, dw, dh) {
216
+ this.flush();
217
+ this.ctx.drawImage(source, sx, sy, sw, sh, dx, dy, dw, dh);
218
+ }
219
+ /** @inheritdoc */
215
220
  fillCircle(cx, cy, radius, color, alpha = 1) {
216
221
  if (this.batchActive && (color !== this.batchColor || alpha !== this.batchAlpha)) {
217
222
  this.flush();
@@ -1809,15 +1814,201 @@ var WebGPUParticleSystemManager = (_class3 = class {
1809
1814
  }
1810
1815
  }, _class3);
1811
1816
 
1817
+ // src/renderer/GlyphRasterAtlas.ts
1818
+ var HARD_MAX_SIZE = 8192;
1819
+ var PAD = 2;
1820
+ var GlyphRasterAtlas = (_class4 = class {
1821
+ __init35() {this.slots = /* @__PURE__ */ new Map()}
1822
+
1823
+
1824
+ __init36() {this.canvas = null}
1825
+ __init37() {this.ctx = null}
1826
+ /**
1827
+ * Shelf packing: glyphs land left-to-right on a row, then a new row starts.
1828
+ * A monospace grid produces near-uniform widths, so shelves waste very little
1829
+ * and cost one comparison per insert — a real 2D packer would buy nothing here.
1830
+ */
1831
+ __init38() {this.penX = PAD}
1832
+ __init39() {this.penY = PAD}
1833
+ __init40() {this.rowHeight = 0}
1834
+ __init41() {this._hits = 0}
1835
+ __init42() {this._misses = 0}
1836
+ __init43() {this._resets = 0}
1837
+ constructor(options = {}) {;_class4.prototype.__init35.call(this);_class4.prototype.__init36.call(this);_class4.prototype.__init37.call(this);_class4.prototype.__init38.call(this);_class4.prototype.__init39.call(this);_class4.prototype.__init40.call(this);_class4.prototype.__init41.call(this);_class4.prototype.__init42.call(this);_class4.prototype.__init43.call(this);
1838
+ this.dpr = Math.max(1, _nullishCoalesce(options.dpr, () => ( 1)));
1839
+ this.maxSize = Math.min(HARD_MAX_SIZE, Math.max(256, _nullishCoalesce(options.maxSize, () => ( 2048))));
1840
+ }
1841
+ /** Live instrumentation snapshot. */
1842
+ get stats() {
1843
+ return {
1844
+ hits: this._hits,
1845
+ misses: this._misses,
1846
+ size: this.slots.size,
1847
+ resets: this._resets
1848
+ };
1849
+ }
1850
+ /**
1851
+ * The atlas canvas, to pass as the blit source.
1852
+ *
1853
+ * `null` until the first successful {@link get}, and in any non-DOM context.
1854
+ */
1855
+ get source() {
1856
+ return this.canvas;
1857
+ }
1858
+ ensureCanvas() {
1859
+ if (this.ctx) return true;
1860
+ if (typeof document === "undefined") return false;
1861
+ const canvas = document.createElement("canvas");
1862
+ canvas.width = this.maxSize;
1863
+ canvas.height = this.maxSize;
1864
+ const ctx = canvas.getContext("2d");
1865
+ if (!ctx) return false;
1866
+ this.canvas = canvas;
1867
+ this.ctx = ctx;
1868
+ return true;
1869
+ }
1870
+ /**
1871
+ * Look up a glyph, rasterizing it into the atlas on first request.
1872
+ *
1873
+ * @param font - Full CSS `font` shorthand, used for measuring and painting.
1874
+ * @param color - CSS color baked into the pixels.
1875
+ * @param glyph - A single grapheme cluster. Long strings are rejected
1876
+ * (`null`): they defeat the atlas's fixed-slot packing and belong in
1877
+ * `fillText` or {@link TextRasterCache}.
1878
+ * @returns The slot, or `null` when the caller must fall back to `fillText`
1879
+ * (headless, unrasterizable, or too large to pack).
1880
+ */
1881
+ get(font, color, glyph) {
1882
+ const key = font + "\0" + color + "\0" + glyph;
1883
+ const existing = this.slots.get(key);
1884
+ if (existing !== void 0) {
1885
+ if (existing) this._hits++;
1886
+ return existing;
1887
+ }
1888
+ this._misses++;
1889
+ if (glyph.length === 0 || glyph.length > 8) {
1890
+ this.slots.set(key, null);
1891
+ return null;
1892
+ }
1893
+ if (!this.ensureCanvas()) return null;
1894
+ const ctx = this.ctx;
1895
+ ctx.font = font;
1896
+ const metrics = ctx.measureText(glyph);
1897
+ const advance = metrics.width;
1898
+ const ascent = metrics.actualBoundingBoxAscent || metrics.fontBoundingBoxAscent || advance * 0.9;
1899
+ const descent = metrics.actualBoundingBoxDescent || metrics.fontBoundingBoxDescent || advance * 0.25;
1900
+ const left = metrics.actualBoundingBoxLeft || 0;
1901
+ const right = metrics.actualBoundingBoxRight || advance;
1902
+ const offsetX = Math.ceil(left) + PAD;
1903
+ const offsetY = Math.ceil(ascent) + PAD;
1904
+ const w = offsetX + Math.ceil(right) + PAD;
1905
+ const h = offsetY + Math.ceil(descent) + PAD;
1906
+ if (!(w > 0) || !(h > 0)) {
1907
+ this.slots.set(key, null);
1908
+ return null;
1909
+ }
1910
+ const dpr = this.dpr;
1911
+ const dw = Math.ceil(w * dpr);
1912
+ const dh = Math.ceil(h * dpr);
1913
+ if (dw + PAD * 2 > this.maxSize || dh + PAD * 2 > this.maxSize) {
1914
+ this.slots.set(key, null);
1915
+ return null;
1916
+ }
1917
+ if (this.penX + dw + PAD > this.maxSize) {
1918
+ this.penX = PAD;
1919
+ this.penY += this.rowHeight + PAD;
1920
+ this.rowHeight = 0;
1921
+ }
1922
+ if (this.penY + dh + PAD > this.maxSize) {
1923
+ this.reset();
1924
+ if (!this.ensureCanvas()) return null;
1925
+ this.ctx.font = font;
1926
+ }
1927
+ const sx = this.penX;
1928
+ const sy = this.penY;
1929
+ const c = this.ctx;
1930
+ c.save();
1931
+ c.translate(sx, sy);
1932
+ if (dpr !== 1) c.scale(dpr, dpr);
1933
+ c.font = font;
1934
+ c.textBaseline = "alphabetic";
1935
+ c.fillStyle = color;
1936
+ c.fillText(glyph, offsetX, offsetY);
1937
+ c.restore();
1938
+ this.penX += dw + PAD;
1939
+ if (dh > this.rowHeight) this.rowHeight = dh;
1940
+ const slot = {
1941
+ sx,
1942
+ sy,
1943
+ sw: dw,
1944
+ sh: dh,
1945
+ w,
1946
+ h,
1947
+ offsetX,
1948
+ offsetY,
1949
+ glyph,
1950
+ font,
1951
+ advance,
1952
+ left,
1953
+ right
1954
+ };
1955
+ this.slots.set(key, slot);
1956
+ return slot;
1957
+ }
1958
+ /**
1959
+ * Find the slot occupying a source position, or `null`.
1960
+ *
1961
+ * The inverse of {@link get}: it maps a blit back to the glyph it drew. Exists
1962
+ * for instrumentation — a test or devtool that traces `drawImage` calls sees
1963
+ * only a source rect, and needs this to recover which cluster was painted and
1964
+ * with what metrics. Linear over resident slots, so it is a diagnostic, not a
1965
+ * per-frame call.
1966
+ */
1967
+ slotAt(sx, sy) {
1968
+ for (const slot of this.slots.values()) {
1969
+ if (slot && slot.sx === sx && slot.sy === sy) return slot;
1970
+ }
1971
+ return null;
1972
+ }
1973
+ /**
1974
+ * Drop every glyph and reuse the canvas.
1975
+ *
1976
+ * Call after a font or theme change: slots are keyed by `(font, color, glyph)`
1977
+ * so stale entries are never *returned* wrongly, but they do occupy space.
1978
+ */
1979
+ reset() {
1980
+ this.slots.clear();
1981
+ this.penX = PAD;
1982
+ this.penY = PAD;
1983
+ this.rowHeight = 0;
1984
+ this._resets++;
1985
+ if (this.ctx && this.canvas) {
1986
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
1987
+ }
1988
+ }
1989
+ /** Release the backing canvas and all slots. */
1990
+ destroy() {
1991
+ this.slots.clear();
1992
+ this.canvas = null;
1993
+ this.ctx = null;
1994
+ this.penX = PAD;
1995
+ this.penY = PAD;
1996
+ this.rowHeight = 0;
1997
+ this._hits = 0;
1998
+ this._misses = 0;
1999
+ this._resets = 0;
2000
+ }
2001
+ }, _class4);
2002
+
1812
2003
  // src/renderer/TextRasterCache.ts
1813
- var TextRasterCache = (_class4 = class {
1814
- __init35() {this.cache = /* @__PURE__ */ new Map()}
2004
+ var TextRasterCache = (_class5 = class {
2005
+ __init44() {this.cache = /* @__PURE__ */ new Map()}
1815
2006
 
1816
2007
 
1817
- __init36() {this.scratchCtx = null}
1818
- __init37() {this._hits = 0}
1819
- __init38() {this._misses = 0}
1820
- constructor(options = {}) {;_class4.prototype.__init35.call(this);_class4.prototype.__init36.call(this);_class4.prototype.__init37.call(this);_class4.prototype.__init38.call(this);
2008
+ __init45() {this.scratchCtx = null}
2009
+ __init46() {this._hits = 0}
2010
+ __init47() {this._misses = 0}
2011
+ constructor(options = {}) {;_class5.prototype.__init44.call(this);_class5.prototype.__init45.call(this);_class5.prototype.__init46.call(this);_class5.prototype.__init47.call(this);
1821
2012
  this.maxEntries = Math.max(1, _nullishCoalesce(options.maxEntries, () => ( 4096)));
1822
2013
  this.dpr = Math.max(1, _nullishCoalesce(options.dpr, () => ( 1)));
1823
2014
  }
@@ -1892,7 +2083,8 @@ var TextRasterCache = (_class4 = class {
1892
2083
  this._hits = 0;
1893
2084
  this._misses = 0;
1894
2085
  }
1895
- }, _class4);
2086
+ }, _class5);
2087
+
1896
2088
 
1897
2089
 
1898
2090
 
@@ -1903,4 +2095,4 @@ var TextRasterCache = (_class4 = class {
1903
2095
 
1904
2096
 
1905
2097
 
1906
- exports.CanvasRenderer = CanvasRenderer; exports.sanitizeUrl = sanitizeUrl; exports.isSafeUrl = isSafeUrl; exports.SVGRenderer = SVGRenderer; exports.parseColorToRGBA = parseColorToRGBA; exports.createWebGLPointRenderer = createWebGLPointRenderer; exports.WebGPUParticleSystemManager = WebGPUParticleSystemManager; exports.TextRasterCache = TextRasterCache;
2098
+ exports.CanvasRenderer = CanvasRenderer; exports.sanitizeUrl = sanitizeUrl; exports.isSafeUrl = isSafeUrl; exports.SVGRenderer = SVGRenderer; exports.parseColorToRGBA = parseColorToRGBA; exports.createWebGLPointRenderer = createWebGLPointRenderer; exports.WebGPUParticleSystemManager = WebGPUParticleSystemManager; exports.GlyphRasterAtlas = GlyphRasterAtlas; exports.TextRasterCache = TextRasterCache;
@@ -210,6 +210,11 @@ var CanvasRenderer = class _CanvasRenderer {
210
210
  this.ctx.drawImage(source, dx, dy, dw, dh);
211
211
  }
212
212
  /** @inheritdoc */
213
+ drawImageRect(source, sx, sy, sw, sh, dx, dy, dw, dh) {
214
+ this.flush();
215
+ this.ctx.drawImage(source, sx, sy, sw, sh, dx, dy, dw, dh);
216
+ }
217
+ /** @inheritdoc */
213
218
  fillCircle(cx, cy, radius, color, alpha = 1) {
214
219
  if (this.batchActive && (color !== this.batchColor || alpha !== this.batchAlpha)) {
215
220
  this.flush();
@@ -1807,6 +1812,192 @@ var WebGPUParticleSystemManager = class {
1807
1812
  }
1808
1813
  };
1809
1814
 
1815
+ // src/renderer/GlyphRasterAtlas.ts
1816
+ var HARD_MAX_SIZE = 8192;
1817
+ var PAD = 2;
1818
+ var GlyphRasterAtlas = class {
1819
+ slots = /* @__PURE__ */ new Map();
1820
+ dpr;
1821
+ maxSize;
1822
+ canvas = null;
1823
+ ctx = null;
1824
+ /**
1825
+ * Shelf packing: glyphs land left-to-right on a row, then a new row starts.
1826
+ * A monospace grid produces near-uniform widths, so shelves waste very little
1827
+ * and cost one comparison per insert — a real 2D packer would buy nothing here.
1828
+ */
1829
+ penX = PAD;
1830
+ penY = PAD;
1831
+ rowHeight = 0;
1832
+ _hits = 0;
1833
+ _misses = 0;
1834
+ _resets = 0;
1835
+ constructor(options = {}) {
1836
+ this.dpr = Math.max(1, options.dpr ?? 1);
1837
+ this.maxSize = Math.min(HARD_MAX_SIZE, Math.max(256, options.maxSize ?? 2048));
1838
+ }
1839
+ /** Live instrumentation snapshot. */
1840
+ get stats() {
1841
+ return {
1842
+ hits: this._hits,
1843
+ misses: this._misses,
1844
+ size: this.slots.size,
1845
+ resets: this._resets
1846
+ };
1847
+ }
1848
+ /**
1849
+ * The atlas canvas, to pass as the blit source.
1850
+ *
1851
+ * `null` until the first successful {@link get}, and in any non-DOM context.
1852
+ */
1853
+ get source() {
1854
+ return this.canvas;
1855
+ }
1856
+ ensureCanvas() {
1857
+ if (this.ctx) return true;
1858
+ if (typeof document === "undefined") return false;
1859
+ const canvas = document.createElement("canvas");
1860
+ canvas.width = this.maxSize;
1861
+ canvas.height = this.maxSize;
1862
+ const ctx = canvas.getContext("2d");
1863
+ if (!ctx) return false;
1864
+ this.canvas = canvas;
1865
+ this.ctx = ctx;
1866
+ return true;
1867
+ }
1868
+ /**
1869
+ * Look up a glyph, rasterizing it into the atlas on first request.
1870
+ *
1871
+ * @param font - Full CSS `font` shorthand, used for measuring and painting.
1872
+ * @param color - CSS color baked into the pixels.
1873
+ * @param glyph - A single grapheme cluster. Long strings are rejected
1874
+ * (`null`): they defeat the atlas's fixed-slot packing and belong in
1875
+ * `fillText` or {@link TextRasterCache}.
1876
+ * @returns The slot, or `null` when the caller must fall back to `fillText`
1877
+ * (headless, unrasterizable, or too large to pack).
1878
+ */
1879
+ get(font, color, glyph) {
1880
+ const key = font + "\0" + color + "\0" + glyph;
1881
+ const existing = this.slots.get(key);
1882
+ if (existing !== void 0) {
1883
+ if (existing) this._hits++;
1884
+ return existing;
1885
+ }
1886
+ this._misses++;
1887
+ if (glyph.length === 0 || glyph.length > 8) {
1888
+ this.slots.set(key, null);
1889
+ return null;
1890
+ }
1891
+ if (!this.ensureCanvas()) return null;
1892
+ const ctx = this.ctx;
1893
+ ctx.font = font;
1894
+ const metrics = ctx.measureText(glyph);
1895
+ const advance = metrics.width;
1896
+ const ascent = metrics.actualBoundingBoxAscent || metrics.fontBoundingBoxAscent || advance * 0.9;
1897
+ const descent = metrics.actualBoundingBoxDescent || metrics.fontBoundingBoxDescent || advance * 0.25;
1898
+ const left = metrics.actualBoundingBoxLeft || 0;
1899
+ const right = metrics.actualBoundingBoxRight || advance;
1900
+ const offsetX = Math.ceil(left) + PAD;
1901
+ const offsetY = Math.ceil(ascent) + PAD;
1902
+ const w = offsetX + Math.ceil(right) + PAD;
1903
+ const h = offsetY + Math.ceil(descent) + PAD;
1904
+ if (!(w > 0) || !(h > 0)) {
1905
+ this.slots.set(key, null);
1906
+ return null;
1907
+ }
1908
+ const dpr = this.dpr;
1909
+ const dw = Math.ceil(w * dpr);
1910
+ const dh = Math.ceil(h * dpr);
1911
+ if (dw + PAD * 2 > this.maxSize || dh + PAD * 2 > this.maxSize) {
1912
+ this.slots.set(key, null);
1913
+ return null;
1914
+ }
1915
+ if (this.penX + dw + PAD > this.maxSize) {
1916
+ this.penX = PAD;
1917
+ this.penY += this.rowHeight + PAD;
1918
+ this.rowHeight = 0;
1919
+ }
1920
+ if (this.penY + dh + PAD > this.maxSize) {
1921
+ this.reset();
1922
+ if (!this.ensureCanvas()) return null;
1923
+ this.ctx.font = font;
1924
+ }
1925
+ const sx = this.penX;
1926
+ const sy = this.penY;
1927
+ const c = this.ctx;
1928
+ c.save();
1929
+ c.translate(sx, sy);
1930
+ if (dpr !== 1) c.scale(dpr, dpr);
1931
+ c.font = font;
1932
+ c.textBaseline = "alphabetic";
1933
+ c.fillStyle = color;
1934
+ c.fillText(glyph, offsetX, offsetY);
1935
+ c.restore();
1936
+ this.penX += dw + PAD;
1937
+ if (dh > this.rowHeight) this.rowHeight = dh;
1938
+ const slot = {
1939
+ sx,
1940
+ sy,
1941
+ sw: dw,
1942
+ sh: dh,
1943
+ w,
1944
+ h,
1945
+ offsetX,
1946
+ offsetY,
1947
+ glyph,
1948
+ font,
1949
+ advance,
1950
+ left,
1951
+ right
1952
+ };
1953
+ this.slots.set(key, slot);
1954
+ return slot;
1955
+ }
1956
+ /**
1957
+ * Find the slot occupying a source position, or `null`.
1958
+ *
1959
+ * The inverse of {@link get}: it maps a blit back to the glyph it drew. Exists
1960
+ * for instrumentation — a test or devtool that traces `drawImage` calls sees
1961
+ * only a source rect, and needs this to recover which cluster was painted and
1962
+ * with what metrics. Linear over resident slots, so it is a diagnostic, not a
1963
+ * per-frame call.
1964
+ */
1965
+ slotAt(sx, sy) {
1966
+ for (const slot of this.slots.values()) {
1967
+ if (slot && slot.sx === sx && slot.sy === sy) return slot;
1968
+ }
1969
+ return null;
1970
+ }
1971
+ /**
1972
+ * Drop every glyph and reuse the canvas.
1973
+ *
1974
+ * Call after a font or theme change: slots are keyed by `(font, color, glyph)`
1975
+ * so stale entries are never *returned* wrongly, but they do occupy space.
1976
+ */
1977
+ reset() {
1978
+ this.slots.clear();
1979
+ this.penX = PAD;
1980
+ this.penY = PAD;
1981
+ this.rowHeight = 0;
1982
+ this._resets++;
1983
+ if (this.ctx && this.canvas) {
1984
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
1985
+ }
1986
+ }
1987
+ /** Release the backing canvas and all slots. */
1988
+ destroy() {
1989
+ this.slots.clear();
1990
+ this.canvas = null;
1991
+ this.ctx = null;
1992
+ this.penX = PAD;
1993
+ this.penY = PAD;
1994
+ this.rowHeight = 0;
1995
+ this._hits = 0;
1996
+ this._misses = 0;
1997
+ this._resets = 0;
1998
+ }
1999
+ };
2000
+
1810
2001
  // src/renderer/TextRasterCache.ts
1811
2002
  var TextRasterCache = class {
1812
2003
  cache = /* @__PURE__ */ new Map();
@@ -1900,5 +2091,6 @@ export {
1900
2091
  parseColorToRGBA,
1901
2092
  createWebGLPointRenderer,
1902
2093
  WebGPUParticleSystemManager,
2094
+ GlyphRasterAtlas,
1903
2095
  TextRasterCache
1904
2096
  };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './renderer/WebGLPointRenderer';
5
5
  export * from './renderer/WebGPUParticleSystemManager';
6
6
  export * from './renderer/colorParse';
7
7
  export * from './renderer/url';
8
+ export * from './renderer/GlyphRasterAtlas';
8
9
  export * from './renderer/TextRasterCache';
9
10
  export * from './tree/Entity';
10
11
  export * from './tree/Scene';