package d2isometricimg

import (
	
	
	
	
	
	
	
	
	
	

	
	
	
)

type nativeSurfaceSVGWriter struct {
	ctx                   context.Context
	doc                   *d2scene.Document
	prefix                string
	defs                  strings.Builder
	next, nodes, commands int
	err                   error
	fonts                 map[d2scene.AssetID]*fontface.ParsedFace
	rasterURLs            map[d2scene.AssetID]string
	transform             d2scene.Matrix
	linearGradients       bool
	glyphs                map[nativeSVGFontGlyph]string
}

// nativeSurfaceSVG emits a self-contained fragment in normalized UV space.
// All generated ink remains paths. Only authored raster-image assets use image.
func nativeSurfaceSVG( context.Context,  *nativeVectorSurface,  string) (string, error) {
	if  == nil ||  == nil || .document == nil ||  == "" {
		return "", fmt.Errorf("native SVG requires a vector surface and prefix")
	}
	for ,  := range  {
		if !( >= 'a' &&  <= 'z' ||  >= 'A' &&  <= 'Z' ||  >= '0' &&  <= '9' ||  == '_' ||  == '-') {
			return "", fmt.Errorf("native SVG has an invalid resource prefix")
		}
	}
	 := &nativeSurfaceSVGWriter{ctx: , doc: .document, prefix: , fonts: make(map[d2scene.AssetID]*fontface.ParsedFace), rasterURLs: make(map[d2scene.AssetID]string)}
	,  := nativeVectorViewport(.doc)
	if  != nil {
		return "", 
	}
	.transform = d2scene.Scale(.doc.LogicalWidth, .doc.LogicalHeight).Mul()
	 := .node(.doc.Root, 0)
	if .err != nil {
		return "", .err
	}
	 := "<g transform=\"" + nativeSVGMatrix() + "\">" +  + "</g>"
	if .capBackground != nil {
		 = "<rect width=\"1\" height=\"1\" fill=\"" + nativeSVGColor(*.capBackground) + "\"/>" + 
	}
	if .capPaint != nil {
		 = .primitive(d2scene.Rect{Box: d2scene.Box{Width: 1, Height: 1}, Fill: .capPaint}, 0) + 
	}
	if .inkCoverage != nil {
		,  := (, .inkCoverage, +"-coverage")
		if  != nil {
			return "", 
		}
		,  := .id("coverage-filter"), .id("coverage-mask")
		// The raster compensation uses 8-bit ink coverage. Preserve every one
		// of its 256 input values exactly, with linear interpolation between
		// them for vector antialiasing at any export resolution.
		 := make([]string, 256)
		for  := range  {
			 := float64() / 255
			[] = nativeSVGNumber((1 - ) / (1 - float64(*.coverageOpacity)))
		}
		// SourceAlpha has black RGB. Make its compensated coverage white so
		// SVG 1.1 luminance-mask readers agree with SVG 2 alpha-mask readers.
		.def("<filter id=\"" +  + "\" filterUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"1\" height=\"1\" color-interpolation-filters=\"sRGB\"><feComponentTransfer in=\"SourceAlpha\"><feFuncR type=\"linear\" slope=\"0\" intercept=\"1\"/><feFuncG type=\"linear\" slope=\"0\" intercept=\"1\"/><feFuncB type=\"linear\" slope=\"0\" intercept=\"1\"/><feFuncA type=\"table\" tableValues=\"" + strings.Join(, " ") + "\"/></feComponentTransfer></filter>")
		.def("<mask id=\"" +  + "\" maskUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"1\" height=\"1\" mask-type=\"alpha\"><g filter=\"url(#" +  + ")\">" +  + "</g></mask>")
		 = "<g mask=\"url(#" +  + ")\">" +  + "</g>"
	}
	if .err != nil {
		return "", .err
	}
	if !.withinBytes(len() + 13) {
		return "", .err
	}
	return "<defs>" + .defs.String() + "</defs>" + , nil
}

