@simon_he/vue-tui 1.1.2 → 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.
Files changed (52) hide show
  1. package/dist/{TMermaidText-frVr8Ysb.js → TMermaidText-wLnMMvVS.js} +1 -1
  2. package/dist/{TSelect-tJ87rKPM.js → TSelect-CMBOOFwK.js} +4 -4
  3. package/dist/{TTree-CGdE-CYu.js → TTree-kk8ntrpg.js} +6 -5
  4. package/dist/agent/mermaid.d.cts +2 -2
  5. package/dist/agent/mermaid.d.ts +2 -2
  6. package/dist/agent.cjs +3896 -13616
  7. package/dist/agent.js +25 -82
  8. package/dist/{block-source-CrlmaBM5.js → block-source-D3YGTErb.js} +447 -326
  9. package/dist/cli.cjs +52 -15
  10. package/dist/cli.js +69 -14
  11. package/dist/core.js +3 -3
  12. package/dist/{hyperlink-_1FAj7Wq.js → create-terminal-Db_-UGVQ.js} +474 -102
  13. package/dist/{dom-renderer-DXx8fWzD.js → dom-renderer-BfwAEQMf.js} +3 -2
  14. package/dist/experimental.cjs +99 -66
  15. package/dist/experimental.js +20 -74
  16. package/dist/hyperlink-clbb_ha_.js +100 -0
  17. package/dist/index.js +8 -8
  18. package/dist/{linkify-D68T7rdW.js → linkify-CL9wwnZN.js} +2 -2
  19. package/dist/markdown.cjs +3376 -13056
  20. package/dist/markdown.d.cts +4 -0
  21. package/dist/markdown.d.ts +4 -0
  22. package/dist/markdown.js +79 -8
  23. package/dist/math-image-BZhmfUeu.js +308 -0
  24. package/dist/mermaid-uEfRItDe.js +1205 -0
  25. package/dist/mermaid.cjs +1089 -3
  26. package/dist/mermaid.d.cts +4 -0
  27. package/dist/mermaid.d.ts +4 -0
  28. package/dist/mermaid.js +7 -5
  29. package/dist/{rect-DUpeDfBg.js → rect-JzWMJxxc.js} +1 -1
  30. package/dist/renderer/cli/stdout-renderer.d.ts +44 -0
  31. package/dist/renderer/terminal-graphics.d.ts +44 -0
  32. package/dist/renderer-dom.js +4 -4
  33. package/dist/{terminal-graphics-Bbe83wSr.js → terminal-graphics-DWZrl3oh.js} +85 -2
  34. package/dist/{tlog-theme-Bul2Na3u.js → tlog-theme-DqnF_zNe.js} +5 -4
  35. package/dist/vue/components/TAgentTerminalGraphic.d.ts +1 -1
  36. package/dist/vue/components/TMarkdownText.d.ts +70 -2
  37. package/dist/vue/components/TMermaidImage.d.ts +733 -0
  38. package/dist/vue/components/TMermaidText.d.ts +2 -2
  39. package/dist/vue/components/TVirtualMarkdown.d.ts +70 -2
  40. package/dist/vue/markdown/ast.d.ts +5 -0
  41. package/dist/vue/markdown/document.d.ts +3 -0
  42. package/dist/vue/markdown/math-image.d.ts +68 -0
  43. package/dist/vue/markdown/math.d.ts +2 -0
  44. package/dist/vue/markdown/theme.d.ts +12 -0
  45. package/dist/vue/markdown/types.d.ts +12 -2
  46. package/dist/vue/mermaid/beautiful-mermaid.d.ts +4 -4
  47. package/dist/vue/mermaid/mermaid-image.d.ts +65 -0
  48. package/dist/vue.js +9 -9
  49. package/package.json +20 -3
  50. package/dist/mermaid-BNhM1jAZ.js +0 -123
  51. package/dist/width-GqllnV8C.js +0 -474
  52. /package/dist/{ansi-palette-CH6jc2Z7.js → ansi-palette-BHF1rN7R.js} +0 -0
@@ -3,6 +3,10 @@ export { TVirtualMarkdown } from "./vue/components/TVirtualMarkdown.js";
3
3
  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
+ 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";
6
10
  export { createTuiMarkdownParser, isSafeMarkdownLink } from "./vue/markdown/parser.js";
7
11
  export type { TuiMarkdownBlockSource, TuiMarkdownBlockSourceOptions, TuiMarkdownBlockSourceSnapshot, } from "./vue/markdown/block-source.js";
