@simon_he/vue-tui 1.1.3 → 1.1.4
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/{TMermaidText-frVr8Ysb.js → TMermaidText-wLnMMvVS.js} +1 -1
- package/dist/{TSelect-Dx34M1pF.js → TSelect-CMBOOFwK.js} +5 -5
- package/dist/{TTree-DdAjCPHu.js → TTree-kk8ntrpg.js} +6 -5
- package/dist/agent/mermaid.d.cts +2 -2
- package/dist/agent/mermaid.d.ts +2 -2
- package/dist/agent.cjs +538 -266
- package/dist/agent.js +25 -82
- package/dist/{block-source-Crro3mxi.js → block-source-D3YGTErb.js} +299 -239
- package/dist/cli.cjs +52 -15
- package/dist/cli.js +69 -14
- package/dist/core.js +3 -3
- package/dist/{hyperlink-_1FAj7Wq.js → create-terminal-Db_-UGVQ.js} +474 -102
- package/dist/{dom-renderer-DXx8fWzD.js → dom-renderer-BfwAEQMf.js} +3 -2
- package/dist/experimental.cjs +99 -66
- package/dist/experimental.js +20 -74
- package/dist/hyperlink-clbb_ha_.js +100 -0
- package/dist/index.js +8 -8
- package/dist/{linkify-BjI4zVfY.js → linkify-CL9wwnZN.js} +3 -3
- package/dist/markdown.cjs +521 -212
- package/dist/markdown.d.cts +3 -0
- package/dist/markdown.d.ts +3 -0
- package/dist/markdown.js +75 -8
- package/dist/math-image-BZhmfUeu.js +308 -0
- package/dist/mermaid-uEfRItDe.js +1205 -0
- package/dist/mermaid.cjs +1089 -3
- package/dist/mermaid.d.cts +4 -0
- package/dist/mermaid.d.ts +4 -0
- package/dist/mermaid.js +7 -5
- package/dist/{rect-DUpeDfBg.js → rect-JzWMJxxc.js} +1 -1
- package/dist/renderer/cli/stdout-renderer.d.ts +44 -0
- package/dist/renderer/terminal-graphics.d.ts +44 -0
- package/dist/renderer-dom.js +4 -4
- package/dist/{terminal-graphics-Bbe83wSr.js → terminal-graphics-DWZrl3oh.js} +85 -2
- package/dist/{tlog-theme-i6S_Oftd.js → tlog-theme-DqnF_zNe.js} +5 -4
- package/dist/vue/components/TAgentTerminalGraphic.d.ts +1 -1
- package/dist/vue/components/TMarkdownText.d.ts +70 -2
- package/dist/vue/components/TMermaidImage.d.ts +733 -0
- package/dist/vue/components/TMermaidText.d.ts +2 -2
- package/dist/vue/components/TVirtualMarkdown.d.ts +70 -2
- package/dist/vue/markdown/ast.d.ts +5 -0
- package/dist/vue/markdown/document.d.ts +3 -0
- package/dist/vue/markdown/math-image.d.ts +68 -0
- package/dist/vue/markdown/theme.d.ts +12 -0
- package/dist/vue/markdown/types.d.ts +12 -2
- package/dist/vue/mermaid/beautiful-mermaid.d.ts +4 -4
- package/dist/vue/mermaid/mermaid-image.d.ts +65 -0
- package/dist/vue.js +9 -9
- package/package.json +14 -1
- package/dist/mermaid-BNhM1jAZ.js +0 -123
- package/dist/width-GqllnV8C.js +0 -474
- /package/dist/{ansi-palette-CH6jc2Z7.js → ansi-palette-BHF1rN7R.js} +0 -0
package/dist/markdown.d.cts
CHANGED
|
@@ -4,6 +4,9 @@ export { createMarkdownBlockSource } from "./vue/markdown/block-source.js";
|
|
|
4
4
|
export { buildMarkdownBlocks, buildMarkdownVisualRows } from "./vue/markdown/document.js";
|
|
5
5
|
export { layoutMarkdownBlocks } from "./vue/markdown/layout.js";
|
|
6
6
|
export { loadMarkdownMathRenderer } from "./vue/markdown/math.js";
|
|
7
|
+
export { clearMarkdownMathImageCache, enqueueMarkdownMathImages, getCachedMarkdownMathImage, getMarkdownMathImage, isMarkdownMathImageRendererReady, loadMarkdownMathImageRenderer, setMarkdownMathRasterizer, subscribeMarkdownMathImage, } from "./vue/markdown/math-image.js";
|
|
8
|
+
export type { TuiMarkdownMathImageCells, TuiMarkdownMathImageOptions, TuiMarkdownMathMode, TuiMarkdownMathRasterizer, } from "./vue/markdown/math-image.js";
|
|
9
|
+
export type { TuiMarkdownMathOptions } from "./vue/markdown/ast.js";
|
|
7
10
|
export { createTuiMarkdownParser, isSafeMarkdownLink } from "./vue/markdown/parser.js";
|
|
8
11
|
export type { TuiMarkdownBlockSource, TuiMarkdownBlockSourceOptions, TuiMarkdownBlockSourceSnapshot, } from "./vue/markdown/block-source.js";
|
|
9
12
|
export type { TuiMarkdownLayoutOptions } from "./vue/markdown/layout.js";
|
package/dist/markdown.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ export { createMarkdownBlockSource } from "./vue/markdown/block-source.js";
|
|
|
4
4
|
export { buildMarkdownBlocks, buildMarkdownVisualRows } from "./vue/markdown/document.js";
|
|
5
5
|
export { layoutMarkdownBlocks } from "./vue/markdown/layout.js";
|
|
6
6
|
export { loadMarkdownMathRenderer } from "./vue/markdown/math.js";
|
|
7
|
+
export { clearMarkdownMathImageCache, enqueueMarkdownMathImages, getCachedMarkdownMathImage, getMarkdownMathImage, isMarkdownMathImageRendererReady, loadMarkdownMathImageRenderer, setMarkdownMathRasterizer, subscribeMarkdownMathImage, } from "./vue/markdown/math-image.js";
|
|
8
|
+
export type { TuiMarkdownMathImageCells, TuiMarkdownMathImageOptions, TuiMarkdownMathMode, TuiMarkdownMathRasterizer, } from "./vue/markdown/math-image.js";
|
|
9
|
+
export type { TuiMarkdownMathOptions } from "./vue/markdown/ast.js";
|
|
7
10
|
export { createTuiMarkdownParser, isSafeMarkdownLink } from "./vue/markdown/parser.js";
|
|
8
11
|
export type { TuiMarkdownBlockSource, TuiMarkdownBlockSourceOptions, TuiMarkdownBlockSourceSnapshot, } from "./vue/markdown/block-source.js";
|
|
9
12
|
export type { TuiMarkdownLayoutOptions } from "./vue/markdown/layout.js";
|
package/dist/markdown.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import "./
|
|
3
|
-
import { EventZIndexContextKey, intersectRect, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility, withTextWidthProvider } from "./rect-
|
|
1
|
+
import "./create-terminal-Db_-UGVQ.js";
|
|
2
|
+
import "./hyperlink-clbb_ha_.js";
|
|
3
|
+
import { EventZIndexContextKey, intersectRect, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility, withTextWidthProvider } from "./rect-JzWMJxxc.js";
|
|
4
4
|
import "./env-Cl8qmNFN.js";
|
|
5
5
|
import "./terminal-selection-4W1SJj4c.js";
|
|
6
6
|
import "./debug-logger-CmsGI4of.js";
|
|
7
7
|
import "./wheel-scroll-DASkEZr-.js";
|
|
8
|
-
import { getTerminalGraphicsOutputVersion, subscribeTerminalGraphicsOutput } from "./terminal-graphics-
|
|
9
|
-
import { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, clearMarkdownImageGraphics, collectVisibleMarkdownImageGraphicIds, createMarkdownBlockSource, createTuiMarkdownParser, findMarkdownImageActionAt, findMarkdownLinkActionAt, findMarkdownMathActionAt, isSafeMarkdownLink, layoutMarkdownBlocks, loadMarkdownMathRenderer, markdownThemeSignature, paintMarkdownVisualRow, subscribeMarkdownMathRenderer } from "./block-source-
|
|
8
|
+
import { getTerminalGraphicsOutput, getTerminalGraphicsOutputVersion, subscribeTerminalGraphicsOutput } from "./terminal-graphics-DWZrl3oh.js";
|
|
9
|
+
import { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, clearMarkdownImageGraphics, collectVisibleMarkdownImageGraphicIds, createMarkdownBlockSource, createTuiMarkdownParser, findMarkdownImageActionAt, findMarkdownLinkActionAt, findMarkdownMathActionAt, isSafeMarkdownLink, layoutMarkdownBlocks, loadMarkdownMathRenderer, markdownThemeSignature, paintMarkdownVisualRow, subscribeMarkdownMathRenderer } from "./block-source-D3YGTErb.js";
|
|
10
|
+
import { clearMarkdownMathImageCache, enqueueMarkdownMathImages, getCachedMarkdownMathImage, getMarkdownMathImage, isMarkdownMathImageRendererReady, loadMarkdownMathImageRenderer, resolveMarkdownMathColor, setMarkdownMathRasterizer, subscribeMarkdownMathImage } from "./math-image-BZhmfUeu.js";
|
|
10
11
|
import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, shallowRef, watch } from "vue";
|
|
11
12
|
|
|
12
13
|
//#region src/vue/components/TMarkdownText.ts
|
|
@@ -85,6 +86,34 @@ const TMarkdownText = defineComponent({
|
|
|
85
86
|
type: Boolean,
|
|
86
87
|
default: true
|
|
87
88
|
},
|
|
89
|
+
mathImages: {
|
|
90
|
+
type: Boolean,
|
|
91
|
+
default: true
|
|
92
|
+
},
|
|
93
|
+
mathCellWidthPx: {
|
|
94
|
+
type: Number,
|
|
95
|
+
default: void 0
|
|
96
|
+
},
|
|
97
|
+
mathCellHeightPx: {
|
|
98
|
+
type: Number,
|
|
99
|
+
default: void 0
|
|
100
|
+
},
|
|
101
|
+
mathScale: {
|
|
102
|
+
type: Number,
|
|
103
|
+
default: void 0
|
|
104
|
+
},
|
|
105
|
+
mathColor: {
|
|
106
|
+
type: String,
|
|
107
|
+
default: void 0
|
|
108
|
+
},
|
|
109
|
+
mathMaxWidthCells: {
|
|
110
|
+
type: Number,
|
|
111
|
+
default: void 0
|
|
112
|
+
},
|
|
113
|
+
mathBaselineRatio: {
|
|
114
|
+
type: Number,
|
|
115
|
+
default: void 0
|
|
116
|
+
},
|
|
88
117
|
imageActions: {
|
|
89
118
|
type: Boolean,
|
|
90
119
|
default: false
|
|
@@ -126,16 +155,44 @@ const TMarkdownText = defineComponent({
|
|
|
126
155
|
const graphicsOutputVersion = shallowRef(getTerminalGraphicsOutputVersion(terminal));
|
|
127
156
|
const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
|
|
128
157
|
graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal);
|
|
158
|
+
enqueuePendingMathImages();
|
|
129
159
|
});
|
|
130
160
|
const unsubscribeMathRenderer = subscribeMarkdownMathRenderer(() => {
|
|
131
161
|
scheduleRebuild();
|
|
132
162
|
});
|
|
163
|
+
const unsubscribeMathImage = subscribeMarkdownMathImage(() => {
|
|
164
|
+
scheduleRebuild();
|
|
165
|
+
});
|
|
133
166
|
watch(() => `${props.streaming ? 1 : 0}:${(props.customHtmlTags ?? []).join("\0")}`, () => {
|
|
134
167
|
parser.value = markRaw(createTuiMarkdownParser({
|
|
135
168
|
streaming: props.streaming,
|
|
136
169
|
customHtmlTags: props.customHtmlTags
|
|
137
170
|
}));
|
|
138
171
|
});
|
|
172
|
+
function mathOptions() {
|
|
173
|
+
if (props.mathImages === false) return { enabled: false };
|
|
174
|
+
const resolvedColor = props.mathColor ?? resolveMarkdownMathColor(defaultStyle.value.fg);
|
|
175
|
+
return {
|
|
176
|
+
enabled: true,
|
|
177
|
+
...props.mathCellWidthPx != null ? { cellWidthPx: props.mathCellWidthPx } : {},
|
|
178
|
+
...props.mathCellHeightPx != null ? { cellHeightPx: props.mathCellHeightPx } : {},
|
|
179
|
+
...props.mathScale != null ? { scale: props.mathScale } : {},
|
|
180
|
+
...resolvedColor != null ? { color: resolvedColor } : {},
|
|
181
|
+
...props.mathBaselineRatio != null ? { baselineRatio: props.mathBaselineRatio } : {},
|
|
182
|
+
maxWidthCells: props.mathMaxWidthCells ?? props.w
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function enqueuePendingMathImages() {
|
|
186
|
+
if (props.mathImages === false) return;
|
|
187
|
+
if (!getTerminalGraphicsOutput(terminal)?.capabilities.supported) return;
|
|
188
|
+
if (!isMarkdownMathImageRendererReady()) {
|
|
189
|
+
loadMarkdownMathImageRenderer().then((ready) => {
|
|
190
|
+
if (ready && alive) scheduleRebuild();
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
enqueueMarkdownMathImages(rows.value, mathOptions());
|
|
195
|
+
}
|
|
139
196
|
function rebuildRows() {
|
|
140
197
|
rows.value = markRaw(withTextWidthProvider(widthProvider, () => buildMarkdownVisualRows(props.content, props.w, parser.value, {
|
|
141
198
|
final: props.final,
|
|
@@ -147,9 +204,11 @@ const TMarkdownText = defineComponent({
|
|
|
147
204
|
minHeight: props.imageMinHeight,
|
|
148
205
|
maxHeight: props.imageMaxHeight,
|
|
149
206
|
preserveAspectRatio: props.imagePreserveAspectRatio
|
|
150
|
-
}
|
|
207
|
+
},
|
|
208
|
+
math: mathOptions()
|
|
151
209
|
})));
|
|
152
210
|
documentVersion.value++;
|
|
211
|
+
enqueuePendingMathImages();
|
|
153
212
|
}
|
|
154
213
|
function scheduleRebuild() {
|
|
155
214
|
const currentVersion = ++rebuildVersion;
|
|
@@ -181,7 +240,14 @@ const TMarkdownText = defineComponent({
|
|
|
181
240
|
() => props.imageMaxWidth,
|
|
182
241
|
() => props.imageMinHeight,
|
|
183
242
|
() => props.imageMaxHeight,
|
|
184
|
-
() => props.imagePreserveAspectRatio
|
|
243
|
+
() => props.imagePreserveAspectRatio,
|
|
244
|
+
() => props.mathImages,
|
|
245
|
+
() => props.mathCellWidthPx,
|
|
246
|
+
() => props.mathCellHeightPx,
|
|
247
|
+
() => props.mathScale,
|
|
248
|
+
() => props.mathColor,
|
|
249
|
+
() => props.mathMaxWidthCells,
|
|
250
|
+
() => props.mathBaselineRatio
|
|
185
251
|
], () => {
|
|
186
252
|
scheduleRebuild();
|
|
187
253
|
}, { immediate: true });
|
|
@@ -190,6 +256,7 @@ const TMarkdownText = defineComponent({
|
|
|
190
256
|
rebuildVersion++;
|
|
191
257
|
unsubscribeGraphicsOutput();
|
|
192
258
|
unsubscribeMathRenderer();
|
|
259
|
+
unsubscribeMathImage();
|
|
193
260
|
clearMarkdownImageGraphics(terminal, fullRect.value);
|
|
194
261
|
});
|
|
195
262
|
const fullRect = computed(() => {
|
|
@@ -351,4 +418,4 @@ const TMarkdownText = defineComponent({
|
|
|
351
418
|
});
|
|
352
419
|
|
|
353
420
|
//#endregion
|
|
354
|
-
export { TMarkdownText, TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, createMarkdownBlockSource, createTuiMarkdownParser, isSafeMarkdownLink, layoutMarkdownBlocks, loadMarkdownMathRenderer };
|
|
421
|
+
export { TMarkdownText, TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, clearMarkdownMathImageCache, createMarkdownBlockSource, createTuiMarkdownParser, enqueueMarkdownMathImages, getCachedMarkdownMathImage, getMarkdownMathImage, isMarkdownMathImageRendererReady, isSafeMarkdownLink, layoutMarkdownBlocks, loadMarkdownMathImageRenderer, loadMarkdownMathRenderer, setMarkdownMathRasterizer, subscribeMarkdownMathImage };
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
//#region src/vue/markdown/math-image.ts
|
|
2
|
+
const DEFAULT_MATH_IMAGE_OPTIONS = Object.freeze({
|
|
3
|
+
cellWidthPx: 8,
|
|
4
|
+
cellHeightPx: 16,
|
|
5
|
+
scale: 2,
|
|
6
|
+
color: "#f8f8f2",
|
|
7
|
+
maxWidthCells: 0,
|
|
8
|
+
baselineRatio: .78
|
|
9
|
+
});
|
|
10
|
+
const MAX_CACHE_ENTRIES = 64;
|
|
11
|
+
const MAX_FAILED_ENTRIES = 128;
|
|
12
|
+
const mathImageCache = new Map();
|
|
13
|
+
const mathImageFailed = new Set();
|
|
14
|
+
const mathImageInflight = new Map();
|
|
15
|
+
const mathImageListeners = new Set();
|
|
16
|
+
let customRasterizer = null;
|
|
17
|
+
let builtinRasterizerLoad = null;
|
|
18
|
+
let builtinRasterizerReady = false;
|
|
19
|
+
/**
|
|
20
|
+
* Map common terminal foreground style values to hex so rasterized formulas
|
|
21
|
+
* stay readable on any theme (a light formula on a light background would be
|
|
22
|
+
* invisible). Unknown values resolve to `undefined` and the caller falls back
|
|
23
|
+
* to a neutral default.
|
|
24
|
+
*/
|
|
25
|
+
const NAMED_STYLE_FG_HEX = Object.freeze({
|
|
26
|
+
black: "#000000",
|
|
27
|
+
red: "#e53935",
|
|
28
|
+
green: "#43a047",
|
|
29
|
+
yellow: "#fdd835",
|
|
30
|
+
blue: "#1e88e5",
|
|
31
|
+
magenta: "#d81b60",
|
|
32
|
+
cyan: "#00acc1",
|
|
33
|
+
white: "#ffffff",
|
|
34
|
+
gray: "#9e9e9e",
|
|
35
|
+
grey: "#9e9e9e",
|
|
36
|
+
blackBright: "#4a4a4a",
|
|
37
|
+
redBright: "#ff6e6e",
|
|
38
|
+
greenBright: "#69db7c",
|
|
39
|
+
yellowBright: "#ffe066",
|
|
40
|
+
blueBright: "#74c0fc",
|
|
41
|
+
magentaBright: "#f783ac",
|
|
42
|
+
cyanBright: "#3bc9db",
|
|
43
|
+
whiteBright: "#f8f8f2",
|
|
44
|
+
grayBright: "#bdbdbd",
|
|
45
|
+
greyBright: "#bdbdbd"
|
|
46
|
+
});
|
|
47
|
+
function resolveMarkdownMathColor(fg) {
|
|
48
|
+
if (typeof fg !== "string" || !fg) return void 0;
|
|
49
|
+
const trimmed = fg.trim();
|
|
50
|
+
if (trimmed.startsWith("#") && /^#[0-9a-f]{3,8}$/i.test(trimmed)) return trimmed.toLowerCase();
|
|
51
|
+
return NAMED_STYLE_FG_HEX[trimmed.toLowerCase()];
|
|
52
|
+
}
|
|
53
|
+
function normalizeMathImageOptions(options) {
|
|
54
|
+
return {
|
|
55
|
+
cellWidthPx: Math.max(1, Math.floor(options?.cellWidthPx ?? DEFAULT_MATH_IMAGE_OPTIONS.cellWidthPx)),
|
|
56
|
+
cellHeightPx: Math.max(1, Math.floor(options?.cellHeightPx ?? DEFAULT_MATH_IMAGE_OPTIONS.cellHeightPx)),
|
|
57
|
+
scale: Math.max(1, Math.floor(options?.scale ?? DEFAULT_MATH_IMAGE_OPTIONS.scale)),
|
|
58
|
+
color: options?.color ?? DEFAULT_MATH_IMAGE_OPTIONS.color,
|
|
59
|
+
maxWidthCells: Math.max(0, Math.floor(options?.maxWidthCells ?? 0)),
|
|
60
|
+
baselineRatio: clamp01(options?.baselineRatio ?? DEFAULT_MATH_IMAGE_OPTIONS.baselineRatio)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function clamp01(value) {
|
|
64
|
+
if (!Number.isFinite(value)) return .78;
|
|
65
|
+
return Math.min(1, Math.max(0, value));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Install a custom TeX -> PNG rasterizer (e.g. consumers that already have a
|
|
69
|
+
* katex/mathjax pipeline). Passing `null` restores the built-in lazy loader.
|
|
70
|
+
*/
|
|
71
|
+
function setMarkdownMathRasterizer(rasterizer) {
|
|
72
|
+
customRasterizer = rasterizer;
|
|
73
|
+
}
|
|
74
|
+
function subscribeMarkdownMathImage(listener) {
|
|
75
|
+
mathImageListeners.add(listener);
|
|
76
|
+
return () => mathImageListeners.delete(listener);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* True when a rasterizer is confirmed available (a custom one was installed or
|
|
80
|
+
* the built-in MathJax+resvg stack finished loading). Components should gate
|
|
81
|
+
* image production on this so they never spend time rasterizing formulas while
|
|
82
|
+
* the math engine is still loading or missing.
|
|
83
|
+
*/
|
|
84
|
+
function isMarkdownMathImageRendererReady() {
|
|
85
|
+
return customRasterizer != null || builtinRasterizerReady;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Ensure the math image rasterizer is loaded (or resolved as unavailable).
|
|
89
|
+
* Resolves `true` once a rasterizer is usable. Safe to call repeatedly; the
|
|
90
|
+
* underlying load is shared and its outcome is cached.
|
|
91
|
+
*/
|
|
92
|
+
async function loadMarkdownMathImageRenderer() {
|
|
93
|
+
if (isMarkdownMathImageRendererReady()) return true;
|
|
94
|
+
const rasterizer = customRasterizer ?? await loadBuiltinMathRasterizer();
|
|
95
|
+
builtinRasterizerReady = rasterizer != null;
|
|
96
|
+
return builtinRasterizerReady;
|
|
97
|
+
}
|
|
98
|
+
function hashMathImageKey(value) {
|
|
99
|
+
let hash = 2166136261;
|
|
100
|
+
for (let i = 0; i < value.length; i++) {
|
|
101
|
+
hash ^= value.charCodeAt(i);
|
|
102
|
+
hash = Math.imul(hash, 16777619);
|
|
103
|
+
}
|
|
104
|
+
return (hash >>> 0).toString(36);
|
|
105
|
+
}
|
|
106
|
+
function mathImageCacheKey(tex, mode, options) {
|
|
107
|
+
return hashMathImageKey([
|
|
108
|
+
mode,
|
|
109
|
+
tex,
|
|
110
|
+
options.cellWidthPx,
|
|
111
|
+
options.cellHeightPx,
|
|
112
|
+
options.scale,
|
|
113
|
+
options.maxWidthCells,
|
|
114
|
+
options.color,
|
|
115
|
+
options.baselineRatio
|
|
116
|
+
].join(""));
|
|
117
|
+
}
|
|
118
|
+
function notifyMathImageListeners() {
|
|
119
|
+
for (const listener of mathImageListeners) try {
|
|
120
|
+
listener();
|
|
121
|
+
} catch {}
|
|
122
|
+
}
|
|
123
|
+
/** Synchronous cache lookup used by the markdown AST/layout phase. */
|
|
124
|
+
function getCachedMarkdownMathImage(tex, mode, options) {
|
|
125
|
+
return mathImageCache.get(mathImageCacheKey(tex, mode, normalizeMathImageOptions(options))) ?? null;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Drop all cached rasterized formulas (PNG + failed lookups). Useful for long
|
|
129
|
+
* running processes that change cell metrics, and for tests.
|
|
130
|
+
*/
|
|
131
|
+
function clearMarkdownMathImageCache() {
|
|
132
|
+
mathImageCache.clear();
|
|
133
|
+
mathImageFailed.clear();
|
|
134
|
+
}
|
|
135
|
+
function evictOldestMathImageEntry() {
|
|
136
|
+
const oldest = mathImageCache.keys().next().value;
|
|
137
|
+
if (oldest != null) mathImageCache.delete(oldest);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Resolve a TeX formula to a PNG base64 + cell size. Cached per
|
|
141
|
+
* (tex, mode, cell metrics, color). In-flight requests are deduped. When a new
|
|
142
|
+
* image finishes, `subscribeMarkdownMathImage` listeners are notified so
|
|
143
|
+
* components can rebuild/re-paint with the real size.
|
|
144
|
+
*/
|
|
145
|
+
async function getMarkdownMathImage(tex, mode, options) {
|
|
146
|
+
const source = String(tex ?? "").trim();
|
|
147
|
+
if (!source) return null;
|
|
148
|
+
const normalized = normalizeMathImageOptions(options);
|
|
149
|
+
const key = mathImageCacheKey(source, mode, normalized);
|
|
150
|
+
const cached = mathImageCache.get(key);
|
|
151
|
+
if (cached) return cached;
|
|
152
|
+
if (mathImageFailed.has(key)) return null;
|
|
153
|
+
const inflight = mathImageInflight.get(key);
|
|
154
|
+
if (inflight) return inflight;
|
|
155
|
+
const pending = (async () => {
|
|
156
|
+
try {
|
|
157
|
+
const rasterizer = customRasterizer ?? await loadBuiltinMathRasterizer();
|
|
158
|
+
if (!rasterizer) return null;
|
|
159
|
+
const result = await rasterizer(source, mode, normalized);
|
|
160
|
+
if (result && result.base64) {
|
|
161
|
+
mathImageCache.set(key, result);
|
|
162
|
+
if (mathImageCache.size > MAX_CACHE_ENTRIES) evictOldestMathImageEntry();
|
|
163
|
+
notifyMathImageListeners();
|
|
164
|
+
} else if (result === null) {
|
|
165
|
+
mathImageFailed.add(key);
|
|
166
|
+
if (mathImageFailed.size > MAX_FAILED_ENTRIES) {
|
|
167
|
+
const oldest = mathImageFailed.values().next().value;
|
|
168
|
+
if (oldest != null) mathImageFailed.delete(oldest);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
} finally {
|
|
175
|
+
mathImageInflight.delete(key);
|
|
176
|
+
}
|
|
177
|
+
})();
|
|
178
|
+
mathImageInflight.set(key, pending);
|
|
179
|
+
return pending;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Kick off rasterization for every math graphic segment in the given row range
|
|
183
|
+
* that does not have a cached PNG yet. Safe to call after every row rebuild;
|
|
184
|
+
* in-flight and cached requests are deduped inside getMarkdownMathImage. Pass a
|
|
185
|
+
* viewport to avoid rasterizing off-screen formulas (e.g. virtualized views).
|
|
186
|
+
*/
|
|
187
|
+
function enqueueMarkdownMathImages(rows, options, viewport) {
|
|
188
|
+
const first = Math.max(0, Math.floor(viewport?.firstRow ?? 0));
|
|
189
|
+
const last = Math.min(rows.length, Math.max(first, Math.ceil(viewport?.lastRow ?? rows.length)));
|
|
190
|
+
for (let index = first; index < last; index++) {
|
|
191
|
+
const row = rows[index];
|
|
192
|
+
if (!row) continue;
|
|
193
|
+
for (const segment of row.segments) {
|
|
194
|
+
const graphic = segment.graphic;
|
|
195
|
+
if (graphic?.kind === "math") {
|
|
196
|
+
if (!graphic.base64 && graphic.tex) getMarkdownMathImage(graphic.tex, "display", options);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const pending = segment.mathAction?.pendingImage;
|
|
200
|
+
if (pending && segment.mathAction?.source) getMarkdownMathImage(segment.mathAction.source, segment.mathAction.mode ?? "display", options);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function base64FromBytes(bytes) {
|
|
205
|
+
const chunk = 32768;
|
|
206
|
+
let binary = "";
|
|
207
|
+
for (let offset = 0; offset < bytes.length; offset += chunk) binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk));
|
|
208
|
+
return typeof btoa === "function" ? btoa(binary) : Buffer.from(bytes).toString("base64");
|
|
209
|
+
}
|
|
210
|
+
function extractSvgMarkup(containerMarkup) {
|
|
211
|
+
const start = containerMarkup.indexOf("<svg");
|
|
212
|
+
if (start < 0) return "";
|
|
213
|
+
const end = containerMarkup.lastIndexOf("</svg>");
|
|
214
|
+
if (end < start) return "";
|
|
215
|
+
return containerMarkup.slice(start, end + 6);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* resvg is a Node-only native addon. Keeping the specifier in a variable (a
|
|
219
|
+
* non-literal dynamic import) stops browser bundlers (vite/rolldown) from
|
|
220
|
+
* statically resolving it and failing on its platform `.node` binding; at
|
|
221
|
+
* runtime a browser load fails and the rasterizer falls back to raw text.
|
|
222
|
+
*/
|
|
223
|
+
const RESVG_MODULE_ID = "@resvg/resvg-js";
|
|
224
|
+
function loadBuiltinMathRasterizer() {
|
|
225
|
+
if (!builtinRasterizerLoad) builtinRasterizerLoad = (async () => {
|
|
226
|
+
try {
|
|
227
|
+
const [{ mathjax }, { TeX }, { SVG }, { liteAdaptor }, { RegisterHTMLHandler }, { AllPackages }, { Resvg }] = await Promise.all([
|
|
228
|
+
import("mathjax-full/js/mathjax.js"),
|
|
229
|
+
import("mathjax-full/js/input/tex.js"),
|
|
230
|
+
import("mathjax-full/js/output/svg.js"),
|
|
231
|
+
import("mathjax-full/js/adaptors/liteAdaptor.js"),
|
|
232
|
+
import("mathjax-full/js/handlers/html.js"),
|
|
233
|
+
import("mathjax-full/js/input/tex/AllPackages.js"),
|
|
234
|
+
import(RESVG_MODULE_ID)
|
|
235
|
+
]);
|
|
236
|
+
const adaptor = liteAdaptor();
|
|
237
|
+
RegisterHTMLHandler(adaptor);
|
|
238
|
+
const texInput = new TeX({
|
|
239
|
+
packages: AllPackages,
|
|
240
|
+
formatError: () => {
|
|
241
|
+
throw new Error("Malformed TeX");
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
const svgOutput = new SVG({ fontCache: "none" });
|
|
245
|
+
const document = mathjax.document("", {
|
|
246
|
+
InputJax: texInput,
|
|
247
|
+
OutputJax: svgOutput
|
|
248
|
+
});
|
|
249
|
+
return async (source, mode, options) => {
|
|
250
|
+
const node = document.convert(source, { display: mode === "display" });
|
|
251
|
+
let svg = extractSvgMarkup(adaptor.outerHTML(node));
|
|
252
|
+
if (!svg) return null;
|
|
253
|
+
const widthEx = Number(/width="([\d.]+)ex"/.exec(svg)?.[1] ?? 0);
|
|
254
|
+
const heightEx = Number(/height="([\d.]+)ex"/.exec(svg)?.[1] ?? 0);
|
|
255
|
+
if (!(widthEx > 0) || !(heightEx > 0)) return null;
|
|
256
|
+
const exPx = options.cellHeightPx / 2 * options.scale;
|
|
257
|
+
if (mode === "inline") {
|
|
258
|
+
const naturalH = heightEx * exPx;
|
|
259
|
+
const naturalW = widthEx * exPx;
|
|
260
|
+
if (naturalH > options.cellHeightPx * options.scale * 2) return null;
|
|
261
|
+
const rectH = options.cellHeightPx * options.scale;
|
|
262
|
+
const k = rectH / naturalH;
|
|
263
|
+
const contentW = Math.max(1, Math.round(naturalW * k));
|
|
264
|
+
const widthCells = Math.max(1, Math.ceil(contentW / (options.cellWidthPx * options.scale)));
|
|
265
|
+
const rectW = widthCells * options.cellWidthPx * options.scale;
|
|
266
|
+
const verticalAlignEx = Number(/vertical-align:\s*(-?[\d.]+)ex/.exec(svg)?.[1] ?? 0);
|
|
267
|
+
const formulaBaseline = (heightEx - Math.abs(verticalAlignEx)) * exPx * k;
|
|
268
|
+
const terminalBaseline = options.baselineRatio * rectH;
|
|
269
|
+
const offsetY = terminalBaseline - formulaBaseline;
|
|
270
|
+
const inner = svg.replace(/style="vertical-align:[^"]*"/, "").replace(/width="[\d.]+ex"/, `width="${contentW}px"`).replace(/height="[\d.]+ex"/, `height="${rectH}px"`);
|
|
271
|
+
const wrapped = `<svg xmlns="http://www.w3.org/2000/svg" color="${options.color}" width="${rectW}px" height="${rectH}px" viewBox="0 0 ${rectW} ${rectH}"><g transform="translate(0 ${offsetY.toFixed(2)})">${inner}</g></svg>`;
|
|
272
|
+
const renderedInline = new Resvg(wrapped, { background: "rgba(0,0,0,0)" }).render();
|
|
273
|
+
const pngInline = renderedInline.asPng();
|
|
274
|
+
if (!pngInline || pngInline.length < 8) return null;
|
|
275
|
+
return {
|
|
276
|
+
base64: base64FromBytes(pngInline),
|
|
277
|
+
widthCells,
|
|
278
|
+
heightCells: 1
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
let widthPx = Math.max(1, Math.round(widthEx * exPx));
|
|
282
|
+
let heightPx = Math.max(1, Math.round(heightEx * exPx));
|
|
283
|
+
if (options.maxWidthCells > 0) {
|
|
284
|
+
const maxPx = options.maxWidthCells * options.cellWidthPx * options.scale;
|
|
285
|
+
if (widthPx > maxPx) {
|
|
286
|
+
heightPx = Math.max(1, Math.round(heightPx * (maxPx / widthPx)));
|
|
287
|
+
widthPx = maxPx;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
svg = svg.replace(/style="vertical-align:[^"]*"/, "").replace(/width="[\d.]+ex"/, `width="${widthPx}px"`).replace(/height="[\d.]+ex"/, `height="${heightPx}px"`).replace(/<svg/, `<svg color="${options.color}"`);
|
|
291
|
+
const rendered = new Resvg(svg, { background: "rgba(0,0,0,0)" }).render();
|
|
292
|
+
const png = rendered.asPng();
|
|
293
|
+
if (!png || png.length < 8) return null;
|
|
294
|
+
return {
|
|
295
|
+
base64: base64FromBytes(png),
|
|
296
|
+
widthCells: Math.max(1, Math.round(widthPx / (options.cellWidthPx * options.scale))),
|
|
297
|
+
heightCells: Math.max(1, Math.round(heightPx / (options.cellHeightPx * options.scale)))
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
} catch {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
})();
|
|
304
|
+
return builtinRasterizerLoad;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
//#endregion
|
|
308
|
+
export { clearMarkdownMathImageCache, enqueueMarkdownMathImages, getCachedMarkdownMathImage, getMarkdownMathImage, isMarkdownMathImageRendererReady, loadMarkdownMathImageRenderer, resolveMarkdownMathColor, setMarkdownMathRasterizer, subscribeMarkdownMathImage };
|