@simonklee/opentui-tex 0.2.0
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/LICENSE +21 -0
- package/README.md +238 -0
- package/dist/backend.d.ts +23 -0
- package/dist/binding-tex-renderable.d.ts +24 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1379 -0
- package/dist/math-layout.d.ts +3 -0
- package/dist/math-parser.d.ts +4 -0
- package/dist/math-symbols.d.ts +11 -0
- package/dist/math-types.d.ts +59 -0
- package/dist/native/ffi.d.ts +18 -0
- package/dist/native/native-renderer.d.ts +15 -0
- package/dist/native/native-tex-backend.d.ts +8 -0
- package/dist/native/platform.d.ts +1 -0
- package/dist/native.bun.js +273 -0
- package/dist/native.d.ts +2 -0
- package/dist/native.js +273 -0
- package/dist/react.d.ts +8 -0
- package/dist/react.js +1501 -0
- package/dist/solid.d.ts +8 -0
- package/dist/solid.js +1501 -0
- package/dist/tex-renderable.d.ts +67 -0
- package/dist/unicode-tex-backend.d.ts +11 -0
- package/docs/native.md +32 -0
- package/docs/releasing.md +130 -0
- package/package.json +103 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1379 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/tex-renderable.ts
|
|
5
|
+
import {
|
|
6
|
+
BoxRenderable,
|
|
7
|
+
ImageRenderable,
|
|
8
|
+
TextRenderable
|
|
9
|
+
} from "@opentui/core";
|
|
10
|
+
import stringWidth3 from "string-width";
|
|
11
|
+
|
|
12
|
+
// src/math-layout.ts
|
|
13
|
+
import stringWidth from "string-width";
|
|
14
|
+
var CELL_LIMIT = 16384;
|
|
15
|
+
var superscript = {
|
|
16
|
+
"0": "⁰",
|
|
17
|
+
"1": "¹",
|
|
18
|
+
"2": "²",
|
|
19
|
+
"3": "³",
|
|
20
|
+
"4": "⁴",
|
|
21
|
+
"5": "⁵",
|
|
22
|
+
"6": "⁶",
|
|
23
|
+
"7": "⁷",
|
|
24
|
+
"8": "⁸",
|
|
25
|
+
"9": "⁹",
|
|
26
|
+
"+": "⁺",
|
|
27
|
+
"-": "⁻",
|
|
28
|
+
"=": "⁼",
|
|
29
|
+
"(": "⁽",
|
|
30
|
+
")": "⁾",
|
|
31
|
+
n: "ⁿ",
|
|
32
|
+
i: "ⁱ"
|
|
33
|
+
};
|
|
34
|
+
var subscript = {
|
|
35
|
+
"0": "₀",
|
|
36
|
+
"1": "₁",
|
|
37
|
+
"2": "₂",
|
|
38
|
+
"3": "₃",
|
|
39
|
+
"4": "₄",
|
|
40
|
+
"5": "₅",
|
|
41
|
+
"6": "₆",
|
|
42
|
+
"7": "₇",
|
|
43
|
+
"8": "₈",
|
|
44
|
+
"9": "₉",
|
|
45
|
+
"+": "₊",
|
|
46
|
+
"-": "₋",
|
|
47
|
+
"=": "₌",
|
|
48
|
+
"(": "₍",
|
|
49
|
+
")": "₎",
|
|
50
|
+
a: "ₐ",
|
|
51
|
+
e: "ₑ",
|
|
52
|
+
h: "ₕ",
|
|
53
|
+
i: "ᵢ",
|
|
54
|
+
j: "ⱼ",
|
|
55
|
+
k: "ₖ",
|
|
56
|
+
l: "ₗ",
|
|
57
|
+
m: "ₘ",
|
|
58
|
+
n: "ₙ",
|
|
59
|
+
o: "ₒ",
|
|
60
|
+
p: "ₚ",
|
|
61
|
+
r: "ᵣ",
|
|
62
|
+
s: "ₛ",
|
|
63
|
+
t: "ₜ",
|
|
64
|
+
u: "ᵤ",
|
|
65
|
+
v: "ᵥ",
|
|
66
|
+
x: "ₓ"
|
|
67
|
+
};
|
|
68
|
+
function layoutMath(node, displayMode) {
|
|
69
|
+
return layout(node, displayMode);
|
|
70
|
+
}
|
|
71
|
+
function boxToString(box, widthMax, heightMax) {
|
|
72
|
+
const lines = [];
|
|
73
|
+
for (let y = 0;y < Math.min(box.height, heightMax); y++) {
|
|
74
|
+
let line = "";
|
|
75
|
+
let width = 0;
|
|
76
|
+
for (let x = 0;x < box.width && width < widthMax; x++) {
|
|
77
|
+
const value = box.cells[y][x];
|
|
78
|
+
if (!value) {
|
|
79
|
+
line += " ";
|
|
80
|
+
width++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const cellWidth = stringWidth(value);
|
|
84
|
+
if (cellWidth > widthMax)
|
|
85
|
+
throw new Error(`Unicode glyph exceeds the ${widthMax}-column TeX width`);
|
|
86
|
+
if (width + cellWidth > widthMax)
|
|
87
|
+
break;
|
|
88
|
+
line += value;
|
|
89
|
+
width += cellWidth;
|
|
90
|
+
x += cellWidth - 1;
|
|
91
|
+
}
|
|
92
|
+
lines.push(line.trimEnd());
|
|
93
|
+
}
|
|
94
|
+
return lines.join(`
|
|
95
|
+
`).trimEnd();
|
|
96
|
+
}
|
|
97
|
+
function layout(node, display) {
|
|
98
|
+
switch (node.type) {
|
|
99
|
+
case "row":
|
|
100
|
+
return layoutRow(node.body, display);
|
|
101
|
+
case "symbol":
|
|
102
|
+
case "text":
|
|
103
|
+
case "operator":
|
|
104
|
+
return textBox(node.value);
|
|
105
|
+
case "space":
|
|
106
|
+
return blank(node.width, 1, 0);
|
|
107
|
+
case "fraction":
|
|
108
|
+
return fraction(layout(node.numerator, display), layout(node.denominator, display), node.bar);
|
|
109
|
+
case "root":
|
|
110
|
+
return root(layout(node.body, display), node.index ? layout(node.index, display) : undefined);
|
|
111
|
+
case "scripts":
|
|
112
|
+
return scripts(node, display);
|
|
113
|
+
case "delimited":
|
|
114
|
+
return delimited(node.left, layout(node.body, display), node.right);
|
|
115
|
+
case "matrix":
|
|
116
|
+
return matrix(node.rows, node.environment, display);
|
|
117
|
+
case "accent":
|
|
118
|
+
return accent(node.accent, layout(node.body, display));
|
|
119
|
+
case "overunder":
|
|
120
|
+
return overUnder(layout(node.base, display), node.over ? layout(node.over, display) : undefined, node.under ? layout(node.under, display) : undefined);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function layoutRow(nodes, display) {
|
|
124
|
+
const boxes = [];
|
|
125
|
+
let previous;
|
|
126
|
+
for (let index = 0;index < nodes.length; index++) {
|
|
127
|
+
const role = normalizedRole(roleOf(nodes[index]), previous, nextRole(nodes, index + 1));
|
|
128
|
+
if (needsSpace(previous, role, boxes.length))
|
|
129
|
+
boxes.push(blank(1, 1, 0));
|
|
130
|
+
boxes.push(layout(nodes[index], display));
|
|
131
|
+
if (nodes[index].type !== "space")
|
|
132
|
+
previous = role ?? "ordinary";
|
|
133
|
+
}
|
|
134
|
+
return hpack(boxes);
|
|
135
|
+
}
|
|
136
|
+
function fraction(top, bottom, bar) {
|
|
137
|
+
const width = Math.max(top.width, bottom.width) + 2;
|
|
138
|
+
const result = blank(width, top.height + bottom.height + 1, top.height);
|
|
139
|
+
overlay(result, top, Math.floor((width - top.width) / 2), 0);
|
|
140
|
+
if (bar)
|
|
141
|
+
horizontal(result, top.height, width, "─");
|
|
142
|
+
overlay(result, bottom, Math.floor((width - bottom.width) / 2), top.height + 1);
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
function root(body, index) {
|
|
146
|
+
const indexWidth = index ? Math.max(0, index.width - 1) : 0;
|
|
147
|
+
const bodyX = indexWidth + 2;
|
|
148
|
+
const result = blank(bodyX + body.width, body.height + 1, body.baseline + 1);
|
|
149
|
+
set(result, bodyX - 1, 0, "╭");
|
|
150
|
+
for (let x = bodyX;x < result.width; x++)
|
|
151
|
+
set(result, x, 0, "─");
|
|
152
|
+
set(result, bodyX - 2, result.baseline, "√");
|
|
153
|
+
overlay(result, body, bodyX, 1);
|
|
154
|
+
if (index)
|
|
155
|
+
overlay(result, index, 0, 0);
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
function scripts(node, display) {
|
|
159
|
+
const base = layout(node.base, display);
|
|
160
|
+
const supText = node.superscript ? simpleText(node.superscript) : undefined;
|
|
161
|
+
const subText = node.subscript ? simpleText(node.subscript) : undefined;
|
|
162
|
+
const limits = node.base.type === "operator" && node.base.limits && display;
|
|
163
|
+
if (!limits) {
|
|
164
|
+
const mappedSup = supText === undefined ? undefined : mapScript(supText, superscript);
|
|
165
|
+
const mappedSub = subText === undefined ? undefined : mapScript(subText, subscript);
|
|
166
|
+
if ((!node.superscript || mappedSup !== undefined) && (!node.subscript || mappedSub !== undefined)) {
|
|
167
|
+
return hpack([base, textBox((mappedSup ?? "") + (mappedSub ?? ""))]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (limits)
|
|
171
|
+
return overUnder(base, node.superscript ? layout(node.superscript, display) : undefined, node.subscript ? layout(node.subscript, display) : undefined);
|
|
172
|
+
const sup = node.superscript ? layout(node.superscript, display) : undefined;
|
|
173
|
+
const sub = node.subscript ? layout(node.subscript, display) : undefined;
|
|
174
|
+
const sideWidth = Math.max(sup?.width ?? 0, sub?.width ?? 0);
|
|
175
|
+
const result = blank(base.width + sideWidth, (sup?.height ?? 0) + base.height + (sub?.height ?? 0), (sup?.height ?? 0) + base.baseline);
|
|
176
|
+
overlay(result, base, 0, sup?.height ?? 0);
|
|
177
|
+
if (sup)
|
|
178
|
+
overlay(result, sup, base.width, 0);
|
|
179
|
+
if (sub)
|
|
180
|
+
overlay(result, sub, base.width, (sup?.height ?? 0) + base.height);
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
function overUnder(base, over, under) {
|
|
184
|
+
const width = Math.max(base.width, over?.width ?? 0, under?.width ?? 0);
|
|
185
|
+
const result = blank(width, (over?.height ?? 0) + base.height + (under?.height ?? 0), (over?.height ?? 0) + base.baseline);
|
|
186
|
+
if (over)
|
|
187
|
+
overlay(result, over, Math.floor((width - over.width) / 2), 0);
|
|
188
|
+
overlay(result, base, Math.floor((width - base.width) / 2), over?.height ?? 0);
|
|
189
|
+
if (under)
|
|
190
|
+
overlay(result, under, Math.floor((width - under.width) / 2), (over?.height ?? 0) + base.height);
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
function delimited(left, body, right) {
|
|
194
|
+
return hpack([delimiter(left, body.height, body.baseline, true), body, delimiter(right, body.height, body.baseline, false)]);
|
|
195
|
+
}
|
|
196
|
+
function matrix(rows, environment, display) {
|
|
197
|
+
const cells = rows.map((row) => row.map((node) => layout(node, display)));
|
|
198
|
+
const columns = Math.max(0, ...cells.map((row) => row.length));
|
|
199
|
+
const widths = Array.from({ length: columns }, (_, x) => Math.max(0, ...cells.map((row) => row[x]?.width ?? 0)));
|
|
200
|
+
const ascents = cells.map((row) => Math.max(0, ...row.map((cell) => cell.baseline)));
|
|
201
|
+
const descents = cells.map((row) => Math.max(0, ...row.map((cell) => cell.height - cell.baseline - 1)));
|
|
202
|
+
const heights = ascents.map((value, y2) => value + descents[y2] + 1);
|
|
203
|
+
const gap = environment === "cases" || environment === "aligned" || environment === "align" ? 2 : 1;
|
|
204
|
+
const width = widths.reduce((sum, value) => sum + value, 0) + Math.max(0, columns - 1) * gap;
|
|
205
|
+
const height = Math.max(1, heights.reduce((sum, value) => sum + value, 0));
|
|
206
|
+
const result = blank(width, height, Math.floor(height / 2));
|
|
207
|
+
let y = 0;
|
|
208
|
+
for (let rowIndex = 0;rowIndex < cells.length; rowIndex++) {
|
|
209
|
+
let x = 0;
|
|
210
|
+
for (let column = 0;column < columns; column++) {
|
|
211
|
+
const cell = cells[rowIndex][column];
|
|
212
|
+
if (cell) {
|
|
213
|
+
const aligned = environment === "aligned" || environment === "align" || environment === "cases";
|
|
214
|
+
const offset = aligned ? column % 2 === 0 ? widths[column] - cell.width : 0 : Math.floor((widths[column] - cell.width) / 2);
|
|
215
|
+
overlay(result, cell, x + offset, y + ascents[rowIndex] - cell.baseline);
|
|
216
|
+
}
|
|
217
|
+
x += widths[column] + gap;
|
|
218
|
+
}
|
|
219
|
+
y += heights[rowIndex];
|
|
220
|
+
}
|
|
221
|
+
const pair = matrixDelimiters(environment);
|
|
222
|
+
return pair ? delimited(pair[0], result, pair[1]) : result;
|
|
223
|
+
}
|
|
224
|
+
function accent(kind, body) {
|
|
225
|
+
if (kind === "underline") {
|
|
226
|
+
const result2 = blank(body.width, body.height + 1, body.baseline);
|
|
227
|
+
overlay(result2, body, 0, 0);
|
|
228
|
+
horizontal(result2, body.height, body.width, "─");
|
|
229
|
+
return result2;
|
|
230
|
+
}
|
|
231
|
+
const result = blank(body.width, body.height + 1, body.baseline + 1);
|
|
232
|
+
overlay(result, body, 0, 1);
|
|
233
|
+
const mark = kind === "hat" || kind === "widehat" ? body.width === 1 ? "^" : "⌢" : kind === "bar" || kind === "overline" ? "─" : kind === "vec" ? "→" : kind === "tilde" ? "~" : kind === "dot" ? "·" : "¨";
|
|
234
|
+
if (kind === "bar" || kind === "overline")
|
|
235
|
+
horizontal(result, 0, body.width, mark);
|
|
236
|
+
else
|
|
237
|
+
set(result, Math.max(0, Math.floor((body.width - stringWidth(mark)) / 2)), 0, mark);
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
function delimiter(value, height, baseline, left) {
|
|
241
|
+
if (!value)
|
|
242
|
+
return blank(0, height, baseline);
|
|
243
|
+
if (height === 1)
|
|
244
|
+
return textBox(value);
|
|
245
|
+
const glyphs = delimiterGlyphs(value, left);
|
|
246
|
+
const result = blank(Math.max(...glyphs.map((glyph) => stringWidth(glyph))), height, baseline);
|
|
247
|
+
for (let y = 0;y < height; y++)
|
|
248
|
+
set(result, 0, y, y === 0 ? glyphs[0] : y === height - 1 ? glyphs[2] : glyphs[1]);
|
|
249
|
+
if ((value === "{" || value === "}") && height >= 3)
|
|
250
|
+
set(result, 0, Math.floor(height / 2), left ? "⎨" : "⎬");
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
function delimiterGlyphs(value, left) {
|
|
254
|
+
if (value === "(")
|
|
255
|
+
return ["⎛", "⎜", "⎝"];
|
|
256
|
+
if (value === ")")
|
|
257
|
+
return ["⎞", "⎟", "⎠"];
|
|
258
|
+
if (value === "[")
|
|
259
|
+
return ["⎡", "⎢", "⎣"];
|
|
260
|
+
if (value === "]")
|
|
261
|
+
return ["⎤", "⎥", "⎦"];
|
|
262
|
+
if (value === "{")
|
|
263
|
+
return ["⎧", "⎪", "⎩"];
|
|
264
|
+
if (value === "}")
|
|
265
|
+
return ["⎫", "⎪", "⎭"];
|
|
266
|
+
if (value === "⟨")
|
|
267
|
+
return ["/", "│", "\\"];
|
|
268
|
+
if (value === "⟩")
|
|
269
|
+
return ["\\", "│", "/"];
|
|
270
|
+
if (value === "⌊" || value === "⌋")
|
|
271
|
+
return ["│", "│", value];
|
|
272
|
+
if (value === "⌈" || value === "⌉")
|
|
273
|
+
return [value, "│", "│"];
|
|
274
|
+
return [value, value, value];
|
|
275
|
+
}
|
|
276
|
+
function matrixDelimiters(value) {
|
|
277
|
+
return value === "pmatrix" ? ["(", ")"] : value === "bmatrix" ? ["[", "]"] : value === "Bmatrix" ? ["{", "}"] : value === "vmatrix" ? ["│", "│"] : value === "Vmatrix" ? ["║", "║"] : value === "cases" ? ["{", ""] : undefined;
|
|
278
|
+
}
|
|
279
|
+
function textBox(text) {
|
|
280
|
+
const parts = typeof Intl.Segmenter === "function" ? [...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(text)].map((part) => part.segment) : [...text];
|
|
281
|
+
const result = blank(parts.reduce((sum, part) => sum + stringWidth(part), 0), 1, 0);
|
|
282
|
+
let x = 0;
|
|
283
|
+
for (const part of parts) {
|
|
284
|
+
set(result, x, 0, part);
|
|
285
|
+
x += stringWidth(part);
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
function hpack(boxes) {
|
|
290
|
+
if (!boxes.length)
|
|
291
|
+
return blank(0, 1, 0);
|
|
292
|
+
const ascent = Math.max(...boxes.map((box) => box.baseline));
|
|
293
|
+
const descent = Math.max(...boxes.map((box) => box.height - box.baseline - 1));
|
|
294
|
+
const result = blank(boxes.reduce((sum, box) => sum + box.width, 0), ascent + descent + 1, ascent);
|
|
295
|
+
let x = 0;
|
|
296
|
+
for (const box of boxes) {
|
|
297
|
+
overlay(result, box, x, ascent - box.baseline);
|
|
298
|
+
x += box.width;
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
function blank(width, height, baseline) {
|
|
303
|
+
width = Math.max(0, width);
|
|
304
|
+
height = Math.max(1, height);
|
|
305
|
+
if (width * height > CELL_LIMIT)
|
|
306
|
+
throw new Error(`Unicode TeX output exceeds ${CELL_LIMIT} characters`);
|
|
307
|
+
return { width, height, baseline: Math.max(0, baseline), cells: Array.from({ length: height }, () => Array(width)) };
|
|
308
|
+
}
|
|
309
|
+
function overlay(target, source, x, y) {
|
|
310
|
+
for (let sy = 0;sy < source.height; sy++)
|
|
311
|
+
for (let sx = 0;sx < source.width; sx++)
|
|
312
|
+
if (source.cells[sy][sx])
|
|
313
|
+
target.cells[y + sy][x + sx] = source.cells[sy][sx];
|
|
314
|
+
}
|
|
315
|
+
function set(box, x, y, value) {
|
|
316
|
+
if (x >= 0 && y >= 0 && x < box.width && y < box.height)
|
|
317
|
+
box.cells[y][x] = value;
|
|
318
|
+
}
|
|
319
|
+
function horizontal(box, y, width, value) {
|
|
320
|
+
for (let x = 0;x < width; x++)
|
|
321
|
+
set(box, x, y, value);
|
|
322
|
+
}
|
|
323
|
+
function simpleText(node) {
|
|
324
|
+
if (node.type === "symbol" || node.type === "text" || node.type === "operator")
|
|
325
|
+
return node.value;
|
|
326
|
+
if (node.type === "row") {
|
|
327
|
+
const values = node.body.map(simpleText);
|
|
328
|
+
if (values.every((value) => value !== undefined))
|
|
329
|
+
return values.join("");
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
function mapScript(value, table) {
|
|
334
|
+
let result = "";
|
|
335
|
+
for (const char of value) {
|
|
336
|
+
if (!table[char])
|
|
337
|
+
return;
|
|
338
|
+
result += table[char];
|
|
339
|
+
}
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
function roleOf(node) {
|
|
343
|
+
if (node.type === "symbol")
|
|
344
|
+
return node.role;
|
|
345
|
+
if (node.type === "operator" || node.type === "fraction" || node.type === "root" || node.type === "matrix")
|
|
346
|
+
return "operator";
|
|
347
|
+
if (node.type === "scripts")
|
|
348
|
+
return roleOf(node.base);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
function nextRole(nodes, start) {
|
|
352
|
+
for (let i = start;i < nodes.length; i++)
|
|
353
|
+
if (nodes[i].type !== "space")
|
|
354
|
+
return roleOf(nodes[i]) ?? "ordinary";
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
function needsSpace(previous, current, count) {
|
|
358
|
+
return count > 0 && previous !== "opening" && previous !== "punctuation" && current !== "closing" && current !== "punctuation" && (previous === "binary" || previous === "relation" || previous === "operator" || current === "binary" || current === "relation" || current === "operator");
|
|
359
|
+
}
|
|
360
|
+
function normalizedRole(role, previous, next) {
|
|
361
|
+
return role === "binary" && (!previous || ["binary", "relation", "operator", "punctuation", "opening"].includes(previous) || !next || ["binary", "relation", "punctuation", "closing"].includes(next)) ? "ordinary" : role;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/math-symbols.ts
|
|
365
|
+
var ordinary = {
|
|
366
|
+
alpha: "α",
|
|
367
|
+
beta: "β",
|
|
368
|
+
gamma: "γ",
|
|
369
|
+
delta: "δ",
|
|
370
|
+
epsilon: "ε",
|
|
371
|
+
varepsilon: "ϵ",
|
|
372
|
+
zeta: "ζ",
|
|
373
|
+
eta: "η",
|
|
374
|
+
theta: "θ",
|
|
375
|
+
vartheta: "ϑ",
|
|
376
|
+
iota: "ι",
|
|
377
|
+
kappa: "κ",
|
|
378
|
+
lambda: "λ",
|
|
379
|
+
mu: "μ",
|
|
380
|
+
nu: "ν",
|
|
381
|
+
xi: "ξ",
|
|
382
|
+
omicron: "ο",
|
|
383
|
+
pi: "π",
|
|
384
|
+
varpi: "ϖ",
|
|
385
|
+
rho: "ρ",
|
|
386
|
+
varrho: "ϱ",
|
|
387
|
+
sigma: "σ",
|
|
388
|
+
varsigma: "ς",
|
|
389
|
+
tau: "τ",
|
|
390
|
+
upsilon: "υ",
|
|
391
|
+
phi: "φ",
|
|
392
|
+
varphi: "ϕ",
|
|
393
|
+
chi: "χ",
|
|
394
|
+
psi: "ψ",
|
|
395
|
+
omega: "ω",
|
|
396
|
+
Gamma: "Γ",
|
|
397
|
+
Delta: "Δ",
|
|
398
|
+
Theta: "Θ",
|
|
399
|
+
Lambda: "Λ",
|
|
400
|
+
Xi: "Ξ",
|
|
401
|
+
Pi: "Π",
|
|
402
|
+
Sigma: "Σ",
|
|
403
|
+
Upsilon: "Υ",
|
|
404
|
+
Phi: "Φ",
|
|
405
|
+
Psi: "Ψ",
|
|
406
|
+
Omega: "Ω",
|
|
407
|
+
infty: "∞",
|
|
408
|
+
partial: "∂",
|
|
409
|
+
nabla: "∇",
|
|
410
|
+
emptyset: "∅",
|
|
411
|
+
varnothing: "∅",
|
|
412
|
+
forall: "∀",
|
|
413
|
+
exists: "∃",
|
|
414
|
+
neg: "¬",
|
|
415
|
+
angle: "∠",
|
|
416
|
+
degree: "°",
|
|
417
|
+
prime: "′",
|
|
418
|
+
hbar: "ℏ",
|
|
419
|
+
ell: "ℓ",
|
|
420
|
+
Re: "ℜ",
|
|
421
|
+
Im: "ℑ",
|
|
422
|
+
aleph: "ℵ",
|
|
423
|
+
top: "⊤",
|
|
424
|
+
bot: "⊥",
|
|
425
|
+
checkmark: "✓"
|
|
426
|
+
};
|
|
427
|
+
var binary = {
|
|
428
|
+
pm: "±",
|
|
429
|
+
mp: "∓",
|
|
430
|
+
times: "×",
|
|
431
|
+
div: "÷",
|
|
432
|
+
cdot: "·",
|
|
433
|
+
ast: "∗",
|
|
434
|
+
star: "⋆",
|
|
435
|
+
circ: "∘",
|
|
436
|
+
bullet: "•",
|
|
437
|
+
oplus: "⊕",
|
|
438
|
+
ominus: "⊖",
|
|
439
|
+
otimes: "⊗",
|
|
440
|
+
oslash: "⊘",
|
|
441
|
+
odot: "⊙",
|
|
442
|
+
cap: "∩",
|
|
443
|
+
cup: "∪",
|
|
444
|
+
land: "∧",
|
|
445
|
+
wedge: "∧",
|
|
446
|
+
lor: "∨",
|
|
447
|
+
vee: "∨",
|
|
448
|
+
setminus: "∖"
|
|
449
|
+
};
|
|
450
|
+
var relation = {
|
|
451
|
+
ne: "≠",
|
|
452
|
+
neq: "≠",
|
|
453
|
+
equiv: "≡",
|
|
454
|
+
approx: "≈",
|
|
455
|
+
sim: "∼",
|
|
456
|
+
simeq: "≃",
|
|
457
|
+
cong: "≅",
|
|
458
|
+
propto: "∝",
|
|
459
|
+
le: "≤",
|
|
460
|
+
leq: "≤",
|
|
461
|
+
ge: "≥",
|
|
462
|
+
geq: "≥",
|
|
463
|
+
ll: "≪",
|
|
464
|
+
gg: "≫",
|
|
465
|
+
in: "∈",
|
|
466
|
+
notin: "∉",
|
|
467
|
+
ni: "∋",
|
|
468
|
+
subset: "⊂",
|
|
469
|
+
supset: "⊃",
|
|
470
|
+
subseteq: "⊆",
|
|
471
|
+
supseteq: "⊇",
|
|
472
|
+
parallel: "∥",
|
|
473
|
+
perp: "⊥",
|
|
474
|
+
vdash: "⊢",
|
|
475
|
+
models: "⊨",
|
|
476
|
+
leftarrow: "←",
|
|
477
|
+
gets: "←",
|
|
478
|
+
rightarrow: "→",
|
|
479
|
+
to: "→",
|
|
480
|
+
leftrightarrow: "↔",
|
|
481
|
+
Leftarrow: "⇐",
|
|
482
|
+
Rightarrow: "⇒",
|
|
483
|
+
Leftrightarrow: "⇔",
|
|
484
|
+
mapsto: "↦",
|
|
485
|
+
longleftarrow: "⟵",
|
|
486
|
+
longrightarrow: "⟶",
|
|
487
|
+
longleftrightarrow: "⟷",
|
|
488
|
+
uparrow: "↑",
|
|
489
|
+
downarrow: "↓",
|
|
490
|
+
updownarrow: "↕"
|
|
491
|
+
};
|
|
492
|
+
var punctuation = { ldots: "…", dots: "…", cdots: "⋯", vdots: "⋮", ddots: "⋱", colon: ":" };
|
|
493
|
+
function definitions(values, role) {
|
|
494
|
+
return Object.fromEntries(Object.entries(values).map(([name, value]) => [name, { value, role }]));
|
|
495
|
+
}
|
|
496
|
+
var symbols = {
|
|
497
|
+
...definitions(ordinary, "ordinary"),
|
|
498
|
+
...definitions(binary, "binary"),
|
|
499
|
+
...definitions(relation, "relation"),
|
|
500
|
+
...definitions(punctuation, "punctuation")
|
|
501
|
+
};
|
|
502
|
+
var operators = {
|
|
503
|
+
sum: "∑",
|
|
504
|
+
prod: "∏",
|
|
505
|
+
coprod: "∐",
|
|
506
|
+
int: "∫",
|
|
507
|
+
iint: "∬",
|
|
508
|
+
iiint: "∭",
|
|
509
|
+
oint: "∮",
|
|
510
|
+
bigcap: "⋂",
|
|
511
|
+
bigcup: "⋃",
|
|
512
|
+
bigvee: "⋁",
|
|
513
|
+
bigwedge: "⋀"
|
|
514
|
+
};
|
|
515
|
+
var namedOperators = new Set(["sin", "cos", "tan", "log", "ln", "exp", "lim", "min", "max", "sup", "inf", "det", "gcd", "ker"]);
|
|
516
|
+
var delimiters = {
|
|
517
|
+
"(": "(",
|
|
518
|
+
")": ")",
|
|
519
|
+
"[": "[",
|
|
520
|
+
"]": "]",
|
|
521
|
+
"{": "{",
|
|
522
|
+
"}": "}",
|
|
523
|
+
"|": "│",
|
|
524
|
+
"\\|": "║",
|
|
525
|
+
lbrace: "{",
|
|
526
|
+
rbrace: "}",
|
|
527
|
+
vert: "│",
|
|
528
|
+
Vert: "║",
|
|
529
|
+
langle: "⟨",
|
|
530
|
+
rangle: "⟩",
|
|
531
|
+
lfloor: "⌊",
|
|
532
|
+
rfloor: "⌋",
|
|
533
|
+
lceil: "⌈",
|
|
534
|
+
rceil: "⌉",
|
|
535
|
+
".": ""
|
|
536
|
+
};
|
|
537
|
+
var accents = {
|
|
538
|
+
hat: "hat",
|
|
539
|
+
widehat: "widehat",
|
|
540
|
+
bar: "bar",
|
|
541
|
+
overline: "overline",
|
|
542
|
+
underline: "underline",
|
|
543
|
+
vec: "vec",
|
|
544
|
+
tilde: "tilde",
|
|
545
|
+
widetilde: "tilde",
|
|
546
|
+
dot: "dot",
|
|
547
|
+
ddot: "ddot"
|
|
548
|
+
};
|
|
549
|
+
var spacing = { ",": 0, ":": 1, ";": 1, "!": 0, quad: 2, qquad: 4, enspace: 1, thinspace: 0 };
|
|
550
|
+
|
|
551
|
+
// src/math-parser.ts
|
|
552
|
+
var MAX_NESTING_DEPTH = 256;
|
|
553
|
+
var environments = new Set([
|
|
554
|
+
"matrix",
|
|
555
|
+
"pmatrix",
|
|
556
|
+
"bmatrix",
|
|
557
|
+
"Bmatrix",
|
|
558
|
+
"vmatrix",
|
|
559
|
+
"Vmatrix",
|
|
560
|
+
"cases",
|
|
561
|
+
"aligned",
|
|
562
|
+
"align",
|
|
563
|
+
"gathered",
|
|
564
|
+
"gather",
|
|
565
|
+
"smallmatrix",
|
|
566
|
+
"array"
|
|
567
|
+
]);
|
|
568
|
+
function parseMath(source) {
|
|
569
|
+
return new Parser(source).parse();
|
|
570
|
+
}
|
|
571
|
+
function parseMathIncomplete(source) {
|
|
572
|
+
return new Parser(source, true).parse();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
class Parser {
|
|
576
|
+
source;
|
|
577
|
+
incomplete;
|
|
578
|
+
offset = 0;
|
|
579
|
+
depth = 0;
|
|
580
|
+
constructor(source, incomplete = false) {
|
|
581
|
+
this.source = source;
|
|
582
|
+
this.incomplete = incomplete;
|
|
583
|
+
}
|
|
584
|
+
parse() {
|
|
585
|
+
const result = row(this.parseRow());
|
|
586
|
+
this.skipWhitespace();
|
|
587
|
+
if (!this.done())
|
|
588
|
+
this.fail(this.peek() === "}" ? "Unexpected closing TeX group" : `Unexpected "${this.peek()}"`);
|
|
589
|
+
return result;
|
|
590
|
+
}
|
|
591
|
+
parseRow(stop) {
|
|
592
|
+
const body = [];
|
|
593
|
+
while (!this.done()) {
|
|
594
|
+
this.skipWhitespace();
|
|
595
|
+
if (this.done() || stop?.() || this.peek() === "}")
|
|
596
|
+
break;
|
|
597
|
+
const char = this.peek();
|
|
598
|
+
if (char === "^" || char === "_") {
|
|
599
|
+
this.offset++;
|
|
600
|
+
const argument = this.parseArgument();
|
|
601
|
+
const previous = body.pop() ?? row([]);
|
|
602
|
+
const scripts2 = previous.type === "scripts" ? previous : { type: "scripts", base: previous };
|
|
603
|
+
if (char === "^")
|
|
604
|
+
scripts2.superscript = argument;
|
|
605
|
+
else
|
|
606
|
+
scripts2.subscript = argument;
|
|
607
|
+
body.push(scripts2);
|
|
608
|
+
} else {
|
|
609
|
+
body.push(this.parseAtom());
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return body;
|
|
613
|
+
}
|
|
614
|
+
parseAtom() {
|
|
615
|
+
this.depth++;
|
|
616
|
+
if (this.depth > MAX_NESTING_DEPTH)
|
|
617
|
+
this.fail(`TeX nesting exceeds the ${MAX_NESTING_DEPTH}-level limit`);
|
|
618
|
+
try {
|
|
619
|
+
if (this.peek() === "{")
|
|
620
|
+
return this.parseGroup();
|
|
621
|
+
if (this.peek() === "\\")
|
|
622
|
+
return this.parseCommand();
|
|
623
|
+
if (this.peek() === "~") {
|
|
624
|
+
this.offset++;
|
|
625
|
+
return { type: "space", width: 1 };
|
|
626
|
+
}
|
|
627
|
+
const value = [...this.source.slice(this.offset)][0] ?? "";
|
|
628
|
+
this.offset += value.length;
|
|
629
|
+
return { type: "symbol", value, role: inferRole(value) };
|
|
630
|
+
} finally {
|
|
631
|
+
this.depth--;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
parseCommand() {
|
|
635
|
+
const start = this.offset;
|
|
636
|
+
const command = this.readCommand();
|
|
637
|
+
if (command === "\\")
|
|
638
|
+
return row([]);
|
|
639
|
+
if (command === "begin")
|
|
640
|
+
return this.parseEnvironment();
|
|
641
|
+
if (command === "end")
|
|
642
|
+
this.fail("Unexpected \\end", start);
|
|
643
|
+
if (["frac", "dfrac", "tfrac", "cfrac"].includes(command)) {
|
|
644
|
+
return { type: "fraction", numerator: this.parseArgument(), denominator: this.parseArgument(), bar: true };
|
|
645
|
+
}
|
|
646
|
+
if (["binom", "dbinom", "tbinom"].includes(command)) {
|
|
647
|
+
return { type: "delimited", left: "(", body: { type: "fraction", numerator: this.parseArgument(), denominator: this.parseArgument(), bar: false }, right: ")" };
|
|
648
|
+
}
|
|
649
|
+
if (command === "sqrt") {
|
|
650
|
+
const index = this.optionalArgument();
|
|
651
|
+
return { type: "root", body: this.parseArgument(), ...index ? { index } : {} };
|
|
652
|
+
}
|
|
653
|
+
if (command === "left")
|
|
654
|
+
return this.parseLeftRight();
|
|
655
|
+
if (command === "right")
|
|
656
|
+
this.fail("Unexpected \\right", start);
|
|
657
|
+
if (command === "middle")
|
|
658
|
+
return { type: "symbol", value: this.readDelimiter() };
|
|
659
|
+
if (command in accents)
|
|
660
|
+
return { type: "accent", accent: accents[command], body: this.parseArgument() };
|
|
661
|
+
if (["mathrm", "mathbf", "mathit", "mathsf", "mathtt", "mathbb", "mathcal", "mathfrak"].includes(command)) {
|
|
662
|
+
return this.parseArgument();
|
|
663
|
+
}
|
|
664
|
+
if (["text", "textrm", "mbox"].includes(command)) {
|
|
665
|
+
return { type: "text", value: this.readRawGroup().replace(/\\([{}%#$&_])/g, "$1").replaceAll("~", " ") };
|
|
666
|
+
}
|
|
667
|
+
if (command === "operatorname")
|
|
668
|
+
return { type: "operator", value: this.readRawGroup(), limits: false };
|
|
669
|
+
if (command === "overset" || command === "stackrel") {
|
|
670
|
+
const over = this.parseArgument();
|
|
671
|
+
return { type: "overunder", over, base: this.parseArgument() };
|
|
672
|
+
}
|
|
673
|
+
if (command === "underset") {
|
|
674
|
+
const under = this.parseArgument();
|
|
675
|
+
return { type: "overunder", under, base: this.parseArgument() };
|
|
676
|
+
}
|
|
677
|
+
if (command === "not") {
|
|
678
|
+
const target = this.parseArgument();
|
|
679
|
+
if (target.type === "symbol")
|
|
680
|
+
return { ...target, value: negate(target.value) };
|
|
681
|
+
return { type: "row", body: [{ type: "symbol", value: "¬" }, target] };
|
|
682
|
+
}
|
|
683
|
+
if (["limits", "nolimits", "displaystyle", "textstyle", "scriptstyle"].includes(command))
|
|
684
|
+
return row([]);
|
|
685
|
+
if (/^(?:big|Big|bigg|Bigg)[lrm]?$/.test(command))
|
|
686
|
+
return { type: "symbol", value: this.readDelimiter() };
|
|
687
|
+
if (command in spacing)
|
|
688
|
+
return { type: "space", width: spacing[command] };
|
|
689
|
+
if (command in symbols)
|
|
690
|
+
return { type: "symbol", ...symbols[command] };
|
|
691
|
+
if (command in operators)
|
|
692
|
+
return { type: "operator", value: operators[command], limits: !command.includes("int") };
|
|
693
|
+
if (namedOperators.has(command))
|
|
694
|
+
return { type: "operator", value: command, limits: ["lim", "min", "max"].includes(command) };
|
|
695
|
+
if (command in delimiters)
|
|
696
|
+
return { type: "symbol", value: delimiters[command] };
|
|
697
|
+
if (["{", "}", "%", "#", "$", "&", "_", "backslash"].includes(command))
|
|
698
|
+
return { type: "symbol", value: command === "backslash" ? "\\" : command };
|
|
699
|
+
return { type: "text", value: `\\${command}` };
|
|
700
|
+
}
|
|
701
|
+
parseEnvironment() {
|
|
702
|
+
const rawName = this.readRawGroup();
|
|
703
|
+
const name = rawName.replace(/\*$/, "");
|
|
704
|
+
if (!environments.has(name))
|
|
705
|
+
this.fail(`Unsupported TeX environment: ${rawName}`);
|
|
706
|
+
if (name === "array") {
|
|
707
|
+
this.skipWhitespace();
|
|
708
|
+
if (this.peek() === "{")
|
|
709
|
+
this.readRawGroup();
|
|
710
|
+
}
|
|
711
|
+
const rows = [];
|
|
712
|
+
let cells = [];
|
|
713
|
+
while (!this.done()) {
|
|
714
|
+
this.skipWhitespace();
|
|
715
|
+
if (this.done())
|
|
716
|
+
break;
|
|
717
|
+
if (this.isEnd(rawName)) {
|
|
718
|
+
this.offset += `\\end{${rawName}}`.length;
|
|
719
|
+
if (cells.length || !rows.length)
|
|
720
|
+
rows.push(cells);
|
|
721
|
+
return { type: "matrix", rows, environment: name };
|
|
722
|
+
}
|
|
723
|
+
const start = this.offset;
|
|
724
|
+
cells.push(row(this.parseRow(() => this.peek() === "&" || this.source.startsWith("\\\\", this.offset) || this.source.startsWith("\\end{", this.offset))));
|
|
725
|
+
if (this.offset === start)
|
|
726
|
+
this.fail(`Unexpected token in ${rawName}`);
|
|
727
|
+
this.skipWhitespace();
|
|
728
|
+
if (this.peek() === "&") {
|
|
729
|
+
this.offset++;
|
|
730
|
+
this.skipWhitespace();
|
|
731
|
+
if (this.incomplete && this.done())
|
|
732
|
+
cells.push(placeholder());
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (this.source.startsWith("\\\\", this.offset)) {
|
|
736
|
+
this.offset += 2;
|
|
737
|
+
this.skipOptionalRowSpacing();
|
|
738
|
+
rows.push(cells);
|
|
739
|
+
cells = [];
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (this.incomplete) {
|
|
744
|
+
if (cells.length)
|
|
745
|
+
rows.push(cells);
|
|
746
|
+
else if (!rows.length)
|
|
747
|
+
rows.push([placeholder()]);
|
|
748
|
+
return { type: "matrix", rows, environment: name };
|
|
749
|
+
}
|
|
750
|
+
this.fail(`Unclosed TeX environment: ${rawName}`);
|
|
751
|
+
}
|
|
752
|
+
parseLeftRight() {
|
|
753
|
+
const left = this.readDelimiter();
|
|
754
|
+
const nodes = this.parseRow(() => this.isCommand("right"));
|
|
755
|
+
const body = row(nodes);
|
|
756
|
+
if (!this.isCommand("right")) {
|
|
757
|
+
if (this.incomplete && this.done())
|
|
758
|
+
return { type: "delimited", left, body: nodes.length ? body : placeholder(), right: "" };
|
|
759
|
+
this.fail("Missing \\right");
|
|
760
|
+
}
|
|
761
|
+
this.readCommand();
|
|
762
|
+
return { type: "delimited", left, body, right: this.readDelimiter() };
|
|
763
|
+
}
|
|
764
|
+
parseArgument() {
|
|
765
|
+
this.skipWhitespace();
|
|
766
|
+
if (this.done()) {
|
|
767
|
+
if (this.incomplete)
|
|
768
|
+
return placeholder();
|
|
769
|
+
this.fail("Expected a TeX argument");
|
|
770
|
+
}
|
|
771
|
+
if (this.peek() === "}")
|
|
772
|
+
this.fail("Unexpected closing TeX group");
|
|
773
|
+
return this.peek() === "{" ? this.parseGroup() : this.parseAtom();
|
|
774
|
+
}
|
|
775
|
+
parseGroup() {
|
|
776
|
+
this.expect("{");
|
|
777
|
+
const nodes = this.parseRow();
|
|
778
|
+
const result = row(nodes);
|
|
779
|
+
if (this.peek() !== "}") {
|
|
780
|
+
if (this.incomplete && this.done())
|
|
781
|
+
return nodes.length ? result : placeholder();
|
|
782
|
+
this.fail("Unclosed TeX group");
|
|
783
|
+
}
|
|
784
|
+
this.offset++;
|
|
785
|
+
return result;
|
|
786
|
+
}
|
|
787
|
+
optionalArgument() {
|
|
788
|
+
this.skipWhitespace();
|
|
789
|
+
if (this.peek() !== "[")
|
|
790
|
+
return;
|
|
791
|
+
this.offset++;
|
|
792
|
+
const nodes = this.parseRow(() => this.peek() === "]");
|
|
793
|
+
const result = row(nodes);
|
|
794
|
+
if (this.peek() !== "]") {
|
|
795
|
+
if (this.incomplete && this.done())
|
|
796
|
+
return nodes.length ? result : placeholder();
|
|
797
|
+
this.fail('Expected "]"');
|
|
798
|
+
}
|
|
799
|
+
this.offset++;
|
|
800
|
+
return result;
|
|
801
|
+
}
|
|
802
|
+
readRawGroup() {
|
|
803
|
+
this.skipWhitespace();
|
|
804
|
+
this.expect("{");
|
|
805
|
+
const start = this.offset;
|
|
806
|
+
let depth = 1;
|
|
807
|
+
while (!this.done()) {
|
|
808
|
+
const char = this.source[this.offset++];
|
|
809
|
+
if (char === "{" && !this.escaped(this.offset - 1))
|
|
810
|
+
depth++;
|
|
811
|
+
else if (char === "}" && !this.escaped(this.offset - 1) && --depth === 0)
|
|
812
|
+
return this.source.slice(start, this.offset - 1);
|
|
813
|
+
if (depth > MAX_NESTING_DEPTH)
|
|
814
|
+
this.fail(`TeX nesting exceeds the ${MAX_NESTING_DEPTH}-level limit`);
|
|
815
|
+
}
|
|
816
|
+
if (this.incomplete)
|
|
817
|
+
return this.source.slice(start) || "□";
|
|
818
|
+
this.fail("Unclosed TeX group", start);
|
|
819
|
+
}
|
|
820
|
+
readCommand() {
|
|
821
|
+
this.expect("\\");
|
|
822
|
+
if (this.done())
|
|
823
|
+
return "\\";
|
|
824
|
+
if (!/[A-Za-z@]/.test(this.peek()))
|
|
825
|
+
return this.source[this.offset++];
|
|
826
|
+
const start = this.offset;
|
|
827
|
+
while (/[A-Za-z@]/.test(this.peek()))
|
|
828
|
+
this.offset++;
|
|
829
|
+
const result = this.source.slice(start, this.offset);
|
|
830
|
+
if (this.peek() === " ")
|
|
831
|
+
this.offset++;
|
|
832
|
+
return result;
|
|
833
|
+
}
|
|
834
|
+
readDelimiter() {
|
|
835
|
+
this.skipWhitespace();
|
|
836
|
+
if (this.done()) {
|
|
837
|
+
if (this.incomplete)
|
|
838
|
+
return "";
|
|
839
|
+
this.fail("Expected a TeX delimiter");
|
|
840
|
+
}
|
|
841
|
+
if (this.peek() === "}")
|
|
842
|
+
this.fail("Unexpected closing TeX group");
|
|
843
|
+
const token = this.peek() === "\\" ? this.readCommand() : this.source[this.offset++] ?? "";
|
|
844
|
+
return delimiters[token] ?? delimiters[`\\${token}`] ?? token;
|
|
845
|
+
}
|
|
846
|
+
skipOptionalRowSpacing() {
|
|
847
|
+
this.skipWhitespace();
|
|
848
|
+
if (this.peek() !== "[")
|
|
849
|
+
return;
|
|
850
|
+
while (!this.done() && this.source[this.offset++] !== "]") {}
|
|
851
|
+
}
|
|
852
|
+
isEnd(name) {
|
|
853
|
+
return this.source.startsWith(`\\end{${name}}`, this.offset);
|
|
854
|
+
}
|
|
855
|
+
isCommand(name) {
|
|
856
|
+
if (!this.source.startsWith(`\\${name}`, this.offset))
|
|
857
|
+
return false;
|
|
858
|
+
return !/[A-Za-z@]/.test(this.source[this.offset + name.length + 1] ?? "");
|
|
859
|
+
}
|
|
860
|
+
skipWhitespace() {
|
|
861
|
+
while (/\s/.test(this.peek()))
|
|
862
|
+
this.offset++;
|
|
863
|
+
}
|
|
864
|
+
done() {
|
|
865
|
+
return this.offset >= this.source.length;
|
|
866
|
+
}
|
|
867
|
+
peek() {
|
|
868
|
+
return this.source[this.offset] ?? "";
|
|
869
|
+
}
|
|
870
|
+
expect(value) {
|
|
871
|
+
if (!this.source.startsWith(value, this.offset))
|
|
872
|
+
this.fail(`Expected "${value}"`);
|
|
873
|
+
this.offset += value.length;
|
|
874
|
+
}
|
|
875
|
+
escaped(index) {
|
|
876
|
+
let count = 0;
|
|
877
|
+
while (index > count && this.source[index - count - 1] === "\\")
|
|
878
|
+
count++;
|
|
879
|
+
return count % 2 === 1;
|
|
880
|
+
}
|
|
881
|
+
fail(message, offset = this.offset) {
|
|
882
|
+
throw new Error(`${message} at offset ${offset}`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
function row(body) {
|
|
886
|
+
return body.length === 1 ? body[0] : { type: "row", body };
|
|
887
|
+
}
|
|
888
|
+
function placeholder() {
|
|
889
|
+
return { type: "symbol", value: "□" };
|
|
890
|
+
}
|
|
891
|
+
function inferRole(value) {
|
|
892
|
+
if ("+-*/×÷±∓".includes(value))
|
|
893
|
+
return "binary";
|
|
894
|
+
if ("=<>≤≥≠≈∈∉⊂⊃".includes(value))
|
|
895
|
+
return "relation";
|
|
896
|
+
if (",;:".includes(value))
|
|
897
|
+
return "punctuation";
|
|
898
|
+
if ("([{".includes(value))
|
|
899
|
+
return "opening";
|
|
900
|
+
if (")]}".includes(value))
|
|
901
|
+
return "closing";
|
|
902
|
+
return "ordinary";
|
|
903
|
+
}
|
|
904
|
+
function negate(value) {
|
|
905
|
+
return { "=": "≠", "<": "≮", ">": "≯", "≤": "≰", "≥": "≱", "∈": "∉", "∋": "∌", "⊂": "⊄", "⊃": "⊅" }[value] ?? `${value}̸`;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// src/unicode-tex-backend.ts
|
|
909
|
+
import stringWidth2 from "string-width";
|
|
910
|
+
var UNICODE_TEX_SOURCE_LENGTH_MAX = 4096;
|
|
911
|
+
var OUTPUT_LENGTH_MAX = 16384;
|
|
912
|
+
|
|
913
|
+
class UnicodeTexBackend {
|
|
914
|
+
renderSync(request) {
|
|
915
|
+
return renderUnicode(request, false);
|
|
916
|
+
}
|
|
917
|
+
async render(request) {
|
|
918
|
+
return this.renderSync(request);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
function renderIncompleteUnicode(request) {
|
|
922
|
+
return renderUnicode(request, true);
|
|
923
|
+
}
|
|
924
|
+
function renderUnicode(request, incomplete) {
|
|
925
|
+
assertNotAborted(request.signal);
|
|
926
|
+
const sourceBytes = Buffer.byteLength(request.formula, "utf8");
|
|
927
|
+
if (sourceBytes === 0 || sourceBytes > UNICODE_TEX_SOURCE_LENGTH_MAX) {
|
|
928
|
+
throw new Error(`TeX formula must be between 1 and ${UNICODE_TEX_SOURCE_LENGTH_MAX} UTF-8 bytes`);
|
|
929
|
+
}
|
|
930
|
+
if (!Number.isFinite(request.widthMax) || !Number.isFinite(request.heightMax) || request.widthMax < 1 || request.heightMax < 1) {
|
|
931
|
+
throw new Error("Unicode TeX dimensions must be finite positive numbers");
|
|
932
|
+
}
|
|
933
|
+
const widthMax = Math.floor(request.widthMax);
|
|
934
|
+
const heightMax = Math.floor(request.heightMax);
|
|
935
|
+
const node = incomplete ? parseMathIncomplete(request.formula) : parseMath(request.formula);
|
|
936
|
+
const text = boxToString(layoutMath(node, request.display), widthMax, heightMax);
|
|
937
|
+
assertNotAborted(request.signal);
|
|
938
|
+
if (!text)
|
|
939
|
+
throw new Error("TeX formula produced no Unicode output");
|
|
940
|
+
if (text.length > OUTPUT_LENGTH_MAX)
|
|
941
|
+
throw new Error(`Unicode TeX output exceeds ${OUTPUT_LENGTH_MAX} characters`);
|
|
942
|
+
const lines = text.split(`
|
|
943
|
+
`);
|
|
944
|
+
return {
|
|
945
|
+
kind: "unicode",
|
|
946
|
+
text,
|
|
947
|
+
columns: Math.max(1, ...lines.map((line) => stringWidth2(line))),
|
|
948
|
+
rows: lines.length
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
function assertNotAborted(signal) {
|
|
952
|
+
if (signal.aborted)
|
|
953
|
+
throw signal.reason ?? new Error("Unicode render cancelled");
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// src/tex-renderable.ts
|
|
957
|
+
var NATIVE_SUPERSAMPLE = 4;
|
|
958
|
+
var RESIZE_AREA_THRESHOLD = 1.3;
|
|
959
|
+
var DEFAULT_PREVIEW_BACKEND = new UnicodeTexBackend;
|
|
960
|
+
function dimensionMax(value) {
|
|
961
|
+
if (!Number.isFinite(value))
|
|
962
|
+
throw new Error("TeX dimensions must be finite");
|
|
963
|
+
return Math.max(1, Math.floor(value));
|
|
964
|
+
}
|
|
965
|
+
function measureTex(width, height, display, widthMax, heightMax) {
|
|
966
|
+
let rows = Math.max(1, Math.ceil(height / NATIVE_SUPERSAMPLE / (display ? 10 : 12)));
|
|
967
|
+
let columns = Math.max(1, Math.round(width / height * rows * 2));
|
|
968
|
+
if (columns > widthMax) {
|
|
969
|
+
rows = Math.max(1, Math.round(rows * widthMax / columns));
|
|
970
|
+
columns = widthMax;
|
|
971
|
+
}
|
|
972
|
+
if (rows > heightMax) {
|
|
973
|
+
columns = Math.max(1, Math.round(columns * heightMax / rows));
|
|
974
|
+
rows = heightMax;
|
|
975
|
+
}
|
|
976
|
+
return { columns, rows };
|
|
977
|
+
}
|
|
978
|
+
function fitImageToPlacement(imageWidth, imageHeight, columns, rows, cellPxWidth, cellPxHeight) {
|
|
979
|
+
const boxWidth = Math.max(1, Math.floor(columns * cellPxWidth));
|
|
980
|
+
const boxHeight = Math.max(1, Math.floor(rows * cellPxHeight));
|
|
981
|
+
const scale = Math.min(boxWidth / imageWidth, boxHeight / imageHeight);
|
|
982
|
+
if (scale >= 1)
|
|
983
|
+
return null;
|
|
984
|
+
const width = Math.max(1, Math.round(imageWidth * scale));
|
|
985
|
+
const height = Math.max(1, Math.round(imageHeight * scale));
|
|
986
|
+
return imageWidth * imageHeight > width * height * RESIZE_AREA_THRESHOLD ? { width, height } : null;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
class TexRenderable extends BoxRenderable {
|
|
990
|
+
ready;
|
|
991
|
+
backend;
|
|
992
|
+
fallback;
|
|
993
|
+
widthMax;
|
|
994
|
+
heightMax;
|
|
995
|
+
autoWidth;
|
|
996
|
+
autoHeight;
|
|
997
|
+
trackExplicitDimensions = false;
|
|
998
|
+
currentDimensions = { columns: 1, rows: 1 };
|
|
999
|
+
imageOptions;
|
|
1000
|
+
onError;
|
|
1001
|
+
_formula;
|
|
1002
|
+
_foreground;
|
|
1003
|
+
_background;
|
|
1004
|
+
_display;
|
|
1005
|
+
_streaming;
|
|
1006
|
+
controller = null;
|
|
1007
|
+
committedOutput = null;
|
|
1008
|
+
constructor(context, options) {
|
|
1009
|
+
const {
|
|
1010
|
+
formula,
|
|
1011
|
+
display = false,
|
|
1012
|
+
foreground,
|
|
1013
|
+
background,
|
|
1014
|
+
widthMax = 80,
|
|
1015
|
+
heightMax = 24,
|
|
1016
|
+
backend,
|
|
1017
|
+
fallback = "message",
|
|
1018
|
+
imageOptions,
|
|
1019
|
+
onError,
|
|
1020
|
+
streaming = false,
|
|
1021
|
+
...boxOptions
|
|
1022
|
+
} = options;
|
|
1023
|
+
super(context, {
|
|
1024
|
+
shouldFill: false,
|
|
1025
|
+
...boxOptions,
|
|
1026
|
+
width: options.width ?? 1,
|
|
1027
|
+
height: options.height ?? 1
|
|
1028
|
+
});
|
|
1029
|
+
this._formula = formula;
|
|
1030
|
+
this._foreground = foreground;
|
|
1031
|
+
this._background = background;
|
|
1032
|
+
this._streaming = streaming;
|
|
1033
|
+
this._display = display;
|
|
1034
|
+
this.backend = backend;
|
|
1035
|
+
this.fallback = fallback;
|
|
1036
|
+
this.widthMax = dimensionMax(widthMax);
|
|
1037
|
+
this.heightMax = dimensionMax(heightMax);
|
|
1038
|
+
this.autoWidth = options.width === undefined;
|
|
1039
|
+
this.autoHeight = options.height === undefined;
|
|
1040
|
+
this.trackExplicitDimensions = true;
|
|
1041
|
+
this.imageOptions = imageOptions;
|
|
1042
|
+
this.onError = onError;
|
|
1043
|
+
this.ready = this.update(formula, foreground, background, display);
|
|
1044
|
+
}
|
|
1045
|
+
get formula() {
|
|
1046
|
+
return this._formula;
|
|
1047
|
+
}
|
|
1048
|
+
set formula(value) {
|
|
1049
|
+
this.setSnapshot(value, this._foreground, this._background);
|
|
1050
|
+
}
|
|
1051
|
+
get display() {
|
|
1052
|
+
return this._display;
|
|
1053
|
+
}
|
|
1054
|
+
set display(value) {
|
|
1055
|
+
this.setSnapshot(this._formula, this._foreground, this._background, value === true);
|
|
1056
|
+
}
|
|
1057
|
+
get width() {
|
|
1058
|
+
return super.width;
|
|
1059
|
+
}
|
|
1060
|
+
set width(value) {
|
|
1061
|
+
if (this.trackExplicitDimensions && (value === "auto" || value == null)) {
|
|
1062
|
+
this.autoWidth = true;
|
|
1063
|
+
super.width = this.currentDimensions.columns;
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
super.width = value;
|
|
1067
|
+
if (this.trackExplicitDimensions)
|
|
1068
|
+
this.autoWidth = false;
|
|
1069
|
+
}
|
|
1070
|
+
get height() {
|
|
1071
|
+
return super.height;
|
|
1072
|
+
}
|
|
1073
|
+
set height(value) {
|
|
1074
|
+
if (this.trackExplicitDimensions && (value === "auto" || value == null)) {
|
|
1075
|
+
this.autoHeight = true;
|
|
1076
|
+
super.height = this.currentDimensions.rows;
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
super.height = value;
|
|
1080
|
+
if (this.trackExplicitDimensions)
|
|
1081
|
+
this.autoHeight = false;
|
|
1082
|
+
}
|
|
1083
|
+
get streaming() {
|
|
1084
|
+
return this._streaming;
|
|
1085
|
+
}
|
|
1086
|
+
set streaming(value) {
|
|
1087
|
+
if (value === this._streaming)
|
|
1088
|
+
return;
|
|
1089
|
+
this._streaming = value;
|
|
1090
|
+
if (value) {
|
|
1091
|
+
this.controller?.abort();
|
|
1092
|
+
this.controller = null;
|
|
1093
|
+
this.ready = Promise.resolve();
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
this.ready = this.update(this._formula, this._foreground, this._background, this._display);
|
|
1097
|
+
}
|
|
1098
|
+
setColors(foreground, background) {
|
|
1099
|
+
this.setSnapshot(this._formula, foreground, background);
|
|
1100
|
+
}
|
|
1101
|
+
setSnapshot(formula, foreground, background, display = this._display) {
|
|
1102
|
+
if (formula === this._formula && foreground === this._foreground && background === this._background && display === this._display)
|
|
1103
|
+
return;
|
|
1104
|
+
this.ready = this.update(formula, foreground, background, display);
|
|
1105
|
+
}
|
|
1106
|
+
async whenReady() {
|
|
1107
|
+
while (!this.isDestroyed) {
|
|
1108
|
+
const pending = this.ready;
|
|
1109
|
+
const controller = this.controller;
|
|
1110
|
+
let onAbort;
|
|
1111
|
+
const changed = new Promise((resolve) => {
|
|
1112
|
+
if (controller) {
|
|
1113
|
+
onAbort = () => resolve();
|
|
1114
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
try {
|
|
1118
|
+
await Promise.race([pending, changed]);
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
if (pending === this.ready)
|
|
1121
|
+
throw error;
|
|
1122
|
+
continue;
|
|
1123
|
+
} finally {
|
|
1124
|
+
if (onAbort)
|
|
1125
|
+
controller?.signal.removeEventListener("abort", onAbort);
|
|
1126
|
+
}
|
|
1127
|
+
if (pending === this.ready)
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
async update(formula, foreground, background, display) {
|
|
1132
|
+
this.controller?.abort();
|
|
1133
|
+
this._formula = formula;
|
|
1134
|
+
this._foreground = foreground;
|
|
1135
|
+
this._background = background;
|
|
1136
|
+
this._display = display;
|
|
1137
|
+
if (!formula) {
|
|
1138
|
+
this.clearOutput();
|
|
1139
|
+
if (!this._streaming) {
|
|
1140
|
+
disposeOutput(this.committedOutput);
|
|
1141
|
+
this.committedOutput = null;
|
|
1142
|
+
}
|
|
1143
|
+
this.controller = null;
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const controller = new AbortController;
|
|
1147
|
+
this.controller = controller;
|
|
1148
|
+
const request = {
|
|
1149
|
+
formula,
|
|
1150
|
+
display,
|
|
1151
|
+
foreground,
|
|
1152
|
+
background,
|
|
1153
|
+
widthMax: this.widthMax,
|
|
1154
|
+
heightMax: this.heightMax,
|
|
1155
|
+
signal: controller.signal
|
|
1156
|
+
};
|
|
1157
|
+
if (this._streaming) {
|
|
1158
|
+
this.applyOutput(this.previewOutput(request));
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
let unicodeOutput = null;
|
|
1162
|
+
try {
|
|
1163
|
+
unicodeOutput = DEFAULT_PREVIEW_BACKEND.renderSync(request);
|
|
1164
|
+
this.applyOutput(unicodeOutput);
|
|
1165
|
+
} catch {}
|
|
1166
|
+
try {
|
|
1167
|
+
const output = await this.backend.render(request);
|
|
1168
|
+
if (controller.signal.aborted || this.isDestroyed) {
|
|
1169
|
+
disposeOutput(output);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
if (this.fallback === "retain") {
|
|
1173
|
+
const previous = this.committedOutput;
|
|
1174
|
+
try {
|
|
1175
|
+
this.applyOutput(output);
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
disposeOutput(output);
|
|
1178
|
+
throw error;
|
|
1179
|
+
}
|
|
1180
|
+
this.committedOutput = output;
|
|
1181
|
+
disposeOutput(previous);
|
|
1182
|
+
} else {
|
|
1183
|
+
try {
|
|
1184
|
+
this.applyOutput(output);
|
|
1185
|
+
} finally {
|
|
1186
|
+
disposeOutput(output);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
if (!controller.signal.aborted && !this.isDestroyed) {
|
|
1191
|
+
if (this.fallback === "retain" && this.committedOutput)
|
|
1192
|
+
this.applyOutput(this.committedOutput);
|
|
1193
|
+
else if (this.fallback === "message" || this.fallback === "unicode" && !unicodeOutput) {
|
|
1194
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1195
|
+
const text = fitLine(`[TeX error: ${message}]`, this.widthMax);
|
|
1196
|
+
this.applyOutput({ kind: "unicode", text, columns: Math.max(1, stringWidth3(text)), rows: 1 });
|
|
1197
|
+
}
|
|
1198
|
+
this.onError?.(error);
|
|
1199
|
+
if (this.fallback === "throw")
|
|
1200
|
+
throw error;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
previewOutput(request) {
|
|
1205
|
+
if (request.formula.length <= UNICODE_TEX_SOURCE_LENGTH_MAX && Buffer.byteLength(request.formula, "utf8") <= UNICODE_TEX_SOURCE_LENGTH_MAX) {
|
|
1206
|
+
try {
|
|
1207
|
+
return DEFAULT_PREVIEW_BACKEND.renderSync(request);
|
|
1208
|
+
} catch {
|
|
1209
|
+
try {
|
|
1210
|
+
return renderIncompleteUnicode(request);
|
|
1211
|
+
} catch {}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return rawSourceOutput(request.formula, this.widthMax, this.heightMax);
|
|
1215
|
+
}
|
|
1216
|
+
clearOutput() {
|
|
1217
|
+
for (const existing of this.getChildren())
|
|
1218
|
+
existing.destroyRecursively();
|
|
1219
|
+
this.currentDimensions = { columns: 1, rows: 1 };
|
|
1220
|
+
if (this.autoWidth)
|
|
1221
|
+
super.width = 1;
|
|
1222
|
+
if (this.autoHeight)
|
|
1223
|
+
super.height = 1;
|
|
1224
|
+
}
|
|
1225
|
+
applyOutput(output) {
|
|
1226
|
+
const dimensions = output.kind === "image" ? measureTex(output.image.width, output.image.height, this.display, this.widthMax, this.heightMax) : { columns: Math.min(this.widthMax, output.columns), rows: Math.min(this.heightMax, output.rows) };
|
|
1227
|
+
const child = output.kind === "image" ? this.createImageChild(output.image, dimensions) : new TextRenderable(this._ctx, {
|
|
1228
|
+
content: output.text,
|
|
1229
|
+
fg: this._foreground,
|
|
1230
|
+
bg: this._background,
|
|
1231
|
+
width: "100%",
|
|
1232
|
+
height: "100%"
|
|
1233
|
+
});
|
|
1234
|
+
let added = false;
|
|
1235
|
+
try {
|
|
1236
|
+
this.clearOutput();
|
|
1237
|
+
this.currentDimensions = dimensions;
|
|
1238
|
+
if (this.autoWidth)
|
|
1239
|
+
super.width = Math.max(1, dimensions.columns);
|
|
1240
|
+
if (this.autoHeight)
|
|
1241
|
+
super.height = Math.max(1, dimensions.rows);
|
|
1242
|
+
this.add(child);
|
|
1243
|
+
added = true;
|
|
1244
|
+
} finally {
|
|
1245
|
+
if (!added)
|
|
1246
|
+
child.destroyRecursively();
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
createImageChild(image, dimensions) {
|
|
1250
|
+
const resized = this.resizeToPlacement(image, dimensions);
|
|
1251
|
+
const source = resized ?? image;
|
|
1252
|
+
if (this._ctx.capabilities?.kitty_graphics) {
|
|
1253
|
+
try {
|
|
1254
|
+
source.ensureEncodedPng();
|
|
1255
|
+
} catch {}
|
|
1256
|
+
}
|
|
1257
|
+
try {
|
|
1258
|
+
return new ImageRenderable(this._ctx, {
|
|
1259
|
+
protocol: "auto",
|
|
1260
|
+
fit: "fit",
|
|
1261
|
+
...this.imageOptions,
|
|
1262
|
+
source,
|
|
1263
|
+
width: "100%",
|
|
1264
|
+
height: "100%"
|
|
1265
|
+
});
|
|
1266
|
+
} finally {
|
|
1267
|
+
resized?.dispose();
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
resizeToPlacement(image, dimensions) {
|
|
1271
|
+
const { terminalWidth, terminalHeight, resolution } = this._ctx;
|
|
1272
|
+
if (!terminalWidth || !terminalHeight || !resolution?.width || !resolution.height)
|
|
1273
|
+
return null;
|
|
1274
|
+
const target = fitImageToPlacement(image.width, image.height, dimensions.columns, dimensions.rows, resolution.width / terminalWidth, resolution.height / terminalHeight);
|
|
1275
|
+
if (!target)
|
|
1276
|
+
return null;
|
|
1277
|
+
try {
|
|
1278
|
+
return image.resize({ ...target, kernel: "area" });
|
|
1279
|
+
} catch {
|
|
1280
|
+
return null;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
destroySelf() {
|
|
1284
|
+
this.controller?.abort();
|
|
1285
|
+
this.controller = null;
|
|
1286
|
+
disposeOutput(this.committedOutput);
|
|
1287
|
+
this.committedOutput = null;
|
|
1288
|
+
super.destroySelf();
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function disposeOutput(output) {
|
|
1292
|
+
if (output?.kind === "image")
|
|
1293
|
+
output.image.dispose();
|
|
1294
|
+
}
|
|
1295
|
+
function fitLine(value, widthMax) {
|
|
1296
|
+
let output = "";
|
|
1297
|
+
for (const character of value) {
|
|
1298
|
+
if (stringWidth3(output + character) > widthMax)
|
|
1299
|
+
break;
|
|
1300
|
+
output += character;
|
|
1301
|
+
}
|
|
1302
|
+
return output || "?";
|
|
1303
|
+
}
|
|
1304
|
+
function rawSourceOutput(source, widthMax, heightMax) {
|
|
1305
|
+
const cellLimit = Math.min(16384, widthMax * heightMax);
|
|
1306
|
+
const tailStart = Math.max(0, source.length - cellLimit * 2);
|
|
1307
|
+
let tail = source.slice(tailStart);
|
|
1308
|
+
if (tailStart > 0) {
|
|
1309
|
+
const restart = safeRawRestart(tail);
|
|
1310
|
+
tail = restart < 0 ? "" : tail.slice(restart);
|
|
1311
|
+
}
|
|
1312
|
+
const tailGraphemes = graphemes(tail);
|
|
1313
|
+
const lines = [];
|
|
1314
|
+
let line = "";
|
|
1315
|
+
let width = 0;
|
|
1316
|
+
for (const sourceGrapheme of tailGraphemes) {
|
|
1317
|
+
const visible2 = [...sourceGrapheme].map(visibleSourceCharacter).join("");
|
|
1318
|
+
const segments = visible2 === sourceGrapheme ? [visible2] : [...visible2];
|
|
1319
|
+
for (const character of segments) {
|
|
1320
|
+
const characterWidth = stringWidth3(character);
|
|
1321
|
+
if (characterWidth > widthMax) {
|
|
1322
|
+
if (line)
|
|
1323
|
+
lines.push(line);
|
|
1324
|
+
lines.push("?");
|
|
1325
|
+
line = "";
|
|
1326
|
+
width = 0;
|
|
1327
|
+
} else if (width + characterWidth > widthMax) {
|
|
1328
|
+
lines.push(line);
|
|
1329
|
+
line = character;
|
|
1330
|
+
width = characterWidth;
|
|
1331
|
+
} else {
|
|
1332
|
+
line += character;
|
|
1333
|
+
width += characterWidth;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
if (line)
|
|
1338
|
+
lines.push(line);
|
|
1339
|
+
const visibleLines = lines.slice(-heightMax);
|
|
1340
|
+
const widths = visibleLines.map((value) => stringWidth3(value));
|
|
1341
|
+
const visible = visibleLines.some((value, index) => /\S/u.test(value) && widths[index] > 0);
|
|
1342
|
+
const text = visible ? visibleLines.join(`
|
|
1343
|
+
`) : "?";
|
|
1344
|
+
return {
|
|
1345
|
+
kind: "unicode",
|
|
1346
|
+
text,
|
|
1347
|
+
columns: visible ? Math.max(1, ...widths) : 1,
|
|
1348
|
+
rows: visible ? Math.max(1, visibleLines.length) : 1
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
function graphemes(value) {
|
|
1352
|
+
return typeof Intl.Segmenter === "function" ? [...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(value)].map((part) => part.segment) : [...value];
|
|
1353
|
+
}
|
|
1354
|
+
function safeRawRestart(value) {
|
|
1355
|
+
for (let index = 1;index < value.length; index++) {
|
|
1356
|
+
if (isPrintableAscii(value.charCodeAt(index - 1)) && isPrintableAscii(value.charCodeAt(index)))
|
|
1357
|
+
return index;
|
|
1358
|
+
}
|
|
1359
|
+
return -1;
|
|
1360
|
+
}
|
|
1361
|
+
function isPrintableAscii(value) {
|
|
1362
|
+
return value >= 32 && value <= 126;
|
|
1363
|
+
}
|
|
1364
|
+
function visibleSourceCharacter(character) {
|
|
1365
|
+
if (character === `
|
|
1366
|
+
`)
|
|
1367
|
+
return "\\n";
|
|
1368
|
+
if (character === "\r")
|
|
1369
|
+
return "\\r";
|
|
1370
|
+
if (character === "\t")
|
|
1371
|
+
return "\\t";
|
|
1372
|
+
const code = character.codePointAt(0);
|
|
1373
|
+
return code < 32 || code >= 127 && code <= 159 ? `\\x${code.toString(16).padStart(2, "0")}` : character;
|
|
1374
|
+
}
|
|
1375
|
+
export {
|
|
1376
|
+
measureTex,
|
|
1377
|
+
UnicodeTexBackend,
|
|
1378
|
+
TexRenderable
|
|
1379
|
+
};
|