package d2sketch

import (
	
	
	
	

	
	
	
	
	
	
	
	rough 
	
)

var floatRE = regexp.MustCompile(`(\d+)\.(\d+)`)

const (
	BG_COLOR = color.N7
	FG_COLOR = color.N1
)

func newGenerator() *rough.Generator {
	return rough.NewGenerator(&rough.Config{Options: &rough.Options{Seed: rough.Float64(1)}})
}

func baseOptions( float64) *rough.Options {
	return &rough.Options{
		Fill:        rough.String("#000"),
		Stroke:      rough.String("#000"),
		StrokeWidth: rough.Float64(),
		FillWeight:  rough.Float64(2),
		HachureGap:  rough.Float64(16),
		FillStyle:   rough.String("solid"),
		Bowing:      rough.Float64(2),
		Seed:        rough.Float64(1),
	}
}

// DefineFillPatterns adds reusable patterns that are overlayed on shapes with
// fill. This gives it a subtle streaky effect that subtly looks hand-drawn but
// not distractingly so.
func ( *bytes.Buffer,  string) {
	 := .String()
	fmt.Fprint(, "<defs>")

	defineFillPattern(, , , "bright", "rgba(0, 0, 0, 0.1)")
	defineFillPattern(, , , "normal", "rgba(0, 0, 0, 0.16)")
	defineFillPattern(, , , "dark", "rgba(0, 0, 0, 0.32)")
	defineFillPattern(, , , "darker", "rgba(255, 255, 255, 0.24)")

	fmt.Fprint(, "</defs>")
}

func defineFillPattern( *bytes.Buffer, ,  string, ,  string) {
	 := fmt.Sprintf(`url(#streaks-%s-%s)`, , )
	if strings.Contains(, ) {
		fmt.Fprintf(, patternassets.StreaksSVG(), , , )
	}
}

func ( d2target.Shape,  string) (string, error) {
	 := newGenerator()
	,  := computeRoughPathData(, .Rectangle(0, 0, float64(.Width), float64(.Height), baseOptions(float64(.StrokeWidth))))
	if  != nil {
		return "", 
	}
	 := ""
	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()

	if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
		.SetMaskID(svg.LiteralID())
	}

	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := d2themes.NewThemableElement("rect", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Width = float64(.Width)
	.Height = float64(.Height)

	if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
		.SetMaskID(svg.LiteralID())
	}

	,  := d2themes.NewThemableSketchOverlay(, .Fill).Render()
	if  != nil {
		return "", 
	}
	 += 

	return , nil
}

func ( d2target.Shape,  string) (string, error) {
	 := newGenerator()
	,  := computeRoughPathData(, .Rectangle(0, 0, float64(.Width), float64(.Height), baseOptions(float64(.StrokeWidth))))
	if  != nil {
		return "", 
	}
	,  := computeRoughPathData(, .Rectangle(0, 0,
		float64(.Width-d2target.INNER_BORDER_OFFSET*2),
		float64(.Height-d2target.INNER_BORDER_OFFSET*2),
		baseOptions(float64(.StrokeWidth))))
	if  != nil {
		return "", 
	}

	 := ""

	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()

	if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
		.SetMaskID(svg.LiteralID())
	}

	for ,  := range  {
		.D = 
		 += .Render()
	}

	 = d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X+d2target.INNER_BORDER_OFFSET), float64(.Pos.Y+d2target.INNER_BORDER_OFFSET))
	.Fill, .Stroke = d2themes.ShapeTheme()
	// No need for inner to double paint
	.Fill = "transparent"
	.ClassName = "shape"
	.Style = .CSSStyle()

	if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
		.SetMaskID(svg.LiteralID())
	}

	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := d2themes.NewThemableElement("rect", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Width = float64(.Width)
	.Height = float64(.Height)

	if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
		.SetMaskID(svg.LiteralID())
	}

	,  := d2themes.NewThemableSketchOverlay(, .Fill).Render()
	if  != nil {
		return "", 
	}
	 += 

	return , nil
}

