// Copyright 2017-2022 The MathJax Consortium
// SPDX-License-Identifier: Apache-2.0
// This file is a Go translation and modification of MathJax 3.2.2.

package svg

import (
	
	
	
	
	

	
	
	
	
)

type wrapper struct {
	renderer *renderer
	node     *mml.Node
	parent   *wrapper
	children []*wrapper

	bbox          *layout.BBox
	bboxComputed  bool
	variant       font.Variant
	explicitFont  bool
	fontFamily    string
	styles        *wrapperStyles
	cssFontSize   string // CommonWrapper.removedStyles.fontSize; consumed by getScale.
	cssFontWeight string
	cssFontStyle  string
	scriptLevel   int
	displayStyle  bool
	element       *Element
	dx            float64

	stretch      font.Delimiter
	hasStretch   bool
	stretchGlyph rune
	size         int
	sizeSet      bool
	surdHeight   float64
	isMathAccent bool
	bevel        *wrapper // Generated fraction slash; not an authored child.
	fencedRow    *wrapper // Temporary wrapper row; its MML node remains empty.

	// table retains CommonMtable's natural and resolved percentage-width
	// state for the lifetime of this wrapped render tree.  It is nil for every
	// non-mtable wrapper and initialized lazily after the complete wrapper tree
	// exists.
	table *tableState
}

func ( *renderer) ( *mml.Node,  *wrapper,  int,  bool) *wrapper {
	 := &wrapper{
		renderer: , node: , parent: ,
		bbox: layout.ZeroBBox(), variant: font.Normal,
		scriptLevel: , displayStyle: ,
	}
	.inheritLayoutState()
	.initializeStyles()
	// CommonTextNode inherits all of these properties from its token parent;
	// in particular its getScale() override is a no-op.  Giving a text wrapper
	// its own relative scale divides glyph metrics by an enclosing mathsize.
	if .Kind != "text" {
		.getVariant()
		.getScale()
		.getSpace()
	}
	for ,  := range .Children {
		,  := .childLayoutState()
		 := .(, , , )
		.children = append(.children, )
		// CommonWrapper propagates a percentage-width marker through wrappers
		// that are transparent to layout and through the root math wrapper.
		if .bbox.PWidth != "" && (.Flags.NotParent || .Kind == "math") {
			.bbox.PWidth = layout.FullWidth
		}
	}
	if .Kind == "ms" {
		.initializeStringQuotes()
	}
	if .Kind == "mtable" {
		// CommonMtable selects the last top table in constructor order, even
		// when it is unlabelled or empty. Do not select during lazy layout.
		,  := findTableContainer()
		if  == nil || (.node.Kind == "math" && .parent == nil) {
			.table = 
		}
		initializeTablePWidth()
	}
	if .Kind == "msqrt" || .Kind == "mroot" {
		.initializeRoot()
	}
	if .Kind == "mfrac" {
		.initializeBevel()
	}
	if .Kind == "mfenced" {
		.initializeFencedRow()
	}
	// CommonMunder, CommonMover, and CommonMunderover stretch their children
	// once in their constructors.  Keep this out of computeBBox: table layout
	// can subsequently resize an embellished core, and a bbox recomputation
	// must not undo that later stretch pass.
	if .Kind == "munder" || .Kind == "mover" || .Kind == "munderover" {
		.initializeMathAccent()
		.stretchUnderOverChildren()
	}
	if .Kind == "math" || .Kind == "mrow" {
		.stretchRowChildren()
	}
	// CommonMrow has fixesPWidth=false, but its constructor explicitly marks
	// itself full-width after stretching. The stretch pass can resolve a
	// table width and clear its marker before the row decides to propagate it.
	if .Kind == "mrow" {
		for ,  := range .children {
			if .bbox.PWidth != "" {
				.bbox.PWidth = layout.FullWidth
				break
			}
		}
	}
	return 
}

func ( *wrapper) () {
	if ,  := numberAttribute(.node, "scriptlevel");  {
		.scriptLevel = int(math.Min(, 2))
	}
	if ,  := boolAttribute(.node, "displaystyle");  {
		.displayStyle = 
	}
}

func ( *wrapper) ( int) (int, bool) {
	,  := .scriptLevel, .displayStyle
	switch .node.Kind {
	case "msub", "msup", "msubsup":
		if  > 0 {
			++
			 = false
		}
	case "munder", "mover", "munderover":
		if  > 0 {
			++
			 = false
		}
	case "mfrac":
		if ! {
			++
		}
		 = false
	case "mroot":
		if  == 1 {
			 += 2
			 = false
		}
	case "mmultiscripts":
		if  > 0 &&  < len(.node.Children) && .node.Children[] != nil && .node.Children[].Kind != "mprescripts" {
			++
			 = false
		}
	case "mtable":
		 = false
	}
	if  > 2 {
		 = 2
	}
	return , 
}

