@canvas-harness/core 0.0.5 → 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.cjs +328 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -6
- package/dist/index.d.ts +122 -6
- package/dist/index.js +322 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -635,8 +635,8 @@ var EdgeGeometryCache = class {
|
|
|
635
635
|
* Caller is responsible for passing the current store-managed version.
|
|
636
636
|
*/
|
|
637
637
|
get(edge, version, getNode) {
|
|
638
|
-
const
|
|
639
|
-
if (
|
|
638
|
+
const cached2 = this.entries.get(edge.id);
|
|
639
|
+
if (cached2 && cached2.version === version) return cached2.geom;
|
|
640
640
|
const geom = computeEdgeGeometry(edge, getNode);
|
|
641
641
|
if (geom) this.entries.set(edge.id, { version, geom });
|
|
642
642
|
else this.entries.delete(edge.id);
|
|
@@ -848,8 +848,8 @@ var mixHex = (a, b, t) => {
|
|
|
848
848
|
};
|
|
849
849
|
var darkenCache = /* @__PURE__ */ new Map();
|
|
850
850
|
var darkenHex = (hex) => {
|
|
851
|
-
const
|
|
852
|
-
if (
|
|
851
|
+
const cached2 = darkenCache.get(hex);
|
|
852
|
+
if (cached2 !== void 0) return cached2;
|
|
853
853
|
const result = mixHex(hex, "#000000", TONE_BLEND);
|
|
854
854
|
darkenCache.set(hex, result);
|
|
855
855
|
return result;
|
|
@@ -1616,7 +1616,7 @@ var buildPath = (type, x, y, w, h, radius) => {
|
|
|
1616
1616
|
};
|
|
1617
1617
|
|
|
1618
1618
|
// src/text/tokens.ts
|
|
1619
|
-
var INLINE_PATTERN = /(
|
|
1619
|
+
var INLINE_PATTERN = /(\$[^$\n]+?\$|\*\*[^*]+\*\*|==[^=\s](?:[^=]*?[^=\s])?==|`[^`]+`|\*[^*]+\*|__[^_]+__|~~[^~]+~~|_[^_]+_|\[[^\]]+\]\([^)]+\))/g;
|
|
1620
1620
|
var HR_LINE_PATTERN = /^[ \t]*---[ \t]*$/;
|
|
1621
1621
|
var DOUBLE_HR_LINE_PATTERN = /^[ \t]*===[ \t]*$/;
|
|
1622
1622
|
var transformSymbols = (value) => value.replace(/<=>|<->|<-|->|\[\]|\[[vx]\]/gi, (match) => {
|
|
@@ -1654,6 +1654,8 @@ var tokenizeInline = (segment) => {
|
|
|
1654
1654
|
tokens.push({ type: "link", content: transformSymbols(match.slice(1, splitIndex)) });
|
|
1655
1655
|
} else if (match.startsWith("`") && match.endsWith("`")) {
|
|
1656
1656
|
tokens.push({ type: "code", content: match.slice(1, -1) });
|
|
1657
|
+
} else if (match.startsWith("$") && match.endsWith("$")) {
|
|
1658
|
+
tokens.push({ type: "math", content: match.slice(1, -1) });
|
|
1657
1659
|
} else {
|
|
1658
1660
|
tokens.push({ type: "text", content: transformSymbols(match) });
|
|
1659
1661
|
}
|
|
@@ -1742,6 +1744,225 @@ var DEFAULT_HIGHLIGHT_COLOR_DARK = "#6b5a23";
|
|
|
1742
1744
|
var LINK_COLOR = "#2563eb";
|
|
1743
1745
|
var CODE_BG_COLOR = "rgba(148, 163, 184, 0.18)";
|
|
1744
1746
|
|
|
1747
|
+
// src/text/math/loader.ts
|
|
1748
|
+
var cached = null;
|
|
1749
|
+
var loadPromise2 = null;
|
|
1750
|
+
var loadFailed = false;
|
|
1751
|
+
var readyCallbacks2 = /* @__PURE__ */ new Set();
|
|
1752
|
+
var getMathJax = () => {
|
|
1753
|
+
if (cached) return cached;
|
|
1754
|
+
if (loadFailed) return null;
|
|
1755
|
+
if (!loadPromise2) {
|
|
1756
|
+
loadPromise2 = loadMathJax().then((instance) => {
|
|
1757
|
+
if (instance) {
|
|
1758
|
+
cached = instance;
|
|
1759
|
+
for (const cb of readyCallbacks2) cb();
|
|
1760
|
+
} else {
|
|
1761
|
+
loadFailed = true;
|
|
1762
|
+
}
|
|
1763
|
+
readyCallbacks2.clear();
|
|
1764
|
+
return cached;
|
|
1765
|
+
}).catch((err) => {
|
|
1766
|
+
console.warn("[math] failed to load MathJax:", err);
|
|
1767
|
+
loadFailed = true;
|
|
1768
|
+
readyCallbacks2.clear();
|
|
1769
|
+
return null;
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
return null;
|
|
1773
|
+
};
|
|
1774
|
+
var onMathJaxReady = (cb) => {
|
|
1775
|
+
if (cached) return;
|
|
1776
|
+
if (loadFailed) return;
|
|
1777
|
+
readyCallbacks2.add(cb);
|
|
1778
|
+
};
|
|
1779
|
+
var loadMathJax = async () => {
|
|
1780
|
+
if (typeof window === "undefined") return null;
|
|
1781
|
+
const winAny = window;
|
|
1782
|
+
winAny.MathJax = {
|
|
1783
|
+
...winAny.MathJax ?? {},
|
|
1784
|
+
startup: { typeset: false },
|
|
1785
|
+
options: {
|
|
1786
|
+
enableMenu: false,
|
|
1787
|
+
enableEnrichment: false,
|
|
1788
|
+
enableSpeech: false,
|
|
1789
|
+
enableComplexity: false,
|
|
1790
|
+
sre: { speech: "none" }
|
|
1791
|
+
},
|
|
1792
|
+
// `fontCache: 'none'` inlines every glyph as a raw <path>
|
|
1793
|
+
// (slightly bigger SVG, no <use> references). Required for SVGs
|
|
1794
|
+
// we extract to a Blob URL and rasterize via <img> — `<use>`
|
|
1795
|
+
// refs to <defs> elsewhere in the page wouldn't resolve.
|
|
1796
|
+
// `linebreaks: { inline: false }` keeps the whole formula in one
|
|
1797
|
+
// <svg> element (v4 defaults to true for long inline math).
|
|
1798
|
+
svg: {
|
|
1799
|
+
scale: 1,
|
|
1800
|
+
fontCache: "none",
|
|
1801
|
+
linebreaks: { inline: false }
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
const VENDOR_URL = "https://cdn.jsdelivr.net/npm/mathjax@4/tex-svg.js";
|
|
1805
|
+
await new Promise((resolve, reject) => {
|
|
1806
|
+
const existing = document.querySelector(
|
|
1807
|
+
`script[src="${VENDOR_URL}"]`
|
|
1808
|
+
);
|
|
1809
|
+
if (existing) {
|
|
1810
|
+
existing.addEventListener("load", () => resolve(), { once: true });
|
|
1811
|
+
existing.addEventListener("error", () => reject(new Error("MathJax CDN load failed")), {
|
|
1812
|
+
once: true
|
|
1813
|
+
});
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
const script = document.createElement("script");
|
|
1817
|
+
script.src = VENDOR_URL;
|
|
1818
|
+
script.async = true;
|
|
1819
|
+
script.onload = () => resolve();
|
|
1820
|
+
script.onerror = () => reject(new Error("MathJax CDN load failed"));
|
|
1821
|
+
document.head.appendChild(script);
|
|
1822
|
+
});
|
|
1823
|
+
const mj = winAny.MathJax;
|
|
1824
|
+
if (!mj) throw new Error("MathJax did not install on window after import");
|
|
1825
|
+
if (typeof mj.tex2svgPromise !== "function") {
|
|
1826
|
+
throw new Error("MathJax loaded but tex2svgPromise is missing \u2014 wrong bundle?");
|
|
1827
|
+
}
|
|
1828
|
+
await mj.startup?.promise;
|
|
1829
|
+
return mj;
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1832
|
+
// src/text/math/cache.ts
|
|
1833
|
+
var normalizeSize = (px) => Math.max(8, Math.round(px));
|
|
1834
|
+
var cache3 = /* @__PURE__ */ new Map();
|
|
1835
|
+
var compileQueue = [];
|
|
1836
|
+
var compileScheduled = false;
|
|
1837
|
+
var mathEpoch = 0;
|
|
1838
|
+
var epochSubscribers = /* @__PURE__ */ new Set();
|
|
1839
|
+
var getMathEpoch = () => mathEpoch;
|
|
1840
|
+
var subscribeMathEpoch = (cb) => {
|
|
1841
|
+
epochSubscribers.add(cb);
|
|
1842
|
+
return () => {
|
|
1843
|
+
epochSubscribers.delete(cb);
|
|
1844
|
+
};
|
|
1845
|
+
};
|
|
1846
|
+
var bumpMathEpoch = () => {
|
|
1847
|
+
mathEpoch += 1;
|
|
1848
|
+
for (const cb of epochSubscribers) cb();
|
|
1849
|
+
};
|
|
1850
|
+
var getMathBitmap = (source, color, sizePx) => {
|
|
1851
|
+
const size = normalizeSize(sizePx);
|
|
1852
|
+
const key = `${size}:${color}:${source}`;
|
|
1853
|
+
const existing = cache3.get(key);
|
|
1854
|
+
if (existing) {
|
|
1855
|
+
if (existing.state === "ready") return existing.bitmap;
|
|
1856
|
+
return null;
|
|
1857
|
+
}
|
|
1858
|
+
cache3.set(key, { state: "pending" });
|
|
1859
|
+
compileQueue.push({ key, source, color, sizePx: size });
|
|
1860
|
+
scheduleCompile();
|
|
1861
|
+
return null;
|
|
1862
|
+
};
|
|
1863
|
+
var scheduleCompile = () => {
|
|
1864
|
+
if (compileScheduled) return;
|
|
1865
|
+
compileScheduled = true;
|
|
1866
|
+
if (typeof window === "undefined" || typeof requestAnimationFrame === "undefined") {
|
|
1867
|
+
void drainQueue();
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
requestAnimationFrame(() => {
|
|
1871
|
+
void drainQueue();
|
|
1872
|
+
});
|
|
1873
|
+
};
|
|
1874
|
+
var drainQueue = async () => {
|
|
1875
|
+
compileScheduled = false;
|
|
1876
|
+
if (compileQueue.length === 0) return;
|
|
1877
|
+
const mj = getMathJax();
|
|
1878
|
+
if (!mj) {
|
|
1879
|
+
onMathJaxReady(() => scheduleCompile());
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
const FRAME_BUDGET_MS = 4;
|
|
1883
|
+
const start = performance.now();
|
|
1884
|
+
let didResolve = false;
|
|
1885
|
+
while (compileQueue.length > 0 && performance.now() - start < FRAME_BUDGET_MS) {
|
|
1886
|
+
const item = compileQueue.shift();
|
|
1887
|
+
if (cache3.get(item.key)?.state !== "pending") continue;
|
|
1888
|
+
try {
|
|
1889
|
+
const bitmap = await compileOne(mj, item.source, item.color, item.sizePx);
|
|
1890
|
+
cache3.set(item.key, { state: "ready", bitmap });
|
|
1891
|
+
didResolve = true;
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
cache3.set(item.key, { state: "error", err });
|
|
1894
|
+
console.warn(`[math] failed to compile "${item.source}":`, err);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
if (didResolve) bumpMathEpoch();
|
|
1898
|
+
if (compileQueue.length > 0) scheduleCompile();
|
|
1899
|
+
};
|
|
1900
|
+
var compileOne = async (mj, source, color, sizePx) => {
|
|
1901
|
+
const svgElement = await mj.tex2svgPromise(source, { display: false, em: sizePx, ex: sizePx / 2 });
|
|
1902
|
+
let markup = mj.startup.adaptor.serializeXML ? mj.startup.adaptor.serializeXML(svgElement) : mj.startup.adaptor.outerHTML(svgElement);
|
|
1903
|
+
const svgMatch = /<svg[\s\S]*?<\/svg>/.exec(markup);
|
|
1904
|
+
if (svgMatch) markup = svgMatch[0];
|
|
1905
|
+
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, "");
|
|
1906
|
+
if (!markup.includes('xmlns="http://www.w3.org/2000/svg"')) {
|
|
1907
|
+
markup = markup.replace(/^<svg\b/, '<svg xmlns="http://www.w3.org/2000/svg"');
|
|
1908
|
+
}
|
|
1909
|
+
markup = markup.replace(/currentColor/gi, color);
|
|
1910
|
+
const dims = parseSvgDims(markup, sizePx);
|
|
1911
|
+
const blob = new Blob([markup], { type: "image/svg+xml" });
|
|
1912
|
+
const url = URL.createObjectURL(blob);
|
|
1913
|
+
try {
|
|
1914
|
+
let img;
|
|
1915
|
+
try {
|
|
1916
|
+
img = await loadImage(url);
|
|
1917
|
+
} catch (e) {
|
|
1918
|
+
console.warn(`[math] SVG failed to load for "${source}":
|
|
1919
|
+
${markup}`);
|
|
1920
|
+
throw e;
|
|
1921
|
+
}
|
|
1922
|
+
const rasterW = Math.max(1, Math.ceil(dims.width * 2));
|
|
1923
|
+
const rasterH = Math.max(1, Math.ceil(dims.height * 2));
|
|
1924
|
+
const bitmap = await createImageBitmap(img, {
|
|
1925
|
+
resizeWidth: rasterW,
|
|
1926
|
+
resizeHeight: rasterH,
|
|
1927
|
+
resizeQuality: "high"
|
|
1928
|
+
});
|
|
1929
|
+
return {
|
|
1930
|
+
bitmap,
|
|
1931
|
+
width: dims.width,
|
|
1932
|
+
height: dims.height,
|
|
1933
|
+
baselineOffset: dims.baselineOffset
|
|
1934
|
+
};
|
|
1935
|
+
} finally {
|
|
1936
|
+
URL.revokeObjectURL(url);
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
var loadImage = (src) => new Promise((resolve, reject) => {
|
|
1940
|
+
const img = new Image();
|
|
1941
|
+
img.onload = () => resolve(img);
|
|
1942
|
+
img.onerror = (e) => reject(e);
|
|
1943
|
+
img.src = src;
|
|
1944
|
+
});
|
|
1945
|
+
var parseSvgDims = (markup, sizePx) => {
|
|
1946
|
+
const exToPx = sizePx / 2;
|
|
1947
|
+
const widthMatch = /<svg[^>]*\bwidth="([0-9.]+)ex"/.exec(markup);
|
|
1948
|
+
const heightMatch = /<svg[^>]*\bheight="([0-9.]+)ex"/.exec(markup);
|
|
1949
|
+
const vAlignMatch = /vertical-align:\s*(-?[0-9.]+)ex/.exec(markup);
|
|
1950
|
+
const widthEx = widthMatch ? Number.parseFloat(widthMatch[1]) : 2;
|
|
1951
|
+
const heightEx = heightMatch ? Number.parseFloat(heightMatch[1]) : 2;
|
|
1952
|
+
const vAlignEx = vAlignMatch ? Number.parseFloat(vAlignMatch[1]) : 0;
|
|
1953
|
+
const width = widthEx * exToPx;
|
|
1954
|
+
const height = heightEx * exToPx;
|
|
1955
|
+
const descent = Math.abs(vAlignEx) * exToPx;
|
|
1956
|
+
const baselineOffset = height - descent;
|
|
1957
|
+
return { width, height, baselineOffset };
|
|
1958
|
+
};
|
|
1959
|
+
var clearMathCache = () => {
|
|
1960
|
+
cache3.clear();
|
|
1961
|
+
compileQueue.length = 0;
|
|
1962
|
+
compileScheduled = false;
|
|
1963
|
+
};
|
|
1964
|
+
var getMathCacheSize = () => cache3.size;
|
|
1965
|
+
|
|
1745
1966
|
// src/text/measure.ts
|
|
1746
1967
|
var MAX_WIDTH_CACHE_SIZE = 5e3;
|
|
1747
1968
|
var measureCanvas = typeof document !== "undefined" ? document.createElement("canvas") : null;
|
|
@@ -1755,10 +1976,16 @@ var getCanvasFont = (opts) => {
|
|
|
1755
1976
|
};
|
|
1756
1977
|
var measureText = (opts) => {
|
|
1757
1978
|
if (!opts.text) return 0;
|
|
1979
|
+
if (opts.type === "math") {
|
|
1980
|
+
const fontSizePx = FONT_SIZE_MAP[opts.fontSize];
|
|
1981
|
+
const bitmap = getMathBitmap(opts.text, DEFAULT_TEXT_COLOR, fontSizePx);
|
|
1982
|
+
if (bitmap) return bitmap.width;
|
|
1983
|
+
return Math.max(8, opts.text.length * fontSizePx * 0.55 + fontSizePx);
|
|
1984
|
+
}
|
|
1758
1985
|
const font = getCanvasFont(opts);
|
|
1759
1986
|
const key = `${font}|${opts.text}`;
|
|
1760
|
-
const
|
|
1761
|
-
if (
|
|
1987
|
+
const cached2 = widthCache.get(key);
|
|
1988
|
+
if (cached2 !== void 0) return cached2;
|
|
1762
1989
|
if (!measureCtx) {
|
|
1763
1990
|
return opts.text.length * FONT_SIZE_MAP[opts.fontSize] * 0.55;
|
|
1764
1991
|
}
|
|
@@ -1900,6 +2127,8 @@ var layoutTokens = (tokens, opts) => {
|
|
|
1900
2127
|
currentRuns.push({ text: chunk, type });
|
|
1901
2128
|
cursorX += chunkWidth;
|
|
1902
2129
|
};
|
|
2130
|
+
const fontSizePx = FONT_SIZE_MAP[opts.fontSize];
|
|
2131
|
+
const mathColor = opts.textColor || DEFAULT_TEXT_COLOR;
|
|
1903
2132
|
for (const token of tokens) {
|
|
1904
2133
|
if (token.type === "code-block") {
|
|
1905
2134
|
pushCodeBlock(token.content);
|
|
@@ -1917,6 +2146,18 @@ var layoutTokens = (tokens, opts) => {
|
|
|
1917
2146
|
pushRule(true);
|
|
1918
2147
|
continue;
|
|
1919
2148
|
}
|
|
2149
|
+
if (token.type === "math") {
|
|
2150
|
+
const bitmap = getMathBitmap(token.content, mathColor, fontSizePx);
|
|
2151
|
+
const width = bitmap ? bitmap.width : (
|
|
2152
|
+
// Placeholder: roughly proportional to source length so wrap
|
|
2153
|
+
// doesn't dramatically shift on resolve. Capped at maxWidth.
|
|
2154
|
+
Math.min(maxWidth, token.content.length * fontSizePx * 0.55 + fontSizePx)
|
|
2155
|
+
);
|
|
2156
|
+
if (cursorX > 0 && cursorX + width > maxWidth) pushLine();
|
|
2157
|
+
currentRuns.push({ text: token.content, type: "math" });
|
|
2158
|
+
cursorX += width;
|
|
2159
|
+
continue;
|
|
2160
|
+
}
|
|
1920
2161
|
for (const chunk of splitChunks(token.content)) pushChunk(chunk, token.type);
|
|
1921
2162
|
}
|
|
1922
2163
|
if (currentRuns.length > 0 || lines.length === 0) {
|
|
@@ -2083,7 +2324,8 @@ var drawTextToCanvas = (ctx, opts) => {
|
|
|
2083
2324
|
width: opts.width,
|
|
2084
2325
|
fontFamily: opts.fontFamily,
|
|
2085
2326
|
fontSize: opts.fontSize,
|
|
2086
|
-
textStyle: opts.textStyle
|
|
2327
|
+
textStyle: opts.textStyle,
|
|
2328
|
+
textColor: opts.textColor
|
|
2087
2329
|
});
|
|
2088
2330
|
ctx.textBaseline = "alphabetic";
|
|
2089
2331
|
ctx.fillStyle = opts.textColor || DEFAULT_TEXT_COLOR;
|
|
@@ -2191,6 +2433,24 @@ var drawTextToCanvas = (ctx, opts) => {
|
|
|
2191
2433
|
fontSize: opts.fontSize,
|
|
2192
2434
|
textStyle: opts.textStyle
|
|
2193
2435
|
});
|
|
2436
|
+
if (run.type === "math") {
|
|
2437
|
+
const mathColor = opts.textColor || DEFAULT_TEXT_COLOR;
|
|
2438
|
+
const bitmap = getMathBitmap(run.text, mathColor, fontSizePx);
|
|
2439
|
+
if (bitmap) {
|
|
2440
|
+
ctx.drawImage(bitmap.bitmap, x, y - bitmap.baselineOffset, runWidth, bitmap.height);
|
|
2441
|
+
} else {
|
|
2442
|
+
ctx.save();
|
|
2443
|
+
ctx.fillStyle = "rgba(148, 163, 184, 0.18)";
|
|
2444
|
+
ctx.fillRect(x, y - fontSizePx + 2, runWidth, fontSizePx);
|
|
2445
|
+
ctx.fillStyle = "#94a3b8";
|
|
2446
|
+
ctx.font = `italic ${Math.max(8, fontSizePx * 0.75)}px system-ui, sans-serif`;
|
|
2447
|
+
ctx.textBaseline = "alphabetic";
|
|
2448
|
+
ctx.fillText("\u2026", x + runWidth / 2 - 4, y - 2);
|
|
2449
|
+
ctx.restore();
|
|
2450
|
+
}
|
|
2451
|
+
x += runWidth;
|
|
2452
|
+
continue;
|
|
2453
|
+
}
|
|
2194
2454
|
if (run.type === "highlight") {
|
|
2195
2455
|
ctx.save();
|
|
2196
2456
|
ctx.fillStyle = opts.highlightColor || DEFAULT_HIGHLIGHT_COLOR;
|
|
@@ -2229,6 +2489,8 @@ subscribeFontEpoch(() => {
|
|
|
2229
2489
|
renderCache.clear();
|
|
2230
2490
|
textHashCache.clear();
|
|
2231
2491
|
});
|
|
2492
|
+
subscribeMathEpoch(() => {
|
|
2493
|
+
});
|
|
2232
2494
|
var getOrRenderTextBitmap = (req) => {
|
|
2233
2495
|
const text = req.text;
|
|
2234
2496
|
if (!text || !text.trim()) return null;
|
|
@@ -2237,10 +2499,10 @@ var getOrRenderTextBitmap = (req) => {
|
|
|
2237
2499
|
const renderScale = resolveRenderScale(1, quantZoom, req.isMoving);
|
|
2238
2500
|
const epoch = getFontEpoch();
|
|
2239
2501
|
const key = makeKey(req, quantZoom, quantDpr, renderScale, epoch);
|
|
2240
|
-
const
|
|
2241
|
-
if (
|
|
2242
|
-
|
|
2243
|
-
return { canvas:
|
|
2502
|
+
const cached2 = renderCache.get(key);
|
|
2503
|
+
if (cached2) {
|
|
2504
|
+
cached2.lastUsed = Date.now();
|
|
2505
|
+
return { canvas: cached2.canvas, width: cached2.width, height: cached2.height };
|
|
2244
2506
|
}
|
|
2245
2507
|
const entry = drawIntoNewCanvas(req, quantDpr, renderScale);
|
|
2246
2508
|
if (!entry) return null;
|
|
@@ -2249,7 +2511,8 @@ var getOrRenderTextBitmap = (req) => {
|
|
|
2249
2511
|
return { canvas: entry.canvas, width: entry.width, height: entry.height };
|
|
2250
2512
|
};
|
|
2251
2513
|
var makeKey = (req, zoom, dpr, scale, epoch) => {
|
|
2252
|
-
|
|
2514
|
+
const mathSuffix = req.text.includes("$") ? `:m${getMathEpoch()}` : "";
|
|
2515
|
+
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}`;
|
|
2253
2516
|
};
|
|
2254
2517
|
var cachedTextHash = (value) => {
|
|
2255
2518
|
const hit = textHashCache.get(value);
|
|
@@ -2305,12 +2568,12 @@ var clearTextBitmapCache = () => {
|
|
|
2305
2568
|
};
|
|
2306
2569
|
var getTextBitmapCacheSize = () => renderCache.size;
|
|
2307
2570
|
var FREEHAND_CACHE_MAX = 500;
|
|
2308
|
-
var
|
|
2571
|
+
var cache4 = /* @__PURE__ */ new Map();
|
|
2309
2572
|
var remember = (key, path) => {
|
|
2310
|
-
|
|
2311
|
-
if (
|
|
2312
|
-
const oldest =
|
|
2313
|
-
if (oldest !== void 0)
|
|
2573
|
+
cache4.set(key, path);
|
|
2574
|
+
if (cache4.size > FREEHAND_CACHE_MAX) {
|
|
2575
|
+
const oldest = cache4.keys().next().value;
|
|
2576
|
+
if (oldest !== void 0) cache4.delete(oldest);
|
|
2314
2577
|
}
|
|
2315
2578
|
};
|
|
2316
2579
|
var signaturePoints = (samples) => {
|
|
@@ -2362,10 +2625,10 @@ var outlineToPath2D = (ring) => {
|
|
|
2362
2625
|
var getOrBuildFreehandPath = (samples, strokeWidth, seed) => {
|
|
2363
2626
|
if (samples.length < 2) return null;
|
|
2364
2627
|
const cacheKey = `${seed}|${strokeWidth.toFixed(2)}|${signaturePoints(samples)}`;
|
|
2365
|
-
const hit =
|
|
2628
|
+
const hit = cache4.get(cacheKey);
|
|
2366
2629
|
if (hit) {
|
|
2367
|
-
|
|
2368
|
-
|
|
2630
|
+
cache4.delete(cacheKey);
|
|
2631
|
+
cache4.set(cacheKey, hit);
|
|
2369
2632
|
return hit;
|
|
2370
2633
|
}
|
|
2371
2634
|
const pts = buildPressurePoints(samples);
|
|
@@ -3244,8 +3507,15 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3244
3507
|
};
|
|
3245
3508
|
const incidentEdges = /* @__PURE__ */ new Map();
|
|
3246
3509
|
let topZ = 0;
|
|
3247
|
-
|
|
3248
|
-
for (const
|
|
3510
|
+
let bottomZ = 0;
|
|
3511
|
+
for (const n of Object.values(initial.nodes)) {
|
|
3512
|
+
if (n.z > topZ) topZ = n.z;
|
|
3513
|
+
if (n.z < bottomZ) bottomZ = n.z;
|
|
3514
|
+
}
|
|
3515
|
+
for (const e of Object.values(initial.edges)) {
|
|
3516
|
+
if (e.z > topZ) topZ = e.z;
|
|
3517
|
+
if (e.z < bottomZ) bottomZ = e.z;
|
|
3518
|
+
}
|
|
3249
3519
|
const getNodeForGeo = (id) => nodeAtoms.get(id)?.value;
|
|
3250
3520
|
let currentBatchOps = null;
|
|
3251
3521
|
let batchDepth = 0;
|
|
@@ -3334,6 +3604,8 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3334
3604
|
nodeAtoms.set(op.node.id, a);
|
|
3335
3605
|
nodeIdsAtom.update((ids) => [...ids, op.node.id]);
|
|
3336
3606
|
reindexNode(op.node);
|
|
3607
|
+
if (op.node.z > topZ) topZ = op.node.z;
|
|
3608
|
+
if (op.node.z < bottomZ) bottomZ = op.node.z;
|
|
3337
3609
|
if (op.node.type === "frame") {
|
|
3338
3610
|
frameOrderAtom.update((ids) => ids.includes(op.node.id) ? ids : [...ids, op.node.id]);
|
|
3339
3611
|
}
|
|
@@ -3345,6 +3617,10 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3345
3617
|
const next = { ...a.value, ...op.patch };
|
|
3346
3618
|
a.set(next);
|
|
3347
3619
|
reindexNode(next);
|
|
3620
|
+
if (op.patch.z !== void 0) {
|
|
3621
|
+
if (op.patch.z > topZ) topZ = op.patch.z;
|
|
3622
|
+
if (op.patch.z < bottomZ) bottomZ = op.patch.z;
|
|
3623
|
+
}
|
|
3348
3624
|
const incident = incidentEdges.get(op.id);
|
|
3349
3625
|
if (incident) {
|
|
3350
3626
|
for (const eid of incident) {
|
|
@@ -3373,6 +3649,8 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3373
3649
|
trackIncidence(op.edge);
|
|
3374
3650
|
bumpEdgeVersion(op.edge.id);
|
|
3375
3651
|
reindexEdge(op.edge);
|
|
3652
|
+
if (op.edge.z > topZ) topZ = op.edge.z;
|
|
3653
|
+
if (op.edge.z < bottomZ) bottomZ = op.edge.z;
|
|
3376
3654
|
break;
|
|
3377
3655
|
}
|
|
3378
3656
|
case "edge.update": {
|
|
@@ -3385,6 +3663,10 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3385
3663
|
a.set(next);
|
|
3386
3664
|
bumpEdgeVersion(op.id);
|
|
3387
3665
|
reindexEdge(next);
|
|
3666
|
+
if (op.patch.z !== void 0) {
|
|
3667
|
+
if (op.patch.z > topZ) topZ = op.patch.z;
|
|
3668
|
+
if (op.patch.z < bottomZ) bottomZ = op.patch.z;
|
|
3669
|
+
}
|
|
3388
3670
|
break;
|
|
3389
3671
|
}
|
|
3390
3672
|
case "edge.remove": {
|
|
@@ -3472,9 +3754,8 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3472
3754
|
clientId,
|
|
3473
3755
|
generateId: () => idGenerator(),
|
|
3474
3756
|
addNode(node) {
|
|
3475
|
-
const
|
|
3476
|
-
|
|
3477
|
-
const fitted = withAutoFitHeight(withZ);
|
|
3757
|
+
const z = node.z ?? ++topZ;
|
|
3758
|
+
const fitted = withAutoFitHeight({ ...node, z });
|
|
3478
3759
|
enqueueOp({ type: "node.add", node: fitted });
|
|
3479
3760
|
return fitted.id;
|
|
3480
3761
|
},
|
|
@@ -3537,7 +3818,6 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3537
3818
|
w,
|
|
3538
3819
|
h,
|
|
3539
3820
|
angle: 0,
|
|
3540
|
-
z: 0,
|
|
3541
3821
|
groups: [],
|
|
3542
3822
|
style: opts2.style,
|
|
3543
3823
|
data: { src, naturalW, naturalH, alt: opts2.alt }
|
|
@@ -3560,7 +3840,6 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3560
3840
|
w,
|
|
3561
3841
|
h,
|
|
3562
3842
|
angle: 0,
|
|
3563
|
-
z: 0,
|
|
3564
3843
|
groups: [],
|
|
3565
3844
|
...mergedStyle ? { style: mergedStyle } : {},
|
|
3566
3845
|
data: { src: sanitized, alt: opts2.alt }
|
|
@@ -3568,8 +3847,8 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3568
3847
|
return id;
|
|
3569
3848
|
},
|
|
3570
3849
|
addEdge(edge) {
|
|
3571
|
-
const
|
|
3572
|
-
|
|
3850
|
+
const z = edge.z ?? ++topZ;
|
|
3851
|
+
const withZ = { ...edge, z };
|
|
3573
3852
|
enqueueOp({ type: "edge.add", edge: withZ });
|
|
3574
3853
|
return withZ.id;
|
|
3575
3854
|
},
|
|
@@ -3592,29 +3871,10 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3592
3871
|
});
|
|
3593
3872
|
},
|
|
3594
3873
|
sendToBack(ids) {
|
|
3595
|
-
const targets = new Set(ids);
|
|
3596
|
-
let minZ = 0;
|
|
3597
|
-
let initialized = false;
|
|
3598
|
-
for (const a of nodeAtoms.values()) {
|
|
3599
|
-
if (targets.has(a.value.id)) continue;
|
|
3600
|
-
if (!initialized || a.value.z < minZ) {
|
|
3601
|
-
minZ = a.value.z;
|
|
3602
|
-
initialized = true;
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
for (const a of edgeAtoms.values()) {
|
|
3606
|
-
if (targets.has(a.value.id)) continue;
|
|
3607
|
-
if (!initialized || a.value.z < minZ) {
|
|
3608
|
-
minZ = a.value.z;
|
|
3609
|
-
initialized = true;
|
|
3610
|
-
}
|
|
3611
|
-
}
|
|
3612
3874
|
this.batch(() => {
|
|
3613
|
-
let next = (initialized ? minZ : 0) - 1;
|
|
3614
3875
|
for (const id of ids) {
|
|
3615
|
-
if (nodeAtoms.has(id)) this.updateNode(id, { z:
|
|
3616
|
-
else if (edgeAtoms.has(id)) this.updateEdge(id, { z:
|
|
3617
|
-
next -= 1;
|
|
3876
|
+
if (nodeAtoms.has(id)) this.updateNode(id, { z: --bottomZ });
|
|
3877
|
+
else if (edgeAtoms.has(id)) this.updateEdge(id, { z: --bottomZ });
|
|
3618
3878
|
}
|
|
3619
3879
|
});
|
|
3620
3880
|
},
|
|
@@ -3632,7 +3892,6 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3632
3892
|
if (currentZ === void 0) continue;
|
|
3633
3893
|
const idx = binaryFirstGreater(allZ, currentZ);
|
|
3634
3894
|
const nextZ = idx >= 0 ? allZ[idx] + 1 : currentZ + 1;
|
|
3635
|
-
if (nextZ > topZ) topZ = nextZ;
|
|
3636
3895
|
if (node) this.updateNode(id, { z: nextZ });
|
|
3637
3896
|
else this.updateEdge(id, { z: nextZ });
|
|
3638
3897
|
}
|
|
@@ -4225,11 +4484,11 @@ var paintPlaceholder = (ctx, w, h, label) => {
|
|
|
4225
4484
|
ctx.fillText(label, w / 2, h / 2);
|
|
4226
4485
|
}
|
|
4227
4486
|
};
|
|
4228
|
-
var paintImageNode = (ctx, node,
|
|
4487
|
+
var paintImageNode = (ctx, node, cache5, theme) => {
|
|
4229
4488
|
if (node.w <= 0 || node.h <= 0) return;
|
|
4230
4489
|
const data = node.data;
|
|
4231
4490
|
if (!data?.src) return;
|
|
4232
|
-
const bitmap =
|
|
4491
|
+
const bitmap = cache5.getImage(data.src);
|
|
4233
4492
|
const opacity = resolveOpacity(node.style, theme);
|
|
4234
4493
|
const needsScope = opacity !== 1;
|
|
4235
4494
|
if (needsScope) {
|
|
@@ -4243,13 +4502,13 @@ var paintImageNode = (ctx, node, cache4, theme) => {
|
|
|
4243
4502
|
}
|
|
4244
4503
|
if (needsScope) ctx.restore();
|
|
4245
4504
|
};
|
|
4246
|
-
var paintIconNode = (ctx, node,
|
|
4505
|
+
var paintIconNode = (ctx, node, cache5, scale, theme) => {
|
|
4247
4506
|
if (node.w <= 0 || node.h <= 0) return;
|
|
4248
4507
|
const data = node.data;
|
|
4249
4508
|
if (!data?.src) return;
|
|
4250
4509
|
const sizePx = Math.max(node.w, node.h) * scale;
|
|
4251
4510
|
const color = node.style?.iconColor;
|
|
4252
|
-
const bitmap =
|
|
4511
|
+
const bitmap = cache5.getIcon(data.src, color, sizePx);
|
|
4253
4512
|
const opacity = resolveOpacity(node.style, theme);
|
|
4254
4513
|
const needsScope = opacity !== 1;
|
|
4255
4514
|
if (needsScope) {
|
|
@@ -4293,7 +4552,7 @@ var paintBackground = (ctx, opts) => {
|
|
|
4293
4552
|
}
|
|
4294
4553
|
};
|
|
4295
4554
|
var paintDots = (ctx, minX, minY, maxX, maxY, gap, color, zoom) => {
|
|
4296
|
-
const sizeWorld = Math.max(1,
|
|
4555
|
+
const sizeWorld = Math.max(1, 1.6 / zoom);
|
|
4297
4556
|
const half = sizeWorld / 2;
|
|
4298
4557
|
ctx.save();
|
|
4299
4558
|
ctx.fillStyle = color;
|
|
@@ -5074,6 +5333,10 @@ var createRenderer = (opts) => {
|
|
|
5074
5333
|
staticDirty = true;
|
|
5075
5334
|
loop.requestFrame();
|
|
5076
5335
|
});
|
|
5336
|
+
const unsubMathEpoch = subscribeMathEpoch(() => {
|
|
5337
|
+
staticDirty = true;
|
|
5338
|
+
loop.requestFrame();
|
|
5339
|
+
});
|
|
5077
5340
|
return {
|
|
5078
5341
|
start() {
|
|
5079
5342
|
loop.start();
|
|
@@ -5123,6 +5386,7 @@ var createRenderer = (opts) => {
|
|
|
5123
5386
|
unsubSelection();
|
|
5124
5387
|
unsubInteraction();
|
|
5125
5388
|
unsubFontEpoch();
|
|
5389
|
+
unsubMathEpoch();
|
|
5126
5390
|
assetCache.dispose();
|
|
5127
5391
|
}
|
|
5128
5392
|
};
|
|
@@ -6200,6 +6464,7 @@ exports.autoRouteControls = autoRouteControls;
|
|
|
6200
6464
|
exports.blobToDataUri = blobToDataUri;
|
|
6201
6465
|
exports.clampEffectiveScale = clampEffectiveScale;
|
|
6202
6466
|
exports.clampZoom = clampZoom;
|
|
6467
|
+
exports.clearMathCache = clearMathCache;
|
|
6203
6468
|
exports.clearMeasureCache = clearMeasureCache;
|
|
6204
6469
|
exports.clearSurface = clearSurface;
|
|
6205
6470
|
exports.clearTextBitmapCache = clearTextBitmapCache;
|
|
@@ -6242,6 +6507,10 @@ exports.getContext = getContext;
|
|
|
6242
6507
|
exports.getDpr = getDpr;
|
|
6243
6508
|
exports.getFontEpoch = getFontEpoch;
|
|
6244
6509
|
exports.getMarkdownLineHeightPx = getMarkdownLineHeightPx;
|
|
6510
|
+
exports.getMathBitmap = getMathBitmap;
|
|
6511
|
+
exports.getMathCacheSize = getMathCacheSize;
|
|
6512
|
+
exports.getMathEpoch = getMathEpoch;
|
|
6513
|
+
exports.getMathJax = getMathJax;
|
|
6245
6514
|
exports.getOrRenderTextBitmap = getOrRenderTextBitmap;
|
|
6246
6515
|
exports.getPointAndTangentAtArcLength = getPointAndTangentAtArcLength;
|
|
6247
6516
|
exports.getTextBitmapCacheSize = getTextBitmapCacheSize;
|
|
@@ -6275,6 +6544,7 @@ exports.nodeIntersectsRect = nodeIntersectsRect;
|
|
|
6275
6544
|
exports.nodeLocalToWorld = nodeLocalToWorld;
|
|
6276
6545
|
exports.notePenActive = notePenActive;
|
|
6277
6546
|
exports.notePenInactive = notePenInactive;
|
|
6547
|
+
exports.onMathJaxReady = onMathJaxReady;
|
|
6278
6548
|
exports.opSchemas = opSchemas;
|
|
6279
6549
|
exports.opSchemasAsAnthropicTools = opSchemasAsAnthropicTools;
|
|
6280
6550
|
exports.paintBackground = paintBackground;
|
|
@@ -6313,6 +6583,7 @@ exports.sideOf = sideOf;
|
|
|
6313
6583
|
exports.sizeSurface = sizeSurface;
|
|
6314
6584
|
exports.storeToJSON = storeToJSON;
|
|
6315
6585
|
exports.subscribeFontEpoch = subscribeFontEpoch;
|
|
6586
|
+
exports.subscribeMathEpoch = subscribeMathEpoch;
|
|
6316
6587
|
exports.tangentAtArcLength = tangentAtArcLength;
|
|
6317
6588
|
exports.toImageBlob = toImageBlob;
|
|
6318
6589
|
exports.toSerialized = toSerialized;
|