// Copyright (c) 2026 Petr Balvín (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, }