package tex
import (
"fmt"
"regexp"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/d2lang/mathjax-go/internal/mml"
texcolor "github.com/d2lang/mathjax-go/internal/tex/extensions/color"
)
type macroDefinition struct {
body string
arguments int
optionalDefault *string
prefix string
delimiters []string
builtinNot bool
}
type pairedDelimiter struct {
open, close string
body string
arguments int
substituteZeroArgs bool
}
type environmentDefinition struct {
begin, end string
arguments int
optionalDefault *string
}
type parseState struct {
operators []*mml .Node
nonscriptSpaces []*mml .Node
poppedScripts []*mml .Node
macros map [string ]macroDefinition
pairedDelimiters map [string ]pairedDelimiter
environments map [string ]environmentDefinition
amsTags *amsTagState
augmentedPackages bool
macroCount int
colorModel *texcolor .Model
}
type parserStackGlobal struct {
eqnEnv bool
leftRoot, upRoot string
}
func newParseState() *parseState {
s := &parseState {
macros : make (map [string ]macroDefinition ),
pairedDelimiters : make (map [string ]pairedDelimiter ),
environments : make (map [string ]environmentDefinition ),
colorModel : texcolor .NewModel (),
}
for name , body := range simpleMacros {
s .macros [name ] = macroDefinition {body : body , arguments : macroArguments (body )}
}
moveEqLeftDefault := "2"
s .macros ["MoveEqLeft" ] = macroDefinition {
body : "\\hspace{#1em}&\\hspace{-#1em}" , arguments : 1 ,
optionalDefault : &moveEqLeftDefault ,
}
return s
}
type identifierPattern uint8
const (
identifierPatternNone identifierPattern = iota
identifierPatternLetters
identifierPatternOperator
)
func (pattern identifierPattern ) matches (r rune ) bool {
return isASCIILetter (r ) || pattern == identifierPatternOperator && (r == '-' || r == '*' )
}
type parser struct {
source string
pos int
state *parseState
stackGlobal *parserStackGlobal
display bool
commandNamedFunction bool
commandNot bool
commandNonscript bool
commandDots *pendingDots
commandPosition *positionItem
commandCell *cellItem
commandEnvironmentEnd *environmentEndItem
commandCasesLeft *environmentEndItem
commandCasesBegin func () error
pendingEnvironmentEnd *environmentEndItem
environmentOwner *environmentFrame
environmentRow *environmentFrame
environmentPopped bool
ordinaryArray *ordinaryArrayItem
pendingCell *cellItem
stoppedCell *cellItem
multiLetterFont string
activeFont string
activeColor string
rowDelimiter *rowDelimiterItem
cdCellEnd *cdCellEnd
cdArrayEntry bool
cdEntryStopped bool
arrayCell *arrayCellState
matrixClose bool
liveMatrix bool
identifierPattern identifierPattern
operatorLetters bool
noAutoOP bool
fontExplicitEmpty bool
vectorFactory bool
vectorFont string
vectorStar bool
vectorAlias bool
genfracPalette bool
starMacroChildren bool
derivativeChildren bool
braketOwner *braketItem
inRoot bool
}
func (p *parser ) ensureStackGlobal () *parserStackGlobal {
if p .stackGlobal == nil {
p .stackGlobal = &parserStackGlobal {}
}
return p .stackGlobal
}
func (p *parser ) checkEquationEnvironment () error {
global := p .ensureStackGlobal ()
if global .eqnEnv {
return texError ("ErroneousNestingEq" , "Erroneous nesting of equation structures" )
}
global .eqnEnv = true
return nil
}
func (p *parser ) parseRow (terminator byte , stopRight bool ) ([]*mml .Node , string , error ) {
return p .parseRowWithPrefix (terminator , stopRight , nil )
}
func (p *parser ) parseRowWithPrefix (terminator byte , stopRight bool , prefix []*mml .Node ) ([]*mml .Node , string , error ) {
owner := p .braketOwner
p .braketOwner = nil
matrixClose , cdArrayEntry , arrayCell := p .matrixClose , p .cdArrayEntry , p .arrayCell
environmentRow , ordinaryArray := p .environmentRow , p .ordinaryArray
p .environmentRow , p .ordinaryArray = nil , nil
p .matrixClose , p .cdArrayEntry , p .arrayCell = false , false , nil
defer func () {
p .braketOwner , p .matrixClose , p .cdArrayEntry , p .arrayCell = owner , matrixClose , cdArrayEntry , arrayCell
p .environmentRow = environmentRow
p .ordinaryArray = ordinaryArray
}()
return p .parseRowContinuation (terminator , stopRight , false , nil , "" , prefix )
}
func (p *parser ) parseRowWithInfix (terminator byte , stopRight , infixPending bool ) ([]*mml .Node , string , error ) {
return p .parseRowWithAutoOpen (terminator , stopRight , infixPending , nil )
}
func (p *parser ) parseRowWithAutoOpen (terminator byte , stopRight , infixPending bool , auto *derivativeAutoOpen ) ([]*mml .Node , string , error ) {
if auto != nil {
owner , environmentRow , ordinaryArray := p .braketOwner , p .environmentRow , p .ordinaryArray
p .braketOwner , p .environmentRow , p .ordinaryArray = nil , nil , nil
defer func () { p .braketOwner , p .environmentRow , p .ordinaryArray = owner , environmentRow , ordinaryArray }()
}
return p .parseRowContinuation (terminator , stopRight , infixPending , auto , "" , nil )
}
type rowStyleFrame struct {
prefix []*mml .Node
attributes mjSourceObject
}
func (p *parser ) parseRowContinuation (terminator byte , stopRight , infixPending bool , auto *derivativeAutoOpen , pendingFont string , prefix []*mml .Node ) ([]*mml .Node , string , error ) {
owner := p .braketOwner
defer func () { p .braketOwner = owner }()
vectorFont , vectorStar , activeFont := p .vectorFont , p .vectorStar , p .activeFont
fontExplicitEmpty := p .fontExplicitEmpty
activeColor := p .activeColor
defer func () {
p .vectorFont , p .vectorStar , p .activeFont = vectorFont , vectorStar , activeFont
p .fontExplicitEmpty = fontExplicitEmpty
p .activeColor = activeColor
}()
nodes := prefix
var pending *pendingPrime
var negation pendingNot
var dots pendingDots
nonscript := false
var styles []rowStyleFrame
var positions []rowPositionFrame
reducePositions := func () {
for len (positions ) != 0 && len (nodes ) != 0 {
frame := positions [len (positions )-1 ]
if frame .styleDepth != len (styles ) {
break
}
positions = positions [:len (positions )-1 ]
nodes = append (frame .item .apply (nodes [0 ]), nodes [1 :]...)
nodes = append (frame .prefix , nodes ...)
}
}
pendingFunction := false
functionOffset := 0
var deliveryErr error
var deliverFinal func ([]finalItem )
var finishFunction func (bool )
deliverFinal = func (items []finalItem ) {
for len (items ) != 0 {
if deliveryErr != nil {
return
}
if auto != nil && auto .closed {
auto .trailing = append (auto .trailing , items ...)
return
}
incoming := items [0 ]
items = items [1 :]
if nonscript {
nonscript = false
incoming .node = p .nonscriptSpace (incoming .node )
}
if pending != nil {
completed := pending .finish ()
pending = nil
items = append ([]finalItem {{node : completed }, incoming }, items ...)
continue
}
if bool (negation ) {
negation = false
if !applyNotToken (incoming .node ) {
items = append ([]finalItem {{node : notFallback ()}, incoming }, items ...)
continue
}
}
if dots .active () {
selected := dots .apply ([]*mml .Node {incoming .node })[0 ]
items = append ([]finalItem {{node : selected }, incoming }, items ...)
continue
}
if pendingFunction {
if len (nodes ) == functionOffset {
nodes = append (nodes , incoming .node )
continue
}
function := nodes [functionOffset :]
nodes = nodes [:functionOffset ]
pendingFunction = false
outputs := finalItems (function )
if !suppressesFunctionApplication (incoming .node ) {
outputs = append (outputs , finalItem {node : p .operator ("\u2061" , mml .TeXClassNone , nil )})
}
outputs = append (outputs , incoming )
items = append (outputs , items ...)
continue
}
if len (positions ) != 0 && positions [len (positions )-1 ].styleDepth == len (styles ) {
frame := positions [len (positions )-1 ]
positions = positions [:len (positions )-1 ]
nodes = append (frame .prefix , nodes ...)
items = append (frame .item .applyFinal (incoming ), items ...)
continue
}
if auto != nil && len (styles ) == 0 && auto .accept (incoming ) {
deliveryErr = auto .parseRight (p )
auto .trailing = append (auto .trailing , items ...)
return
}
nodes = append (nodes , incoming .node )
}
}
finishFunction = func (apply bool ) {
if !pendingFunction {
return
}
function := nodes [functionOffset :]
nodes = nodes [:functionOffset ]
pendingFunction = false
outputs := finalItems (function )
if apply && len (function ) != 0 {
outputs = append (outputs , finalItem {node : p .operator ("\u2061" , mml .TeXClassNone , nil )})
}
deliverFinal (outputs )
}
finishNot := func () { deliverFinal (finalItems (negation .finish ())) }
finishDots := func () { deliverFinal (finalItems (dots .finish ())) }
finishPrime := func () {
if pending != nil {
completed := pending .finish ()
pending = nil
deliverFinal ([]finalItem {{node : completed }})
}
}
finishPending := func () {
nonscript = false
finishPrime ()
finishNot ()
finishDots ()
finishFunction (false )
reducePositions ()
}
pushStyle := func (attributes mjSourceObject ) {
finishPending ()
styles = append (styles , rowStyleFrame {nodes , attributes })
p .braketOwner = nil
nodes = nil
}
closeStyles := func () error {
finishPending ()
for i := len (styles ) - 1 ; i >= 0 ; i -- {
if len (positions ) != 0 && positions [len (positions )-1 ].styleDepth == len (styles ) {
return positions [len (positions )-1 ].item .missingBox ()
}
frame := styles [i ]
styled := node ("mstyle" , row (nodes , true ))
for _ , attribute := range frame .attributes {
styled .Attributes .Set (attribute .Name , attribute .Value )
}
nodes = append (frame .prefix , styled )
styles = styles [:i ]
reducePositions ()
}
if len (positions ) != 0 {
return positions [len (positions )-1 ].item .missingBox ()
}
return nil
}
prepareFinal := func (items []finalItem ) {
for _ , item := range items {
if pendingFont != "" {
applyScopedMathVariant (item .node , pendingFont )
}
p .applyVectorFactory (item .node )
}
}
appendNodes := func (created []*mml .Node , namedFunction bool ) {
if len (created ) == 0 {
return
}
items := finalItems (created )
prepareFinal (items )
if namedFunction {
nonscript = false
finishPrime ()
finishNot ()
finishDots ()
finishFunction (true )
functionOffset = len (nodes )
nodes = append (nodes , created ...)
pendingFunction = true
return
}
deliverFinal (items )
}
for p .pos < len (p .source ) || p .pendingCell != nil || p .pendingEnvironmentEnd != nil || deliveryErr != nil || auto != nil && auto .closed {
if deliveryErr != nil {
return nil , "" , deliveryErr
}
if auto != nil && auto .closed {
return nodes , "" , nil
}
if p .cdEntryStopped {
break
}
if item := p .pendingEnvironmentEnd ; item != nil {
if item .spread {
p .pendingEnvironmentEnd = nil
switch {
case pending != nil :
parts := []*mml .Node {pending .base , pending .prime }
if err := mathtoolsSpreadPending (parts ); err != nil {
return nil , "" , err
}
pending = nil
appendNodes (parts , false )
case bool (negation ):
negation = false
case dots .active ():
dots = pendingDots {}
case nonscript :
nonscript = false
case pendingFunction :
if err := mathtoolsSpreadPending (nodes [len (nodes )-1 :]); err != nil {
return nil , "" , err
}
pendingFunction = false
reducePositions ()
case len (positions ) != 0 && positions [len (positions )-1 ].styleDepth == len (styles ):
if err := mathtoolsSpreadPending (nodes ); err != nil {
return nil , "" , err
}
frame := positions [len (positions )-1 ]
positions = positions [:len (positions )-1 ]
nodes = append (frame .prefix , nodes ...)
reducePositions ()
case len (styles ) != 0 :
if err := mathtoolsSpreadPending (nodes ); err != nil {
return nil , "" , err
}
frame := styles [len (styles )-1 ]
styles = styles [:len (styles )-1 ]
nodes = append (frame .prefix , nodes ...)
reducePositions ()
case infixPending || terminator != 0 && !(p .matrixClose && p .ordinaryArray != nil && p .ordinaryArray .begin == nil ) || stopRight || auto != nil || owner != nil :
if infixPending || auto == nil && owner == nil {
if err := mathtoolsSpreadPending (nodes ); err != nil {
return nil , "" , err
}
}
p .environmentPopped = true
return nodes , "" , nil
case p .ordinaryArray != nil :
if err := mathtoolsSpreadPending (nodes ); err != nil {
return nil , "" , err
}
p .ordinaryArray .popped = true
return nodes , "" , nil
case p .environmentRow != nil :
if cases := p .environmentRow .casesBegin ; cases != nil {
content := row (nodes , true )
if err := mathtoolsSpreadPop (content , nil ); err != nil {
return nil , "" , err
}
cases .entries --
if cases .entries == 0 {
p .environmentRow .closed = true
return []*mml .Node {content }, "" , nil
}
appendNodes ([]*mml .Node {content }, false )
continue
}
if p .environmentRow .ordinaryArray {
if err := mathtoolsSpreadPending (nodes ); err != nil {
return nil , "" , err
}
} else if p .environmentRow .name != item .name {
return nil , "" , p .environmentRow .missing ()
}
p .environmentRow .closed = true
return nodes , "" , nil
default :
return nil , "" , item .extra ()
}
continue
}
if closeErr := closeStyles (); closeErr != nil {
return nil , "" , closeErr
}
if infixPending || owner != nil && owner .single && len (nodes ) != 0 {
return nodes , "" , nil
}
if p .matrixClose && terminator != 0 {
return nil , "" , texError ("MissingCloseBrace" , "Missing close brace" )
}
if terminator != 0 || stopRight || auto != nil || owner != nil {
return nil , "" , item .extra ()
}
if p .ordinaryArray != nil {
p .ordinaryArray .end = item
p .pendingEnvironmentEnd = nil
return nodes , "" , nil
}
if err := p .closeEnvironment (p .environmentRow , item ); err != nil {
return nil , "" , err
}
return nodes , "" , nil
}
if item := p .pendingCell ; item != nil {
item .saveEnvironment (p )
if closeErr := closeStyles (); closeErr != nil {
return nil , "" , closeErr
}
if infixPending || owner != nil && owner .single && len (nodes ) != 0 {
return nodes , "" , nil
}
p .pendingCell = nil
if p .cdArrayEntry && !stopRight && auto == nil && owner == nil {
p .stoppedCell , p .cdEntryStopped = item , true
return nodes , "" , nil
}
if !item .linebreak {
return nil , "" , item .misplaced ()
}
p .activeFont , p .fontExplicitEmpty , p .activeColor = item .font , item .emptyFont , item .color
pendingFont = item .font
appendNodes (item .spaces (), false )
continue
}
if owner != nil && owner .single && len (nodes ) > 0 && len (styles ) == 0 && len (positions ) == 0 && !pendingFunction && !nonscript {
if closeErr := closeStyles (); closeErr != nil {
return nil , "" , closeErr
}
return nodes , "" , nil
}
p .braketOwner = owner
if len (styles ) != 0 || pending != nil || bool (negation ) || dots .active () || pendingFunction || nonscript || auto != nil || len (positions ) != 0 {
p .braketOwner = nil
}
c := p .source [p .pos ]
if terminator != 0 && c == terminator {
if closeErr := closeStyles (); closeErr != nil {
return nil , "" , closeErr
}
p .pos ++
return nodes , "" , nil
}
if c == '}' {
if len (positions ) != 0 {
if closeErr := closeStyles (); closeErr != nil {
return nil , "" , closeErr
}
}
return nil , "" , texError ("ExtraCloseMissingOpen" , "Extra close brace or missing open brace" )
}
if c == '%' {
p .skipComment ()
continue
}
if isMathTokenSpace (p .peekRune ()) {
p .consumeRune ()
continue
}
if c == '\\' {
p .pos ++
name := p .readControlSequence ()
_ , registeredMacro := p .state .macros [name ]
_ , registeredDelimiter := p .state .pairedDelimiters [name ]
registered := registeredMacro || registeredDelimiter
if (name == "shoveleft" || name == "shoveright" ) && !registered {
if p .arrayCell == nil || !p .arrayCell .multline || owner != nil || auto != nil || infixPending ||
len (styles ) != 0 || len (positions ) != 0 || pending != nil || bool (negation ) || dots .active () || pendingFunction || nonscript {
return nil , "" , texError ("CommandOnlyAllowedInEnv" , "\\%s only allowed in %s environment" , name , "multline" )
}
if p .arrayCell .offset +len (nodes ) != 0 {
return nil , "" , texError ("CommandAtTheBeginingOfLine" , "\\%s must come at the beginning of the line" , name )
}
p .arrayCell .shove = strings .TrimPrefix (name , "shove" )
continue
}
if isMathtoolsArrayCommand (name ) && !registered {
actualArray := p .arrayCell != nil && owner == nil && auto == nil && !infixPending &&
len (styles ) == 0 && len (positions ) == 0 && pending == nil &&
!bool (negation ) && !dots .active () && !pendingFunction && !nonscript
star , guardName := false , name
if name == "shortvdotswithin" {
star = p .readMathtoolsStar ()
guardName = "MTFlushSpaceAbove"
}
if !actualArray || (name != "vdotswithin" && p .arrayCell .equation == nil ) {
return nil , "" , notInAlignment (guardName )
}
equation := p .arrayCell .equation
switch name {
case "ArrowBetweenLines" :
if p .arrayCell .offset +len (nodes ) != 0 || len (equation .entries ) != 0 {
return nil , "" , texError ("BetweenLines" , "%s must be on a row by itself" , "\\" +name )
}
if err := p .arrowBetweenLines (name ); err != nil {
return nil , "" , err
}
case "Aboxed" :
if err := p .equationAboxed (name ); err != nil {
return nil , "" , err
}
case "MTFlushSpaceAbove" :
equation .table .flushAbove = equation .table .rows
equation .table .addSpacing ("-" + p .mathtoolsOption ("shortvdotsadjustabove" ))
case "MTFlushSpaceBelow" :
if len (nodes ) != 0 {
equation .endEntry (nodes )
nodes = nil
}
if err := equation .endRow (); err != nil {
return nil , "" , err
}
equation .table .addSpacing ("-" + p .mathtoolsOption ("shortvdotsadjustbelow" ))
case "vdotswithin" , "shortvdotswithin" :
if name == "shortvdotswithin" {
equation .table .flushAbove = equation .table .rows
equation .table .addSpacing ("-" + p .mathtoolsOption ("shortvdotsadjustabove" ))
if !star {
equation .endEntry (nodes )
nodes = nil
}
}
flush := equation != nil && equation .table .flushAbove == equation .table .rows
content , err := p .equationVDots ("vdotswithin" , flush )
if err != nil {
return nil , "" , err
}
appendNodes ([]*mml .Node {content }, false )
if name == "shortvdotswithin" {
if len (nodes ) != 0 {
equation .endEntry (nodes )
nodes = nil
}
if err := equation .endRow (); err != nil {
return nil , "" , err
}
equation .table .addSpacing ("-" + p .mathtoolsOption ("shortvdotsadjustbelow" ))
}
}
continue
}
if isHLine (name ) && !registered {
if p .arrayCell == nil || p .arrayCell .rules == nil || owner != nil || auto != nil || infixPending ||
len (styles ) != 0 || len (positions ) != 0 || pending != nil ||
bool (negation ) || dots .active () || pendingFunction || nonscript ||
p .arrayCell .offset +len (nodes ) != 0 {
return nil , "" , misplacedHLine (name )
}
p .arrayCell .rules .add (name )
continue
}
if isHFill (name ) && !registered {
if p .arrayCell == nil || owner != nil || auto != nil || infixPending ||
len (styles ) != 0 || len (positions ) != 0 || pending != nil ||
bool (negation ) || dots .active () || pendingFunction || nonscript {
return nil , "" , unsupportedHFill (name )
}
p .arrayCell .fills = append (p .arrayCell .fills , p .arrayCell .offset +len (nodes ))
continue
}
if !registered && (name == "right" || name == "middle" ) {
delim , err := p .readDelimiter (name , false )
if err != nil {
return nil , "" , err
}
p .rowDelimiter = &rowDelimiterItem {middle : name == "middle" , delimiter : delim , color : p .activeColor }
if closeErr := closeStyles (); closeErr != nil {
return nil , "" , closeErr
}
if auto != nil || !stopRight {
if auto == nil && p .matrixClose {
return nil , "" , texError ("MissingCloseBrace" , "Missing close brace" )
}
if name == "middle" {
return nil , "" , texError ("ExtraMiddle" , "Extra \\middle" )
}
return nil , "" , texError ("MissingLeftExtraRight" , "Missing \\left or extra \\right" )
}
return nodes , delim , nil
}
if !registered && (name == "over" || name == "atop" || name == "above" || name == "choose" || name == "brace" || name == "brack" || name == "overwithdelims" || name == "atopwithdelims" || name == "abovewithdelims" ) {
spec , err := p .readInfixSpec (name )
if err != nil {
return nil , "" , err
}
if closeErr := closeStyles (); closeErr != nil {
return nil , "" , closeErr
}
p .braketOwner = nil
fraction , right , err := p .finishInfixFraction (name , nodes , spec , terminator , stopRight , infixPending , pendingFont )
if err != nil {
return nil , "" , err
}
if auto != nil {
if p .pendingCell == nil && p .pendingEnvironmentEnd == nil {
return nil , "" , auto .stopError ()
}
}
if p .environmentPopped {
p .environmentPopped = false
nodes = unwrapInferred (fraction )
continue
}
if p .pendingCell != nil || p .pendingEnvironmentEnd != nil {
nodes = []*mml .Node {fraction }
continue
}
return []*mml .Node {fraction }, right , nil
}
if !registered && name == "color" {
attributes , err := p .colorDeclaration ()
if err != nil {
return nil , "" , err
}
pushStyle (attributes )
continue
}
if style , ok := styleDeclarations [name ]; ok && !registered {
pushStyle (mjSourceObject {
{Name : "displaystyle" , Value : style ["displaystyle" ]},
{Name : "scriptlevel" , Value : style ["scriptlevel" ]},
})
continue
}
if size , ok := sizeDeclarations [name ]; ok && !registered {
pushStyle (mjSourceObject {{Name : "mathsize" , Value : emLength (size )}})
continue
}
if variant , ok := fontDeclarations [name ]; ok && !registered {
if (p .arrayCell != nil && p .arrayCell .equation != nil ) || len (styles ) != 0 || pending != nil || bool (negation ) || dots .active () || pendingFunction || nonscript || len (positions ) != 0 {
p .activeFont , pendingFont = variant , variant
p .fontExplicitEmpty = variant == ""
continue
}
oldFont := p .activeFont
oldFontExplicitEmpty := p .fontExplicitEmpty
p .activeFont = variant
p .fontExplicitEmpty = variant == ""
if p .arrayCell != nil {
p .arrayCell .offset += len (nodes )
}
rest , right , err := p .parseRowWithAutoOpen (terminator , stopRight , infixPending , auto )
if p .arrayCell != nil {
p .arrayCell .offset -= len (nodes )
}
p .activeFont = oldFont
p .fontExplicitEmpty = oldFontExplicitEmpty
if err != nil {
return nil , "" , err
}
applyScopedMathVariant (row (rest , true ), variant )
nodes = append (nodes , rest ...)
return nodes , right , nil
}
if !registered && (name == "limits" || name == "nolimits" ) {
if pending != nil || bool (negation ) || dots .active () || nonscript {
return nil , "" , texError ("MisplacedLimits" , "%s is allowed only on operators" , "\\" +name )
}
var err error
nodes , err = p .parseLimits (nodes , name )
if err != nil {
return nil , "" , err
}
continue
}
result , err := p .commandEventWithCasesBegin (name , func () error {
nonscript = false
finishPrime ()
finishNot ()
finishDots ()
return deliveryErr
})
if err != nil {
return nil , "" , err
}
if result .casesLeft != nil {
var first *mml .Node
left := "undefined"
switch {
case pending != nil :
first = pending .base
case bool (negation ), dots .active (), nonscript :
case pendingFunction :
if len (nodes ) > functionOffset {
first = nodes [functionOffset ]
}
default :
if len (nodes ) != 0 {
first = nodes [0 ]
}
}
if err := p .finishCasesLeft (first , left ); err != nil {
return nil , "" , err
}
p .pendingEnvironmentEnd = result .casesLeft
continue
}
if result .environmentEnd != nil {
p .pendingEnvironmentEnd = result .environmentEnd
continue
}
if result .cellItem != nil {
p .pendingCell = result .cellItem
continue
}
if result .notItem {
nonscript = false
finishPrime ()
finishDots ()
finishFunction (false )
finishNot ()
negation = true
}
if result .dotsItem != nil {
nonscript = false
finishPrime ()
finishNot ()
finishDots ()
finishFunction (false )
dots = *result .dotsItem
}
if result .positionItem != nil {
finishPending ()
positions = append (positions , rowPositionFrame {item : result .positionItem , prefix : nodes , styleDepth : len (styles )})
nodes = nil
}
if result .nonscriptItem {
finishPending ()
nonscript = true
}
appendNodes (result .nodes , result .namedFunction )
tail , err := result .afterNode .completeAfter (p , func () {
final := pending != nil || bool (negation ) || dots .active ()
nonscript = false
finishPrime ()
finishNot ()
finishDots ()
if final {
reducePositions ()
}
})
if err != nil {
return nil , "" , err
}
appendNodes (tail , false )
deliverFinal (result .afterNode .takeTrailing ())
continue
}
switch c {
case '{' :
nonscript = false
finishPrime ()
p .pos ++
contents , _ , err := p .parseRow ('}' , false )
if err != nil {
return nil , "" , err
}
if p .environmentPopped {
p .environmentPopped = false
appendNodes (contents , false )
continue
}
appendNodes ([]*mml .Node {texAtom (row (contents , true ), mml .TeXClassOrd )}, false )
case '^' , '_' :
p .pos ++
var err error
var after *derivativeAutoOpen
var trailing []*mml .Node
if bool (negation ) || dots .active () || nonscript {
finishNot ()
finishDots ()
nonscript = false
var scriptNodes []*mml .Node
scriptNodes , pendingFont , trailing , after , err = p .attachScriptWithFont (nil , c , pendingFont )
nodes = append (nodes , scriptNodes ...)
} else if pending == nil {
nodes , pendingFont , trailing , after , err = p .attachScriptWithFont (nodes , c , pendingFont )
} else {
var script *mml .Node
p .scriptInitialLookahead ()
moves , _ := pending .base .Property ("movesupsub" )
var attachment *scriptAttachment
attachment , err = prepareScriptAttachment (pending .base , c , limitsTruthy (moves ))
if err == nil {
script , pendingFont , trailing , after , err = p .parseScriptArgument (attachment , pendingFont )
}
if err == nil {
var result *mml .Node
if p .environmentPopped {
p .environmentPopped = false
result = script
} else {
result , err = pending .attach (script , c )
}
if err == nil {
nodes = append (nodes , result )
pending = nil
}
}
}
if err != nil {
return nil , "" , err
}
if !pendingFunction {
reducePositions ()
}
appendNodes (trailing , false )
tail , err := after .completeAfter (p , nil )
if err != nil {
return nil , "" , err
}
appendNodes (tail , false )
deliverFinal (after .takeTrailing ())
case '\'' , 0xE2 :
if isPrimeRune (p .peekRune ()) {
p .consumeRune ()
var err error
finishPrime ()
var base *mml .Node
if bool (negation ) || dots .active () || nonscript {
finishNot ()
finishDots ()
nonscript = false
base = node ("mi" )
} else if len (nodes ) == 0 {
base = node ("mi" )
} else {
base = nodes [len (nodes )-1 ]
nodes = nodes [:len (nodes )-1 ]
}
pending , err = p .startPrime (base )
if err != nil {
return nil , "" , err
}
} else {
created , err := p .parseCharacterChecked ()
if err != nil {
return nil , "" , err
}
appendNodes ([]*mml .Node {created }, false )
}
case '&' :
p .pos ++
numCasesText := p .arrayCell != nil && p .arrayCell .numCases &&
owner == nil && auto == nil && !infixPending &&
len (styles ) == 0 && len (positions ) == 0 && pending == nil &&
!bool (negation ) && !dots .active () && !pendingFunction && !nonscript
p .pendingCell = &cellItem {name : "&" , numCasesText : numCasesText }
case '#' :
return nil , "" , texError ("CantUseHash1" , "You can't use 'macro parameter character #' in math mode" )
default :
created , err := p .parseCharacterChecked ()
if err != nil {
return nil , "" , err
}
item := finalItem {node : created }
if c == ')' || c == ']' || c == '|' {
item .autoclose = c
}
prepareFinal ([]finalItem {item })
deliverFinal ([]finalItem {item })
}
}
if end := p .cdCellEnd ; end != nil && !p .cdEntryStopped && terminator == 0 && !stopRight {
p .cdCellEnd = nil
pendingItem := len (styles ) != 0 || len (positions ) != 0 || infixPending ||
pending != nil || bool (negation ) || dots .active () || pendingFunction || nonscript ||
(owner != nil && owner .single && len (nodes ) == 0 )
if end .firstObjectCell || pendingItem {
appendNodes ([]*mml .Node {cdStrut ()}, false )
}
}
if closeErr := closeStyles (); closeErr != nil {
return nil , "" , closeErr
}
if p .environmentRow != nil && p .environmentRow .stream && !p .environmentRow .closed {
return nil , "" , p .environmentRow .missing ()
}
if auto != nil {
return nil , "" , auto .stopError ()
}
if p .matrixClose && p .ordinaryArray != nil && p .ordinaryArray .begin == nil && terminator != 0 {
return nil , "" , texError ("MissingCloseBrace" , "Missing close brace" )
}
if terminator != 0 {
return nil , "" , texError ("ExtraOpenMissingClose" , "Extra open brace or missing close brace" )
}
if stopRight {
return nil , "" , texError ("ExtraLeftMissingRight" , "Extra \\left or missing \\right" )
}
return nodes , "" , nil
}
var ordinaryNumberPattern = regexp .MustCompile (`^(?:[0-9]+(?:\{,\}[0-9]{3})*(?:\.[0-9]*)?|\.[0-9]+)` )
func (p *parser ) parseCharacterChecked () (*mml .Node , error ) {
r := p .peekRune ()
if p .operatorLetters && (r == '-' || r == '*' ) &&
p .identifierPattern == identifierPatternLetters && !p .fontExplicitEmpty {
p .consumeRune ()
return nil , fmt .Errorf ("identifier pattern does not match %q" , string (r ))
}
n := p .parseCharacter ()
if n == nil {
return nil , fmt .Errorf ("no Unicode range for character U+%04X" , r )
}
return n , nil
}
func (p *parser ) parseCharacter () *mml .Node {
r := p .consumeRune ()
if r == '~' {
return token ("mtext" , "\u00a0" )
}
if isASCIILetter (r ) || p .operatorLetters && (r == '-' || r == '*' ) {
text := string (r )
grouping := p .identifierPattern != identifierPatternNone && !p .fontExplicitEmpty && p .identifierPattern .matches (r )
if grouping {
start := p .pos - utf8 .RuneLen (r )
for p .pos < len (p .source ) && p .identifierPattern .matches (p .peekRune ()) {
p .consumeRune ()
}
text = p .source [start :p .pos ]
}
identifier := token ("mi" , text )
if grouping && p .noAutoOP && p .activeFont == "normal" && len (text ) > 1 {
identifier .SetProperty ("autoOP" , false )
}
return ambientLiteralToken (identifier , r )
}
if r >= '0' && r <= '9' || r == '.' || r == ',' {
start := p .pos - 1
if number := ordinaryNumberPattern .FindString (p .source [start :]); number != "" {
p .pos = start + len (number )
text := strings .NewReplacer ("{" , "" , "}" , "" ).Replace (number )
return ambientFontToken (token ("mn" , text ))
}
return ambientFontToken (p .token ("mo" , string (r )))
}
if unicode .IsDigit (r ) {
for _ , interval := range mjOperatorRanges {
if int (r ) < interval .First {
break
}
if int (r ) <= interval .Last {
n := p .token (interval .Kind , string (r ))
if p .activeFont != "" {
n .Attributes .Set ("mathvariant" , p .activeFont )
}
p .applyVectorFactory (n )
return ambientLiteralToken (n , r )
}
}
start := p .pos - utf8 .RuneLen (r )
for p .pos < len (p .source ) {
next := p .peekRune ()
if !unicode .IsDigit (next ) && next != '.' && next != ',' {
break
}
p .consumeRune ()
}
return ambientLiteralToken (token ("mn" , p .source [start :p .pos ]), r )
}
if r == ':' {
return p .mathtoolsCenterColon (true , false , false )
}
if r == ')' || r == ']' || r == '|' {
return setAttributes (p .token ("mo" , string (r )), map [string ]any {"stretchy" : false })
}
text := string (r )
if r == '-' {
text = "−"
} else if r == '*' {
text = "∗"
} else if r == '`' {
text = "‘"
}
kind := ""
for _ , interval := range mjOperatorRanges {
if int (r ) < interval .First {
break
}
if int (r ) <= interval .Last {
kind = interval .Kind
break
}
}
if kind == "" {
return nil
}
other := p .token (kind , text )
if p .activeFont != "" {
other .Attributes .Set ("mathvariant" , p .activeFont )
}
p .applyVectorFactory (other )
other = ambientLiteralToken (other , r )
if kind == "mo" {
other .SetProperty ("fixStretchy" , true )
other .SetProperty ("in-lists" , "fixStretchy" )
}
return other
}
func (p *parser ) attachScript (nodes []*mml .Node , marker byte ) ([]*mml .Node , error ) {
result , _ , trailing , after , err := p .attachScriptWithFont (nodes , marker , "" )
if err != nil {
return nil , err
}
result = append (result , trailing ...)
tail , err := after .complete (p )
return append (result , tail ...), err
}
func (p *parser ) attachScriptWithFont (nodes []*mml .Node , marker byte , font string ) ([]*mml .Node , string , []*mml .Node , *derivativeAutoOpen , error ) {
var base *mml .Node
if len (nodes ) == 0 {
base = token ("mi" , "" )
} else {
base = nodes [len (nodes )-1 ]
nodes = nodes [:len (nodes )-1 ]
}
moves , hasMoves := base .Property ("movesupsub" )
moveLimits , _ := moves .(bool )
if value , ok := base .Attributes .Get ("movesupsub" ); ok {
moveLimits , _ = value .(bool )
}
p .scriptInitialLookahead ()
attachment , err := prepareScriptAttachment (base , marker , moveLimits )
if err != nil {
return nil , font , nil , nil , err
}
script , font , trailing , after , err := p .parseScriptArgument (attachment , font )
if err != nil {
return nil , font , nil , nil , err
}
if p .environmentPopped {
p .environmentPopped = false
return append (nodes , script ), font , trailing , after , nil
}
result := attachment .fill (script )
result .Flags .Embellished = base .Flags .Embellished
result .Flags .CoreIndex = 0
result .SetProperty (limitsScriptOrigin , true )
if hasMoves {
result .SetProperty ("movesupsub" , moves )
}
return append (nodes , result ), font , trailing , after , nil
}
func (p *parser ) parseOneToken () ([]*mml .Node , error ) {
result , err := p .parseOneTokenEvent ()
if err != nil {
return nil , err
}
if result .environmentEnd != nil {
return nil , result .environmentEnd .extra ()
}
if result .notItem {
result .nodes = append (result .nodes , notFallback ())
}
if result .dotsItem != nil {
result .nodes = append (result .nodes , result .dotsItem .finish ()...)
}
tail , err := result .afterNode .complete (p )
return append (result .nodes , tail ...), err
}
func (p *parser ) parseOneTokenEvent () (commandResult , error ) {
return p .parseOneTokenEventWithCasesBegin (nil )
}
func (p *parser ) parseOneTokenEventWithCasesBegin (onBegin func () error ) (result commandResult , err error ) {
defer func () {
if err == nil {
for _ , n := range result .nodes {
p .applyVectorFactory (n )
}
}
}()
p .skipMathTokenSpaces ()
if p .pos >= len (p .source ) {
return result , texError ("MissingArgFor" , "Missing argument" )
}
if p .source [p .pos ] == '\\' {
p .pos ++
result , err = p .commandEventWithCasesBegin (p .readControlSequence (), onBegin )
return result , err
}
if p .source [p .pos ] == '{' {
p .pos ++
result .nodes , _, err = p .parseRow ('}' , false )
return result , err
}
created , err := p .parseCharacterChecked ()
if err != nil {
return result , err
}
result .nodes = []*mml .Node {created }
return result , nil
}
func (p *parser ) parseArgument (name string ) (*mml .Node , error ) {
raw , _ , err := p .readArgument (name , false )
if err != nil {
return nil , err
}
return p .parseArgumentString (raw )
}
func (p *parser ) parseArgumentString (raw string ) (*mml .Node , error ) {
count := p .state .macroCount
p .state .macroCount = 0
defer func () { p .state .macroCount = count }()
return p .parseString (raw )
}
func (p *parser ) parseString (source string ) (*mml .Node , error ) {
return p .parseStringWithStack (source , nil )
}
func (p *parser ) parseContinuationString (source string ) (*mml .Node , error ) {
return p .parseStringWithEnvironment (source , p .ensureStackGlobal (), nil , nil , p .environmentOwner )
}
func (p *parser ) parseStringWithStack (source string , global *parserStackGlobal ) (*mml .Node , error ) {
return p .parseStringWithStackCDCell (source , global , nil )
}
func (p *parser ) parseStringWithStackCDCell (source string , global *parserStackGlobal , end *cdCellEnd ) (*mml .Node , error ) {
return p .parseStringWithStackArray (source , global , end , nil )
}
func (p *parser ) parseStringWithStackArray (source string , global *parserStackGlobal , end *cdCellEnd , arrayCell *arrayCellState ) (*mml .Node , error ) {
return p .parseStringWithEnvironment (source , global , end , arrayCell , nil )
}
func (p *parser ) parseStringWithEnvironment (source string , global *parserStackGlobal , end *cdCellEnd , arrayCell *arrayCellState , environment *environmentFrame ) (*mml .Node , error ) {
sub := &parser {environmentOwner : environment , source : source , state : p .state , stackGlobal : global , display : p .display , inRoot : p .inRoot , cdCellEnd : end , arrayCell : arrayCell ,
activeFont : p .activeFont , activeColor : p .activeColor , vectorFactory : p .vectorFactory ,
operatorLetters : p .operatorLetters , noAutoOP : p .noAutoOP , fontExplicitEmpty : p .fontExplicitEmpty ,
vectorFont : p .vectorFont , vectorStar : p .vectorStar , vectorAlias : p .vectorAlias ,
genfracPalette : p .genfracPalette , starMacroChildren : p .starMacroChildren ,
derivativeChildren : p .derivativeChildren }
if p .vectorFactory || p .vectorAlias || p .derivativeChildren {
sub .multiLetterFont = p .multiLetterFont
sub .identifierPattern = p .identifierPattern
}
var children []*mml .Node
var err error
if arrayCell == nil {
children , _, err = sub .parseRow (0 , false )
} else {
children , _, err = sub .parseRowWithInfix (0 , false , false )
}
if err != nil {
return nil , err
}
return arrayCell .finish (row (children , true ), len (children )), nil
}
func (p *parser ) parseMathFontString (source , variant string , ambientOnly bool ) (*mml .Node , error ) {
count := p .state .macroCount
p .state .macroCount = 0
defer func () { p .state .macroCount = count }()
sub := &parser {
source : source ,
state : p .state ,
display : p .display ,
inRoot : p .inRoot ,
multiLetterFont : variant ,
activeFont : variant ,
activeColor : p .activeColor ,
identifierPattern : identifierPatternLetters ,
operatorLetters : p .operatorLetters ,
noAutoOP : true ,
fontExplicitEmpty : variant == "" ,
vectorFactory : p .vectorFactory ,
vectorFont : p .vectorFont ,
vectorStar : p .vectorStar ,
vectorAlias : p .vectorAlias ,
genfracPalette : p .genfracPalette ,
derivativeChildren : p .derivativeChildren ,
}
children , _ , err := sub .parseRow (0 , false )
if err != nil {
return nil , err
}
result := row (children , true )
applyFontScope (result , variant , ambientOnly )
return result , nil
}
func suppressesFunctionApplication(next *mml .Node ) bool {
if next == nil {
return false
}
if next .Kind == "mspace" {
return true
}
if next .Kind == "mstyle" && len (next .Children ) != 0 {
contents := next .Children [0 ]
if contents != nil && len (contents .Children ) != 0 && contents .Children [0 ] != nil && contents .Children [0 ].Kind == "mspace" {
return true
}
}
core := next
for core != nil && core .Kind != "mo" && core .Flags .Embellished {
index := core .Flags .CoreIndex
if index < 0 || index >= len (core .Children ) {
break
}
child := core .Children [index ]
if child == core {
break
}
core = child
}
if core == nil || core .Kind != "mo" {
return false
}
class := core .TeXClass
return class == mml .TeXClassBin || class == mml .TeXClassRel || class == mml .TeXClassClose || class == mml .TeXClassPunct
}
func (p *parser ) readArgument (name string , noneOK bool ) (string , bool , error ) {
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
return p .readArgumentAtCursor (name , noneOK )
}
func (p *parser ) readArgumentAtCursor (name string , noneOK bool ) (string , bool , error ) {
if p .pos >= len (p .source ) {
if noneOK {
return "" , false , nil
}
return "" , false , texError ("MissingArgFor" , "Missing argument for %s" , "\\" +name )
}
if p .source [p .pos ] == '}' {
if noneOK {
return "" , false , nil
}
return "" , false , texError ("ExtraCloseMissingOpen" , "Extra close brace or missing open brace" )
}
if p .source [p .pos ] == '{' {
start := p .pos + 1
end , err := p .scanBalanced (p .pos , '{' , '}' )
if err != nil {
return "" , false , err
}
p .pos = end + 1
return p .source [start :end ], true , nil
}
if p .source [p .pos ] == '\\' {
p .pos ++
return "\\" + p .readControlSequence (), false , nil
}
start := p .pos
p .consumeRune ()
return p .source [start :p .pos ], false , nil
}
func (p *parser ) readBrackets (defaultValue *string ) (string , bool , error ) {
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
if p .pos >= len (p .source ) || p .source [p .pos ] != '[' {
if defaultValue == nil {
return "" , false , nil
}
return *defaultValue , false , nil
}
start := p .pos + 1
depth := 0
for i := start ; i < len (p .source ); {
switch p .source [i ] {
case '\\' :
i ++
if i < len (p .source ) {
_ , n := utf8 .DecodeRuneInString (p .source [i :])
i += n
}
case '{' :
depth ++
i ++
case '}' :
if depth == 0 {
return "" , false , texError ("ExtraCloseLooking" , "Extra close brace while looking for ']'" )
}
depth --
i ++
case ']' :
if depth == 0 {
p .pos = i + 1
return p .source [start :i ], true , nil
}
i ++
default :
_ , n := utf8 .DecodeRuneInString (p .source [i :])
i += n
}
}
return "" , false , texError ("MissingCloseBracket" , "Could not find closing ']' for argument" )
}
func (p *parser ) scanBalanced (at int , open , close byte ) (int , error ) {
depth := 0
for i := at ; i < len (p .source ); i ++ {
switch p .source [i ] {
case '\\' :
i ++
case open :
depth ++
case close :
depth --
if depth == 0 {
return i , nil
}
}
}
return 0 , texError ("MissingCloseBrace" , "Missing close brace" )
}
func (p *parser ) readControlSequence () string {
if p .pos >= len (p .source ) {
return " "
}
r , size := utf8 .DecodeRuneInString (p .source [p .pos :])
if !isASCIILetter (r ) {
p .pos += size
switch r {
case '\n' , '\r' , '\u2028' , '\u2029' :
return " "
}
return string (r )
}
start := p .pos
for p .pos < len (p .source ) {
r , size = utf8 .DecodeRuneInString (p .source [p .pos :])
if !isASCIILetter (r ) {
break
}
p .pos += size
}
name := p .source [start :p .pos ]
if p .pos < len (p .source ) && p .source [p .pos ] == ' ' {
p .pos ++
}
return name
}
func (p *parser ) readDelimiter (command string , braceOK bool ) (string , error ) {
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
if p .pos < len (p .source ) {
var key string
if p .source [p .pos ] == '\\' {
p .pos ++
key = "\\" + p .readControlSequence ()
} else if p .source [p .pos ] == '{' && braceOK {
raw , _ , err := p .readArgument (command , false )
if err != nil {
return "" , err
}
key = strings .TrimFunc (raw , internalTextSpace )
} else {
key = string (p .consumeRune ())
}
if delimiter , ok := lookupDelimiter (key ); ok {
return delimiter , nil
}
}
return "" , texError ("MissingOrUnrecognizedDelim" , "Missing or unrecognized delimiter for \\%s" , command )
}
func (p *parser ) skipSpaces () {
for p .pos < len (p .source ) && unicode .IsSpace (p .peekRune ()) {
p .consumeRune ()
}
}
func (p *parser ) skipComment () {
for p .pos < len (p .source ) && p .peekRune () != '\n' {
p .consumeRune ()
}
}
func (p *parser ) peekRune () rune {
r , _ := utf8 .DecodeRuneInString (p .source [p .pos :])
return r
}
func (p *parser ) consumeRune () rune {
r , size := utf8 .DecodeRuneInString (p .source [p .pos :])
p .pos += size
return r
}
func isASCIILetter(r rune ) bool {
return r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z'
}
func macroArguments(body string ) int {
max := 0
for i := 0 ; i +1 < len (body ); i ++ {
if body [i ] != '#' || body [i +1 ] < '1' || body [i +1 ] > '9' {
continue
}
n := int (body [i +1 ] - '0' )
if n > max {
max = n
}
}
return max
}
func parseInteger(value string , id string ) (int , error ) {
n , err := strconv .Atoi (strings .TrimSpace (value ))
if err != nil {
return 0 , texError (id , "Invalid number %q" , value )
}
return n , nil
}
func applyMathVariant(n *mml .Node , variant string ) {
n .Walk (func (current *mml .Node ) bool {
if current .Kind == "mi" || current .Kind == "mn" || current .Kind == "mo" {
if keep , ok := current .Attributes .GetExplicit ("mjx-keep-attrs" ); ok {
if names , ok := keep .(string ); ok && strings .Contains (" " +names +" " , " mathvariant " ) {
return true
}
}
current .Attributes .Set ("mathvariant" , variant )
}
return true
})
}
const resolvedFontScope = "go-resolved-font-scope"
func applyScopedMathVariant(n *mml .Node , variant string ) {
applyFontScope (n , variant , true )
}
func applyFontScope(n *mml .Node , variant string , ambientOnly bool ) {
n .Walk (func (current *mml .Node ) bool {
if current .Kind != "mi" && current .Kind != "mn" && current .Kind != "mo" {
return true
}
if eligible , _ := current .Property (ambientFontSource ); ambientOnly && eligible != true {
return true
}
if _ , resolved := current .Property (resolvedFontScope ); resolved {
return true
}
if keep , ok := current .Attributes .GetExplicit ("mjx-keep-attrs" ); ok {
if names , ok := keep .(string ); ok && strings .Contains (" " +names +" " , " mathvariant " ) {
return true
}
}
current .Attributes .Set ("mathvariant" , variant )
current .SetProperty (resolvedFontScope , true )
return true
})
}
const ambientFontSource = "go-ambient-font-source"
func ambientFontToken(n *mml .Node ) *mml .Node {
n .SetProperty (ambientFontSource , true )
return n
}
func ambientLiteralToken(n *mml .Node , character rune ) *mml .Node {
for _ , interval := range mjOperatorRanges {
if int (character ) < interval .First {
break
}
if int (character ) <= interval .Last {
if interval .HasVariant {
n .Attributes .Set ("mathvariant" , interval .Variant )
return n
}
break
}
}
return ambientFontToken (n )
}
var styleDeclarations = map [string ]map [string ]any {
"displaystyle" : {"displaystyle" : true , "scriptlevel" : 0 },
"textstyle" : {"displaystyle" : false , "scriptlevel" : 0 },
"scriptstyle" : {"displaystyle" : false , "scriptlevel" : 1 },
"scriptscriptstyle" : {"displaystyle" : false , "scriptlevel" : 2 },
}
var sizeDeclarations = map [string ]float64 {
"tiny" : 0.5 , "Tiny" : 0.6 , "scriptsize" : 0.7 , "small" : 0.85 ,
"normalsize" : 1 , "large" : 1.2 , "Large" : 1.44 , "LARGE" : 1.73 ,
"huge" : 2.07 , "Huge" : 2.49 ,
}
func emLength(value float64 ) string {
if value > -0.001 && value < 0.001 {
return "0"
}
formatted := strconv .FormatFloat (value , 'f' , 3 , 64 )
formatted = strings .TrimRight (strings .TrimRight (formatted , "0" ), "." )
return formatted + "em"
}
var fontDeclarations = baseFontMappings ("SetFont" )
type infixFractionSpec struct {
attributes map [string ]any
open, close string
}
func (p *parser ) infixFraction (name string , left []*mml .Node , terminator byte , stopRight , infixPending bool ) (*mml .Node , string , error ) {
spec , err := p .readInfixSpec (name )
if err != nil {
return nil , "" , err
}
return p .finishInfixFraction (name , left , spec , terminator , stopRight , infixPending , "" )
}
func (p *parser ) readInfixSpec (name string ) (infixFractionSpec , error ) {
spec := infixFractionSpec {attributes : map [string ]any {}}
if strings .HasSuffix (name , "withdelims" ) {
var err error
spec .open , err = p .readDelimiter (name , false )
if err != nil {
return spec , err
}
spec .close , err = p .readDelimiter (name , false )
if err != nil {
return spec , err
}
}
if name == "atop" || name == "choose" || name == "brace" || name == "brack" {
spec .attributes ["linethickness" ] = "0"
} else if name == "atopwithdelims" {
spec .attributes ["linethickness" ] = 0
}
if name == "above" || name == "abovewithdelims" {
thickness , err := p .readDimension (name )
if err != nil {
return spec , err
}
spec .attributes ["linethickness" ] = thickness
}
return spec , nil
}
func (p *parser ) finishInfixFraction (name string , left []*mml .Node , spec infixFractionSpec , terminator byte , stopRight , infixPending bool , pendingFont string ) (*mml .Node , string , error ) {
if infixPending {
return nil , "" , texError ("AmbiguousUseOf" , "Ambiguous use of \\%s" , name )
}
rightNodes , right , err := p .parseRowContinuation (terminator , stopRight , true , nil , pendingFont , nil )
if err != nil {
return nil , "" , err
}
if p .environmentPopped {
return row (rightNodes , true ), right , nil
}
frac := setAttributes (node ("mfrac" , row (left , true ), row (rightNodes , true )), spec .attributes )
if name == "choose" || name == "brace" || name == "brack" {
open , close := "(" , ")"
if name == "brace" {
open , close = "{" , "}"
} else if name == "brack" {
open , close = "[" , "]"
}
frac .Attributes .Set ("linethickness" , 0 )
frac .SetProperty ("withDelims" , true )
frac = forcedRow ([]*mml .Node {
p .amsFixedFencePalette (open , mml .TeXClassOpen ),
frac ,
p .amsFixedFencePalette (close , mml .TeXClassClose ),
}, false )
frac .SetProperty ("open" , open )
frac .SetProperty ("close" , close )
frac .SetProperty ("texClass" , mml .TeXClassOrd )
frac .TeXClass = mml .TeXClassOrd
} else if spec .open != "" || spec .close != "" {
frac .SetProperty ("withDelims" , true )
fenced := forcedRow (nil , false )
fenced .SetProperty ("open" , spec .open )
fenced .SetProperty ("close" , spec .close )
fenced .SetProperty ("texClass" , mml .TeXClassOrd )
fenced .TeXClass = mml .TeXClassOrd
if spec .open != "" {
open , err := p .amsGenfracPalette (spec .open , "l" )
if err != nil {
return nil , "" , err
}
fenced .AppendChild (open )
}
fenced .AppendChild (frac )
if spec .close != "" {
close , err := p .amsGenfracPalette (spec .close , "r" )
if err != nil {
return nil , "" , err
}
fenced .AppendChild (close )
}
frac = fenced
}
return frac , right , nil
}
func fenced(open string , content *mml .Node , close string , stretchy bool ) *mml .Node {
return fencedWithOperator (open , content , close , stretchy , operator )
}
func fencedWithOperator(open string , content *mml .Node , close string , stretchy bool , makeOperator func (string , mml .TeXClass , map [string ]any ) *mml .Node ) *mml .Node {
children := make ([]*mml .Node , 0 , 3 )
openNode := makeOperator (open , mml .TeXClassOpen , nil )
openNode .Attributes .Set ("fence" , true )
openNode .Attributes .Set ("stretchy" , stretchy )
openNode .Attributes .Set ("symmetric" , true )
children = append (children , openNode )
if content != nil && content .Kind == "mrow" && content .Flags .Inferred {
children = append (children , content .Children ...)
} else {
children = append (children , content )
}
closeNode := makeOperator (close , mml .TeXClassClose , nil )
closeNode .Attributes .Set ("fence" , true )
closeNode .Attributes .Set ("stretchy" , stretchy )
closeNode .Attributes .Set ("symmetric" , true )
children = append (children , closeNode )
return forcedRow (children , false )
}
func leftRightFenced(open string , content *mml .Node , close string , stretchy bool ) *mml .Node {
n := fenced (open , content , close , stretchy )
n .SetProperty ("open" , open )
n .SetProperty ("close" , close )
n .SetProperty ("texClass" , mml .TeXClassInner )
n .TeXClass = mml .TeXClassInner
return n
}
func debugNode(n *mml .Node ) string {
return fmt .Sprintf ("%s(%q)" , n .Kind , textContent (n ))
}
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 .