func nativeSVGNumber( float64) string {
	if  == 0 {
		return "0"
	}
	return strconv.FormatFloat(, 'g', -1, 64)
}
func nativeSVGMatrix( d2scene.Matrix) string {
	return "matrix(" + nativeSVGNumber(.A) + " " + nativeSVGNumber(.B) + " " + nativeSVGNumber(.C) + " " + nativeSVGNumber(.D) + " " + nativeSVGNumber(.E) + " " + nativeSVGNumber(.F) + ")"
}
func nativeSVGAttr(,  string) string {
	return " " +  + "=\"" + html.EscapeString() + "\""
}
func nativeSVGFloat( string,  float64) string {
	return nativeSVGAttr(, nativeSVGNumber())
}
func nativeSVGColor( color.NRGBA) string { return fmt.Sprintf("#%02x%02x%02x", .R, .G, .B) }
func ( *nativeSurfaceSVGWriter) ( string) string {
	.next++
	return fmt.Sprintf("%s-%s-%d", .prefix, , .next)
}
func ( *nativeSurfaceSVGWriter) ( int) bool {
	if .err != nil {
		return false
	}
	if  := .ctx.Err();  != nil {
		.err = 
		return false
	}
	.nodes++
	if  > 256 || .nodes > 200000 || .commands > 2000000 {
		.err = fmt.Errorf("native SVG surface exceeds node, depth, path or byte budget")
		return false
	}
	return true
}
func ( *nativeSurfaceSVGWriter) ( int) bool {
	if .err != nil {
		return false
	}
	if  > (32<<20)-.defs.Len() {
		.err = fmt.Errorf("native SVG surface exceeds 32 MiB byte budget")
		return false
	}
	return true
}
func ( *nativeSurfaceSVGWriter) ( string) string {
	if !.withinBytes(len()) {
		return ""
	}
	return 
}
func ( *nativeSurfaceSVGWriter) ( *strings.Builder,  string) {
	if .withinBytes(.Len() + len()) {
		.WriteString()
	}
}

func ( *nativeSurfaceSVGWriter) ( string) {
	if .keep() != "" {
		.defs.WriteString()
	}
}

func ( *nativeSurfaceSVGWriter) ( *d2scene.Node,  int) string {
	if  == nil || !.admit() {
		return ""
	}
	var  error
	,  = nativeVectorInitialNode()
	if  != nil {
		.err = 
		return ""
	}
	if .Opacity <= 0 {
		return ""
	}
	if !.Transform.IsFinite() || !captionFinite(.Opacity) {
		.err = fmt.Errorf("native SVG has invalid node transform or opacity")
		return ""
	}
	 := .transform
	.transform = .Mul(.Transform)
	defer func() { .transform =  }()
	 := nativeSVGAttr("transform", nativeSVGMatrix(.Transform))
	if .Opacity < 1 {
		 += nativeSVGFloat("opacity", .Opacity)
	}
	if .Blend != d2scene.BlendNormal {
		 := map[d2scene.BlendMode]string{d2scene.BlendMultiply: "multiply", d2scene.BlendDarken: "darken", d2scene.BlendColorBurn: "color-burn", d2scene.BlendOverlay: "overlay", d2scene.BlendLighten: "lighten"}
		,  := [.Blend]
		if ! {
			.err = fmt.Errorf("native SVG unsupported blend mode %d", .Blend)
			return ""
		}
		 += nativeSVGAttr("style", "mix-blend-mode:"++";isolation:isolate")
	}
	if .Clip != nil {
		 := .id("clip")
		.def("<clipPath id=\"" +  + "\" clipPathUnits=\"userSpaceOnUse\"><path transform=\"" + nativeSVGMatrix(.Clip.Transform) + "\" d=\"" + .path(.Clip.Path) + "\" clip-rule=\"" + nativeSVGFillRule(.Clip.Path.FillRule) + "\"/></clipPath>")
		 += nativeSVGAttr("clip-path", "url(#"++")")
	}
	if .Mask != nil {
		 := .id("mask")
		 := "alpha"
		if .Mask.Type == d2scene.MaskLuminance {
			 = "luminance"
		}
		 := .(.Mask.Root, +1)
		.def("<mask id=\"" +  + "\" maskUnits=\"userSpaceOnUse\"" + nativeSVGBoxAttributes(.surfaceBounds()) + " mask-type=\"" +  + "\"><g transform=\"" + nativeSVGMatrix(.Mask.Transform) + "\">" +  + "</g></mask>")
		 += nativeSVGAttr("mask", "url(#"++")")
	}
	if len(.Filters) > 0 {
		 := .id("filter")
		var  strings.Builder
		for ,  := range .Filters {
			switch f := .(type) {
			case *d2scene.GaussianBlur:
				if  != nil {
					 = *
				}
			case *d2scene.DropShadow:
				if  != nil {
					 = *
				}
			}
			switch f := .(type) {
			case d2scene.GaussianBlur:
				.WriteString("<feGaussianBlur stdDeviation=\"" + nativeSVGNumber(.SigmaX) + " " + nativeSVGNumber(.SigmaY) + "\"/>")
			case d2scene.DropShadow:
				.WriteString("<feDropShadow" + nativeSVGFloat("dx", .OffsetX) + nativeSVGFloat("dy", .OffsetY) + nativeSVGAttr("stdDeviation", nativeSVGNumber(.SigmaX)+" "+nativeSVGNumber(.SigmaY)) + nativeSVGAttr("flood-color", nativeSVGColor(.Color)) + nativeSVGFloat("flood-opacity", float64(.Color.A)/255) + "/>")
			default:
				.err = fmt.Errorf("native SVG unsupported filter %T", )
			}
		}
		.def("<filter id=\"" +  + "\" x=\"-100%\" y=\"-100%\" width=\"300%\" height=\"300%\" color-interpolation-filters=\"sRGB\">" + .String() + "</filter>")
		 += nativeSVGAttr("filter", "url(#"++")")
	}
	var  strings.Builder
	.append(&, .primitive(.Primitive, +1))
	for ,  := range .Children {
		.append(&, .(, +1))
	}
	return .keep("<g" +  + ">" + .String() + "</g>")
}