8
12
  export type { TuiMarkdownLayoutOptions } from "./vue/markdown/layout.js";
@@ -3,6 +3,10 @@ export { TVirtualMarkdown } from "./vue/components/TVirtualMarkdown.js";
3
3
  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
+ 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";
6
10
  export { createTuiMarkdownParser, isSafeMarkdownLink } from "./vue/markdown/parser.js";
7
11
  export type { TuiMarkdownBlockSource, TuiMarkdownBlockSourceOptions, TuiMarkdownBlockSourceSnapshot, } from "./vue/markdown/block-source.js";
8
12
  export type { TuiMarkdownLayoutOptions } from "./vue/markdown/layout.js";
package/dist/markdown.js CHANGED
@@ -1,12 +1,13 @@
1
- import "./hyperlink-_1FAj7Wq.js";
2
- import "./width-GqllnV8C.js";
3
- import { EventZIndexContextKey, intersectRect, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility, withTextWidthProvider } from "./rect-DUpeDfBg.js";
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-Bbe83wSr.js";
9
- import { TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, clearMarkdownImageGraphics, collectVisibleMarkdownImageGraphicIds, createMarkdownBlockSource, createTuiMarkdownParser, findMarkdownImageActionAt, findMarkdownLinkActionAt, findMarkdownMathActionAt, isSafeMarkdownLink, layoutMarkdownBlocks, markdownThemeSignature, paintMarkdownVisualRow } from "./block-source-CrlmaBM5.js";
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,6 +155,13 @@ const TMarkdownText = defineComponent({
126
155
  const graphicsOutputVersion = shallowRef(getTerminalGraphicsOutputVersion(terminal));
127
156
  const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
128
157
  graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal);
158
+ enqueuePendingMathImages();
159
+ });
160
+ const unsubscribeMathRenderer = subscribeMarkdownMathRenderer(() => {
161
+ scheduleRebuild();
162
+ });
163
+ const unsubscribeMathImage = subscribeMarkdownMathImage(() => {
164
+ scheduleRebuild();
129
165
  });
130
166
  watch(() => `${props.streaming ? 1 : 0}:${(props.customHtmlTags ?? []).join("\0")}`, () => {
131
167
  parser.value = markRaw(createTuiMarkdownParser({
@@ -133,6 +169,30 @@ const TMarkdownText = defineComponent({
133
169
  customHtmlTags: props.customHtmlTags
134
170
  }));
135
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
+ }
136
196
  function rebuildRows() {
137
197
  rows.value = markRaw(withTextWidthProvider(widthProvider, () => buildMarkdownVisualRows(props.content, props.w, parser.value, {
138
198
  final: props.final,
@@ -144,9 +204,11 @@ const TMarkdownText = defineComponent({
144
204
  minHeight: props.imageMinHeight,
145
205
  maxHeight: props.imageMaxHeight,
146
206
  preserveAspectRatio: props.imagePreserveAspectRatio
147
- }
207
+ },
208
+ math: mathOptions()
148
209
  })));
149
210
  documentVersion.value++;
211
+ enqueuePendingMathImages();
150
212
  }
151
213
  function scheduleRebuild() {
152
214
  const currentVersion = ++rebuildVersion;
@@ -178,7 +240,14 @@ const TMarkdownText = defineComponent({
178
240
  () => props.imageMaxWidth,
179
241
  () => props.imageMinHeight,
180
242
  () => props.imageMaxHeight,
181
- () => 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
182
251
  ], () => {
183
252
  scheduleRebuild();
184
253
  }, { immediate: true });
@@ -186,6 +255,8 @@ const TMarkdownText = defineComponent({
186
255
  alive = false;
187
256
  rebuildVersion++;
188
257
  unsubscribeGraphicsOutput();
258
+ unsubscribeMathRenderer();
259
+ unsubscribeMathImage();
189
260
  clearMarkdownImageGraphics(terminal, fullRect.value);
190
261
  });
191
262
  const fullRect = computed(() => {
@@ -347,4 +418,4 @@ const TMarkdownText = defineComponent({
347
418
  });
348
419
 
349
420
  //#endregion
350
- export { TMarkdownText, TVirtualMarkdown, buildMarkdownBlocks, buildMarkdownVisualRows, createMarkdownBlockSource, createTuiMarkdownParser, isSafeMarkdownLink, layoutMarkdownBlocks };
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 };