func ( *wrapper) () {
	if !.node.Flags.Token && .node.Kind != "mi" && .node.Kind != "mn" && .node.Kind != "mo" && .node.Kind != "mtext" && .node.Kind != "ms" {
		return
	}
	// The frozen Node adaptor uses OutputJax's default merrorFont (serif).
	// CommonMtext turns that into an explicit-font variant rather than using
	// MathJax's path font.  The inferred mrow in our shared tree is transparent
	// to the semantic parent relationship, so look through wrapper ancestors.
	if .node.Kind == "mtext" && .hasAncestor("merror") {
		.explicitFont = true
		.fontFamily = "serif"
		return
	}
	if .authoredFontFamily() {
		return
	}
	// CommonMo chooses large operators and contextual pseudo-script variants
	// before the ordinary variant policy. Authored family handling above also
	// removes CSS font declarations when these specialized variants apply.
	if .node.Kind == "mo" {
		if boolAttributeDefault(.node, "largeop", false) {
			.variant = font.SmallOp
			if .displayStyle {
				.variant = font.LargeOp
			}
			return
		}
		if .pseudoScriptVariant() {
			.variant = font.TeXVariant
			return
		}
	}
	if  := stringAttribute(.node, "mathvariant", "");  != "" && font.ValidVariant(font.Variant()) {
		.variant = font.Variant()
	} else if .node.Kind == "mi" {
		 := nodeText(.node)
		if utf8.RuneCountInString() == 1 {
			.variant = font.Italic
		}
	}
	if ,  := .node.Property("variantForm");  && truthy() {
		.variant = font.TeXVariant
	}
	.applyFontWeightStyle()
}

// CommonWrapper.getVariant converts authored weight/style to native math
// variants when no family or explicit mathvariant was selected.
func ( *wrapper) () {
	if ,  := .node.Attributes.GetExplicit("mathvariant"); truthy() {
		return
	}
	 := stringAttribute(.node, "fontweight", "")
	 := stringAttribute(.node, "fontstyle", "")
	if  == "" {
		 = .cssFontWeight
	}
	if  == "" {
		 = .cssFontStyle
	}
	 = normalizedFontWeight()
	if  := fontWeightVariants[];  != nil {
		if ,  := [.variant];  {
			.variant = 
		}
	}
	if  := fontStyleVariants[];  != nil {
		if ,  := [.variant];  {
			.variant = 
		}
	}
}

// MathJax 3.2.2 CommonWrapper.BOLDVARIANTS and ITALICVARIANTS.
var fontWeightVariants = map[string]map[font.Variant]font.Variant{
	"bold": {font.Normal: font.Bold, font.Italic: font.BoldItalic,
		font.Fraktur: font.BoldFraktur, font.Script: font.BoldScript,
		font.SansSerif: font.BoldSansSerif, font.SansSerifItalic: font.SansSerifBoldItalic},
	"normal": {font.Bold: font.Normal, font.BoldItalic: font.Italic,
		font.BoldFraktur: font.Fraktur, font.BoldScript: font.Script,
		font.BoldSansSerif: font.SansSerif, font.SansSerifBoldItalic: font.SansSerifItalic},
}

var fontStyleVariants = map[string]map[font.Variant]font.Variant{
	"italic": {font.Normal: font.Italic, font.Bold: font.BoldItalic,
		font.SansSerif: font.SansSerifItalic, font.BoldSansSerif: font.SansSerifBoldItalic},
	"normal": {font.Italic: font.Normal, font.BoldItalic: font.Bold,
		font.SansSerifItalic: font.SansSerif, font.SansSerifBoldItalic: font.BoldSansSerif},
}

func normalizedFontWeight( string) string {
	if  != "" && strings.IndexFunc(, func( rune) bool { return  < '0' ||  > '9' }) == -1 {
		,  := strconv.ParseFloat(, 64)
		if  > 600 {
			return "bold"
		}
		return "normal"
	}
	return 
}

func ( *wrapper) () bool {
	,  := .node.Attributes.GetExplicit("mathvariant")
	,  := .node.Property("pseudoscript")
	return !truthy() &&  &&  == false
}

