@hncode/hncode 0.5.6 → 0.6.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/package.json +37 -38
- package/src/agent.js +67 -2
- package/src/image-row.js +111 -0
- package/src/latex.js +1557 -0
- package/src/llm.js +19 -4
- package/src/mermaid/ansi.js +55 -0
- package/src/mermaid/canvas.js +449 -0
- package/src/mermaid/class-style.js +57 -0
- package/src/mermaid/css-colors.js +153 -0
- package/src/mermaid/diagrams/class.js +289 -0
- package/src/mermaid/diagrams/er.js +201 -0
- package/src/mermaid/diagrams/flowchart.js +488 -0
- package/src/mermaid/diagrams/gitgraph.js +205 -0
- package/src/mermaid/diagrams/mindmap.js +101 -0
- package/src/mermaid/diagrams/pie.js +104 -0
- package/src/mermaid/diagrams/sequence.js +282 -0
- package/src/mermaid/diagrams/state.js +292 -0
- package/src/mermaid/diagrams/timeline.js +99 -0
- package/src/mermaid/graph.js +138 -0
- package/src/mermaid/index.js +59 -0
- package/src/mermaid/labels.js +339 -0
- package/src/mermaid/layout-seq.js +225 -0
- package/src/mermaid/layout.js +1454 -0
- package/src/mermaid/registry.js +54 -0
- package/src/mermaid/source-box.js +102 -0
- package/src/mermaid/statements.js +235 -0
- package/src/mermaid/types.js +1 -0
- package/src/mermaid/width.js +29 -0
- package/src/screen-diff.js +512 -0
- package/src/screen-paint.js +128 -0
- package/src/screen.js +277 -0
- package/src/subagent-types.js +136 -147
- package/src/term-caps.js +10 -4
- package/src/tex/font.js +292 -0
- package/src/tex/fonts/LICENSE.txt +11 -0
- package/src/tex/fonts/STIX2Math.ttf +0 -0
- package/src/tex/index.js +293 -0
- package/src/tex/kitty.js +186 -0
- package/src/tex/layout.js +446 -0
- package/src/tex/parse.js +331 -0
- package/src/tex/raster.js +216 -0
- package/src/tex/render.js +167 -0
- package/src/tex/sixel.js +326 -0
- package/src/tex/svg.js +237 -0
- package/src/tool-result.js +2 -2
- package/src/tools/agent-swarm.js +3 -15
- package/src/tools/agent.js +454 -464
- package/src/tools/ask-user-question.js +195 -206
- package/src/tools/bash.js +39 -3
- package/src/tools/edit.js +10 -112
- package/src/tools/fetch-url.js +130 -120
- package/src/tools/memory.js +80 -88
- package/src/tools/read.js +27 -23
- package/src/tools/todo.js +8 -13
- package/src/tools/validate-args.js +78 -0
- package/src/tui.js +1346 -502
package/src/latex.js
ADDED
|
@@ -0,0 +1,1557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LaTeX math -> terminal Unicode text.
|
|
3
|
+
*
|
|
4
|
+
* A TeX subset rendered as text: symbols, scripts, stacked fractions, roots, operator limits, matrices,
|
|
5
|
+
* cases, aligned rows and accents. The layout is real geometry, not a flattened one-liner: a fraction is
|
|
6
|
+
* three rows, and in display mode a sum carries its limits above and below.
|
|
7
|
+
*
|
|
8
|
+
* This is the text renderer. A terminal that can show a bitmap gets the TYPESET version instead
|
|
9
|
+
* (`src/tex/`), and this one is the fallback for one that cannot.
|
|
10
|
+
*
|
|
11
|
+
* `renderLatex` returns undefined when the source is outside the subset or malformed; the caller then shows
|
|
12
|
+
* the ORIGINAL text, because a half-rendered formula reads worse than the formula and the reader may want
|
|
13
|
+
* the TeX.
|
|
14
|
+
*
|
|
15
|
+
* VENDORED — do not edit this file. It is generated by `tools/build-latex.mjs` from
|
|
16
|
+
* `D:/dsh-tui/src/terminal-utils/latex.ts`, which is Pi's renderer (packages/tui/src/latex.ts, MIT,
|
|
17
|
+
* Copyright (c) 2025 Mario Zechner). Only the imports and the two exports below are this project's.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
MIT License
|
|
22
|
+
*
|
|
23
|
+
* Copyright (c) 2025 Mario Zechner
|
|
24
|
+
*
|
|
25
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
26
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
27
|
+
* in the Software without restriction, including without limitation the rights
|
|
28
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
29
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
30
|
+
* furnished to do so, subject to the following conditions:
|
|
31
|
+
*
|
|
32
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
33
|
+
* copies or substantial portions of the Software.
|
|
34
|
+
*
|
|
35
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
36
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
37
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
38
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
39
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
40
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
41
|
+
* SOFTWARE.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { visualWidth as visibleWidth } from "./term.js";
|
|
45
|
+
|
|
46
|
+
const SYMBOLS = {
|
|
47
|
+
alpha: "α",
|
|
48
|
+
beta: "β",
|
|
49
|
+
gamma: "γ",
|
|
50
|
+
delta: "δ",
|
|
51
|
+
epsilon: "ϵ",
|
|
52
|
+
varepsilon: "ε",
|
|
53
|
+
zeta: "ζ",
|
|
54
|
+
eta: "η",
|
|
55
|
+
theta: "θ",
|
|
56
|
+
vartheta: "ϑ",
|
|
57
|
+
iota: "ι",
|
|
58
|
+
kappa: "κ",
|
|
59
|
+
varkappa: "ϰ",
|
|
60
|
+
lambda: "λ",
|
|
61
|
+
mu: "μ",
|
|
62
|
+
nu: "ν",
|
|
63
|
+
xi: "ξ",
|
|
64
|
+
pi: "π",
|
|
65
|
+
varpi: "ϖ",
|
|
66
|
+
rho: "ρ",
|
|
67
|
+
varrho: "ϱ",
|
|
68
|
+
sigma: "σ",
|
|
69
|
+
varsigma: "ς",
|
|
70
|
+
tau: "τ",
|
|
71
|
+
upsilon: "υ",
|
|
72
|
+
phi: "ϕ",
|
|
73
|
+
varphi: "φ",
|
|
74
|
+
chi: "χ",
|
|
75
|
+
psi: "ψ",
|
|
76
|
+
omega: "ω",
|
|
77
|
+
Gamma: "Γ",
|
|
78
|
+
Delta: "Δ",
|
|
79
|
+
Theta: "Θ",
|
|
80
|
+
Lambda: "Λ",
|
|
81
|
+
Xi: "Ξ",
|
|
82
|
+
Pi: "Π",
|
|
83
|
+
Sigma: "Σ",
|
|
84
|
+
Upsilon: "Υ",
|
|
85
|
+
Phi: "Φ",
|
|
86
|
+
Psi: "Ψ",
|
|
87
|
+
Omega: "Ω",
|
|
88
|
+
pm: "±",
|
|
89
|
+
mp: "∓",
|
|
90
|
+
times: "×",
|
|
91
|
+
div: "÷",
|
|
92
|
+
cdot: "·",
|
|
93
|
+
ast: "∗",
|
|
94
|
+
star: "⋆",
|
|
95
|
+
circ: "∘",
|
|
96
|
+
bullet: "•",
|
|
97
|
+
oplus: "⊕",
|
|
98
|
+
ominus: "⊖",
|
|
99
|
+
otimes: "⊗",
|
|
100
|
+
oslash: "⊘",
|
|
101
|
+
odot: "⊙",
|
|
102
|
+
bigcirc: "○",
|
|
103
|
+
dagger: "†",
|
|
104
|
+
ddagger: "‡",
|
|
105
|
+
amalg: "⨿",
|
|
106
|
+
uplus: "⊎",
|
|
107
|
+
sqcap: "⊓",
|
|
108
|
+
sqcup: "⊔",
|
|
109
|
+
bowtie: "⋈",
|
|
110
|
+
Join: "⋈",
|
|
111
|
+
ltimes: "⋉",
|
|
112
|
+
rtimes: "⋊",
|
|
113
|
+
leftouterjoin: "⟕",
|
|
114
|
+
rightouterjoin: "⟖",
|
|
115
|
+
fullouterjoin: "⟗",
|
|
116
|
+
triangleleft: "◁",
|
|
117
|
+
triangleright: "▷",
|
|
118
|
+
wr: "≀",
|
|
119
|
+
cap: "∩",
|
|
120
|
+
cup: "∪",
|
|
121
|
+
bigcap: "⋂",
|
|
122
|
+
bigcup: "⋃",
|
|
123
|
+
bigwedge: "⋀",
|
|
124
|
+
bigvee: "⋁",
|
|
125
|
+
bigsqcup: "⨆",
|
|
126
|
+
biguplus: "⨄",
|
|
127
|
+
bigoplus: "⨁",
|
|
128
|
+
bigotimes: "⨂",
|
|
129
|
+
bigodot: "⨀",
|
|
130
|
+
setminus: "∖",
|
|
131
|
+
in: "∈",
|
|
132
|
+
notin: "∉",
|
|
133
|
+
ni: "∋",
|
|
134
|
+
subset: "⊂",
|
|
135
|
+
supset: "⊃",
|
|
136
|
+
subseteq: "⊆",
|
|
137
|
+
supseteq: "⊇",
|
|
138
|
+
sqsubset: "⊏",
|
|
139
|
+
sqsupset: "⊐",
|
|
140
|
+
sqsubseteq: "⊑",
|
|
141
|
+
sqsupseteq: "⊒",
|
|
142
|
+
prec: "≺",
|
|
143
|
+
preceq: "≼",
|
|
144
|
+
succ: "≻",
|
|
145
|
+
succeq: "≽",
|
|
146
|
+
ll: "≪",
|
|
147
|
+
gg: "≫",
|
|
148
|
+
le: "≤",
|
|
149
|
+
leq: "≤",
|
|
150
|
+
leqslant: "≤",
|
|
151
|
+
ge: "≥",
|
|
152
|
+
geq: "≥",
|
|
153
|
+
geqslant: "≥",
|
|
154
|
+
ne: "≠",
|
|
155
|
+
neq: "≠",
|
|
156
|
+
equiv: "≡",
|
|
157
|
+
approx: "≈",
|
|
158
|
+
sim: "∼",
|
|
159
|
+
simeq: "≃",
|
|
160
|
+
cong: "≅",
|
|
161
|
+
asymp: "≍",
|
|
162
|
+
doteq: "≐",
|
|
163
|
+
propto: "∝",
|
|
164
|
+
parallel: "∥",
|
|
165
|
+
perp: "⊥",
|
|
166
|
+
mid: "∣",
|
|
167
|
+
vdash: "⊢",
|
|
168
|
+
dashv: "⊣",
|
|
169
|
+
models: "⊨",
|
|
170
|
+
Vdash: "⊩",
|
|
171
|
+
Vvdash: "⊪",
|
|
172
|
+
nvdash: "⊬",
|
|
173
|
+
nvDash: "⊭",
|
|
174
|
+
forall: "∀",
|
|
175
|
+
exists: "∃",
|
|
176
|
+
nexists: "∄",
|
|
177
|
+
neg: "¬",
|
|
178
|
+
land: "∧",
|
|
179
|
+
wedge: "∧",
|
|
180
|
+
lor: "∨",
|
|
181
|
+
vee: "∨",
|
|
182
|
+
to: "→",
|
|
183
|
+
rightarrow: "→",
|
|
184
|
+
longrightarrow: "→",
|
|
185
|
+
leftarrow: "←",
|
|
186
|
+
longleftarrow: "←",
|
|
187
|
+
gets: "←",
|
|
188
|
+
leftrightarrow: "↔",
|
|
189
|
+
longleftrightarrow: "↔",
|
|
190
|
+
hookleftarrow: "↩",
|
|
191
|
+
hookrightarrow: "↪",
|
|
192
|
+
twoheadleftarrow: "↞",
|
|
193
|
+
twoheadrightarrow: "↠",
|
|
194
|
+
leftharpoonup: "↼",
|
|
195
|
+
leftharpoondown: "↽",
|
|
196
|
+
rightharpoonup: "⇀",
|
|
197
|
+
rightharpoondown: "⇁",
|
|
198
|
+
rightleftharpoons: "⇌",
|
|
199
|
+
leftrightharpoons: "⇋",
|
|
200
|
+
nearrow: "↗",
|
|
201
|
+
searrow: "↘",
|
|
202
|
+
swarrow: "↙",
|
|
203
|
+
nwarrow: "↖",
|
|
204
|
+
rightsquigarrow: "⇝",
|
|
205
|
+
leadsto: "⇝",
|
|
206
|
+
Rightarrow: "⇒",
|
|
207
|
+
Longrightarrow: "⇒",
|
|
208
|
+
Leftarrow: "⇐",
|
|
209
|
+
Longleftarrow: "⇐",
|
|
210
|
+
Leftrightarrow: "⇔",
|
|
211
|
+
Longleftrightarrow: "⇔",
|
|
212
|
+
implies: "⇒",
|
|
213
|
+
iff: "⇔",
|
|
214
|
+
mapsto: "↦",
|
|
215
|
+
longmapsto: "↦",
|
|
216
|
+
uparrow: "↑",
|
|
217
|
+
downarrow: "↓",
|
|
218
|
+
partial: "∂",
|
|
219
|
+
nabla: "∇",
|
|
220
|
+
int: "∫",
|
|
221
|
+
iint: "∬",
|
|
222
|
+
iiint: "∭",
|
|
223
|
+
oint: "∮",
|
|
224
|
+
sum: "∑",
|
|
225
|
+
prod: "∏",
|
|
226
|
+
coprod: "∐",
|
|
227
|
+
infty: "∞",
|
|
228
|
+
emptyset: "∅",
|
|
229
|
+
varnothing: "∅",
|
|
230
|
+
angle: "∠",
|
|
231
|
+
therefore: "∴",
|
|
232
|
+
because: "∵",
|
|
233
|
+
aleph: "ℵ",
|
|
234
|
+
beth: "ℶ",
|
|
235
|
+
gimel: "ℷ",
|
|
236
|
+
daleth: "ℸ",
|
|
237
|
+
top: "⊤",
|
|
238
|
+
bot: "⊥",
|
|
239
|
+
triangle: "△",
|
|
240
|
+
square: "□",
|
|
241
|
+
lozenge: "◊",
|
|
242
|
+
checkmark: "✓",
|
|
243
|
+
complement: "∁",
|
|
244
|
+
wp: "℘",
|
|
245
|
+
prime: "′",
|
|
246
|
+
ldots: "…",
|
|
247
|
+
dots: "…",
|
|
248
|
+
cdots: "⋯",
|
|
249
|
+
vdots: "⋮",
|
|
250
|
+
ddots: "⋱",
|
|
251
|
+
ell: "ℓ",
|
|
252
|
+
hbar: "ℏ",
|
|
253
|
+
Im: "ℑ",
|
|
254
|
+
Re: "ℜ",
|
|
255
|
+
langle: "⟨",
|
|
256
|
+
rangle: "⟩",
|
|
257
|
+
vert: "|",
|
|
258
|
+
lvert: "|",
|
|
259
|
+
rvert: "|",
|
|
260
|
+
Vert: "‖",
|
|
261
|
+
lVert: "‖",
|
|
262
|
+
rVert: "‖",
|
|
263
|
+
lbrace: "{",
|
|
264
|
+
rbrace: "}",
|
|
265
|
+
backslash: "\\",
|
|
266
|
+
lfloor: "⌊",
|
|
267
|
+
rfloor: "⌋",
|
|
268
|
+
lceil: "⌈",
|
|
269
|
+
rceil: "⌉",
|
|
270
|
+
colon: ":",
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const NAMED_OPERATORS = new Set([
|
|
274
|
+
"arccos",
|
|
275
|
+
"arcsin",
|
|
276
|
+
"arctan",
|
|
277
|
+
"arg",
|
|
278
|
+
"cos",
|
|
279
|
+
"cosh",
|
|
280
|
+
"cot",
|
|
281
|
+
"coth",
|
|
282
|
+
"csc",
|
|
283
|
+
"deg",
|
|
284
|
+
"det",
|
|
285
|
+
"dim",
|
|
286
|
+
"exp",
|
|
287
|
+
"gcd",
|
|
288
|
+
"hom",
|
|
289
|
+
"inf",
|
|
290
|
+
"ker",
|
|
291
|
+
"lg",
|
|
292
|
+
"lim",
|
|
293
|
+
"liminf",
|
|
294
|
+
"limsup",
|
|
295
|
+
"ln",
|
|
296
|
+
"log",
|
|
297
|
+
"max",
|
|
298
|
+
"min",
|
|
299
|
+
"Pr",
|
|
300
|
+
"sec",
|
|
301
|
+
"sin",
|
|
302
|
+
"sinh",
|
|
303
|
+
"sup",
|
|
304
|
+
"tan",
|
|
305
|
+
"tanh",
|
|
306
|
+
]);
|
|
307
|
+
|
|
308
|
+
const LIMIT_OPERATORS = new Set([
|
|
309
|
+
"argmax",
|
|
310
|
+
"argmin",
|
|
311
|
+
"inf",
|
|
312
|
+
"injlim",
|
|
313
|
+
"lim",
|
|
314
|
+
"liminf",
|
|
315
|
+
"limsup",
|
|
316
|
+
"max",
|
|
317
|
+
"min",
|
|
318
|
+
"projlim",
|
|
319
|
+
"sup",
|
|
320
|
+
]);
|
|
321
|
+
|
|
322
|
+
const DISPLAY_LIMIT_SYMBOLS = new Set([
|
|
323
|
+
"bigcap",
|
|
324
|
+
"bigcup",
|
|
325
|
+
"bigodot",
|
|
326
|
+
"bigoplus",
|
|
327
|
+
"bigotimes",
|
|
328
|
+
"bigsqcup",
|
|
329
|
+
"biguplus",
|
|
330
|
+
"bigvee",
|
|
331
|
+
"bigwedge",
|
|
332
|
+
"coprod",
|
|
333
|
+
"int",
|
|
334
|
+
"iint",
|
|
335
|
+
"iiint",
|
|
336
|
+
"oint",
|
|
337
|
+
"prod",
|
|
338
|
+
"sum",
|
|
339
|
+
]);
|
|
340
|
+
|
|
341
|
+
const RELATION_COMMANDS = new Set([
|
|
342
|
+
"Leftarrow",
|
|
343
|
+
"Leftrightarrow",
|
|
344
|
+
"Longleftarrow",
|
|
345
|
+
"Longleftrightarrow",
|
|
346
|
+
"Longrightarrow",
|
|
347
|
+
"Rightarrow",
|
|
348
|
+
"Join",
|
|
349
|
+
"Vdash",
|
|
350
|
+
"Vvdash",
|
|
351
|
+
"approx",
|
|
352
|
+
"asymp",
|
|
353
|
+
"bowtie",
|
|
354
|
+
"cong",
|
|
355
|
+
"dashv",
|
|
356
|
+
"fullouterjoin",
|
|
357
|
+
"doteq",
|
|
358
|
+
"downarrow",
|
|
359
|
+
"equiv",
|
|
360
|
+
"ge",
|
|
361
|
+
"geq",
|
|
362
|
+
"geqslant",
|
|
363
|
+
"gets",
|
|
364
|
+
"gg",
|
|
365
|
+
"hookleftarrow",
|
|
366
|
+
"hookrightarrow",
|
|
367
|
+
"iff",
|
|
368
|
+
"implies",
|
|
369
|
+
"in",
|
|
370
|
+
"leadsto",
|
|
371
|
+
"le",
|
|
372
|
+
"leftarrow",
|
|
373
|
+
"leftharpoondown",
|
|
374
|
+
"leftharpoonup",
|
|
375
|
+
"leftrightarrow",
|
|
376
|
+
"leftrightharpoons",
|
|
377
|
+
"leftouterjoin",
|
|
378
|
+
"leq",
|
|
379
|
+
"leqslant",
|
|
380
|
+
"ll",
|
|
381
|
+
"longleftarrow",
|
|
382
|
+
"longleftrightarrow",
|
|
383
|
+
"longmapsto",
|
|
384
|
+
"longrightarrow",
|
|
385
|
+
"ltimes",
|
|
386
|
+
"mapsto",
|
|
387
|
+
"mid",
|
|
388
|
+
"models",
|
|
389
|
+
"ne",
|
|
390
|
+
"nearrow",
|
|
391
|
+
"neq",
|
|
392
|
+
"ni",
|
|
393
|
+
"notin",
|
|
394
|
+
"nvdash",
|
|
395
|
+
"nvDash",
|
|
396
|
+
"nwarrow",
|
|
397
|
+
"parallel",
|
|
398
|
+
"perp",
|
|
399
|
+
"prec",
|
|
400
|
+
"preceq",
|
|
401
|
+
"propto",
|
|
402
|
+
"rightharpoondown",
|
|
403
|
+
"rightharpoonup",
|
|
404
|
+
"rightleftharpoons",
|
|
405
|
+
"rightouterjoin",
|
|
406
|
+
"rightarrow",
|
|
407
|
+
"rightsquigarrow",
|
|
408
|
+
"rtimes",
|
|
409
|
+
"searrow",
|
|
410
|
+
"sim",
|
|
411
|
+
"simeq",
|
|
412
|
+
"sqsubset",
|
|
413
|
+
"sqsubseteq",
|
|
414
|
+
"sqsupset",
|
|
415
|
+
"sqsupseteq",
|
|
416
|
+
"subset",
|
|
417
|
+
"subseteq",
|
|
418
|
+
"succ",
|
|
419
|
+
"succeq",
|
|
420
|
+
"supset",
|
|
421
|
+
"supseteq",
|
|
422
|
+
"swarrow",
|
|
423
|
+
"to",
|
|
424
|
+
"triangleleft",
|
|
425
|
+
"triangleright",
|
|
426
|
+
"twoheadleftarrow",
|
|
427
|
+
"twoheadrightarrow",
|
|
428
|
+
"uparrow",
|
|
429
|
+
"vdash",
|
|
430
|
+
]);
|
|
431
|
+
|
|
432
|
+
const NEGATED_SYMBOLS = {
|
|
433
|
+
"<": "≮",
|
|
434
|
+
">": "≯",
|
|
435
|
+
"=": "≠",
|
|
436
|
+
"∈": "∉",
|
|
437
|
+
"∋": "∌",
|
|
438
|
+
"∣": "∤",
|
|
439
|
+
"∥": "∦",
|
|
440
|
+
"∼": "≁",
|
|
441
|
+
"≃": "≄",
|
|
442
|
+
"≅": "≇",
|
|
443
|
+
"≈": "≉",
|
|
444
|
+
"≡": "≢",
|
|
445
|
+
"≤": "≰",
|
|
446
|
+
"≥": "≱",
|
|
447
|
+
"≺": "⊀",
|
|
448
|
+
"≻": "⊁",
|
|
449
|
+
"⊂": "⊄",
|
|
450
|
+
"⊃": "⊅",
|
|
451
|
+
"⊆": "⊈",
|
|
452
|
+
"⊇": "⊉",
|
|
453
|
+
"⊢": "⊬",
|
|
454
|
+
"⊨": "⊭",
|
|
455
|
+
"↔": "↮",
|
|
456
|
+
"←": "↚",
|
|
457
|
+
"→": "↛",
|
|
458
|
+
"⇒": "⇏",
|
|
459
|
+
"⇐": "⇍",
|
|
460
|
+
"⇔": "⇎",
|
|
461
|
+
"≼": "⋠",
|
|
462
|
+
"≽": "⋡",
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const BLACKBOARD = {
|
|
466
|
+
C: "ℂ",
|
|
467
|
+
H: "ℍ",
|
|
468
|
+
N: "ℕ",
|
|
469
|
+
P: "ℙ",
|
|
470
|
+
Q: "ℚ",
|
|
471
|
+
R: "ℝ",
|
|
472
|
+
Z: "ℤ",
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const SUPERSCRIPTS = {
|
|
476
|
+
"0": "⁰",
|
|
477
|
+
"1": "¹",
|
|
478
|
+
"2": "²",
|
|
479
|
+
"3": "³",
|
|
480
|
+
"4": "⁴",
|
|
481
|
+
"5": "⁵",
|
|
482
|
+
"6": "⁶",
|
|
483
|
+
"7": "⁷",
|
|
484
|
+
"8": "⁸",
|
|
485
|
+
"9": "⁹",
|
|
486
|
+
"+": "⁺",
|
|
487
|
+
"-": "⁻",
|
|
488
|
+
"=": "⁼",
|
|
489
|
+
"(": "⁽",
|
|
490
|
+
")": "⁾",
|
|
491
|
+
a: "ᵃ",
|
|
492
|
+
b: "ᵇ",
|
|
493
|
+
c: "ᶜ",
|
|
494
|
+
d: "ᵈ",
|
|
495
|
+
e: "ᵉ",
|
|
496
|
+
f: "ᶠ",
|
|
497
|
+
g: "ᵍ",
|
|
498
|
+
h: "ʰ",
|
|
499
|
+
i: "ⁱ",
|
|
500
|
+
j: "ʲ",
|
|
501
|
+
k: "ᵏ",
|
|
502
|
+
l: "ˡ",
|
|
503
|
+
m: "ᵐ",
|
|
504
|
+
n: "ⁿ",
|
|
505
|
+
o: "ᵒ",
|
|
506
|
+
p: "ᵖ",
|
|
507
|
+
r: "ʳ",
|
|
508
|
+
s: "ˢ",
|
|
509
|
+
t: "ᵗ",
|
|
510
|
+
u: "ᵘ",
|
|
511
|
+
v: "ᵛ",
|
|
512
|
+
w: "ʷ",
|
|
513
|
+
x: "ˣ",
|
|
514
|
+
y: "ʸ",
|
|
515
|
+
z: "ᶻ",
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const SUBSCRIPTS = {
|
|
519
|
+
"0": "₀",
|
|
520
|
+
"1": "₁",
|
|
521
|
+
"2": "₂",
|
|
522
|
+
"3": "₃",
|
|
523
|
+
"4": "₄",
|
|
524
|
+
"5": "₅",
|
|
525
|
+
"6": "₆",
|
|
526
|
+
"7": "₇",
|
|
527
|
+
"8": "₈",
|
|
528
|
+
"9": "₉",
|
|
529
|
+
"+": "₊",
|
|
530
|
+
"-": "₋",
|
|
531
|
+
"=": "₌",
|
|
532
|
+
"(": "₍",
|
|
533
|
+
")": "₎",
|
|
534
|
+
a: "ₐ",
|
|
535
|
+
e: "ₑ",
|
|
536
|
+
h: "ₕ",
|
|
537
|
+
i: "ᵢ",
|
|
538
|
+
j: "ⱼ",
|
|
539
|
+
k: "ₖ",
|
|
540
|
+
l: "ₗ",
|
|
541
|
+
m: "ₘ",
|
|
542
|
+
n: "ₙ",
|
|
543
|
+
o: "ₒ",
|
|
544
|
+
p: "ₚ",
|
|
545
|
+
r: "ᵣ",
|
|
546
|
+
s: "ₛ",
|
|
547
|
+
t: "ₜ",
|
|
548
|
+
u: "ᵤ",
|
|
549
|
+
v: "ᵥ",
|
|
550
|
+
x: "ₓ",
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const SPACING_COMMANDS = new Set([
|
|
554
|
+
",",
|
|
555
|
+
":",
|
|
556
|
+
";",
|
|
557
|
+
" ",
|
|
558
|
+
">",
|
|
559
|
+
"enspace",
|
|
560
|
+
"enskip",
|
|
561
|
+
"medspace",
|
|
562
|
+
"quad",
|
|
563
|
+
"qquad",
|
|
564
|
+
"thickspace",
|
|
565
|
+
"thinspace",
|
|
566
|
+
]);
|
|
567
|
+
const NEGATIVE_SPACING_COMMANDS = new Set(["!", "negmedspace", "negthickspace", "negthinspace"]);
|
|
568
|
+
const NEGATIVE_SPACE = "\u0000";
|
|
569
|
+
const FONT_SWITCH_COMMANDS = new Set(["bf", "cal", "it", "rm", "sf", "sl", "tt"]);
|
|
570
|
+
const IGNORED_COMMANDS = new Set([
|
|
571
|
+
"displaystyle",
|
|
572
|
+
"limits",
|
|
573
|
+
"nolimits",
|
|
574
|
+
"scriptstyle",
|
|
575
|
+
"scriptscriptstyle",
|
|
576
|
+
"textstyle",
|
|
577
|
+
]);
|
|
578
|
+
const SIZE_COMMANDS = new Set([
|
|
579
|
+
"big",
|
|
580
|
+
"Big",
|
|
581
|
+
"bigg",
|
|
582
|
+
"Bigg",
|
|
583
|
+
"bigl",
|
|
584
|
+
"Bigl",
|
|
585
|
+
"biggl",
|
|
586
|
+
"Biggl",
|
|
587
|
+
"bigr",
|
|
588
|
+
"Bigr",
|
|
589
|
+
"biggr",
|
|
590
|
+
"Biggr",
|
|
591
|
+
]);
|
|
592
|
+
const PLAIN_WRAPPERS = new Set([
|
|
593
|
+
"emph",
|
|
594
|
+
"mathcal",
|
|
595
|
+
"mathbf",
|
|
596
|
+
"mathfrak",
|
|
597
|
+
"mathit",
|
|
598
|
+
"mathrm",
|
|
599
|
+
"mathnormal",
|
|
600
|
+
"mathscr",
|
|
601
|
+
"mathsf",
|
|
602
|
+
"mathtt",
|
|
603
|
+
"mathup",
|
|
604
|
+
"mbox",
|
|
605
|
+
"overbrace",
|
|
606
|
+
"pmb",
|
|
607
|
+
"smash",
|
|
608
|
+
"substack",
|
|
609
|
+
"text",
|
|
610
|
+
"textbf",
|
|
611
|
+
"textit",
|
|
612
|
+
"textmd",
|
|
613
|
+
"textnormal",
|
|
614
|
+
"textrm",
|
|
615
|
+
"textsc",
|
|
616
|
+
"textsf",
|
|
617
|
+
"textsl",
|
|
618
|
+
"texttt",
|
|
619
|
+
"textup",
|
|
620
|
+
"underbrace",
|
|
621
|
+
"bm",
|
|
622
|
+
"boldsymbol",
|
|
623
|
+
]);
|
|
624
|
+
const ACCENTS = {
|
|
625
|
+
acute: "\u0301",
|
|
626
|
+
bar: "\u0305",
|
|
627
|
+
breve: "\u0306",
|
|
628
|
+
check: "\u030c",
|
|
629
|
+
ddot: "\u0308",
|
|
630
|
+
dot: "\u0307",
|
|
631
|
+
grave: "\u0300",
|
|
632
|
+
hat: "\u0302",
|
|
633
|
+
mathring: "\u030a",
|
|
634
|
+
overleftarrow: "\u20d6",
|
|
635
|
+
overleftrightarrow: "\u20e1",
|
|
636
|
+
overline: "\u0305",
|
|
637
|
+
overrightarrow: "\u20d7",
|
|
638
|
+
tilde: "\u0303",
|
|
639
|
+
underline: "\u0332",
|
|
640
|
+
vec: "\u20d7",
|
|
641
|
+
widehat: "\u0302",
|
|
642
|
+
widetilde: "\u0303",
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
function replaceCharacters(value , replacements ) {
|
|
646
|
+
let result = "";
|
|
647
|
+
for (const character of value) {
|
|
648
|
+
const replacement = replacements[character];
|
|
649
|
+
if (replacement === undefined) {
|
|
650
|
+
return undefined;
|
|
651
|
+
}
|
|
652
|
+
result += replacement;
|
|
653
|
+
}
|
|
654
|
+
return result;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function normalizeScriptValue(value ) {
|
|
658
|
+
return value.trim().replace(/\s*([=+-])\s*/g, "$1");
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function formatUnicodeScript(value , kind ) {
|
|
662
|
+
return replaceCharacters(normalizeScriptValue(value), kind === "sub" ? SUBSCRIPTS : SUPERSCRIPTS);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function formatScript(value , kind ) {
|
|
666
|
+
value = normalizeScriptValue(value);
|
|
667
|
+
const unicode = formatUnicodeScript(value, kind);
|
|
668
|
+
if (unicode !== undefined) {
|
|
669
|
+
return unicode;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const prefix = kind === "sub" ? "_" : "^";
|
|
673
|
+
if (Array.from(value).length === 1 || (kind === "sub" && /^[A-Za-z]+$/.test(value))) {
|
|
674
|
+
return `${prefix}${value}`;
|
|
675
|
+
}
|
|
676
|
+
return `${prefix}(${value})`;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function formatFraction(numerator , denominator ) {
|
|
680
|
+
numerator = numerator.trim();
|
|
681
|
+
denominator = denominator.trim();
|
|
682
|
+
const simpleNumerator = /^[\p{L}\p{N}.]+$/u.test(numerator);
|
|
683
|
+
const simpleDenominator = /^[\p{N}.]+$/u.test(denominator) || Array.from(denominator).length === 1;
|
|
684
|
+
return `${simpleNumerator ? numerator : `(${numerator})`}/${simpleDenominator ? denominator : `(${denominator})`}`;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function formatRoot(value , symbol = "√") {
|
|
688
|
+
value = value.trim();
|
|
689
|
+
return /^[\p{L}\p{N}.]+$/u.test(value) ? `${symbol}${value}` : `${symbol}(${value})`;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const NAMED_OPERATOR_START = "\u{f0004}";
|
|
693
|
+
const NAMED_OPERATOR_END = "\u{f0005}";
|
|
694
|
+
const NAMED_OPERATOR_LEFT_SPACING_PATTERN = /(?<=[\p{L}\p{N})\]}\u{f0001}])\u{f0004}/gu;
|
|
695
|
+
const NAMED_OPERATOR_RIGHT_SPACING_PATTERN = /\u{f0005}(?=[\p{L}\p{N}√\u{f0000}])/gu;
|
|
696
|
+
|
|
697
|
+
function normalizeOutput(value ) {
|
|
698
|
+
return value
|
|
699
|
+
.replace(NAMED_OPERATOR_LEFT_SPACING_PATTERN, " ")
|
|
700
|
+
.replaceAll(NAMED_OPERATOR_START, "")
|
|
701
|
+
.replace(NAMED_OPERATOR_RIGHT_SPACING_PATTERN, " ")
|
|
702
|
+
.replaceAll(NAMED_OPERATOR_END, "")
|
|
703
|
+
.split("\n")
|
|
704
|
+
.map((line) => line.replace(/[ \t]+/g, " ").trim())
|
|
705
|
+
.filter((line, index, lines) => line.length > 0 || (index > 0 && index < lines.length - 1))
|
|
706
|
+
.join("\n")
|
|
707
|
+
.trim();
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
const LAYOUT_MARKER_START = "\u{f0000}";
|
|
744
|
+
const LAYOUT_MARKER_END = "\u{f0001}";
|
|
745
|
+
const LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}/gu;
|
|
746
|
+
const TRAILING_LAYOUT_MARKER_PATTERN = /\u{f0000}(\d+)\u{f0001}$/u;
|
|
747
|
+
const PROTECTED_SPACE = "\u{f0002}";
|
|
748
|
+
|
|
749
|
+
function padLayoutLine(line , width , centered = false) {
|
|
750
|
+
const padding = Math.max(0, width - visibleWidth(line));
|
|
751
|
+
const left = centered ? Math.floor(padding / 2) : 0;
|
|
752
|
+
return `${" ".repeat(left)}${line}${" ".repeat(padding - left)}`;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function joinLayouts(layouts ) {
|
|
756
|
+
if (layouts.length === 0) {
|
|
757
|
+
return { lines: [""], width: 0, baseline: 0 };
|
|
758
|
+
}
|
|
759
|
+
const baseline = Math.max(...layouts.map((layout) => layout.baseline));
|
|
760
|
+
const below = Math.max(...layouts.map((layout) => layout.lines.length - layout.baseline - 1));
|
|
761
|
+
const lines = [];
|
|
762
|
+
for (let row = 0; row <= baseline + below; row++) {
|
|
763
|
+
let line = "";
|
|
764
|
+
for (const layout of layouts) {
|
|
765
|
+
const sourceRow = row - baseline + layout.baseline;
|
|
766
|
+
line +=
|
|
767
|
+
sourceRow >= 0 && sourceRow < layout.lines.length
|
|
768
|
+
? padLayoutLine(layout.lines[sourceRow] ?? "", layout.width)
|
|
769
|
+
: " ".repeat(layout.width);
|
|
770
|
+
}
|
|
771
|
+
lines.push(line.trimEnd());
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
lines,
|
|
775
|
+
width: layouts.reduce((width, layout) => width + layout.width, 0),
|
|
776
|
+
baseline,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function renderLayout(source , nodes ) {
|
|
781
|
+
const renderedLines = [];
|
|
782
|
+
let firstBaseline = 0;
|
|
783
|
+
for (const sourceLine of source.split("\n")) {
|
|
784
|
+
const layouts = [];
|
|
785
|
+
let position = 0;
|
|
786
|
+
let previousNode ;
|
|
787
|
+
for (const match of sourceLine.matchAll(LAYOUT_MARKER_PATTERN)) {
|
|
788
|
+
const index = match.index;
|
|
789
|
+
const node = nodes[Number(match[1])];
|
|
790
|
+
if (!node) {
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (index > position) {
|
|
794
|
+
const sliced = sourceLine.slice(position, index);
|
|
795
|
+
const trimmed = (previousNode ? sliced.trimStart() : sliced).trimEnd();
|
|
796
|
+
const preserveLeadingSpace = previousNode?.type === "matrix" && /^\s/.test(sliced);
|
|
797
|
+
const preserveTrailingSpace = node.type === "matrix" && /\s$/.test(sliced);
|
|
798
|
+
const text = trimmed
|
|
799
|
+
? `${preserveLeadingSpace ? " " : ""}${trimmed}${preserveTrailingSpace ? " " : ""}`
|
|
800
|
+
: preserveLeadingSpace || preserveTrailingSpace
|
|
801
|
+
? " "
|
|
802
|
+
: "";
|
|
803
|
+
layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
|
|
804
|
+
}
|
|
805
|
+
if (node.type === "fraction") {
|
|
806
|
+
const numerator = renderLayout(node.numerator, nodes);
|
|
807
|
+
const denominator = renderLayout(node.denominator, nodes);
|
|
808
|
+
const contentWidth = Math.max(numerator.width, denominator.width, 1);
|
|
809
|
+
const width = contentWidth + 2;
|
|
810
|
+
layouts.push({
|
|
811
|
+
lines: [
|
|
812
|
+
...numerator.lines.map((line) => padLayoutLine(line, width, true)),
|
|
813
|
+
` ${"─".repeat(contentWidth)} `,
|
|
814
|
+
...denominator.lines.map((line) => padLayoutLine(line, width, true)),
|
|
815
|
+
],
|
|
816
|
+
width,
|
|
817
|
+
baseline: numerator.lines.length,
|
|
818
|
+
});
|
|
819
|
+
} else if (node.type === "operator") {
|
|
820
|
+
const contentWidth = Math.max(
|
|
821
|
+
visibleWidth(node.operator),
|
|
822
|
+
node.lower === undefined ? 0 : visibleWidth(node.lower),
|
|
823
|
+
node.upper === undefined ? 0 : visibleWidth(node.upper),
|
|
824
|
+
);
|
|
825
|
+
const lines = [];
|
|
826
|
+
if (node.upper !== undefined) {
|
|
827
|
+
lines.push(`${padLayoutLine(node.upper, contentWidth, true)} `);
|
|
828
|
+
}
|
|
829
|
+
lines.push(`${padLayoutLine(node.operator, contentWidth, true)} `);
|
|
830
|
+
if (node.lower !== undefined) {
|
|
831
|
+
lines.push(`${padLayoutLine(node.lower, contentWidth, true)} `);
|
|
832
|
+
}
|
|
833
|
+
layouts.push({
|
|
834
|
+
lines,
|
|
835
|
+
width: contentWidth + 1,
|
|
836
|
+
baseline: node.upper === undefined ? 0 : 1,
|
|
837
|
+
});
|
|
838
|
+
} else if (node.type === "script") {
|
|
839
|
+
const upper = node.upper === undefined ? undefined : renderLayout(node.upper, nodes);
|
|
840
|
+
const lower = node.lower === undefined ? undefined : renderLayout(node.lower, nodes);
|
|
841
|
+
const width = Math.max(upper?.width ?? 0, lower?.width ?? 0);
|
|
842
|
+
layouts.push({
|
|
843
|
+
lines: [
|
|
844
|
+
...(upper?.lines.map((line) => padLayoutLine(line, width)) ?? []),
|
|
845
|
+
" ".repeat(width),
|
|
846
|
+
...(lower?.lines.map((line) => padLayoutLine(line, width)) ?? []),
|
|
847
|
+
],
|
|
848
|
+
width,
|
|
849
|
+
baseline: upper?.lines.length ?? 0,
|
|
850
|
+
});
|
|
851
|
+
} else {
|
|
852
|
+
const width = Math.max(0, ...node.lines.map((line) => visibleWidth(line)));
|
|
853
|
+
layouts.push({
|
|
854
|
+
lines: node.lines.map((line) => padLayoutLine(line, width)),
|
|
855
|
+
width,
|
|
856
|
+
baseline: node.baseline,
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
position = index + match[0].length;
|
|
860
|
+
previousNode = node;
|
|
861
|
+
}
|
|
862
|
+
if (position < sourceLine.length) {
|
|
863
|
+
const sliced = sourceLine.slice(position);
|
|
864
|
+
const trimmed = previousNode ? sliced.trimStart() : sliced;
|
|
865
|
+
const text = previousNode?.type === "matrix" && /^\s/.test(sliced) ? ` ${trimmed}` : trimmed;
|
|
866
|
+
layouts.push({ lines: [text], width: visibleWidth(text), baseline: 0 });
|
|
867
|
+
}
|
|
868
|
+
const lineLayout = joinLayouts(layouts);
|
|
869
|
+
if (renderedLines.length === 0) {
|
|
870
|
+
firstBaseline = lineLayout.baseline;
|
|
871
|
+
}
|
|
872
|
+
renderedLines.push(...lineLayout.lines);
|
|
873
|
+
}
|
|
874
|
+
return {
|
|
875
|
+
lines: renderedLines,
|
|
876
|
+
width: Math.max(0, ...renderedLines.map((line) => visibleWidth(line))),
|
|
877
|
+
baseline: firstBaseline,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
class LatexParser {
|
|
882
|
+
source ;
|
|
883
|
+
layoutNodes ;
|
|
884
|
+
display ;
|
|
885
|
+
position = 0;
|
|
886
|
+
supported = true;
|
|
887
|
+
stackFractions = true;
|
|
888
|
+
scriptDepth = 0;
|
|
889
|
+
|
|
890
|
+
constructor(source , layoutNodes , display ) {
|
|
891
|
+
this.source = source;
|
|
892
|
+
this.layoutNodes = layoutNodes;
|
|
893
|
+
this.display = display;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
render() {
|
|
897
|
+
const rendered = this.parseSequence();
|
|
898
|
+
if (!this.supported || this.position !== this.source.length) {
|
|
899
|
+
return undefined;
|
|
900
|
+
}
|
|
901
|
+
return normalizeOutput(rendered);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
parseSequence(endCharacter ) {
|
|
905
|
+
let result = "";
|
|
906
|
+
while (this.position < this.source.length) {
|
|
907
|
+
const character = this.source[this.position];
|
|
908
|
+
if (endCharacter && character === endCharacter) {
|
|
909
|
+
this.position++;
|
|
910
|
+
return result;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (character === "}") {
|
|
914
|
+
this.supported = false;
|
|
915
|
+
return result;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (character === "{") {
|
|
919
|
+
this.position++;
|
|
920
|
+
result += this.parseSequence("}");
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (character === "\\") {
|
|
925
|
+
const command = this.parseCommand();
|
|
926
|
+
if (command === NEGATIVE_SPACE) {
|
|
927
|
+
result = result.trimEnd();
|
|
928
|
+
if (result.endsWith(NAMED_OPERATOR_END)) {
|
|
929
|
+
result = result.slice(0, -NAMED_OPERATOR_END.length);
|
|
930
|
+
}
|
|
931
|
+
} else {
|
|
932
|
+
result += command;
|
|
933
|
+
}
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (character === "^" || character === "_") {
|
|
938
|
+
this.position++;
|
|
939
|
+
result = result.trimEnd();
|
|
940
|
+
const script = this.parseScripts(character);
|
|
941
|
+
if (result.endsWith(NAMED_OPERATOR_END)) {
|
|
942
|
+
result = `${result.slice(0, -NAMED_OPERATOR_END.length)}${script}${NAMED_OPERATOR_END}`;
|
|
943
|
+
} else {
|
|
944
|
+
result += script;
|
|
945
|
+
}
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (/\s/.test(character)) {
|
|
950
|
+
result += this.parseWhitespace();
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (character === "=" || character === "<" || character === ">") {
|
|
955
|
+
result = `${result.trimEnd()} ${character} `;
|
|
956
|
+
this.position++;
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (character === "&") {
|
|
961
|
+
this.position++;
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (character === "~") {
|
|
966
|
+
this.position++;
|
|
967
|
+
result += " ";
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
if (character === ".") {
|
|
972
|
+
const marker = TRAILING_LAYOUT_MARKER_PATTERN.exec(result);
|
|
973
|
+
const node = marker ? this.layoutNodes[Number(marker[1])] : undefined;
|
|
974
|
+
if (node?.type === "matrix") {
|
|
975
|
+
const lastLine = node.lines.length - 1;
|
|
976
|
+
node.lines[lastLine] = `${node.lines[lastLine] ?? ""}${character}`;
|
|
977
|
+
this.position++;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
result += character;
|
|
983
|
+
this.position++;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (endCharacter) {
|
|
987
|
+
this.supported = false;
|
|
988
|
+
}
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
parseScripts(initialMarker ) {
|
|
993
|
+
const scripts = {};
|
|
994
|
+
const order = [];
|
|
995
|
+
const parse = (marker ) => {
|
|
996
|
+
const kind = marker === "_" ? "sub" : "sup";
|
|
997
|
+
this.scriptDepth++;
|
|
998
|
+
try {
|
|
999
|
+
scripts[kind] = this.parseRequiredArgument(false);
|
|
1000
|
+
} finally {
|
|
1001
|
+
this.scriptDepth--;
|
|
1002
|
+
}
|
|
1003
|
+
order.push(kind);
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
parse(initialMarker);
|
|
1007
|
+
let nextPosition = this.position;
|
|
1008
|
+
while (nextPosition < this.source.length && /\s/.test(this.source[nextPosition] ?? "")) {
|
|
1009
|
+
nextPosition++;
|
|
1010
|
+
}
|
|
1011
|
+
const nextMarker = this.source[nextPosition];
|
|
1012
|
+
if ((nextMarker === "^" || nextMarker === "_") && nextMarker !== initialMarker) {
|
|
1013
|
+
this.position = nextPosition + 1;
|
|
1014
|
+
parse(nextMarker);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const subUnicode = scripts.sub === undefined ? undefined : formatUnicodeScript(scripts.sub, "sub");
|
|
1018
|
+
const supUnicode = scripts.sup === undefined ? undefined : formatUnicodeScript(scripts.sup, "sup");
|
|
1019
|
+
const canUseLayout = ![scripts.sub, scripts.sup].some(
|
|
1020
|
+
(value) =>
|
|
1021
|
+
value !== undefined &&
|
|
1022
|
+
(value.includes("/") ||
|
|
1023
|
+
(!value.includes(LAYOUT_MARKER_START) && Array.from(value).length > 1 && !/[A-Z*∗]/.test(value))),
|
|
1024
|
+
);
|
|
1025
|
+
const needsLayout =
|
|
1026
|
+
this.display &&
|
|
1027
|
+
canUseLayout &&
|
|
1028
|
+
(this.scriptDepth > 0 ||
|
|
1029
|
+
(scripts.sub !== undefined && subUnicode === undefined) ||
|
|
1030
|
+
(scripts.sup !== undefined && supUnicode === undefined));
|
|
1031
|
+
if (!needsLayout) {
|
|
1032
|
+
return order
|
|
1033
|
+
.map((kind) =>
|
|
1034
|
+
kind === "sub"
|
|
1035
|
+
? (subUnicode ?? formatScript(scripts.sub ?? "", kind))
|
|
1036
|
+
: (supUnicode ?? formatScript(scripts.sup ?? "", kind)),
|
|
1037
|
+
)
|
|
1038
|
+
.join("");
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const index =
|
|
1042
|
+
this.layoutNodes.push({
|
|
1043
|
+
type: "script",
|
|
1044
|
+
lower: scripts.sub === undefined ? undefined : normalizeOutput(scripts.sub),
|
|
1045
|
+
upper: scripts.sup === undefined ? undefined : normalizeOutput(scripts.sup),
|
|
1046
|
+
}) - 1;
|
|
1047
|
+
return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
parseWhitespace() {
|
|
1051
|
+
while (this.position < this.source.length && /\s/.test(this.source[this.position] ?? "")) {
|
|
1052
|
+
this.position++;
|
|
1053
|
+
}
|
|
1054
|
+
return " ";
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
parseCommand() {
|
|
1058
|
+
this.position++;
|
|
1059
|
+
if (this.position >= this.source.length) {
|
|
1060
|
+
this.supported = false;
|
|
1061
|
+
return "";
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
let command = "";
|
|
1065
|
+
const first = this.source[this.position] ?? "";
|
|
1066
|
+
if (first === "\n" || first === "\r") {
|
|
1067
|
+
this.position++;
|
|
1068
|
+
if (first === "\r" && this.source[this.position] === "\n") {
|
|
1069
|
+
this.position++;
|
|
1070
|
+
}
|
|
1071
|
+
return " ";
|
|
1072
|
+
}
|
|
1073
|
+
if (/[A-Za-z]/.test(first)) {
|
|
1074
|
+
const start = this.position;
|
|
1075
|
+
while (this.position < this.source.length && /[A-Za-z]/.test(this.source[this.position] ?? "")) {
|
|
1076
|
+
this.position++;
|
|
1077
|
+
}
|
|
1078
|
+
command = this.source.slice(start, this.position);
|
|
1079
|
+
} else {
|
|
1080
|
+
command = first;
|
|
1081
|
+
this.position++;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
if (command === "\\") {
|
|
1085
|
+
return "\n";
|
|
1086
|
+
}
|
|
1087
|
+
if (SPACING_COMMANDS.has(command)) {
|
|
1088
|
+
return " ";
|
|
1089
|
+
}
|
|
1090
|
+
if (NEGATIVE_SPACING_COMMANDS.has(command)) {
|
|
1091
|
+
return NEGATIVE_SPACE;
|
|
1092
|
+
}
|
|
1093
|
+
if (FONT_SWITCH_COMMANDS.has(command)) {
|
|
1094
|
+
while (this.position < this.source.length && /\s/.test(this.source[this.position] ?? "")) {
|
|
1095
|
+
this.position++;
|
|
1096
|
+
}
|
|
1097
|
+
return "";
|
|
1098
|
+
}
|
|
1099
|
+
if (IGNORED_COMMANDS.has(command)) {
|
|
1100
|
+
return "";
|
|
1101
|
+
}
|
|
1102
|
+
if (
|
|
1103
|
+
command === "{" ||
|
|
1104
|
+
command === "}" ||
|
|
1105
|
+
command === "$" ||
|
|
1106
|
+
command === "%" ||
|
|
1107
|
+
command === "#" ||
|
|
1108
|
+
command === "_" ||
|
|
1109
|
+
command === "&"
|
|
1110
|
+
) {
|
|
1111
|
+
return command;
|
|
1112
|
+
}
|
|
1113
|
+
if (command === "|") {
|
|
1114
|
+
return "‖";
|
|
1115
|
+
}
|
|
1116
|
+
if (command === "not") {
|
|
1117
|
+
const value = this.parseRequiredArgument(false).trim();
|
|
1118
|
+
const negated = NEGATED_SYMBOLS[value];
|
|
1119
|
+
if (negated !== undefined) {
|
|
1120
|
+
return ` ${negated} `;
|
|
1121
|
+
}
|
|
1122
|
+
const characters = Array.from(value);
|
|
1123
|
+
if (characters.length === 0) {
|
|
1124
|
+
this.supported = false;
|
|
1125
|
+
return "";
|
|
1126
|
+
}
|
|
1127
|
+
return ` ${characters[0]}\u0338${characters.slice(1).join("")} `;
|
|
1128
|
+
}
|
|
1129
|
+
if (LIMIT_OPERATORS.has(command)) {
|
|
1130
|
+
return this.parseOperator(command, "bracket", true, true);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const symbol = SYMBOLS[command];
|
|
1134
|
+
if (symbol !== undefined) {
|
|
1135
|
+
if (DISPLAY_LIMIT_SYMBOLS.has(command)) {
|
|
1136
|
+
return this.parseOperator(symbol, "script", true);
|
|
1137
|
+
}
|
|
1138
|
+
return command === "cdot" || command === "times" || RELATION_COMMANDS.has(command) ? ` ${symbol} ` : symbol;
|
|
1139
|
+
}
|
|
1140
|
+
if (NAMED_OPERATORS.has(command)) {
|
|
1141
|
+
return `${NAMED_OPERATOR_START}${command}${NAMED_OPERATOR_END}`;
|
|
1142
|
+
}
|
|
1143
|
+
if (SIZE_COMMANDS.has(command)) {
|
|
1144
|
+
return "";
|
|
1145
|
+
}
|
|
1146
|
+
if (command === "left" || command === "middle" || command === "right") {
|
|
1147
|
+
if (this.source[this.position] === ".") {
|
|
1148
|
+
this.position++;
|
|
1149
|
+
}
|
|
1150
|
+
return "";
|
|
1151
|
+
}
|
|
1152
|
+
if (command === "frac" || command === "dfrac" || command === "tfrac") {
|
|
1153
|
+
const shouldStack = this.display && this.stackFractions && command !== "tfrac";
|
|
1154
|
+
const numerator = this.parseRequiredArgument(!shouldStack);
|
|
1155
|
+
const denominator = this.parseRequiredArgument(!shouldStack);
|
|
1156
|
+
if (shouldStack) {
|
|
1157
|
+
const index =
|
|
1158
|
+
this.layoutNodes.push({
|
|
1159
|
+
type: "fraction",
|
|
1160
|
+
numerator: normalizeOutput(numerator),
|
|
1161
|
+
denominator: normalizeOutput(denominator),
|
|
1162
|
+
}) - 1;
|
|
1163
|
+
return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
|
|
1164
|
+
}
|
|
1165
|
+
return formatFraction(numerator, denominator);
|
|
1166
|
+
}
|
|
1167
|
+
if (command === "sqrt") {
|
|
1168
|
+
const degree = this.parseOptionalArgument()?.trim();
|
|
1169
|
+
const value = this.parseRequiredArgument();
|
|
1170
|
+
if (degree === undefined || degree === "2") {
|
|
1171
|
+
return formatRoot(value);
|
|
1172
|
+
}
|
|
1173
|
+
if (degree === "3") {
|
|
1174
|
+
return formatRoot(value, "∛");
|
|
1175
|
+
}
|
|
1176
|
+
if (degree === "4") {
|
|
1177
|
+
return formatRoot(value, "∜");
|
|
1178
|
+
}
|
|
1179
|
+
return `${formatScript(degree, "sup")}${formatRoot(value)}`;
|
|
1180
|
+
}
|
|
1181
|
+
if (command === "boxed" || command === "fbox") {
|
|
1182
|
+
return `[${this.parseRequiredArgument().trim()}]`;
|
|
1183
|
+
}
|
|
1184
|
+
if (command === "binom" || command === "dbinom" || command === "tbinom") {
|
|
1185
|
+
return `(${this.parseRequiredArgument()} choose ${this.parseRequiredArgument()})`;
|
|
1186
|
+
}
|
|
1187
|
+
const accent = ACCENTS[command];
|
|
1188
|
+
if (accent !== undefined) {
|
|
1189
|
+
const value = this.parseRequiredArgument();
|
|
1190
|
+
return Array.from(value).length === 1 ? `${value}${accent}` : `${command}(${value})`;
|
|
1191
|
+
}
|
|
1192
|
+
if (command === "mathbb") {
|
|
1193
|
+
const value = this.parseRequiredArgument();
|
|
1194
|
+
return Array.from(value, (character) => BLACKBOARD[character] ?? character).join("");
|
|
1195
|
+
}
|
|
1196
|
+
if (command === "operatorname") {
|
|
1197
|
+
const starred = this.source[this.position] === "*";
|
|
1198
|
+
if (starred) {
|
|
1199
|
+
this.position++;
|
|
1200
|
+
}
|
|
1201
|
+
const operator = normalizeOutput(this.parseRequiredArgument()).trim();
|
|
1202
|
+
return this.parseOperator(operator, "bracket", starred, true);
|
|
1203
|
+
}
|
|
1204
|
+
if (command === "mod" || command === "bmod") {
|
|
1205
|
+
return " mod ";
|
|
1206
|
+
}
|
|
1207
|
+
if (command === "pmod" || command === "pod") {
|
|
1208
|
+
const value = this.parseRequiredArgument().trim();
|
|
1209
|
+
return command === "pmod" ? ` (mod ${value})` : ` (${value})`;
|
|
1210
|
+
}
|
|
1211
|
+
if (command === "overset" || command === "stackrel") {
|
|
1212
|
+
const upper = this.parseRequiredArgument();
|
|
1213
|
+
const value = this.parseRequiredArgument().trim();
|
|
1214
|
+
return `${value}${formatScript(upper, "sup")}`;
|
|
1215
|
+
}
|
|
1216
|
+
if (command === "underset") {
|
|
1217
|
+
const lower = this.parseRequiredArgument();
|
|
1218
|
+
const value = this.parseRequiredArgument().trim();
|
|
1219
|
+
return `${value}${formatScript(lower, "sub")}`;
|
|
1220
|
+
}
|
|
1221
|
+
if (PLAIN_WRAPPERS.has(command)) {
|
|
1222
|
+
const value = this.parseRequiredArgument();
|
|
1223
|
+
return command.startsWith("text") || command === "mbox" ? value : value.trim();
|
|
1224
|
+
}
|
|
1225
|
+
if (command === "begin") {
|
|
1226
|
+
return this.parseEnvironment();
|
|
1227
|
+
}
|
|
1228
|
+
if (command === "end") {
|
|
1229
|
+
this.supported = false;
|
|
1230
|
+
return "";
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
this.supported = false;
|
|
1234
|
+
return `\\${command}`;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
parseOperator(
|
|
1238
|
+
operator ,
|
|
1239
|
+
inlineLowerStyle ,
|
|
1240
|
+
displayLimits ,
|
|
1241
|
+
spaced = false,
|
|
1242
|
+
) {
|
|
1243
|
+
let useDisplayLimits = displayLimits;
|
|
1244
|
+
let modifierPosition = this.position;
|
|
1245
|
+
while (modifierPosition < this.source.length && /[ \t]/.test(this.source[modifierPosition] ?? "")) {
|
|
1246
|
+
modifierPosition++;
|
|
1247
|
+
}
|
|
1248
|
+
const modifier = /^\\(limits|nolimits)(?![A-Za-z])/.exec(this.source.slice(modifierPosition));
|
|
1249
|
+
if (modifier) {
|
|
1250
|
+
useDisplayLimits = modifier[1] === "limits";
|
|
1251
|
+
this.position = modifierPosition + modifier[0].length;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
let lower ;
|
|
1255
|
+
let upper ;
|
|
1256
|
+
while (true) {
|
|
1257
|
+
let scriptPosition = this.position;
|
|
1258
|
+
while (scriptPosition < this.source.length && /[ \t]/.test(this.source[scriptPosition] ?? "")) {
|
|
1259
|
+
scriptPosition++;
|
|
1260
|
+
}
|
|
1261
|
+
const kind = this.source[scriptPosition];
|
|
1262
|
+
if (kind !== "_" && kind !== "^") {
|
|
1263
|
+
break;
|
|
1264
|
+
}
|
|
1265
|
+
this.position = scriptPosition + 1;
|
|
1266
|
+
const value = normalizeOutput(this.parseRequiredArgument(false)).replaceAll(" ", "");
|
|
1267
|
+
if (kind === "_") {
|
|
1268
|
+
if (lower !== undefined) {
|
|
1269
|
+
this.supported = false;
|
|
1270
|
+
}
|
|
1271
|
+
lower = value;
|
|
1272
|
+
} else {
|
|
1273
|
+
if (upper !== undefined) {
|
|
1274
|
+
this.supported = false;
|
|
1275
|
+
}
|
|
1276
|
+
upper = value;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
if (this.display && useDisplayLimits && (lower !== undefined || upper !== undefined)) {
|
|
1281
|
+
const index = this.layoutNodes.push({ type: "operator", operator, lower, upper }) - 1;
|
|
1282
|
+
return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
let rendered = operator;
|
|
1286
|
+
if (lower !== undefined) {
|
|
1287
|
+
rendered += inlineLowerStyle === "bracket" ? `[${lower}]` : formatScript(lower, "sub");
|
|
1288
|
+
}
|
|
1289
|
+
if (upper !== undefined) {
|
|
1290
|
+
rendered += formatScript(upper, "sup");
|
|
1291
|
+
}
|
|
1292
|
+
return spaced ? ` ${rendered} ` : rendered;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
parseRequiredArgument(stackFractions = true) {
|
|
1296
|
+
const previousStackFractions = this.stackFractions;
|
|
1297
|
+
this.stackFractions = previousStackFractions && stackFractions;
|
|
1298
|
+
const value = this.parseRequiredArgumentValue();
|
|
1299
|
+
this.stackFractions = previousStackFractions;
|
|
1300
|
+
return value;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
parseRequiredArgumentValue() {
|
|
1304
|
+
while (this.position < this.source.length && /\s/.test(this.source[this.position] ?? "")) {
|
|
1305
|
+
this.position++;
|
|
1306
|
+
}
|
|
1307
|
+
if (this.position >= this.source.length) {
|
|
1308
|
+
this.supported = false;
|
|
1309
|
+
return "";
|
|
1310
|
+
}
|
|
1311
|
+
if (this.source[this.position] === "{") {
|
|
1312
|
+
this.position++;
|
|
1313
|
+
return this.parseSequence("}");
|
|
1314
|
+
}
|
|
1315
|
+
if (this.source[this.position] === "\\") {
|
|
1316
|
+
return this.parseCommand();
|
|
1317
|
+
}
|
|
1318
|
+
const value = this.source[this.position] ?? "";
|
|
1319
|
+
this.position++;
|
|
1320
|
+
return value;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
parseOptionalArgument() {
|
|
1324
|
+
while (this.position < this.source.length && /[ \t]/.test(this.source[this.position] ?? "")) {
|
|
1325
|
+
this.position++;
|
|
1326
|
+
}
|
|
1327
|
+
if (this.source[this.position] !== "[") {
|
|
1328
|
+
return undefined;
|
|
1329
|
+
}
|
|
1330
|
+
const end = this.source.indexOf("]", this.position + 1);
|
|
1331
|
+
if (end < 0) {
|
|
1332
|
+
this.supported = false;
|
|
1333
|
+
return undefined;
|
|
1334
|
+
}
|
|
1335
|
+
const value = this.source.slice(this.position + 1, end);
|
|
1336
|
+
this.position = end + 1;
|
|
1337
|
+
return this.renderNested(value);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
readRawGroup() {
|
|
1341
|
+
while (this.position < this.source.length && /[ \t]/.test(this.source[this.position] ?? "")) {
|
|
1342
|
+
this.position++;
|
|
1343
|
+
}
|
|
1344
|
+
if (this.source[this.position] !== "{") {
|
|
1345
|
+
this.supported = false;
|
|
1346
|
+
return undefined;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
const start = ++this.position;
|
|
1350
|
+
let depth = 1;
|
|
1351
|
+
while (this.position < this.source.length) {
|
|
1352
|
+
const character = this.source[this.position];
|
|
1353
|
+
if (character === "\\") {
|
|
1354
|
+
this.position += 2;
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
if (character === "{") depth++;
|
|
1358
|
+
if (character === "}") depth--;
|
|
1359
|
+
if (depth === 0) {
|
|
1360
|
+
const value = this.source.slice(start, this.position);
|
|
1361
|
+
this.position++;
|
|
1362
|
+
return value;
|
|
1363
|
+
}
|
|
1364
|
+
this.position++;
|
|
1365
|
+
}
|
|
1366
|
+
this.supported = false;
|
|
1367
|
+
return undefined;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
splitEnvironmentRows(body ) {
|
|
1371
|
+
return body.split(/\\\\(?:\[[^\]\n]*\])?/);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
parseEnvironment() {
|
|
1375
|
+
const environment = this.readRawGroup();
|
|
1376
|
+
if (!environment) {
|
|
1377
|
+
return "";
|
|
1378
|
+
}
|
|
1379
|
+
const endMarker = `\\end{${environment}}`;
|
|
1380
|
+
const end = this.source.indexOf(endMarker, this.position);
|
|
1381
|
+
if (end < 0) {
|
|
1382
|
+
this.supported = false;
|
|
1383
|
+
return "";
|
|
1384
|
+
}
|
|
1385
|
+
const body = this.source.slice(this.position, end);
|
|
1386
|
+
this.position = end + endMarker.length;
|
|
1387
|
+
|
|
1388
|
+
if (environment === "equation" || environment === "equation*" || environment === "displaymath") {
|
|
1389
|
+
return this.renderNested(body).trim();
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
if (
|
|
1393
|
+
environment === "aligned" ||
|
|
1394
|
+
environment === "align" ||
|
|
1395
|
+
environment === "align*" ||
|
|
1396
|
+
environment === "alignedat" ||
|
|
1397
|
+
environment === "alignat" ||
|
|
1398
|
+
environment === "alignat*" ||
|
|
1399
|
+
environment === "gather" ||
|
|
1400
|
+
environment === "gathered" ||
|
|
1401
|
+
environment === "multline" ||
|
|
1402
|
+
environment === "multline*" ||
|
|
1403
|
+
environment === "split"
|
|
1404
|
+
) {
|
|
1405
|
+
const alignedAt = ["alignedat", "alignat", "alignat*"].includes(environment);
|
|
1406
|
+
const alignedBody = alignedAt ? body.replace(/^\s*\{[^}]*\}/, "") : body;
|
|
1407
|
+
return this.splitEnvironmentRows(alignedBody)
|
|
1408
|
+
.map((row) => {
|
|
1409
|
+
const cells = row.split("&");
|
|
1410
|
+
const source = alignedAt
|
|
1411
|
+
? Array.from({ length: Math.ceil(cells.length / 2) }, (_, index) =>
|
|
1412
|
+
cells.slice(index * 2, index * 2 + 2).join(""),
|
|
1413
|
+
).join(" ")
|
|
1414
|
+
: cells.join("");
|
|
1415
|
+
return this.renderNested(source).trim();
|
|
1416
|
+
})
|
|
1417
|
+
.filter(Boolean)
|
|
1418
|
+
.join("\n");
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
if (environment === "cases" || environment === "cases*") {
|
|
1422
|
+
return this.renderCases(body);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
if (
|
|
1426
|
+
["array", "matrix", "smallmatrix", "pmatrix", "bmatrix", "Bmatrix", "vmatrix", "Vmatrix"].includes(environment)
|
|
1427
|
+
) {
|
|
1428
|
+
const matrixBody = environment === "array" ? body.replace(/^\s*\{[^}]*\}/, "") : body;
|
|
1429
|
+
return this.renderMatrix(environment, matrixBody);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
this.supported = false;
|
|
1433
|
+
return body;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
renderCases(body ) {
|
|
1437
|
+
const rows = this.splitEnvironmentRows(body)
|
|
1438
|
+
.map((row) => row.split("&").map((cell) => this.renderNested(cell, false).trim()))
|
|
1439
|
+
.filter((row) => row.some(Boolean));
|
|
1440
|
+
const valueWidth = Math.max(0, ...rows.map((row) => visibleWidth((row[0] ?? "").replace(/,\s*$/, ""))));
|
|
1441
|
+
const contents = rows.map((row) => {
|
|
1442
|
+
const value = (row[0] ?? "").replace(/,\s*$/, "");
|
|
1443
|
+
const condition = row[1] ?? "";
|
|
1444
|
+
if (!condition) {
|
|
1445
|
+
return value;
|
|
1446
|
+
}
|
|
1447
|
+
const conditionPrefix = /^(?:if|when|for|otherwise)\b/i.test(condition) ? " " : " if ";
|
|
1448
|
+
return `${value}${PROTECTED_SPACE.repeat(valueWidth - visibleWidth(value))}${conditionPrefix}${condition}`;
|
|
1449
|
+
});
|
|
1450
|
+
if (contents.length <= 1) {
|
|
1451
|
+
return contents.length === 0 ? "" : `⎧ ${contents[0]}`;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
const middle = Math.floor(contents.length / 2);
|
|
1455
|
+
const visualRows = [...contents];
|
|
1456
|
+
if (contents.length % 2 === 0) {
|
|
1457
|
+
visualRows.splice(middle, 0, undefined);
|
|
1458
|
+
}
|
|
1459
|
+
const lines = visualRows.map((content, index) => {
|
|
1460
|
+
const delimiter = index === 0 ? "⎧" : index === visualRows.length - 1 ? "⎩" : "⎨";
|
|
1461
|
+
return content === undefined ? delimiter : `${delimiter} ${content}`;
|
|
1462
|
+
});
|
|
1463
|
+
const index = this.layoutNodes.push({ type: "matrix", lines, baseline: middle }) - 1;
|
|
1464
|
+
return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
renderMatrix(environment , body ) {
|
|
1468
|
+
const matrix = this.splitEnvironmentRows(body)
|
|
1469
|
+
.map((row) => row.split("&").map((cell) => this.renderNested(cell, false).trim()))
|
|
1470
|
+
.filter((row) => row.some(Boolean));
|
|
1471
|
+
const columnCount = Math.max(0, ...matrix.map((row) => row.length));
|
|
1472
|
+
const columnWidths = Array.from({ length: columnCount }, (_, column) =>
|
|
1473
|
+
Math.max(0, ...matrix.map((row) => visibleWidth(row[column] ?? ""))),
|
|
1474
|
+
);
|
|
1475
|
+
const rows = matrix.map((row) =>
|
|
1476
|
+
Array.from({ length: columnCount }, (_, column) => {
|
|
1477
|
+
const cell = row[column] ?? "";
|
|
1478
|
+
return `${cell}${PROTECTED_SPACE.repeat(Math.max(0, (columnWidths[column] ?? 0) - visibleWidth(cell)))}`;
|
|
1479
|
+
}).join(" │ "),
|
|
1480
|
+
);
|
|
1481
|
+
|
|
1482
|
+
let lines ;
|
|
1483
|
+
if (environment === "array" || environment === "matrix" || environment === "smallmatrix") {
|
|
1484
|
+
lines = rows;
|
|
1485
|
+
} else {
|
|
1486
|
+
const delimiters = {
|
|
1487
|
+
pmatrix: ["⎛", "⎞", "⎜", "⎟", "⎝", "⎠"],
|
|
1488
|
+
bmatrix: ["⎡", "⎤", "⎢", "⎥", "⎣", "⎦"],
|
|
1489
|
+
Bmatrix: ["⎧", "⎫", "⎨", "⎬", "⎩", "⎭"],
|
|
1490
|
+
vmatrix: ["│", "│", "│", "│", "│", "│"],
|
|
1491
|
+
Vmatrix: ["║", "║", "║", "║", "║", "║"],
|
|
1492
|
+
};
|
|
1493
|
+
const delimiter = delimiters[environment];
|
|
1494
|
+
if (!delimiter) {
|
|
1495
|
+
this.supported = false;
|
|
1496
|
+
return rows.join("\n");
|
|
1497
|
+
}
|
|
1498
|
+
lines = rows.map((row, index) => {
|
|
1499
|
+
const left = index === 0 ? delimiter[0] : index === rows.length - 1 ? delimiter[4] : delimiter[2];
|
|
1500
|
+
const right = index === 0 ? delimiter[1] : index === rows.length - 1 ? delimiter[5] : delimiter[3];
|
|
1501
|
+
return `${left} ${row} ${right}`;
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
if (lines.length <= 1) {
|
|
1506
|
+
return lines[0] ?? "";
|
|
1507
|
+
}
|
|
1508
|
+
const index = this.layoutNodes.push({ type: "matrix", lines, baseline: 0 }) - 1;
|
|
1509
|
+
return `${LAYOUT_MARKER_START}${index}${LAYOUT_MARKER_END}`;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
renderNested(source , stackFractions = true) {
|
|
1513
|
+
const rendered = new LatexParser(source, this.layoutNodes, this.display && stackFractions).render();
|
|
1514
|
+
if (rendered === undefined) {
|
|
1515
|
+
this.supported = false;
|
|
1516
|
+
return source;
|
|
1517
|
+
}
|
|
1518
|
+
return rendered;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* Render a basic LaTeX math expression as terminal-friendly Unicode text.
|
|
1529
|
+
* Returns undefined when the expression contains unsupported or malformed syntax.
|
|
1530
|
+
*/
|
|
1531
|
+
export function renderLatex(source , options = {}) {
|
|
1532
|
+
const layoutNodes = [];
|
|
1533
|
+
const rendered = new LatexParser(source, layoutNodes, options.display === true).render();
|
|
1534
|
+
if (rendered === undefined) {
|
|
1535
|
+
return undefined;
|
|
1536
|
+
}
|
|
1537
|
+
if (layoutNodes.length === 0) {
|
|
1538
|
+
return rendered.replaceAll(PROTECTED_SPACE, " ");
|
|
1539
|
+
}
|
|
1540
|
+
const lines = renderLayout(rendered, layoutNodes).lines;
|
|
1541
|
+
const indentation = Math.min(
|
|
1542
|
+
...lines.filter((line) => line.trim()).map((line) => line.length - line.trimStart().length),
|
|
1543
|
+
);
|
|
1544
|
+
return lines
|
|
1545
|
+
.map((line) => line.slice(indentation).trimEnd())
|
|
1546
|
+
.join("\n")
|
|
1547
|
+
.trimEnd()
|
|
1548
|
+
.replaceAll(PROTECTED_SPACE, " ");
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/**
|
|
1552
|
+
* TeX command -> Unicode table.
|
|
1553
|
+
*
|
|
1554
|
+
* Exported because the bitmap renderer (`src/tex/parse.js`) shares it: a formula drawn as pixels must map a
|
|
1555
|
+
* command to the same character the text renderer picks, or the two disagree about what a name looks like.
|
|
1556
|
+
*/
|
|
1557
|
+
export { SYMBOLS };
|