func nativeSVGFillRule( d2scene.FillRule) string {
	if  == d2scene.EvenOdd {
		return "evenodd"
	}
	return "nonzero"
}
func ( *nativeSurfaceSVGWriter) ( d2scene.Path) string {
	.commands += len(.Commands)
	if .commands > 2000000 {
		.err = fmt.Errorf("native SVG surface exceeds path command budget")
		return ""
	}
	var  strings.Builder
	 := func( d2scene.Point) string { return nativeSVGNumber(.X) + " " + nativeSVGNumber(.Y) }
	for ,  := range .Commands {
		switch .Kind {
		case d2scene.MoveCommand:
			.WriteString("M" + (.P1))
		case d2scene.LineCommand:
			.WriteString("L" + (.P1))
		case d2scene.QuadraticCommand:
			.WriteString("Q" + (.P1) + " " + (.P2))
		case d2scene.CubicCommand:
			.WriteString("C" + (.P1) + " " + (.P2) + " " + (.P3))
		case d2scene.ArcCommand:
			,  := "0", "0"
			if .LargeArc {
				 = "1"
			}
			if .Sweep {
				 = "1"
			}
			.WriteString("A" + nativeSVGNumber(.RadiusX) + " " + nativeSVGNumber(.RadiusY) + " " + nativeSVGNumber(.Rotation*180/math.Pi) + " " +  + " " +  + " " + (.P1))
		case d2scene.CloseCommand:
			.WriteByte('Z')
		default:
			.err = fmt.Errorf("native SVG unsupported path command %d", .Kind)
		}
		if !.withinBytes(.Len()) {
			return ""
		}
	}
	return .String()
}
func ( *nativeSurfaceSVGWriter) ( d2scene.Paint,  string,  int) string {
	if  == nil {
		return nativeSVGAttr(, "none")
	}
	switch p := .(type) {
	case *d2scene.SolidPaint:
		if  != nil {
			return .(*, , )
		}
	case d2scene.SolidPaint:
		return nativeSVGAttr(, nativeSVGColor(.Color)) + nativeSVGFloat(+"-opacity", float64(.Color.A)/255)
	case *d2scene.LinearGradient:
		if  != nil {
			return .(*, , )
		}
	case *d2scene.RadialGradient:
		if  != nil {
			return .(*, , )
		}
	case *d2scene.PatternPaint:
		if  != nil {
			return .(*, , )
		}
	}
	 := .id("paint")
	 := func( d2scene.PaintUnits) string {
		if  == d2scene.UserSpaceOnUse {
			return "userSpaceOnUse"
		}
		return "objectBoundingBox"
	}
	 := func( d2scene.SpreadMethod) string {
		if  == d2scene.SpreadReflect {
			return "reflect"
		}
		if  == d2scene.SpreadRepeat {
			return "repeat"
		}
		return "pad"
	}
	 := func( []d2scene.GradientStop) string {
		var  strings.Builder
		for ,  := range  {
			.WriteString("<stop" + nativeSVGFloat("offset", .Offset) + nativeSVGAttr("stop-color", nativeSVGColor(.Color)) + nativeSVGFloat("stop-opacity", float64(.Color.A)/255) + "/>")
		}
		return .String()
	}
	 := "sRGB"
	if .linearGradients {
		 = "linearRGB"
	}
	 := nativeSVGAttr("color-interpolation", )
	switch p := .(type) {
	case d2scene.LinearGradient:
		.def("<linearGradient id=\"" +  + "\"" + nativeSVGAttr("gradientUnits", (.Units)) +  + nativeSVGAttr("gradientTransform", nativeSVGMatrix(.Transform)) + nativeSVGAttr("spreadMethod", (.Spread)) + nativeSVGFloat("x1", .Start.X) + nativeSVGFloat("y1", .Start.Y) + nativeSVGFloat("x2", .End.X) + nativeSVGFloat("y2", .End.Y) + ">" + (.Stops) + "</linearGradient>")
	case d2scene.RadialGradient:
		.def("<radialGradient id=\"" +  + "\"" + nativeSVGAttr("gradientUnits", (.Units)) +  + nativeSVGAttr("gradientTransform", nativeSVGMatrix(.Transform)) + nativeSVGAttr("spreadMethod", (.Spread)) + nativeSVGFloat("cx", .Center.X) + nativeSVGFloat("cy", .Center.Y) + nativeSVGFloat("r", .Radius) + nativeSVGFloat("fx", .Focal.X) + nativeSVGFloat("fy", .Focal.Y) + nativeSVGFloat("fr", .FocalRadius) + ">" + (.Stops) + "</radialGradient>")
	case d2scene.PatternPaint:
		 := .node(.Root, +1)
		.def("<pattern id=\"" +  + "\"" + nativeSVGAttr("patternUnits", (.Units)) + nativeSVGAttr("patternTransform", nativeSVGMatrix(.Transform)) + nativeSVGFloat("x", .Tile.X) + nativeSVGFloat("y", .Tile.Y) + nativeSVGFloat("width", .Tile.Width) + nativeSVGFloat("height", .Tile.Height) + nativeSVGAttr("viewBox", "0 0 "+nativeSVGNumber(.Tile.Width)+" "+nativeSVGNumber(.Tile.Height)) + " preserveAspectRatio=\"none\">" +  + "</pattern>")
	default:
		.err = fmt.Errorf("native SVG unsupported paint %T", )
	}
	return nativeSVGAttr(, "url(#"++")")
}
func ( *nativeSurfaceSVGWriter) ( d2scene.Paint,  *d2scene.Stroke,  int) string {
	 := .paint(, "fill", )
	if  == nil || .Paint == nil || .Width <= 0 {
		return  + " stroke=\"none\""
	}
	 += .paint(.Paint, "stroke", ) + nativeSVGFloat("stroke-width", .Width)
	 := []string{"butt", "round", "square"}
	 := []string{"miter", "round", "bevel"}
	if int(.Cap) >= len() || int(.Join) >= len() {
		.err = fmt.Errorf("native SVG has invalid stroke joins")
		return ""
	}
	 += nativeSVGAttr("stroke-linecap", [.Cap]) + nativeSVGAttr("stroke-linejoin", [.Join])
	if .MiterLimit > 0 {
		 += nativeSVGFloat("stroke-miterlimit", .MiterLimit)
	}
	if len(.Dashes) > 0 {
		 := make([]string, len(.Dashes))
		for ,  := range .Dashes {
			[] = nativeSVGNumber()
		}
		 += nativeSVGAttr("stroke-dasharray", strings.Join(, " ")) + nativeSVGFloat("stroke-dashoffset", .DashOffset)
	}
	return 
}

