// Copyright (c) 2020-2022 MathJax Consortium // SPDX-License-Identifier: Apache-2.0 // Source: ts/input/tex/mathtools/MathtoolsMethods.ts, MathtoolsUtil.ts, // and base/BaseItems.ts (EqnArrayItem and ArrayItem.addRowSpacing).
package tex import ( ) // Only EqnArrayItem owns this state. FlalignItem has a different kind even // though its implementation extends EqnArrayItem. type equationRowState struct { entries []*mml.Node table *equationTableState } type equationTableState struct { rules *arrayRules rows int flushAbove int spacing []string appendRow func(*mml.Node) error initialSpacing string } func newEquationTableState( func(*mml.Node) error) *equationTableState { := &equationTableState{flushAbove: -1, appendRow: } .rules = &arrayRules{rows: func() int { return .rows }} return } func ( *equationRowState) ( []*mml.Node) { if len(.entries) != 0 { = fixInitialMO() } .entries = append(.entries, node("mtd", matrixCellContent())) } func ( *equationRowState) () error { if := .table.appendRow(node("mtr", .entries...)); != nil { return } .entries = nil .table.rows++ return nil } func ( *equationTableState) ( string) { , := "3pt", .3 if .initialSpacing != "" { , = .initialSpacing, matrixDimensionEm(.initialSpacing) } if .spacing == nil { .spacing = []string{} } // The source caches the original spacing, not a previous adjustment. for len(.spacing) < .rows { .spacing = append(.spacing, arraySpacingEm()) } if .rows != 0 { := + matrixDimensionEm() if < 0 { = 0 } .spacing[.rows-1] = arraySpacingEm() } } func ( *equationTableState) ( *mml.Node) { if != nil { .SetProperty(arrayRulesProperty, .rules) } if != nil && .spacing != nil { // ArrayItem.checkLines fills trailing rows with the cached original // spacing, so a shortened last explicit value is not repeated. := "0.3em" if .initialSpacing != "" { = arraySpacingEm(matrixDimensionEm(.initialSpacing)) } for len(.spacing) < .rows { .spacing = append(.spacing, ) } .Attributes.Set("rowspacing", strings.Join(.spacing, " ")) } } func notInAlignment( string) error { return texError("NotInAlignment", "\\%s can only be used in aligment environments", ) } func isMathtoolsArrayCommand( string) bool { switch { case "ArrowBetweenLines", "Aboxed", "vdotswithin", "shortvdotswithin", "MTFlushSpaceAbove", "MTFlushSpaceBelow": return true } return false } func ( *parser) () bool { for .pos < len(.source) && internalTextSpace(.peekRune()) { .consumeRune() } := .pos < len(.source) && .source[.pos] == '*' if { .pos++ } return } func ( *parser) ( string) (string, error) { for .pos < len(.source) && internalTextSpace(.peekRune()) { .consumeRune() } , , := .readArgumentAtCursor(, false) return , } func ( *parser) ( string) error { := .readMathtoolsStar() := "\\Updownarrow" , , := .readBrackets(&) if != nil { if , := .(*Error); && .ID == "MissingCloseBracket" { return texError(.ID, "Could not find closing ']' for argument to \\%s", ) } return } := .arrayCell.equation := + "\\quad" if { // Direct EndEntry does not clear the lexical environment. .endEntry(nil) .endEntry(nil) = "\\quad" + } , := .parseChild() if != nil { return } if .activeFont != "" || .fontExplicitEmpty { applyScopedMathVariant(, .activeFont) } .endEntry(unwrapInferred()) return .endRow() } func ( *parser) ( string) error { := .arrayCell.equation if len(.entries)%2 == 1 { .entries = append(.entries, node("mtd")) } , := .readMathtoolsArgument() if != nil { return } := .source[.pos:] .source, .pos = +"&&\\endAboxed", 0 , := .readUpTo(, "&") if != nil { return } , := .readUpTo(, "&") if != nil { return } if , := .readUpTo(, "\\endAboxed"); != nil { return } := "\\rlap{\\boxed{" + + "{}" + + "}}\\kern.267em\\phantom{" + + "}&\\phantom{{}" + + "}\\kern.267em" .source, .pos = +, 0 return nil } func ( *parser) ( string, bool) (*mml.Node, error) { , := .readMathtoolsArgument() if != nil { return nil, } , := .parseChild("\\mmlToken{mi}{}" + + "\\mmlToken{mi}{}") if != nil { return nil, } := node("mpadded", .token("mo", "⋮")) .Attributes.Set("width", 0) .Attributes.Set("lspace", "-.5width") if { .Attributes.Set("height", "-.6em") .Attributes.Set("voffset", "-.18em") } := node("mpadded", , node("mphantom", )) .Attributes.Set("lspace", ".5width") return , nil } // Authored and macro-generated Entry tokens use the same parser boundary as // CD. Readers can consume an ampersand as an argument without delivering one. // Direct EndEntry/EndRow commands keep reading in that parser, preserving its // font environment; an actual Entry starts a cell with the cleared Array env. func ( *parser) ( []string, bool, func(*mml.Node) error, ...*equationTableState) error { := newEquationTableState() if len() != 0 { = [0] } := strings.Join(, "&") if ! { += "\\cr" } return .parseEquationBody(, ) } func ( *parser) ( string, *equationTableState) error { := &equationRowState{table: } return .parseArrayBody(, arrayBodyOwner{ rules: .rules, configure: func( *parser) { .arrayCell.equation = }, hasEntries: func() bool { return len(.entries) != 0 }, endEntry: func( []*mml.Node, *arrayCellState) error { .endEntry() return nil }, endRow: .endRow, addSpacing: .addSpacing, }) } func isEquationArray( string) bool { switch { case "align", "align*", "aligned", "alignedat", "alignat", "alignat*", "split", "gather", "gather*", "gathered", "lgathered", "rgathered": return true } return false } func ( *parser) (, string) (*mml.Node, *equationTableState, error) { := .amsTags() := == "alignat" || == "alignat*" .start(, , == "alignat") defer .end() var , []*mml.Node := func( *mml.Node) error { = append(, ) , := .getTag() if != nil { return } = append(, ) .clearTag() return nil } := newEquationTableState() if := .parseEquationBody(, ); != nil { return nil, nil, } := node("mtable", ...) for , := range { if != nil { .Children[] = node("mlabeledtr", append([]*mml.Node{}, .Children[].Children...)...) .Children[].Parent = } } return , , nil }