func ( d2target.Shape,  string) (string, error) {
	 := newGenerator()
	,  := computeRoughPathData(, .Ellipse(
		float64(.Width/2), float64(.Height/2),
		float64(.Width), float64(.Height), baseOptions(float64(.StrokeWidth))))
	if  != nil {
		return "", 
	}
	 := ""
	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()

	if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
		.SetMaskID(svg.LiteralID())
	}

	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := d2themes.NewThemableElement("ellipse", nil)
	.SetTranslate(float64(.Pos.X+.Width/2), float64(.Pos.Y+.Height/2))
	.Rx = float64(.Width / 2)
	.Ry = float64(.Height / 2)

	if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
		.SetMaskID(svg.LiteralID())
	}

	,  := d2themes.NewThemableSketchOverlay(
		,
		.Fill,
	).Render()
	if  != nil {
		return "", 
	}
	 += 

	return , nil
}

func ( d2target.Shape,  string) (string, error) {
	 := newGenerator()
	,  := computeRoughPathData(, .Ellipse(
		float64(.Width/2), float64(.Height/2),
		float64(.Width), float64(.Height), baseOptions(float64(.StrokeWidth))))
	if  != nil {
		return "", 
	}
	,  := computeRoughPathData(, .Ellipse(
		float64(.Width/2), float64(.Height/2),
		float64(.Width-d2target.INNER_BORDER_OFFSET*2),
		float64(.Height-d2target.INNER_BORDER_OFFSET*2),
		baseOptions(float64(.StrokeWidth))))
	if  != nil {
		return "", 
	}

	 := ""

	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()

	if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
		.SetMaskID(svg.LiteralID())
	}

	for ,  := range  {
		.D = 
		 += .Render()
	}

	 = d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	// No need for inner to double paint
	.Fill = "transparent"
	.ClassName = "shape"
	.Style = .CSSStyle()

	if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
		.SetMaskID(svg.LiteralID())
	}

	for ,  := range  {
		.D = 
		 += .Render()
	}
	 := d2themes.NewThemableElement("ellipse", nil)
	.SetTranslate(float64(.Pos.X+.Width/2), float64(.Pos.Y+.Height/2))
	.Rx = float64(.Width / 2)
	.Ry = float64(.Height / 2)

	if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
		.SetMaskID(svg.LiteralID())
	}

	,  := d2themes.NewThemableSketchOverlay(
		,
		.Fill,
	).Render()
	if  != nil {
		return "", 
	}
	 += 

	return , nil
}

// TODO need to personalize this per shape like we do in Terrastruct app
func ( d2target.Shape,  string,  []string) (string, error) {
	 := ""
	for ,  := range  {
		 := newGenerator()
		,  := computeRoughPathData(, .Path(, baseOptions(float64(.StrokeWidth))))
		if  != nil {
			return "", 
		}
		 := d2themes.NewThemableElement("path", nil)
		.Fill, .Stroke = d2themes.ShapeTheme()
		.FillPattern = .FillPattern
		.ClassName = "shape"
		.Style = .CSSStyle()

		if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
			.SetMaskID(svg.LiteralID())
		}

		for ,  := range  {
			.D = 
			 += .Render()
		}

		 := d2themes.NewThemableElement("path", nil)

		if .Label != "" && label.FromString(.LabelPosition).IsBorder() {
			.SetMaskID(svg.LiteralID())
		}

		for ,  := range  {
			.D = 
			,  := d2themes.NewThemableSketchOverlay(
				,
				.Fill,
			).Render()
			if  != nil {
				return "", 
			}
			 += 
		}
	}
	return , nil
}

// Connection is retained for source compatibility. Its legacy textual
// attribute list is parsed and re-serialized before use.
// Deprecated: use ConnectionWithAttributes.
func ( d2target.Connection, ,  string) (string, error) {
	,  := svg.ParseAttributes()
	if  != nil {
		return "", 
	}
	return ConnectionWithAttributes(, , )
}

