package d2svgimport

import (
	
	
	
	
	
	
	

	

	
)

var presentationProperties = map[string]struct{}{
	"fill": {}, "stroke": {}, "opacity": {}, "fill-opacity": {}, "stroke-opacity": {},
	"stroke-width": {}, "stroke-linecap": {}, "stroke-linejoin": {}, "stroke-miterlimit": {},
	"stroke-dasharray": {}, "stroke-dashoffset": {}, "fill-rule": {}, "clip-rule": {}, "clip-path": {}, "color": {},
	"display": {}, "visibility": {}, "transform": {}, "stop-color": {}, "stop-opacity": {},
}

type svgStyle struct {
	fill          d2scene.Paint
	stroke        d2scene.Paint
	fillCurrent   bool
	strokeCurrent bool
	color         color.NRGBA
	fillOpacity   float64
	strokeOpacity float64
	strokeWidth   float64
	lineCap       d2scene.LineCap
	lineJoin      d2scene.LineJoin
	miterLimit    float64
	dashes        []float64
	dashOffset    float64
	fillRule      d2scene.FillRule
	clipRule      d2scene.FillRule
	clipPathID    string
	opacity       float64
	display       bool
	visible       bool
}

func defaultSVGStyle() svgStyle {
	return defaultSVGStyleWithColor(color.NRGBA{A: 255})
}

func defaultSVGStyleWithColor( color.NRGBA) svgStyle {
	 := color.NRGBA{A: 255}
	return svgStyle{
		fill: d2scene.SolidPaint{Color: }, color: ,
		fillOpacity: 1, strokeOpacity: 1, strokeWidth: 1,
		lineCap: d2scene.CapButt, lineJoin: d2scene.JoinMiter, miterLimit: 4,
		fillRule: d2scene.NonZero, clipRule: d2scene.NonZero, opacity: 1, display: true, visible: true,
	}
}

// declarationsFor applies presentation attributes before an inline style, as
// required by SVG's author-style cascade. It rejects every unimplemented CSS
// property instead of silently changing the imported image.
func ( *svgImporter) ( *svgElement) (map[string]string, error) {
	 := make(map[string]string)
	for  := range presentationProperties {
		if ,  := .attrs[];  {
			,  := trimSVGSpace(.ctx, )
			if  != nil {
				return nil, 
			}
			[] = 
		}
	}
	for ,  := range .stylesheetRules {
		if &255 == 0 {
			if  := .ctx.Err();  != nil {
				return nil, 
			}
		}
		if !elementHasClass(, .class) {
			continue
		}
		for ,  := range .declarations {
			[] = 
		}
	}
	,  := .attrs["style"]
	if ! {
		return , nil
	}
	for  := 0;  < len(); ++ {
		if &4095 == 0 {
			if  := .ctx.Err();  != nil {
				return nil, 
			}
		}
		if [] == '{' || [] == '}' ||
			+1 < len() && ([:+2] == "/*" || [:+2] == "*/") {
			return nil, .errorf("element <%s> has unsupported inline CSS syntax", .name)
		}
	}
	for  := 0;  <= len(); {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		 := 
		for  < len() && [] != ';' {
			++
			if &4095 == 0 {
				if  := .ctx.Err();  != nil {
					return nil, 
				}
			}
		}
		,  := trimSVGSpace(.ctx, [:])
		if  != nil {
			return nil, 
		}
		if  == len() {
			 = len() + 1
		} else {
			 =  + 1
		}
		if  == "" {
			continue
		}
		 := -1
		for  := 0;  < len(); ++ {
			if &4095 == 0 {
				if  := .ctx.Err();  != nil {
					return nil, 
				}
			}
			if [] == ':' {
				 = 
				break
			}
		}
		if  <= 0 {
			return nil, .errorf("element <%s> has malformed inline style", .name)
		}
		,  := trimSVGSpace(.ctx, [:])
		if  != nil {
			return nil, 
		}
		,  := trimSVGSpace(.ctx, [+1:])
		if  != nil {
			return nil, 
		}
		if len() > 64 {
			return nil, .errorf("element <%s> has malformed inline style", .name)
		}
		 = strings.ToLower()
		if !validCSSPropertyName() ||  == "" {
			return nil, .errorf("element <%s> has malformed inline style", .name)
		}
		if  == "enable-background" {
			if !.isRoot {
				return nil, .errorf("element <%s> has unsupported enable-background outside the root <svg>", .name)
			}
			if  := validateEnableBackground(.ctx, );  != nil {
				if  := .ctx.Err();  != nil {
					return nil, 
				}
				return nil, .errorf("root <svg> has invalid enable-background: %v", )
			}
			continue
		}
		if  == "vertical-align" {
			if !.isRoot {
				return nil, .errorf("element <%s> has unsupported vertical-align outside the root <svg>", .name)
			}
			if ,  := parseMathJaxEXLength();  != nil {
				return nil, .errorf("root <svg> has invalid MathJax vertical-align: %v", )
			}
			.mathJaxRoot = 
			continue
		}
		if ,  := presentationProperties[]; ! {
			return nil, .errorf("element <%s> has unsupported style property %q", .name, )
		}
		if  == "transform" {
			return nil, .errorf("element <%s> has unsupported CSS transform; use the transform presentation attribute", .name)
		}
		,  := containsASCIIEqualFold(.ctx, , "!important")
		if  != nil {
			return nil, 
		}
		if  {
			return nil, .errorf("element <%s> uses unsupported !important", .name)
		}
		[] = 
	}
	return , nil
}

