package d2scenebuild

import (
	
	
	
	
	

	
	
	
	libcolor 
	
	
	
	rough 
)

const (
	sketchCubicFlatness             = 0.35
	sketchCubicMaxDepth             = 8
	sketchStreakSubpathCount        = 85
	sketchStreakCompoundSubpath     = 16
	sketchStreakCompoundSubpathSpan = 2
)

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

func sketchShapeOptions( float64,  []float64,  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),
		StrokeLineDash:       append([]float64(nil), ...),
		StrokeLineDashOffset: rough.Float64(),
	}
}

// compileSketchShapeDrawable applies the D2 fill and stroke to every emitted
// rough path instead of rough-go's per-set paint. Geometry remains structured,
// and the shared paint keeps filled outlines intact.
func ( *builder) (
	, , ,  string,
	 float64,
	 []float64,
	 float64,
	 rough.Drawable,
	 d2scene.Matrix,
	 d2target.Shape,
) ([]*d2scene.Node, error) {
	,  := .compileSketchDrawable(, )
	if  != nil {
		return nil, 
	}
	 := make([]*d2scene.Node, 0, len())
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		.Fill = noneIfSketchEmpty()
		.Stroke = noneIfSketchEmpty()
		.StrokeWidth = 
		.Dash = append([]float64(nil), ...)
		.DashOffset = 
		,  := .sketchScenePath(, )
		if  != nil {
			return nil, 
		}
		if .Fill == nil && .Stroke == nil {
			continue
		}
		 := d2scene.NewNode()
		.ID = fmt.Sprintf("%s:set:%d", , )
		.Transform = 
		.maskSketchShapeNode(, )
		 = append(, )
	}
	return , nil
}

func noneIfSketchEmpty( string) string {
	if  == "" {
		return libcolor.None
	}
	return 
}

func ( *builder) ( *d2scene.Node,  d2target.Shape) {
	if  == nil || .connectionMask == nil || .Label == "" || !label.FromString(.LabelPosition).IsBorder() {
		return
	}
	.Mask = .connectionMask
}

func ( *builder) ( d2target.Shape,  string) ([]*d2scene.Node, error) {
	if .Type == d2target.ShapeText {
		return nil, nil
	}
	if  == "" {
		 = .ID + ":sketch"
	}
	,  := d2themes.ShapeTheme()
	,  := .stroke(, .StrokeWidth, .StrokeDash, d2scene.CapRound, d2scene.JoinRound, fmt.Sprintf("shape %q sketch stroke", .ID))
	if  != nil {
		return nil, 
	}
	, ,  := sketchStrokeValues()
	 := sketchShapeOptions(, , )
	 := fmt.Sprintf("shape %q", .ID)

	var  []*d2scene.Node
	switch .Type {
	case d2target.ShapeOval, d2target.ShapeCircle:
		 := newSketchGenerator().Ellipse(
			float64(.Width)/2, float64(.Height)/2,
			float64(.Width), float64(.Height), ,
		)
		,  = .compileSketchShapeDrawable(
			, , , , , , ,
			, d2scene.Translate(float64(.Pos.X), float64(.Pos.Y)), ,
		)
	case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, "":
		 := newSketchGenerator().Rectangle(0, 0, float64(.Width), float64(.Height), )
		,  = .compileSketchShapeDrawable(
			, , , , , , ,
			, d2scene.Translate(float64(.Pos.X), float64(.Pos.Y)), ,
		)
	default:
		,  = .buildSketchTypedShapePaths(, , , , , , , , )
	}
	if  != nil {
		return nil, 
	}
	 := 
	,  = .appendSketchBuiltinPattern(, , )
	if  != nil {
		return nil, 
	}

	 := make([]d2scene.Primitive, 0, len())
	switch .Type {
	case d2target.ShapeOval, d2target.ShapeCircle:
		 = append(, d2scene.Ellipse{
			Center: d2scene.Point{
				X: float64(.Pos.X) + float64(.Width)/2,
				Y: float64(.Pos.Y) + float64(.Height)/2,
			},
			RadiusX: float64(.Width) / 2,
			RadiusY: float64(.Height) / 2,
		})
	case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, "":
		 = append(, d2scene.Rect{Box: sketchShapeBox()})
	default:
		for ,  := range  {
			if ,  := sketchNodePath();  {
				 = append(, )
			}
		}
	}
	,  := .buildSketchStreakOverlays(, , , )
	if  != nil {
		return nil, 
	}
	return append(, ...), nil
}