func ( *nativeSurfaceSVGWriter) ( d2scene.Primitive,  int) string {
	switch p := .(type) {
	case nil:
		return ""
	case *d2scene.Path:
		if  != nil {
			return .(*, )
		}
	case *d2scene.Rect:
		if  != nil {
			return .(*, )
		}
	case *d2scene.Ellipse:
		if  != nil {
			return .(*, )
		}
	case *d2scene.TextRun:
		if  != nil {
			return .(*, )
		}
	case *d2scene.Image:
		if  != nil {
			return .(*, )
		}
	case d2scene.Path:
		return "<path" + nativeSVGAttr("d", .path()) + nativeSVGAttr("fill-rule", nativeSVGFillRule(.FillRule)) + .style(.Fill, .Stroke, ) + "/>"
	case d2scene.Rect:
		return "<rect" + nativeSVGFloat("x", .Box.X) + nativeSVGFloat("y", .Box.Y) + nativeSVGFloat("width", .Box.Width) + nativeSVGFloat("height", .Box.Height) + nativeSVGFloat("rx", .RadiusX) + nativeSVGFloat("ry", .RadiusY) + .style(.Fill, .Stroke, ) + "/>"
	case d2scene.Ellipse:
		return "<ellipse" + nativeSVGFloat("cx", .Center.X) + nativeSVGFloat("cy", .Center.Y) + nativeSVGFloat("rx", .RadiusX) + nativeSVGFloat("ry", .RadiusY) + .style(.Fill, .Stroke, ) + "/>"
	case d2scene.TextRun:
		return .text(, )
	case d2scene.Image:
		,  := .doc.Assets[.Asset]
		if ! {
			.err = fmt.Errorf("native SVG missing image asset %q", .Asset)
			return ""
		}
		switch a := .(type) {
		case d2scene.RasterAsset:
			if len(.Data) > 32<<20 {
				.err = fmt.Errorf("native SVG raster asset exceeds byte budget")
				return ""
			}
			,  := .rasterURL(.Asset, )
			if  != nil {
				.err = 
				return ""
			}
			 := nativeSVGAspect(.Aspect)
			return "<image" + nativeSVGFloat("x", .Box.X) + nativeSVGFloat("y", .Box.Y) + nativeSVGFloat("width", .Box.Width) + nativeSVGFloat("height", .Box.Height) + nativeSVGAttr("preserveAspectRatio", ) + nativeSVGAttr("href", ) + "/>"
		case d2scene.VectorAsset:
			,  := d2scene.AspectRatioMatrix(.ViewBox, .Box, .Aspect)
			if  != nil {
				.err = 
				return ""
			}
			 := .id("image-clip")
			.def("<clipPath id=\"" +  + "\"><rect" + nativeSVGFloat("x", .Box.X) + nativeSVGFloat("y", .Box.Y) + nativeSVGFloat("width", .Box.Width) + nativeSVGFloat("height", .Box.Height) + "/></clipPath>")
			 := .transform
			.transform = .Mul()
			 := .node(.Root, +1)
			.transform = 
			return "<g clip-path=\"url(#" +  + ")\"><g transform=\"" + nativeSVGMatrix() + "\">" +  + "</g></g>"
		default:
			.err = fmt.Errorf("native SVG unsupported image asset %T", )
		}
	}
	if .err == nil {
		.err = fmt.Errorf("native SVG unsupported primitive %T", )
	}
	return ""
}