func ( d2target.Connection,  string,  []svg.Attribute) (string, error) {
	 := map[string]struct{}{
		"class":  {},
		"d":      {},
		"fill":   {},
		"stroke": {},
		"style":  {},
	}
	for ,  := range  {
		,  := .Name()
		if  != nil {
			return "", 
		}
		if ,  := [];  {
			return "", fmt.Errorf("connection attribute %q conflicts with a renderer-controlled attribute", )
		}
		[] = struct{}{}
	}

	 := ""
	if .Animated {
		 = " animated-connection"
	}

	if .Animated {
		// If connection is animated and bidirectional
		if (.DstArrow == d2target.NoArrowhead && .SrcArrow == d2target.NoArrowhead) || (.DstArrow != d2target.NoArrowhead && .SrcArrow != d2target.NoArrowhead) {
			// There is no pure CSS way to animate bidirectional connections in two directions, so we split it up
			, ,  := svg.SplitPath(, 0.5)

			if  != nil {
				return "", 
			}

			 := d2themes.NewThemableElement("path", nil)
			.D = 
			.Fill = color.None
			.Stroke = .Stroke
			.ClassName = fmt.Sprintf("connection%s", )
			.Style = .CSSStyle()
			.Style += "animation-direction: reverse;"
			.AddAttributes(...)

			 := d2themes.NewThemableElement("path", nil)
			.D = 
			.Fill = color.None
			.Stroke = .Stroke
			.ClassName = fmt.Sprintf("connection%s", )
			.Style = .CSSStyle()
			.AddAttributes(...)
			return .Render() + " " + .Render(), nil
		} else {
			 := d2themes.NewThemableElement("path", nil)
			.D = 
			.Fill = color.None
			.Stroke = .Stroke
			.ClassName = fmt.Sprintf("connection%s", )
			.Style = .CSSStyle()
			.AddAttributes(...)
			return .Render(), nil
		}
	} else {
		 := 0.5
		 := newGenerator()
		,  := computeRoughPathData(, .Path(, &rough.Options{
			Roughness: rough.Float64(),
			Seed:      rough.Float64(1),
		}))
		if  != nil {
			return "", 
		}

		 := ""

		 := d2themes.NewThemableElement("path", nil)
		.Fill = color.None
		.Stroke = .Stroke
		.ClassName = fmt.Sprintf("connection%s", )
		.Style = .CSSStyle()
		.AddAttributes(...)
		for ,  := range  {
			.D = 
			 += .Render()
		}
		return , nil
	}
}

// TODO cleanup
func ( d2target.Shape) (string, error) {
	 := ""
	 := newGenerator()
	,  := computeRoughPathData(, .Rectangle(0, 0, float64(.Width), float64(.Height), baseOptions(float64(.StrokeWidth))))
	if  != nil {
		return "", 
	}
	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := geo.NewBox(
		geo.NewPoint(float64(.Pos.X), float64(.Pos.Y)),
		float64(.Width),
		float64(.Height),
	)
	 := .Height / float64(1+len(.SQLTable.Columns))
	 := geo.NewBox(.TopLeft, .Width, )

	,  = computeRoughPathData(, .Rectangle(0, 0, float64(.Width), , baseOptions(1)))
	if  != nil {
		return "", 
	}
	 = d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill = .Fill
	.FillPattern = .FillPattern
	.ClassName = "class_header"
	for ,  := range  {
		.D = 
		 += .Render()
	}

	if .Label != "" {
		 := label.InsideMiddleLeft.GetPointOnBox(
			,
			20,
			float64(.LabelWidth),
			float64(.LabelHeight),
		)

		 := d2themes.NewThemableElement("text", nil)
		.X = .X
		.Y = .Y + float64(.LabelHeight)*3/4
		.Fill = .GetFontColor()
		.ClassName = "text"
		.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
			"start", 4+.FontSize,
		)
		.SetText(.Label)
		 += .Render()
	}

	var  int
	for ,  := range .Columns {
		 = go2.Max(, .Name.LabelWidth)
	}

	 := geo.NewBox(.TopLeft.Copy(), .Width, )
	.TopLeft.Y += .Height
	for ,  := range .Columns {
		 := label.InsideMiddleLeft.GetPointOnBox(
			,
			d2target.NamePadding,
			.Width,
			float64(.FontSize),
		)
		 := label.InsideMiddleRight.GetPointOnBox(
			,
			d2target.TypePadding,
			0,
			float64(.FontSize),
		)

		 := d2themes.NewThemableElement("text", nil)
		.X = .X
		.Y = .Y + float64(.FontSize)*3/4
		.Fill = .PrimaryAccentColor
		.ClassName = "text"
		.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", float64(.FontSize))
		.SetText(.Name.Label)
		 += .Render()

		.X = .X + float64() + 2*d2target.NamePadding
		.Fill = .NeutralAccentColor
		.SetText(.Type.Label)
		 += .Render()

		.X = .X
		.Y = .Y + float64(.FontSize)*3/4
		.Fill = .SecondaryAccentColor
		.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx;letter-spacing:2px", "end", float64(.FontSize))
		.SetText(.ConstraintAbbr())
		 += .Render()

		.TopLeft.Y += 

		,  = computeRoughPathData(, .Line(
			.TopLeft.X, .TopLeft.Y,
			.TopLeft.X+.Width, .TopLeft.Y,
			baseOptions(1)))
		if  != nil {
			return "", 
		}
		 := d2themes.NewThemableElement("path", nil)
		.Fill = .Fill
		.FillPattern = .FillPattern
		for ,  := range  {
			.D = 
			 += .Render()
		}
	}

	 := d2themes.NewThemableElement("rect", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Width = float64(.Width)
	.Height = float64(.Height)
	,  := d2themes.NewThemableSketchOverlay(, .Fill).Render()
	if  != nil {
		return "", 
	}
	 += 

	return , nil
}