// CommonWrapper.getVariant selects explicit-font text for an authored family.
// MathML font attributes override CSS, while an explicit mathvariant overrides
// both. Keep the selected styles on this wrapper, never on the input MathML.
func ( *wrapper) () bool {
	// Deprecated mglyph character rendering has its own family handler.
	// This path handles the five text-token kinds accepted by MmlToken.
	switch .node.Kind {
	case "mi", "mn", "mo", "mtext", "ms":
	default:
		return false
	}
	 := stringAttribute(.node, "fontfamily", "")
	 := stringAttribute(.node, "fontweight", "")
	 := stringAttribute(.node, "fontstyle", "")
	 := ""
	if .styles != nil {
		 = .styles.value("font-family")
	}
	if  == "" &&  == "" {
		return false
	}
	if .styles == nil {
		.styles = &wrapperStyles{}
	}
	// The primary wrapper removes these CSS declarations before resolving
	// the variant and appends only the chosen explicit-font styles afterward.
	 := .styles.other[:0]
	for ,  := range .styles.other {
		if .name != "font-family" && .name != "font-weight" && .name != "font-style" {
			 = append(, )
		}
	}
	.styles.other = 
	// CommonMo selects large-operator and contextual pseudo-script variants
	// before family resolution; CSS declarations have already been removed.
	if .node.Kind == "mo" && (boolAttributeDefault(.node, "largeop", false) || .pseudoScriptVariant()) {
		return false
	}
	if ,  := .node.Attributes.GetExplicit("mathvariant");  && truthy() {
		return false
	}
	if  == "" {
		 = 
	}
	if  == "" {
		 = .cssFontWeight
	}
	if  == "" {
		 = .cssFontStyle
	}
	 = normalizedFontWeight()
	.styles.setOther("font-family", )
	if  != "" {
		.styles.setOther("font-weight", )
	}
	if  != "" {
		.styles.setOther("font-style", )
	}
	.variant = font.Variant("-explicitFont")
	return true
}

func ( *wrapper) () {
	 := 1.0
	if .scriptLevel != 0 {
		,  := numberAttribute(.node, "scriptsizemultiplier")
		if ! {
			// Raw MathML nodes can lack the registered global defaults.
			 = 0.7071067811865475
		}
		 = math.Pow(, float64(.scriptLevel))
		 := layout.Length2Em(stringAttribute(.node, "scriptminsize", "8px"), .8, 1, .renderer.pxPerEm)
		if  <  {
			 = 
		}
	}
	// CommonWrapper uses an authored mathsize only on tokens and mstyle.
	// Containers use the inherited layer, including its default prototypes.
	,  := .node.Attributes.Get("mathsize")
	if !.node.Flags.Token && .node.Kind != "mstyle" {
		,  = .node.Attributes.GetInherited("mathsize")
	}
	 := "normal"
	if  {
		 = fmt.Sprint()
		if mml.IsInherit() {
			 = "_inherit_"
		}
	}
	// CommonWrapper.getScale resolves deprecated fontsize before inherited
	// mathsize. An authored mathsize takes precedence over both fontsize and
	// the font-size CSS declaration removed during getStyles().
	 := attribute(.node, "fontsize", "")
	if !truthy() && .cssFontSize != "" {
		 = .cssFontSize
	}
	,  := .node.Attributes.GetExplicit("mathsize")
	if truthy() && !truthy() {
		 = fmt.Sprint()
	}
	if  != "1" {
		 *= layout.Length2Em(, 1, 1, .renderer.pxPerEm)
	}
	 := 1.0
	if .parent != nil {
		 = .parent.bbox.Scale
	}
	.bbox.Scale = 
	.bbox.RScale =  / 
	if .node.Flags.Inferred && .parent != nil {
		.bbox.Scale = 
		.bbox.RScale = 1
	}
}

func ( *wrapper) () {
	if .node.Flags.Inferred || .node.Kind == "text" {
		return
	}
	 := coreMONode(.node)
	if  != nil && (.Attributes.IsSet("lspace") || .Attributes.IsSet("rspace")) {
		// CommonWrapper selects MathML spacing before consulting prevClass.
		// Only the outermost embellished wrapper owns the operator's space.
		if .node.Flags.Embellished && (.node.Parent == nil || !.node.Parent.Flags.Embellished) {
			.getMathMLSpacing()
		}
		return
	}
	if .node.PrevClass == mml.TeXClassNone {
		return
	}
	 := .scriptLevel > 0 || .node.PrevLevel > 0
	if  := mml.TeXSpacing(.node.PrevClass, effectiveTeXClass(.node), );  != "" {
		.bbox.L = layout.Length2Em(, 1, .bbox.Scale, .renderer.pxPerEm)
	}
}