func sketchStrokeValues( *d2scene.Stroke) (float64, []float64, float64) {
	if  == nil {
		return 0, nil, 0
	}
	return .Width, append([]float64(nil), .Dashes...), .DashOffset
}

func sketchShapeBox( d2target.Shape) d2scene.Box {
	return d2scene.Box{
		X: float64(.Pos.X), Y: float64(.Pos.Y),
		Width: float64(.Width), Height: float64(.Height),
	}
}

func sketchNodePath( *d2scene.Node) (d2scene.Path, bool) {
	if  == nil {
		return d2scene.Path{}, false
	}
	switch path := .Primitive.(type) {
	case d2scene.Path:
		.Fill, .Stroke = nil, nil
		return , true
	case *d2scene.Path:
		if  == nil {
			return d2scene.Path{}, false
		}
		 := *
		.Fill, .Stroke = nil, nil
		return , true
	default:
		return d2scene.Path{}, false
	}
}

func ( *builder) ( d2target.Shape,  []*d2scene.Node,  string) ([]*d2scene.Node, error) {
	,  := .builtinPattern(.FillPattern)
	if  != nil {
		return nil, fmt.Errorf("scene: shape %q fill pattern: %w", .ID, )
	}
	if  == nil {
		return , nil
	}
	 := make([]*d2scene.Node, 0, len()*2)
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		 = append(, )
		,  := sketchNodePath()
		if ! {
			continue
		}
		if  := .chargeSketchPathCommands(
			fmt.Sprintf("shape %q sketch fill-pattern overlay", .ID), len(.Commands),
		);  != nil {
			return nil, 
		}
		,  := overlayPatternNode(, , fmt.Sprintf("%s:fill-pattern:%d", , ))
		if  != nil {
			return nil, 
		}
		 = append(, )
	}
	return , nil
}

func ( *builder) (
	 d2target.Shape,
	, , ,  string,
	 float64,
	 []float64,
	 float64,
	 *rough.Options,
) ([]*d2scene.Node, error) {
	 := targetGeometry()
	 := shape.GetPathCommands()
	if len() == 0 {
		return nil, unsupported(, "typed sketch geometry for "+.Type)
	}
	 := .remainingSketchOperations()
	if  <= 0 {
		return nil, fmt.Errorf("scene: %s sketch typed-path expansion exceeds remaining operation budget", )
	}
	// A rough polygon can retain a solid-fill operation per vertex plus two
	// multi-stroke operations per edge. Eight operations per flattened point,
	// with a small fixed reserve, is deliberately conservative and rejects
	// before rough-go allocates an over-budget drawable.
	 := ( - 32) / 8
	if  <= 1 {
		return nil, fmt.Errorf("scene: %s sketch typed-path expansion exceeds remaining operation budget", )
	}

	 := make([]flattenedSketchPath, 0, len())
	 := 0
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		,  := flattenSketchPath(.ctx, , -)
		if  != nil {
			return nil, fmt.Errorf("scene: %s typed sketch path %d: %w", , , )
		}
		for ,  := range  {
			 += len(.points)
			if  >  {
				return nil, fmt.Errorf("scene: %s sketch typed-path expansion exceeds remaining operation budget", )
			}
			 = append(, )
		}
	}

	var  []*d2scene.Node
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		if len(.points) < 2 {
			return nil, fmt.Errorf("scene: %s typed sketch path %d has fewer than two points", , )
		}
		 := newSketchGenerator()
		var  rough.Drawable
		if .closed {
			 = .Polygon(.points, )
		} else {
			 = .LinearPath(.points, )
		}
		,  := .compileSketchShapeDrawable(
			fmt.Sprintf("%s typed path %d", , ), fmt.Sprintf("%s:path:%d", , ),
			, , , , , , d2scene.Identity(), ,
		)
		if  != nil {
			return nil, 
		}
		 = append(, ...)
	}
	return , nil
}

type flattenedSketchPath struct {
	points []rough.Point
	closed bool
}

