Files

350 lines
18 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
package mathml
// symbol names the MathML element a bare command becomes and whether its
// scripts sit under and over it.
type symbol struct {
char string
mi bool
movable bool
}
func op(char string) symbol { return symbol{char: char} }
func mov(char string) symbol { return symbol{char: char, movable: true} }
func id(char string) symbol { return symbol{char: char, mi: true} }
// symbols holds the commands that stand for one character: Greek letters,
// letterlike symbols, binary operators, relations, arrows, delimiters, big
// operators and miscellaneous symbols.
var symbols = map[string]symbol{
// Greek letters, lowercase.
"alpha": id("α"), "beta": id("β"), "gamma": id("γ"), "delta": id("δ"),
"epsilon": id("ϵ"), "varepsilon": id("ε"), "zeta": id("ζ"), "eta": id("η"),
"theta": id("θ"), "vartheta": id("ϑ"), "iota": id("ι"), "kappa": id("κ"),
"lambda": id("λ"), "mu": id("μ"), "nu": id("ν"), "xi": id("ξ"),
"omicron": id("ο"), "pi": id("π"), "varpi": id("ϖ"), "rho": id("ρ"),
"varrho": id("ϱ"), "sigma": id("σ"), "varsigma": id("ς"), "tau": id("τ"),
"upsilon": id("υ"), "phi": id("ϕ"), "varphi": id("φ"), "chi": id("χ"),
"psi": id("ψ"), "omega": id("ω"), "digamma": id("ϝ"),
// Greek letters, uppercase; the ones that coincide with Latin capitals
// still stand as identifiers, and the var forms are the italic capitals.
"Gamma": id("Γ"), "Delta": id("Δ"), "Theta": id("Θ"), "Lambda": id("Λ"),
"Xi": id("Ξ"), "Pi": id("Π"), "Sigma": id("Σ"), "Upsilon": id("Υ"),
"Phi": id("Φ"), "Psi": id("Ψ"), "Omega": id("Ω"),
"Alpha": id("Α"), "Beta": id("Β"), "Epsilon": id("Ε"), "Zeta": id("Ζ"),
"Eta": id("Η"), "Iota": id("Ι"), "Kappa": id("Κ"), "Mu": id("Μ"),
"Nu": id("Ν"), "Omicron": id("Ο"), "Rho": id("Ρ"), "Tau": id("Τ"),
"Chi": id("Χ"),
"varGamma": id("𝛤"), "varDelta": id("𝛥"), "varTheta": id("𝛩"),
"varLambda": id("𝛬"), "varXi": id("𝛯"), "varPi": id("𝛱"),
"varSigma": id("𝛴"), "varUpsilon": id("𝛶"), "varPhi": id("𝛷"),
"varPsi": id("𝛹"), "varOmega": id("𝛺"),
"varkappa": id("ϰ"), "thetasym": id("ϑ"),
// Letterlike symbols.
"hbar": id("ℏ"), "ell": id("ℓ"), "imath": id("ı"), "jmath": id("ȷ"),
"wp": id("℘"), "Re": id("ℜ"), "Im": id("ℑ"), "aleph": id("ℵ"),
"beth": id("ℶ"), "gimel": id("ℷ"), "daleth": id("ℸ"),
"partial": id("∂"), "nabla": op("∇"), "mho": id("℧"),
"complement": op("∁"), "eth": id("ð"), "Finv": id("⅁"), "Game": id("⅂"),
"circledS": id("Ⓢ"), "Bbbk": id("𝕜"), "weierp": id("℘"),
// Big operators, with movable limits.
"sum": mov("∑"), "prod": mov("∏"), "coprod": mov("∐"),
"int": op("∫"), "iint": op("∬"), "iiint": op("∭"), "iiiint": op("⨌"),
"oint": op("∮"), "oiint": op("∯"), "oiiint": op("∰"),
"bigcap": mov("⋂"), "bigcup": mov("⋃"), "bigsqcup": mov("⨆"),
"bigvee": mov("⋁"), "bigwedge": mov("⋀"), "bigodot": mov("⨀"),
"bigotimes": mov("⨂"), "bigoplus": mov("⨁"), "biguplus": mov("⨄"),
"varointclockwise": op("∱"), "ointclockwise": op("∲"),
"ointctrclockwise": op("∳"),
// Binary operators.
"pm": op("±"), "mp": op("∓"), "times": op("×"), "div": op("÷"),
"ast": op("∗"), "star": op("⋆"), "circ": op("∘"), "bullet": op("∙"),
"cdot": op("⋅"), "cap": op("∩"), "cup": op("∪"), "uplus": op("⊎"),
"sqcap": op("⊓"), "sqcup": op("⊔"), "vee": op("∨"), "lor": op("∨"),
"wedge": op("∧"), "land": op("∧"), "setminus": op("∖"),
"wr": op("≀"), "diamond": op("⋄"), "bigtriangleup": op("△"),
"bigtriangledown": op("▽"), "triangleleft": op("◃"), "triangleright": op("▹"),
"lhd": op("⊲"), "rhd": op("⊳"), "unlhd": op("⊴"), "unrhd": op("⊵"),
"oplus": op("⊕"), "ominus": op("⊖"), "otimes": op("⊗"), "oslash": op("⊘"),
"odot": op("⊙"), "bigcirc": op("○"), "dagger": op("†"), "ddagger": op("‡"),
"amalg": op("⨿"), "dotplus": op("∔"), "smallsetminus": op("∖"),
"Cap": op("⋒"), "Cup": op("⋓"), "barwedge": op("⊼"), "veebar": op("⊻"),
"doublebarwedge": op("⩞"), "boxminus": op("⊟"), "boxplus": op("⊞"),
"boxtimes": op("⊠"), "boxdot": op("⊡"), "divideontimes": op("⋇"),
"intercal": op("⊺"), "circledcirc": op("⊚"), "circledast": op("⊛"),
"circleddash": op("⊝"), "curlywedge": op("⋏"), "curlyvee": op("⋎"),
"leftthreetimes": op("⋋"), "rightthreetimes": op("⋌"),
"looparrowleft": op("↫"), "looparrowright": op("↬"),
"curvearrowleft": op("↶"), "curvearrowright": op("↷"),
"circlearrowleft": op("↺"), "circlearrowright": op("↻"),
// Relations.
"leq": op("≤"), "le": op("≤"), "geq": op("≥"), "ge": op("≥"),
"neq": op("≠"), "ne": op("≠"), "sim": op("∼"), "simeq": op("≃"),
"approx": op("≈"), "cong": op("≅"), "equiv": op("≡"),
"prec": op("≺"), "preceq": op("⪯"), "precapprox": op("⪵"),
"precsim": op("≾"), "succ": op("≻"), "succeq": op("⪰"),
"succapprox": op("⪶"), "succsim": op("≿"),
"ll": op("≪"), "lll": op("⋘"), "gg": op("≫"), "ggg": op("⋙"),
"asymp": op("≍"), "doteq": op("≐"), "propto": op("∝"),
"mid": op("∣"), "nmid": op("∤"), "parallel": op("∥"), "shortparallel": op("∥"),
"perp": op("⊥"), "Subset": op("⋐"), "Supset": op("⋑"),
"sqsubset": op("⊏"), "sqsupset": op("⊐"),
"subset": op("⊂"), "supset": op("⊃"), "subseteq": op("⊆"), "supseteq": op("⊇"),
"subseteqq": op("⫅"), "supseteqq": op("⫆"),
"sqsubseteq": op("⊑"), "sqsupseteq": op("⊒"),
"in": op("∈"), "ni": op("∋"), "owns": op("∋"), "notin": op("∉"),
"vdash": op("⊢"), "dashv": op("⊣"), "Vdash": op("⊩"), "Vvdash": op("⊪"),
"models": op("⊨"), "smile": op("⌣"), "frown": op("⌢"),
"lesssim": op("≲"), "gtrsim": op("≳"), "lessapprox": op("⪅"),
"gtrapprox": op("⪆"), "lessgtr": op("≶"), "gtrless": op("≷"),
"lesseqgtr": op("⋚"), "gtreqless": op("⋛"), "leqq": op("≦"),
"geqq": op("≧"), "lneq": op("⪇"), "gneq": op("⪈"), "lvertneqq": op("≨"),
"gvertneqq": op("≩"), "lnsim": op("⪦"), "gnsim": op("⪧"),
"eqslantless": op("⪕"), "eqslantgtr": op("⪖"), "backsim": op("∽"),
"backsimeq": op("⋍"), "lesseqqgtr": op("⪋"), "gtreqqless": op("⪌"),
"nestedlessgreater": op("≺"), "nless": op("≮"), "ngtr": op("≯"),
"nleq": op("≰"), "nleqslant": op("≰"), "ngeq": op("≱"), "ngeqslant": op("≱"),
"nprec": op("⊀"), "nsucc": op("⊁"), "precnsim": op("⋨"), "succnsim": op("⋩"),
"nsubseteq": op("⊈"), "nsupseteq": op("⊉"), "subsetneq": op("⊊"),
"supsetneq": op("⊋"), "subsetneqq": op("⫋"), "supsetneqq": op("⫌"),
"vartriangleleft": op("⊲"), "vartriangleright": op("⊳"),
"trianglelefteq": op("⊴"), "trianglerighteq": op("⊵"),
"triangleq": op("≜"), "bumpeq": op("≏"), "Bumpeq": op("≎"),
"eqcirc": op("≖"), "circeq": op("≗"), "doteqdot": op("≑"),
"risingdotseq": op("≓"), "fallingdotseq": op("≒"),
"pitchfork": op("⋔"), "smallfrown": op("⌢"), "smallsmile": op("⌣"),
"therefore": op("∴"), "because": op("∵"),
"eqsim": op("≟"),
"bowtie": op("⋈"), "Join": op("⋈"), "backepsilon": op("϶"),
"thicksim": op("∼"), "thickapprox": op("≈"),
"preccurlyeq": op("≼"), "succcurlyeq": op("≽"),
"varpropto": op("∝"), "ratio": op("∶"), "vcentcolon": op(":"),
"curlyeqprec": op("⋞"), "curlyeqsucc": op("⋟"),
"between": op("≬"),
// The colon relations, mapped the way KaTeX's MathML branch maps
// them: the precomposed character where Unicode has one, the linear
// two-character operator where it has none.
"dblcolon": op("∷"),
"coloneqq": op("≔"),
"coloneq": op(":−"),
"Coloneqq": op("∷="),
"Coloneq": op("∷−"),
"eqqcolon": op("≕"),
"eqcolon": op("∹"),
"Eqqcolon": op("=∷"),
"Eqcolon": op("−∷"),
"colonapprox": op(":≈"),
"Colonapprox": op("∷≈"),
"colonsim": op(":∼"),
"Colonsim": op("∷∼"),
"approxcolon": op("≈:"),
"approxcoloncolon": op("≈∷"),
"simcolon": op("∼:"),
"simcoloncolon": op("∼∷"),
"origof": op("⊶"),
"imageof": op("⊷"),
// The last stragglers the full KaTeX symbol table carries.
"Doteq": op("≑"),
"Diamond": op("◆"),
"approxeq": op("≊"),
"doublecap": op("⋒"),
"doublecup": op("⋓"),
"geqslant": op("⩾"),
"leqslant": op("⩽"),
"gggtr": op("⋙"),
"llless": op("⋘"),
"gneqq": op("≩"),
"lneqq": op("≨"),
"gtrdot": op("⋗"),
"lessdot": op("⋖"),
"intop": op("∫"),
"smallint": op("∫"),
"ltimes": op("⋉"),
"rtimes": op("⋊"),
"nparallel": op("∦"),
"nsim": op("≁"),
"nvdash": op("⊬"),
"vDash": op("⊨"),
"shortmid": op("∣"),
"varvdots": op("⋮"),
// Negated relations.
"ncong": op("≇"), "npreceq": op("⋠"),
"nsucceq": op("⋡"),
"precnapprox": op("⪹"), "succnapprox": op("⪺"),
"precneqq": op("⪵"), "succneqq": op("⪶"),
"gnapprox": op("⪊"), "lnapprox": op("⪉"),
"nshortmid": op("∤"), "nshortparallel": op("∦"),
"nvDash": op("⊭"), "nVDash": op("⊯"), "nVdash": op("⊮"),
"ntriangleleft": op("⋪"), "ntriangleright": op("⋫"),
"ntrianglelefteq": op("⋬"), "ntrianglerighteq": op("⋭"),
"nleftrightarrow": op("↮"), "nLeftarrow": op("⇍"),
"nLeftrightarrow": op("⇎"), "nRightarrow": op("⇏"),
"nleftarrow": op("↰"), "nrightarrow": op("↱"),
// Arrows.
"leftarrow": op("←"), "gets": op("←"), "Leftarrow": op("⇐"),
"rightarrow": op("→"), "to": op("→"), "Rightarrow": op("⇒"),
"leftrightarrow": op("↔"), "Leftrightarrow": op("⇔"), "iff": op("⟺"),
"longleftarrow": op("⟵"), "Longleftarrow": op("⟸"),
"longrightarrow": op("⟶"), "Longrightarrow": op("⟹"),
"longleftrightarrow": op("⟷"), "Longleftrightarrow": op("⟺"),
"implies": op("⟹"), "impliedby": op("⟸"),
"mapsto": op("↦"), "longmapsto": op("⟼"),
"hookleftarrow": op("↩"), "hookrightarrow": op("↪"),
"leftharpoonup": op("↼"), "leftharpoondown": op("↽"),
"rightharpoonup": op("⇀"), "rightharpoondown": op("⇁"),
"rightleftharpoons": op("⇌"), "leadsto": op("↝"),
"nearrow": op("↗"), "searrow": op("↘"), "swarrow": op("↙"), "nwarrow": op("↖"),
"uparrow": op("↑"), "downarrow": op("↓"), "updownarrow": op("↕"),
"Uparrow": op("⇑"), "Downarrow": op("⇓"), "Updownarrow": op("⇕"),
"downdownarrows": op("⇊"), "upuparrows": op("⇈"),
"rightrightarrows": op("⇉"), "leftleftarrows": op("⇇"),
"rightleftarrows": op("⇄"), "leftrightarrows": op("⇆"),
"twoheadrightarrow": op("↠"), "twoheadleftarrow": op("↞"),
"leftarrowtail": op("↢"), "rightarrowtail": op("↣"),
"Lleftarrow": op("⤅"), "Rrightarrow": op("⤇"),
"upharpoonleft": op("↿"), "upharpoonright": op("↾"),
"downharpoonleft": op("⇃"), "downharpoonright": op("⇂"),
"restriction": op("↾"), "multimap": op("⊸"),
"harr": op("↔"), "hArr": op("⇔"), "Harr": op("⇔"),
"larr": op("←"), "lArr": op("⇐"), "Larr": op("⇐"),
"rarr": op("→"), "rArr": op("⇒"), "Rarr": op("⇒"),
"lrarr": op("↔"), "lrArr": op("⇔"), "Lrarr": op("⇔"),
"darr": op("↓"), "dArr": op("⇓"), "Darr": op("⇓"),
"uarr": op("↑"), "uArr": op("⇑"), "Uarr": op("⇑"),
"dashleftarrow": op("⇠"), "dashrightarrow": op("⇢"),
"rightsquigarrow": op("↝"), "leftrightsquigarrow": op("↭"),
"leftrightharpoons": op("⇋"), "mapsfrom": op("↤"),
"Lsh": op("↰"), "Rsh": op("↱"),
// Delimiters.
"langle": op("⟨"), "rangle": op("⟩"), "lfloor": op("⌊"), "rfloor": op("⌋"),
"lceil": op("⌈"), "rceil": op("⌉"), "vert": op("|"), "lvert": op("|"),
"rvert": op("|"), "Vert": op("‖"), "lVert": op("‖"), "rVert": op("‖"),
"lbrace": op("{"), "rbrace": op("}"), "lbrack": op("["), "rbrack": op("]"),
"lgroup": op("⟮"), "rgroup": op("⟯"),
"lmoustache": op("⌠"), "rmoustache": op("⌡"), "backslash": op("\\"),
"lparen": op("("), "rparen": op(")"), "lang": op("⟨"), "rang": op("⟩"),
"ulcorner": op("⌜"), "urcorner": op("⌝"), "llcorner": op("⌞"),
"lrcorner": op("⌟"), "llbracket": op("⟦"), "rrbracket": op("⟧"),
"lBrace": op("{"), "rBrace": op("}"),
// Miscellaneous symbols.
"infty": op("∞"), "forall": op("∀"), "exists": op("∃"), "nexists": op("∄"),
"emptyset": op("∅"), "varnothing": op("∅"), "top": op("⊤"), "bot": op("⊥"),
"vdots": op("⋮"), "cdots": op("⋯"), "ddots": op("⋱"), "iddots": op("⋰"),
"ldots": op("…"), "dots": op("…"), "dotsc": op("…"), "dotsb": op("⋯"),
"dotsm": op("⋯"), "dotsi": op("⋯"), "dotso": op("…"),
"prime": op("′"), "backprime": op("‵"), "degree": op("°"),
"angle": op("∠"), "measuredangle": op("∡"), "sphericalangle": op("∢"),
"triangle": op("△"), "square": op("□"), "blacksquare": op("■"),
"bigstar": op("★"), "blacktriangle": op("▲"), "blacktriangledown": op("▼"),
"blacktriangleleft": op("◀"), "blacktriangleright": op("▶"),
"diamondsuit": op("♦"), "heartsuit": op("♥"), "clubsuit": op("♣"),
"spadesuit": op("♠"), "flat": op("♭"), "natural": op("♮"), "sharp": op("♯"),
"checkmark": op("✓"), "maltese": op("✠"), "bull": op("∙"),
"ldotp": op("."), "cdotp": op("⋅"), "colon": op(":"),
"S": op("§"), "P": op("¶"), "copyright": op("©"), "circledR": op("®"),
"diagup": op("╱"), "diagdown": op("╲"),
"lozenge": op("◊"), "blacklozenge": op("◆"), "surd": op("√"),
"Box": op("□"), "triangledown": op("▽"), "vartriangle": op("△"),
"pounds": op("£"), "mathsterling": op("£"), "yen": op("¥"),
"dag": op("†"), "ddag": op("‡"), "Dagger": op("‡"),
"minuso": op("⦵"), "centerdot": op("·"), "plusmn": op("±"),
"And": op("&"), "lq": op("‘"), "rq": op("’"),
"sdot": op("⋅"), "mathellipsis": op("…"),
"neg": op("¬"), "lnot": op("¬"), "empty": op("∅"),
"isin": op("∈"), "exist": op("∃"),
"lt": op("<"), "gt": op(">"),
// Letter-like aliases KaTeX carries.
"alef": id("ℵ"), "alefsym": id("ℵ"), "hslash": id("ℏ"),
"image": id("ℑ"), "real": id("ℜ"), "reals": id("ℝ"),
"cnums": id("ℂ"), "Complex": id("ℂ"), "natnums": id("ℕ"),
"RR": id("ℝ"), "NN": id("ℕ"), "ZZ": id("ℤ"), "Q": id("ℚ"),
"infin": op("∞"),
}
// functions are the names typeset upright as identifiers.
var functions = map[string]bool{
"arccos": true, "arcsin": true, "arctan": true, "arg": true,
"cos": true, "cosh": true, "cot": true, "coth": true, "csc": true,
"deg": true, "det": true, "dim": true, "exp": true, "gcd": true,
"hom": true, "ker": true, "lg": true, "ln": true, "log": true,
"Pr": true, "sec": true, "sin": true, "sinh": true, "tan": true,
"tanh": true, "arcsinh": true, "arccosh": true, "arctanh": true,
"argmax": true, "argmin": true,
"mod": true, "bmod": true,
"min": true, "max": true, "sup": true, "inf": true,
"lim": true, "limsup": true, "liminf": true,
"injlim": true, "projlim": true, "varinjlim": true, "varprojlim": true,
"varliminf": true, "varlimsup": true, "plim": true,
"arctg": true, "arcctg": true, "ch": true, "cosec": true, "cotg": true,
"ctg": true, "cth": true, "sh": true, "tg": true, "th": true,
}
// movableFunctions take their scripts under and over: the limit operators.
var movableFunctions = map[string]bool{
"lim": true, "limsup": true, "liminf": true, "max": true, "min": true,
"sup": true, "inf": true, "gcd": true, "det": true, "Pr": true,
"injlim": true, "projlim": true, "varinjlim": true, "varprojlim": true,
"varliminf": true, "varlimsup": true, "plim": true,
"argmax": true, "argmin": true,
}
// accents put a mark over or under their argument.
var accents = map[string]struct {
char string
under bool
}{
"hat": {"\u005e", false}, "widehat": {"\u005e", false},
"tilde": {"~", false}, "widetilde": {"~", false},
"utilde": {"~", true},
"bar": {"\u00af", false}, "overline": {"\u203e", false},
"vec": {"\u2192", false}, "dot": {"\u02d9", false}, "ddot": {"\u00a8", false},
"dddot": {"\u20db", false}, "ddddot": {"\u20dc", false},
"mathring": {"\u02da", false}, "breve": {"\u02d8", false},
"check": {"\u02c7", false}, "widecheck": {"\u02c7", false},
"acute": {"\u00b4", false}, "grave": {"\u0060", false},
"overbrace": {"\u23de", false}, "underbrace": {"\u23df", true},
"overbracket": {"\u23b4", false}, "underbracket": {"\u23b5", true},
"overleftarrow": {"\u2190", false}, "overrightarrow": {"\u2192", false},
"Overrightarrow": {"\u21d2", false},
"underleftarrow": {"\u2190", true}, "underrightarrow": {"\u2192", true},
"overleftrightarrow": {"\u2194", false}, "underleftrightarrow": {"\u2194", true},
"overleftharpoon": {"\u21bc", false}, "overrightharpoon": {"\u21c0", false},
"overgroup": {"\u23e0", false}, "undergroup": {"\u23e1", true},
"underline": {"_", true}, "underbar": {"\u02cd", true},
"overlinesegment": {"\u23af", false}, "underlinesegment": {"\u23af", true},
}
// styles map the style commands to mathvariant values; the empty value
// marks a switch that renders its argument unchanged.
var styles = map[string]string{
"mathrm": "normal", "mathnormal": "italic", "mathit": "italic",
"mathbf": "bold", "mathbfit": "bold-italic", "mathbb": "double-struck",
"mathcal": "script", "mathscr": "script", "mathfrak": "fraktur",
"mathsf": "sans-serif", "mathsfit": "sans-serif-italic",
"mathsfbf": "sans-serif-bold", "mathtt": "monospace",
"boldsymbol": "bold-italic", "bm": "bold-italic",
// The old TeX switches, applied to what follows in the group.
"rm": "normal", "bf": "bold", "it": "italic", "sf": "sans-serif",
"tt": "monospace", "cal": "script", "scr": "script", "frak": "fraktur",
}
// spaces maps spacing commands to mspace widths; the empty width carries
// nothing.
var spaces = map[string]string{
",": "0.1667em", "thinspace": "0.1667em",
":": "0.2222em", "medspace": "0.2222em",
";": "0.2778em", "thickspace": "0.2778em",
"!": "-0.1667em", "negthinspace": "-0.1667em",
"negmedspace": "-0.2222em", "negthickspace": "-0.2778em",
" ": "0.25em", "quad": "1em", "qquad": "2em", "enspace": "0.5em",
}
// delimiterChars are the single characters accepted after \left, \right
// and the big size commands.
var delimiterChars = map[string]bool{
"(": true, ")": true, "[": true, "]": true, "|": true, "/": true,
"<": true, ">": true,
}