// 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 (
	
	
	

	
)

const borderFuzz = .005

var cssCommentPattern = regexp.MustCompile(`(?s)/\*.*?\*/`)
var cssStyleNamePattern = regexp.MustCompile(`^[-a-z]+$`)
var cssBorderWidthPattern = regexp.MustCompile(`^(?:[\d.]+[a-z]+|thin|medium|thick|inherit|initial|unset)$`)

type cssDeclaration struct {
	name  string
	value string
}

type borderStyle struct {
	raw   string
	width string
	style string
	color string
	set   bool
}

type wrapperStyles struct {
	border  [4]borderStyle
	padding [4]string
	padSet  [4]bool
	other   []cssDeclaration
}

var sideNames = [...]string{"top", "right", "bottom", "left"}

func ( *wrapper) () {
	 := stringAttributeExplicit(.node, "style")
	if  == "" {
		return
	}
	.styles = parseWrapperStyles()
	// The primary wrapper removes CSS font-size before rendering paths or
	// fallback text; getScale applies the value to the wrapper's geometry.
	 := .styles.other[:0]
	for ,  := range .styles.other {
		switch .name {
		case "font-size":
			.cssFontSize = .value
		case "font-weight":
			.cssFontWeight = .value
		case "font-style":
			.cssFontStyle = .value
		default:
			 = append(, )
		}
	}
	.styles.other = 
}

func parseWrapperStyles( string) *wrapperStyles {
	 := &wrapperStyles{}
	 := splitCSSDeclarations(cssCommentPattern.ReplaceAllString(, ""))
	for ,  := range  {
		, ,  := strings.Cut(, ":")
		if ! {
			// Styles.parse stops when unmatched text before the next property
			// includes more than JavaScript whitespace or another separator.
			if strings.TrimFunc(, cssValueSpace) != "" ||  < len()-1 {
				return 
			}
			continue
		}
		 = strings.TrimFunc(, cssValueSpace)
		// Styles.pattern captures only lowercase property names. A preceding
		// non-whitespace character makes Styles.parse stop at that declaration.
		if !cssStyleNamePattern.MatchString() {
			return 
		}
		// Styles.pattern removes JavaScript whitespace around authored values.
		 = strings.TrimFunc(, cssValueSpace)
		if .setBorderComponent(, ) {
			continue
		}
		switch  {
		case "border", "border-top", "border-right", "border-bottom", "border-left",
			"border-width", "border-style", "border-color":
			// Styles.set also splits an empty border declaration so that its
			// previously authored side or component values are removed.
		default:
			if  == "" {
				continue
			}
		}
		switch  {
		case "border":
			 := splitBorder()
			for  := range .border {
				.border[] = 
			}
		case "border-top", "border-right", "border-bottom", "border-left":
			.border[sideIndex(strings.TrimPrefix(, "border-"))] = splitBorder()
		case "border-width", "border-style", "border-color":
			 := splitTRBL()
			for  := range .border {
				.border[].set = true
				.border[].raw = ""
				switch strings.TrimPrefix(, "border-") {
				case "width":
					.border[].width = []
				case "style":
					.border[].style = []
				case "color":
					.border[].color = []
				}
			}
		case "padding":
			 := splitTRBL()
			for  := range .padding {
				.padding[], .padSet[] = [], true
			}
		case "padding-top", "padding-right", "padding-bottom", "padding-left":
			 := sideIndex(strings.TrimPrefix(, "padding-"))
			.padding[], .padSet[] = , true
		default:
			.setOther(, )
		}
	}
	return 
}

// Styles.set recombines a side's width/style/color after a component changes,
// including an empty value that removes a previously authored component.
func ( *wrapperStyles) (,  string) bool {
	 := strings.Split(, "-")
	if len() != 3 || [0] != "border" {
		return false
	}
	 := -1
	for ,  := range sideNames {
		if [1] ==  {
			 = 
			break
		}
	}
	if  < 0 {
		return false
	}
	 := &.border[]
	switch [2] {
	case "width":
		.width = 
	case "style":
		.style = 
	case "color":
		.color = 
	default:
		return false
	}
	.raw = ""
	.set = .cssValue() != ""
	return true
}

func splitCSSDeclarations( string) []string {
	var  []string
	 := 0
	 := rune(0)
	for ,  := range  {
		switch {
		case  != 0 &&  == :
			 = 0
		case  == 0 && ( == '\'' ||  == '"'):
			 = 
		case  == 0 &&  == ';':
			 = append(, [:])
			 =  + 1
		}
	}
	 = append(, [:])
	return 
}

