Files

350 lines
18 KiB
Go
Raw Permalink Normal View History

// 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,
}