@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.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
|
|
637
|
-
if (
|
|
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
|
|
850
|
-
if (
|
|
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 = /(
|
|
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
|
|
1759
|
-
if (
|
|
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
|
|
2239
|
-
if (
|
|
2240
|
-
|
|
2241
|
-
return { canvas:
|
|
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
|
-
|
|
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
|
|
2569
|
+
var cache4 = /* @__PURE__ */ new Map();
|
|
2307
2570
|
var remember = (key, path) => {
|
|
2308
|
-
|
|
2309
|
-
if (
|
|
2310
|
-
const oldest =
|
|
2311
|
-
if (oldest !== void 0)
|
|
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 =
|
|
2626
|
+
const hit = cache4.get(cacheKey);
|
|
2364
2627
|
if (hit) {
|
|
2365
|
-
|
|
2366
|
-
|
|
2628
|
+
cache4.delete(cacheKey);
|
|
2629
|
+
cache4.set(cacheKey, hit);
|
|
2367
2630
|
return hit;
|
|
2368
2631
|
}
|
|
2369
2632
|
const pts = buildPressurePoints(samples);
|
|
@@ -3242,8 +3505,15 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3242
3505
|
};
|
|
3243
3506
|
const incidentEdges = /* @__PURE__ */ new Map();
|
|
3244
3507
|
let topZ = 0;
|
|
3245
|
-
|
|
3246
|
-
for (const
|
|
3508
|
+
let bottomZ = 0;
|
|
3509
|
+
for (const n of Object.values(initial.nodes)) {
|
|
3510
|
+
if (n.z > topZ) topZ = n.z;
|
|
3511
|
+
if (n.z < bottomZ) bottomZ = n.z;
|
|
3512
|
+
}
|
|
3513
|
+
for (const e of Object.values(initial.edges)) {
|
|
3514
|
+
if (e.z > topZ) topZ = e.z;
|
|
3515
|
+
if (e.z < bottomZ) bottomZ = e.z;
|
|
3516
|
+
}
|
|
3247
3517
|
const getNodeForGeo = (id) => nodeAtoms.get(id)?.value;
|
|
3248
3518
|
let currentBatchOps = null;
|
|
3249
3519
|
let batchDepth = 0;
|
|
@@ -3332,6 +3602,8 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3332
3602
|
nodeAtoms.set(op.node.id, a);
|
|
3333
3603
|
nodeIdsAtom.update((ids) => [...ids, op.node.id]);
|
|
3334
3604
|
reindexNode(op.node);
|
|
3605
|
+
if (op.node.z > topZ) topZ = op.node.z;
|
|
3606
|
+
if (op.node.z < bottomZ) bottomZ = op.node.z;
|
|
3335
3607
|
if (op.node.type === "frame") {
|
|
3336
3608
|
frameOrderAtom.update((ids) => ids.includes(op.node.id) ? ids : [...ids, op.node.id]);
|
|
3337
3609
|
}
|
|
@@ -3343,6 +3615,10 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3343
3615
|
const next = { ...a.value, ...op.patch };
|
|
3344
3616
|
a.set(next);
|
|
3345
3617
|
reindexNode(next);
|
|
3618
|
+
if (op.patch.z !== void 0) {
|
|
3619
|
+
if (op.patch.z > topZ) topZ = op.patch.z;
|
|
3620
|
+
if (op.patch.z < bottomZ) bottomZ = op.patch.z;
|
|
3621
|
+
}
|
|
3346
3622
|
const incident = incidentEdges.get(op.id);
|
|
3347
3623
|
if (incident) {
|
|
3348
3624
|
for (const eid of incident) {
|
|
@@ -3371,6 +3647,8 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3371
3647
|
trackIncidence(op.edge);
|
|
3372
3648
|
bumpEdgeVersion(op.edge.id);
|
|
3373
3649
|
reindexEdge(op.edge);
|
|
3650
|
+
if (op.edge.z > topZ) topZ = op.edge.z;
|
|
3651
|
+
if (op.edge.z < bottomZ) bottomZ = op.edge.z;
|
|
3374
3652
|
break;
|
|
3375
3653
|
}
|
|
3376
3654
|
case "edge.update": {
|
|
@@ -3383,6 +3661,10 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3383
3661
|
a.set(next);
|
|
3384
3662
|
bumpEdgeVersion(op.id);
|
|
3385
3663
|
reindexEdge(next);
|
|
3664
|
+
if (op.patch.z !== void 0) {
|
|
3665
|
+
if (op.patch.z > topZ) topZ = op.patch.z;
|
|
3666
|
+
if (op.patch.z < bottomZ) bottomZ = op.patch.z;
|
|
3667
|
+
}
|
|
3386
3668
|
break;
|
|
3387
3669
|
}
|
|
3388
3670
|
case "edge.remove": {
|
|
@@ -3470,9 +3752,8 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3470
3752
|
clientId,
|
|
3471
3753
|
generateId: () => idGenerator(),
|
|
3472
3754
|
addNode(node) {
|
|
3473
|
-
const
|
|
3474
|
-
|
|
3475
|
-
const fitted = withAutoFitHeight(withZ);
|
|
3755
|
+
const z = node.z ?? ++topZ;
|
|
3756
|
+
const fitted = withAutoFitHeight({ ...node, z });
|
|
3476
3757
|
enqueueOp({ type: "node.add", node: fitted });
|
|
3477
3758
|
return fitted.id;
|
|
3478
3759
|
},
|
|
@@ -3535,7 +3816,6 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3535
3816
|
w,
|
|
3536
3817
|
h,
|
|
3537
3818
|
angle: 0,
|
|
3538
|
-
z: 0,
|
|
3539
3819
|
groups: [],
|
|
3540
3820
|
style: opts2.style,
|
|
3541
3821
|
data: { src, naturalW, naturalH, alt: opts2.alt }
|
|
@@ -3558,7 +3838,6 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3558
3838
|
w,
|
|
3559
3839
|
h,
|
|
3560
3840
|
angle: 0,
|
|
3561
|
-
z: 0,
|
|
3562
3841
|
groups: [],
|
|
3563
3842
|
...mergedStyle ? { style: mergedStyle } : {},
|
|
3564
3843
|
data: { src: sanitized, alt: opts2.alt }
|
|
@@ -3566,8 +3845,8 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3566
3845
|
return id;
|
|
3567
3846
|
},
|
|
3568
3847
|
addEdge(edge) {
|
|
3569
|
-
const
|
|
3570
|
-
|
|
3848
|
+
const z = edge.z ?? ++topZ;
|
|
3849
|
+
const withZ = { ...edge, z };
|
|
3571
3850
|
enqueueOp({ type: "edge.add", edge: withZ });
|
|
3572
3851
|
return withZ.id;
|
|
3573
3852
|
},
|
|
@@ -3590,29 +3869,10 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3590
3869
|
});
|
|
3591
3870
|
},
|
|
3592
3871
|
sendToBack(ids) {
|
|
3593
|
-
const targets = new Set(ids);
|
|
3594
|
-
let minZ = 0;
|
|
3595
|
-
let initialized = false;
|
|
3596
|
-
for (const a of nodeAtoms.values()) {
|
|
3597
|
-
if (targets.has(a.value.id)) continue;
|
|
3598
|
-
if (!initialized || a.value.z < minZ) {
|
|
3599
|
-
minZ = a.value.z;
|
|
3600
|
-
initialized = true;
|
|
3601
|
-
}
|
|
3602
|
-
}
|
|
3603
|
-
for (const a of edgeAtoms.values()) {
|
|
3604
|
-
if (targets.has(a.value.id)) continue;
|
|
3605
|
-
if (!initialized || a.value.z < minZ) {
|
|
3606
|
-
minZ = a.value.z;
|
|
3607
|
-
initialized = true;
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
3872
|
this.batch(() => {
|
|
3611
|
-
let next = (initialized ? minZ : 0) - 1;
|
|
3612
3873
|
for (const id of ids) {
|
|
3613
|
-
if (nodeAtoms.has(id)) this.updateNode(id, { z:
|
|
3614
|
-
else if (edgeAtoms.has(id)) this.updateEdge(id, { z:
|
|
3615
|
-
next -= 1;
|
|
3874
|
+
if (nodeAtoms.has(id)) this.updateNode(id, { z: --bottomZ });
|
|
3875
|
+
else if (edgeAtoms.has(id)) this.updateEdge(id, { z: --bottomZ });
|
|
3616
3876
|
}
|
|
3617
3877
|
});
|
|
3618
3878
|
},
|
|
@@ -3630,7 +3890,6 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3630
3890
|
if (currentZ === void 0) continue;
|
|
3631
3891
|
const idx = binaryFirstGreater(allZ, currentZ);
|
|
3632
3892
|
const nextZ = idx >= 0 ? allZ[idx] + 1 : currentZ + 1;
|
|
3633
|
-
if (nextZ > topZ) topZ = nextZ;
|
|
3634
3893
|
if (node) this.updateNode(id, { z: nextZ });
|
|
3635
3894
|
else this.updateEdge(id, { z: nextZ });
|
|
3636
3895
|
}
|
|
@@ -4223,11 +4482,11 @@ var paintPlaceholder = (ctx, w, h, label) => {
|
|
|
4223
4482
|
ctx.fillText(label, w / 2, h / 2);
|
|
4224
4483
|
}
|
|
4225
4484
|
};
|
|
4226
|
-
var paintImageNode = (ctx, node,
|
|
4485
|
+
var paintImageNode = (ctx, node, cache5, theme) => {
|
|
4227
4486
|
if (node.w <= 0 || node.h <= 0) return;
|
|
4228
4487
|
const data = node.data;
|
|
4229
4488
|
if (!data?.src) return;
|
|
4230
|
-
const bitmap =
|
|
4489
|
+
const bitmap = cache5.getImage(data.src);
|
|
4231
4490
|
const opacity = resolveOpacity(node.style, theme);
|
|
4232
4491
|
const needsScope = opacity !== 1;
|
|
4233
4492
|
if (needsScope) {
|
|
@@ -4241,13 +4500,13 @@ var paintImageNode = (ctx, node, cache4, theme) => {
|
|
|
4241
4500
|
}
|
|
4242
4501
|
if (needsScope) ctx.restore();
|
|
4243
4502
|
};
|
|
4244
|
-
var paintIconNode = (ctx, node,
|
|
4503
|
+
var paintIconNode = (ctx, node, cache5, scale, theme) => {
|
|
4245
4504
|
if (node.w <= 0 || node.h <= 0) return;
|
|
4246
4505
|
const data = node.data;
|
|
4247
4506
|
if (!data?.src) return;
|
|
4248
4507
|
const sizePx = Math.max(node.w, node.h) * scale;
|
|
4249
4508
|
const color = node.style?.iconColor;
|
|
4250
|
-
const bitmap =
|
|
4509
|
+
const bitmap = cache5.getIcon(data.src, color, sizePx);
|
|
4251
4510
|
const opacity = resolveOpacity(node.style, theme);
|
|
4252
4511
|
const needsScope = opacity !== 1;
|
|
4253
4512
|
if (needsScope) {
|
|
@@ -4291,7 +4550,7 @@ var paintBackground = (ctx, opts) => {
|
|
|
4291
4550
|
}
|
|
4292
4551
|
};
|
|
4293
4552
|
var paintDots = (ctx, minX, minY, maxX, maxY, gap, color, zoom) => {
|
|
4294
|
-
const sizeWorld = Math.max(1,
|
|
4553
|
+
const sizeWorld = Math.max(1, 1.6 / zoom);
|
|
4295
4554
|
const half = sizeWorld / 2;
|
|
4296
4555
|
ctx.save();
|
|
4297
4556
|
ctx.fillStyle = color;
|
|
@@ -5072,6 +5331,10 @@ var createRenderer = (opts) => {
|
|
|
5072
5331
|
staticDirty = true;
|
|
5073
5332
|
loop.requestFrame();
|
|
5074
5333
|
});
|
|
5334
|
+
const unsubMathEpoch = subscribeMathEpoch(() => {
|
|
5335
|
+
staticDirty = true;
|
|
5336
|
+
loop.requestFrame();
|
|
5337
|
+
});
|
|
5075
5338
|
return {
|
|
5076
5339
|
start() {
|
|
5077
5340
|
loop.start();
|
|
@@ -5121,6 +5384,7 @@ var createRenderer = (opts) => {
|
|
|
5121
5384
|
unsubSelection();
|
|
5122
5385
|
unsubInteraction();
|
|
5123
5386
|
unsubFontEpoch();
|
|
5387
|
+
unsubMathEpoch();
|
|
5124
5388
|
assetCache.dispose();
|
|
5125
5389
|
}
|
|
5126
5390
|
};
|
|
@@ -6153,6 +6417,6 @@ var installedExtensions = (store) => {
|
|
|
6153
6417
|
// src/index.ts
|
|
6154
6418
|
var VERSION = "0.0.0";
|
|
6155
6419
|
|
|
6156
|
-
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 };
|
|
6157
6421
|
//# sourceMappingURL=index.js.map
|
|
6158
6422
|
//# sourceMappingURL=index.js.map
|