func trimSVGSpace( context.Context,  string) (string, error) {
	 := 0
	for  < len() && isSVGListSpace([]) {
		++
		if &4095 == 0 {
			if  := .Err();  != nil {
				return "", 
			}
		}
	}
	 := len()
	for  >  && isSVGListSpace([-1]) {
		--
		if (len()-)&4095 == 0 {
			if  := .Err();  != nil {
				return "", 
			}
		}
	}
	if  := .Err();  != nil {
		return "", 
	}
	return [:], nil
}

func containsASCIIEqualFold( context.Context, ,  string) (bool, error) {
	if len() == 0 {
		return true, nil
	}
	for  := 0; +len() <= len(); ++ {
		if &4095 == 0 {
			if  := .Err();  != nil {
				return false, 
			}
		}
		 := true
		for  := range  {
			 := [+]
			 := []
			if  >= 'A' &&  <= 'Z' {
				 += 'a' - 'A'
			}
			if  >= 'A' &&  <= 'Z' {
				 += 'a' - 'A'
			}
			if  !=  {
				 = false
				break
			}
		}
		if  {
			return true, nil
		}
	}
	return false, .Err()
}

func validCSSPropertyName( string) bool {
	if  == "" {
		return false
	}
	for ,  := range  {
		if !( >= 'a' &&  <= 'z') &&  != '-' {
			return false
		}
	}
	return true
}