// getMathMLSpacing ports CommonWrapper.getMathMLSpacing. The actual core
// parent matters: an inferred multi-child row counts, but an isolated operator
// or one directly inside a fixed-arity construct receives no outer space.
func ( *wrapper) ( *mml.Node) {
	 := 
	for  := .Parent;  != nil && .Kind != "math" &&
		.Flags.Embellished && coreMONode() == ;  = .Parent {
		 = 
	}
	 := .Parent
	if  == nil || .Kind != "mrow" || len(.Children) <= 1 {
		return
	}
	 := .scriptLevel > 0
	if ,  := numberAttribute(, "scriptlevel");  {
		 =  > 0
	}
	 := func( string,  float64) float64 {
		if .Attributes.IsSet() {
			,  := .Attributes.Get()
			return math.Max(0, layout.Length2Em(, 1, .bbox.Scale, .renderer.pxPerEm))
		}
		if  {
			if  < 2.0/18 {
				return 0
			}
			return 2.0 / 18
		}
		return 
	}
	.bbox.L = ("lspace", .OperatorLspace)
	.bbox.R = ("rspace", .OperatorRspace)
	 := .ChildIndex()
	if  <= 0 || !.Children[-1].Flags.Embellished {
		return
	}
	// Siblings are wrapped in source order; their complete bbox is available
	// even while this wrapper's constructor is still running.
	if .parent != nil && .parent.node ==  &&  <= len(.parent.children) {
		 := .parent.children[-1]
		.bbox.L = math.Max(0, .bbox.L-.getBBox().R)
	}
}

func coreMONode( *mml.Node) *mml.Node {
	for  != nil && .Kind != "mo" && .Flags.Embellished {
		 := .Flags.CoreIndex
		if  < 0 ||  >= len(.Children) {
			return nil
		}
		 = .Children[]
	}
	if  != nil && .Kind == "mo" {
		return 
	}
	return nil
}

func ( *wrapper) () *layout.BBox { return .getBBoxWithSave(true) }

// CommonWrapper.getBBox(false) computes a temporary box for a stretchy
// child that will be resized after the row measures its siblings.
func ( *wrapper) ( bool) *layout.BBox {
	if .bboxComputed {
		return .bbox
	}
	 := .bbox
	if ! {
		 = layout.ZeroBBox()
	}
	.computeBBox()
	.bboxComputed = 
	return 
}

func ( *wrapper) () *layout.BBox { return .styledOuterBBox() }

func ( *wrapper) ( *layout.BBox) {
	switch .node.Kind {
	case "text":
		.computeTextBBox()
	case "mi":
		.computeChildrenBBox()
		.copySkewIC()
	case "mo":
		.computeMoBBox()
	case "mfrac":
		.computeFractionBBox()
	case "msub", "msup", "msubsup":
		.computeScriptsBBox()
	case "munder", "mover", "munderover":
		.computeUnderOverBBox()
	case "msqrt", "mroot":
		.computeRootBBox()
	case "mmultiscripts":
		.computeMultiscriptsBBox()
	case "mfenced":
		.computeFencedBBox()
	case "mglyph":
		.computeGlyphBBox()
	case "maction":
		.computeActionBBox()
	case "semantics":
		.computeSemanticsBBox()
	case "annotation":
		.computeAnnotationBBox()
	case "annotation-xml":
		.computeAnnotationXMLBBox()
	case "XML", "xml":
		.computeXMLBBox()
	case "menclose":
		.computeEncloseBBox()
	case "mtable":
		.computeTableBBox()
	case "mtr", "mlabeledtr":
		.computeRowBBox()
	case "mtd":
		.computeCellBBox()
	case "mpadded":
		.computePaddedBBox()
	case "TeXAtom":
		.computeTeXAtomBBox()
	case "mspace":
		.W = layout.Length2Em(attribute(.node, "width", 0), 0, .Scale, .renderer.pxPerEm)
		.H = layout.Length2Em(attribute(.node, "height", 0), 0, .Scale, .renderer.pxPerEm)
		.D = layout.Length2Em(attribute(.node, "depth", 0), 0, .Scale, .renderer.pxPerEm)
	default:
		.computeChildrenBBox()
	}
}

func ( *wrapper) ( *layout.BBox) {
	.Empty()
	for ,  := range .children {
		.Append(.outerBBox())
	}
	.Clean()
	if .node.Kind == "math" && .parent == nil {
		if .setChildPWidths(80*.renderer.params.XHeight, false) {
			.Empty()
			for ,  := range .children {
				.Append(.outerBBox())
			}
			.Clean()
		}
	}
}

// setChildPWidths follows CommonWrapper's recursive percentage-width pass.
// SVGmath supplies its container width with clear=false. Fixed containers
// supply each child's width with clear=true; CommonMtable clears its own and
// its container markers when it resolves the table.
func ( *wrapper) ( float64,  bool) bool {
	if .node.Kind == "mtable" {
		 := .tableState()
		if !tableIsPercent(tableString("width", )) || .resolved {
			return false
		}
		 := .width
		.resolvePercentageWidth()
		return .pWidth != 
	}
	if  {
		.bbox.PWidth = ""
	}
	 := false
	for ,  := range .children {
		if .outerBBox().PWidth != "" && .(, ) {
			 = true
		}
	}
	return 
}

