package tex
import (
"strings"
"github.com/d2lang/mathjax-go/internal/mml"
)
func (p *parser ) baseAMSCommand (name string ) (nodes []*mml .Node , handled bool , err error ) {
if _ , ok := amsArrowSizes [name ]; ok {
nodes , err = p .amsXArrow (name )
return nodes , true , err
}
switch name {
case "genfrac" :
nodes , err = p .amsGenfrac (name )
case "DeclareMathOperator" :
err = p .amsDeclareMathOperator (name )
case "operatorname" :
nodes , err = p .amsOperatorName (name )
case "idotsint" :
nodes , err = p .amsMultiIntegral (name )
case "sideset" :
nodes , err = p .amsSideSet (name )
default :
return nil , false , nil
}
return nodes , true , err
}
func (p *parser ) baseAMSEnvironment (name string ) (nodes []*mml .Node , handled bool , err error ) {
if name != "flalign" && name != "flalign*" && !isAMSXAlignAt (name ) {
return nil , false , nil
}
nodes , err = p .amsFlalignEnvironment (name )
return nodes , true , err
}
func (p *parser ) amsGenfrac (name string ) ([]*mml .Node , error ) {
left , err := p .amsGenfracDelimiter (name )
if err != nil {
return nil , err
}
right , err := p .amsGenfracDelimiter (name )
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
}
style = amsGenfracTrimStyle (style )
numerator , err := p .parseArgument (name )
if err != nil {
return nil , err
}
denominator , err := p .parseArgument (name )
if err != nil {
return nil , err
}
fraction := node ("mfrac" , numerator , denominator )
if thickness != "" {
fraction .Attributes .Set ("linethickness" , thickness )
}
var content *mml .Node = fraction
if left != nil || right != nil {
fraction .SetProperty ("withDelims" , true )
content , err = p .amsGenfracFixedFence (left , fraction , right )
if err != nil {
return nil , err
}
}
if style != "" {
styleIndex , ok := amsGenfracStyleIndex (style )
if !ok {
return nil , texError ("BadMathStyleFor" , "Bad math style for \\%s" , name )
}
attributes := map [string ]any {}
if styleIndex == 0 {
attributes ["displaystyle" ] = true
attributes ["scriptlevel" ] = 0
} else {
attributes ["displaystyle" ] = false
attributes ["scriptlevel" ] = styleIndex - 1
}
styled := node ("mstyle" , content )
styled .Attributes .Set ("displaystyle" , attributes ["displaystyle" ])
styled .Attributes .Set ("scriptlevel" , attributes ["scriptlevel" ])
content = styled
}
return []*mml .Node {content }, nil
}
func amsGenfracTrimStyle(raw string ) string {
style := strings .TrimFunc (raw , internalTextSpace )
if strings .HasSuffix (style , "\\" ) && strings .HasSuffix (raw , " " ) {
style += " "
}
return style
}
func amsGenfracStyleIndex(style string ) (int , bool ) {
i := 0
negative := false
if len (style ) != 0 && (style [0 ] == '+' || style [0 ] == '-' ) {
negative = style [0 ] == '-'
i ++
}
start := i
value := 0
for i < len (style ) && style [i ] >= '0' && style [i ] <= '9' {
value = value *10 + int (style [i ]-'0' )
if value > 3 {
return 0 , false
}
i ++
}
if i == start || (negative && value != 0 ) {
return 0 , false
}
return value , true
}
func amsFixedFencePalette(character string , class mml .TeXClass ) *mml .Node {
return amsFixedFencePaletteWithToken (character , class , token )
}
func amsFixedFencePaletteWithToken(character string , class mml .TeXClass , makeToken func (string , string ) *mml .Node ) *mml .Node {
display := amsFixedFenceWithToken (character , class , "2.047em" , makeToken )
text := amsFixedFenceWithToken (character , class , "1.2em" , makeToken )
script := amsFixedFenceWithToken (character , class , "1.2em" , makeToken )
scriptScript := amsFixedFenceWithToken (character , class , "1.2em" , makeToken )
return node ("MathChoice" , display , text , script , scriptScript )
}
func amsFixedFence(character string , class mml .TeXClass , size string ) *mml .Node {
return amsFixedFenceWithToken (character , class , size , token )
}
func amsFixedFenceWithToken(character string , class mml .TeXClass , size string , makeToken func (string , string ) *mml .Node ) *mml .Node {
mo := makeToken ("mo" , character )
mo .Attributes .Set ("minsize" , size )
mo .Attributes .Set ("maxsize" , size )
mo .Attributes .Set ("fence" , true )
mo .Attributes .Set ("stretchy" , true )
mo .Attributes .Set ("symmetric" , true )
return texAtom (mo , class )
}
func (p *parser ) amsDeclareMathOperator (name string ) error {
star := p .readStar ()
raw , _ , err := p .readArgument (name , false )
if err != nil {
return err
}
cs := strings .TrimFunc (raw , internalTextSpace )
if strings .HasSuffix (cs , "\\" ) && strings .HasSuffix (raw , " " ) {
cs += " "
}
cs = strings .TrimPrefix (cs , "\\" )
operator , _ , err := p .readArgument (name , false )
if err != nil {
return err
}
body := "\\operatorname"
if star {
body += "*"
}
p .state .macros [cs ] = macroDefinition {body : body + "{" + operator + "}" }
return nil
}
func (p *parser ) amsOperatorName (name string ) ([]*mml .Node , error ) {
star := p .readStar ()
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
op := strings .TrimFunc (raw , internalTextSpace )
if strings .HasSuffix (op , "\\" ) && strings .HasSuffix (raw , " " ) {
op += " "
}
operatorParser := *p
operatorParser .activeFont = "normal"
operatorParser .fontExplicitEmpty = false
operatorParser .identifierPattern = identifierPatternOperator
operatorParser .operatorLetters = true
result , err := operatorParser .parseChild (op )
if err != nil {
return nil , err
}
if result .Kind != "mi" {
result = node ("TeXAtom" , result )
}
result .Walk (func (n *mml .Node ) bool {
if origin , _ := n .Property (vectorFactoryToken ); origin == true {
n .SetProperty (vectorFactoryDone , true )
}
return true
})
result .TeXClass = mml .TeXClassOp
if result .Kind != "mi" {
result .SetProperty ("texClass" , mml .TeXClassOp )
}
result .SetProperty ("movesupsub" , star )
result .SetProperty ("movablelimits" , true )
result .SetProperty ("texClass" , mml .TeXClassOp )
if !star {
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
start := p .pos
if p .pos < len (p .source ) && p .source [p .pos ] == '\\' {
p .pos ++
if p .readControlSequence () != "limits" {
p .pos = start
}
}
}
return []*mml .Node {result }, nil
}
func (p *parser ) amsMultiIntegral (_ string ) ([]*mml .Node , error ) {
return p .parseContinuationExpansion ("\\int\\cdots\\int" )
}
func (p *parser ) amsSideSet (name string ) ([]*mml .Node , error ) {
pre , err := p .parseSideSetArgument (name )
if err != nil {
return nil , err
}
preScripts , preRest := splitSideSet (pre )
post , err := p .parseSideSetArgument (name )
if err != nil {
return nil , err
}
postScripts , postRest := splitSideSet (post )
base , err := p .parseSideSetArgument (name )
if err != nil {
return nil , err
}
mmlNode := base
if preScripts != nil {
if preRest != nil {
padded := node ("mpadded" , p .copyNode (base ))
padded .Attributes .Set ("width" , 0 )
phantom := node ("mphantom" , padded )
if err := preScripts .ReplaceChild (phantom , preScripts .Children [0 ]); err != nil {
return nil , err
}
refreshDynamicFlags (preScripts )
} else {
children := []*mml .Node {base }
if postScripts != nil {
children = append (children , sideSetScript (postScripts , 1 ), sideSetScript (postScripts , 2 ))
}
children = append (children , node ("mprescripts" ), sideSetScript (preScripts , 1 ), sideSetScript (preScripts , 2 ))
mmlNode = node ("mmultiscripts" , children ...)
mmlNode .SetProperty ("scriptalign" , "left" )
}
}
if postScripts != nil && mmlNode == base {
if err := postScripts .ReplaceChild (base , postScripts .Children [0 ]); err != nil {
return nil , err
}
refreshDynamicFlags (postScripts )
mmlNode = postScripts
}
var children []*mml .Node
appendPart := func (n *mml .Node ) {
if n == nil {
return
}
if n .Flags .Inferred {
children = append (children , n .Children ...)
} else {
children = append (children , n )
}
}
if preRest != nil {
appendPart (preScripts )
appendPart (preRest )
}
appendPart (mmlNode )
appendPart (postRest )
result := texAtom (forcedRow (children , true ), mml .TeXClassOp )
result .SetProperty ("movesupsub" , true )
result .SetProperty ("movablelimits" , true )
return []*mml .Node {result }, nil
}
func (p *parser ) parseSideSetArgument (name string ) (*mml .Node , error ) {
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
return p .parseChild (raw )
}
func splitSideSet(n *mml .Node ) (scripts , rest *mml .Node ) {
if n == nil || n .Flags .Inferred && len (n .Children ) == 0 {
return nil , nil
}
if (n .Kind == "msubsup" || n .Kind == "msub" || n .Kind == "msup" || n .Kind == "mmultiscripts" ) && sideSetEmptyBase (n ) {
return n , nil
}
if !n .Flags .Inferred || len (n .Children ) == 0 || !sideSetEmptyBase (n .Children [0 ]) {
return nil , n
}
scripts = n .Children [0 ]
n .Children = n .Children [1 :]
refreshDynamicFlags (n )
return scripts , n
}
func sideSetEmptyBase(n *mml .Node ) bool {
return n != nil && len (n .Children ) != 0 && n .Children [0 ] != nil &&
n .Children [0 ].Kind == "mi" && textContent (n .Children [0 ]) == ""
}
func sideSetScript(n *mml .Node , index int ) *mml .Node {
if origin , _ := n .Property (limitsScriptOrigin ); origin == true && n .Kind == "msup" {
if index == 1 {
return node ("none" )
}
index = 1
}
if index < len (n .Children ) && n .Children [index ] != nil {
return n .Children [index ]
}
return node ("none" )
}
type amsArrowSize struct {
character string
left float64
right float64
}
var amsArrowSizes = func () map [string ]amsArrowSize {
sizes := make (map [string ]amsArrowSize )
for _ , sourceMap := range mjSourceMaps {
if sourceMap .Kind != mjSourceCommandMap {
continue
}
for _ , entry := range sourceMap .Entries {
handler , args , ok := sourceHandler (entry .Value )
if !ok || handler != "xArrow" || len (args ) != 3 {
continue
}
character , charOK := sourceInt (args [0 ])
left , leftOK := sourceInt (args [1 ])
right , rightOK := sourceInt (args [2 ])
if charOK && leftOK && rightOK {
sizes [entry .Name ] = amsArrowSize {string (rune (character )), float64 (left ), float64 (right )}
}
}
}
return sizes
}()
func (p *parser ) amsXArrow (name string ) ([]*mml .Node , error ) {
belowRaw , _ , err := p .readBrackets (nil )
if err != nil {
if failure , ok := err .(*Error ); ok && failure .ID == "MissingCloseBracket" {
return nil , texError (failure .ID , "Could not find closing ']' for argument to \\%s" , name )
}
return nil , err
}
above , err := p .parseArgument (name )
if err != nil {
return nil , err
}
size := amsArrowSizes [name ]
arrow := p .operator (size .character , mml .TeXClassRel , nil )
arrow .Attributes .Set ("stretchy" , true )
arrowStyle := node ("mstyle" , arrow )
arrowStyle .Attributes .Set ("scriptlevel" , 0 )
overChildren := append (unwrapInferred (above ), amsArrowStrut ("depth" , ".25em" ))
over := node ("mpadded" , overChildren ...)
amsSetArrowPadding (over , size )
over .Attributes .Set ("voffset" , "-.2em" )
over .Attributes .Set ("height" , "-.2em" )
if belowRaw == "" {
result := node ("mover" , arrowStyle , over )
result .SetProperty ("subsupOK" , true )
return []*mml .Node {result }, nil
}
below , err := p .parseArgumentString (belowRaw )
if err != nil {
return nil , err
}
underChildren := append (unwrapInferred (below ), amsArrowStrut ("height" , ".75em" ))
under := node ("mpadded" , underChildren ...)
amsSetArrowPadding (under , size )
under .Attributes .Set ("voffset" , ".15em" )
under .Attributes .Set ("depth" , "-.15em" )
result := node ("munderover" , arrowStyle , under , over )
result .SetProperty ("subsupOK" , true )
return []*mml .Node {result }, nil
}
func amsArrowStrut(attribute , value string ) *mml .Node {
strut := node ("mspace" )
strut .Attributes .Set (attribute , value )
return strut
}
func amsSetArrowPadding(padded *mml .Node , size amsArrowSize ) {
padded .Attributes .Set ("width" , "+" +emLength ((size .left +size .right )/18 ))
padded .Attributes .Set ("lspace" , emLength (size .left /18 ))
}
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 .