func ( d2target.Shape) (string, error) {
	 := ""
	 := newGenerator()
	,  := computeRoughPathData(, .Rectangle(0, 0, float64(.Width), float64(.Height), baseOptions(float64(.StrokeWidth))))
	if  != nil {
		return "", 
	}
	 := d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill, .Stroke = d2themes.ShapeTheme()
	.FillPattern = .FillPattern
	.ClassName = "shape"
	.Style = .CSSStyle()
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := geo.NewBox(
		geo.NewPoint(float64(.Pos.X), float64(.Pos.Y)),
		float64(.Width),
		float64(.Height),
	)

	 := .Height / float64(2+len(.Class.Fields)+len(.Class.Methods))
	 := geo.NewBox(.TopLeft, .Width, 2*)

	,  = computeRoughPathData(, .Rectangle(0, 0, float64(.Width), .Height, baseOptions(1)))
	if  != nil {
		return "", 
	}
	 = d2themes.NewThemableElement("path", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Fill = .Fill
	.FillPattern = .FillPattern
	.ClassName = "class_header"
	for ,  := range  {
		.D = 
		 += .Render()
	}

	 := d2themes.NewThemableElement("rect", nil)
	.SetTranslate(float64(.Pos.X), float64(.Pos.Y))
	.Width = float64(.Width)
	.Height = .Height
	,  := d2themes.NewThemableSketchOverlay(, .Fill).Render()
	if  != nil {
		return "", 
	}
	 += 

	if .Label != "" {
		 := label.InsideMiddleCenter.GetPointOnBox(
			,
			0,
			float64(.LabelWidth),
			float64(.LabelHeight),
		)

		 := d2themes.NewThemableElement("text", nil)
		.X = .X + float64(.LabelWidth)/2
		.Y = .Y + float64(.LabelHeight)*3/4
		.Fill = .GetFontColor()
		.ClassName = "text-mono"
		.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx",
			"middle",
			4+.FontSize,
		)
		.SetText(.Label)
		 += .Render()
	}

	 := geo.NewBox(.TopLeft.Copy(), .Width, )
	.TopLeft.Y += .Height
	for ,  := range .Fields {
		 += classRow(, , .VisibilityToken(), .Name, .Type, float64(.FontSize))
		.TopLeft.Y += 
	}

	,  = computeRoughPathData(, .Line(
		.TopLeft.X, .TopLeft.Y,
		.TopLeft.X+.Width, .TopLeft.Y,
		baseOptions(1)))
	if  != nil {
		return "", 
	}
	 = d2themes.NewThemableElement("path", nil)
	.Fill = .Fill
	.FillPattern = .FillPattern
	.ClassName = "class_header"
	for ,  := range  {
		.D = 
		 += .Render()
	}

	for ,  := range .Methods {
		 += classRow(, , .VisibilityToken(), .Name, .Return, float64(.FontSize))
		.TopLeft.Y += 
	}

	return , nil
}

