@canvas-harness/core 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -633,8 +633,8 @@ var EdgeGeometryCache = class {
633
633
  * Caller is responsible for passing the current store-managed version.
634
634
  */
635
635
  get(edge, version, getNode) {
636
- const cached = this.entries.get(edge.id);
637
- if (cached && cached.version === version) return cached.geom;
636
+ const cached2 = this.entries.get(edge.id);
637
+ if (cached2 && cached2.version === version) return cached2.geom;
638
638
  const geom = computeEdgeGeometry(edge, getNode);
639
639
  if (geom) this.entries.set(edge.id, { version, geom });
640
640
  else this.entries.delete(edge.id);
@@ -846,8 +846,8 @@ var mixHex = (a, b, t) => {
846
846
  };
847
847
  var darkenCache = /* @__PURE__ */ new Map();
848
848
  var darkenHex = (hex) => {
849
- const cached = darkenCache.get(hex);
850
- if (cached !== void 0) return cached;
849
+ const cached2 = darkenCache.get(hex);
850
+ if (cached2 !== void 0) return cached2;
851
851
  const result = mixHex(hex, "#000000", TONE_BLEND);
852
852
  darkenCache.set(hex, result);
853
853
  return result;
@@ -1614,7 +1614,7 @@ var buildPath = (type, x, y, w, h, radius) => {
1614
1614
  };
1615
1615
 
1616
1616
  // src/text/tokens.ts
1617
- var INLINE_PATTERN = /(\*\*[^*]+\*\*|==[^=\s](?:[^=]*?[^=\s])?==|`[^`]+`|\*[^*]+\*|__[^_]+__|~~[^~]+~~|_[^_]+_|\[[^\]]+\]\([^)]+\))/g;
1617
+ var INLINE_PATTERN = /(\$[^$\n]+?\$|\*\*[^*]+\*\*|==[^=\s](?:[^=]*?[^=\s])?==|`[^`]+`|\*[^*]+\*|__[^_]+__|~~[^~]+~~|_[^_]+_|\[[^\]]+\]\([^)]+\))/g;
1618
1618
  var HR_LINE_PATTERN = /^[ \t]*---[ \t]*$/;
1619
1619
  var DOUBLE_HR_LINE_PATTERN = /^[ \t]*===[ \t]*$/;
1620
1620
  var transformSymbols = (value) => value.replace(/<=>|<->|<-|->|\[\]|\[[vx]\]/gi, (match) => {
@@ -1652,6 +1652,8 @@ var tokenizeInline = (segment) => {
1652
1652
  tokens.push({ type: "link", content: transformSymbols(match.slice(1, splitIndex)) });
1653
1653
  } else if (match.startsWith("`") && match.endsWith("`")) {
1654
1654
  tokens.push({ type: "code", content: match.slice(1, -1) });
1655
+ } else if (match.startsWith("$") && match.endsWith("$")) {
1656
+ tokens.push({ type: "math", content: match.slice(1, -1) });
1655
1657
  } else {
1656
1658
  tokens.push({ type: "text", content: transformSymbols(match) });
1657
1659
  }
@@ -1740,6 +1742,225 @@ var DEFAULT_HIGHLIGHT_COLOR_DARK = "#6b5a23";
1740
1742
  var LINK_COLOR = "#2563eb";
1741
1743
  var CODE_BG_COLOR = "rgba(148, 163, 184, 0.18)";
1742
1744
 
1745
+ // src/text/math/loader.ts
1746
+ var cached = null;
1747
+ var loadPromise2 = null;
1748
+ var loadFailed = false;
1749
+ var readyCallbacks2 = /* @__PURE__ */ new Set();
1750
+ var getMathJax = () => {
1751
+ if (cached) return cached;
1752
+ if (loadFailed) return null;
1753
+ if (!loadPromise2) {
1754
+ loadPromise2 = loadMathJax().then((instance) => {
1755
+ if (instance) {
1756
+ cached = instance;
1757
+ for (const cb of readyCallbacks2) cb();
1758
+ } else {
1759
+ loadFailed = true;
1760
+ }
1761
+ readyCallbacks2.clear();
1762
+ return cached;
1763
+ }).catch((err) => {
1764
+ console.warn("[math] failed to load MathJax:", err);
1765
+ loadFailed = true;
1766
+ readyCallbacks2.clear();
1767
+ return null;
1768
+ });
1769
+ }
1770
+ return null;
1771
+ };
1772
+ var onMathJaxReady = (cb) => {
1773
+ if (cached) return;
1774
+ if (loadFailed) return;
1775
+ readyCallbacks2.add(cb);
1776
+ };
1777
+ var loadMathJax = async () => {
1778
+ if (typeof window === "undefined") return null;
1779
+ const winAny = window;
1780
+ winAny.MathJax = {
1781
+ ...winAny.MathJax ?? {},
1782
+ startup: { typeset: false },
1783
+ options: {
1784
+ enableMenu: false,
1785
+ enableEnrichment: false,
1786
+ enableSpeech: false,
1787
+ enableComplexity: false,
1788
+ sre: { speech: "none" }
1789
+ },
1790
+ // `fontCache: 'none'` inlines every glyph as a raw <path>
1791
+ // (slightly bigger SVG, no <use> references). Required for SVGs
1792
+ // we extract to a Blob URL and rasterize via <img> — `<use>`
1793
+ // refs to <defs> elsewhere in the page wouldn't resolve.
1794
+ // `linebreaks: { inline: false }` keeps the whole formula in one
1795
+ // <svg> element (v4 defaults to true for long inline math).
1796
+ svg: {
1797
+ scale: 1,
1798
+ fontCache: "none",
1799
+ linebreaks: { inline: false }
1800
+ }
1801
+ };
1802
+ const VENDOR_URL = "https://cdn.jsdelivr.net/npm/mathjax@4/tex-svg.js";
1803
+ await new Promise((resolve, reject) => {
1804
+ const existing = document.querySelector(
1805
+ `script[src="${VENDOR_URL}"]`
1806
+ );
1807
+ if (existing) {
1808
+ existing.addEventListener("load", () => resolve(), { once: true });
1809
+ existing.addEventListener("error", () => reject(new Error("MathJax CDN load failed")), {
1810
+ once: true
1811
+ });
1812
+ return;
1813
+ }
1814
+ const script = document.createElement("script");
1815
+ script.src = VENDOR_URL;
1816
+ script.async = true;
1817
+ script.onload = () => resolve();
1818
+ script.onerror = () => reject(new Error("MathJax CDN load failed"));
1819
+ document.head.appendChild(script);
1820
+ });
1821
+ const mj = winAny.MathJax;
1822
+ if (!mj) throw new Error("MathJax did not install on window after import");
1823
+ if (typeof mj.tex2svgPromise !== "function") {
1824
+ throw new Error("MathJax loaded but tex2svgPromise is missing \u2014 wrong bundle?");
1825
+ }
1826
+ await mj.startup?.promise;
1827
+ return mj;
1828
+ };
1829
+
1830
+ // src/text/math/cache.ts
1831
+ var normalizeSize = (px) => Math.max(8, Math.round(px));
1832
+ var cache3 = /* @__PURE__ */ new Map();
1833
+ var compileQueue = [];
1834
+ var compileScheduled = false;
1835
+ var mathEpoch = 0;
1836
+ var epochSubscribers = /* @__PURE__ */ new Set();
1837
+ var getMathEpoch = () => mathEpoch;
1838
+ var subscribeMathEpoch = (cb) => {
1839
+ epochSubscribers.add(cb);
1840
+ return () => {
1841
+ epochSubscribers.delete(cb);
1842
+ };
1843
+ };
1844
+ var bumpMathEpoch = () => {
1845
+ mathEpoch += 1;
1846
+ for (const cb of epochSubscribers) cb();
1847
+ };
1848
+ var getMathBitmap = (source, color, sizePx) => {
1849
+ const size = normalizeSize(sizePx);
1850
+ const key = `${size}:${color}:${source}`;
1851
+ const existing = cache3.get(key);
1852
+ if (existing) {
1853
+ if (existing.state === "ready") return existing.bitmap;
1854
+ return null;
1855
+ }
1856
+ cache3.set(key, { state: "pending" });
1857
+ compileQueue.push({ key, source, color, sizePx: size });
1858
+ scheduleCompile();
1859
+ return null;
1860
+ };
1861
+ var scheduleCompile = () => {
1862
+ if (compileScheduled) return;
1863
+ compileScheduled = true;
1864
+ if (typeof window === "undefined" || typeof requestAnimationFrame === "undefined") {
1865
+ void drainQueue();
1866
+ return;
1867
+ }
1868
+ requestAnimationFrame(() => {
1869
+ void drainQueue();
1870
+ });
1871
+ };
1872
+ var drainQueue = async () => {
1873
+ compileScheduled = false;
1874
+ if (compileQueue.length === 0) return;
1875
+ const mj = getMathJax();
1876
+ if (!mj) {
1877
+ onMathJaxReady(() => scheduleCompile());
1878
+ return;
1879
+ }
1880
+ const FRAME_BUDGET_MS = 4;
1881
+ const start = performance.now();
1882
+ let didResolve = false;
1883
+ while (compileQueue.length > 0 && performance.now() - start < FRAME_BUDGET_MS) {
1884
+ const item = compileQueue.shift();
1885
+ if (cache3.get(item.key)?.state !== "pending") continue;
1886
+ try {
1887
+ const bitmap = await compileOne(mj, item.source, item.color, item.sizePx);
1888
+ cache3.set(item.key, { state: "ready", bitmap });
1889
+ didResolve = true;
1890
+ } catch (err) {
1891
+ cache3.set(item.key, { state: "error", err });
1892
+ console.warn(`[math] failed to compile "${item.source}":`, err);
1893
+ }
1894
+ }
1895
+ if (didResolve) bumpMathEpoch();
1896
+ if (compileQueue.length > 0) scheduleCompile();
1897
+ };
1898
+ var compileOne = async (mj, source, color, sizePx) => {
1899
+ const svgElement = await mj.tex2svgPromise(source, { display: false, em: sizePx, ex: sizePx / 2 });
1900
+ let markup = mj.startup.adaptor.serializeXML ? mj.startup.adaptor.serializeXML(svgElement) : mj.startup.adaptor.outerHTML(svgElement);
1901
+ const svgMatch = /<svg[\s\S]*?<\/svg>/.exec(markup);
1902
+ if (svgMatch) markup = svgMatch[0];
1903
+ markup = markup.replace(/\sdata-semantic-[a-z0-9-]+="[^"]*"/g, "").replace(/\sdata-speech-[a-z0-9-]+="[^"]*"/g, "").replace(/\sdata-mml-node="[^"]*"/g, "").replace(/\sdata-latex="[^"]*"/g, "").replace(/\sdata-braille[a-z0-9-]*="[^"]*"/g, "").replace(/\saria-[a-z0-9-]+="[^"]*"/g, "").replace(/\srole="[^"]*"/g, "").replace(/\sfocusable="[^"]*"/g, "").replace(/\stabindex="[^"]*"/g, "").replace(/\shas-speech="[^"]*"/g, "");
1904
+ if (!markup.includes('xmlns="http://www.w3.org/2000/svg"')) {
1905
+ markup = markup.replace(/^<svg\b/, '<svg xmlns="http://www.w3.org/2000/svg"');
1906
+ }
1907
+ markup = markup.replace(/currentColor/gi, color);
1908
+ const dims = parseSvgDims(markup, sizePx);
1909
+ const blob = new Blob([markup], { type: "image/svg+xml" });
1910
+ const url = URL.createObjectURL(blob);
1911
+ try {
1912
+ let img;
1913
+ try {
1914
+ img = await loadImage(url);
1915
+ } catch (e) {
1916
+ console.warn(`[math] SVG failed to load for "${source}":
1917
+ ${markup}`);
1918
+ throw e;
1919
+ }
1920
+ const rasterW = Math.max(1, Math.ceil(dims.width * 2));
1921
+ const rasterH = Math.max(1, Math.ceil(dims.height * 2));
1922
+ const bitmap = await createImageBitmap(img, {
1923
+ resizeWidth: rasterW,
1924
+ resizeHeight: rasterH,
1925
+ resizeQuality: "high"
1926
+ });
1927
+ return {
1928
+ bitmap,
1929
+ width: dims.width,
1930
+ height: dims.height,
1931
+ baselineOffset: dims.baselineOffset
1932
+ };
1933
+ } finally {
1934
+ URL.revokeObjectURL(url);
1935
+ }
1936
+ };
1937
+ var loadImage = (src) => new Promise((resolve, reject) => {
1938
+ const img = new Image();
1939
+ img.onload = () => resolve(img);
1940
+ img.onerror = (e) => reject(e);
1941
+ img.src = src;
1942
+ });
1943
+ var parseSvgDims = (markup, sizePx) => {
1944
+ const exToPx = sizePx / 2;
1945
+ const widthMatch = /<svg[^>]*\bwidth="([0-9.]+)ex"/.exec(markup);
1946
+ const heightMatch = /<svg[^>]*\bheight="([0-9.]+)ex"/.exec(markup);
1947
+ const vAlignMatch = /vertical-align:\s*(-?[0-9.]+)ex/.exec(markup);
1948
+ const widthEx = widthMatch ? Number.parseFloat(widthMatch[1]) : 2;
1949
+ const heightEx = heightMatch ? Number.parseFloat(heightMatch[1]) : 2;
1950
+ const vAlignEx = vAlignMatch ? Number.parseFloat(vAlignMatch[1]) : 0;
1951
+ const width = widthEx * exToPx;
1952
+ const height = heightEx * exToPx;
1953
+ const descent = Math.abs(vAlignEx) * exToPx;
1954
+ const baselineOffset = height - descent;
1955
+ return { width, height, baselineOffset };
1956
+ };
1957
+ var clearMathCache = () => {
1958
+ cache3.clear();
1959
+ compileQueue.length = 0;
1960
+ compileScheduled = false;
1961
+ };
1962
+ var getMathCacheSize = () => cache3.size;
1963
+
1743
1964
  // src/text/measure.ts
1744
1965
  var MAX_WIDTH_CACHE_SIZE = 5e3;
1745
1966
  var measureCanvas = typeof document !== "undefined" ? document.createElement("canvas") : null;
@@ -1753,10 +1974,16 @@ var getCanvasFont = (opts) => {
1753
1974
  };
1754
1975
  var measureText = (opts) => {
1755
1976
  if (!opts.text) return 0;
1977
+ if (opts.type === "math") {
1978
+ const fontSizePx = FONT_SIZE_MAP[opts.fontSize];
1979
+ const bitmap = getMathBitmap(opts.text, DEFAULT_TEXT_COLOR, fontSizePx);
1980
+ if (bitmap) return bitmap.width;
1981
+ return Math.max(8, opts.text.length * fontSizePx * 0.55 + fontSizePx);
1982
+ }
1756
1983
  const font = getCanvasFont(opts);
1757
1984
  const key = `${font}|${opts.text}`;
1758
- const cached = widthCache.get(key);
1759
- if (cached !== void 0) return cached;
1985
+ const cached2 = widthCache.get(key);
1986
+ if (cached2 !== void 0) return cached2;
1760
1987
  if (!measureCtx) {
1761
1988
  return opts.text.length * FONT_SIZE_MAP[opts.fontSize] * 0.55;
1762
1989
  }
@@ -1898,6 +2125,8 @@ var layoutTokens = (tokens, opts) => {
1898
2125
  currentRuns.push({ text: chunk, type });
1899
2126
  cursorX += chunkWidth;
1900
2127
  };
2128
+ const fontSizePx = FONT_SIZE_MAP[opts.fontSize];
2129
+ const mathColor = opts.textColor || DEFAULT_TEXT_COLOR;
1901
2130
  for (const token of tokens) {
1902
2131
  if (token.type === "code-block") {
1903
2132
  pushCodeBlock(token.content);
@@ -1915,6 +2144,18 @@ var layoutTokens = (tokens, opts) => {
1915
2144
  pushRule(true);
1916
2145
  continue;
1917
2146
  }
2147
+ if (token.type === "math") {
2148
+ const bitmap = getMathBitmap(token.content, mathColor, fontSizePx);
2149
+ const width = bitmap ? bitmap.width : (
2150
+ // Placeholder: roughly proportional to source length so wrap
2151
+ // doesn't dramatically shift on resolve. Capped at maxWidth.
2152
+ Math.min(maxWidth, token.content.length * fontSizePx * 0.55 + fontSizePx)
2153
+ );
2154
+ if (cursorX > 0 && cursorX + width > maxWidth) pushLine();
2155
+ currentRuns.push({ text: token.content, type: "math" });
2156
+ cursorX += width;
2157
+ continue;
2158
+ }
1918
2159
  for (const chunk of splitChunks(token.content)) pushChunk(chunk, token.type);
1919
2160
  }
1920
2161
  if (currentRuns.length > 0 || lines.length === 0) {
@@ -2081,7 +2322,8 @@ var drawTextToCanvas = (ctx, opts) => {
2081
2322
  width: opts.width,
2082
2323
  fontFamily: opts.fontFamily,
2083
2324
  fontSize: opts.fontSize,
2084
- textStyle: opts.textStyle
2325
+ textStyle: opts.textStyle,
2326
+ textColor: opts.textColor
2085
2327
  });
2086
2328
  ctx.textBaseline = "alphabetic";
2087
2329
  ctx.fillStyle = opts.textColor || DEFAULT_TEXT_COLOR;
@@ -2189,6 +2431,24 @@ var drawTextToCanvas = (ctx, opts) => {
2189
2431
  fontSize: opts.fontSize,
2190
2432
  textStyle: opts.textStyle
2191
2433
  });
2434
+ if (run.type === "math") {
2435
+ const mathColor = opts.textColor || DEFAULT_TEXT_COLOR;
2436
+ const bitmap = getMathBitmap(run.text, mathColor, fontSizePx);
2437
+ if (bitmap) {
2438
+ ctx.drawImage(bitmap.bitmap, x, y - bitmap.baselineOffset, runWidth, bitmap.height);
2439
+ } else {
2440
+ ctx.save();
2441
+ ctx.fillStyle = "rgba(148, 163, 184, 0.18)";
2442
+ ctx.fillRect(x, y - fontSizePx + 2, runWidth, fontSizePx);
2443
+ ctx.fillStyle = "#94a3b8";
2444
+ ctx.font = `italic ${Math.max(8, fontSizePx * 0.75)}px system-ui, sans-serif`;
2445
+ ctx.textBaseline = "alphabetic";
2446
+ ctx.fillText("\u2026", x + runWidth / 2 - 4, y - 2);
2447
+ ctx.restore();
2448
+ }
2449
+ x += runWidth;
2450
+ continue;
2451
+ }
2192
2452
  if (run.type === "highlight") {
2193
2453
  ctx.save();
2194
2454
  ctx.fillStyle = opts.highlightColor || DEFAULT_HIGHLIGHT_COLOR;
@@ -2227,6 +2487,8 @@ subscribeFontEpoch(() => {
2227
2487
  renderCache.clear();
2228
2488
  textHashCache.clear();
2229
2489
  });
2490
+ subscribeMathEpoch(() => {
2491
+ });
2230
2492
  var getOrRenderTextBitmap = (req) => {
2231
2493
  const text = req.text;
2232
2494
  if (!text || !text.trim()) return null;
@@ -2235,10 +2497,10 @@ var getOrRenderTextBitmap = (req) => {
2235
2497
  const renderScale = resolveRenderScale(1, quantZoom, req.isMoving);
2236
2498
  const epoch = getFontEpoch();
2237
2499
  const key = makeKey(req, quantZoom, quantDpr, renderScale, epoch);
2238
- const cached = renderCache.get(key);
2239
- if (cached) {
2240
- cached.lastUsed = Date.now();
2241
- return { canvas: cached.canvas, width: cached.width, height: cached.height };
2500
+ const cached2 = renderCache.get(key);
2501
+ if (cached2) {
2502
+ cached2.lastUsed = Date.now();
2503
+ return { canvas: cached2.canvas, width: cached2.width, height: cached2.height };
2242
2504
  }
2243
2505
  const entry = drawIntoNewCanvas(req, quantDpr, renderScale);
2244
2506
  if (!entry) return null;
@@ -2247,7 +2509,8 @@ var getOrRenderTextBitmap = (req) => {
2247
2509
  return { canvas: entry.canvas, width: entry.width, height: entry.height };
2248
2510
  };
2249
2511
  var makeKey = (req, zoom, dpr, scale, epoch) => {
2250
- return `${epoch}:${cachedTextHash(req.text)}:${req.width}:${req.height}:${zoom}:${dpr}:${scale}:${req.align}:${req.fontFamily}:${req.fontSize}:${req.textStyle}:${req.textColor}:${req.highlightColor}`;
2512
+ const mathSuffix = req.text.includes("$") ? `:m${getMathEpoch()}` : "";
2513
+ return `${epoch}:${cachedTextHash(req.text)}:${req.width}:${req.height}:${zoom}:${dpr}:${scale}:${req.align}:${req.fontFamily}:${req.fontSize}:${req.textStyle}:${req.textColor}:${req.highlightColor}${mathSuffix}`;
2251
2514
  };
2252
2515
  var cachedTextHash = (value) => {
2253
2516
  const hit = textHashCache.get(value);
@@ -2303,12 +2566,12 @@ var clearTextBitmapCache = () => {
2303
2566
  };
2304
2567
  var getTextBitmapCacheSize = () => renderCache.size;
2305
2568
  var FREEHAND_CACHE_MAX = 500;
2306
- var cache3 = /* @__PURE__ */ new Map();
2569
+ var cache4 = /* @__PURE__ */ new Map();
2307
2570
  var remember = (key, path) => {
2308
- cache3.set(key, path);
2309
- if (cache3.size > FREEHAND_CACHE_MAX) {
2310
- const oldest = cache3.keys().next().value;
2311
- if (oldest !== void 0) cache3.delete(oldest);
2571
+ cache4.set(key, path);
2572
+ if (cache4.size > FREEHAND_CACHE_MAX) {
2573
+ const oldest = cache4.keys().next().value;
2574
+ if (oldest !== void 0) cache4.delete(oldest);
2312
2575
  }
2313
2576
  };
2314
2577
  var signaturePoints = (samples) => {
@@ -2360,10 +2623,10 @@ var outlineToPath2D = (ring) => {
2360
2623
  var getOrBuildFreehandPath = (samples, strokeWidth, seed) => {
2361
2624
  if (samples.length < 2) return null;
2362
2625
  const cacheKey = `${seed}|${strokeWidth.toFixed(2)}|${signaturePoints(samples)}`;
2363
- const hit = cache3.get(cacheKey);
2626
+ const hit = cache4.get(cacheKey);
2364
2627
  if (hit) {
2365
- cache3.delete(cacheKey);
2366
- cache3.set(cacheKey, hit);
2628
+ cache4.delete(cacheKey);
2629
+ cache4.set(cacheKey, hit);
2367
2630
  return hit;
2368
2631
  }
2369
2632
  const pts = buildPressurePoints(samples);
@@ -4219,11 +4482,11 @@ var paintPlaceholder = (ctx, w, h, label) => {
4219
4482
  ctx.fillText(label, w / 2, h / 2);
4220
4483
  }
4221
4484
  };
4222
- var paintImageNode = (ctx, node, cache4, theme) => {
4485
+ var paintImageNode = (ctx, node, cache5, theme) => {
4223
4486
  if (node.w <= 0 || node.h <= 0) return;
4224
4487
  const data = node.data;
4225
4488
  if (!data?.src) return;
4226
- const bitmap = cache4.getImage(data.src);
4489
+ const bitmap = cache5.getImage(data.src);
4227
4490
  const opacity = resolveOpacity(node.style, theme);
4228
4491
  const needsScope = opacity !== 1;
4229
4492
  if (needsScope) {
@@ -4237,13 +4500,13 @@ var paintImageNode = (ctx, node, cache4, theme) => {
4237
4500
  }
4238
4501
  if (needsScope) ctx.restore();
4239
4502
  };
4240
- var paintIconNode = (ctx, node, cache4, scale, theme) => {
4503
+ var paintIconNode = (ctx, node, cache5, scale, theme) => {
4241
4504
  if (node.w <= 0 || node.h <= 0) return;
4242
4505
  const data = node.data;
4243
4506
  if (!data?.src) return;
4244
4507
  const sizePx = Math.max(node.w, node.h) * scale;
4245
4508
  const color = node.style?.iconColor;
4246
- const bitmap = cache4.getIcon(data.src, color, sizePx);
4509
+ const bitmap = cache5.getIcon(data.src, color, sizePx);
4247
4510
  const opacity = resolveOpacity(node.style, theme);
4248
4511
  const needsScope = opacity !== 1;
4249
4512
  if (needsScope) {
@@ -5068,6 +5331,10 @@ var createRenderer = (opts) => {
5068
5331
  staticDirty = true;
5069
5332
  loop.requestFrame();
5070
5333
  });
5334
+ const unsubMathEpoch = subscribeMathEpoch(() => {
5335
+ staticDirty = true;
5336
+ loop.requestFrame();
5337
+ });
5071
5338
  return {
5072
5339
  start() {
5073
5340
  loop.start();
@@ -5117,6 +5384,7 @@ var createRenderer = (opts) => {
5117
5384
  unsubSelection();
5118
5385
  unsubInteraction();
5119
5386
  unsubFontEpoch();
5387
+ unsubMathEpoch();
5120
5388
  assetCache.dispose();
5121
5389
  }
5122
5390
  };
@@ -6149,6 +6417,6 @@ var installedExtensions = (store) => {
6149
6417
  // src/index.ts
6150
6418
  var VERSION = "0.0.0";
6151
6419
 
6152
- export { BEZIER_SEGMENTS, CODE_BG_COLOR, CODE_BLOCK_MARGIN_Y, CODE_BLOCK_PADDING_X, CONTENT_HEIGHT_BUFFER, CONTENT_PADDING, DEFAULT_BACKGROUND, DEFAULT_CAMERA, DEFAULT_HIGHLIGHT_COLOR, DEFAULT_HIGHLIGHT_COLOR_DARK, DEFAULT_MINIMAP_MAX_NODES, DEFAULT_STYLE, DEFAULT_TEXT_COLOR, EDGE_HANDLE_SLOP_PX, EDGE_HIT_SLOP_PX, EdgeGeometryCache, FONT_FAMILY_MAP, FONT_SIZE_MAP, LINE_HEIGHT_MAP, LINK_COLOR, MAX_IMAGE_BYTES, MAX_SVG_BYTES, MAX_ZOOM, MIN_ZOOM, PALM_REJECTION_GRACE_MS, RESIZE_HANDLES, RESIZE_HANDLE_SIZE_PX, ROTATE_HANDLE_OFFSET_PX, ROTATE_HANDLE_RADIUS_PX, SCHEMA_VERSION, UniformGrid, VERSION, applyCameraTransform, applySvgColor, arrowheadLength, asBatchId, asClientId, asEdgeId, asGroupId, asNodeId, attachSync, autoRouteControls, blobToDataUri, clampEffectiveScale, clampZoom, clearMeasureCache, clearSurface, clearTextBitmapCache, clipSamples, computeAutoFitHeight, computeEdgeGeometry, copy, createCanvasStore, createDefaultTextareaEditor, createFrameLoop, createPalmRejectionState, createRenderer, cubicBezier, cubicBezierTangent, cut, defineExtension, defineNode, deserializeClipboard, detectConflicts, downscaleImageBlob, drawArrowhead, drawEdge, drawMinimapViewport, drawShape, drawTextToCanvas, drawWithNodeTransform, edgeAABBFromSamples, edgeLabelBoundsWorld, emptyPresenceState, estimateMarkdownContentHeight, exportSelection, exportSelectionSvg, exportViewport, extractSvgDimensions, fromSerialized, fullVisibleClipResult, getCanvasFont, getContentHeight, getContext, getDpr, getFontEpoch, getMarkdownLineHeightPx, getOrRenderTextBitmap, getPointAndTangentAtArcLength, getTextBitmapCacheSize, handleEnter, handleWorldPositions, hitTestAny, hitTestEdge, hitTestHandles, hitTestPoint, hitTestRotateHandle, idleInteractionState, inflateRect, insertLink, installExtension, installedExtensions, inverseBatch, inverseOp, isAttached, isCanvasHarnessClipboard, isDrawablePrimitive, isMoving, isNodeRemoteEditing, layoutTokens, makeIdGenerator, marqueeNodes, measureText, midpointToCubicControls, minimapScreenToWorld, nodeAABB, nodeIntersectsRect, nodeLocalToWorld, notePenActive, notePenInactive, opSchemas, opSchemasAsAnthropicTools, paintBackground, panByScreen, paste, pointInNode, projectEndToWorld, projectToNodeBoundary, quantizeDpr, quantizeZoom, randomClientId, rectContainsPoint, rectFromPoints, rectsIntersect, registerMigrator, renderMinimapContent, resolveColor, resolveOpacity, resolveRenderScale, resolveStrokeWidth, rotateHandleWorldPosition, rotateVecByAngle, sampleBezier, sampleSelfLoop, samplesFor, sanitizeSvg, sceneBounds, screenToWorld, selfLoopGeometry, serializeSelection, setupSurface, shouldAutoFit, shouldRejectTouch, sideNormalLocal, sideOf, sizeSurface, storeToJSON, subscribeFontEpoch, tangentAtArcLength, toImageBlob, toSerialized, toggleBold, toggleCode, toggleItalic, toggleStrike, toggleUnderline, tokenize, unionRects, validateImageInput, validateSvgMarkup, viewportWorldRect, withAutoFitHeight, worldToNodeLocal, worldToScreen, worldViewport, worldViewportFromCamera, zoomAtScreenPoint };
6420
+ export { BEZIER_SEGMENTS, CODE_BG_COLOR, CODE_BLOCK_MARGIN_Y, CODE_BLOCK_PADDING_X, CONTENT_HEIGHT_BUFFER, CONTENT_PADDING, DEFAULT_BACKGROUND, DEFAULT_CAMERA, DEFAULT_HIGHLIGHT_COLOR, DEFAULT_HIGHLIGHT_COLOR_DARK, DEFAULT_MINIMAP_MAX_NODES, DEFAULT_STYLE, DEFAULT_TEXT_COLOR, EDGE_HANDLE_SLOP_PX, EDGE_HIT_SLOP_PX, EdgeGeometryCache, FONT_FAMILY_MAP, FONT_SIZE_MAP, LINE_HEIGHT_MAP, LINK_COLOR, MAX_IMAGE_BYTES, MAX_SVG_BYTES, MAX_ZOOM, MIN_ZOOM, PALM_REJECTION_GRACE_MS, RESIZE_HANDLES, RESIZE_HANDLE_SIZE_PX, ROTATE_HANDLE_OFFSET_PX, ROTATE_HANDLE_RADIUS_PX, SCHEMA_VERSION, UniformGrid, VERSION, applyCameraTransform, applySvgColor, arrowheadLength, asBatchId, asClientId, asEdgeId, asGroupId, asNodeId, attachSync, autoRouteControls, blobToDataUri, clampEffectiveScale, clampZoom, clearMathCache, clearMeasureCache, clearSurface, clearTextBitmapCache, clipSamples, computeAutoFitHeight, computeEdgeGeometry, copy, createCanvasStore, createDefaultTextareaEditor, createFrameLoop, createPalmRejectionState, createRenderer, cubicBezier, cubicBezierTangent, cut, defineExtension, defineNode, deserializeClipboard, detectConflicts, downscaleImageBlob, drawArrowhead, drawEdge, drawMinimapViewport, drawShape, drawTextToCanvas, drawWithNodeTransform, edgeAABBFromSamples, edgeLabelBoundsWorld, emptyPresenceState, estimateMarkdownContentHeight, exportSelection, exportSelectionSvg, exportViewport, extractSvgDimensions, fromSerialized, fullVisibleClipResult, getCanvasFont, getContentHeight, getContext, getDpr, getFontEpoch, getMarkdownLineHeightPx, getMathBitmap, getMathCacheSize, getMathEpoch, getMathJax, getOrRenderTextBitmap, getPointAndTangentAtArcLength, getTextBitmapCacheSize, handleEnter, handleWorldPositions, hitTestAny, hitTestEdge, hitTestHandles, hitTestPoint, hitTestRotateHandle, idleInteractionState, inflateRect, insertLink, installExtension, installedExtensions, inverseBatch, inverseOp, isAttached, isCanvasHarnessClipboard, isDrawablePrimitive, isMoving, isNodeRemoteEditing, layoutTokens, makeIdGenerator, marqueeNodes, measureText, midpointToCubicControls, minimapScreenToWorld, nodeAABB, nodeIntersectsRect, nodeLocalToWorld, notePenActive, notePenInactive, onMathJaxReady, opSchemas, opSchemasAsAnthropicTools, paintBackground, panByScreen, paste, pointInNode, projectEndToWorld, projectToNodeBoundary, quantizeDpr, quantizeZoom, randomClientId, rectContainsPoint, rectFromPoints, rectsIntersect, registerMigrator, renderMinimapContent, resolveColor, resolveOpacity, resolveRenderScale, resolveStrokeWidth, rotateHandleWorldPosition, rotateVecByAngle, sampleBezier, sampleSelfLoop, samplesFor, sanitizeSvg, sceneBounds, screenToWorld, selfLoopGeometry, serializeSelection, setupSurface, shouldAutoFit, shouldRejectTouch, sideNormalLocal, sideOf, sizeSurface, storeToJSON, subscribeFontEpoch, subscribeMathEpoch, tangentAtArcLength, toImageBlob, toSerialized, toggleBold, toggleCode, toggleItalic, toggleStrike, toggleUnderline, tokenize, unionRects, validateImageInput, validateSvgMarkup, viewportWorldRect, withAutoFitHeight, worldToNodeLocal, worldToScreen, worldViewport, worldViewportFromCamera, zoomAtScreenPoint };
6153
6421
  //# sourceMappingURL=index.js.map
6154
6422
  //# sourceMappingURL=index.js.map