func nativeSVGAspect( d2scene.AspectRatio) string {
	 := []string{"none", "xMinYMin", "xMidYMin", "xMaxYMin", "xMinYMid", "xMidYMid", "xMaxYMid", "xMinYMax", "xMidYMax", "xMaxYMax"}
	if int(.Align) >= len() {
		return "none"
	}
	if .Align == d2scene.AlignNone {
		return "none"
	}
	 := "meet"
	if .Fit == d2scene.AspectSlice {
		 = "slice"
	}
	return [.Align] + " " + 
}

// Only authored raster assets are encoded as images. Normalize the same first
// frame and JPEG orientation as PNG output; browsers must not animate an
// embedded GIF or reinterpret the source image's dimensions.
func ( *nativeSurfaceSVGWriter) ( d2scene.AssetID,  d2scene.RasterAsset) (string, error) {
	if  := .rasterURLs[];  != "" {
		return , nil
	}
	 := strings.TrimPrefix(strings.ToLower(.MIMEType), "image/")
	if  == "jpg" {
		 = "jpeg"
	}
	, ,  := rasterimage.Config(.ctx, .Data, )
	if  != nil {
		return "", 
	}
	if .Width <= 0 || .Height <= 0 || int64(.Width) > 16000000/int64(.Height) {
		return "", fmt.Errorf("native SVG raster asset exceeds 16 million pixels")
	}
	,  := rasterimage.DecodeFirst(.ctx, .Data, )
	if  != nil {
		return "", 
	}
	var  bytes.Buffer
	if  := png.Encode(&, );  != nil {
		return "", 
	}
	if  := .ctx.Err();  != nil {
		return "", 
	}
	if .Len() > 32<<20 {
		return "", fmt.Errorf("native SVG raster asset exceeds 32 MiB")
	}
	 := "data:image/png;base64," + base64.StdEncoding.EncodeToString(.Bytes())
	.rasterURLs[] = 
	return , nil
}

func nativeSVGBoxAttributes( d2scene.Box) string {
	return nativeSVGFloat("x", .X) + nativeSVGFloat("y", .Y) + nativeSVGFloat("width", .Width) + nativeSVGFloat("height", .Height)
}

// Resource allocations only need to cover the retained viewport. A mask with
// an effectively infinite user-space rectangle can ask SVG clients to allocate
// enormous offscreen surfaces even though the face itself is small.
func ( *nativeSurfaceSVGWriter) () d2scene.Box {
	,  := .transform.Inverse()
	if  != nil {
		.err = 
		return d2scene.Box{}
	}
	 := (d2scene.Box{Width: .doc.LogicalWidth, Height: .doc.LogicalHeight}).Bounds().Transform()
	return d2scene.Box{X: .Min.X, Y: .Min.Y, Width: .Max.X - .Min.X, Height: .Max.Y - .Min.Y}
}