func ( *svgImporter) ( svgStyle,  *svgElement) (svgStyle, error) {
	 := 
	// Opacity and display do not inherit in SVG. Visibility and all of the
	// paint/stroke properties represented here do. clip-path is also reset:
	// unlike clip-rule, it is not inherited.
	.opacity = 1
	.display = true
	.clipPathID = ""

	 := .declarations
	if ,  := ["color"];  {
		if equalASCIIEqualFold(, "inherit") || equalASCIIEqualFold(, "currentColor") {
			.color = .color
		} else {
			,  := parseSolidColor()
			if  != nil {
				return svgStyle{}, .propertyError(, "color", )
			}
			.color = 
		}
	}

	if ,  := ["fill"];  {
		if equalASCIIEqualFold(, "inherit") {
			.fill = .fill
			.fillCurrent = .fillCurrent
		} else {
			, ,  := .parseSVGPaint()
			if  != nil {
				return svgStyle{}, .propertyError(, "fill", )
			}
			.fill = 
			.fillCurrent = 
		}
	}
	if ,  := ["stroke"];  {
		if equalASCIIEqualFold(, "inherit") {
			.stroke = .stroke
			.strokeCurrent = .strokeCurrent
		} else {
			, ,  := .parseSVGPaint()
			if  != nil {
				return svgStyle{}, .propertyError(, "stroke", )
			}
			.stroke = 
			.strokeCurrent = 
		}
	}

	var  error
	if .fillOpacity,  = .inheritedOpacity(, , "fill-opacity", .fillOpacity);  != nil {
		return svgStyle{}, 
	}
	if .strokeOpacity,  = .inheritedOpacity(, , "stroke-opacity", .strokeOpacity);  != nil {
		return svgStyle{}, 
	}
	if ,  := ["stroke-width"];  {
		if equalASCIIEqualFold(, "inherit") {
			.strokeWidth = .strokeWidth
		} else {
			.strokeWidth,  = parseSVGLength(, true)
			if  != nil || .strokeWidth < 0 {
				if  == nil {
					 = fmt.Errorf("must not be negative")
				}
				return svgStyle{}, .propertyError(, "stroke-width", )
			}
		}
	}
	if ,  := ["stroke-linecap"];  {
		,  := styleKeyword()
		if  != nil {
			return svgStyle{}, .propertyError(, "stroke-linecap", )
		}
		switch  {
		case "inherit":
			.lineCap = .lineCap
		case "butt":
			.lineCap = d2scene.CapButt
		case "round":
			.lineCap = d2scene.CapRound
		case "square":
			.lineCap = d2scene.CapSquare
		default:
			return svgStyle{}, .propertyError(, "stroke-linecap", fmt.Errorf("unsupported value"))
		}
	}
	if ,  := ["stroke-linejoin"];  {
		,  := styleKeyword()
		if  != nil {
			return svgStyle{}, .propertyError(, "stroke-linejoin", )
		}
		switch  {
		case "inherit":
			.lineJoin = .lineJoin
		case "miter":
			.lineJoin = d2scene.JoinMiter
		case "round":
			.lineJoin = d2scene.JoinRound
		case "bevel":
			.lineJoin = d2scene.JoinBevel
		default:
			return svgStyle{}, .propertyError(, "stroke-linejoin", fmt.Errorf("unsupported value"))
		}
	}
	if ,  := ["stroke-miterlimit"];  {
		if equalASCIIEqualFold(, "inherit") {
			.miterLimit = .miterLimit
		} else {
			.miterLimit,  = parseSVGNumber()
			if  != nil || .miterLimit < 1 {
				if  == nil {
					 = fmt.Errorf("must be at least 1")
				}
				return svgStyle{}, .propertyError(, "stroke-miterlimit", )
			}
		}
	}
	if ,  := ["stroke-dasharray"];  {
		if equalASCIIEqualFold(, "inherit") {
			.dashes = append([]float64(nil), .dashes...)
		} else if equalASCIIEqualFold(, "none") {
			.dashes = nil
		} else {
			.dashes,  = parseDashArray(.ctx, )
			if  != nil {
				if  := .ctx.Err();  != nil {
					return svgStyle{}, 
				}
				return svgStyle{}, .propertyError(, "stroke-dasharray", )
			}
		}
	}
	if ,  := ["stroke-dashoffset"];  {
		if equalASCIIEqualFold(, "inherit") {
			.dashOffset = .dashOffset
		} else {
			.dashOffset,  = parseSVGLength(, true)
			if  != nil {
				return svgStyle{}, .propertyError(, "stroke-dashoffset", )
			}
		}
	}
	if ,  := ["fill-rule"];  {
		,  := styleKeyword()
		if  != nil {
			return svgStyle{}, .propertyError(, "fill-rule", )
		}
		switch  {
		case "inherit":
			.fillRule = .fillRule
		case "nonzero":
			.fillRule = d2scene.NonZero
		case "evenodd":
			.fillRule = d2scene.EvenOdd
		default:
			return svgStyle{}, .propertyError(, "fill-rule", fmt.Errorf("unsupported value"))
		}
	}
	if ,  := ["clip-rule"];  {
		,  := styleKeyword()
		if  != nil {
			return svgStyle{}, .propertyError(, "clip-rule", )
		}
		switch  {
		case "inherit":
			.clipRule = .clipRule
		case "nonzero":
			.clipRule = d2scene.NonZero
		case "evenodd":
			.clipRule = d2scene.EvenOdd
		default:
			return svgStyle{}, .propertyError(, "clip-rule", fmt.Errorf("unsupported value"))
		}
	}
	if ,  := ["clip-path"];  {
		if equalASCIIEqualFold(, "none") {
			.clipPathID = ""
		} else {
			.clipPathID,  = .localClipPathID()
			if  != nil {
				return svgStyle{}, .propertyError(, "clip-path", )
			}
		}
	}
	if ,  := ["opacity"];  {
		if equalASCIIEqualFold(, "inherit") {
			.opacity = .opacity
		} else {
			.opacity,  = parseUnitInterval()
			if  != nil {
				return svgStyle{}, .propertyError(, "opacity", )
			}
		}
	}
	if ,  := ["display"];  {
		,  := styleKeyword()
		if  != nil {
			return svgStyle{}, .propertyError(, "display", )
		}
		switch  {
		case "inherit":
			.display = .display
		case "none":
			.display = false
		case "inline", "block":
			.display = true
		default:
			return svgStyle{}, .propertyError(, "display", fmt.Errorf("unsupported value"))
		}
	}
	if ,  := ["visibility"];  {
		,  := styleKeyword()
		if  != nil {
			return svgStyle{}, .propertyError(, "visibility", )
		}
		switch  {
		case "inherit":
			.visible = .visible
		case "visible":
			.visible = true
		case "hidden", "collapse":
			.visible = false
		default:
			return svgStyle{}, .propertyError(, "visibility", fmt.Errorf("unsupported value"))
		}
	}
	return , nil
}

