@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.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
+ "&": "&amp;",
6
+ "<": "&lt;",
7
+ ">": "&gt;",
8
+ "\"": "&quot;"
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\">&#x2061;</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