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