package tex
import (
"errors"
"fmt"
"strconv"
"strings"
"github.com/d2lang/mathjax-go/internal/mhchem"
"github.com/d2lang/mathjax-go/internal/mml"
"github.com/d2lang/mathjax-go/internal/ordered"
texcancel "github.com/d2lang/mathjax-go/internal/tex/extensions/cancel"
texcolor "github.com/d2lang/mathjax-go/internal/tex/extensions/color"
"github.com/d2lang/mathjax-go/internal/tex/extensions/enclose"
"github.com/d2lang/mathjax-go/internal/tex/extensions/gensymb"
)
func (p *parser ) commandNodes (name string , after **derivativeAutoOpen ) ([]*mml .Node , error ) {
_ , paired := p .state .pairedDelimiters [name ]
if definition , ok := p .state .macros [name ]; ok && !paired {
if definition .builtinNot {
p .commandNot = true
return nil , nil
}
return p .invokeMacro (name , definition )
}
if !paired {
if nodes , handled , err := p .amsTagCommand (name ); handled {
return nodes , err
}
}
if nodes , handled , err := p .mathtoolsCommand (name ); handled {
return nodes , err
}
if p .state .augmentedPackages {
if nodes , handled , err := p .empheqCommand (name ); handled {
return nodes , err
}
}
if nodes , handled , err := p .physicsCommand (name , after ); handled {
return nodes , err
}
if nodes , handled , err := p .amscdCommand (name ); handled {
return nodes , err
}
if nodes , handled , err := p .baseAMSCommand (name ); handled {
return nodes , err
}
if isHFill (name ) {
return nil , unsupportedHFill (name )
}
if name == "\n" {
return []*mml .Node {setAttributes (node ("mspace" ), map [string ]any {"linebreak" : "newline" })}, nil
}
if p .state .augmentedPackages {
switch name {
case "newcommand" , "renewcommand" , "providecommand" :
return nil , p .defineCommand (name )
case "newenvironment" , "renewenvironment" :
return nil , p .defineEnvironment (name )
case "def" , "gdef" , "edef" , "xdef" :
return nil , p .macroDef (name )
case "let" :
return nil , p .letCommand (name )
}
}
if definition , ok := p .state .pairedDelimiters [name ]; ok {
return p .invokePairedDelimiter (name , definition )
}
if isHLine (name ) {
return nil , misplacedHLine (name )
}
if name == "\\" || name == "newline" || name == "cr" {
return nil , p .crCommand (name )
}
if name == "|" {
return p .braketBar (), nil
}
if symbol , ok := p .lookupExtensionSymbol (name ); ok {
return []*mml .Node {symbol }, nil
}
if symbol , ok := p .lookupMJSourceSymbol (name ); ok {
return []*mml .Node {symbol }, nil
}
if symbol , ok := identifierSymbols [name ]; ok {
n := token ("mi" , symbol .char )
if symbol .attrs == nil {
n .Attributes .Set ("mathvariant" , "normal" )
} else {
setAttributes (n , symbol .attrs )
}
return []*mml .Node {n }, nil
}
if symbol , ok := operatorSymbols [name ]; ok {
n := p .operator (symbol .char , symbol .class , symbol .attrs )
if symbol .attrs != nil {
if moves , ok := symbol .attrs ["movesupsub" ]; ok {
n .SetProperty ("movesupsub" , moves )
}
}
return []*mml .Node {n }, nil
}
if function , ok := functionNames [name ]; ok {
if name == "injlim" || name == "projlim" {
text := map [string ]string {"injlim" : "inj\u2006lim" , "projlim" : "proj\u2006lim" }[name ]
return []*mml .Node {p .namedOperator (text )}, nil
}
if name == "gcd" || name == "lim" || name == "liminf" || name == "limsup" || name == "max" || name == "min" || name == "sup" || name == "inf" {
return []*mml .Node {p .namedOperator (function )}, nil
}
fn := token ("mi" , function )
fn .TeXClass = mml .TeXClassOp
fn .SetProperty ("texClass" , mml .TeXClassOp )
p .commandNamedFunction = true
return []*mml .Node {fn }, nil
}
if _ , ok := mathVariants [name ]; ok {
return p .mathFont (name )
}
switch name {
case "not" :
p .commandNot = true
return nil , nil
case "nonscript" :
p .commandNonscript = true
return nil , nil
case "dots" :
p .commandDots = p .startDots ()
return nil , nil
case "mmlToken" :
return p .mmlToken (name )
case "," :
return []*mml .Node {spacer ("0.167em" )}, nil
case ":" :
return []*mml .Node {spacer ("0.222em" )}, nil
case ";" :
return []*mml .Node {spacer ("0.278em" )}, nil
case "!" :
return []*mml .Node {spacer ("-0.167em" )}, nil
case ">" :
return []*mml .Node {spacer ("0.222em" )}, nil
case "quad" :
return []*mml .Node {spacer ("1em" )}, nil
case "qquad" :
return []*mml .Node {spacer ("2em" )}, nil
case "enspace" :
return []*mml .Node {spacer ("0.5em" )}, nil
case "thinspace" :
return []*mml .Node {spacer ("0.167em" )}, nil
case "negthinspace" :
return []*mml .Node {spacer ("-0.167em" )}, nil
case "enskip" :
return []*mml .Node {spacer ("0.5em" )}, nil
case "space" , "\u00a0" :
return []*mml .Node {token ("mtext" , "\u00a0" )}, nil
case "{" , "}" , "$" , "%" , "#" , "&" , "_" :
return []*mml .Node {p .token ("mo" , name )}, nil
case "backslash" :
return []*mml .Node {p .token ("mo" , "∖" )}, nil
case "Vert" :
return []*mml .Node {p .operator ("‖" , mml .TeXClassOrd , map [string ]any {"fence" : false , "stretchy" : false })}, nil
case "vert" :
return []*mml .Node {p .operator ("|" , mml .TeXClassOrd , map [string ]any {"fence" : false , "stretchy" : false })}, nil
case "frac" :
return p .fraction (name , "" )
case "dfrac" :
return p .fraction (name , "D" )
case "tfrac" :
return p .fraction (name , "T" )
case "binom" :
return p .binomial (name , "" )
case "dbinom" :
return p .binomial (name , "D" )
case "tbinom" :
return p .binomial (name , "T" )
case "genfrac" :
return p .generalizedFraction (name )
case "cfrac" :
return p .continuedFraction (name )
case "sqrt" :
return p .sqrt (name )
case "root" :
return p .root (name )
case "uproot" , "leftroot" :
return nil , p .moveRoot (name )
case "mathchoice" :
return p .mathChoice (name )
case "left" :
return p .leftRight (name )
case "middle" :
if _ , err := p .readDelimiter (name , false ); err != nil {
return nil , err
}
return nil , texError ("ExtraMiddle" , "Extra \\middle" )
case "big" , "Big" , "bigg" , "Bigg" , "bigl" , "Bigl" , "biggl" , "Biggl" , "bigr" , "Bigr" , "biggr" , "Biggr" , "bigm" , "Bigm" , "biggm" , "Biggm" :
return p .bigDelimiter (name )
case "overline" , "underline" , "overbrace" , "underbrace" , "overparen" , "underparen" , "overrightarrow" , "overleftarrow" , "underrightarrow" , "underleftarrow" , "overleftrightarrow" , "underleftrightarrow" :
return p .underOver (name )
case "bar" , "hat" , "widehat" , "tilde" , "widetilde" , "vec" , "dot" , "ddot" , "dddot" , "ddddot" , "acute" , "grave" , "breve" , "check" , "mathring" :
return p .accent (name )
case "overset" , "stackrel" :
return p .overSet (name )
case "buildrel" :
return p .buildRelation (name )
case "underset" :
return p .underSet (name )
case "overunderset" :
return p .overUnderSet (name )
case "xrightarrow" , "xleftarrow" , "xleftrightarrow" , "xLeftarrow" , "xRightarrow" , "xLeftrightarrow" , "xhookleftarrow" , "xhookrightarrow" , "xmapsto" , "xrightharpoondown" , "xleftharpoondown" , "xrightleftharpoons" , "xrightharpoonup" , "xleftharpoonup" , "xleftrightharpoons" :
return p .xArrow (name )
case "text" , "textnormal" :
return p .hboxCommand (name , "" , false )
case "textrm" , "textup" :
return p .hboxCommand (name , "normal" , false )
case "mbox" , "hbox" :
return p .hboxCommand (name , "" , true )
case "textbf" :
return p .hboxCommand (name , "bold" , false )
case "textit" :
return p .hboxCommand (name , "italic" , false )
case "texttt" :
return p .hboxCommand (name , "monospace" , false )
case "textsf" :
return p .hboxCommand (name , "sans-serif" , false )
case "operatorname" :
return p .operatorName (name )
case "DeclareMathOperator" :
return nil , p .declareMathOperator (name )
case "mathop" , "mathrel" , "mathbin" , "mathord" , "mathopen" , "mathclose" , "mathpunct" , "mathinner" :
return p .mathClass (name )
case "injlim" , "projlim" :
text := map [string ]string {"injlim" : "inj\u2006lim" , "projlim" : "proj\u2006lim" }[name ]
op := p .operator (text , mml .TeXClassOp , map [string ]any {"movablelimits" : true })
op .SetProperty ("movesupsub" , true )
return []*mml .Node {op }, nil
case "kern" , "mkern" , "hskip" , "mskip" , "hspace" , "mspace" :
return p .horizontalSpace (name )
case "hspace*" :
return p .horizontalSpace (name )
case "strut" :
padded := node ("mpadded" , node ("mrow" ))
padded .Attributes .Set ("height" , "8.6pt" )
padded .Attributes .Set ("depth" , "3pt" )
padded .Attributes .Set ("width" , 0 )
return []*mml .Node {padded }, nil
case "phantom" , "hphantom" , "vphantom" :
return p .phantom (name )
case "smash" :
return p .smash (name )
case "llap" , "rlap" , "clap" , "mathllap" , "mathrlap" , "mathclap" , "crampedllap" , "crampedrlap" , "crampedclap" :
return p .lap (name )
case "cramped" :
return p .cramped (name )
case "mathmbox" :
return p .mathMBox (name )
case "mathmakebox" :
return p .mathMakeBox (name )
case "raise" , "lower" :
return p .raiseLower (name )
case "moveleft" , "moveright" :
return p .moveLeftRight (name )
case "rule" :
return p .rule (name )
case "Rule" , "Space" :
return p .rule3D (name )
case "vcenter" :
arg , err := p .parseArgument (name )
if err != nil {
return nil , err
}
return []*mml .Node {texAtom (arg , mml .TeXClassVCenter )}, nil
case "fbox" :
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
content , err := p .internalMath (raw , "" , false )
if err != nil {
return nil , err
}
return []*mml .Node {setAttributes (node ("menclose" , content ...), map [string ]any {"notation" : "box" })}, nil
case "framebox" :
return p .frameBox (name )
case "begin" :
return p .beginEnvironment (name )
case "end" :
return p .endEnvironment (name )
case "matrix" , "array" , "pmatrix" , "cases" , "eqalign" , "eqalignno" , "leqalignno" , "displaylines" :
return p .matrixCommand (name )
case "DeclarePairedDelimiter" , "DeclarePairedDelimiters" , "DeclarePairedDelimiterX" , "DeclarePairedDelimitersX" , "DeclarePairedDelimiterXPP" , "DeclarePairedDelimitersXPP" :
return nil , p .declarePairedDelimiter (name )
case "textcolor" :
return p .textColor (name )
case "definecolor" :
return nil , p .defineColor (name )
case "colorbox" :
return p .colorBox (name , false )
case "fcolorbox" :
return p .colorBox (name , true )
case "enclose" :
return p .enclose (name )
case "cancel" , "bcancel" , "xcancel" :
return p .cancel (name )
case "cancelto" :
return p .cancelTo (name )
case "ce" , "pu" :
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
mode := mhchem .ModeCE
if name == "pu" {
mode = mhchem .ModePU
}
expansion , err := mhchem .ToTeX (raw , mode )
if err != nil {
return nil , texError ("MhchemParse" , "%s" , err )
}
p .source = p .source [:p .pos ] + expansion + p .source [p .pos :]
return nil , nil
case "bra" , "ket" , "braket" , "innerproduct" , "ip" , "outerproduct" , "dyad" , "ketbra" , "op" , "expectationvalue" , "expval" , "ev" , "matrixelement" , "matrixel" , "mel" :
return p .physicsBraket (name )
case "Braket" , "Set" , "set" :
return p .braket (name )
case "dd" , "differential" , "variation" , "var" , "dv" , "derivative" , "pdv" , "pderivative" , "partialderivative" , "fdv" , "fderivative" , "functionalderivative" :
return p .derivative (name , after )
case "diffd" :
return []*mml .Node {physicsDifferential ("d" )}, nil
case "commutator" , "comm" , "anticommutator" , "acomm" , "poissonbracket" , "pb" :
return p .commutator (name )
case "vectorbold" , "vb" :
return p .vectorBold (name )
case "vectorarrow" , "va" , "vectorunit" , "vu" :
return p .vectorAccent (name )
case "vnabla" :
return []*mml .Node {p .physicsNabla ()}, nil
case "qqtext" , "qq" , "qcc" , "qif" , "qthen" , "qelse" , "qotherwise" , "qunless" , "qgiven" , "qusing" , "qassume" , "qsince" , "qlet" , "qfor" , "qall" , "qeven" , "qodd" , "qinteger" , "qand" , "qor" , "qas" , "qin" :
return p .quickQuadText (name )
case "mqty" , "matrixquantity" , "smqty" , "smallmatrixquantity" :
return p .matrixQuantity (name )
case "prescript" :
return p .prescript (name )
case "nobreakspace" :
return []*mml .Node {token ("mtext" , "\u00a0" )}, nil
case "negmedspace" :
return []*mml .Node {spacer ("-0.222em" )}, nil
case "negthickspace" :
return []*mml .Node {spacer ("-0.278em" )}, nil
case "color" :
color , err := p .readColor (name )
if err != nil {
return nil , err
}
return []*mml .Node {setAttributes (node ("mstyle" , forcedRow (nil , true )), map [string ]any {"mathcolor" : color })}, nil
case "noalign" , "notag" , "nonumber" , "nolimits" , "limits" , "displaystyle" , "textstyle" , "scriptstyle" , "scriptscriptstyle" :
return nil , nil
}
return nil , texError ("UndefinedControlSequence" , "Undefined control sequence \\%s" , name )
}
func namedOperator(text string ) *mml .Node {
n := token ("mo" , text )
n .Attributes .Set ("movablelimits" , true )
n .SetProperty ("movablelimits" , true )
n .SetProperty ("movesupsub" , true )
n .Attributes .Set ("form" , "prefix" )
n .TeXClass = mml .TeXClassOp
n .SetProperty ("texClass" , mml .TeXClassOp )
return n
}
func lookupExtensionSymbol(name string ) (*mml .Node , bool ) {
for _ , definition := range gensymb .Configuration .Symbols {
if definition .Name != name {
continue
}
n := token (definition .TokenKind , definition .Character )
for _ , attribute := range definition .Attributes {
n .Attributes .Set (attribute .Name , attribute .Value )
}
return n , true
}
return nil , false
}
func space(width string ) *mml .Node {
n := setAttributes (node ("mspace" ), map [string ]any {"width" : width })
n .Flags .Spacelike = true
return n
}
func spacer(width string ) *mml .Node {
return setAttributes (node ("mstyle" , space (width )), map [string ]any {"scriptlevel" : 0 })
}
func (p *parser ) fraction (name , style string ) ([]*mml .Node , error ) {
numerator , err := p .parseArgument (name )
if err != nil {
return nil , err
}
denominator , err := p .parseArgument (name )
if err != nil {
return nil , err
}
frac := node ("mfrac" , numerator , denominator )
if style != "" {
frac = setAttributes (node ("mstyle" , frac ), styleAttributes (style ))
}
return []*mml .Node {frac }, nil
}
func (p *parser ) binomial (name , style string ) ([]*mml .Node , error ) {
numerator , err := p .parseArgument (name )
if err != nil {
return nil , err
}
denominator , err := p .parseArgument (name )
if err != nil {
return nil , err
}
frac := setAttributes (node ("mfrac" , numerator , denominator ), map [string ]any {"linethickness" : "0" })
frac .SetProperty ("withDelims" , true )
content := forcedRow ([]*mml .Node {
p .amsFixedFencePalette ("(" , mml .TeXClassOpen ),
frac ,
p .amsFixedFencePalette (")" , mml .TeXClassClose ),
}, false )
content .SetProperty ("open" , "(" )
content .SetProperty ("close" , ")" )
content .SetProperty ("texClass" , mml .TeXClassOrd )
content .TeXClass = mml .TeXClassOrd
if style != "" {
content = setAttributes (node ("mstyle" , content ), styleAttributes (style ))
}
return []*mml .Node {content }, nil
}
func (p *parser ) generalizedFraction (name string ) ([]*mml .Node , error ) {
left , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
right , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
thickness , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
style , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
numerator , err := p .parseArgument (name )
if err != nil {
return nil , err
}
denominator , err := p .parseArgument (name )
if err != nil {
return nil , err
}
frac := node ("mfrac" , numerator , denominator )
if thickness != "" {
frac .Attributes .Set ("linethickness" , thickness )
}
content := frac
if style != "" {
content = setAttributes (node ("mstyle" , content ), styleAttributes (style ))
}
open , err := p .convertDelimiterArgument (left )
if err != nil {
return nil , err
}
close , err := p .convertDelimiterArgument (right )
if err != nil {
return nil , err
}
if open != "" || close != "" {
content = p .fenced (open , content , close , true )
}
return []*mml .Node {content }, nil
}
func (p *parser ) continuedFraction (name string ) ([]*mml .Node , error ) {
align , _ , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
numerator , err := p .parseArgument (name )
if err != nil {
return nil , err
}
denominator , err := p .parseArgument (name )
if err != nil {
return nil , err
}
strutStyle := func (arg *mml .Node ) *mml .Node {
strut := setAttributes (node ("mpadded" , node ("mrow" )), map [string ]any {
"width" : "0" , "height" : "8.6pt" , "depth" : "3pt" ,
})
styled := setAttributes (node ("mstyle" , texAtom (arg , mml .TeXClassOrd )), map [string ]any {"displaystyle" : false , "scriptlevel" : 0 })
return node ("mrow" , strut , styled )
}
frac := node ("mfrac" , strutStyle (numerator ), strutStyle (denominator ))
if align == "l" || align == "r" {
value := map [string ]string {"l" : "left" , "r" : "right" }[align ]
frac .Attributes .Set ("numalign" , value )
frac .Attributes .Set ("denomalign" , value )
}
return []*mml .Node {frac }, nil
}
func (p *parser ) sqrt (name string ) ([]*mml .Node , error ) {
index , _ , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
if raw == `\frac` {
for i := 0 ; i < 2 ; i ++ {
argument , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
raw += "{" + argument + "}"
}
}
radical , err := p .parseArgumentString (raw )
if err != nil {
return nil , err
}
if index == "" {
return []*mml .Node {node ("msqrt" , radical )}, nil
}
root , err := p .parseRootIndex (index )
if err != nil {
return nil , err
}
return []*mml .Node {node ("mroot" , radical , root )}, nil
}
func (p *parser ) root (name string ) ([]*mml .Node , error ) {
start := p .pos
depth := 0
for p .pos < len (p .source ) {
if depth == 0 && strings .HasPrefix (p .source [p .pos :], "\\of" ) {
index := p .source [start :p .pos ]
p .pos += len ("\\of" )
radical , err := p .parseArgument (name )
if err != nil {
return nil , err
}
root , err := p .parseRootIndex (index )
if err != nil {
return nil , err
}
return []*mml .Node {node ("mroot" , radical , root )}, nil
}
switch p .source [p .pos ] {
case '{' :
depth ++
case '}' :
if depth > 0 {
depth --
}
}
p .consumeRune ()
}
return nil , texError ("TokenNotFoundForCommand" , "Could not find \\of for \\root" )
}
func (p *parser ) mathChoice (name string ) ([]*mml .Node , error ) {
choices := make ([]*mml .Node , 4 )
for i := range choices {
choice , err := p .parseArgument (name )
if err != nil {
return nil , err
}
choices [i ] = choice
}
return []*mml .Node {node ("MathChoice" , choices ...)}, nil
}
func styleAttributes(style string ) map [string ]any {
switch strings .TrimSpace (style ) {
case "0" , "D" , "D'" :
return map [string ]any {"displaystyle" : true , "scriptlevel" : 0 }
case "1" , "T" , "T'" :
return map [string ]any {"displaystyle" : false , "scriptlevel" : 0 }
case "2" , "S" , "S'" :
return map [string ]any {"displaystyle" : false , "scriptlevel" : 1 }
case "3" , "SS" , "SS'" :
return map [string ]any {"displaystyle" : false , "scriptlevel" : 2 }
}
return nil
}
func (p *parser ) bigDelimiter (name string ) ([]*mml .Node , error ) {
delim , err := p .readDelimiter (name , true )
if err != nil {
return nil , err
}
size := map [string ]string {"big" : "1.2em" , "Big" : "1.623em" , "bigg" : "2.047em" , "Bigg" : "2.470em" }
base := strings .TrimRight (name , "lrm" )
class := mml .TeXClassOrd
if strings .HasSuffix (name , "l" ) {
class = mml .TeXClassOpen
} else if strings .HasSuffix (name , "r" ) {
class = mml .TeXClassClose
} else if strings .HasSuffix (name , "m" ) {
class = mml .TeXClassRel
}
mo := p .token ("mo" , delim )
mo .Attributes .Set ("minsize" , size [base ])
mo .Attributes .Set ("maxsize" , size [base ])
mo .Attributes .Set ("fence" , true )
mo .Attributes .Set ("stretchy" , true )
mo .Attributes .Set ("symmetric" , true )
return []*mml .Node {texAtom (mo , class )}, nil
}
var accentCharacters = map [string ]string {
"bar" : "¯" , "hat" : "^" , "widehat" : "^" , "tilde" : "~" , "widetilde" : "~" , "vec" : "→" ,
"dot" : "˙" , "ddot" : "¨" , "dddot" : "⃛" , "ddddot" : "⃜" ,
"acute" : "´" , "grave" : "`" , "breve" : "˘" , "check" : "ˇ" , "mathring" : "˚" ,
}
func (p *parser ) accent (name string ) ([]*mml .Node , error ) {
if name == "vec" || name == "hat" {
alias , children := p .vectorAlias , p .starMacroChildren
p .vectorAlias , p .starMacroChildren = true , true
defer func () { p .vectorAlias , p .starMacroChildren = alias , children }()
}
base , err := p .parseArgument (name )
if err != nil {
return nil , err
}
wide := strings .HasPrefix (name , "wide" )
accent := p .operator (accentCharacters [name ], mml .TeXClassOrd , map [string ]any {"accent" : true , "stretchy" : wide })
ambientFontToken (accent )
accent .SetProperty ("mathaccent" , true )
core := base
if base .Flags .Embellished {
core = limitsCore (base )
}
if core != nil {
movable , _ := core .Property ("movablelimits" )
if core .Kind == "mo" || limitsTruthy (movable ) {
applySourceObject (core , mjSourceObject {{Name : "movablelimits" , Value : false }})
}
}
result := node ("mover" , base , accent )
return []*mml .Node {texAtom (result , mml .TeXClassOrd )}, nil
}
func (p *parser ) underOver (name string ) ([]*mml .Node , error ) {
base , err := p .parseArgument (name )
if err != nil {
return nil , err
}
under := strings .HasPrefix (name , "under" )
char := "¯"
stretchy := true
switch name {
case "bar" :
stretchy = false
case "overline" , "underline" :
char = "―"
case "overbrace" , "underbrace" :
char = map [bool ]string {true : "⏟" , false : "⏞" }[under ]
case "overparen" :
char = "⏜"
case "underparen" :
char = "⏝"
case "overleftrightarrow" , "underleftrightarrow" :
char = "↔"
case "overrightarrow" , "underrightarrow" :
char = "→"
case "overleftarrow" , "underleftarrow" :
char = "←"
}
mark := p .operator (char , mml .TeXClassOrd , map [string ]any {"stretchy" : stretchy })
if name == "overbrace" || name == "underbrace" {
if moves , _ := base .Property ("movesupsub" ); moves == true && base .Flags .Embellished {
kind := map [string ]string {"msub" : "munder" , "msup" : "mover" , "msubsup" : "munderover" }[base .Kind ]
if kind != "" {
replacement := node (kind , base .Children ...)
replacement .Attributes .SetList (base .Attributes .Explicit ())
replacement .Properties = base .Properties .Clone ()
base = replacement
}
}
movable , _ := base .Property ("movablelimits" )
attribute , _ := base .Attributes .Get ("movablelimits" )
moveLimits := propertyBool (movable )
if base .Kind == "mo" {
moveLimits = moveLimits || propertyBool (attribute )
forms := operatorForms (base )
if form , ok := base .Attributes .GetExplicit ("form" ); ok {
forms = append ([]string {propertyString (form )}, forms ...)
}
if definition , ok := lookupOperatorDefinition (textContent (base ), forms ); ok {
for _ , property := range definition .Properties {
if property .Name == "movablelimits" && propertyBool (property .Value ) {
moveLimits = true
}
}
}
}
if moveLimits {
applySourceObject (base , mjSourceObject {{Name : "movablelimits" , Value : false }})
}
if (base .Kind == "munder" || base .Kind == "mover" || base .Kind == "munderover" ) && base .Flags .Embellished {
core := base
for core .Kind != "mo" && core .Flags .Embellished {
next := core .Core ()
if next == nil || next == core {
break
}
core = next
}
if core .Kind == "mo" {
core .Attributes .Set ("lspace" , 0 )
core .Attributes .Set ("rspace" , 0 )
}
empty := p .noteMO (node ("mo" ))
empty .Attributes .Set ("rspace" , 0 )
base = node ("mrow" , empty , base )
}
mark .Attributes .Set ("accent" , true )
kind , accent := "mover" , "accent"
if under {
kind , accent = "munder" , "accentunder"
}
stack := texAtom (setAttributes (node (kind , base , mark ), map [string ]any {accent : true }), mml .TeXClassOp )
stack .SetProperty ("movesupsub" , true )
stack .SetProperty ("subsupOK" , true )
return []*mml .Node {stack }, nil
}
checkMovableLimits (base )
base = p .normalizeDecorationBase (base )
if under {
decoration := setAttributes (node ("munder" , base , mark ), map [string ]any {"accentunder" : true })
decoration .SetProperty ("subsupOK" , true )
return []*mml .Node {decoration }, nil
}
decoration := setAttributes (node ("mover" , base , mark ), map [string ]any {"accent" : true })
decoration .SetProperty ("subsupOK" , true )
return []*mml .Node {decoration }, nil
}
func normalizeDecorationBase(base *mml .Node ) *mml .Node {
return normalizeDecorationBaseWithMO (base , func () *mml .Node { return node ("mo" ) })
}
func normalizeDecorationBaseWithMO(base *mml .Node , makeMO func () *mml .Node ) *mml .Node {
if (base .Kind != "munder" && base .Kind != "mover" && base .Kind != "munderover" ) || !base .Flags .Embellished {
return base
}
applySourceObject (limitsCore (base ), mjSourceObject {{Name : "lspace" , Value : 0 }, {Name : "rspace" , Value : 0 }})
empty := makeMO ()
empty .Attributes .Set ("rspace" , 0 )
return node ("mrow" , empty , base )
}
func normalizeStackBase(base *mml .Node ) *mml .Node {
if moves , _ := base .Property ("movesupsub" ); moves == true && base .Flags .Embellished {
kind := map [string ]string {"msub" : "munder" , "msup" : "mover" , "msubsup" : "munderover" }[base .Kind ]
if kind != "" {
replacement := node (kind , base .Children ...)
replacement .Attributes .SetList (base .Attributes .Explicit ())
replacement .Properties = base .Properties .Clone ()
base = replacement
}
}
checkMovableLimits (base )
return base
}
func (p *parser ) overSet (name string ) ([]*mml .Node , error ) {
over , err := p .parseArgument (name )
if err != nil {
return nil , err
}
base , err := p .parseArgument (name )
if err != nil {
return nil , err
}
if name == "overset" {
base = normalizeStackBase (base )
if over .Kind == "mo" {
over .Attributes .Set ("accent" , false )
}
}
return []*mml .Node {node ("mover" , base , over )}, nil
}
func (p *parser ) underSet (name string ) ([]*mml .Node , error ) {
under , err := p .parseArgument (name )
if err != nil {
return nil , err
}
base , err := p .parseArgument (name )
if err != nil {
return nil , err
}
base = normalizeStackBase (base )
if under .Kind == "mo" {
under .Attributes .Set ("accent" , false )
}
return []*mml .Node {setAttributes (node ("munder" , base , under ), map [string ]any {"accentunder" : false })}, nil
}
func (p *parser ) overUnderSet (name string ) ([]*mml .Node , error ) {
over , err := p .parseArgument (name )
if err != nil {
return nil , err
}
under , err := p .parseArgument (name )
if err != nil {
return nil , err
}
base , err := p .parseArgument (name )
if err != nil {
return nil , err
}
checkMovableLimits (base )
if over .Kind == "mo" {
over .Attributes .Set ("accent" , false )
}
if under .Kind == "mo" {
under .Attributes .Set ("accent" , false )
}
return []*mml .Node {setAttributes (node ("munderover" , base , under , over ), map [string ]any {"accent" : false , "accentunder" : false })}, nil
}
func checkMovableLimits(base *mml .Node ) {
movable , _ := base .Property ("movablelimits" )
moveLimits := movable != nil
switch value := movable .(type ) {
case bool :
moveLimits = value
case string :
moveLimits = value != ""
case int :
moveLimits = value != 0
case int64 :
moveLimits = value != 0
case float64 :
moveLimits = value != 0 && value == value
}
if base .Kind == "mo" {
if definition , ok := lookupOperatorDefinition (textContent (base ), operatorForms (base )); ok {
for _ , property := range definition .Properties {
if property .Name == "movablelimits" && propertyBool (property .Value ) {
moveLimits = true
}
}
}
}
if moveLimits {
applySourceObject (base , mjSourceObject {{Name : "movablelimits" , Value : false }})
}
}
var arrowCharacters = map [string ]string {
"xrightarrow" : "→" , "xleftarrow" : "←" , "xleftrightarrow" : "↔" ,
"xLeftarrow" : "⇐" , "xRightarrow" : "⇒" , "xLeftrightarrow" : "⇔" ,
"xhookleftarrow" : "↩" , "xhookrightarrow" : "↪" , "xmapsto" : "↦" ,
"xrightharpoondown" : "⇁" , "xleftharpoondown" : "↽" ,
"xrightleftharpoons" : "⇌" , "xrightharpoonup" : "⇀" ,
"xleftharpoonup" : "↼" , "xleftrightharpoons" : "⇋" ,
}
func (p *parser ) xArrow (name string ) ([]*mml .Node , error ) {
belowRaw , hasBelow , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
above , err := p .parseArgument (name )
if err != nil {
return nil , err
}
arrow := p .operator (arrowCharacters [name ], mml .TeXClassRel , map [string ]any {"stretchy" : true })
if hasBelow {
below , err := p .parseArgumentString (belowRaw )
if err != nil {
return nil , err
}
return []*mml .Node {node ("munderover" , arrow , below , above )}, nil
}
return []*mml .Node {node ("mover" , arrow , above )}, nil
}
func (p *parser ) textCommand (name , variant string ) ([]*mml .Node , error ) {
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
n := textRow (raw )
if variant != "normal" {
applyMathVariant (n , variant )
n .Walk (func (current *mml .Node ) bool {
if current .Kind == "mtext" {
current .Attributes .Set ("mathvariant" , variant )
}
return true
})
}
return []*mml .Node {n }, nil
}
func textRow(raw string ) *mml .Node {
return node ("mtext" , mml .NewText (strings .ReplaceAll (raw , "~" , "\u00a0" )))
}
var mathVariants = func () map [string ]string {
variants := baseFontMappings ("MathFont" )
variants ["boldsymbol" ] = "bold-italic"
return variants
}()
func (p *parser ) mathFont (name string ) ([]*mml .Node , error ) {
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
arg , err := p .parseMathFontString (raw , mathVariants [name ], name != "boldsymbol" )
if err != nil {
return nil , err
}
return []*mml .Node {texAtom (arg , mml .TeXClassOrd )}, nil
}
func (p *parser ) operatorName (name string ) ([]*mml .Node , error ) {
star := p .readStar ()
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
n := p .operator (strings .TrimSpace (raw ), mml .TeXClassOp , map [string ]any {"mathvariant" : "normal" , "movablelimits" : star })
n .SetProperty ("movesupsub" , star )
return []*mml .Node {n }, nil
}
func (p *parser ) declareMathOperator (name string ) error {
star := p .readStar ()
cs , err := p .readCSNameArgument (name )
if err != nil {
return err
}
body , _ , err := p .readArgument (name , false )
if err != nil {
return err
}
command := "\\operatorname"
if star {
command += "*"
}
p .state .macros [cs ] = macroDefinition {body : command + "{" + body + "}" }
return nil
}
func (p *parser ) mathClass (name string ) ([]*mml .Node , error ) {
arg , err := p .parseArgument (name )
if err != nil {
return nil , err
}
classes := map [string ]mml .TeXClass {
"mathop" : mml .TeXClassOp , "mathrel" : mml .TeXClassRel , "mathbin" : mml .TeXClassBin ,
"mathord" : mml .TeXClassOrd , "mathopen" : mml .TeXClassOpen , "mathclose" : mml .TeXClassClose ,
"mathpunct" : mml .TeXClassPunct , "mathinner" : mml .TeXClassInner ,
}
n := texAtom (arg , classes [name ])
if name == "mathop" {
n .SetProperty ("movablelimits" , true )
n .SetProperty ("movesupsub" , true )
p .commandNamedFunction = true
}
return []*mml .Node {n }, nil
}
func (p *parser ) horizontalSpace (name string ) ([]*mml .Node , error ) {
width , err := p .readDimension (name )
if err != nil {
return nil , err
}
return []*mml .Node {space (width )}, nil
}
func (p *parser ) phantom (name string ) ([]*mml .Node , error ) {
arg , err := p .parseArgument (name )
if err != nil {
return nil , err
}
phantom := node ("mphantom" , arg )
if name == "hphantom" {
return []*mml .Node {texAtom (setAttributes (node ("mpadded" , phantom ), map [string ]any {"height" : 0 , "depth" : 0 }), mml .TeXClassOrd )}, nil
}
if name == "vphantom" {
return []*mml .Node {texAtom (setAttributes (node ("mpadded" , phantom ), map [string ]any {"width" : 0 }), mml .TeXClassOrd )}, nil
}
return []*mml .Node {texAtom (phantom , mml .TeXClassOrd )}, nil
}
func (p *parser ) smash (name string ) ([]*mml .Node , error ) {
option , _ , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
arg , err := p .parseArgument (name )
if err != nil {
return nil , err
}
attrs := map [string ]any {}
if option == "" || option == "t" {
attrs ["height" ] = 0
}
if option == "" || option == "b" {
attrs ["depth" ] = 0
}
return []*mml .Node {texAtom (setAttributes (node ("mpadded" , arg ), attrs ), mml .TeXClassOrd )}, nil
}
func (p *parser ) lap (name string ) ([]*mml .Node , error ) {
if strings .HasPrefix (name , "math" ) || strings .HasPrefix (name , "cramped" ) {
return p .mathLap (name )
}
if name == "clap" || name == "textclap" || name == "textllap" || name == "textrlap" {
return p .textLap (name )
}
arg , err := p .parseArgument (name )
if err != nil {
return nil , err
}
align := "left"
if strings .Contains (name , "llap" ) || name == "llap" {
align = "right"
} else if strings .Contains (name , "clap" ) || name == "clap" {
align = "center"
}
padded := node ("mpadded" , arg )
padded .Attributes .Set ("width" , 0 )
if align == "right" {
padded .Attributes .Set ("lspace" , "-1width" )
} else if align == "center" {
padded .Attributes .Set ("lspace" , "-.5width" )
}
return []*mml .Node {texAtom (padded , mml .TeXClassOrd )}, nil
}
func (p *parser ) cramped (name string ) ([]*mml .Node , error ) {
style , _ , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
arg , err := p .parseArgument (name )
if err != nil {
return nil , err
}
styled := node ("mstyle" , arg )
styled .Attributes .Set ("data-cramped" , true )
setMathtoolsDisplayLevel (styled , style )
return []*mml .Node {styled }, nil
}
func (p *parser ) mathMBox (name string ) ([]*mml .Node , error ) {
arg , err := p .parseArgument (name )
if err != nil {
return nil , err
}
children := []*mml .Node {arg }
if arg .Kind == "mrow" && arg .Flags .Inferred {
children = arg .Children
}
return []*mml .Node {node ("mrow" , children ...)}, nil
}
func (p *parser ) mathMakeBox (name string ) ([]*mml .Node , error ) {
width , hasWidth , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
defaultPosition := "c"
position , _ , err := p .readBrackets (&defaultPosition )
if err != nil {
return nil , err
}
arg , err := p .parseArgument (name )
if err != nil {
return nil , err
}
box := node ("mpadded" , arg )
if hasWidth && width != "" {
box .Attributes .Set ("width" , width )
}
if position == "c" {
box .Attributes .Set ("data-align" , "center" )
} else if position == "r" {
box .Attributes .Set ("data-align" , "right" )
}
return []*mml .Node {box }, nil
}
func (p *parser ) raiseLower (name string ) ([]*mml .Node , error ) {
amount , err := p .readDimension (name )
if err != nil {
return nil , err
}
lower := name == "lower"
if strings .HasPrefix (amount , "-" ) {
amount = amount [1 :]
lower = !lower
}
dh , dd := "+" +amount , "-" +amount
if lower {
dh , dd = dd , dh
}
p .commandPosition = &positionItem {name : name , height : dh , depth : dd }
return nil , nil
}
func (p *parser ) moveLeftRight (name string ) ([]*mml .Node , error ) {
amount , err := p .readDimension (name )
if err != nil {
return nil , err
}
opposite := "-" + amount
if strings .HasPrefix (amount , "-" ) {
opposite = amount [1 :]
}
if name == "moveleft" {
amount , opposite = opposite , amount
}
p .commandPosition = &positionItem {
name : name ,
left : setAttributes (node ("mspace" ), map [string ]any {"width" : amount }),
right : setAttributes (node ("mspace" ), map [string ]any {"width" : opposite }),
}
return nil , nil
}
func negateDimension(value string ) string {
if strings .HasPrefix (value , "-" ) {
return strings .TrimPrefix (value , "-" )
}
return "-" + strings .TrimPrefix (value , "+" )
}
func (p *parser ) rule (name string ) ([]*mml .Node , error ) {
voffset , _ , err := p .readBrackets (nil )
if err != nil {
var failure *Error
if errors .As (err , &failure ) && failure .ID == "MissingCloseBracket" {
return nil , texError (failure .ID , "Could not find closing ']' for argument to %s" , "\\" +name )
}
return nil , err
}
width , err := p .readDimension (name )
if err != nil {
return nil , err
}
height , err := p .readDimension (name )
if err != nil {
return nil , err
}
color := p .activeColor
if color == "" {
color = "black"
}
space := setAttributes (node ("mspace" ), map [string ]any {"width" : width , "height" : height , "mathbackground" : color })
if voffset != "" {
padded := setAttributes (node ("mpadded" , space ), map [string ]any {"voffset" : voffset })
if strings .HasPrefix (voffset , "-" ) {
padded .Attributes .Set ("height" , voffset )
padded .Attributes .Set ("depth" , "+" +voffset [1 :])
} else {
padded .Attributes .Set ("height" , "+" +voffset )
}
return []*mml .Node {padded }, nil
}
return []*mml .Node {space }, nil
}
func (p *parser ) readStar () bool {
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
if p .pos < len (p .source ) && p .source [p .pos ] == '*' {
p .pos ++
return true
}
return false
}
func (p *parser ) convertDelimiterArgument (raw string ) (string , error ) {
raw = strings .TrimSpace (raw )
if raw == "" || raw == "." {
return "" , nil
}
if delim , ok := lookupDelimiter (raw ); ok {
return delim , nil
}
return "" , texError ("MissingOrUnrecognizedDelim" , "Missing or unrecognized delimiter" )
}
func (p *parser ) declarePairedDelimiter (name string ) error {
cs , err := p .readCSNameArgument (name )
if err != nil {
return err
}
arguments := 1
body := "#1"
if strings .Contains (name , "X" ) {
countRaw , _ , err := p .readBrackets (nil )
if err != nil {
return err
}
if countRaw != "" {
arguments , err = parseInteger (countRaw , "IllegalMacroParam" )
if err != nil {
return err
}
}
}
open , _ , err := p .readArgument (name , false )
if err != nil {
return err
}
close , _ , err := p .readArgument (name , false )
if err != nil {
return err
}
if strings .Contains (name , "X" ) {
body , _, err = p .readArgument (name , false )
if err != nil {
return err
}
}
p .state .pairedDelimiters [cs ] = pairedDelimiter {open : open , close : close , body : body , arguments : arguments }
return nil
}
func (p *parser ) invokePairedDelimiter (name string , definition pairedDelimiter ) ([]*mml .Node , error ) {
star := p .readStar ()
size , hasSize , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
args := make ([]string , definition .arguments )
for i := range args {
args [i ], _, err = p .readArgument (name , false )
if err != nil {
return nil , err
}
}
body , err := substituteArguments (definition .body , args )
if err != nil {
return nil , err
}
content , err := p .parseString (body )
if err != nil {
return nil , err
}
open , err := p .convertDelimiterArgument (definition .open )
if err != nil {
return nil , err
}
close , err := p .convertDelimiterArgument (definition .close )
if err != nil {
return nil , err
}
stretchy := star
if hasSize && size != "" {
stretchy = true
}
return []*mml .Node {p .fenced (open , content , close , stretchy )}, nil
}
func (p *parser ) readColor (name string ) (string , error ) {
model , _ , err := p .readBrackets (nil )
if err != nil {
return "" , err
}
definition , _ , err := p .readArgument (name , false )
if err != nil {
return "" , err
}
value , err := p .state .colorModel .GetColor (model , definition )
return value , translateColorError (err )
}
func translateColorError(err error ) error {
if err == nil {
return nil
}
var colorError *texcolor .Error
if errors .As (err , &colorError ) {
return &Error {ID : colorError .ID , Message : colorError .Message }
}
return err
}
func (p *parser ) textColor (name string ) ([]*mml .Node , error ) {
color , err := p .readColor (name )
if err != nil {
return nil , err
}
oldColor := p .activeColor
p .activeColor = color
defer func () { p .activeColor = oldColor }()
math , err := p .parseArgument (name )
if err != nil {
return nil , err
}
return []*mml .Node {setAttributes (node ("mstyle" , math ), map [string ]any {"mathcolor" : color })}, nil
}
func (p *parser ) defineColor (name string ) error {
cname , _ , err := p .readArgument (name , false )
if err != nil {
return err
}
model , _ , err := p .readArgument (name , false )
if err != nil {
return err
}
definition , _ , err := p .readArgument (name , false )
if err != nil {
return err
}
return translateColorError (p .state .colorModel .DefineColor (model , cname , definition ))
}
func (p *parser ) colorBox (name string , framed bool ) ([]*mml .Node , error ) {
frameColor := ""
var err error
if framed {
frameColor , _, err = p .readArgument (name , false )
if err != nil {
return nil , err
}
}
background , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
content , err := p .internalMath (raw , "" , false )
if err != nil {
return nil , err
}
background , err = p .state .colorModel .GetColor ("named" , background )
if err != nil {
return nil , translateColorError (err )
}
box := setAttributes (node ("mpadded" , content ...), map [string ]any {"mathbackground" : background })
for _ , attribute := range texcolor .PaddingProperties ("5px" ) {
box .Attributes .Set (attribute .Name , attribute .Value )
}
if framed {
frameColor , err = p .state .colorModel .GetColor ("named" , frameColor )
if err != nil {
return nil , translateColorError (err )
}
box .Attributes .Set ("style" , "border: 2px solid " +frameColor )
}
return []*mml .Node {box }, nil
}
func (p *parser ) enclose (name string ) ([]*mml .Node , error ) {
notation , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
options , _ , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
math , err := p .parseArgument (name )
if err != nil {
return nil , err
}
attrs , err := keyvalOptions (options , enclose .AllowedOptions )
if err != nil {
return nil , err
}
attrs .Set ("notation" , strings .ReplaceAll (notation , "," , " " ))
return []*mml .Node {setKeyvalAttributes (node ("menclose" , math ), attrs )}, nil
}
func (p *parser ) cancel (name string ) ([]*mml .Node , error ) {
options , _ , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
math , err := p .parseArgument (name )
if err != nil {
return nil , err
}
attrs , err := keyvalOptions (options , enclose .AllowedOptions )
if err != nil {
return nil , err
}
attrs .Set ("notation" , map [string ]string {
"cancel" : texcancel .UpDiagonalStrike , "bcancel" : texcancel .DownDiagonalStrike ,
"xcancel" : texcancel .UpDiagonalStrike + " " + texcancel .DownDiagonalStrike ,
}[name ])
return []*mml .Node {setKeyvalAttributes (node ("menclose" , math ), attrs )}, nil
}
func (p *parser ) cancelTo (name string ) ([]*mml .Node , error ) {
options , _ , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
value , err := p .parseArgument (name )
if err != nil {
return nil , err
}
math , err := p .parseArgument (name )
if err != nil {
return nil , err
}
attrs , err := keyvalOptions (options , enclose .AllowedOptions )
if err != nil {
return nil , err
}
attrs .Set ("notation" , texcancel .CancelToNotation )
padded := node ("mpadded" , value )
for _ , attribute := range texcancel .CancelToPadding {
padded .Attributes .Set (attribute .Name , attribute .Value )
}
return []*mml .Node {node ("msup" , setKeyvalAttributes (node ("menclose" , math ), attrs ), padded )}, nil
}
func keyvalOptions(raw string , allowed []string ) (*ordered .Map [any ], error ) {
allowedSet := make (map [string ]bool , len (allowed ))
for _ , key := range allowed {
allowedSet [key ] = true
}
return parseUtilKeyvalOptions (raw , func (key string ) bool {
return allowedSet [key ]
}, false )
}
func setKeyvalAttributes(n *mml .Node , attributes *ordered .Map [any ]) *mml .Node {
for _ , name := range attributes .JavaScriptKeys () {
value , _ := attributes .Get (name )
n .Attributes .Set (name , value )
}
return n
}
func (p *parser ) colorDeclaration () (mjSourceObject , error ) {
color , err := p .readColor ("color" )
if err != nil {
return nil , err
}
p .activeColor = color
return mjSourceObject {{Name : "mathcolor" , Value : color }}, nil
}
func (p *parser ) physicsBraket (name string ) ([]*mml .Node , error ) {
switch name {
case "bra" :
starBra := p .readStar ()
bra , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
ket , starKet , hasKet , err := p .physicsFollowingKet (name )
if err != nil {
return nil , err
}
if hasKet {
macro := "\\left\\langle{" + bra + "}\\middle\\vert{" + ket + "}\\right\\rangle"
if starBra || starKet {
macro = "\\langle{" + bra + "}\\vert{" + ket + "}\\rangle"
}
parsed , err := p .parseChild (macro )
if err != nil {
return nil , err
}
return unwrapInferred (parsed ), nil
}
macro := "\\left\\langle{" + bra + "}\\right\\vert{}"
if starBra {
macro = "\\langle{" + bra + "}\\vert"
}
parsed , err := p .parseChild (macro )
if err != nil {
return nil , err
}
return unwrapInferred (parsed ), nil
case "ket" :
star := p .readStar ()
ket , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
macro := "\\left\\vert{" + ket + "}\\right\\rangle"
if star {
macro = "\\vert{" + ket + "}\\rangle"
}
parsed , err := p .parseChild (macro )
if err != nil {
return nil , err
}
return unwrapInferred (parsed ), nil
case "braket" , "innerproduct" , "ip" :
star := p .readStar ()
left , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
right := ""
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
if p .pos < len (p .source ) && p .source [p .pos ] == '{' {
right , _, err = p .readArgument (name , true )
if err != nil {
return nil , err
}
} else {
right = left
}
macro := "\\left\\langle{" + left + "}\\middle\\vert{" + right + "}\\right\\rangle"
if star {
macro = "\\langle{" + left + "}\\vert{" + right + "}\\rangle"
}
parsed , err := p .parseChild (macro )
if err != nil {
return nil , err
}
nodes := unwrapInferred (parsed )
for _ , n := range nodes {
n .Walk (func (current *mml .Node ) bool {
if current .Kind == "mo" && textContent (current ) == "|" {
current .RemoveProperty ("in-lists" )
}
return true
})
}
return nodes , nil
case "outerproduct" , "dyad" , "ketbra" , "op" :
ket , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
bra , _ , err := p .readArgument (name , true )
if err != nil {
return nil , err
}
if bra == "" {
bra = ket
}
left , err := p .parseString (ket )
if err != nil {
return nil , err
}
right , err := p .parseString (bra )
if err != nil {
return nil , err
}
return []*mml .Node {p .fenced ("|" , left , "⟩" , true ), p .fenced ("⟨" , right , "|" , true )}, nil
case "expectationvalue" , "expval" , "ev" :
return p .physicsExpectation (name )
case "matrixelement" , "matrixel" , "mel" :
bra , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
operatorRaw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
ket , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
content , err := p .parseString (bra + "\\middle|" + operatorRaw + "\\middle|" + ket )
if err != nil {
return nil , err
}
return []*mml .Node {p .fenced ("⟨" , content , "⟩" , true )}, nil
}
return nil , nil
}
func (p *parser ) parseExpansion (source string ) ([]*mml .Node , error ) {
count := p .state .macroCount
p .state .macroCount = 0
defer func () { p .state .macroCount = count }()
return p .parseExpansionWithStack (source , nil )
}
func (p *parser ) parseContinuationExpansion (source string ) ([]*mml .Node , error ) {
parsed , err := p .parseStringWithEnvironment (source , p .ensureStackGlobal (), nil , nil , p .environmentOwner )
if err != nil {
return nil , err
}
return unwrapInferred (parsed ), nil
}
func (p *parser ) parseExpansionWithStack (source string , global *parserStackGlobal ) ([]*mml .Node , error ) {
parsed , err := p .parseStringWithStack (source , global )
if err != nil {
return nil , err
}
if parsed .Kind == "mrow" && parsed .Flags .Inferred {
return parsed .Children , nil
}
return []*mml .Node {parsed }, nil
}
func physicsFenced(open string , content *mml .Node , close string , stretchy bool ) *mml .Node {
row := fenced (open , content , close , stretchy )
row .TeXClass = mml .TeXClassInner
return row
}
func (p *parser ) readUpToByte (close byte ) (string , error ) {
start := p .pos
braceDepth := 0
fenceDepth := 0
open := byte (0 )
switch close {
case ')' :
open = '('
case ']' :
open = '['
case '|' :
open = '|'
}
for p .pos < len (p .source ) {
c := p .source [p .pos ]
if c == '\\' {
p .pos ++
p .readControlSequence ()
continue
}
if c == '{' {
braceDepth ++
} else if c == '}' && braceDepth > 0 {
braceDepth --
} else if braceDepth == 0 {
if c == open && open != close {
fenceDepth ++
} else if c == close {
if fenceDepth == 0 {
result := p .source [start :p .pos ]
p .pos ++
return result , nil
}
fenceDepth --
}
}
p .consumeRune ()
}
return "" , texError ("TokenNotFoundForCommand" , "Could not find closing delimiter" )
}
func (p *parser ) physicsFollowingKet (name string ) (ket string , star bool , ok bool , err error ) {
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
if p .pos >= len (p .source ) || p .source [p .pos ] != '\\' {
return "" , false , false , nil
}
start := p .pos
p .pos ++
if p .readControlSequence () != "ket" {
p .pos = start
return "" , false , false , nil
}
afterName := p .pos
star = p .readStar ()
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
if p .pos >= len (p .source ) || p .source [p .pos ] != '{' {
p .pos = afterName
return "" , false , true , nil
}
ket , _, err = p .readArgument (name , true )
return ket , star , true , err
}
func physicsDifferential(character string ) *mml .Node {
d := token ("mi" , character )
if character != "d" {
return d
}
d .Attributes .Set ("mathvariant" , "normal" )
return texAtom (d , mml .TeXClassOrd )
}
func physicsNabla() *mml .Node {
return physicsNablaWithOperator (operator )
}
func physicsNablaWithOperator(makeOperator func (string , mml .TeXClass , map [string ]any ) *mml .Node ) *mml .Node {
inner := texAtom (makeOperator ("∇" , mml .TeXClassOrd , map [string ]any {"mathvariant" : "bold" }), mml .TeXClassOrd )
return texAtom (inner , mml .TeXClassOrd )
}
func (p *parser ) commutator (name string ) ([]*mml .Node , error ) {
star := p .readStar ()
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
big := ""
if p .pos < len (p .source ) && p .source [p .pos ] == '\\' {
p .pos ++
big = p .readControlSequence ()
switch big {
case "big" , "Big" , "bigg" , "Bigg" :
default :
return nil , texError ("MissingArgFor" , "Missing argument for %s" , "\\" +name )
}
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
}
if p .pos >= len (p .source ) || p .source [p .pos ] != '{' {
return nil , texError ("MissingArgFor" , "Missing argument for %s" , "\\" +name )
}
leftRaw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
rightRaw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
open , close := "[" , "]"
switch name {
case "anticommutator" , "acomm" , "poissonbracket" , "pb" :
open , close = "\\{" , "\\}"
}
argument := leftRaw + "," + rightRaw
switch {
case star :
argument = open + " " + argument + " " + close
case big != "" :
argument = "\\" + big + "l" + open + " " + argument + " " + "\\" + big + "r" + close
default :
argument = "\\left" + open + " " + argument + " " + "\\right" + close
}
parsed , err := p .parseChild (argument )
if err != nil {
return nil , err
}
return unwrapInferred (parsed ), nil
}
func (p *parser ) vectorBold (name string ) ([]*mml .Node , error ) {
star := p .readStar ()
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
arg , err := p .parseVectorString (raw , star )
if err != nil {
return nil , err
}
return unwrapInferred (arg ), nil
}
func (p *parser ) vectorAccent (name string ) ([]*mml .Node , error ) {
star := p .readStar ()
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
accent := "vec"
if name == "vectorunit" || name == "vu" {
accent = "hat"
}
bold := "vb"
if star {
bold += "*"
}
expansion , err := substituteMacroArguments ("\\" +accent +"{\\" +bold +"{#1}}" , []string {raw })
if err != nil {
return nil , err
}
joined , err := macroAddArgs (expansion , p .source [p .pos :], maxMacroBuffer )
if err != nil {
return nil , err
}
p .source , p .pos = joined , 0
p .state .macroCount ++
if p .state .macroCount > maxMacros {
return nil , texError ("MaxMacroSub1" , "MathJax maximum macro substitution count exceeded; is here a recursive macro call?" )
}
return nil , nil
}
func (p *parser ) quickQuadText (name string ) ([]*mml .Node , error ) {
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
star := p .pos < len (p .source ) && p .source [p .pos ] == '*'
if star {
p .pos ++
}
defaults := map [string ]string {
"qcc" : "c.c." , "qif" : "if" , "qthen" : "then" , "qelse" : "else" , "qotherwise" : "otherwise" ,
"qunless" : "unless" , "qgiven" : "given" , "qusing" : "using" , "qassume" : "assume" , "qsince" : "since" ,
"qlet" : "let" , "qfor" : "for" , "qall" : "all" , "qeven" : "even" , "qodd" : "odd" , "qinteger" : "integer" ,
"qand" : "and" , "qor" : "or" , "qas" : "as" , "qin" : "in" ,
}
text := defaults [name ]
cursorOverrun := 0
if text == "" {
var err error
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
if p .pos < len (p .source ) && p .pos +1 == len (p .source ) && p .source [p .pos ] == '\\' {
cursorOverrun = 1
}
text , _, err = p .readArgumentAtCursor (name , false )
if err != nil {
return nil , err
}
}
expansion := "\\text{" + text + "}\\quad "
if !star {
expansion = "\\quad" + expansion
}
p .source = p .source [:p .pos ] + expansion + p .source [p .pos :]
p .pos += cursorOverrun
return nil , nil
}
func (p *parser ) prescript (name string ) ([]*mml .Node , error ) {
sup , err := p .mathtoolsPrescriptArgument (name , "sup" )
if err != nil {
return nil , err
}
sub , err := p .mathtoolsPrescriptArgument (name , "sub" )
if err != nil {
return nil , err
}
base , err := p .mathtoolsPrescriptArgument (name , "arg" )
if err != nil {
return nil , err
}
if sup .Kind == "none" && sub .Kind == "none" {
return unwrapInferred (base ), nil
}
result := node ("mmultiscripts" , base , nil , nil , node ("mprescripts" ), sub , sup )
result .SetProperty ("fixPrescript" , true )
return []*mml .Node {result }, nil
}
func (p *parser ) mathtoolsPrescriptArgument (name , position string ) (*mml .Node , error ) {
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
arg := strings .TrimFunc (raw , internalTextSpace )
if strings .HasSuffix (arg , "\\" ) && strings .HasSuffix (raw , " " ) {
arg += " "
}
if arg == "" {
return node ("none" ), nil
}
format := p .mathtoolsOption ("prescript-" + position + "-format" )
if format != "" && format != "false" {
arg = format + "{" + arg + "}"
}
return p .parseChild (arg )
}
func unwrapInferred(n *mml .Node ) []*mml .Node {
if n != nil && n .Kind == "mrow" && n .Flags .Inferred {
return n .Children
}
return []*mml .Node {n }
}
func (p *parser ) derivative (name string , after **derivativeAutoOpen ) ([]*mml .Node , error ) {
if name == "dd" || name == "differential" || name == "variation" || name == "var" {
return p .differential (name , after )
}
star := p .readStar ()
order , hasOrder , err := p .readBrackets (nil )
if err != nil {
var failure *Error
if errors .As (err , &failure ) && failure .ID == "MissingCloseBracket" {
return nil , texError (failure .ID , "Could not find closing ']' for argument to %s" , "\\" +name )
}
return nil , err
}
firstRaw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
args := []string {firstRaw }
argMax := 2
op := "\\diffd"
switch name {
case "pdv" , "pderivative" , "partialderivative" :
argMax = 3
op = "\\partial"
case "fdv" , "fderivative" , "functionalderivative" :
op = "\\delta"
}
for {
p .skipNextSpaces ()
if p .pos >= len (p .source ) || p .source [p .pos ] != '{' || len (args ) == argMax {
break
}
arg , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
args = append (args , arg )
}
ignore := false
power1 , power2 := " " , " "
if argMax > 2 && len (args ) > 2 {
power1 = "^{" + strconv .Itoa (len (args )-1 ) + "}"
ignore = true
} else if hasOrder {
ignore = argMax > 2 && len (args ) > 1
power1 = "^{" + order + "}"
power2 = power1
}
frac := "\\frac"
if star {
frac = "\\flatfrac"
}
first , second := "" , args [0 ]
if len (args ) > 1 {
first , second = args [0 ], args [1 ]
}
rest := ""
for i := 2 ; i < len (args ) && args [i ] != "" ; i ++ {
rest += op + " " + args [i ]
}
expansion := frac + "{" + op + power1 + first + "}" +
"{" + op + " " + second + power2 + " " + rest + "}"
parsed , err := p .parseChild (expansion )
if err != nil {
return nil , err
}
*after = &derivativeAutoOpen {ignore : ignore }
return unwrapInferred (parsed ), nil
}
func keyvalString(raw string ) map [string ]string {
result := make (map [string ]string )
for _ , entry := range splitTopLevel (raw , ',' ) {
parts := strings .SplitN (entry , "=" , 2 )
if len (parts ) == 2 {
result [strings .TrimSpace (parts [0 ])] = strings .TrimSpace (parts [1 ])
}
}
return result
}
func numberString(value any ) string { return fmt .Sprint (value ) }
func parseFloat(value string ) (float64 , error ) {
return strconv .ParseFloat (strings .TrimSpace (value ), 64 )
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .