@dskripchenko/math 0.0.0-stage → 0.1.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 +75 -2
- package/dist/index.cjs +1875 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.mjs +1868 -0
- package/package.json +70 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1875 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
// src/mathml.ts
|
|
3
|
+
function escape(text) {
|
|
4
|
+
return text.replace(/[&<>"]/g, (c) => ({
|
|
5
|
+
"&": "&",
|
|
6
|
+
"<": "<",
|
|
7
|
+
">": ">",
|
|
8
|
+
"\"": """
|
|
9
|
+
})[c]);
|
|
10
|
+
}
|
|
11
|
+
var em = (n) => `${Math.round(n * 1e3) / 1e3}em`;
|
|
12
|
+
/** Function application, invisible: `sin x` read as a function of x. */
|
|
13
|
+
var APPLY = "<mo lspace=\"0\" rspace=\"0.1667em\">⁡</mo>";
|
|
14
|
+
function toMathML(node) {
|
|
15
|
+
switch (node.type) {
|
|
16
|
+
case "ident": return `<mi${node.upright && [...node.text].length === 1 ? " mathvariant=\"normal\"" : ""}>${escape(node.text)}</mi>`;
|
|
17
|
+
case "number": return `<mn>${escape(node.text)}</mn>`;
|
|
18
|
+
case "op": {
|
|
19
|
+
const a = [];
|
|
20
|
+
if (node.kind === "large" && [...node.text].length === 1) a.push("largeop=\"true\"");
|
|
21
|
+
if (node.movableLimits) a.push("movablelimits=\"true\"");
|
|
22
|
+
if (node.kind === "prefix") a.push("form=\"prefix\"", "lspace=\"0\"", "rspace=\"0.1667em\"");
|
|
23
|
+
if (node.stretchy) {
|
|
24
|
+
a.push("stretchy=\"true\"", "symmetric=\"true\"");
|
|
25
|
+
if (node.size) a.push(`minsize="${em(node.size)}"`, `maxsize="${em(node.size)}"`);
|
|
26
|
+
} else if (node.kind === "open" || node.kind === "close" || node.text === "|" || node.text === "‖" || node.text === "/") a.push("stretchy=\"false\"");
|
|
27
|
+
if (node.kind === "rel" && node.text.length === 1 && ":".includes(node.text)) a.push("lspace=\"0.2778em\"", "rspace=\"0.2778em\"");
|
|
28
|
+
if (node.kind === "punct") a.push("separator=\"true\"", "lspace=\"0\"", "rspace=\"0.1667em\"");
|
|
29
|
+
if (node.kind === "ord") a.push("lspace=\"0\"", "rspace=\"0\"");
|
|
30
|
+
return `<mo${a.length ? " " + a.join(" ") : ""}>${escape(node.text)}</mo>`;
|
|
31
|
+
}
|
|
32
|
+
case "text": return `<mtext>${escape(node.text.replace(/^ | $/g, "\xA0"))}</mtext>`;
|
|
33
|
+
case "space": return node.width >= 0 ? `<mspace width="${em(node.width)}"/>` : `<mspace style="margin-left:${em(node.width)}"/>`;
|
|
34
|
+
case "row": return `<mrow>${rowChildren(node.children)}</mrow>`;
|
|
35
|
+
case "scripts": {
|
|
36
|
+
const base = toMathML(node.base);
|
|
37
|
+
const fn = isFunction(node.base) ? APPLY : "";
|
|
38
|
+
const sub = node.sub ? toMathML(node.sub) : "";
|
|
39
|
+
const sup = node.sup ? toMathML(node.sup) : "";
|
|
40
|
+
if (node.over) {
|
|
41
|
+
if (node.sub && node.sup) return `<munderover>${base}${sub}${sup}</munderover>${fn}`;
|
|
42
|
+
if (node.sub) return `<munder>${base}${sub}</munder>${fn}`;
|
|
43
|
+
return `<mover>${base}${sup}</mover>${fn}`;
|
|
44
|
+
}
|
|
45
|
+
if (node.sub && node.sup) return `<msubsup>${base}${sub}${sup}</msubsup>${fn}`;
|
|
46
|
+
if (node.sub) return `<msub>${base}${sub}</msub>${fn}`;
|
|
47
|
+
return `<msup>${base}${sup}</msup>${fn}`;
|
|
48
|
+
}
|
|
49
|
+
case "frac": {
|
|
50
|
+
const frac = `<mfrac${node.thickness === 0 ? " linethickness=\"0\"" : ""}>${toMathML(node.num)}${toMathML(node.den)}</mfrac>`;
|
|
51
|
+
if (node.style === "display") return `<mrow displaystyle="true" scriptlevel="0">${frac}</mrow>`;
|
|
52
|
+
if (node.style === "text") return `<mrow displaystyle="false" scriptlevel="0">${frac}</mrow>`;
|
|
53
|
+
return frac;
|
|
54
|
+
}
|
|
55
|
+
case "sqrt": return node.index ? `<mroot>${toMathML(node.body)}${toMathML(node.index)}</mroot>` : `<msqrt>${toMathML(node.body)}</msqrt>`;
|
|
56
|
+
case "fenced": return `<mrow>${fence(node.open, "prefix")}${toMathML(node.body)}${fence(node.close, "postfix")}</mrow>`;
|
|
57
|
+
case "accent": {
|
|
58
|
+
const mark = `<mo stretchy="${node.stretchy ? "true" : "false"}">${escape(node.mark)}</mo>`;
|
|
59
|
+
return node.under ? `<munder accentunder="true">${toMathML(node.base)}${mark}</munder>` : `<mover accent="true">${toMathML(node.base)}${mark}</mover>`;
|
|
60
|
+
}
|
|
61
|
+
case "table": {
|
|
62
|
+
const rows = node.rows.map((cells) => {
|
|
63
|
+
return `<mtr>${cells.map((cell, i) => {
|
|
64
|
+
const style = [`text-align:${alignOf(node, i)}`];
|
|
65
|
+
if (node.aligned) style.push(i % 2 === 0 ? "padding-right:0" : "padding-left:0", i % 2 === 1 && i < cells.length - 1 ? "padding-right:2em" : "");
|
|
66
|
+
if (node.lines?.[i] === "solid") style.push("border-right:1px solid");
|
|
67
|
+
return `<mtd style="${style.filter(Boolean).join(";")}">${toMathML(cell)}</mtd>`;
|
|
68
|
+
}).join("")}</mtr>`;
|
|
69
|
+
}).join("");
|
|
70
|
+
const table = `<mtable${node.small ? " scriptlevel=\"1\"" : ""}>${rows}</mtable>`;
|
|
71
|
+
if (!node.open && !node.close) return table;
|
|
72
|
+
return `<mrow>${fence(node.open ?? "", "prefix")}${table}${fence(node.close ?? "", "postfix")}</mrow>`;
|
|
73
|
+
}
|
|
74
|
+
case "style": {
|
|
75
|
+
const a = [];
|
|
76
|
+
if (node.display !== void 0) a.push(`displaystyle="${node.display}"`);
|
|
77
|
+
if (node.scriptLevel !== void 0) a.push(`scriptlevel="${node.scriptLevel}"`);
|
|
78
|
+
if (node.color) a.push(`style="color:${escape(node.color)}"`);
|
|
79
|
+
return `<mrow ${a.join(" ")}>${toMathML(node.body)}</mrow>`;
|
|
80
|
+
}
|
|
81
|
+
case "phantom": return `<mphantom>${toMathML(node.body)}</mphantom>`;
|
|
82
|
+
case "boxed": return `<mrow style="border:1px solid;padding:0.15em 0.3em">${toMathML(node.body)}</mrow>`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** A row's children; a function name followed by its argument gets the invisible application sign. */
|
|
86
|
+
function rowChildren(children) {
|
|
87
|
+
return children.map((c, i) => toMathML(c) + (c.type === "ident" && isFunction(c) && i < children.length - 1 ? APPLY : "")).join("");
|
|
88
|
+
}
|
|
89
|
+
function isFunction(node) {
|
|
90
|
+
return node.type === "ident" && node.upright === true && [...node.text].length > 1;
|
|
91
|
+
}
|
|
92
|
+
function alignOf(node, i) {
|
|
93
|
+
const a = node.aligned ? i % 2 === 0 ? "r" : "l" : node.align[Math.min(i, node.align.length - 1)] ?? "c";
|
|
94
|
+
return a === "l" ? "left" : a === "r" ? "right" : "center";
|
|
95
|
+
}
|
|
96
|
+
function fence(text, form) {
|
|
97
|
+
if (text === "") return "";
|
|
98
|
+
return `<mo fence="true" form="${form}" stretchy="true" symmetric="true">${escape(text)}</mo>`;
|
|
99
|
+
}
|
|
100
|
+
// src/fonts.ts
|
|
101
|
+
/** Where a style's capitals, small letters and digits start (null: no digits in that style). */
|
|
102
|
+
var BASES = {
|
|
103
|
+
bold: {
|
|
104
|
+
upper: 119808,
|
|
105
|
+
lower: 119834,
|
|
106
|
+
digit: 120782
|
|
107
|
+
},
|
|
108
|
+
italic: {
|
|
109
|
+
upper: 119860,
|
|
110
|
+
lower: 119886,
|
|
111
|
+
digit: null
|
|
112
|
+
},
|
|
113
|
+
"bold-italic": {
|
|
114
|
+
upper: 119912,
|
|
115
|
+
lower: 119938,
|
|
116
|
+
digit: 120782
|
|
117
|
+
},
|
|
118
|
+
script: {
|
|
119
|
+
upper: 119964,
|
|
120
|
+
lower: 119990,
|
|
121
|
+
digit: null
|
|
122
|
+
},
|
|
123
|
+
"bold-script": {
|
|
124
|
+
upper: 120016,
|
|
125
|
+
lower: 120042,
|
|
126
|
+
digit: 120782
|
|
127
|
+
},
|
|
128
|
+
fraktur: {
|
|
129
|
+
upper: 120068,
|
|
130
|
+
lower: 120094,
|
|
131
|
+
digit: null
|
|
132
|
+
},
|
|
133
|
+
"double-struck": {
|
|
134
|
+
upper: 120120,
|
|
135
|
+
lower: 120146,
|
|
136
|
+
digit: 120792
|
|
137
|
+
},
|
|
138
|
+
"bold-fraktur": {
|
|
139
|
+
upper: 120172,
|
|
140
|
+
lower: 120198,
|
|
141
|
+
digit: 120782
|
|
142
|
+
},
|
|
143
|
+
"sans-serif": {
|
|
144
|
+
upper: 120224,
|
|
145
|
+
lower: 120250,
|
|
146
|
+
digit: 120802
|
|
147
|
+
},
|
|
148
|
+
"bold-sans-serif": {
|
|
149
|
+
upper: 120276,
|
|
150
|
+
lower: 120302,
|
|
151
|
+
digit: 120812
|
|
152
|
+
},
|
|
153
|
+
"sans-serif-italic": {
|
|
154
|
+
upper: 120328,
|
|
155
|
+
lower: 120354,
|
|
156
|
+
digit: 120802
|
|
157
|
+
},
|
|
158
|
+
monospace: {
|
|
159
|
+
upper: 120432,
|
|
160
|
+
lower: 120458,
|
|
161
|
+
digit: 120822
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
/** The letters Unicode had before the block: their place in it is empty. */
|
|
165
|
+
var HOLES = {
|
|
166
|
+
italic: { h: "ℎ" },
|
|
167
|
+
script: {
|
|
168
|
+
B: "ℬ",
|
|
169
|
+
E: "ℰ",
|
|
170
|
+
F: "ℱ",
|
|
171
|
+
H: "ℋ",
|
|
172
|
+
I: "ℐ",
|
|
173
|
+
L: "ℒ",
|
|
174
|
+
M: "ℳ",
|
|
175
|
+
R: "ℛ",
|
|
176
|
+
e: "ℯ",
|
|
177
|
+
g: "ℊ",
|
|
178
|
+
o: "ℴ"
|
|
179
|
+
},
|
|
180
|
+
fraktur: {
|
|
181
|
+
C: "ℭ",
|
|
182
|
+
H: "ℌ",
|
|
183
|
+
I: "ℑ",
|
|
184
|
+
R: "ℜ",
|
|
185
|
+
Z: "ℨ"
|
|
186
|
+
},
|
|
187
|
+
"double-struck": {
|
|
188
|
+
C: "ℂ",
|
|
189
|
+
H: "ℍ",
|
|
190
|
+
N: "ℕ",
|
|
191
|
+
P: "ℙ",
|
|
192
|
+
Q: "ℚ",
|
|
193
|
+
R: "ℝ",
|
|
194
|
+
Z: "ℤ"
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
/** Bold and bold italic Greek: where capitals Α–Ω and small α–ω start. */
|
|
198
|
+
var GREEK = {
|
|
199
|
+
bold: {
|
|
200
|
+
upper: 120488,
|
|
201
|
+
lower: 120514
|
|
202
|
+
},
|
|
203
|
+
"bold-italic": {
|
|
204
|
+
upper: 120604,
|
|
205
|
+
lower: 120630
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
/** A text in a letter style: Latin letters and digits mapped, anything else as it is. */
|
|
209
|
+
function styled(text, font) {
|
|
210
|
+
if (font === "normal") return text;
|
|
211
|
+
const base = BASES[font];
|
|
212
|
+
const holes = HOLES[font] ?? {};
|
|
213
|
+
const greek = GREEK[font];
|
|
214
|
+
let out = "";
|
|
215
|
+
for (const ch of text) {
|
|
216
|
+
const code = ch.codePointAt(0);
|
|
217
|
+
if (holes[ch]) out += holes[ch];
|
|
218
|
+
else if (code >= 65 && code <= 90) out += String.fromCodePoint(base.upper + code - 65);
|
|
219
|
+
else if (code >= 97 && code <= 122) out += String.fromCodePoint(base.lower + code - 97);
|
|
220
|
+
else if (code >= 48 && code <= 57 && base.digit !== null) out += String.fromCodePoint(base.digit + code - 48);
|
|
221
|
+
else if (greek && code >= 913 && code <= 937) out += String.fromCodePoint(greek.upper + code - 913);
|
|
222
|
+
else if (greek && code >= 945 && code <= 969) out += String.fromCodePoint(greek.lower + code - 945);
|
|
223
|
+
else out += ch;
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
/** The style a command sets. */
|
|
228
|
+
var FONT_COMMANDS = {
|
|
229
|
+
mathrm: "normal",
|
|
230
|
+
mathup: "normal",
|
|
231
|
+
textrm: "normal",
|
|
232
|
+
mathbf: "bold",
|
|
233
|
+
textbf: "bold",
|
|
234
|
+
bf: "bold",
|
|
235
|
+
mathit: "italic",
|
|
236
|
+
textit: "italic",
|
|
237
|
+
mathbb: "double-struck",
|
|
238
|
+
Bbb: "double-struck",
|
|
239
|
+
mathcal: "script",
|
|
240
|
+
mathscr: "script",
|
|
241
|
+
mathfrak: "fraktur",
|
|
242
|
+
mathsf: "sans-serif",
|
|
243
|
+
textsf: "sans-serif",
|
|
244
|
+
mathtt: "monospace",
|
|
245
|
+
texttt: "monospace",
|
|
246
|
+
boldsymbol: "bold-italic",
|
|
247
|
+
bm: "bold-italic",
|
|
248
|
+
pmb: "bold-italic",
|
|
249
|
+
mathbfit: "bold-italic",
|
|
250
|
+
mathsfit: "sans-serif-italic"
|
|
251
|
+
};
|
|
252
|
+
// src/symbols.ts
|
|
253
|
+
/**
|
|
254
|
+
* What a TeX command is: a letter, an operator, a relation, a big operator,
|
|
255
|
+
* a function name, a delimiter. Each maps to the character it stands for.
|
|
256
|
+
*/
|
|
257
|
+
/** Greek letters and other letter-like symbols: identifiers. */
|
|
258
|
+
var LETTERS = {
|
|
259
|
+
alpha: "α",
|
|
260
|
+
beta: "β",
|
|
261
|
+
gamma: "γ",
|
|
262
|
+
delta: "δ",
|
|
263
|
+
epsilon: "ϵ",
|
|
264
|
+
varepsilon: "ε",
|
|
265
|
+
zeta: "ζ",
|
|
266
|
+
eta: "η",
|
|
267
|
+
theta: "θ",
|
|
268
|
+
vartheta: "ϑ",
|
|
269
|
+
iota: "ι",
|
|
270
|
+
kappa: "κ",
|
|
271
|
+
varkappa: "ϰ",
|
|
272
|
+
lambda: "λ",
|
|
273
|
+
mu: "μ",
|
|
274
|
+
nu: "ν",
|
|
275
|
+
xi: "ξ",
|
|
276
|
+
omicron: "ο",
|
|
277
|
+
pi: "π",
|
|
278
|
+
varpi: "ϖ",
|
|
279
|
+
rho: "ρ",
|
|
280
|
+
varrho: "ϱ",
|
|
281
|
+
sigma: "σ",
|
|
282
|
+
varsigma: "ς",
|
|
283
|
+
tau: "τ",
|
|
284
|
+
upsilon: "υ",
|
|
285
|
+
phi: "ϕ",
|
|
286
|
+
varphi: "φ",
|
|
287
|
+
chi: "χ",
|
|
288
|
+
psi: "ψ",
|
|
289
|
+
omega: "ω",
|
|
290
|
+
digamma: "ϝ",
|
|
291
|
+
Gamma: "Γ",
|
|
292
|
+
Delta: "Δ",
|
|
293
|
+
Theta: "Θ",
|
|
294
|
+
Lambda: "Λ",
|
|
295
|
+
Xi: "Ξ",
|
|
296
|
+
Pi: "Π",
|
|
297
|
+
Sigma: "Σ",
|
|
298
|
+
Upsilon: "Υ",
|
|
299
|
+
Phi: "Φ",
|
|
300
|
+
Psi: "Ψ",
|
|
301
|
+
Omega: "Ω",
|
|
302
|
+
varGamma: "𝛤",
|
|
303
|
+
varDelta: "𝛥",
|
|
304
|
+
varTheta: "𝛩",
|
|
305
|
+
varLambda: "𝛬",
|
|
306
|
+
varXi: "𝛯",
|
|
307
|
+
varPi: "𝛱",
|
|
308
|
+
varSigma: "𝛴",
|
|
309
|
+
varUpsilon: "𝛶",
|
|
310
|
+
varPhi: "𝛷",
|
|
311
|
+
varPsi: "𝛹",
|
|
312
|
+
varOmega: "𝛺",
|
|
313
|
+
aleph: "ℵ",
|
|
314
|
+
beth: "ℶ",
|
|
315
|
+
gimel: "ℷ",
|
|
316
|
+
hbar: "ℏ",
|
|
317
|
+
hslash: "ℏ",
|
|
318
|
+
ell: "ℓ",
|
|
319
|
+
wp: "℘",
|
|
320
|
+
Re: "ℜ",
|
|
321
|
+
Im: "ℑ",
|
|
322
|
+
imath: "ı",
|
|
323
|
+
jmath: "ȷ",
|
|
324
|
+
partial: "∂",
|
|
325
|
+
infty: "∞",
|
|
326
|
+
nabla: "∇",
|
|
327
|
+
emptyset: "∅",
|
|
328
|
+
varnothing: "⌀",
|
|
329
|
+
top: "⊤",
|
|
330
|
+
bot: "⊥",
|
|
331
|
+
angle: "∠",
|
|
332
|
+
measuredangle: "∡",
|
|
333
|
+
triangle: "△",
|
|
334
|
+
square: "□",
|
|
335
|
+
Box: "□",
|
|
336
|
+
blacksquare: "■",
|
|
337
|
+
diamond: "⋄",
|
|
338
|
+
Diamond: "◊",
|
|
339
|
+
lozenge: "◊",
|
|
340
|
+
clubsuit: "♣",
|
|
341
|
+
diamondsuit: "♢",
|
|
342
|
+
heartsuit: "♡",
|
|
343
|
+
spadesuit: "♠",
|
|
344
|
+
flat: "♭",
|
|
345
|
+
natural: "♮",
|
|
346
|
+
sharp: "♯",
|
|
347
|
+
prime: "′",
|
|
348
|
+
backprime: "‵",
|
|
349
|
+
degree: "°",
|
|
350
|
+
checkmark: "✓",
|
|
351
|
+
dagger: "†",
|
|
352
|
+
ddagger: "‡",
|
|
353
|
+
S: "§",
|
|
354
|
+
P: "¶",
|
|
355
|
+
copyright: "©",
|
|
356
|
+
pounds: "£",
|
|
357
|
+
euro: "€"
|
|
358
|
+
};
|
|
359
|
+
/** Binary operators: spaced as operations. */
|
|
360
|
+
var BINARY = {
|
|
361
|
+
pm: "±",
|
|
362
|
+
mp: "∓",
|
|
363
|
+
times: "×",
|
|
364
|
+
div: "÷",
|
|
365
|
+
cdot: "⋅",
|
|
366
|
+
ast: "∗",
|
|
367
|
+
star: "⋆",
|
|
368
|
+
circ: "∘",
|
|
369
|
+
bullet: "∙",
|
|
370
|
+
cap: "∩",
|
|
371
|
+
cup: "∪",
|
|
372
|
+
uplus: "⊎",
|
|
373
|
+
sqcap: "⊓",
|
|
374
|
+
sqcup: "⊔",
|
|
375
|
+
wedge: "∧",
|
|
376
|
+
land: "∧",
|
|
377
|
+
vee: "∨",
|
|
378
|
+
lor: "∨",
|
|
379
|
+
setminus: "∖",
|
|
380
|
+
smallsetminus: "∖",
|
|
381
|
+
oplus: "⊕",
|
|
382
|
+
ominus: "⊖",
|
|
383
|
+
otimes: "⊗",
|
|
384
|
+
oslash: "⊘",
|
|
385
|
+
odot: "⊙",
|
|
386
|
+
dagger: "†",
|
|
387
|
+
ddagger: "‡",
|
|
388
|
+
amalg: "⨿",
|
|
389
|
+
wr: "≀",
|
|
390
|
+
triangleleft: "◃",
|
|
391
|
+
triangleright: "▹",
|
|
392
|
+
bigtriangleup: "△",
|
|
393
|
+
bigtriangledown: "▽",
|
|
394
|
+
diamond: "⋄",
|
|
395
|
+
ltimes: "⋉",
|
|
396
|
+
rtimes: "⋊",
|
|
397
|
+
cdotp: "⋅",
|
|
398
|
+
centerdot: "⋅",
|
|
399
|
+
dotplus: "∔",
|
|
400
|
+
boxplus: "⊞",
|
|
401
|
+
boxminus: "⊟",
|
|
402
|
+
boxtimes: "⊠"
|
|
403
|
+
};
|
|
404
|
+
/** Relations and arrows. */
|
|
405
|
+
var RELATIONS = {
|
|
406
|
+
le: "≤",
|
|
407
|
+
leq: "≤",
|
|
408
|
+
ge: "≥",
|
|
409
|
+
geq: "≥",
|
|
410
|
+
leqslant: "⩽",
|
|
411
|
+
geqslant: "⩾",
|
|
412
|
+
ne: "≠",
|
|
413
|
+
neq: "≠",
|
|
414
|
+
lt: "<",
|
|
415
|
+
gt: ">",
|
|
416
|
+
ll: "≪",
|
|
417
|
+
gg: "≫",
|
|
418
|
+
lll: "⋘",
|
|
419
|
+
ggg: "⋙",
|
|
420
|
+
approx: "≈",
|
|
421
|
+
approxeq: "≊",
|
|
422
|
+
equiv: "≡",
|
|
423
|
+
sim: "∼",
|
|
424
|
+
simeq: "≃",
|
|
425
|
+
cong: "≅",
|
|
426
|
+
propto: "∝",
|
|
427
|
+
doteq: "≐",
|
|
428
|
+
triangleq: "≜",
|
|
429
|
+
coloneqq: "≔",
|
|
430
|
+
eqqcolon: "≕",
|
|
431
|
+
asymp: "≍",
|
|
432
|
+
prec: "≺",
|
|
433
|
+
succ: "≻",
|
|
434
|
+
preceq: "⪯",
|
|
435
|
+
succeq: "⪰",
|
|
436
|
+
lesssim: "≲",
|
|
437
|
+
gtrsim: "≳",
|
|
438
|
+
in: "∈",
|
|
439
|
+
notin: "∉",
|
|
440
|
+
ni: "∋",
|
|
441
|
+
owns: "∋",
|
|
442
|
+
subset: "⊂",
|
|
443
|
+
supset: "⊃",
|
|
444
|
+
subseteq: "⊆",
|
|
445
|
+
supseteq: "⊇",
|
|
446
|
+
subsetneq: "⊊",
|
|
447
|
+
supsetneq: "⊋",
|
|
448
|
+
nsubseteq: "⊈",
|
|
449
|
+
nsupseteq: "⊉",
|
|
450
|
+
sqsubset: "⊏",
|
|
451
|
+
sqsupset: "⊐",
|
|
452
|
+
sqsubseteq: "⊑",
|
|
453
|
+
sqsupseteq: "⊒",
|
|
454
|
+
mid: "∣",
|
|
455
|
+
nmid: "∤",
|
|
456
|
+
parallel: "∥",
|
|
457
|
+
nparallel: "∦",
|
|
458
|
+
perp: "⊥",
|
|
459
|
+
vdash: "⊢",
|
|
460
|
+
dashv: "⊣",
|
|
461
|
+
models: "⊨",
|
|
462
|
+
vDash: "⊨",
|
|
463
|
+
Vdash: "⊩",
|
|
464
|
+
smile: "⌣",
|
|
465
|
+
frown: "⌢",
|
|
466
|
+
bowtie: "⋈",
|
|
467
|
+
nless: "≮",
|
|
468
|
+
ngtr: "≯",
|
|
469
|
+
nleq: "≰",
|
|
470
|
+
ngeq: "≱",
|
|
471
|
+
nsim: "≁",
|
|
472
|
+
ncong: "≇",
|
|
473
|
+
therefore: "∴",
|
|
474
|
+
because: "∵",
|
|
475
|
+
to: "→",
|
|
476
|
+
rightarrow: "→",
|
|
477
|
+
leftarrow: "←",
|
|
478
|
+
gets: "←",
|
|
479
|
+
leftrightarrow: "↔",
|
|
480
|
+
Rightarrow: "⇒",
|
|
481
|
+
Leftarrow: "⇐",
|
|
482
|
+
Leftrightarrow: "⇔",
|
|
483
|
+
implies: "⟹",
|
|
484
|
+
impliedby: "⟸",
|
|
485
|
+
iff: "⟺",
|
|
486
|
+
mapsto: "↦",
|
|
487
|
+
longmapsto: "⟼",
|
|
488
|
+
longrightarrow: "⟶",
|
|
489
|
+
longleftarrow: "⟵",
|
|
490
|
+
longleftrightarrow: "⟷",
|
|
491
|
+
Longrightarrow: "⟹",
|
|
492
|
+
Longleftarrow: "⟸",
|
|
493
|
+
Longleftrightarrow: "⟺",
|
|
494
|
+
uparrow: "↑",
|
|
495
|
+
downarrow: "↓",
|
|
496
|
+
updownarrow: "↕",
|
|
497
|
+
Uparrow: "⇑",
|
|
498
|
+
Downarrow: "⇓",
|
|
499
|
+
Updownarrow: "⇕",
|
|
500
|
+
nearrow: "↗",
|
|
501
|
+
searrow: "↘",
|
|
502
|
+
swarrow: "↙",
|
|
503
|
+
nwarrow: "↖",
|
|
504
|
+
hookrightarrow: "↪",
|
|
505
|
+
hookleftarrow: "↩",
|
|
506
|
+
rightharpoonup: "⇀",
|
|
507
|
+
rightharpoondown: "⇁",
|
|
508
|
+
leftharpoonup: "↼",
|
|
509
|
+
leftharpoondown: "↽",
|
|
510
|
+
rightleftharpoons: "⇌",
|
|
511
|
+
leftrightharpoons: "⇋",
|
|
512
|
+
rightrightarrows: "⇉",
|
|
513
|
+
leftleftarrows: "⇇",
|
|
514
|
+
twoheadrightarrow: "↠",
|
|
515
|
+
rightsquigarrow: "⇝",
|
|
516
|
+
leadsto: "⇝",
|
|
517
|
+
nrightarrow: "↛",
|
|
518
|
+
nleftarrow: "↚",
|
|
519
|
+
nRightarrow: "⇏",
|
|
520
|
+
nLeftarrow: "⇍",
|
|
521
|
+
circlearrowleft: "↺",
|
|
522
|
+
circlearrowright: "↻"
|
|
523
|
+
};
|
|
524
|
+
/** Punctuation that TeX treats as such, and the dots. */
|
|
525
|
+
var PUNCT = {
|
|
526
|
+
colon: ":",
|
|
527
|
+
ldots: "…",
|
|
528
|
+
dots: "…",
|
|
529
|
+
dotsc: "…",
|
|
530
|
+
dotso: "…",
|
|
531
|
+
cdots: "⋯",
|
|
532
|
+
dotsb: "⋯",
|
|
533
|
+
dotsm: "⋯",
|
|
534
|
+
dotsi: "⋯",
|
|
535
|
+
vdots: "⋮",
|
|
536
|
+
ddots: "⋱",
|
|
537
|
+
iddots: "⋰",
|
|
538
|
+
forall: "∀",
|
|
539
|
+
exists: "∃",
|
|
540
|
+
nexists: "∄",
|
|
541
|
+
neg: "¬",
|
|
542
|
+
lnot: "¬",
|
|
543
|
+
complement: "∁"
|
|
544
|
+
};
|
|
545
|
+
/** Big operators: limits above and below in display, beside inline. */
|
|
546
|
+
var LARGE = {
|
|
547
|
+
sum: "∑",
|
|
548
|
+
prod: "∏",
|
|
549
|
+
coprod: "∐",
|
|
550
|
+
bigcup: "⋃",
|
|
551
|
+
bigcap: "⋂",
|
|
552
|
+
bigsqcup: "⨆",
|
|
553
|
+
bigvee: "⋁",
|
|
554
|
+
bigwedge: "⋀",
|
|
555
|
+
bigoplus: "⨁",
|
|
556
|
+
bigotimes: "⨂",
|
|
557
|
+
bigodot: "⨀",
|
|
558
|
+
biguplus: "⨄"
|
|
559
|
+
};
|
|
560
|
+
/** Integrals: limits beside them unless `\limits`. */
|
|
561
|
+
var INTEGRALS = {
|
|
562
|
+
int: "∫",
|
|
563
|
+
iint: "∬",
|
|
564
|
+
iiint: "∭",
|
|
565
|
+
iiiint: "⨌",
|
|
566
|
+
oint: "∮",
|
|
567
|
+
oiint: "∯",
|
|
568
|
+
oiiint: "∰",
|
|
569
|
+
intop: "∫",
|
|
570
|
+
smallint: "∫"
|
|
571
|
+
};
|
|
572
|
+
/** Function names set upright; the ones with limits take them under in display. */
|
|
573
|
+
var FUNCTIONS = /* @__PURE__ */ new Set([
|
|
574
|
+
"sin",
|
|
575
|
+
"cos",
|
|
576
|
+
"tan",
|
|
577
|
+
"cot",
|
|
578
|
+
"sec",
|
|
579
|
+
"csc",
|
|
580
|
+
"arcsin",
|
|
581
|
+
"arccos",
|
|
582
|
+
"arctan",
|
|
583
|
+
"arccot",
|
|
584
|
+
"sinh",
|
|
585
|
+
"cosh",
|
|
586
|
+
"tanh",
|
|
587
|
+
"coth",
|
|
588
|
+
"sech",
|
|
589
|
+
"csch",
|
|
590
|
+
"log",
|
|
591
|
+
"ln",
|
|
592
|
+
"lg",
|
|
593
|
+
"exp",
|
|
594
|
+
"arg",
|
|
595
|
+
"deg",
|
|
596
|
+
"dim",
|
|
597
|
+
"hom",
|
|
598
|
+
"ker",
|
|
599
|
+
"sgn",
|
|
600
|
+
"tg",
|
|
601
|
+
"ctg",
|
|
602
|
+
"arctg",
|
|
603
|
+
"arcctg",
|
|
604
|
+
"sh",
|
|
605
|
+
"ch",
|
|
606
|
+
"th",
|
|
607
|
+
"cth"
|
|
608
|
+
]);
|
|
609
|
+
var LIMIT_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
610
|
+
"lim",
|
|
611
|
+
"limsup",
|
|
612
|
+
"liminf",
|
|
613
|
+
"max",
|
|
614
|
+
"min",
|
|
615
|
+
"sup",
|
|
616
|
+
"inf",
|
|
617
|
+
"det",
|
|
618
|
+
"gcd",
|
|
619
|
+
"lcm",
|
|
620
|
+
"Pr",
|
|
621
|
+
"argmax",
|
|
622
|
+
"argmin",
|
|
623
|
+
"injlim",
|
|
624
|
+
"projlim"
|
|
625
|
+
]);
|
|
626
|
+
/** How a function name is written when it differs from the command. */
|
|
627
|
+
var FUNCTION_TEXT = {
|
|
628
|
+
limsup: "lim sup",
|
|
629
|
+
liminf: "lim inf",
|
|
630
|
+
argmax: "arg max",
|
|
631
|
+
argmin: "arg min",
|
|
632
|
+
injlim: "inj lim",
|
|
633
|
+
projlim: "proj lim"
|
|
634
|
+
};
|
|
635
|
+
/** Delimiters, for `\left`, `\right`, `\big` and as they are. */
|
|
636
|
+
var DELIMITERS = {
|
|
637
|
+
"(": "(",
|
|
638
|
+
")": ")",
|
|
639
|
+
"[": "[",
|
|
640
|
+
"]": "]",
|
|
641
|
+
"\\{": "{",
|
|
642
|
+
"\\}": "}",
|
|
643
|
+
lbrace: "{",
|
|
644
|
+
rbrace: "}",
|
|
645
|
+
"|": "|",
|
|
646
|
+
"\\|": "‖",
|
|
647
|
+
"/": "/",
|
|
648
|
+
"\\backslash": "∖",
|
|
649
|
+
backslash: "∖",
|
|
650
|
+
langle: "⟨",
|
|
651
|
+
rangle: "⟩",
|
|
652
|
+
lfloor: "⌊",
|
|
653
|
+
rfloor: "⌋",
|
|
654
|
+
lceil: "⌈",
|
|
655
|
+
rceil: "⌉",
|
|
656
|
+
lvert: "|",
|
|
657
|
+
rvert: "|",
|
|
658
|
+
vert: "|",
|
|
659
|
+
lVert: "‖",
|
|
660
|
+
rVert: "‖",
|
|
661
|
+
Vert: "‖",
|
|
662
|
+
lbrack: "[",
|
|
663
|
+
rbrack: "]",
|
|
664
|
+
lgroup: "⟮",
|
|
665
|
+
rgroup: "⟯",
|
|
666
|
+
lmoustache: "⎰",
|
|
667
|
+
rmoustache: "⎱",
|
|
668
|
+
uparrow: "↑",
|
|
669
|
+
downarrow: "↓",
|
|
670
|
+
updownarrow: "↕",
|
|
671
|
+
Uparrow: "⇑",
|
|
672
|
+
Downarrow: "⇓",
|
|
673
|
+
Updownarrow: "⇕",
|
|
674
|
+
"<": "⟨",
|
|
675
|
+
">": "⟩",
|
|
676
|
+
".": ""
|
|
677
|
+
};
|
|
678
|
+
/** Opening delimiters among them: the rest close. */
|
|
679
|
+
var OPENING = /* @__PURE__ */ new Set([
|
|
680
|
+
"(",
|
|
681
|
+
"[",
|
|
682
|
+
"{",
|
|
683
|
+
"⟨",
|
|
684
|
+
"⌊",
|
|
685
|
+
"⌈",
|
|
686
|
+
"⟮",
|
|
687
|
+
"⎰"
|
|
688
|
+
]);
|
|
689
|
+
/** Accents over (and under) a base: the mark and whether it stretches over a wide base. */
|
|
690
|
+
var ACCENTS = {
|
|
691
|
+
hat: {
|
|
692
|
+
mark: "^",
|
|
693
|
+
stretchy: false
|
|
694
|
+
},
|
|
695
|
+
widehat: {
|
|
696
|
+
mark: "^",
|
|
697
|
+
stretchy: true
|
|
698
|
+
},
|
|
699
|
+
check: {
|
|
700
|
+
mark: "ˇ",
|
|
701
|
+
stretchy: false
|
|
702
|
+
},
|
|
703
|
+
widecheck: {
|
|
704
|
+
mark: "ˇ",
|
|
705
|
+
stretchy: true
|
|
706
|
+
},
|
|
707
|
+
tilde: {
|
|
708
|
+
mark: "~",
|
|
709
|
+
stretchy: false
|
|
710
|
+
},
|
|
711
|
+
widetilde: {
|
|
712
|
+
mark: "~",
|
|
713
|
+
stretchy: true
|
|
714
|
+
},
|
|
715
|
+
bar: {
|
|
716
|
+
mark: "¯",
|
|
717
|
+
stretchy: false
|
|
718
|
+
},
|
|
719
|
+
overline: {
|
|
720
|
+
mark: "‾",
|
|
721
|
+
stretchy: true
|
|
722
|
+
},
|
|
723
|
+
vec: {
|
|
724
|
+
mark: "→",
|
|
725
|
+
stretchy: false
|
|
726
|
+
},
|
|
727
|
+
overrightarrow: {
|
|
728
|
+
mark: "→",
|
|
729
|
+
stretchy: true
|
|
730
|
+
},
|
|
731
|
+
overleftarrow: {
|
|
732
|
+
mark: "←",
|
|
733
|
+
stretchy: true
|
|
734
|
+
},
|
|
735
|
+
overleftrightarrow: {
|
|
736
|
+
mark: "↔",
|
|
737
|
+
stretchy: true
|
|
738
|
+
},
|
|
739
|
+
dot: {
|
|
740
|
+
mark: "˙",
|
|
741
|
+
stretchy: false
|
|
742
|
+
},
|
|
743
|
+
ddot: {
|
|
744
|
+
mark: "¨",
|
|
745
|
+
stretchy: false
|
|
746
|
+
},
|
|
747
|
+
dddot: {
|
|
748
|
+
mark: "⃛",
|
|
749
|
+
stretchy: false
|
|
750
|
+
},
|
|
751
|
+
acute: {
|
|
752
|
+
mark: "´",
|
|
753
|
+
stretchy: false
|
|
754
|
+
},
|
|
755
|
+
grave: {
|
|
756
|
+
mark: "`",
|
|
757
|
+
stretchy: false
|
|
758
|
+
},
|
|
759
|
+
breve: {
|
|
760
|
+
mark: "˘",
|
|
761
|
+
stretchy: false
|
|
762
|
+
},
|
|
763
|
+
mathring: {
|
|
764
|
+
mark: "˚",
|
|
765
|
+
stretchy: false
|
|
766
|
+
},
|
|
767
|
+
overbrace: {
|
|
768
|
+
mark: "⏞",
|
|
769
|
+
stretchy: true
|
|
770
|
+
},
|
|
771
|
+
underbrace: {
|
|
772
|
+
mark: "⏟",
|
|
773
|
+
stretchy: true,
|
|
774
|
+
under: true
|
|
775
|
+
},
|
|
776
|
+
underline: {
|
|
777
|
+
mark: "_",
|
|
778
|
+
stretchy: true,
|
|
779
|
+
under: true
|
|
780
|
+
},
|
|
781
|
+
underrightarrow: {
|
|
782
|
+
mark: "→",
|
|
783
|
+
stretchy: true,
|
|
784
|
+
under: true
|
|
785
|
+
},
|
|
786
|
+
underleftarrow: {
|
|
787
|
+
mark: "←",
|
|
788
|
+
stretchy: true,
|
|
789
|
+
under: true
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
/** Spaces, in em. */
|
|
793
|
+
var SPACES = {
|
|
794
|
+
",": 3 / 18,
|
|
795
|
+
thinspace: 3 / 18,
|
|
796
|
+
":": 4 / 18,
|
|
797
|
+
">": 4 / 18,
|
|
798
|
+
medspace: 4 / 18,
|
|
799
|
+
";": 5 / 18,
|
|
800
|
+
thickspace: 5 / 18,
|
|
801
|
+
"!": -3 / 18,
|
|
802
|
+
negthinspace: -3 / 18,
|
|
803
|
+
negmedspace: -4 / 18,
|
|
804
|
+
negthickspace: -5 / 18,
|
|
805
|
+
quad: 1,
|
|
806
|
+
qquad: 2,
|
|
807
|
+
" ": 1 / 3,
|
|
808
|
+
enspace: .5,
|
|
809
|
+
enskip: .5
|
|
810
|
+
};
|
|
811
|
+
/** `\big` and its kin: the delimiter's height, in em. */
|
|
812
|
+
var SIZES = {
|
|
813
|
+
big: 1.2,
|
|
814
|
+
bigl: 1.2,
|
|
815
|
+
bigr: 1.2,
|
|
816
|
+
bigm: 1.2,
|
|
817
|
+
Big: 1.8,
|
|
818
|
+
Bigl: 1.8,
|
|
819
|
+
Bigr: 1.8,
|
|
820
|
+
Bigm: 1.8,
|
|
821
|
+
bigg: 2.4,
|
|
822
|
+
biggl: 2.4,
|
|
823
|
+
biggr: 2.4,
|
|
824
|
+
biggm: 2.4,
|
|
825
|
+
Bigg: 3,
|
|
826
|
+
Biggl: 3,
|
|
827
|
+
Biggr: 3,
|
|
828
|
+
Biggm: 3
|
|
829
|
+
};
|
|
830
|
+
/** A character typed as it is: its kind. */
|
|
831
|
+
function charKind(ch) {
|
|
832
|
+
if ("+-*".includes(ch)) return "bin";
|
|
833
|
+
if ("=<>:".includes(ch)) return "rel";
|
|
834
|
+
if ("([{".includes(ch)) return "open";
|
|
835
|
+
if (")]}".includes(ch)) return "close";
|
|
836
|
+
if (",;".includes(ch)) return "punct";
|
|
837
|
+
return "ord";
|
|
838
|
+
}
|
|
839
|
+
// src/parser.ts
|
|
840
|
+
var MathError = class extends Error {
|
|
841
|
+
position;
|
|
842
|
+
constructor(message, position) {
|
|
843
|
+
super(message);
|
|
844
|
+
this.position = position;
|
|
845
|
+
this.name = "MathError";
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
/** How deep groups may nest, how long a formula may be, how many times macros may expand. */
|
|
849
|
+
var MAX_DEPTH = 256;
|
|
850
|
+
var MAX_TOKENS = 2e4;
|
|
851
|
+
var MAX_EXPANSIONS = 2e3;
|
|
852
|
+
function tokenize(tex, offset = 0) {
|
|
853
|
+
const out = [];
|
|
854
|
+
let i = 0;
|
|
855
|
+
while (i < tex.length) {
|
|
856
|
+
const ch = tex[i];
|
|
857
|
+
if (ch === "\\") {
|
|
858
|
+
const name = /^[a-zA-Z]+/.exec(tex.slice(i + 1))?.[0];
|
|
859
|
+
if (name) {
|
|
860
|
+
out.push({
|
|
861
|
+
kind: "command",
|
|
862
|
+
value: name,
|
|
863
|
+
pos: offset + i
|
|
864
|
+
});
|
|
865
|
+
i += 1 + name.length;
|
|
866
|
+
} else if (i + 1 < tex.length) {
|
|
867
|
+
out.push({
|
|
868
|
+
kind: "command",
|
|
869
|
+
value: tex[i + 1],
|
|
870
|
+
pos: offset + i
|
|
871
|
+
});
|
|
872
|
+
i += 2;
|
|
873
|
+
} else throw new MathError("A backslash at the end", offset + i);
|
|
874
|
+
} else if (ch === "%") {
|
|
875
|
+
const end = tex.indexOf("\n", i);
|
|
876
|
+
i = end < 0 ? tex.length : end + 1;
|
|
877
|
+
} else if (/\s/.test(ch)) {
|
|
878
|
+
const start = i;
|
|
879
|
+
while (i < tex.length && /\s/.test(tex[i])) i++;
|
|
880
|
+
out.push({
|
|
881
|
+
kind: "space",
|
|
882
|
+
value: " ",
|
|
883
|
+
pos: offset + start
|
|
884
|
+
});
|
|
885
|
+
} else {
|
|
886
|
+
const cp = tex.codePointAt(i);
|
|
887
|
+
const s = String.fromCodePoint(cp);
|
|
888
|
+
out.push({
|
|
889
|
+
kind: "char",
|
|
890
|
+
value: s,
|
|
891
|
+
pos: offset + i
|
|
892
|
+
});
|
|
893
|
+
i += s.length;
|
|
894
|
+
}
|
|
895
|
+
if (out.length > MAX_TOKENS) throw new MathError("The formula is too long", offset + i);
|
|
896
|
+
}
|
|
897
|
+
return out;
|
|
898
|
+
}
|
|
899
|
+
var NEGATED = {
|
|
900
|
+
"=": "≠",
|
|
901
|
+
"<": "≮",
|
|
902
|
+
">": "≯",
|
|
903
|
+
"≤": "≰",
|
|
904
|
+
"≥": "≱",
|
|
905
|
+
"∈": "∉",
|
|
906
|
+
"∋": "∌",
|
|
907
|
+
"⊂": "⊄",
|
|
908
|
+
"⊃": "⊅",
|
|
909
|
+
"⊆": "⊈",
|
|
910
|
+
"⊇": "⊉",
|
|
911
|
+
"∼": "≁",
|
|
912
|
+
"≅": "≇",
|
|
913
|
+
"≈": "≉",
|
|
914
|
+
"≡": "≢",
|
|
915
|
+
"∣": "∤",
|
|
916
|
+
"∥": "∦",
|
|
917
|
+
"→": "↛",
|
|
918
|
+
"←": "↚",
|
|
919
|
+
"⇒": "⇏",
|
|
920
|
+
"⇐": "⇍",
|
|
921
|
+
"⇔": "⇎",
|
|
922
|
+
"↔": "↮"
|
|
923
|
+
};
|
|
924
|
+
var ENVIRONMENTS = {
|
|
925
|
+
matrix: { align: ["c"] },
|
|
926
|
+
pmatrix: {
|
|
927
|
+
align: ["c"],
|
|
928
|
+
open: "(",
|
|
929
|
+
close: ")"
|
|
930
|
+
},
|
|
931
|
+
bmatrix: {
|
|
932
|
+
align: ["c"],
|
|
933
|
+
open: "[",
|
|
934
|
+
close: "]"
|
|
935
|
+
},
|
|
936
|
+
Bmatrix: {
|
|
937
|
+
align: ["c"],
|
|
938
|
+
open: "{",
|
|
939
|
+
close: "}"
|
|
940
|
+
},
|
|
941
|
+
vmatrix: {
|
|
942
|
+
align: ["c"],
|
|
943
|
+
open: "|",
|
|
944
|
+
close: "|"
|
|
945
|
+
},
|
|
946
|
+
Vmatrix: {
|
|
947
|
+
align: ["c"],
|
|
948
|
+
open: "‖",
|
|
949
|
+
close: "‖"
|
|
950
|
+
},
|
|
951
|
+
smallmatrix: {
|
|
952
|
+
align: ["c"],
|
|
953
|
+
small: true
|
|
954
|
+
},
|
|
955
|
+
cases: {
|
|
956
|
+
align: ["l", "l"],
|
|
957
|
+
open: "{"
|
|
958
|
+
},
|
|
959
|
+
dcases: {
|
|
960
|
+
align: ["l", "l"],
|
|
961
|
+
open: "{",
|
|
962
|
+
display: true
|
|
963
|
+
},
|
|
964
|
+
rcases: {
|
|
965
|
+
align: ["l", "l"],
|
|
966
|
+
close: "}"
|
|
967
|
+
},
|
|
968
|
+
aligned: {
|
|
969
|
+
aligned: true,
|
|
970
|
+
display: true
|
|
971
|
+
},
|
|
972
|
+
align: {
|
|
973
|
+
aligned: true,
|
|
974
|
+
display: true
|
|
975
|
+
},
|
|
976
|
+
"align*": {
|
|
977
|
+
aligned: true,
|
|
978
|
+
display: true
|
|
979
|
+
},
|
|
980
|
+
alignat: {
|
|
981
|
+
aligned: true,
|
|
982
|
+
display: true
|
|
983
|
+
},
|
|
984
|
+
"alignat*": {
|
|
985
|
+
aligned: true,
|
|
986
|
+
display: true
|
|
987
|
+
},
|
|
988
|
+
alignedat: {
|
|
989
|
+
aligned: true,
|
|
990
|
+
display: true
|
|
991
|
+
},
|
|
992
|
+
split: {
|
|
993
|
+
aligned: true,
|
|
994
|
+
display: true
|
|
995
|
+
},
|
|
996
|
+
eqnarray: {
|
|
997
|
+
align: [
|
|
998
|
+
"r",
|
|
999
|
+
"c",
|
|
1000
|
+
"l"
|
|
1001
|
+
],
|
|
1002
|
+
display: true
|
|
1003
|
+
},
|
|
1004
|
+
"eqnarray*": {
|
|
1005
|
+
align: [
|
|
1006
|
+
"r",
|
|
1007
|
+
"c",
|
|
1008
|
+
"l"
|
|
1009
|
+
],
|
|
1010
|
+
display: true
|
|
1011
|
+
},
|
|
1012
|
+
gathered: {
|
|
1013
|
+
align: ["c"],
|
|
1014
|
+
display: true
|
|
1015
|
+
},
|
|
1016
|
+
gather: {
|
|
1017
|
+
align: ["c"],
|
|
1018
|
+
display: true
|
|
1019
|
+
},
|
|
1020
|
+
"gather*": {
|
|
1021
|
+
align: ["c"],
|
|
1022
|
+
display: true
|
|
1023
|
+
},
|
|
1024
|
+
multline: {
|
|
1025
|
+
align: ["c"],
|
|
1026
|
+
display: true
|
|
1027
|
+
},
|
|
1028
|
+
"multline*": {
|
|
1029
|
+
align: ["c"],
|
|
1030
|
+
display: true
|
|
1031
|
+
},
|
|
1032
|
+
equation: {
|
|
1033
|
+
align: ["c"],
|
|
1034
|
+
display: true
|
|
1035
|
+
},
|
|
1036
|
+
"equation*": {
|
|
1037
|
+
align: ["c"],
|
|
1038
|
+
display: true
|
|
1039
|
+
},
|
|
1040
|
+
array: {},
|
|
1041
|
+
darray: { display: true },
|
|
1042
|
+
subarray: { small: true }
|
|
1043
|
+
};
|
|
1044
|
+
/** A colour as CSS takes it safely: a name or a hex value. */
|
|
1045
|
+
function safeColor(value, pos) {
|
|
1046
|
+
const v = value.trim();
|
|
1047
|
+
if (/^[a-zA-Z]{1,32}$/.test(v)) return v;
|
|
1048
|
+
if (/^#?[0-9a-fA-F]{3,8}$/.test(v)) return v.startsWith("#") ? v : `#${v}`;
|
|
1049
|
+
throw new MathError(`Not a colour: ${v}`, pos);
|
|
1050
|
+
}
|
|
1051
|
+
/** A TeX length in em (`1em`, `3mu`, `2pt`, `1ex`, `0.5cm`). */
|
|
1052
|
+
function emOf(value, pos) {
|
|
1053
|
+
const m = /^\s*(-?\d*\.?\d+)\s*(em|ex|mu|pt|px|mm|cm|in)\s*$/.exec(value);
|
|
1054
|
+
if (!m) throw new MathError(`Not a length: ${value}`, pos);
|
|
1055
|
+
return Number(m[1]) * {
|
|
1056
|
+
em: 1,
|
|
1057
|
+
ex: .43,
|
|
1058
|
+
mu: 1 / 18,
|
|
1059
|
+
pt: 1 / 10,
|
|
1060
|
+
px: 1 / 16,
|
|
1061
|
+
mm: .284,
|
|
1062
|
+
cm: 2.84,
|
|
1063
|
+
in: 7.23
|
|
1064
|
+
}[m[2]];
|
|
1065
|
+
}
|
|
1066
|
+
var Parser = class {
|
|
1067
|
+
tex;
|
|
1068
|
+
tokens;
|
|
1069
|
+
i = 0;
|
|
1070
|
+
depth = 0;
|
|
1071
|
+
expansions = 0;
|
|
1072
|
+
font = null;
|
|
1073
|
+
/** In a display formula: big operators take limits under and over. */
|
|
1074
|
+
macros;
|
|
1075
|
+
constructor(tex, options = {}) {
|
|
1076
|
+
this.tex = tex;
|
|
1077
|
+
this.tokens = tokenize(tex);
|
|
1078
|
+
this.macros = options.macros ?? {};
|
|
1079
|
+
}
|
|
1080
|
+
/** The whole formula; rows (`\\`) and columns (`&`) at the top make a table of aligned or gathered lines. */
|
|
1081
|
+
parse() {
|
|
1082
|
+
const rows = this.rows("eof");
|
|
1083
|
+
if (rows.length === 1 && rows[0].length === 1) return rows[0][0];
|
|
1084
|
+
return Math.max(...rows.map((r) => r.length)) > 1 ? {
|
|
1085
|
+
type: "table",
|
|
1086
|
+
rows: rows.map((r) => r.map((c) => this.display(c))),
|
|
1087
|
+
align: ["r", "l"],
|
|
1088
|
+
aligned: true
|
|
1089
|
+
} : {
|
|
1090
|
+
type: "table",
|
|
1091
|
+
rows: rows.map((r) => r.map((c) => this.display(c))),
|
|
1092
|
+
align: ["c"]
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
display(node) {
|
|
1096
|
+
return {
|
|
1097
|
+
type: "style",
|
|
1098
|
+
body: node,
|
|
1099
|
+
display: true
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
peek(skipSpace = true) {
|
|
1103
|
+
if (skipSpace) while (this.tokens[this.i]?.kind === "space") this.i++;
|
|
1104
|
+
return this.tokens[this.i];
|
|
1105
|
+
}
|
|
1106
|
+
next(skipSpace = true) {
|
|
1107
|
+
const t = this.peek(skipSpace);
|
|
1108
|
+
if (t) this.i++;
|
|
1109
|
+
return t;
|
|
1110
|
+
}
|
|
1111
|
+
pos() {
|
|
1112
|
+
return this.tokens[this.i]?.pos ?? this.tex.length;
|
|
1113
|
+
}
|
|
1114
|
+
isChar(t, ch) {
|
|
1115
|
+
return !!t && t.kind === "char" && t.value === ch;
|
|
1116
|
+
}
|
|
1117
|
+
isCommand(t, name) {
|
|
1118
|
+
return !!t && t.kind === "command" && t.value === name;
|
|
1119
|
+
}
|
|
1120
|
+
expect(ch) {
|
|
1121
|
+
const t = this.next();
|
|
1122
|
+
if (!this.isChar(t, ch)) throw new MathError(`Expected "${ch}"`, t?.pos ?? this.tex.length);
|
|
1123
|
+
}
|
|
1124
|
+
enter() {
|
|
1125
|
+
if (++this.depth > MAX_DEPTH) throw new MathError("Nested too deeply", this.pos());
|
|
1126
|
+
}
|
|
1127
|
+
/** Atoms until the stop; what `\color` and `\displaystyle` change runs to it too. */
|
|
1128
|
+
expression(stop) {
|
|
1129
|
+
const out = [];
|
|
1130
|
+
for (;;) {
|
|
1131
|
+
const t = this.peek();
|
|
1132
|
+
if (!t) {
|
|
1133
|
+
if (stop === "eof" || stop === "cell") return out;
|
|
1134
|
+
throw new MathError(stop === "}" ? "A \"{\" is not closed" : stop === "right" ? "\\left without \\right" : stop === "end" ? "An environment is not ended" : `Expected "${stop}"`, this.tex.length);
|
|
1135
|
+
}
|
|
1136
|
+
if (this.isChar(t, "}")) {
|
|
1137
|
+
if (stop === "}") return out;
|
|
1138
|
+
if (stop === "cell" || stop === "eof") throw new MathError("An unmatched \"}\"", t.pos);
|
|
1139
|
+
throw new MathError("An unmatched \"}\"", t.pos);
|
|
1140
|
+
}
|
|
1141
|
+
if (stop === "]" && this.isChar(t, "]")) return out;
|
|
1142
|
+
if (stop === "right" && (this.isCommand(t, "right") || this.isCommand(t, "middle"))) return out;
|
|
1143
|
+
if ((stop === "cell" || stop === "eof" || stop === "end") && (this.isChar(t, "&") || this.isCommand(t, "\\") || this.isCommand(t, "cr") || this.isCommand(t, "end"))) {
|
|
1144
|
+
if (this.isCommand(t, "end") && stop === "eof") throw new MathError("\\end without \\begin", t.pos);
|
|
1145
|
+
return out;
|
|
1146
|
+
}
|
|
1147
|
+
if (t.kind === "command" && (t.value === "color" || t.value in STYLE_SWITCHES)) {
|
|
1148
|
+
this.i++;
|
|
1149
|
+
const style = t.value === "color" ? { color: safeColor(this.textArgument(), t.pos) } : STYLE_SWITCHES[t.value];
|
|
1150
|
+
out.push({
|
|
1151
|
+
type: "style",
|
|
1152
|
+
body: row(this.expression(stop)),
|
|
1153
|
+
...style
|
|
1154
|
+
});
|
|
1155
|
+
return out;
|
|
1156
|
+
}
|
|
1157
|
+
const atom = this.atomWithScripts();
|
|
1158
|
+
if (atom) out.push(atom);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
/** Rows of cells, up to the stop: for environments and the top. */
|
|
1162
|
+
rows(stop) {
|
|
1163
|
+
const rows = [[]];
|
|
1164
|
+
for (;;) {
|
|
1165
|
+
const cell = row(this.expression(stop === "end" ? "cell" : "eof"));
|
|
1166
|
+
rows[rows.length - 1].push(cell);
|
|
1167
|
+
const t = this.peek();
|
|
1168
|
+
if (this.isChar(t, "&")) {
|
|
1169
|
+
this.i++;
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
if (this.isCommand(t, "\\") || this.isCommand(t, "cr")) {
|
|
1173
|
+
this.i++;
|
|
1174
|
+
if (this.isChar(this.peek(), "[")) this.textUntil("]");
|
|
1175
|
+
const after = this.peek();
|
|
1176
|
+
if (!after || this.isCommand(after, "end")) continue;
|
|
1177
|
+
rows.push([]);
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
if (stop === "end") {
|
|
1181
|
+
if (!this.isCommand(t, "end")) throw new MathError("An environment is not ended", this.pos());
|
|
1182
|
+
}
|
|
1183
|
+
return rows.filter((r, i) => i === 0 || r.length > 1 || !isEmpty(r[0]));
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
/** One atom and its scripts: `x`, `x_1^2`, `f'`, `\sum_{i=1}^n`. */
|
|
1187
|
+
atomWithScripts() {
|
|
1188
|
+
this.pos();
|
|
1189
|
+
let base = this.atom();
|
|
1190
|
+
let sub;
|
|
1191
|
+
let sup;
|
|
1192
|
+
let primes = "";
|
|
1193
|
+
let limits;
|
|
1194
|
+
for (;;) {
|
|
1195
|
+
const t = this.peek();
|
|
1196
|
+
if (this.isChar(t, "^") || this.isChar(t, "_")) {
|
|
1197
|
+
this.i++;
|
|
1198
|
+
const which = t.value === "^" ? "sup" : "sub";
|
|
1199
|
+
if ((which === "sup" ? sup : sub) !== void 0) throw new MathError(`Double ${which === "sup" ? "superscript" : "subscript"}`, t.pos);
|
|
1200
|
+
const arg = this.argument();
|
|
1201
|
+
if (which === "sup") sup = arg;
|
|
1202
|
+
else sub = arg;
|
|
1203
|
+
} else if (this.isChar(t, "'")) {
|
|
1204
|
+
this.i++;
|
|
1205
|
+
primes += "′";
|
|
1206
|
+
} else if (this.isCommand(t, "limits") || this.isCommand(t, "nolimits")) {
|
|
1207
|
+
this.i++;
|
|
1208
|
+
limits = t.value === "limits";
|
|
1209
|
+
} else break;
|
|
1210
|
+
}
|
|
1211
|
+
if (primes) sup = sup ? row([op(primes, "ord"), sup]) : op(primes, "ord");
|
|
1212
|
+
if (!sub && !sup) return base;
|
|
1213
|
+
base ??= row([]);
|
|
1214
|
+
const over = limits ?? takesLimits(base);
|
|
1215
|
+
if (base.type === "op" && base.kind === "large" && limits === false) base = {
|
|
1216
|
+
...base,
|
|
1217
|
+
movableLimits: false
|
|
1218
|
+
};
|
|
1219
|
+
return {
|
|
1220
|
+
type: "scripts",
|
|
1221
|
+
base,
|
|
1222
|
+
sub,
|
|
1223
|
+
sup,
|
|
1224
|
+
over
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
/** A script's or a command's argument: a group, or one token. */
|
|
1228
|
+
argument() {
|
|
1229
|
+
const t = this.peek();
|
|
1230
|
+
if (!t) throw new MathError("An argument is missing", this.tex.length);
|
|
1231
|
+
if (this.isChar(t, "{")) return this.group();
|
|
1232
|
+
if (this.isChar(t, "}") || this.isChar(t, "&") || this.isChar(t, "^") || this.isChar(t, "_")) throw new MathError("An argument is missing", t.pos);
|
|
1233
|
+
const atom = this.atom(true);
|
|
1234
|
+
if (!atom) throw new MathError("An argument is missing", t.pos);
|
|
1235
|
+
return atom;
|
|
1236
|
+
}
|
|
1237
|
+
group() {
|
|
1238
|
+
this.expect("{");
|
|
1239
|
+
this.enter();
|
|
1240
|
+
const font = this.font;
|
|
1241
|
+
const body = this.expression("}");
|
|
1242
|
+
this.font = font;
|
|
1243
|
+
this.depth--;
|
|
1244
|
+
this.expect("}");
|
|
1245
|
+
return row(body);
|
|
1246
|
+
}
|
|
1247
|
+
/** An argument read as plain text (a colour, a name, a length, an environment). */
|
|
1248
|
+
textArgument() {
|
|
1249
|
+
const t = this.peek();
|
|
1250
|
+
if (this.isChar(t, "{")) {
|
|
1251
|
+
this.i++;
|
|
1252
|
+
return this.textUntil("}", false);
|
|
1253
|
+
}
|
|
1254
|
+
const one = this.next();
|
|
1255
|
+
if (!one) throw new MathError("An argument is missing", this.tex.length);
|
|
1256
|
+
return one.value;
|
|
1257
|
+
}
|
|
1258
|
+
/** A length: in braces, or written on (`\\mkern18mu`, `\\kern-1em`). */
|
|
1259
|
+
dimension() {
|
|
1260
|
+
if (this.isChar(this.peek(), "{")) return this.textArgument();
|
|
1261
|
+
let out = "";
|
|
1262
|
+
while (this.tokens[this.i]?.kind === "char" && /^-?\d*\.?\d*[a-z]{0,2}$/.test(out + this.tokens[this.i].value)) out += this.tokens[this.i++].value;
|
|
1263
|
+
return out;
|
|
1264
|
+
}
|
|
1265
|
+
/** The raw text up to a closing character (after its opening one, or the `[` here), groups inside kept balanced. */
|
|
1266
|
+
textUntil(close, opening = true) {
|
|
1267
|
+
if (opening) this.i++;
|
|
1268
|
+
let out = "";
|
|
1269
|
+
let level = 0;
|
|
1270
|
+
for (;;) {
|
|
1271
|
+
const t = this.next(false);
|
|
1272
|
+
if (!t) throw new MathError(`Expected "${close}"`, this.tex.length);
|
|
1273
|
+
if (t.kind === "char" && t.value === "{") level++;
|
|
1274
|
+
if (t.kind === "char" && t.value === "}" && level > 0) level--;
|
|
1275
|
+
else if (t.kind === "char" && t.value === close && level === 0) return out;
|
|
1276
|
+
out += t.kind === "command" ? `\\${t.value}` : t.value;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
/** A single atom (no scripts); null for a token that makes nothing (`\limits` alone, a stray space). */
|
|
1280
|
+
atom(single = false) {
|
|
1281
|
+
const t = this.next();
|
|
1282
|
+
if (!t) return null;
|
|
1283
|
+
this.enter();
|
|
1284
|
+
try {
|
|
1285
|
+
return t.kind === "char" ? this.character(t, single) : this.command(t);
|
|
1286
|
+
} finally {
|
|
1287
|
+
this.depth--;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
character(t, single) {
|
|
1291
|
+
const ch = t.value;
|
|
1292
|
+
if (ch === "{") {
|
|
1293
|
+
this.i--;
|
|
1294
|
+
return this.group();
|
|
1295
|
+
}
|
|
1296
|
+
if (ch === "^" || ch === "_") {
|
|
1297
|
+
this.i--;
|
|
1298
|
+
return row([]);
|
|
1299
|
+
}
|
|
1300
|
+
if (ch === "~") return {
|
|
1301
|
+
type: "space",
|
|
1302
|
+
width: 1 / 3
|
|
1303
|
+
};
|
|
1304
|
+
if (ch === "&") throw new MathError("\"&\" outside a table", t.pos);
|
|
1305
|
+
if (ch === "#") throw new MathError("\"#\" outside a macro", t.pos);
|
|
1306
|
+
if (ch === "$") throw new MathError("\"$\" inside a formula", t.pos);
|
|
1307
|
+
if (/[0-9]/.test(ch)) {
|
|
1308
|
+
let text = ch;
|
|
1309
|
+
while (!single) {
|
|
1310
|
+
const n = this.tokens[this.i];
|
|
1311
|
+
if (n?.kind === "char" && /[0-9]/.test(n.value)) text += n.value;
|
|
1312
|
+
else if (n?.kind === "char" && n.value === "." && /[0-9]/.test(this.tokens[this.i + 1]?.value ?? "") && !text.includes(".")) text += n.value;
|
|
1313
|
+
else break;
|
|
1314
|
+
this.i++;
|
|
1315
|
+
}
|
|
1316
|
+
return {
|
|
1317
|
+
type: "number",
|
|
1318
|
+
text: this.font ? styled(text, this.font) : text
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
if (/\p{L}/u.test(ch)) {
|
|
1322
|
+
if (this.font && !single) {
|
|
1323
|
+
let text = ch;
|
|
1324
|
+
while (this.tokens[this.i]?.kind === "char" && /\p{L}/u.test(this.tokens[this.i].value)) text += this.tokens[this.i++].value;
|
|
1325
|
+
return this.font === "normal" ? {
|
|
1326
|
+
type: "ident",
|
|
1327
|
+
text,
|
|
1328
|
+
upright: true
|
|
1329
|
+
} : {
|
|
1330
|
+
type: "ident",
|
|
1331
|
+
text: styled(text, this.font)
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
return this.font === "normal" ? {
|
|
1335
|
+
type: "ident",
|
|
1336
|
+
text: ch,
|
|
1337
|
+
upright: true
|
|
1338
|
+
} : {
|
|
1339
|
+
type: "ident",
|
|
1340
|
+
text: this.font ? styled(ch, this.font) : ch
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
const kind = charKind(ch);
|
|
1344
|
+
if (ch === "-") return op("−", "bin");
|
|
1345
|
+
if (ch === "*") return op("∗", "bin");
|
|
1346
|
+
if (ch === "|" || ch === "/") return op(ch, "ord");
|
|
1347
|
+
return op(ch, kind);
|
|
1348
|
+
}
|
|
1349
|
+
command(t) {
|
|
1350
|
+
const name = t.value;
|
|
1351
|
+
const macro = this.macros[name];
|
|
1352
|
+
if (macro !== void 0) return this.expand(t, macro);
|
|
1353
|
+
if (name in LETTERS) return {
|
|
1354
|
+
type: "ident",
|
|
1355
|
+
text: this.font && this.font !== "normal" ? styled(LETTERS[name], this.font) : LETTERS[name]
|
|
1356
|
+
};
|
|
1357
|
+
if (name in BINARY) return op(BINARY[name], "bin");
|
|
1358
|
+
if (name in RELATIONS) return op(RELATIONS[name], "rel");
|
|
1359
|
+
if (name in PUNCT) return op(PUNCT[name], name === "colon" ? "punct" : "ord");
|
|
1360
|
+
if (name in LARGE) return {
|
|
1361
|
+
type: "op",
|
|
1362
|
+
text: LARGE[name],
|
|
1363
|
+
kind: "large",
|
|
1364
|
+
movableLimits: true
|
|
1365
|
+
};
|
|
1366
|
+
if (name in INTEGRALS) return {
|
|
1367
|
+
type: "op",
|
|
1368
|
+
text: INTEGRALS[name],
|
|
1369
|
+
kind: "large"
|
|
1370
|
+
};
|
|
1371
|
+
if (FUNCTIONS.has(name)) return {
|
|
1372
|
+
type: "ident",
|
|
1373
|
+
text: name,
|
|
1374
|
+
upright: true
|
|
1375
|
+
};
|
|
1376
|
+
if (LIMIT_FUNCTIONS.has(name)) return {
|
|
1377
|
+
type: "op",
|
|
1378
|
+
text: FUNCTION_TEXT[name] ?? name,
|
|
1379
|
+
kind: "prefix",
|
|
1380
|
+
movableLimits: true
|
|
1381
|
+
};
|
|
1382
|
+
if (name in SPACES) return {
|
|
1383
|
+
type: "space",
|
|
1384
|
+
width: SPACES[name]
|
|
1385
|
+
};
|
|
1386
|
+
if (name.length > 1 && DELIMITERS[name]) {
|
|
1387
|
+
const sign = DELIMITERS[name];
|
|
1388
|
+
return op(sign, OPENING.has(sign) || name.startsWith("l") ? "open" : name === "vert" || name === "Vert" ? "ord" : "close");
|
|
1389
|
+
}
|
|
1390
|
+
if (name in SIZES) return {
|
|
1391
|
+
...this.delimiter(),
|
|
1392
|
+
stretchy: true,
|
|
1393
|
+
size: SIZES[name]
|
|
1394
|
+
};
|
|
1395
|
+
if (name in ACCENTS) {
|
|
1396
|
+
const a = ACCENTS[name];
|
|
1397
|
+
return {
|
|
1398
|
+
type: "accent",
|
|
1399
|
+
base: this.argument(),
|
|
1400
|
+
mark: a.mark,
|
|
1401
|
+
under: a.under,
|
|
1402
|
+
stretchy: a.stretchy
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
if (name in FONT_COMMANDS && !name.startsWith("text")) {
|
|
1406
|
+
const font = this.font;
|
|
1407
|
+
this.font = FONT_COMMANDS[name];
|
|
1408
|
+
const body = this.argument();
|
|
1409
|
+
this.font = font;
|
|
1410
|
+
return body;
|
|
1411
|
+
}
|
|
1412
|
+
switch (name) {
|
|
1413
|
+
case "{":
|
|
1414
|
+
case "}": return op(name === "{" ? "{" : "}", name === "{" ? "open" : "close");
|
|
1415
|
+
case "|": return op("‖", "ord");
|
|
1416
|
+
case "#":
|
|
1417
|
+
case "$":
|
|
1418
|
+
case "%":
|
|
1419
|
+
case "&":
|
|
1420
|
+
case "_": return {
|
|
1421
|
+
type: "ident",
|
|
1422
|
+
text: name,
|
|
1423
|
+
upright: true
|
|
1424
|
+
};
|
|
1425
|
+
case " ": return {
|
|
1426
|
+
type: "space",
|
|
1427
|
+
width: 1 / 3
|
|
1428
|
+
};
|
|
1429
|
+
case "\\":
|
|
1430
|
+
case "cr": throw new MathError("A line break outside a table", t.pos);
|
|
1431
|
+
case "frac":
|
|
1432
|
+
case "dfrac":
|
|
1433
|
+
case "tfrac":
|
|
1434
|
+
case "cfrac": return {
|
|
1435
|
+
type: "frac",
|
|
1436
|
+
num: this.argument(),
|
|
1437
|
+
den: this.argument(),
|
|
1438
|
+
style: name === "dfrac" || name === "cfrac" ? "display" : name === "tfrac" ? "text" : void 0
|
|
1439
|
+
};
|
|
1440
|
+
case "binom":
|
|
1441
|
+
case "dbinom":
|
|
1442
|
+
case "tbinom":
|
|
1443
|
+
case "choose": return {
|
|
1444
|
+
type: "fenced",
|
|
1445
|
+
open: "(",
|
|
1446
|
+
close: ")",
|
|
1447
|
+
body: {
|
|
1448
|
+
type: "frac",
|
|
1449
|
+
num: this.argument(),
|
|
1450
|
+
den: this.argument(),
|
|
1451
|
+
thickness: 0,
|
|
1452
|
+
style: name === "dbinom" ? "display" : name === "tbinom" ? "text" : void 0
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
case "sqrt": {
|
|
1456
|
+
let index;
|
|
1457
|
+
if (this.isChar(this.peek(), "[")) {
|
|
1458
|
+
this.i++;
|
|
1459
|
+
this.enter();
|
|
1460
|
+
index = row(this.expression("]"));
|
|
1461
|
+
this.depth--;
|
|
1462
|
+
this.expect("]");
|
|
1463
|
+
}
|
|
1464
|
+
return {
|
|
1465
|
+
type: "sqrt",
|
|
1466
|
+
body: this.argument(),
|
|
1467
|
+
index
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
case "left": {
|
|
1471
|
+
const open = this.delimiter().text;
|
|
1472
|
+
this.enter();
|
|
1473
|
+
const parts = [];
|
|
1474
|
+
for (;;) {
|
|
1475
|
+
parts.push(...this.expression("right"));
|
|
1476
|
+
const end = this.next();
|
|
1477
|
+
if (this.isCommand(end, "middle")) {
|
|
1478
|
+
parts.push({
|
|
1479
|
+
...this.delimiter(),
|
|
1480
|
+
kind: "rel",
|
|
1481
|
+
stretchy: true
|
|
1482
|
+
});
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
if (!this.isCommand(end, "right")) throw new MathError("\\left without \\right", t.pos);
|
|
1486
|
+
break;
|
|
1487
|
+
}
|
|
1488
|
+
this.depth--;
|
|
1489
|
+
return {
|
|
1490
|
+
type: "fenced",
|
|
1491
|
+
open,
|
|
1492
|
+
close: this.delimiter().text,
|
|
1493
|
+
body: row(parts)
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
case "right": throw new MathError("\\right without \\left", t.pos);
|
|
1497
|
+
case "middle": throw new MathError("\\middle outside \\left … \\right", t.pos);
|
|
1498
|
+
case "text":
|
|
1499
|
+
case "textrm":
|
|
1500
|
+
case "textnormal":
|
|
1501
|
+
case "textup":
|
|
1502
|
+
case "mbox":
|
|
1503
|
+
case "hbox":
|
|
1504
|
+
case "textbf":
|
|
1505
|
+
case "textit":
|
|
1506
|
+
case "textsf":
|
|
1507
|
+
case "texttt":
|
|
1508
|
+
case "textmd": {
|
|
1509
|
+
const text = this.text();
|
|
1510
|
+
const font = FONT_COMMANDS[name];
|
|
1511
|
+
return {
|
|
1512
|
+
type: "text",
|
|
1513
|
+
text: font && font !== "normal" ? styled(text, font) : text
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
case "operatorname":
|
|
1517
|
+
case "operatornamewithlimits": {
|
|
1518
|
+
const star = this.isChar(this.tokens[this.i], "*");
|
|
1519
|
+
if (star) this.i++;
|
|
1520
|
+
const text = this.textArgument().replace(/\\,|\\ /g, " ").replace(/[{}\\]/g, "").trim();
|
|
1521
|
+
return star || name === "operatornamewithlimits" ? {
|
|
1522
|
+
type: "op",
|
|
1523
|
+
text,
|
|
1524
|
+
kind: "prefix",
|
|
1525
|
+
movableLimits: true
|
|
1526
|
+
} : {
|
|
1527
|
+
type: "ident",
|
|
1528
|
+
text,
|
|
1529
|
+
upright: true
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
case "overset":
|
|
1533
|
+
case "underset":
|
|
1534
|
+
case "stackrel": {
|
|
1535
|
+
const mark = this.argument();
|
|
1536
|
+
const base = this.argument();
|
|
1537
|
+
return name === "underset" ? {
|
|
1538
|
+
type: "scripts",
|
|
1539
|
+
base,
|
|
1540
|
+
sub: mark,
|
|
1541
|
+
over: true
|
|
1542
|
+
} : {
|
|
1543
|
+
type: "scripts",
|
|
1544
|
+
base: { ...base },
|
|
1545
|
+
sup: mark,
|
|
1546
|
+
over: true
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
case "not": {
|
|
1550
|
+
const next = this.atom(true);
|
|
1551
|
+
if (next?.type === "op" && NEGATED[next.text]) return {
|
|
1552
|
+
...next,
|
|
1553
|
+
text: NEGATED[next.text],
|
|
1554
|
+
kind: "rel"
|
|
1555
|
+
};
|
|
1556
|
+
if (next?.type === "op" || next?.type === "ident") return {
|
|
1557
|
+
...next,
|
|
1558
|
+
text: next.text + "̸"
|
|
1559
|
+
};
|
|
1560
|
+
return op("/", "rel");
|
|
1561
|
+
}
|
|
1562
|
+
case "mod": return row([
|
|
1563
|
+
{
|
|
1564
|
+
type: "space",
|
|
1565
|
+
width: 1
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
type: "ident",
|
|
1569
|
+
text: "mod",
|
|
1570
|
+
upright: true
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
type: "space",
|
|
1574
|
+
width: 1 / 3
|
|
1575
|
+
}
|
|
1576
|
+
]);
|
|
1577
|
+
case "bmod": return op("mod", "bin");
|
|
1578
|
+
case "pmod": return row([
|
|
1579
|
+
{
|
|
1580
|
+
type: "space",
|
|
1581
|
+
width: 1
|
|
1582
|
+
},
|
|
1583
|
+
op("(", "open"),
|
|
1584
|
+
{
|
|
1585
|
+
type: "ident",
|
|
1586
|
+
text: "mod",
|
|
1587
|
+
upright: true
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
type: "space",
|
|
1591
|
+
width: 1 / 3
|
|
1592
|
+
},
|
|
1593
|
+
this.argument(),
|
|
1594
|
+
op(")", "close")
|
|
1595
|
+
]);
|
|
1596
|
+
case "hspace":
|
|
1597
|
+
case "hskip":
|
|
1598
|
+
case "kern":
|
|
1599
|
+
case "mkern":
|
|
1600
|
+
case "mskip":
|
|
1601
|
+
if (this.isChar(this.tokens[this.i], "*")) this.i++;
|
|
1602
|
+
return {
|
|
1603
|
+
type: "space",
|
|
1604
|
+
width: emOf(this.dimension(), t.pos)
|
|
1605
|
+
};
|
|
1606
|
+
case "textcolor":
|
|
1607
|
+
case "colorbox": {
|
|
1608
|
+
const color = safeColor(this.textArgument(), t.pos);
|
|
1609
|
+
if (name === "colorbox") return {
|
|
1610
|
+
type: "style",
|
|
1611
|
+
color,
|
|
1612
|
+
body: {
|
|
1613
|
+
type: "text",
|
|
1614
|
+
text: this.text()
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
return {
|
|
1618
|
+
type: "style",
|
|
1619
|
+
color,
|
|
1620
|
+
body: this.argument()
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
case "phantom":
|
|
1624
|
+
case "hphantom":
|
|
1625
|
+
case "vphantom": return {
|
|
1626
|
+
type: "phantom",
|
|
1627
|
+
body: this.argument()
|
|
1628
|
+
};
|
|
1629
|
+
case "boxed": return {
|
|
1630
|
+
type: "boxed",
|
|
1631
|
+
body: this.argument()
|
|
1632
|
+
};
|
|
1633
|
+
case "fbox": return {
|
|
1634
|
+
type: "boxed",
|
|
1635
|
+
body: {
|
|
1636
|
+
type: "text",
|
|
1637
|
+
text: this.text()
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
case "begin": return this.environment(t);
|
|
1641
|
+
case "end": throw new MathError("\\end without \\begin", t.pos);
|
|
1642
|
+
case "displaystyle":
|
|
1643
|
+
case "textstyle":
|
|
1644
|
+
case "scriptstyle":
|
|
1645
|
+
case "scriptscriptstyle":
|
|
1646
|
+
case "color": return null;
|
|
1647
|
+
case "label":
|
|
1648
|
+
case "nonumber":
|
|
1649
|
+
case "notag":
|
|
1650
|
+
case "tag":
|
|
1651
|
+
case "hline":
|
|
1652
|
+
case "displaylimits":
|
|
1653
|
+
if (name === "label" || name === "tag") this.textArgument();
|
|
1654
|
+
return null;
|
|
1655
|
+
case "limits":
|
|
1656
|
+
case "nolimits": return null;
|
|
1657
|
+
}
|
|
1658
|
+
throw new MathError(`Unknown command \\${name}`, t.pos);
|
|
1659
|
+
}
|
|
1660
|
+
/** `\text{…}`: the text as written, spaces kept, `\{` and the like as their signs, `$…$` inside as it is. */
|
|
1661
|
+
text() {
|
|
1662
|
+
const t = this.peek();
|
|
1663
|
+
if (!this.isChar(t, "{")) return this.next()?.value ?? "";
|
|
1664
|
+
this.i++;
|
|
1665
|
+
let out = "";
|
|
1666
|
+
let level = 0;
|
|
1667
|
+
for (;;) {
|
|
1668
|
+
const n = this.next(false);
|
|
1669
|
+
if (!n) throw new MathError("A \"{\" is not closed", this.tex.length);
|
|
1670
|
+
if (n.kind === "char" && n.value === "{") {
|
|
1671
|
+
level++;
|
|
1672
|
+
continue;
|
|
1673
|
+
}
|
|
1674
|
+
if (n.kind === "char" && n.value === "}") {
|
|
1675
|
+
if (level === 0) return out.replace(/\s+/g, " ");
|
|
1676
|
+
level--;
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
if (n.kind === "command") out += n.value === " " || n.value === "," || n.value === ";" || n.value === "quad" ? " " : n.value.length === 1 ? n.value : n.value === "textbackslash" ? "\\" : "";
|
|
1680
|
+
else out += n.value;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
/** A delimiter after `\left`, `\right`, `\big`…: a sign, or a command naming one. */
|
|
1684
|
+
delimiter() {
|
|
1685
|
+
const t = this.next();
|
|
1686
|
+
if (!t) throw new MathError("A delimiter is missing", this.tex.length);
|
|
1687
|
+
const text = DELIMITERS[t.kind === "command" && t.value.length === 1 ? `\\${t.value}` : t.value];
|
|
1688
|
+
if (text === void 0) throw new MathError(`Not a delimiter: ${t.kind === "command" ? "\\" : ""}${t.value}`, t.pos);
|
|
1689
|
+
return {
|
|
1690
|
+
type: "op",
|
|
1691
|
+
text,
|
|
1692
|
+
kind: OPENING.has(text) ? "open" : "close",
|
|
1693
|
+
stretchy: true
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
/** `\begin{name} … \end{name}`: a table of the environment's kind. */
|
|
1697
|
+
environment(t) {
|
|
1698
|
+
const name = this.textArgument();
|
|
1699
|
+
const env = ENVIRONMENTS[name];
|
|
1700
|
+
if (!env) throw new MathError(`Unknown environment ${name}`, t.pos);
|
|
1701
|
+
let align = env.align ?? ["c"];
|
|
1702
|
+
let lines;
|
|
1703
|
+
if (name === "array" || name === "darray" || name === "subarray") {
|
|
1704
|
+
const spec = this.textArgument();
|
|
1705
|
+
align = [];
|
|
1706
|
+
lines = [];
|
|
1707
|
+
for (const ch of spec.replace(/@\{[^}]*\}/g, "")) if (ch === "|") {
|
|
1708
|
+
if (align.length > 0) lines[align.length - 1] = "solid";
|
|
1709
|
+
} else if (ch === "l" || ch === "c" || ch === "r") {
|
|
1710
|
+
align.push(ch);
|
|
1711
|
+
lines[align.length - 1] ??= "none";
|
|
1712
|
+
}
|
|
1713
|
+
if (align.length === 0) align = ["c"];
|
|
1714
|
+
lines = lines.slice(0, Math.max(0, align.length - 1)).map((l) => l ?? "none");
|
|
1715
|
+
} else if (name === "alignat" || name === "alignat*" || name === "alignedat") this.textArgument();
|
|
1716
|
+
if (this.isChar(this.peek(), "[")) this.textUntil("]");
|
|
1717
|
+
this.enter();
|
|
1718
|
+
const rows = this.rows("end");
|
|
1719
|
+
this.depth--;
|
|
1720
|
+
const end = this.next();
|
|
1721
|
+
if (!this.isCommand(end, "end")) throw new MathError("An environment is not ended", this.pos());
|
|
1722
|
+
const closing = this.textArgument();
|
|
1723
|
+
if (closing !== name) throw new MathError(`\\begin{${name}} ended by \\end{${closing}}`, end.pos);
|
|
1724
|
+
return {
|
|
1725
|
+
type: "table",
|
|
1726
|
+
rows: env.display ? rows.map((r) => r.map((c) => ({
|
|
1727
|
+
type: "style",
|
|
1728
|
+
body: c,
|
|
1729
|
+
display: true
|
|
1730
|
+
}))) : rows,
|
|
1731
|
+
align,
|
|
1732
|
+
lines: lines && lines.some((l) => l === "solid") ? lines : void 0,
|
|
1733
|
+
aligned: env.aligned,
|
|
1734
|
+
open: env.open,
|
|
1735
|
+
close: env.close,
|
|
1736
|
+
small: env.small
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
/** A macro of the writer's: its text, arguments put in, parsed in place. */
|
|
1740
|
+
expand(t, body) {
|
|
1741
|
+
if (++this.expansions > MAX_EXPANSIONS) throw new MathError("Macros expand too many times", t.pos);
|
|
1742
|
+
const count = Math.max(0, ...[...body.matchAll(/#([1-9])/g)].map((m) => Number(m[1])));
|
|
1743
|
+
const args = [];
|
|
1744
|
+
for (let k = 0; k < count; k++) {
|
|
1745
|
+
const a = this.peek();
|
|
1746
|
+
if (!a) throw new MathError(`\\${t.value} takes ${count} arguments`, t.pos);
|
|
1747
|
+
if (this.isChar(a, "{")) {
|
|
1748
|
+
const start = this.i + 1;
|
|
1749
|
+
this.textUntil("}");
|
|
1750
|
+
args.push(this.tokens.slice(start, this.i - 1));
|
|
1751
|
+
} else args.push([this.next()]);
|
|
1752
|
+
}
|
|
1753
|
+
const expanded = [];
|
|
1754
|
+
for (const token of tokenize(body, t.pos)) {
|
|
1755
|
+
expanded.push(token);
|
|
1756
|
+
const last = expanded.length - 2;
|
|
1757
|
+
if (token.kind === "char" && /[1-9]/.test(token.value) && expanded[last]?.kind === "char" && expanded[last].value === "#") expanded.splice(last, 2, {
|
|
1758
|
+
kind: "char",
|
|
1759
|
+
value: "{",
|
|
1760
|
+
pos: t.pos
|
|
1761
|
+
}, ...args[Number(token.value) - 1] ?? [], {
|
|
1762
|
+
kind: "char",
|
|
1763
|
+
value: "}",
|
|
1764
|
+
pos: t.pos
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
if (this.tokens.length + expanded.length > MAX_TOKENS) throw new MathError("The formula is too long", t.pos);
|
|
1768
|
+
this.tokens.splice(this.i, 0, {
|
|
1769
|
+
kind: "char",
|
|
1770
|
+
value: "{",
|
|
1771
|
+
pos: t.pos
|
|
1772
|
+
}, ...expanded, {
|
|
1773
|
+
kind: "char",
|
|
1774
|
+
value: "}",
|
|
1775
|
+
pos: t.pos
|
|
1776
|
+
});
|
|
1777
|
+
return this.atom();
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
var STYLE_SWITCHES = {
|
|
1781
|
+
displaystyle: {
|
|
1782
|
+
display: true,
|
|
1783
|
+
scriptLevel: 0
|
|
1784
|
+
},
|
|
1785
|
+
textstyle: {
|
|
1786
|
+
display: false,
|
|
1787
|
+
scriptLevel: 0
|
|
1788
|
+
},
|
|
1789
|
+
scriptstyle: {
|
|
1790
|
+
display: false,
|
|
1791
|
+
scriptLevel: 1
|
|
1792
|
+
},
|
|
1793
|
+
scriptscriptstyle: {
|
|
1794
|
+
display: false,
|
|
1795
|
+
scriptLevel: 2
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
function op(text, kind) {
|
|
1799
|
+
return {
|
|
1800
|
+
type: "op",
|
|
1801
|
+
text,
|
|
1802
|
+
kind
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
/** A list as one node: itself when one, a row otherwise. */
|
|
1806
|
+
function row(children) {
|
|
1807
|
+
return children.length === 1 ? children[0] : {
|
|
1808
|
+
type: "row",
|
|
1809
|
+
children
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
function isEmpty(node) {
|
|
1813
|
+
return node.type === "row" && node.children.length === 0;
|
|
1814
|
+
}
|
|
1815
|
+
/** Whether scripts on a base go under and over it: big operators and limit functions (movable when inline), braces. */
|
|
1816
|
+
function takesLimits(base) {
|
|
1817
|
+
if (base.type === "op") return base.movableLimits === true;
|
|
1818
|
+
if (base.type === "accent") return base.mark === "⏞" || base.mark === "⏟";
|
|
1819
|
+
return false;
|
|
1820
|
+
}
|
|
1821
|
+
// src/index.ts
|
|
1822
|
+
/** The formula as a tree. Throws `MathError` for TeX it cannot read. */
|
|
1823
|
+
function parseMath(tex, options = {}) {
|
|
1824
|
+
return new Parser(tex, options).parse();
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* The formula as a `<math>` element: MathML Core, its TeX kept in an
|
|
1828
|
+
* annotation (copied with it, read back by `texOf`). TeX it cannot read comes
|
|
1829
|
+
* out as a `<span class="dsk-math-error">` with the TeX and the reason in its
|
|
1830
|
+
* title — or throws, with `throwOnError`.
|
|
1831
|
+
*/
|
|
1832
|
+
function renderMath(tex, options = {}) {
|
|
1833
|
+
let body;
|
|
1834
|
+
try {
|
|
1835
|
+
body = toMathML(parseMath(tex, options));
|
|
1836
|
+
} catch (e) {
|
|
1837
|
+
if (options.throwOnError || !(e instanceof MathError)) throw e;
|
|
1838
|
+
return `<span class="dsk-math-error" title="${escape(e.message)}">${escape(tex)}</span>`;
|
|
1839
|
+
}
|
|
1840
|
+
return `<math xmlns="http://www.w3.org/1998/Math/MathML" class="dsk-math"${options.display ? " display=\"block\"" : ""}><semantics><mrow>${body}</mrow><annotation encoding="application/x-tex">${escape(tex)}</annotation></semantics></math>`;
|
|
1841
|
+
}
|
|
1842
|
+
/** Whether a formula can be read: null when it can, the error (with its place) when not. */
|
|
1843
|
+
function checkMath(tex, options = {}) {
|
|
1844
|
+
try {
|
|
1845
|
+
parseMath(tex, options);
|
|
1846
|
+
return null;
|
|
1847
|
+
} catch (e) {
|
|
1848
|
+
if (e instanceof MathError) return e;
|
|
1849
|
+
throw e;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
/** The TeX a drawn formula was made from (its annotation, or an error's text); null for anything else. */
|
|
1853
|
+
function texOf(element) {
|
|
1854
|
+
if (element.localName === "math") return element.querySelector("annotation[encoding=\"application/x-tex\"]")?.textContent ?? null;
|
|
1855
|
+
if (element.classList.contains("dsk-math-error")) return element.textContent;
|
|
1856
|
+
return null;
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* The page's part: a math font for the formulas (only a font with an OpenType
|
|
1860
|
+
* MATH table lets fences, roots and big operators stretch — STIX Two Math on
|
|
1861
|
+
* macOS, Cambria Math on Windows; elsewhere one the page provides), a displayed
|
|
1862
|
+
* formula's margins, and the look of TeX that could not be read.
|
|
1863
|
+
*/
|
|
1864
|
+
var MATH_CSS = `math.dsk-math{font-family:"STIX Two Math","Cambria Math","Latin Modern Math","Noto Sans Math",math}
|
|
1865
|
+
math.dsk-math[display="block"]{margin:.75em 0}
|
|
1866
|
+
.dsk-math-error{color:#d1242f;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;border-bottom:1px dotted}`;
|
|
1867
|
+
exports.MATH_CSS = MATH_CSS;
|
|
1868
|
+
exports.MathError = MathError;
|
|
1869
|
+
exports.checkMath = checkMath;
|
|
1870
|
+
exports.parseMath = parseMath;
|
|
1871
|
+
exports.renderMath = renderMath;
|
|
1872
|
+
exports.texOf = texOf;
|
|
1873
|
+
exports.toMathML = toMathML;
|
|
1874
|
+
|
|
1875
|
+
//# sourceMappingURL=index.cjs.map
|