func splitBorder( string) borderStyle {
	// Styles.splitWSC retains the authored shorthand until an individual
	// width/style/color component is changed and combineWSC rebuilds it.
	 := borderStyle{set:  != "", raw: }
	for ,  := range splitCSSValue() {
		switch {
		case isBorderWidth() && .width == "":
			.width = 
		case isBorderStyle() && .style == "":
			.style = 
		default:
			.color = 
		}
	}
	return 
}

func isBorderWidth( string) bool {
	return cssBorderWidthPattern.MatchString()
}

func isBorderStyle( string) bool {
	switch  {
	case "none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset", "inherit", "initial", "unset":
		return true
	default:
		return false
	}
}

func splitTRBL( string) [4]string {
	 := splitCSSValue()
	if len() == 0 {
		return [4]string{}
	}
	for len() < 4 {
		switch len() {
		case 1:
			 = append(, [0])
		case 2:
			 = append(, [0])
		case 3:
			 = append(, [1])
		}
	}
	return [4]string{[0], [1], [2], [3]}
}

// Styles.splitSpaces keeps quoted strings and comma-followed whitespace in
// one part, so rgb()/hsl() colours remain intact in border shorthands. Repeated
// separating whitespace produces empty parts, as in the source regular expression.
func splitCSSValue( string) []string {
	 := []rune()
	var  []string
	 := 0
	for  := 0;  < len(); ++ {
		 := []
		if  == '\'' ||  == '"' {
			for  :=  + 1;  < len(); ++ {
				if [] ==  {
					 = 
					break
				}
			}
			continue
		}
		if  == ',' && +1 < len() && cssValueSpace([+1]) {
			++
			continue
		}
		if cssValueSpace() {
			// The source's zero-length regexp match at the beginning of the
			// string is suppressed by String.split. Later empty fields remain.
			if  != 0 {
				 = append(, string([:]))
			}
			 =  + 1
		}
	}
	if  < len() {
		 = append(, string([:]))
	}
	return 
}

func cssValueSpace( rune) bool {
	return  >= '\t' &&  <= '\r' ||  == ' ' ||  == '\u00a0' ||  == '\u1680' ||
		 >= '\u2000' &&  <= '\u200a' ||  == '\u2028' ||  == '\u2029' ||
		 == '\u202f' ||  == '\u205f' ||  == '\u3000' ||  == '\ufeff'
}

func sideIndex( string) int {
	for ,  := range sideNames {
		if  ==  {
			return 
		}
	}
	return 0
}

func ( *wrapperStyles) (,  string) {
	for  := range .other {
		if .other[].name ==  {
			.other[].value = 
			return
		}
	}
	.other = append(.other, cssDeclaration{name: , value: })
}

func ( *wrapperStyles) ( string) string {
	for ,  := range .other {
		if .name ==  {
			return .value
		}
	}
	return ""
}

func ( *wrapperStyles) () string {
	 := make([]cssDeclaration, 0, 8+len(.other))
	if .allBordersEqual() {
		 = append(, cssDeclaration{name: "border", value: .border[0].cssValue()})
	} else {
		for ,  := range .border {
			if .set {
				 = append(, cssDeclaration{name: "border-" + sideNames[], value: .cssValue()})
			}
		}
	}
	if .allPaddingEqual() {
		 = append(, cssDeclaration{name: "padding", value: .padding[0]})
	} else {
		for ,  := range .padSet {
			if  {
				 = append(, cssDeclaration{name: "padding-" + sideNames[], value: .padding[]})
			}
		}
	}
	 = append(, .other...)
	var  strings.Builder
	for ,  := range  {
		if .value == "" {
			continue
		}
		.WriteString(.name)
		.WriteString(": ")
		.WriteString(.value)
		.WriteByte(';')
		.WriteByte(' ')
	}
	return strings.TrimSuffix(.String(), " ")
}

func ( *wrapperStyles) () bool {
	if !.border[0].set {
		return false
	}
	for  := 1;  < len(.border); ++ {
		if .border[] != .border[0] {
			return false
		}
	}
	return true
}

func ( *wrapperStyles) () bool {
	if !.padSet[0] {
		return false
	}
	for  := 1;  < len(.padding); ++ {
		if !.padSet[] || .padding[] != .padding[0] {
			return false
		}
	}
	return true
}

func ( borderStyle) () string {
	if .raw != "" {
		return .raw
	}
	 := make([]string, 0, 3)
	for ,  := range []string{.width, .style, .color} {
		if  != "" {
			 = append(, )
		}
	}
	return strings.Join(, " ")
}

func ( *wrapper) ( string,  float64) float64 {
	if  == "" {
		return 0
	}
	// CommonWrapper uses one em as the relative size for percentages and
	// retains signed padding in both the outer box and content offset.
	return layout.Length2Em(, 1, , .renderer.pxPerEm)
}

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