func flattenSketchPath( context.Context,  []svg.PathCommand,  int) ([]flattenedSketchPath, error) {
	if  <= 1 {
		return nil, fmt.Errorf("flattened point limit must exceed one")
	}
	var  []flattenedSketchPath
	var  flattenedSketchPath
	var ,  rough.Point
	 := false
	 := 0

	 := func( rough.Point) error {
		if !finite([0]) || !finite([1]) {
			return fmt.Errorf("non-finite typed point")
		}
		if len(.points) != 0 && .points[len(.points)-1] ==  {
			 = 
			return nil
		}
		if  >=  {
			return fmt.Errorf("flattened point count exceeds limit %d", )
		}
		.points = append(.points, )
		++
		 = 
		return nil
	}
	 := func() {
		if len(.points) != 0 {
			 = append(, )
		}
		 = flattenedSketchPath{}
		 = false
	}

	for ,  := range  {
		if &63 == 0 {
			if  := .Err();  != nil {
				return nil, 
			}
		}
		switch .Kind {
		case svg.PathCommandMove:
			()
			 = rough.Point{.End.X, .End.Y}
			if  := ();  != nil {
				return nil, fmt.Errorf("command %d: %w", , )
			}
			 = true
		case svg.PathCommandLine:
			if ! {
				return nil, fmt.Errorf("command %d line has no current point", )
			}
			if  := (rough.Point{.End.X, .End.Y});  != nil {
				return nil, fmt.Errorf("command %d: %w", , )
			}
		case svg.PathCommandCubic:
			if ! {
				return nil, fmt.Errorf("command %d cubic has no current point", )
			}
			,  := flattenSketchCubic(
				, ,
				rough.Point{.Control1.X, .Control1.Y},
				rough.Point{.Control2.X, .Control2.Y},
				rough.Point{.End.X, .End.Y},
				-,
			)
			if  != nil {
				return nil, fmt.Errorf("command %d: %w", , )
			}
			for ,  := range  {
				if  := ();  != nil {
					return nil, fmt.Errorf("command %d: %w", , )
				}
			}
		case svg.PathCommandClose:
			if ! {
				return nil, fmt.Errorf("command %d close has no current point", )
			}
			.closed = true
			 = 
		default:
			return nil, fmt.Errorf("command %d has unsupported kind %d", , .Kind)
		}
	}
	()
	if  := .Err();  != nil {
		return nil, 
	}
	if len() == 0 {
		return nil, fmt.Errorf("typed path is empty")
	}
	return , nil
}

func flattenSketchCubic( context.Context, , , ,  rough.Point,  int) ([]rough.Point, error) {
	if  <= 0 {
		return nil, fmt.Errorf("flattened point count exceeds limit")
	}
	 := make([]rough.Point, 0, 16)
	var  func(rough.Point, rough.Point, rough.Point, rough.Point, int) error
	 = func(, , ,  rough.Point,  int) error {
		if  := .Err();  != nil {
			return 
		}
		if  >= sketchCubicMaxDepth || cubicSketchFlatEnough(, , , ) {
			if len() >=  {
				return fmt.Errorf("flattened point count exceeds limit")
			}
			 = append(, )
			return nil
		}
		, ,  := roughMidpoint(, ), roughMidpoint(, ), roughMidpoint(, )
		,  := roughMidpoint(, ), roughMidpoint(, )
		 := roughMidpoint(, )
		if  := (, , , , +1);  != nil {
			return 
		}
		return (, , , , +1)
	}
	if  := (, , , , 0);  != nil {
		return nil, 
	}
	return , nil
}

func roughMidpoint(,  rough.Point) rough.Point {
	return rough.Point{([0] + [0]) / 2, ([1] + [1]) / 2}
}

func cubicSketchFlatEnough(, , ,  rough.Point) bool {
	 := sketchCubicFlatness * sketchCubicFlatness
	return sketchPointLineDistanceSquared(, , ) <=  &&
		sketchPointLineDistanceSquared(, , ) <= 
}

func sketchPointLineDistanceSquared(, ,  rough.Point) float64 {
	,  := [0]-[0], [1]-[1]
	 := * + *
	if  == 0 {
		,  := [0]-[0], [1]-[1]
		return * + *
	}
	 := *[0] - *[1] + [0]*[1] - [1]*[0]
	return  *  / 
}