func ( *wrapper) ( *layout.BBox) {
	.Empty()
	if .parent != nil && (.parent.explicitFont || .parent.variant == font.Variant("-explicitFont")) {
		.H = .75
		.D = .2
		for ,  := range .node.Text {
			if isCJK() {
				.W++
			} else {
				.W += .6
			}
		}
		.Clean()
		return
	}
	 := font.Normal
	if .parent != nil {
		 = .parent.variant
	}
	 := .remappedText()
	for ,  := range  {
		,  := font.Lookup(, )
		if ! {
			if isCJK() {
				.W++
			} else {
				.W += .6
			}
			.H = math.Max(.H, .75)
			.D = math.Max(.D, .2)
			// TextNode assigns the current glyph's corrections even when
			// it is measured as unknown text. Do not retain a prior glyph's.
			.IC, .Skew = 0, 0
			continue
		}
		 := .Metrics
		.W += .Width
		.H = math.Max(.H, .Height)
		.D = math.Max(.D, .Depth)
		if .HasItalicCorrection {
			.IC = .ItalicCorrection
		} else {
			.IC = 0
		}
		if .HasSkew {
			.Skew = .Skew
		} else {
			.Skew = 0
		}
	}
	if utf8.RuneCountInString() > 1 {
		.Skew = 0
	}
	.Clean()
}

func ( *wrapper) ( *layout.BBox) {
	if len(.children) == 0 {
		return
	}
	 := .children[0].getBBox()
	.Skew = .Skew
	.DX = .DX
	 := .children[len(.children)-1].getBBox()
	if .IC != 0 {
		.IC = .IC
		.W += .IC
	}
}

func ( *wrapper) ( *Element) {
	if .node.Kind == "text" {
		.textToSVG()
		return
	}
	if .node.Flags.Inferred {
		// SVGinferredMrow uses its parent as its element.  This matters to
		// callers that place a wrapper after rendering it and also avoids a nil
		// element for parser-produced inferred rows.
		.element = 
		.addChildren()
		return
	}
	if .node.Kind == "mfrac" {
		.fractionToSVG()
		return
	}
	if .node.Kind == "mo" {
		.moToSVG()
		return
	}
	if .node.Kind == "msub" || .node.Kind == "msup" || .node.Kind == "msubsup" {
		.scriptsToSVG()
		return
	}
	if .node.Kind == "munder" || .node.Kind == "mover" || .node.Kind == "munderover" {
		.underOverToSVG()
		return
	}
	if .node.Kind == "msqrt" || .node.Kind == "mroot" {
		.rootToSVG()
		return
	}
	if .node.Kind == "mmultiscripts" {
		.multiscriptsToSVG()
		return
	}
	if .node.Kind == "mfenced" {
		.fencedToSVG()
		return
	}
	if .node.Kind == "mglyph" {
		.glyphToSVG()
		return
	}
	if .node.Kind == "maction" {
		.actionToSVG()
		return
	}
	if .node.Kind == "semantics" {
		.semanticsToSVG()
		return
	}
	if .node.Kind == "annotation" {
		.annotationToSVG()
		return
	}
	if .node.Kind == "annotation-xml" {
		.annotationXMLToSVG()
		return
	}
	if .node.Kind == "XML" || .node.Kind == "xml" {
		.xmlToSVG()
		return
	}
	if .node.Kind == "menclose" {
		.encloseToSVG()
		return
	}
	if .node.Kind == "mtable" {
		.tableToSVG()
		return
	}
	if .node.Kind == "mtr" || .node.Kind == "mlabeledtr" {
		.rowToSVG()
		return
	}
	if .node.Kind == "mtd" {
		.cellToSVG()
		return
	}
	if .node.Kind == "mpadded" {
		.paddedToSVG()
		return
	}
	if .node.Kind == "mphantom" {
		.phantomToSVG()
		return
	}
	if .node.Kind == "TeXAtom" {
		.texAtomToSVG()
		return
	}
	if .node.Kind == "merror" {
		.errorToSVG()
		return
	}
	 := .standardSVG()
	.addChildren()
	if .node.Kind == "math" && attribute(.node, "display", "inline") == "block" {
		.handleMathMinWidth()
	}
}

func ( *wrapper) ( *Element) *Element {
	 := NewElement("g").SetAttr("data-mml-node", .node.Kind)
	.element = 
	 = .linkParent(, )
	.handleStyles()
	if .bbox.RScale != 1 {
		.SetAttr("transform", "scale("+fixed(.bbox.RScale/1000, 3)+")")
	}
	if .explicitFont {
		.SetStyle("font-family", .fontFamily).StylesAfterAttributes()
	}
	.handleBorder()
	.handleColor()
	.handleAttributes()
	.Append()
	return 
}