func ( *wrapper) ( bool) *layout.BBox {
	 := .getBBoxWithSave()
	if .styles == nil {
		return 
	}
	 := .Clone()
	// Preserve BBox.StyleAdjust's sequence: all border widths, then padding.
	// Combining terms first changes rounding in serialized dimensions.
	.H += .styleLength(.styles.border[0].width, .RScale)
	.W += .styleLength(.styles.border[1].width, .RScale)
	.D += .styleLength(.styles.border[2].width, .RScale)
	.W += .styleLength(.styles.border[3].width, .RScale)
	.H += .styleLength(.styles.padding[0], .RScale)
	.W += .styleLength(.styles.padding[1], .RScale)
	.D += .styleLength(.styles.padding[2], .RScale)
	.W += .styleLength(.styles.padding[3], .RScale)
	return 
}

func ( *wrapper) ( *Element) {
	if .styles == nil {
		return
	}
	if  := .styles.cssText();  != "" {
		.SetAttr("style", )
	}
	.dx += .styleLength(.styles.border[3].width, .bbox.RScale)
	.dx += .styleLength(.styles.padding[3], .bbox.RScale)
}

func ( *wrapper) ( *Element) {
	if .styles == nil {
		return
	}
	var  [4]float64
	for ,  := range .styles.border {
		if .width != "" {
			// SVGWrapper clamps the drawn border independently of box sizing.
			// Its painter uses every authored width; only dotted/dashed styles
			// select a special path, while other styles use a solid polygon.
			[] = math.Max(0, .styleLength(.width, .bbox.RScale))
		}
	}
	 := .outerBBox()
	, ,  := .H+borderFuzz, .D+borderFuzz, .W+borderFuzz
	,  := [2]float64{, }, [2]float64{-borderFuzz, }
	,  := [2]float64{, -}, [2]float64{-borderFuzz, -}
	 := [2]float64{ - [1],  - [0]}
	 := [2]float64{-borderFuzz + [3],  - [0]}
	 := [2]float64{ - [1], - + [2]}
	 := [2]float64{-borderFuzz + [3], - + [2]}
	 := [4][4][2]float64{
		{, , , },
		{, , , },
		{, , , },
		{, , , },
	}
	var  Node
	if len(.Children) != 0 {
		 = .Children[0]
	}
	for ,  := range  {
		if  == 0 {
			continue
		}
		 := .styles.border[]
		 := .color
		if  == "" {
			 = "currentColor"
		}
		if .style == "dashed" || .style == "dotted" {
			.addBrokenBorder(, [], , .style, , )
		} else {
			 := NewElement("polygon").
				SetAttr("points", .borderPoints([])).
				SetAttr("stroke", "none").
				SetAttr("fill", )
			insertBeforeNode(, , )
		}
	}
}

func ( *wrapper) ( [4][2]float64) string {
	 := make([]string, len())
	for ,  := range  {
		[] = fixed([0]-.dx) + "," + fixed([1])
	}
	return strings.Join(, " ")
}

func insertBeforeNode( *Element,  Node,  Node) {
	if  == nil {
		.Append()
		return
	}
	for ,  := range .Children {
		if  !=  {
			continue
		}
		.Children = append(.Children, nil)
		copy(.Children[+1:], .Children[:])
		.Children[] = 
		return
	}
	.Append()
}

func ( *wrapper) ( *Element,  [4][2]float64, ,  string,  float64,  int) {
	 :=  / 2
	 := [4][4]float64{{, -, -, -}, {-, , -, -}, {, , -, }, {, , , -}}
	 := []
	 := [0][0] + [0] - .dx
	 := [0][1] + [1]
	 := [1][0] + [2] - .dx
	 := [1][1] + [3]
	 := math.Abs( - )
	if %2 == 1 {
		 = math.Abs( - )
	}
	 :=  == "dotted"
	 := math.Ceil(( - ) / (4 * ))
	if  {
		 = math.Ceil( / (2 * ))
	}
	// SVGWrapper permits zero when a dashed segment is shorter than its
	// thickness; the resulting unit is the whole segment length.
	 := ""
	 := "square"
	if  {
		 = "round"
		 = "1 " + fixed(/-.002)
	} else {
		 :=  / (4* + 1)
		 = fixed() + " " + fixed(3*)
	}
	 := NewElement("line").
		SetAttr("x1", fixed()).
		SetAttr("y1", fixed()).
		SetAttr("x2", fixed()).
		SetAttr("y2", fixed()).
		SetAttr("stroke-width", fixed()).
		SetAttr("stroke", ).
		SetAttr("stroke-linecap", ).
		SetAttr("stroke-dasharray", )
	var  Node
	if len(.Children) != 0 {
		 = .Children[0]
	}
	insertBeforeNode(, , )
}