func ( *builder) ( d2target.Shape,  d2scene.Paint,  *d2scene.Stroke) ([]*d2scene.Node, error) {
	if .ThreeDee {
		// SVG sketch leaves 3D faces precise; only ordinary outlines are
		// roughened. Keep its main-face fill-pattern behavior and omit streaks.
		,  := .buildShapeEffects(, , )
		if  != nil {
			return nil, 
		}
		,  := .builtinPattern(.FillPattern)
		if  != nil {
			return nil, fmt.Errorf("scene: shape %q fill pattern: %w", .ID, )
		}
		return .interleavePattern(, , .ID, effectPatternNode())
	}
	if .DoubleBorder {
		return .buildSketchDoubleBorderShape(, , )
	}
	if .Multiple {
		 := 
		.Pos.X += d2target.MULTIPLE_OFFSET
		.Pos.Y -= d2target.MULTIPLE_OFFSET
		,  := .buildOrdinaryShapeOutline(, , , .ID+":multiple")
		if  != nil {
			return nil, 
		}
		,  := .buildSketchOrdinaryShape(, .ID+":main:sketch")
		if  != nil {
			return nil, 
		}
		return append(, ...), nil
	}
	return nil, fmt.Errorf("scene: shape %q has no sketch shape effect", .ID)
}

func ( *builder) ( d2target.Shape,  d2scene.Paint,  *d2scene.Stroke) ([]*d2scene.Node, error) {
	var  []*d2scene.Node
	if .Multiple {
		 := 
		.Pos.X += d2target.MULTIPLE_OFFSET
		.Pos.Y -= d2target.MULTIPLE_OFFSET
		 := doubleBorderPair(, , , , .ID+":multiple")
		if .Type == "" || .Type == d2target.ShapeRectangle || .Type == d2target.ShapeSquare {
			,  := .builtinPattern(.FillPattern)
			if  != nil {
				return nil, fmt.Errorf("scene: shape %q fill pattern: %w", .ID, )
			}
			,  = .interleavePattern(, , .ID, func( *d2scene.Node) bool {
				return  != nil && .ID == .ID+":multiple:outer"
			})
			if  != nil {
				return nil, 
			}
		}
		 = append(, ...)
	}

	 := 
	.Multiple = false
	,  := .buildSketchOutlineOnly(, .ID+":double-border:outer:sketch")
	if  != nil {
		return nil, 
	}
	,  = .appendSketchBuiltinPattern(, , .ID+":double-border:outer")
	if  != nil {
		return nil, 
	}
	 = append(, ...)

	 := 
	.Pos.X += d2target.INNER_BORDER_OFFSET
	.Pos.Y += d2target.INNER_BORDER_OFFSET
	.Width -= 2 * d2target.INNER_BORDER_OFFSET
	.Height -= 2 * d2target.INNER_BORDER_OFFSET
	.Fill = "transparent"
	,  := .buildSketchOutlineOnly(, .ID+":double-border:inner:sketch")
	if  != nil {
		return nil, 
	}
	 = append(, ...)

	,  := d2themes.ShapeTheme()
	var  d2scene.Primitive
	if .Type == d2target.ShapeOval || .Type == d2target.ShapeCircle {
		 := sketchShapeBox()
		 = d2scene.Ellipse{
			Center:  d2scene.Point{X: .X + .Width/2, Y: .Y + .Height/2},
			RadiusX: .Width / 2, RadiusY: .Height / 2,
		}
	} else {
		 = d2scene.Rect{Box: sketchShapeBox()}
	}
	,  := .buildSketchStreakOverlays(, , .ID+":double-border", []d2scene.Primitive{})
	if  != nil {
		return nil, 
	}
	return append(, ...), nil
}

func ( *builder) ( d2target.Shape,  string) ([]*d2scene.Node, error) {
	,  := d2themes.ShapeTheme()
	,  := .stroke(, .StrokeWidth, .StrokeDash, d2scene.CapRound, d2scene.JoinRound, fmt.Sprintf("shape %q sketch stroke", .ID))
	if  != nil {
		return nil, 
	}
	, ,  := sketchStrokeValues()
	 := sketchShapeOptions(, , )
	 := fmt.Sprintf("shape %q", .ID)
	switch .Type {
	case d2target.ShapeOval, d2target.ShapeCircle:
		return .compileSketchShapeDrawable(
			, , , , , , ,
			newSketchGenerator().Ellipse(
				float64(.Width)/2, float64(.Height)/2,
				float64(.Width), float64(.Height), ,
			),
			d2scene.Translate(float64(.Pos.X), float64(.Pos.Y)), ,
		)
	case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, "":
		return .compileSketchShapeDrawable(
			, , , , , , ,
			newSketchGenerator().Rectangle(0, 0, float64(.Width), float64(.Height), ),
			d2scene.Translate(float64(.Pos.X), float64(.Pos.Y)), ,
		)
	default:
		return .buildSketchTypedShapePaths(, , , , , , , , )
	}
}