func ( *wrapper) ( *Element) {
	if .node == nil || .node.Attributes == nil {
		return
	}
	for ,  := range .node.Attributes.ExplicitNames() {
		// SVGWrapper.handleAttributes excludes this node's defaults, rather
		// than every MathML attribute name. An attribute meaningful to one
		// kind (e.g. columnalign) can be passthrough data on another kind.
		if .node.Attributes.HasDefault() {
			continue
		}
		switch  {
		case "fontfamily", "fontsize", "fontweight", "fontstyle", "color", "background",
			"class", "href", "style", "xmlns":
			continue
		// The source defaults and skip map also inherit Object.prototype.
		case "constructor", "__defineGetter__", "__defineSetter__", "hasOwnProperty",
			"__lookupGetter__", "__lookupSetter__", "isPrototypeOf", "propertyIsEnumerable",
			"toString", "valueOf", "__proto__", "toLocaleString":
			continue
		}
		if hasElementAttribute(, ) {
			continue
		}
		,  := .node.Attributes.GetExplicit()
		.SetAttr(, fmt.Sprint())
	}
	if ,  := .node.Attributes.Get("class");  {
		for ,  := range strings.Fields(fmt.Sprint()) {
			addElementClass(, )
		}
	}
}

func addElementClass( *Element,  string) {
	for  := range .Attributes {
		if .Attributes[].Name == "class" {
			.Attributes[].Value += " " + 
			return
		}
	}
	.SetAttr("class", " "+)
}

func hasElementAttribute( *Element,  string) bool {
	for ,  := range .Attributes {
		if .Name ==  {
			return true
		}
	}
	return false
}

func ( *wrapper) ( *Element) {
	 := 0.0
	for ,  := range .children {
		.toSVG()
		 := .outerBBox()
		if .element != nil {
			.place(+.L*.RScale, 0)
		}
		 += (.L + .W + .R) * .RScale
	}
}

func ( *wrapper) ( *Element) {
	if .node.Text == "" {
		return
	}
	if .parent != nil && (.parent.explicitFont || .parent.variant == font.Variant("-explicitFont")) {
		.element = .unknownText(.node.Text, font.Variant("-explicitFont"))
		.Append(.element)
		return
	}
	 := font.Normal
	if .parent != nil {
		 = .parent.variant
	}
	 := .remappedText()
	// SVGTextNode gives each text child a positionable group whenever its
	// parent has multiple children, so addChildren can apply their advances.
	if .parent != nil && len(.parent.children) > 1 {
		.element = NewElement("g").SetAttr("data-mml-node", "text")
		.Append(.element)
		 = .element
	}
	 := 0.0
	for ,  := range  {
		 += .placeChar(, , 0, , )
	}
}

// remappedText is shared by measurement and emission. Explicit fonts are
// handled by each caller; a selected stretch glyph precedes CommonMo's primes
// property, which in turn precedes the ordinary accent/mo character remap.
func ( *wrapper) () string {
	 := .node.Text
	if .parent != nil && .parent.stretchGlyph != 0 {
		return remapAccentText(.parent, string(.parent.stretchGlyph))
	}
	if .parent != nil && .parent.node.Kind == "mo" {
		if ,  := .parent.node.Property("primes");  {
			if ,  := .(string);  &&  != "" {
				return 
			}
		}
	}
	if .parent != nil && .parent.node.Kind == "mn" && strings.HasPrefix(, "-") {
		 = "−" + strings.TrimPrefix(, "-")
	}
	return remapAccentText(.parent, )
}

func ( *wrapper) ( string) bool {
	for  := .parent;  != nil;  = .parent {
		if .node.Kind ==  {
			return true
		}
	}
	return false
}

// isCJK matches NodeMixin.cjkPattern from MathJax 3.2.2.  LiteAdaptor uses
// this to assign deterministic widths when browser text measurement is not
// available.
func isCJK( rune) bool {
	switch {
	case  >= 0x1100 &&  <= 0x115F:
		return true
	case  == 0x2329 ||  == 0x232A:
		return true
	case  >= 0x2E80 &&  <= 0x303E:
		return true
	case  >= 0x3040 &&  <= 0x3247:
		return true
	case  >= 0x3250 &&  <= 0x4DBF:
		return true
	case  >= 0x4E00 &&  <= 0xA4C6:
		return true
	case  >= 0xA960 &&  <= 0xA97C:
		return true
	case  >= 0xAC00 &&  <= 0xD7A3:
		return true
	case  >= 0xF900 &&  <= 0xFAFF:
		return true
	case  >= 0xFE10 &&  <= 0xFE19:
		return true
	case  >= 0xFE30 &&  <= 0xFE6B:
		return true
	case  >= 0xFF01 &&  <= 0xFF60:
		return true
	case  >= 0xFFE0 &&  <= 0xFFE6:
		return true
	case  >= 0x1B000 &&  <= 0x1B001:
		return true
	case  >= 0x1F200 &&  <= 0x1F251:
		return true
	case  >= 0x20000 &&  <= 0x3FFFD:
		return true
	default:
		return false
	}
}