func styleKeyword( string) (string, error) {
	if len() > 64 {
		return "", fmt.Errorf("keyword is too long")
	}
	return strings.ToLower(), nil
}

func ( *svgImporter) ( *svgElement,  map[string]string,  string,  float64) (float64, error) {
	,  := []
	if ! || equalASCIIEqualFold(, "inherit") {
		return , nil
	}
	,  := parseUnitInterval()
	if  != nil {
		return 0, .propertyError(, , )
	}
	return , nil
}

func ( *svgImporter) ( *svgElement,  string,  error) error {
	return .errorf("element <%s> has invalid %s: %v", .name, , )
}

func ( *svgImporter) ( string) (d2scene.Paint, bool, error) {
	if len() > 4096 {
		return nil, false, fmt.Errorf("paint value is too long")
	}
	 := strings.TrimSpace()
	switch {
	case equalASCIIEqualFold(, "none"):
		return nil, false, nil
	case equalASCIIEqualFold(, "currentColor"):
		return nil, true, nil
	case strings.Contains(strings.ToLower(), "url("):
		,  := .localLinearGradientPaint()
		return , false, 
	}
	,  := parseSolidColor()
	if  != nil {
		return nil, false, 
	}
	return d2scene.SolidPaint{Color: }, false, nil
}

func parseSolidColor( string) (color.NRGBA, error) {
	if len() > 4096 {
		return color.NRGBA{}, fmt.Errorf("solid color is too long")
	}
	,  := csscolorparser.Parse(strings.TrimSpace())
	if  != nil {
		return color.NRGBA{}, fmt.Errorf("unsupported solid color")
	}
	, , ,  := .RGBA255()
	return color.NRGBA{R: , G: , B: , A: }, nil
}