func ( *builder) ( d2target.Shape,  d2scene.Paint,  *d2scene.Stroke) ([]*d2scene.Node, error) {
	,  := .buildStructuredShape(, , )
	if  != nil {
		return nil, 
	}
	 := sketchShapeBox()
	 := structuredSketchHeaderHeight()
	 := d2scene.Box{X: .X, Y: .Y, Width: .Width, Height: }
	,  := d2themes.ShapeTheme()
	, ,  := sketchStrokeValues()
	 := 

	 := make([]*d2scene.Node, 0, len()+8)
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		switch {
		case  != nil && .ID == .ID+":outline":
			,  := .compileSketchRectangle(
				fmt.Sprintf("shape %q structured outline", .ID), .ID+":outline:sketch",
				, , , , , , ,
			)
			if  != nil {
				return nil, 
			}
			,  = .appendSketchBuiltinPattern(, , .ID+":outline")
			if  != nil {
				return nil, 
			}
			 = append(, ...)
		case  != nil && (.ID == .ID+":class-header" || .ID == .ID+":table-header"):
			,  := .paint(.Fill, fmt.Sprintf("shape %q structured sketch header", .ID))
			if  != nil {
				return nil, 
			}
			_ =  // validates the raw paint before rough generation.
			,  := .compileSketchRectangle(
				fmt.Sprintf("shape %q structured header", .ID), .ID+":sketch",
				, .Fill, "", 1, nil, 0, ,
			)
			if  != nil {
				return nil, 
			}
			,  = .appendSketchBuiltinPattern(, , .ID)
			if  != nil {
				return nil, 
			}
			 = append(, ...)
			if .Type == d2target.ShapeClass {
				,  := .buildSketchStreakOverlays(, .Fill, .ID, []d2scene.Primitive{d2scene.Rect{Box: }})
				if  != nil {
					return nil, 
				}
				 = append(, ...)
			}
		case  != nil && (strings.HasSuffix(.ID, ":separator") || .ID == .ID+":class-separator"):
			, ,  := sketchLineEndpoints()
			if ! {
				return nil, fmt.Errorf("scene: shape %q structured separator %q is not a typed line", .ID, .ID)
			}
			 := 1.0
			if .Type == d2target.ShapeSQLTable {
				 = 2
			}
			,  := .compileSketchLine(
				fmt.Sprintf("shape %q structured separator", .ID), .ID+":sketch",
				, , .Fill, "", , ,
			)
			if  != nil {
				return nil, 
			}
			,  = .appendSketchBuiltinPattern(, , .ID)
			if  != nil {
				return nil, 
			}
			 = append(, ...)
		default:
			 = append(, )
		}
	}
	if .Type == d2target.ShapeSQLTable {
		,  := .buildSketchStreakOverlays(, .Fill, .ID+":table", []d2scene.Primitive{d2scene.Rect{Box: }})
		if  != nil {
			return nil, 
		}
		 = append(, ...)
	}
	return , nil
}

func structuredSketchHeaderHeight( d2target.Shape) float64 {
	switch .Type {
	case d2target.ShapeClass:
		 := 2 + len(.Fields) + len(.Methods)
		 := float64(.Height) / float64()
		return math.Max(2*, float64(.LabelHeight)+2*label.PADDING)
	case d2target.ShapeSQLTable:
		return float64(.Height) / float64(1+len(.Columns))
	default:
		return 0
	}
}

func ( *builder) (
	,  string,
	 d2scene.Box,
	,  string,
	 float64,
	 []float64,
	 float64,
	 d2target.Shape,
) ([]*d2scene.Node, error) {
	 := sketchShapeOptions(, , )
	 := newSketchGenerator().Rectangle(0, 0, .Width, .Height, )
	return .compileSketchShapeDrawable(
		, , , , , , ,
		, d2scene.Translate(.X, .Y), ,
	)
}

