package tex
import (
"strings"
"unicode/utf8"
"github.com/d2lang/mathjax-go/internal/mml"
)
func (p *parser ) beginEnvironment (name string ) ([]*mml .Node , error ) {
environment , err := p .readEnvironmentName (name )
if err != nil {
return nil , err
}
if err := p .countEnvironment (); err != nil {
return nil , err
}
registered , _ := p .sourceEndDefinition (environment )
if !registered {
return nil , texError ("UnknownEnv" , "Unknown environment '%s'" , environment )
}
outerEnvironment := p .environmentOwner
frame := &environmentFrame {name : environment , parent : outerEnvironment , stream : environment == "spreadlines" || environment == "numcases" || environment == "subnumcases" }
p .environmentOwner = frame
defer func () { p .environmentOwner = outerEnvironment }()
if _ , defined := p .state .environments [environment ]; !defined && isOrdinaryArrayEnvironment (environment ) {
return p .ordinaryArrayEnvironment (environment , frame )
}
if nodes , handled , err := p .casesEnvironment (environment ); handled {
return nodes , err
}
if rootIndexArrayEnvironment (environment ) {
inRoot , color := p .inRoot , p .activeColor
p .inRoot , p .activeColor = false , ""
defer func () { p .inRoot , p .activeColor = inRoot , color }()
}
if nodes , handled , err := p .mathtoolsEnvironment (environment ); handled {
return nodes , err
}
if p .state .augmentedPackages {
if nodes , handled , err := p .empheqEnvironment (environment ); handled {
return nodes , err
}
}
if nodes , handled , err := p .physicsEnvironment (environment ); handled {
return nodes , err
}
if nodes , handled , err := p .amscdEnvironment (environment ); handled {
return nodes , err
}
if nodes , handled , err := p .baseAMSEnvironment (environment ); handled {
return nodes , err
}
if nodes , handled , err := p .amsTagEnvironment (environment ); handled {
return nodes , err
}
if definition , ok := p .state .environments [environment ]; ok {
args := make ([]string , 0 , definition .arguments )
if definition .optionalDefault != nil {
arg , present , err := p .readBrackets (definition .optionalDefault )
if err != nil {
return nil , err
}
if !present {
arg = *definition .optionalDefault
}
args = append (args , arg )
}
for len (args ) < definition .arguments {
arg , _ , err := p .readArgument ("begin{" +environment +"}" , false )
if err != nil {
return nil , err
}
args = append (args , arg )
}
begin , err := substituteArguments (definition .begin , args )
if err != nil {
return nil , err
}
end , err := substituteArguments (definition .end , nil )
if err != nil {
return nil , err
}
body , err := p .captureEnvironment (environment )
if err != nil {
return nil , err
}
parsed , err := p .parseContinuationString (begin + body + end )
if err != nil {
return nil , err
}
return []*mml .Node {parsed }, nil
}
columnSpec := "c"
if environment == "array" || environment == "subarray" || environment == "crampedsubarray" {
columnSpec , _, err = p .readArgument ("begin{" +environment +"}" , false )
if err != nil {
return nil , err
}
}
if strings .HasSuffix (environment , "*" ) && strings .Contains (environment , "matrix" ) {
defaultAlignment := "c"
alignment , _ , err := p .readBrackets (&defaultAlignment )
if err != nil {
return nil , err
}
columnSpec , err = p .completeArrayAlignment (alignment )
if err != nil {
return nil , err
}
}
if strings .Contains (environment , "alignat" ) {
if _ , err := p .readEquationPairCount (environment ); err != nil {
return nil , err
}
}
verticalAlign := ""
if environment == "gathered" || environment == "lgathered" || environment == "rgathered" {
verticalAlign , err = p .readArrayAlignment ()
if err != nil {
return nil , err
}
}
if isGuardedEquationEnvironment (environment ) {
if err := p .checkEquationEnvironment (); err != nil {
return nil , err
}
}
body , err := p .captureEnvironment (environment )
if err != nil {
return nil , err
}
switch environment {
case "equation" , "equation*" , "displaymath" , "math" , "split" :
contents , err := p .parseContinuationString (body )
if err != nil {
return nil , err
}
return []*mml .Node {contents }, nil
case "matrix" , "matrix*" , "smallmatrix" , "smallmatrix*" , "array" , "subarray" , "crampedsubarray" ,
"pmatrix" , "pmatrix*" , "bmatrix" , "bmatrix*" , "Bmatrix" , "Bmatrix*" , "vmatrix" , "vmatrix*" , "Vmatrix" , "Vmatrix*" ,
"psmallmatrix" , "psmallmatrix*" , "bsmallmatrix" , "bsmallmatrix*" , "Bsmallmatrix" , "Bsmallmatrix*" , "vsmallmatrix" , "vsmallmatrix*" , "Vsmallmatrix" , "Vsmallmatrix*" :
style := "T"
if strings .Contains (environment , "small" ) || strings .Contains (environment , "subarray" ) {
style = "S"
}
rowspacing := "4pt"
if strings .Contains (environment , "subarray" ) {
rowspacing = "0.1em"
}
table , err := p .parseTable (body , style , rowspacing )
if err != nil {
return nil , err
}
if strings .Contains (environment , "matrix" ) {
table .Attributes .Set ("columnalign" , "center" )
}
if environment == "subarray" || environment == "crampedsubarray" {
resetTableAttributes (table ,
"columnspacing" , "0em" ,
"rowspacing" , tableRowSpacing (table ),
)
table .SetProperty ("useHeight" , false )
table .SetProperty ("scriptlevel" , 1 )
table .Attributes .Set ("displaystyle" , false )
}
if environment == "crampedsubarray" {
table .Attributes .Set ("data-cramped" , true )
}
table = applyColumnSpec (table , columnSpec )
open , close := matrixDelimiters (environment )
if open != "" || close != "" {
table = p .leftRightFenced (open , table , close , true )
}
return []*mml .Node {table }, nil
case "cases" , "dcases" , "rcases" , "drcases" , "cases*" , "dcases*" , "rcases*" , "drcases*" :
style := "T"
if strings .HasPrefix (environment , "d" ) {
style = "D"
}
table , err := p .parseTable (body , style , ".2em" )
if err != nil {
return nil , err
}
resetTableAttributes (table ,
"columnalign" , "left left" ,
"columnspacing" , "1em" ,
"rowspacing" , tableRowSpacing (table ),
)
table .Attributes .Set ("displaystyle" , false )
open , close := "{" , ""
if strings .Contains (environment , "rcases" ) {
open , close = "" , "}"
}
return []*mml .Node {p .leftRightFenced (open , finishArrayRules (table ), close , true )}, nil
case "numcases" , "subnumcases" :
left , _ , err := p .readArgument ("begin{" +environment +"}" , true )
if err != nil {
return nil , err
}
table , err := p .parseTable (body , "T" )
if err != nil {
return nil , err
}
if left != "" {
prefix , err := p .parseString (left )
if err != nil {
return nil , err
}
return []*mml .Node {prefix , p .leftRightFenced ("{" , finishArrayRules (table ), "" , true )}, nil
}
return []*mml .Node {p .leftRightFenced ("{" , finishArrayRules (table ), "" , true )}, nil
case "align" , "align*" , "alignat" , "alignat*" , "xalignat" , "xalignat*" , "xxalignat" , "aligned" , "alignedat" , "gather" , "gather*" , "gathered" , "multline" , "multline*" , "multlined" , "lgathered" , "rgathered" , "spreadlines" :
var table *mml .Node
var spacing *equationTableState
if isEquationArray (environment ) {
table , spacing , err = p .parseEquationTable (body , environment )
} else {
table , err = p .parseTableWithAlignment (body , "D" , false )
}
if err != nil {
return nil , err
}
if strings .Contains (environment , "gather" ) {
alignment := "center"
switch environment {
case "lgathered" :
alignment = "left"
case "rgathered" :
alignment = "right"
}
resetTableAttributes (table ,
"displaystyle" , true ,
"columnalign" , alignment ,
"columnspacing" , "1em" ,
"rowspacing" , "3pt" ,
"side" , "right" ,
"minlabelspacing" , "0.8em" ,
)
} else if strings .Contains (environment , "multline" ) {
finishAMSMultlineTable (table )
} else {
if !isEquationArray (environment ) {
fixInitialArrayOperators (table , 0 , 1 )
}
resetTableAttributes (table ,
"displaystyle" , true ,
"columnalign" , "right left" ,
"columnspacing" , "0em" ,
"rowspacing" , "3pt" ,
)
}
setArrayAlign (table , verticalAlign )
spacing .applySpacing (table )
return []*mml .Node {finishArrayRules (table )}, nil
case "CD" :
return p .parseCD (body )
default :
return nil , texError ("UnknownEnv" , "Unknown environment '%s'" , environment )
}
}
func isGuardedEquationEnvironment(environment string ) bool {
switch environment {
case "equation" , "equation*" , "align" , "align*" , "gather" , "gather*" , "eqnarray" , "eqnarray*" ,
"multline" , "multline*" , "alignat" , "alignat*" , "xalignat" , "xalignat*" ,
"xxalignat" , "flalign" , "flalign*" :
return true
}
return false
}
func (p *parser ) readEquationPairCount (environment string ) (string , error ) {
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
count , _ , err := p .readArgumentAtCursor ("begin" , false )
if err != nil {
return "" , err
}
if environment == "alignat" || environment == "alignat*" || environment == "alignedat" || isAMSXAlignAt (environment ) {
for _ , c := range count {
if c < '0' || c > '9' {
return "" , texError ("PositiveIntegerArg" , "Argument to \\begin{%s} must me a positive integer" , environment )
}
}
}
return count , nil
}
func isAMSXAlignAt(environment string ) bool {
return environment == "xalignat" || environment == "xalignat*" || environment == "xxalignat"
}
func finishAMSMultlineTable(table *mml .Node ) {
fixInitialArrayOperators (table , 1 , 0 )
alignAMSMultlineCells (table )
resetTableAttributes (table ,
"displaystyle" , true ,
"rowspacing" , ".5em" ,
"columnspacing" , "100%" ,
"width" , "100%" ,
"side" , "right" ,
"minlabelspacing" , "0.8em" ,
"framespacing" , "1em 0" ,
"frame" , "" ,
"data-width-includes-label" , true ,
)
}
func alignAMSMultlineCells(table *mml .Node ) {
if len (table .Children ) != 0 {
firstCell := table .Children [0 ].Children [0 ]
if _ , explicit := firstCell .Attributes .GetExplicit ("columnalign" ); !explicit {
firstCell .Attributes .Set ("columnalign" , "left" )
}
lastRow := table .Children [len (table .Children )-1 ]
lastCell := lastRow .Children [len (lastRow .Children )-1 ]
if _ , explicit := lastCell .Attributes .GetExplicit ("columnalign" ); !explicit {
lastCell .Attributes .Set ("columnalign" , "right" )
}
}
}
func (p *parser ) captureEnvironment (environment string ) (string , error ) {
start := p .pos
depth := 1
for i := p .pos ; i < len (p .source ); {
if p .source [i ] != '\\' {
_ , size := utf8 .DecodeRuneInString (p .source [i :])
i += size
continue
}
commandStart := i
scanner := &parser {source : p .source , pos : i + 1 }
name := scanner .readControlSequence ()
if name != "begin" && name != "end" {
i = scanner .pos
continue
}
raw , _ , err := scanner .readArgument (name , false )
if err != nil {
return "" , err
}
if strings .TrimSpace (raw ) != environment {
i = scanner .pos
continue
}
if name == "begin" {
depth ++
} else {
depth --
if depth == 0 {
body := p .source [start :commandStart ]
p .pos = scanner .pos
return body , nil
}
}
i = scanner .pos
}
return "" , texError ("MissingEnd" , "Missing \\end{%s}" , environment )
}
func (p *parser ) parseTable (body , style string , initialSpacing ...string ) (*mml .Node , error ) {
return p .parseTableWithAlignment (body , style , true , initialSpacing ...)
}
func (p *parser ) parseTableWithAlignment (body , style string , alignFills bool , initialSpacing ...string ) (*mml .Node , error ) {
return p .parseTableWithOrdinaryArray (body , style , alignFills , nil , initialSpacing ...)
}
func (p *parser ) parseTableWithOrdinaryArray (body , style string , alignFills bool , ordinary *ordinaryArrayItem , initialSpacing ...string ) (*mml .Node , error ) {
table := node ("mtable" )
var entries []*mml .Node
spacing := &arrayRowSpacing {}
var environment *environmentFrame
if ordinary != nil {
environment = ordinary .begin
}
err := p .parseArrayBody (body , arrayBodyOwner {
environment : environment ,
ordinary : ordinary ,
rules : newArrayRules (table ),
hasEntries : func () bool { return len (entries ) != 0 },
endEntry : func (children []*mml .Node , fill *arrayCellState ) error {
content := fill .finish (matrixCellContent (children ), len (children ))
cell := node ("mtd" , content )
if alignFills {
cell = arrayCellNode (content )
}
entries = append (entries , cell )
return nil
},
endRow : func () error {
table .AppendChild (node ("mtr" , entries ...))
entries = nil
spacing .rows ++
return nil
},
addSpacing : spacing .add ,
})
if err != nil {
return nil , err
}
if ordinary != nil && ordinary .popped {
return nil , nil
}
table .Attributes .Set ("columnspacing" , "1em" )
initial := "4pt"
if len (initialSpacing ) != 0 {
initial = initialSpacing [0 ]
}
spacing .apply (table , initial )
return table , nil
}
func resetTableAttributes(table *mml .Node , entries ...any ) {
for _ , name := range table .Attributes .ExplicitNames () {
table .Attributes .Explicit ().Delete (name )
}
for i := 0 ; i < len (entries ); i += 2 {
table .Attributes .Set (entries [i ].(string ), entries [i +1 ])
}
}
func repeatAMSEqnArrayDefinition(definition string , maximum int ) string {
values := strings .Fields (definition )
if len (values ) <= 1 {
return definition
}
for len (values ) < maximum {
values = append (values , values ...)
}
if maximum < 0 {
maximum += len (values )
if maximum < 0 {
maximum = 0
}
}
if maximum < len (values ) {
values = values [:maximum ]
}
return strings .Join (values , " " )
}
func finishAMSEqnArrayTable(table *mml .Node , environment string , maximum int ) {
if environment == "eqnarray" || environment == "eqnarray*" {
resetTableAttributes (table ,
"displaystyle" , true ,
"columnalign" , repeatAMSEqnArrayDefinition ("right center left" , maximum ),
"columnspacing" , repeatAMSEqnArrayDefinition ("0em 0.278em" , maximum -1 ),
"rowspacing" , "3pt" ,
"side" , "right" ,
"minlabelspacing" , "0.8em" ,
)
return
}
if environment == "gather" || environment == "gather*" {
resetTableAttributes (table ,
"displaystyle" , true ,
"columnalign" , "center" ,
"columnspacing" , "1em" ,
"rowspacing" , "3pt" ,
"side" , "right" ,
"minlabelspacing" , "0.8em" ,
)
return
}
spacing := "0em"
switch environment {
case "align" , "align*" , "aligned" :
spacing = "0em 2em"
}
resetTableAttributes (table ,
"displaystyle" , true ,
"columnalign" , repeatAMSEqnArrayDefinition ("right left" , maximum ),
"columnspacing" , repeatAMSEqnArrayDefinition (spacing , maximum -1 ),
"rowspacing" , "3pt" ,
)
}
func splitTable(body string ) [][]string {
rows := [][]string {{}}
cellStart := 0
depth := 0
for i := 0 ; i < len (body ); {
switch body [i ] {
case '{' :
depth ++
i ++
case '}' :
if depth > 0 {
depth --
}
i ++
case '\\' :
if i +1 < len (body ) && body [i +1 ] == '\\' && depth == 0 {
rows [len (rows )-1 ] = append (rows [len (rows )-1 ], body [cellStart :i ])
i += 2
if i < len (body ) && body [i ] == '[' {
if end := strings .IndexByte (body [i :], ']' ); end >= 0 {
i += end + 1
}
}
rows = append (rows , []string {})
cellStart = i
continue
}
i ++
if i < len (body ) {
_ , size := utf8 .DecodeRuneInString (body [i :])
i += size
}
case '&' :
if depth == 0 {
rows [len (rows )-1 ] = append (rows [len (rows )-1 ], body [cellStart :i ])
cellStart = i + 1
}
i ++
default :
_ , size := utf8 .DecodeRuneInString (body [i :])
i += size
}
}
rows [len (rows )-1 ] = append (rows [len (rows )-1 ], body [cellStart :])
return rows
}
func splitTopLevel(value string , delimiter rune ) []string {
parts := []string {}
start := 0
depth := 0
for i := 0 ; i < len (value ); {
r , size := utf8 .DecodeRuneInString (value [i :])
if r == '\\' {
i += size
if i < len (value ) {
_ , nextSize := utf8 .DecodeRuneInString (value [i :])
i += nextSize
}
continue
}
if r == '{' {
depth ++
} else if r == '}' && depth > 0 {
depth --
} else if r == delimiter && depth == 0 {
parts = append (parts , value [start :i ])
start = i + size
}
i += size
}
return append (parts , value [start :])
}
func matrixDelimiters(environment string ) (string , string ) {
environment = strings .TrimSuffix (environment , "*" )
switch environment {
case "pmatrix" , "psmallmatrix" :
return "(" , ")"
case "bmatrix" , "bsmallmatrix" :
return "[" , "]"
case "Bmatrix" , "Bsmallmatrix" :
return "{" , "}"
case "vmatrix" , "vsmallmatrix" :
return "|" , "|"
case "Vmatrix" , "Vsmallmatrix" :
return "‖" , "‖"
}
return "" , ""
}
func (p *parser ) completeArrayAlignment (alignment string ) (string , error ) {
if alignment != "" {
return alignment , nil
}
for p .pos < len (p .source ) && internalTextSpace (p .peekRune ()) {
p .consumeRune ()
}
alignment , _ , err := p .readArgumentAtCursor ("begin" , false )
return alignment , err
}
func applyColumnSpec(table *mml .Node , specification string ) *mml .Node {
var aligns []string
syntax := []byte {'c' }
for _ , char := range specification {
switch char {
case 'l' :
aligns = append (aligns , "left" )
syntax = append (syntax , 'l' )
case 'c' :
aligns = append (aligns , "center" )
syntax = append (syntax , 'c' )
case 'r' :
aligns = append (aligns , "right" )
syntax = append (syntax , 'r' )
case '|' , ':' :
syntax = append (syntax , byte (char ))
}
}
table .Attributes .Set ("columnalign" , strings .Join (aligns , " " ))
var positions []byte
isRule := func (char byte ) bool { return char == '|' || char == ':' }
for i := 0 ; i < len (syntax ); i ++ {
char := syntax [i ]
if !isRule (char ) {
for i +1 < len (syntax ) && isRule (syntax [i +1 ]) {
i ++
char = syntax [i ]
}
}
positions = append (positions , char )
}
if !strings .ContainsAny (string (positions ), "|:" ) {
return finishArrayRules (table )
}
var frame , lines []string
if isRule (positions [0 ]) {
frame = append (frame , "left" )
}
if isRule (positions [len (positions )-1 ]) {
frame = append (frame , "right" )
}
for i := 1 ; i < len (positions )-1 ; i ++ {
line := "none"
if positions [i ] == '|' {
line = "solid"
} else if positions [i ] == ':' {
line = "dashed"
}
lines = append (lines , line )
}
columnLines := strings .Join (lines , " " )
table .Attributes .Set ("columnlines" , columnLines )
rules := tableArrayRules (table )
rules .frame = append (frame , rules .frame ...)
rules .dashed = positions [0 ] == ':'
return finishArrayRules (table )
}
func (p *parser ) parseCD (body string ) ([]*mml .Node , error ) {
arrayParser := p .matrixCellParser ("" )
arrayParser .matrixClose = false
table := node ("mtable" )
arrayParser .arrayCell .rules = newArrayRules (table )
spacing := &arrayRowSpacing {}
source := body
for {
cells , rest , stop , err := arrayParser .parseCDRow (source , len (table .Children ))
if err != nil {
return nil , err
}
if len (cells ) != 0 {
table .AppendChild (node ("mtr" , cells ...))
spacing .rows ++
}
if stop == nil {
break
}
if stop .spacing != "" {
spacing .add (stop .spacing )
}
source = rest
}
table .Attributes .Set ("columnspacing" , "5pt" )
spacing .apply (table , "5pt" )
table .Attributes .Set ("displaystyle" , true )
return []*mml .Node {matrixCellContent ([]*mml .Node {table })}, nil
}
type cdCellEnd struct {
firstObjectCell bool
}
type cdSegmentResult struct {
cells []*mml .Node
contents *mml .Node
fill *arrayCellState
remaining string
stop *cellItem
}
func (p *parser ) parseCDSegment (source string , cells []*mml .Node , objectRow , atArrow bool ) (cdSegmentResult , error ) {
for {
sub := p .matrixCellParser (source )
sub .matrixClose = false
sub .cdArrayEntry = true
sub .arrayCell .rules = p .arrayCell .rules
if atArrow {
sub .cdCellEnd = &cdCellEnd {firstObjectCell : objectRow && len (cells ) == 0 }
}
children , _ , err := sub .parseRowWithInfix (0 , false , false )
if err != nil {
return cdSegmentResult {}, err
}
contents := sub .arrayCell .finish (matrixCellContent (children ), len (children ))
if sub .stoppedCell == nil {
return cdSegmentResult {cells : cells , contents : contents , fill : sub .arrayCell }, nil
}
cells = append (cells , arrayCellNode (contents ))
source = sub .source [sub .pos :]
if sub .stoppedCell .cr {
return cdSegmentResult {cells : cells , remaining : source , stop : sub .stoppedCell }, nil
}
}
}
func (p *parser ) parseCDRow (raw string , rowIndex int ) ([]*mml .Node , string , *cellItem , error ) {
var cells []*mml .Node
position := 0
objectRow := rowIndex %2 == 0
for position < len (raw ) {
at := position
for {
next := strings .IndexByte (raw [at :], '@' )
if next < 0 {
at = -1
break
}
at += next
if at +1 < len (raw ) && strings .ContainsRune ("><VA.|=" , rune (raw [at +1 ])) {
break
}
at ++
}
if at < 0 {
break
}
segment , err := p .parseCDSegment (raw [position :at ], cells , objectRow , true )
if err != nil {
return nil , "" , nil , err
}
if segment .stop != nil {
return segment .cells , segment .remaining + raw [at :], segment .stop , nil
}
cells = segment .cells
contents , fill := segment .contents , segment .fill
if !emptyCDContents (contents ) {
cells = append (cells , arrayCellNode (contents ))
fill = nil
}
if (len (cells )%2 == 0 ) == objectRow {
if fill != nil && len (fill .fills ) != 0 {
cells = append (cells , arrayCellNode (fill .finish (forcedRow (nil , true ), 0 )))
} else {
cells = append (cells , node ("mtd" ))
}
fill = nil
}
arrow , next , err := p .parseCDArrow (raw , at )
if err != nil {
return nil , "" , nil , err
}
if fill != nil {
size := 1
if emptyCDContents (arrow ) {
size = 0
}
fill .finish (arrow , size )
}
cells = append (cells , arrayCellNode (arrow ))
position = next
}
segment , err := p .parseCDSegment (raw [position :], cells , objectRow , false )
if err != nil {
return nil , "" , nil , err
}
if segment .stop != nil {
return segment .cells , segment .remaining , segment .stop , nil
}
cells = segment .cells
if len (cells ) != 0 || !emptyCDContents (segment .contents ) {
cells = append (cells , arrayCellNode (segment .contents ))
}
return cells , "" , nil , nil
}
func emptyCDContents(contents *mml .Node ) bool {
return contents .Kind == "mrow" && contents .Flags .Inferred && len (contents .Children ) == 0
}
func cdStrut() *mml .Node {
strut := node ("mpadded" )
strut .Attributes .Set ("height" , "8.5pt" )
strut .Attributes .Set ("depth" , "2pt" )
return strut
}
func (p *parser ) parseCDArrow (raw string , at int ) (*mml .Node , int , error ) {
if at +1 >= len (raw ) {
return nil , at , texError ("CDMissingArrow" , "Missing CD arrow after @" )
}
kind := raw [at +1 ]
if !strings .ContainsRune ("><VA.|=" , rune (kind )) {
ordinary , err := p .parseString ("@" )
return ordinary , at + 1 , err
}
position := at + 2
if kind == '.' {
return forcedRow (nil , true ), position , nil
}
if kind == '|' {
return p .cdVerticalArrow ("‖" ), position , nil
}
if kind == '=' {
return p .cdHorizontalArrow ("=" ), position , nil
}
first , next , err := cdLabel (raw , position , kind )
if err != nil {
return nil , at , err
}
second , next , err := cdLabel (raw , next , kind )
if err != nil {
return nil , at , err
}
arrows := map [byte ]string {'>' : "→" , '<' : "←" , 'V' : "↓" , 'A' : "↑" }
arrow := p .cdHorizontalArrow (arrows [kind ])
if kind == 'V' || kind == 'A' {
arrow = p .cdVerticalArrow (arrows [kind ])
}
if kind == '>' || kind == '<' {
if first == "" {
first = "\\kern " + p .amscdState (amscdMinWidthState , "2.75em" )
}
over , err := p .parseArgumentString (first )
if err != nil {
return nil , at , err
}
over = cdArrowLabel (over , true )
if second == "" {
return node ("mover" , arrow , over ), next , nil
}
under , err := p .parseArgumentString (second )
if err != nil {
return nil , at , err
}
under = cdArrowLabel (under , false )
return node ("munderover" , arrow , under , over ), next , nil
}
arrow .TeXClass = mml .TeXClassOrd
if first == "" && second == "" {
return arrow , next , nil
}
var children []*mml .Node
if first != "" {
label , err := p .parseArgumentString ("\\scriptstyle\\llap{" + first + "}" )
if err != nil {
return nil , at , err
}
children = append (children , label )
}
children = append (children , arrow )
if second != "" {
label , err := p .parseArgumentString ("\\scriptstyle\\rlap{" + second + "}" )
if err != nil {
return nil , at , err
}
children = append (children , label )
}
return forcedRow (children , false ), next , nil
}
func cdArrowLabel(label *mml .Node , over bool ) *mml .Node {
padded := node ("mpadded" , label )
padded .Attributes .Set ("width" , "+.67em" )
padded .Attributes .Set ("lspace" , ".33em" )
if over {
padded .Attributes .Set ("voffset" , ".1em" )
}
return padded
}
func cdHorizontalArrow(character string ) *mml .Node {
arrow := token ("mo" , character )
arrow .Attributes .Set ("minsize" , "2.75em" )
arrow .Attributes .Set ("stretchy" , true )
return arrow
}
func cdVerticalArrow(character string ) *mml .Node {
arrow := token ("mo" , character )
arrow .Attributes .Set ("minsize" , "1.75em" )
arrow .Attributes .Set ("stretchy" , true )
arrow .Attributes .Set ("symmetric" , true )
arrow .Attributes .Set ("lspace" , 0 )
arrow .Attributes .Set ("rspace" , 0 )
return arrow
}
func cdLabel(raw string , start int , delimiter byte ) (string , int , error ) {
depth := 0
for index := start ; index < len (raw ); index ++ {
switch raw [index ] {
case '\\' :
index ++
case '{' :
depth ++
case '}' :
if depth > 0 {
depth --
}
default :
if raw [index ] == delimiter && depth == 0 {
return raw [start :index ], index + 1 , nil
}
}
}
return "" , start , texError ("CDMissingArrow" , "Missing closing %c for CD arrow" , delimiter )
}
func (p *parser ) parseEmpheq (body string ) ([]*mml .Node , error ) {
options , _ , err := p .readBrackets (nil )
if err != nil {
return nil , err
}
innerEnvRaw , _ , err := p .readArgument ("empheq" , false )
if err != nil {
return nil , err
}
innerEnv := strings .TrimSpace (innerEnvRaw )
inner := &parser {source : "\\begin{" + innerEnv + "}" + body + "\\end{" + innerEnv + "}" , state : p .state , display : p .display }
parsed , _ , err := inner .parseRow (0 , false )
if err != nil {
return nil , err
}
content := row (parsed , true )
settings := keyvalString (options )
if left := settings ["left" ]; left != "" {
open , _ := p .convertDelimiterArgument (left )
content = p .fenced (open , content , "" , true )
}
if right := settings ["right" ]; right != "" {
close , _ := p .convertDelimiterArgument (right )
content = p .fenced ("" , content , close , true )
}
return []*mml .Node {content }, nil
}
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 .