func parseUnitInterval( string) (float64, error) {
	if len() > 512 {
		return 0, fmt.Errorf("number is too long")
	}
	if strings.Contains(, "%") {
		return 0, fmt.Errorf("percentages are unsupported")
	}
	,  := parseSVGNumber()
	if  != nil {
		return 0, 
	}
	if  < 0 ||  > 1 {
		return 0, fmt.Errorf("must be in [0,1]")
	}
	return , nil
}

func parseSVGLength( string,  bool) (float64, error) {
	if len() > 512 {
		return 0, fmt.Errorf("number is too long")
	}
	 := strings.TrimSpace()
	if strings.Contains(, "%") {
		return 0, fmt.Errorf("percentages are unsupported")
	}
	 := 1.0
	 := 0
	if  {
		 := []struct {
			 string
			  float64
		}{
			{: "px", : 1},
			{: "in", : 96},
			{: "cm", : 96 / 2.54},
			{: "mm", : 96 / 25.4},
			{: "pt", : 96.0 / 72.0},
			{: "pc", : 16},
			{: "q", : 96 / 101.6},
		}
		for ,  := range  {
			if len() >= len(.) && equalASCIIEqualFold([len()-len(.):], .) {
				 = len(.)
				 = .
				break
			}
		}
	}
	if  != 0 {
		 := [:len()-]
		if strings.TrimSpace() !=  {
			return 0, fmt.Errorf("units must immediately follow their number")
		}
		 = 
	}
	,  := parseSVGNumber()
	if  != nil {
		return 0, 
	}
	 :=  * 
	if math.IsNaN() || math.IsInf(, 0) {
		return 0, fmt.Errorf("absolute length must be finite")
	}
	return , nil
}

const mathJaxEXPixels = 8.0

// parseMathJaxEXLength implements the frozen mathjax-go contract used by
// d2latex.Measure: MathJax emits root dimensions and baseline offsets in ex,
// and its measurement adapter treats one ex as eight CSS pixels. It is kept
// separate from parseSVGLength so relative ex units remain rejected everywhere
// else in the imported SVG subset.
func parseMathJaxEXLength( string) (float64, error) {
	if len() > 512 {
		return 0, fmt.Errorf("number is too long")
	}
	 := strings.TrimSpace()
	if len() <= len("ex") || !equalASCIIEqualFold([len()-len("ex"):], "ex") {
		return 0, fmt.Errorf("expected a finite ex length")
	}
	 := [:len()-len("ex")]
	if strings.TrimSpace() !=  {
		return 0, fmt.Errorf("units must immediately follow their number")
	}
	,  := parseSVGNumber()
	if  != nil {
		return 0, fmt.Errorf("expected a finite ex length")
	}
	 :=  * mathJaxEXPixels
	if math.IsNaN() || math.IsInf(, 0) {
		return 0, fmt.Errorf("ex length must be finite")
	}
	return , nil
}

func parseSVGNumber( string) (float64, error) {
	if len() > 512 {
		return 0, fmt.Errorf("number is too long")
	}
	 := strings.TrimSpace()
	if  == "" {
		return 0, fmt.Errorf("empty number")
	}
	 := 0
	if [] == '+' || [] == '-' {
		++
	}
	 := 0
	for  < len() && [] >= '0' && [] <= '9' {
		++
		++
	}
	if  < len() && [] == '.' {
		++
		for  < len() && [] >= '0' && [] <= '9' {
			++
			++
		}
	}
	if  == 0 {
		return 0, fmt.Errorf("expected a finite unitless number or px length")
	}
	if  < len() && ([] == 'e' || [] == 'E') {
		++
		if  < len() && ([] == '+' || [] == '-') {
			++
		}
		 := 0
		for  < len() && [] >= '0' && [] <= '9' {
			++
			++
		}
		if  == 0 {
			return 0, fmt.Errorf("expected a finite unitless number or px length")
		}
	}
	if  != len() {
		return 0, fmt.Errorf("expected a finite unitless number or px length")
	}
	,  := strconv.ParseFloat(, 64)
	if  != nil || math.IsNaN() || math.IsInf(, 0) {
		return 0, fmt.Errorf("expected a finite unitless number or px length")
	}
	return , nil
}