func ( *builder) (
	,  string,
	,  d2scene.Point,
	,  string,
	 float64,
	 d2target.Shape,
) ([]*d2scene.Node, error) {
	 := newSketchGenerator().Line(.X, .Y, .X, .Y, sketchShapeOptions(, nil, 0))
	return .compileSketchShapeDrawable(
		, , , , , nil, 0,
		, d2scene.Identity(), ,
	)
}

func sketchLineEndpoints( *d2scene.Node) (d2scene.Point, d2scene.Point, bool) {
	,  := sketchNodePath()
	if ! || len(.Commands) != 2 || .Commands[0].Kind != d2scene.MoveCommand || .Commands[1].Kind != d2scene.LineCommand {
		return d2scene.Point{}, d2scene.Point{}, false
	}
	return .Commands[0].P1, .Commands[1].P1, true
}

func ( *builder) (
	 d2target.Shape,
	,  string,
	 []d2scene.Primitive,
) ([]*d2scene.Node, error) {
	if len() == 0 || strings.EqualFold(, libcolor.None) ||  == "" {
		return nil, nil
	}
	, , ,  := .sketchStreakPattern(, fmt.Sprintf("shape %q", .ID))
	if  != nil {
		return nil, 
	}
	 := make([]*d2scene.Node, 0, len())
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		if  := sketchPrimitivePathCommandCount();  != 0 {
			if  := .chargeSketchPathCommands(
				fmt.Sprintf("shape %q sketch streak overlay", .ID), ,
			);  != nil {
				return nil, 
			}
		}
		,  := sketchOverlayPrimitive(, )
		if  != nil {
			return nil, fmt.Errorf("scene: shape %q streak overlay %d: %w", .ID, , )
		}
		 := d2scene.NewNode()
		.ID = fmt.Sprintf("%s:streak:%d", , )
		.Classes = []string{"sketch-streak-overlay", "sketch-streak-" + }
		.Blend = 
		.maskSketchShapeNode(, )
		 = append(, )
	}
	return , nil
}

func sketchPrimitivePathCommandCount( d2scene.Primitive) int {
	switch value := .(type) {
	case d2scene.Path:
		return len(.Commands)
	case *d2scene.Path:
		if  != nil {
			return len(.Commands)
		}
	}
	return 0
}

func sketchOverlayPrimitive( d2scene.Primitive,  d2scene.PatternPaint) (d2scene.Primitive, error) {
	switch value := .(type) {
	case d2scene.Rect:
		.Fill, .Stroke = , nil
		return , nil
	case *d2scene.Rect:
		if  == nil {
			return nil, fmt.Errorf("nil rectangle")
		}
		 := *
		.Fill, .Stroke = , nil
		return , nil
	case d2scene.Ellipse:
		.Fill, .Stroke = , nil
		return , nil
	case *d2scene.Ellipse:
		if  == nil {
			return nil, fmt.Errorf("nil ellipse")
		}
		 := *
		.Fill, .Stroke = , nil
		return , nil
	case d2scene.Path:
		.Commands = append([]d2scene.PathCommand(nil), .Commands...)
		.Fill, .Stroke = , nil
		return , nil
	case *d2scene.Path:
		if  == nil {
			return nil, fmt.Errorf("nil path")
		}
		 := *
		.Commands = append([]d2scene.PathCommand(nil), .Commands...)
		.Fill, .Stroke = , nil
		return , nil
	default:
		return nil, fmt.Errorf("primitive %T cannot carry a sketch streak", )
	}
}