func classRow( d2target.Shape,  *geo.Box, , ,  string,  float64) string {
	 := ""
	 := label.InsideMiddleLeft.GetPointOnBox(
		,
		d2target.PrefixPadding,
		.Width,
		,
	)
	 := label.InsideMiddleRight.GetPointOnBox(
		,
		d2target.TypePadding,
		0,
		,
	)

	 := d2themes.NewThemableElement("text", nil)
	.X = .X
	.Y = .Y + *3/4
	.Fill = .PrimaryAccentColor
	.ClassName = "text-mono"
	.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "start", )
	.SetText()
	 += .Render()

	.X = .X + d2target.PrefixWidth
	.Fill = .Fill
	.SetText()
	 += .Render()

	.X = .X
	.Y = .Y + *3/4
	.Fill = .SecondaryAccentColor
	.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "end", )
	.SetText()
	 += .Render()

	return 
}

func computeRoughPathData( *rough.Generator,  rough.Drawable) ([]string, error) {
	return extractPathData(computeRoughPaths(, ))
}

func computeRoughPaths( *rough.Generator,  rough.Drawable) []roughPath {
	 := .ToPaths()
	 := make([]roughPath, 0, len())
	for ,  := range  {
		 = append(, roughPath{
			Attrs: attrs{D: truncatePathData(.D)},
			Style: style{
				Stroke:      .Stroke,
				StrokeWidth: rough.NumberString(.StrokeWidth),
				Fill:        .Fill,
			},
		})
	}
	return 
}

type attrs struct {
	D string `json:"d"`
}

type style struct {
	Stroke      string `json:"stroke,omitempty"`
	StrokeWidth string `json:"strokeWidth,omitempty"`
	Fill        string `json:"fill,omitempty"`
}

type roughPath struct {
	Attrs attrs `json:"attrs"`
	Style style `json:"style"`
}

func ( roughPath) () string {
	 := ""
	if .Style.StrokeWidth != "" {
		 += fmt.Sprintf("stroke-width:%s;", .Style.StrokeWidth)
	}
	return 
}

func truncatePathData( string) string {
	// we want to have a fixed precision to the decimals in the path data
	return floatRE.ReplaceAllStringFunc(, func( string) string {
		 := strings.Index(, ".")
		 := len() -  - 1
		 :=  + go2.Min(, 6)
		return [:+1]
	})
}

func extractPathData( []roughPath) ([]string, error) {
	var  []string
	for ,  := range  {
		 = append(, .Attrs.D)
	}
	return , nil
}

func arrowOptions( string,  int,  float64) *rough.Options {
	return &rough.Options{
		StrokeWidth: rough.Float64(float64()),
		Stroke:      rough.String(),
		Seed:        rough.Float64(),
	}
}

func solidArrowOptions(,  string,  int, ,  float64) *rough.Options {
	 := arrowOptions(, , )
	.Fill = rough.String()
	.FillStyle = rough.String("solid")
	if  >= 0 {
		.FillWeight = rough.Float64()
	}
	return 
}