func ( *wrapper) ( rune, ,  float64,  *Element,  font.Variant) float64 {
	 = font.RemapCodepoint(, )
	,  := font.Lookup(, )
	if ! {
		 := .unknownText(string(), )
		.Append()
		.place(, , )
		if isCJK() {
			return 1
		}
		return .6
	}
	if .Content != "" {
		 := NewElement("g").SetAttr("data-c", strings.ToUpper(strconv.FormatInt(int64(.Codepoint), 16)))
		.Append()
		.place(, , )
		 := 0.0
		for ,  := range .Content {
			 += .(, , , , )
		}
		return .Metrics.Width
	}
	 := ""
	if .Path != "" {
		 = "M" + .Path + "Z"
	}
	 := NewElement("path").
		SetAttr("data-c", strings.ToUpper(strconv.FormatInt(int64(.Codepoint), 16))).
		SetAttr("d", )
	.Append()
	.place(, , )
	return .Metrics.Width
}

func ( *wrapper) ( string,  font.Variant) *Element {
	 := .renderer.params.XHeight / .renderer.options.Ex * .renderer.options.Em * 1000
	 := NewElement("text", Text()).
		SetAttr("data-variant", string()).
		SetAttr("transform", "scale(1,-1)").
		SetAttr("font-size", jsFixed(, 1)+"px")
	if  == font.Variant("-explicitFont") {
		return 
	}
	// A mathematical-alphanumeric character already encodes its font style.
	// SVG.unknownText leaves its CSS font attributes unset in this case.
	if ,  := utf8.DecodeRuneInString();  == len() &&  >= 0x1D400 &&  <= 0x1D7FF {
		return 
	}
	, ,  := "serif", false, false
	// CommonTeXFontMixin extends FontData's CSS fallbacks for its internal
	// variants; these are distinct from their glyph inheritance chains.
	switch  {
	case font.Script, font.BoldScript, font.TeXCalligraphic, font.TeXBoldCalligraphic:
		 = "cursive"
	case font.SansSerif, font.BoldSansSerif, font.SansSerifItalic, font.SansSerifBoldItalic:
		 = "sans-serif"
	case font.Monospace:
		 = "monospace"
	}
	switch  {
	case font.Italic, font.BoldItalic, font.SansSerifItalic, font.SansSerifBoldItalic, font.TeXMathItalic, font.TeXCalligraphic, font.TeXBoldCalligraphic:
		 = true
	}
	switch  {
	case font.Bold, font.BoldItalic, font.DoubleStruck, font.BoldFraktur, font.BoldScript, font.BoldSansSerif, font.SansSerifBoldItalic, font.TeXBoldCalligraphic, font.TeXBoldOldStyle:
		 = true
	}
	.SetAttr("font-family", )
	if  {
		.SetAttr("font-style", "italic")
	}
	if  {
		.SetAttr("font-weight", "bold")
	}
	return 
}

func jsFixed( float64,  int) string {
	if math.Abs() < .0006 {
		return "0"
	}
	 := strconv.FormatFloat(, 'f', , 64)
	 = strings.TrimRight(strings.TrimRight(, "0"), ".")
	return 
}

// An omitted or nil element selects the wrapper's own element. An explicit
// element bypasses ID compensation even when it is the same element pointer.
func ( *wrapper) (,  float64,  ...*Element) {
	 += .dx
	if !jscompat.TruthyNumber() && !jscompat.TruthyNumber() {
		return
	}
	var  *Element
	if len() != 0 {
		 = [0]
	}
	if  == nil {
		 = .element
		if  == nil {
			return
		}
		 = .handleID()
	}
	 := fmt.Sprintf("translate(%s,%s)", fixed(), fixed())
	for  := range .Attributes {
		if .Attributes[].Name == "transform" {
			if  := .Attributes[].Value;  != "" {
				 += " " + 
			}
			.Attributes[].Value = 
			return
		}
	}
	.SetAttr("transform", )
}

// SVGWrapper.handleId compensates browser anchor placement without moving
// the rendered content. The original child objects stay in their original order.
func ( *wrapper) ( float64) float64 {
	 := attribute(.node, "id", nil)
	 := truthy()
	if ,  := .(string);  {
		 =  != ""
	}
	if ! {
		return 
	}
	 := .getBBox().H
	 := .element.Children
	.element.Children = nil
	 := NewElement("g", ...).SetAttr("data-idbox", "true").SetAttr("transform", "translate(0,"+fixed(-)+")")
	.element.Append(NewElement("text", Text("")).SetAttr("data-id-align", "true"), )
	return  + 
}