func parseDashArray( context.Context,  string) ([]float64, error) {
	,  := splitSVGNumberList(, , -1)
	if  != nil || len() == 0 {
		if  == nil {
			 = fmt.Errorf("expected at least one dash length")
		}
		return nil, 
	}
	 := make([]float64, len())
	 := false
	 := false
	for ,  := range  {
		[],  = parseSVGLength(, true)
		if  != nil {
			return nil, 
		}
		if [] < 0 {
			return nil, fmt.Errorf("dash lengths must not be negative")
		}
		 =  || [] > 0
		 =  || [] == 0
	}
	if ! {
		return nil, nil
	}
	if  {
		return nil, fmt.Errorf("mixed zero-length dash entries are unsupported")
	}
	if len()%2 == 1 {
		 = append(, ...)
	}
	 := 0.0
	for ,  := range  {
		 += 
		if math.IsInf(, 0) || math.IsNaN() {
			return nil, fmt.Errorf("dash total must be finite")
		}
	}
	return , nil
}

// splitSVGNumberList preserves exponent signs while accepting SVG's comma or
// whitespace separators. Empty comma-separated fields are rejected.
var errSVGNumberListLimit = errors.New("SVG number list exceeds item limit")

func splitSVGNumberList( context.Context,  string,  int) ([]string, error) {
	var  []string
	 := -1
	 := true
	 := func( int) error {
		if  >= 0 {
			if  >= 0 && len() >=  {
				return errSVGNumberListLimit
			}
			 = append(, [:])
			 = -1
			 = false
		}
		return nil
	}
	for  := 0;  < len(); ++ {
		if &4095 == 0 {
			if  := .Err();  != nil {
				return nil, 
			}
		}
		 := []
		if ( == '+' ||  == '-') &&  >= 0 && [-1] != 'e' && [-1] != 'E' {
			if  := ();  != nil {
				return nil, 
			}
			 = 
			continue
		}
		if  == ',' {
			if  := ();  != nil {
				return nil, 
			}
			if  {
				return nil, fmt.Errorf("empty comma-separated number")
			}
			 = true
			continue
		}
		if isSVGListSpace() {
			if  := ();  != nil {
				return nil, 
			}
			continue
		}
		if  < 0 {
			 = 
		}
	}
	if  := (len());  != nil {
		return nil, 
	}
	if  && len() != 0 {
		return nil, fmt.Errorf("trailing comma")
	}
	return , nil
}

func isSVGListSpace( byte) bool {
	switch  {
	case ' ', '\t', '\n', '\r', '\f':
		return true
	default:
		return false
	}
}

func paintWithOpacity( context.Context,  d2scene.Paint,  float64) (d2scene.Paint, error) {
	if  == nil {
		return nil, nil
	}
	switch paint := .(type) {
	case d2scene.SolidPaint:
		.Color.A = uint8(math.Round(float64(.Color.A) * ))
		return , nil
	case d2scene.LinearGradient:
		if  := .Err();  != nil {
			return nil, 
		}
		 := make([]d2scene.GradientStop, len(.Stops))
		for ,  := range .Stops {
			if &255 == 0 {
				if  := .Err();  != nil {
					return nil, 
				}
			}
			[] = 
		}
		.Stops = 
		for  := range  {
			if &255 == 0 {
				if  := .Err();  != nil {
					return nil, 
				}
			}
			.Stops[].Color.A = uint8(math.Round(float64(.Stops[].Color.A) * ))
		}
		return , nil
	default:
		return nil, nil
	}
}