func arrowheadDrawable( *rough.Generator,  d2target.Arrowhead,  string,  int) ( rough.Drawable,  *rough.Drawable,  bool) {
	// Note: selected each seed that looks the good for consistent renders
	switch  {
	case d2target.ArrowArrowhead:
		 = .LinearPath([]rough.Point{{-10, -4}, {0, 0}, {-10, 4}}, arrowOptions(, , 3))
	case d2target.TriangleArrowhead:
		 = .Polygon([]rough.Point{{-10, -4}, {0, 0}, {-10, 4}}, solidArrowOptions(, , , -1, 2))
	case d2target.UnfilledTriangleArrowhead:
		 = .Polygon([]rough.Point{{-10, -4}, {0, 0}, {-10, 4}}, solidArrowOptions(, BG_COLOR, , -1, 2))
	case d2target.DiamondArrowhead:
		 = .Polygon([]rough.Point{{-20, 0}, {-10, 5}, {0, 0}, {-10, -5}, {-20, 0}}, solidArrowOptions(, BG_COLOR, , -1, 1))
	case d2target.FilledDiamondArrowhead:
		 := solidArrowOptions(, , , 4, 1)
		.FillStyle = rough.String("zigzag")
		 = .Polygon([]rough.Point{{-20, 0}, {-10, 5}, {0, 0}, {-10, -5}, {-20, 0}}, )
	case d2target.CrossArrowhead:
		 = .LinearPath([]rough.Point{{-6, -6}, {6, 6}, {0, 0}, {-6, 6}, {0, 0}, {6, -6}}, arrowOptions(, , 3))
	case d2target.CfManyRequired:
		 = .Path("M-15,-10 -15,10 M0,10 -15,0 M0,-10 -15,0", solidArrowOptions(, , , 4, 2))
	case d2target.CfMany:
		 = .Path("M0,10 -15,0 M0,-10 -15,0", solidArrowOptions(, , , 4, 8))
		 := .Circle(-20, 0, 8, solidArrowOptions(, BG_COLOR, , 1, 4))
		 = &
	case d2target.CfOneRequired:
		 = .Path("M-15,-10 -15,10 M-10,-10 -10,10", solidArrowOptions(, , , 4, 2))
	case d2target.CfOne:
		 = .Path("M-10,-10 -10,10", solidArrowOptions(, , , 4, 3))
		 := .Circle(-20, 0, 8, solidArrowOptions(, BG_COLOR, , 1, 5))
		 = &
	case d2target.CircleArrowhead:
		 = .Circle(-2, -1, 8, solidArrowOptions(, BG_COLOR, , 1, 5))
	case d2target.BoxArrowhead:
		 = .Polygon([]rough.Point{{0, -10}, {0, 10}, {-20, 10}, {-20, -10}}, solidArrowOptions(, BG_COLOR, , -1, 1))
	case d2target.FilledBoxArrowhead:
		 = .Polygon([]rough.Point{{0, -10}, {0, 10}, {-20, 10}, {-20, -10}}, solidArrowOptions(, , , -1, 1))
	default:
		return rough.Drawable{}, nil, false
	}
	return , , true
}

func ( d2target.Connection, ,  *geo.Point) (string, error) {
	 := []string{}

	if .SrcArrow != d2target.NoArrowhead {
		 := newGenerator()
		, ,  := arrowheadDrawable(, .SrcArrow, .Stroke, .StrokeWidth)
		if ! {
			return "", nil
		}

		 := geo.NewSegment(.Route[0], .Route[1])
		 := .ToVector().Reverse()
		 := .Degrees()

		 := fmt.Sprintf("translate(%f %f) rotate(%v)",
			.Start.X+.X, .Start.Y+.Y, ,
		)

		 := computeRoughPaths(, )
		if  != nil {
			 = append(, computeRoughPaths(, *)...)
		}

		 := d2themes.NewThemableElement("path", nil)
		.ClassName = "connection"
		.AddAttributes(svg.Attr("transform", ))
		for ,  := range  {
			.D = .Attrs.D
			.Fill = .Style.Fill
			.Stroke = .Style.Stroke
			.Style = .StyleCSS()
			 = append(, .Render())
		}
	}

	if .DstArrow != d2target.NoArrowhead {
		 := newGenerator()
		, ,  := arrowheadDrawable(, .DstArrow, .Stroke, .StrokeWidth)
		if ! {
			return "", nil
		}

		 := len(.Route)
		 := geo.NewSegment(.Route[-2], .Route[-1])
		 := .ToVector()
		 := .Degrees()

		 := fmt.Sprintf("translate(%f %f) rotate(%v)",
			.End.X+.X, .End.Y+.Y, ,
		)

		 := computeRoughPaths(, )
		if  != nil {
			 = append(, computeRoughPaths(, *)...)
		}

		 := d2themes.NewThemableElement("path", nil)
		.ClassName = "connection"
		.AddAttributes(svg.Attr("transform", ))
		for ,  := range  {
			.D = .Attrs.D
			.Fill = .Style.Fill
			.Stroke = .Style.Stroke
			.Style = .StyleCSS()
			 = append(, .Render())
		}
	}

	return strings.Join(, " "), nil
}