func ( *builder) (,  string) (d2scene.PatternPaint, d2scene.BlendMode, string, error) {
	,  := .sketchLuminanceCategory()
	if  != nil {
		return d2scene.PatternPaint{}, d2scene.BlendNormal, "", fmt.Errorf("scene: %s sketch streak fill %q: %w", , , )
	}
	, ,  := sketchStreakStyle()
	if  != nil {
		return d2scene.PatternPaint{}, d2scene.BlendNormal, "", 
	}
	if ,  := .sketchStreakPatterns[];  {
		return , , , nil
	}
	if .sketchStreakCommands == nil {
		,  := sharedSketchStreakCommands(.ctx)
		if  != nil {
			return d2scene.PatternPaint{}, d2scene.BlendNormal, "", fmt.Errorf("scene: load built-in sketch streak: %w", )
		}
		.sketchStreakCommands = append([]d2scene.PathCommand(nil), ...)
	}
	if  := .chargeSketchPathCommands(+" sketch streak pattern", len(.sketchStreakCommands));  != nil {
		return d2scene.PatternPaint{}, d2scene.BlendNormal, "", 
	}
	,  := sketchStreakRoot(.ctx, .sketchStreakCommands, , )
	if  != nil {
		return d2scene.PatternPaint{}, d2scene.BlendNormal, "", fmt.Errorf("scene: build built-in sketch streak: %w", )
	}
	 := d2scene.PatternPaint{
		Tile: d2scene.Box{Width: 100, Height: 100}, Root: ,
		Units: d2scene.UserSpaceOnUse, Transform: d2scene.Identity(),
	}
	if .sketchStreakPatterns == nil {
		.sketchStreakPatterns = make(map[string]d2scene.PatternPaint, 4)
	}
	.sketchStreakPatterns[] = 
	return , , , nil
}

// sketchStreakRoot preserves the canonical streak path's even-odd result while
// bounding raster point-in-edge work to each local closed shape. Subpaths 16
// and 17 form the source's sole nested compound shape and must remain in one
// even-odd primitive. Every command slice otherwise aliases the builder-owned,
// once-accounted backing store.
func sketchStreakRoot( context.Context,  []d2scene.PathCommand,  color.NRGBA,  string) (*d2scene.Node, error) {
	if  == nil {
		 = context.Background()
	}
	if  := .Err();  != nil {
		return nil, 
	}
	 := make([]int, 0, sketchStreakSubpathCount+1)
	for ,  := range  {
		if &255 == 0 {
			if  := .Err();  != nil {
				return nil, 
			}
		}
		if .Kind == d2scene.MoveCommand {
			 = append(, )
		}
	}
	if len() != sketchStreakSubpathCount || len() == 0 || [0] != 0 {
		return nil, fmt.Errorf("unexpected subpath topology: got %d subpaths, want %d", len(), sketchStreakSubpathCount)
	}
	 = append(, len())
	for  := 0;  < sketchStreakSubpathCount; ++ {
		if [+1] <= []+1 || [[+1]-1].Kind != d2scene.CloseCommand {
			return nil, fmt.Errorf("unexpected open or empty subpath %d", )
		}
	}

	 := d2scene.NewNode(nil)
	.ID = "builtin:sketch-streak:" + 
	for  := 0;  < sketchStreakSubpathCount; ++ {
		if  := .Err();  != nil {
			return nil, 
		}
		 :=  + 1
		if  == sketchStreakCompoundSubpath {
			 =  + sketchStreakCompoundSubpathSpan
		}
		 := d2scene.Path{
			Commands: [[]:[]],
			FillRule: d2scene.EvenOdd,
			Fill:     d2scene.SolidPaint{Color: },
		}
		 := d2scene.NewNode()
		.ID = fmt.Sprintf("%s:subpath:%d", .ID, )
		.Children = append(.Children, )
		if  > +1 {
			 =  - 1
		}
	}
	return , .Err()
}

func ( *builder) ( string) (string, error) {
	if libcolor.IsGradient() || libcolor.IsURLGradientID() {
		return "normal", nil
	}
	 := d2themes.ResolveThemeColor(.theme, )
	if  == "" {
		return "", fmt.Errorf("unknown theme color")
	}
	return libcolor.LuminanceCategory()
}

func sketchStreakStyle( string) (d2scene.BlendMode, color.NRGBA, error) {
	switch  {
	case "bright":
		return d2scene.BlendDarken, color.NRGBA{A: 26}, nil
	case "normal":
		return d2scene.BlendColorBurn, color.NRGBA{A: 41}, nil
	case "dark":
		return d2scene.BlendOverlay, color.NRGBA{A: 82}, nil
	case "darker":
		return d2scene.BlendLighten, color.NRGBA{R: 255, G: 255, B: 255, A: 61}, nil
	default:
		return d2scene.BlendNormal, color.NRGBA{}, fmt.Errorf("scene: invalid sketch streak luminance category %q", )
	}
}