// Copyright 2017-2022 The MathJax Consortium // SPDX-License-Identifier: Apache-2.0 // Based on BaseMethods.Limits and the node-specific MmlNode.coreMO methods.
package tex import ( ) // limitsCore follows the source node's coreMO delegation. Its result need not // be an operator: a nonembellished row is deliberately returned as itself. // Keep this parser policy separate from the existing generic tree helpers. func limitsCore( *mml.Node) *mml.Node { for != nil { := 0 switch .Kind { case "mrow": if !.Flags.Embellished { return } = .Flags.CoreIndex case "msub", "msup", "msubsup", "munder", "mover", "munderover", "mmultiscripts", "TeXAtom", "mstyle", "mpadded", "mphantom", "semantics", "mfrac", "math", "mtd": case "maction": , := .Attributes.Get("selection") := limitsSelection(, ) := math.Max(1, math.Min(float64(len(.Children)), )) - 1 if math.IsNaN() || != math.Trunc() || < 0 || >= float64(len(.Children)) || .Children[int()] == nil { // MmlMaction.selected returns a new empty mrow when the // numeric index does not address a child. return texMMLFactory.Create("mrow") } = int() default: return } if < 0 || >= len(.Children) || .Children[] == nil { return } = .Children[] } return nil } func ( *parser) ( []*mml.Node, string) ([]*mml.Node, error) { var *mml.Node if len() != 0 { = [len()-1] } := limitsCore() var any if != nil { , _ = .Property("movesupsub") } if == nil || == nil || (.TeXClass != mml.TeXClassOp && == nil) { return nil, texError("MisplacedLimits", "%s is allowed only on operators", "\\"+) } := == "limits" := "" switch .Kind { case "munder", "mover", "munderover": if ! { = "msubsup" } case "msub", "msup", "msubsup", "mmultiscripts": if { = "munderover" } } if != "" { := .Kind == "mmultiscripts" // NodeUtil.copyChildren reuses child identities and reparents them; it // does not copy the old wrapper's attributes or properties. := texMMLFactory.Create() .SetChildren(.Children) refreshDynamicFlags() if { // The original registers the new generic family with cleanSubSup. // It may still have both right slots absent at the end of parsing. .SetProperty(poppedScriptOrigin, true) .notePoppedScript() } = [len()-1] = } .SetProperty("movesupsub", ) applySourceObject(limitsCore(), mjSourceObject{{Name: "movablelimits", Value: false}}) , := .Attributes.Get("movablelimits") , := .Property("movablelimits") if limitsTruthy() || limitsTruthy() { applySourceObject(, mjSourceObject{{Name: "movablelimits", Value: false}}) } return , nil } func limitsTruthy( any) bool { switch v := .(type) { case nil: return false case bool: return case string: return != "" case int: return != 0 case int64: return != 0 case float64: return != 0 && == default: return true } } // limitsSelection retains the parser entry point while sharing the exact // scalar conversion with the renderer's source coreMO selection. func limitsSelection( any, bool) float64 { return mml.MactionSelectionNumber(, ) } // This parser compacts script nodes as it creates them, before MathJax's final // filter would. Remember that origin only until Compile's existing cleanup. const limitsScriptOrigin = "_texLimitsScriptOrigin" // parseLimits bridges only parser-created eager script nodes to the source // handler's full family/slot representation. Authored annotation nodes enter // setLimits unchanged, including their original child positions. func ( *parser) ( []*mml.Node, string) ([]*mml.Node, error) { if len() != 0 { := [len()-1] if , := .Property(limitsScriptOrigin); == true && len(.Children) == 2 { := []*mml.Node{.Children[0], .Children[1]} switch .Kind { case "msub", "munder": = append(, nil) case "msup", "mover": = []*mml.Node{.Children[0], nil, .Children[1]} default: = nil } if != nil { if .Kind == "msub" || .Kind == "msup" { .Kind = "msubsup" } else { .Kind = "munderover" } .Flags.Arity = 3 .SetChildren() refreshDynamicFlags() } } } , := .setLimits(, ) if != nil { return nil, } := [len()-1] if .Kind == "msubsup" || .Kind == "munderover" { var , *mml.Node if len(.Children) > 1 { = .Children[1] } if len(.Children) > 2 { = .Children[2] } if len(.Children) > 0 && ( == nil || == nil) && ( != nil || != nil) { := if == nil { = } := "msub" if .Kind == "munderover" { = "munder" } if == nil { if == "msub" { = "msup" } else { = "mover" } } .Kind = .Flags.Arity = 2 .SetChildren([]*mml.Node{.Children[0], }) refreshDynamicFlags() .SetProperty(limitsScriptOrigin, true) } } return , nil }