func ( *wrapper) () Node { return elementFirstChild(.element) }

// Match SVGWrapper.firstChild's one-level traversal, including the order in
// which it skips alignment text and a single href hitbox.
func elementFirstChild( *Element) Node {
	if  == nil || len(.Children) == 0 {
		return nil
	}
	 := .Children
	 := [0]
	if ,  := .(*Element);  && .Tag == "text" && elementAttributeTruthy(, "data-id-align") {
		if len() < 2 {
			return nil
		}
		,  := [1].(*Element)
		if ! || len(.Children) == 0 {
			return nil
		}
		 = .Children
		 = [0]
	}
	if ,  := .(*Element);  && .Tag == "rect" && elementAttributeTruthy(, "data-hitbox") {
		if len() < 2 {
			return nil
		}
		 = [1]
	}
	return 
}

func elementAttributeTruthy( *Element,  string) bool {
	for ,  := range .Attributes {
		if .Name ==  {
			return .Value != ""
		}
	}
	return false
}

func ( *wrapper) ( *Element) {
	 := colorAttributeExplicit(.node, "mathcolor")
	if  == "" {
		 = colorAttributeExplicit(.node, "color")
	}
	if  != "" {
		.SetAttr("fill", ).SetAttr("stroke", )
	}
	 := colorAttributeExplicit(.node, "mathbackground")
	if  == "" {
		 = colorAttributeExplicit(.node, "background")
	}
	if  == "" && .styles != nil {
		 = .styles.value("background-color")
	}
	if  != "" &&  != "transparent" {
		 := .outerBBox()
		 := NewElement("rect").
			SetAttr("fill", ).
			SetAttr("x", fixed(-.dx)).
			SetAttr("y", fixed(-.D)).
			SetAttr("width", fixed(.W)).
			SetAttr("height", fixed(.H+.D)).
			SetAttr("data-bgcolor", "true")
		.Prepend()
	}
}

// handleColor tests the original attribute value before string coercion.
// Boolean false must fall through to another explicit color or a style, while
// strings such as "false" and "0" remain truthy JavaScript strings.
func colorAttributeExplicit( *mml.Node,  string) string {
	if  == nil || .Attributes == nil {
		return ""
	}
	,  := .Attributes.GetExplicit()
	if ! {
		return ""
	}
	if ,  := .(string);  {
		return 
	}
	if !truthy() {
		return ""
	}
	return fmt.Sprint()
}

func nodeText( *mml.Node) string {
	if  == nil {
		return ""
	}
	if .Kind == "text" {
		return .Text
	}
	var  strings.Builder
	for ,  := range .Children {
		.WriteString(())
	}
	return .String()
}

func attribute( *mml.Node,  string,  any) any {
	if  != nil && .Attributes != nil {
		if ,  := .Attributes.Get();  {
			return 
		}
	}
	return 
}

func stringAttribute( *mml.Node, ,  string) string {
	 := attribute(, , )
	if ,  := .(string);  {
		return 
	}
	return fmt.Sprint()
}

func stringAttributeExplicit( *mml.Node,  string) string {
	if  == nil || .Attributes == nil {
		return ""
	}
	,  := .Attributes.GetExplicit()
	if ! ||  == nil {
		return ""
	}
	return fmt.Sprint()
}

func boolAttribute( *mml.Node,  string) (bool, bool) {
	if  == nil || .Attributes == nil {
		return false, false
	}
	,  := .Attributes.Get()
	if ! {
		return false, false
	}
	return truthy(), true
}

func boolAttributeDefault( *mml.Node,  string,  bool) bool {
	,  := boolAttribute(, )
	if ! {
		return 
	}
	return 
}

func truthy( any) bool {
	switch x := .(type) {
	case bool:
		return 
	case string:
		return  != "" &&  != "false" &&  != "0"
	case int:
		return  != 0
	case int64:
		return  != 0
	case float64:
		return  != 0 && !math.IsNaN()
	case nil:
		return false
	default:
		return true
	}
}

func numberAttribute( *mml.Node,  string) (float64, bool) {
	if  == nil || .Attributes == nil {
		return 0, false
	}
	,  := .Attributes.Get()
	if ! {
		return 0, false
	}
	switch x := .(type) {
	case int:
		return float64(), true
	case int64:
		return float64(), true
	case float64:
		return , true
	case string:
		,  := strconv.ParseFloat(, 64)
		return ,  == nil
	default:
		return 0, false
	}
}