package d2raster

import (
	
	
	

	

	
)

// retainedVectorValidator validates the supported scene structure without
// selecting any device transform. Each retained vector definition is visited
// exactly once; visible Image instances are prepared separately by preflight.
// Cached summaries retain both the longest vector-import chain and composed
// node depth, preserving both limits when a dependency was memoized from an
// earlier sorted root.
type retainedVectorValidator struct {
	preflight           *preflight
	validated           map[d2scene.AssetID]retainedVectorSummary
	active              map[d2scene.AssetID]bool
	nodes               map[*d2scene.Node]bool
	ancestorsInvertible bool
}

// retainedVectorSummary describes work depth independently of any placement.
// importHeight counts vector definitions beginning with the summarized asset;
// nodeHeight counts nodes beginning with its root and continues across normal,
// mask, and vector-image edges.
type retainedVectorSummary struct {
	importHeight int
	nodeHeight   int
}

func ( *preflight) ( []string) error {
	if len(.vectors) == 0 {
		return nil
	}
	 := &retainedVectorValidator{
		preflight:           ,
		validated:           make(map[d2scene.AssetID]retainedVectorSummary, len(.vectors)),
		active:              make(map[d2scene.AssetID]bool),
		nodes:               make(map[*d2scene.Node]bool),
		ancestorsInvertible: true,
	}
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return 
		}
		 := d2scene.AssetID()
		if ,  := .vectors[]; ! {
			continue
		}
		// A VectorAsset cannot be a Document root directly. Its shallowest
		// possible placement has a host Image at depth one and the asset root
		// at depth two, so strict retained validation rejects definitions that
		// cannot fit in any visible placement.
		if ,  := .asset(, 1, 2);  != nil {
			return 
		}
	}
	return nil
}

func ( *retainedVectorValidator) ( d2scene.AssetID, ,  int) (retainedVectorSummary, error) {
	 := .preflight
	if  := .ctx.Err();  != nil {
		return retainedVectorSummary{}, 
	}
	if  > .options.MaxImportDepth {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: vector asset %q import depth %d exceeds limit %d", , , .options.MaxImportDepth)
	}
	if .active[] {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: cyclic vector asset reference at %q", )
	}
	if ,  := .validated[];  {
		if  := .checkSummaryDepths(, , , );  != nil {
			return retainedVectorSummary{}, 
		}
		return , nil
	}
	,  := .vectors[]
	if ! {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: missing vector asset %q", )
	}
	.active[] = true
	defer delete(.active, )

	 := .ancestorsInvertible
	.ancestorsInvertible = true
	,  := .node(.Root, 1, )
	.ancestorsInvertible = 
	if  != nil {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: vector asset %q retained validation: %w", , )
	}
	 := retainedVectorSummary{
		importHeight: 1,
		nodeHeight:   .nodeHeight,
	}
	if .importHeight != 0 {
		.importHeight += .importHeight
	}
	.validated[] = 
	if  := .checkSummaryDepths(, , , );  != nil {
		return retainedVectorSummary{}, 
	}
	return , nil
}

func ( *retainedVectorValidator) ( d2scene.AssetID,  retainedVectorSummary, ,  int) error {
	 := .preflight
	 :=  + .importHeight - 1
	if  > .options.MaxImportDepth {
		return fmt.Errorf("d2raster: vector asset %q import depth %d exceeds limit %d", , , .options.MaxImportDepth)
	}
	 :=  + .nodeHeight - 1
	if  > .options.MaxDepth {
		return fmt.Errorf("d2raster: vector asset %q composed node depth %d exceeds limit %d", , , .options.MaxDepth)
	}
	return nil
}

// node returns relative import and composed node heights for this subtree.
// Definitions are charged only on their first structural visit; cached
// summaries propagate depth across vector-image edges without retraversal.
func ( *retainedVectorValidator) ( *d2scene.Node, ,  int) (retainedVectorSummary, error) {
	 := .preflight
	if  := .ctx.Err();  != nil {
		return retainedVectorSummary{}, 
	}
	if  == nil {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: nil retained vector node")
	}
	if  > .options.MaxDepth {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: node depth %d exceeds limit %d", , .options.MaxDepth)
	}
	if .nodes[] {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: node cycle at %q", .ID)
	}
	.nodes++
	if .nodes > .options.MaxNodes {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: node count exceeds limit %d", .options.MaxNodes)
	}
	if !.Transform.IsFinite() || !finite(.Opacity) || .Opacity < 0 || .Opacity > 1 {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: node %q has invalid transform or opacity", .ID)
	}
	if !supportedBlendMode(.Blend) {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: node %q uses invalid or unsupported blend mode %d", .ID, .Blend)
	}
	if  := .chargeFilterWork(.ID, .Filters);  != nil {
		return retainedVectorSummary{}, 
	}
	, ,  := .animations()
	if  != nil {
		return retainedVectorSummary{}, 
	}
	if ,  := normalizeNodeFilters(.ctx, .ID, .Filters, .dropShadows);  != nil {
		return retainedVectorSummary{}, 
	}
	 := .ancestorsInvertible
	.ancestorsInvertible =  && finiteLinearTransformInvertible()
	defer func() { .ancestorsInvertible =  }()
	.nodes[] = true
	defer delete(.nodes, )

	, ,  := .primitive(.ID, .Primitive, , , )
	if  != nil {
		return retainedVectorSummary{}, 
	}
	 := retainedVectorSummary{importHeight: .importHeight, nodeHeight: 1}
	if .nodeHeight != 0 {
		.nodeHeight = 1 + .nodeHeight
	}
	if ! && (.fillColor != nil || .strokeColor != nil || .dashOffset != nil) {
		return retainedVectorSummary{}, fmt.Errorf("d2raster: node %q has a paint or stroke animation but no primitive", .ID)
	}
	if .Clip != nil {
		if !.Clip.Transform.IsFinite() {
			return retainedVectorSummary{}, fmt.Errorf("d2raster: node %q has invalid clip transform", .ID)
		}
		if .Clip.Path.FillRule > d2scene.EvenOdd {
			return retainedVectorSummary{}, fmt.Errorf("d2raster: node %q clip has invalid fill rule %d", .ID, .Clip.Path.FillRule)
		}
		if  := .path(.Clip.Path);  != nil {
			return retainedVectorSummary{}, fmt.Errorf("d2raster: node %q clip: %w", .ID, )
		}
	}
	if .Mask != nil {
		if !.Mask.Transform.IsFinite() || .Mask.Type > d2scene.MaskLuminance || .Mask.Root == nil {
			return retainedVectorSummary{}, fmt.Errorf("d2raster: node %q has invalid mask", .ID)
		}
		 := .ancestorsInvertible
		.ancestorsInvertible =  && finiteLinearTransformInvertible(.Mask.Transform)
		,  := .(.Mask.Root, +1, )
		.ancestorsInvertible = 
		if  != nil {
			return retainedVectorSummary{}, fmt.Errorf("d2raster: node %q mask: %w", .ID, )
		}
		 = mergeRetainedNodeSummary(, )
	}
	for ,  := range .Children {
		if  := .ctx.Err();  != nil {
			return retainedVectorSummary{}, 
		}
		if  == nil {
			return retainedVectorSummary{}, fmt.Errorf("d2raster: node %q child %d is nil", .ID, )
		}
		,  := .(, +1, )
		if  != nil {
			return retainedVectorSummary{}, 
		}
		 = mergeRetainedNodeSummary(, )
	}
	return , nil
}

func mergeRetainedNodeSummary(,  retainedVectorSummary) retainedVectorSummary {
	.importHeight = maxRetainedHeight(.importHeight, .importHeight)
	.nodeHeight = maxRetainedHeight(.nodeHeight, 1+.nodeHeight)
	return 
}

func mergeRetainedPrimitiveSummary(,  retainedVectorSummary) retainedVectorSummary {
	return retainedVectorSummary{
		importHeight: maxRetainedHeight(.importHeight, .importHeight),
		nodeHeight:   maxRetainedHeight(.nodeHeight, .nodeHeight),
	}
}

func ( *retainedVectorValidator) ( *d2scene.Node) (animationOverrides, d2scene.Matrix, error) {
	 := .preflight
	if  := .chargeAnimationWork(.ID, .Animations);  != nil {
		return animationOverrides{}, d2scene.Matrix{}, 
	}
	var  animationOverrides
	 := .Transform
	 := make(map[[2]int]bool, len(.Animations))
	for ,  := range .Animations {
		if  := .ctx.Err();  != nil {
			return animationOverrides{}, d2scene.Matrix{}, 
		}
		 := [2]int{int(.Property), .TargetIndex}
		if [] {
			return animationOverrides{}, d2scene.Matrix{}, fmt.Errorf("d2raster: node %q has duplicate animation target property %d index %d", .ID, .Property, .TargetIndex)
		}
		[] = true
		,  := .animationValueAt()
		if  != nil {
			return animationOverrides{}, d2scene.Matrix{}, fmt.Errorf("d2raster: node %q animation %d: %w", .ID, , )
		}
		if .Property != d2scene.AnimateDropShadow && .TargetIndex != 0 {
			return animationOverrides{}, d2scene.Matrix{}, fmt.Errorf("d2raster: node %q animation %d uses non-zero target index for scalar property %d", .ID, , .Property)
		}
		switch .Property {
		case d2scene.AnimateOpacity:
			if .Number < 0 || .Number > 1 {
				return animationOverrides{}, d2scene.Matrix{}, fmt.Errorf("d2raster: node %q animation %d resolves opacity outside [0,1]", .ID, )
			}
		case d2scene.AnimateTransform:
			 = .Transform
		case d2scene.AnimateStrokeDashOffset:
			 := .Number
			.dashOffset = &
		case d2scene.AnimateFillColor:
			 := .Color
			.fillColor = &
		case d2scene.AnimateStrokeColor:
			 := .Color
			.strokeColor = &
		case d2scene.AnimateDropShadow:
			if .dropShadows == nil {
				.dropShadows = make(map[int]d2scene.DropShadow)
			}
			.dropShadows[.TargetIndex] = .Shadow
		default:
			return animationOverrides{}, d2scene.Matrix{}, fmt.Errorf("d2raster: node %q animation %d uses unknown property %d", .ID, , .Property)
		}
	}
	return , , nil
}

func ( *retainedVectorValidator) ( string,  d2scene.Primitive,  animationOverrides, ,  int) (retainedVectorSummary, bool, error) {
	switch primitive := .(type) {
	case nil:
		return retainedVectorSummary{}, false, nil
	case d2scene.Rect:
		return .rect(, , , , )
	case *d2scene.Rect:
		if  == nil {
			return retainedVectorSummary{}, false, nil
		}
		return .rect(, *, , , )
	case d2scene.Ellipse:
		return .ellipse(, , , , )
	case *d2scene.Ellipse:
		if  == nil {
			return retainedVectorSummary{}, false, nil
		}
		return .ellipse(, *, , , )
	case d2scene.Path:
		return .scenePath(, , , , )
	case *d2scene.Path:
		if  == nil {
			return retainedVectorSummary{}, false, nil
		}
		return .scenePath(, *, , , )
	case d2scene.TextRun:
		return .text(, , , , )
	case *d2scene.TextRun:
		if  == nil {
			return retainedVectorSummary{}, false, nil
		}
		return .text(, *, , , )
	case d2scene.Image:
		return .image(, , , , )
	case *d2scene.Image:
		if  == nil {
			return retainedVectorSummary{}, false, nil
		}
		return .image(, *, , , )
	default:
		return retainedVectorSummary{}, false, fmt.Errorf("d2raster: node %q uses unsupported primitive %T", , )
	}
}

func ( *retainedVectorValidator) ( string,  d2scene.Rect,  animationOverrides, ,  int) (retainedVectorSummary, bool, error) {
	if  := validateBox(.Box);  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q rectangle: %w", , )
	}
	if !finite(.RadiusX) || !finite(.RadiusY) || .RadiusX < 0 || .RadiusY < 0 {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q rectangle has invalid corner radius", )
	}
	 := .Box
	, ,  := .paint(.Fill, .fillColor != nil, &, , )
	if  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q rectangle fill: %w", , )
	}
	,  := .stroke(.Stroke, .strokeColor != nil, .dashOffset != nil, &, , )
	if  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q rectangle stroke: %w", , )
	}
	return mergeRetainedPrimitiveSummary(, ), true, nil
}

func ( *retainedVectorValidator) ( string,  d2scene.Ellipse,  animationOverrides, ,  int) (retainedVectorSummary, bool, error) {
	if !finitePoint(.Center) || !finite(.RadiusX) || !finite(.RadiusY) || .RadiusX < 0 || .RadiusY < 0 {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q ellipse has invalid geometry", )
	}
	 := d2scene.Box{
		X: .Center.X - .RadiusX, Y: .Center.Y - .RadiusY,
		Width: 2 * .RadiusX, Height: 2 * .RadiusY,
	}
	, ,  := .paint(.Fill, .fillColor != nil, &, , )
	if  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q ellipse fill: %w", , )
	}
	,  := .stroke(.Stroke, .strokeColor != nil, .dashOffset != nil, &, , )
	if  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q ellipse stroke: %w", , )
	}
	return mergeRetainedPrimitiveSummary(, ), true, nil
}

func ( *retainedVectorValidator) ( string,  d2scene.Path,  animationOverrides, ,  int) (retainedVectorSummary, bool, error) {
	if .FillRule > d2scene.EvenOdd {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q path has invalid fill rule %d", , .FillRule)
	}
	if  := .path();  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q path: %w", , )
	}
	var  *d2scene.Box
	if paintUsesObjectBoundingBox(.Fill) || strokeUsesObjectBoundingBox(.Stroke) {
		,  := .GeometryBounds()
		if  != nil {
			return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q path bounds: %w", , )
		}
		 := .Box()
		 = &
	}
	, ,  := .paint(.Fill, .fillColor != nil, , , )
	if  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q path fill: %w", , )
	}
	,  := .stroke(.Stroke, .strokeColor != nil, .dashOffset != nil, , , )
	if  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q path stroke: %w", , )
	}
	return mergeRetainedPrimitiveSummary(, ), true, nil
}

func ( *retainedVectorValidator) ( string,  d2scene.TextRun,  animationOverrides, ,  int) (retainedVectorSummary, bool, error) {
	 := .preflight
	if !finitePoint(.Origin) || !finite(.Font.Size) || .Font.Size <= 0 || .Font.Size > float64(math.MaxInt32)/64 {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q text has invalid origin or font size", )
	}
	if .Anchor > d2scene.AnchorEnd {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q text has invalid anchor %d", , .Anchor)
	}
	if !.Ink.IsFinite() {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q text has invalid ink bounds", )
	}
	if .Font.Asset == "" {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q text has empty font asset ID", )
	}
	,  := .fonts[.Font.Asset]
	if ! {
		if ,  := .document.Assets[.Font.Asset];  {
			return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q text asset %q is not a usable font", , .Font.Asset)
		}
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q text references missing font asset %q", , .Font.Asset)
	}
	 := fixed.Int26_6(math.Round(.Font.Size * 64))
	, ,  := .positionGlyphs(, )
	if  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q text: %w", , )
	}
	var  *d2scene.Box
	if .Ink.Valid {
		 := .Ink.Box()
		 = &
	} else if paintUsesObjectBoundingBox(.Fill) || strokeUsesObjectBoundingBox(.Stroke) {
		,  := .textObjectBounds(, , , )
		if  != nil {
			return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q text bounds: %w", , )
		}
		 = &
	}
	, ,  := .paint(.Fill, .fillColor != nil, , , )
	if  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q text fill: %w", , )
	}
	,  := .stroke(.Stroke, .strokeColor != nil, .dashOffset != nil, , , )
	if  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q text stroke: %w", , )
	}
	return mergeRetainedPrimitiveSummary(, ), true, nil
}

func ( *retainedVectorValidator) ( d2scene.TextRun,  fixed.Int26_6,  []positionedGlyph,  float64) (d2scene.Box, error) {
	 := 0.0
	switch .Anchor {
	case d2scene.AnchorStart:
	case d2scene.AnchorMiddle:
		 = - / 2
	case d2scene.AnchorEnd:
		 = -
	}
	var  d2scene.Bounds
	for ,  := range  {
		if  := .preflight.ctx.Err();  != nil {
			return d2scene.Box{}, 
		}
		if .empty {
			continue
		}
		, ,  := .font.glyphRenderBounds(uint32(.id), )
		if  != nil {
			return d2scene.Box{}, fmt.Errorf("load glyph %d (ID %d) render bounds from font asset %q: %w", , .id, .asset, )
		}
		 := .Origin.X +  + .position.X
		 := .Origin.Y + .position.Y
		 = .Union(d2scene.NewBounds(
			+fixedToFloat(.Min.X),
			+fixedToFloat(.Min.Y),
			+fixedToFloat(.Max.X),
			+fixedToFloat(.Max.Y),
		))
	}
	if (.Underline || .Strike) &&  > 0 {
		// Only zero-area detection is needed here. Visible preparation derives
		// exact decoration metrics under the real placement transform.
		 := .Origin.X + 
		 = .Union(d2scene.NewBounds(, .Origin.Y, +, .Origin.Y+.Font.Size/16))
	}
	return .Box(), nil
}

func ( *retainedVectorValidator) ( string,  d2scene.Image,  animationOverrides, ,  int) (retainedVectorSummary, bool, error) {
	 := .preflight
	if .fillColor != nil || .strokeColor != nil || .dashOffset != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q image cannot be targeted by paint or stroke animation", )
	}
	if .Asset == "" {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q image has an empty asset ID", )
	}
	if  := validateBox(.Box);  != nil {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q image: %w", , )
	}
	if .Aspect.Align > d2scene.AlignXMaxYMax || .Aspect.Fit > d2scene.AspectSlice {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q image has invalid aspect-ratio policy align=%d fit=%d", , .Aspect.Align, .Aspect.Fit)
	}
	if ,  := .rasters[.Asset];  {
		return retainedVectorSummary{}, true, nil
	}
	if ,  := .vectors[.Asset];  {
		// AspectRatioMatrix is independent of outer placement and can itself
		// overflow for incompatible finite source/destination dimensions. Mirror
		// visible preparation for painting instances; zero-area or singular-CTM
		// images deliberately use canonical accounting and paint nothing.
		if .Box.Width != 0 && .Box.Height != 0 && .ancestorsInvertible {
			,  := d2scene.AspectRatioMatrix(.ViewBox, .Box, .Aspect)
			if  != nil {
				return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q vector asset %q aspect ratio: %w", , .Asset, )
			}
			if !finiteLinearTransformInvertible() {
				return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q vector asset %q aspect ratio: mapping is singular in the finite numeric domain", , .Asset)
			}
		}
		,  := .asset(.Asset, +1, +1)
		return , true, 
	}
	if ,  := .document.Assets[.Asset];  {
		return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q image asset %q is not a raster or vector asset (got %T)", , .Asset, )
	}
	return retainedVectorSummary{}, true, fmt.Errorf("d2raster: node %q image references missing asset %q", , .Asset)
}

func ( *retainedVectorValidator) ( d2scene.Path) error {
	 := .preflight
	if len(.Commands) > .options.MaxPathCommands-.pathSegments {
		return fmt.Errorf("path command count exceeds limit %d", .options.MaxPathCommands)
	}
	.pathSegments += len(.Commands)
	 := false
	for ,  := range .Commands {
		if &255 == 0 {
			if  := .ctx.Err();  != nil {
				return 
			}
		}
		 := func( d2scene.Point) bool { return finitePoint() }
		switch .Kind {
		case d2scene.MoveCommand:
			if !(.P1) {
				return fmt.Errorf("command %d: non-finite point", )
			}
			 = true
		case d2scene.LineCommand:
			if ! {
				return fmt.Errorf("command %d: line before move", )
			}
			if !(.P1) {
				return fmt.Errorf("command %d: non-finite point", )
			}
		case d2scene.QuadraticCommand:
			if ! {
				return fmt.Errorf("command %d: quadratic before move", )
			}
			if !(.P1) || !(.P2) {
				return fmt.Errorf("command %d: non-finite point", )
			}
		case d2scene.CubicCommand:
			if ! {
				return fmt.Errorf("command %d: cubic before move", )
			}
			if !(.P1) || !(.P2) || !(.P3) {
				return fmt.Errorf("command %d: non-finite point", )
			}
		case d2scene.ArcCommand:
			if ! {
				return fmt.Errorf("command %d: arc before move", )
			}
			if !(.P1) || !finite(.RadiusX) || !finite(.RadiusY) || !finite(.Rotation) {
				return fmt.Errorf("command %d: non-finite arc", )
			}
		case d2scene.CloseCommand:
			if ! {
				return fmt.Errorf("command %d: close before move", )
			}
		default:
			return fmt.Errorf("command %d: unknown kind %d", , .Kind)
		}
	}
	return nil
}

// paint validates intrinsic paint structure only. Device invertibility and
// objectBoundingBox geometry are checked later for each visible placement.
func ( *retainedVectorValidator) ( d2scene.Paint,  bool,  *d2scene.Box, ,  int) (retainedVectorSummary, bool, error) {
	switch paint := .(type) {
	case nil:
		if  {
			return retainedVectorSummary{}, false, fmt.Errorf("color animation targets missing paint")
		}
		return retainedVectorSummary{}, false, nil
	case d2scene.SolidPaint:
		return retainedVectorSummary{}, true, nil
	case *d2scene.SolidPaint:
		if  == nil {
			if  {
				return retainedVectorSummary{}, false, fmt.Errorf("color animation targets missing paint")
			}
			return retainedVectorSummary{}, false, nil
		}
		return retainedVectorSummary{}, true, nil
	case d2scene.LinearGradient:
		if  {
			return retainedVectorSummary{}, false, fmt.Errorf("color animation targets non-solid paint")
		}
		return retainedVectorSummary{}, true, .linearGradient(, )
	case *d2scene.LinearGradient:
		if  == nil {
			return retainedVectorSummary{}, false, fmt.Errorf("nil linear gradient")
		}
		return .(*, , , , )
	case d2scene.RadialGradient:
		if  {
			return retainedVectorSummary{}, false, fmt.Errorf("color animation targets non-solid paint")
		}
		return retainedVectorSummary{}, true, .radialGradient(, )
	case *d2scene.RadialGradient:
		if  == nil {
			return retainedVectorSummary{}, false, fmt.Errorf("nil radial gradient")
		}
		return .(*, , , , )
	case d2scene.PatternPaint:
		if  {
			return retainedVectorSummary{}, false, fmt.Errorf("color animation targets pattern paint")
		}
		,  := .pattern(, , )
		return , true, 
	case *d2scene.PatternPaint:
		if  == nil {
			return retainedVectorSummary{}, false, fmt.Errorf("nil pattern paint")
		}
		return .(*, , , , )
	default:
		return retainedVectorSummary{}, false, fmt.Errorf("unsupported paint %T", )
	}
}

func ( *retainedVectorValidator) ( d2scene.PatternPaint,  *d2scene.Box,  int) (retainedVectorSummary, error) {
	if  := validateBox(.Tile);  != nil {
		return retainedVectorSummary{}, fmt.Errorf("pattern tile: %w", )
	}
	if .Tile.Width == 0 || .Tile.Height == 0 {
		return retainedVectorSummary{}, fmt.Errorf("pattern tile has zero width or height")
	}
	if !finite(.Tile.X+.Tile.Width) || !finite(.Tile.Y+.Tile.Height) {
		return retainedVectorSummary{}, fmt.Errorf("pattern tile endpoints are non-finite")
	}
	if .Units > d2scene.UserSpaceOnUse {
		return retainedVectorSummary{}, fmt.Errorf("invalid paint units %d", .Units)
	}
	if !.Transform.IsFinite() {
		return retainedVectorSummary{}, fmt.Errorf("non-finite pattern transform")
	}
	if !finiteLinearTransformInvertible(.Transform) || !.ancestorsInvertible {
		return retainedVectorSummary{}, fmt.Errorf("singular pattern transform")
	}
	if .Units == d2scene.ObjectBoundingBox &&  != nil {
		if  := validateBox(*);  != nil {
			return retainedVectorSummary{}, fmt.Errorf("invalid object bounding box: %w", )
		}
		if .Width == 0 || .Height == 0 {
			return retainedVectorSummary{}, fmt.Errorf("object bounding box has zero width or height")
		}
	}
	if .Root == nil {
		return retainedVectorSummary{}, fmt.Errorf("pattern has no root node")
	}
	 :=  + 1
	 := .ancestorsInvertible
	.ancestorsInvertible = true
	,  := .node(.Root, 1, )
	.ancestorsInvertible = 
	if  != nil {
		return retainedVectorSummary{}, fmt.Errorf("pattern root: %w", )
	}
	// A pattern root starts an independent node-depth domain, but its import
	// depth is inherited by any vector asset reached below it. Pattern edges
	// without a downstream vector do not themselves exercise MaxImportDepth.
	.nodeHeight = 0
	if .importHeight != 0 {
		.importHeight++
	}
	return , nil
}

func ( *retainedVectorValidator) ( d2scene.LinearGradient,  *d2scene.Box) error {
	if !finitePoint(.Start) || !finitePoint(.End) {
		return fmt.Errorf("linear gradient has non-finite geometry")
	}
	if  := .gradientCommon(.Units, .Transform, .Stops, .Spread, );  != nil {
		return fmt.Errorf("linear gradient: %w", )
	}
	,  := .End.X-.Start.X, .End.Y-.Start.Y
	if  := * + *; !finite() {
		return fmt.Errorf("linear gradient vector is outside the finite numeric domain")
	}
	return nil
}

func ( *retainedVectorValidator) ( d2scene.RadialGradient,  *d2scene.Box) error {
	if !finitePoint(.Center) || !finitePoint(.Focal) || !finite(.Radius) || !finite(.FocalRadius) {
		return fmt.Errorf("radial gradient has non-finite geometry")
	}
	if .Radius < 0 || .FocalRadius < 0 {
		return fmt.Errorf("radial gradient has negative radius")
	}
	if  := .gradientCommon(.Units, .Transform, .Stops, .Spread, );  != nil {
		return fmt.Errorf("radial gradient: %w", )
	}
	if len(.Stops) == 1 {
		return nil
	}
	,  := .Center.X-.Focal.X, .Center.Y-.Focal.Y
	 := .Radius - .FocalRadius
	if  := * + * - *; !finite() || !finite() {
		return fmt.Errorf("radial gradient cone is outside the finite numeric domain")
	}
	return nil
}

func ( *retainedVectorValidator) ( d2scene.PaintUnits,  d2scene.Matrix,  []d2scene.GradientStop,  d2scene.SpreadMethod,  *d2scene.Box) error {
	if  > d2scene.UserSpaceOnUse {
		return fmt.Errorf("invalid paint units %d", )
	}
	if !.IsFinite() {
		return fmt.Errorf("non-finite gradient transform")
	}
	if !finiteLinearTransformInvertible() {
		return fmt.Errorf("singular gradient transform")
	}
	if !.ancestorsInvertible {
		return fmt.Errorf("singular gradient transform")
	}
	if  == d2scene.ObjectBoundingBox &&  != nil {
		if  := validateBox(*);  != nil {
			return fmt.Errorf("invalid object bounding box: %w", )
		}
		if .Width == 0 || .Height == 0 {
			return fmt.Errorf("object bounding box has zero width or height")
		}
	}
	if len() == 0 {
		return fmt.Errorf("gradient has no stops")
	}
	for ,  := range  {
		if &255 == 0 {
			if  := .preflight.ctx.Err();  != nil {
				return 
			}
		}
		if !finite(.Offset) {
			return fmt.Errorf("gradient stop %d has non-finite offset", )
		}
	}
	if  > d2scene.SpreadRepeat {
		return fmt.Errorf("invalid spread method %d", )
	}
	return nil
}

func ( *retainedVectorValidator) ( *d2scene.Stroke, ,  bool,  *d2scene.Box, ,  int) (retainedVectorSummary, error) {
	if  == nil {
		if  ||  {
			return retainedVectorSummary{}, fmt.Errorf("stroke animation targets missing stroke")
		}
		return retainedVectorSummary{}, nil
	}
	if !finite(.Width) || .Width < 0 || !finite(.MiterLimit) || !finite(.DashOffset) {
		return retainedVectorSummary{}, fmt.Errorf("invalid stroke")
	}
	, ,  := .paint(.Paint, , , , )
	if  != nil {
		return retainedVectorSummary{}, 
	}
	 := 0.0
	for ,  := range .Dashes {
		if &255 == 0 {
			if  := .preflight.ctx.Err();  != nil {
				return retainedVectorSummary{}, 
			}
		}
		if !finite() ||  <= 0 {
			return retainedVectorSummary{}, fmt.Errorf("invalid stroke dash")
		}
		 += 
		if !finite() {
			return retainedVectorSummary{}, fmt.Errorf("invalid stroke dash total")
		}
	}
	if .Width == 0 || ! {
		return , nil
	}
	switch .Cap {
	case d2scene.CapButt, d2scene.CapRound, d2scene.CapSquare:
	default:
		return retainedVectorSummary{}, fmt.Errorf("unsupported line cap %d", .Cap)
	}
	switch .Join {
	case d2scene.JoinMiter, d2scene.JoinRound, d2scene.JoinBevel:
	default:
		return retainedVectorSummary{}, fmt.Errorf("unsupported line join %d", .Join)
	}
	if .Join == d2scene.JoinMiter && .MiterLimit != 0 && .MiterLimit < 1 {
		return retainedVectorSummary{}, fmt.Errorf("invalid miter limit %g", .MiterLimit)
	}
	return , nil
}

func paintUsesObjectBoundingBox( d2scene.Paint) bool {
	switch paint := .(type) {
	case d2scene.LinearGradient:
		return .Units == d2scene.ObjectBoundingBox
	case *d2scene.LinearGradient:
		return  != nil && .Units == d2scene.ObjectBoundingBox
	case d2scene.RadialGradient:
		return .Units == d2scene.ObjectBoundingBox
	case *d2scene.RadialGradient:
		return  != nil && .Units == d2scene.ObjectBoundingBox
	case d2scene.PatternPaint:
		return .Units == d2scene.ObjectBoundingBox
	case *d2scene.PatternPaint:
		return  != nil && .Units == d2scene.ObjectBoundingBox
	default:
		return false
	}
}

func strokeUsesObjectBoundingBox( *d2scene.Stroke) bool {
	return  != nil && paintUsesObjectBoundingBox(.Paint)
}

// finiteLinearTransformInvertible tests rank without constructing the inverse.
// An exact rational determinant preserves the full float64 exponent range and
// cannot misclassify a singular dyadic matrix through normalization rounding.
func finiteLinearTransformInvertible( d2scene.Matrix) bool {
	if !.IsFinite() {
		return false
	}
	if (.A == 0 && .C == 0) || (.B == 0 && .D == 0) {
		return false
	}
	 := .A * .D
	 := .B * .C
	// If both correctly-rounded products are finite and differ, their exact
	// products cannot have been equal. Ambiguous equal/overflow/underflow cases
	// fall through to the exact determinant.
	if finite() && finite() &&  !=  {
		return true
	}
	var , , ,  big.Rat
	.SetFloat64(.A)
	.SetFloat64(.B)
	.SetFloat64(.C)
	.SetFloat64(.D)
	var ,  big.Rat
	.Mul(&, &)
	.Mul(&, &)
	return .Cmp(&) != 0
}

// finiteAffineInverse computes a full inverse without allowing determinant
// overflow or underflow to misclassify an invertible float64 matrix. The fast
// path covers ordinary transforms; exact rationals handle extreme exponents.
func finiteAffineInverse( d2scene.Matrix) (d2scene.Matrix, bool, error) {
	if !.IsFinite() {
		return d2scene.Matrix{}, false, fmt.Errorf("non-finite transform")
	}
	if ,  := .Inverse();  == nil && .IsFinite() {
		return , true, nil
	}

	var , , , , ,  big.Rat
	.SetFloat64(.A)
	.SetFloat64(.B)
	.SetFloat64(.C)
	.SetFloat64(.D)
	.SetFloat64(.E)
	.SetFloat64(.F)
	var , ,  big.Rat
	.Mul(&, &)
	.Mul(&, &)
	.Sub(&, &)
	if .Sign() == 0 {
		return d2scene.Matrix{}, false, nil
	}
	 := func( *big.Rat) (float64, error) {
		var  big.Rat
		.Quo(, &)
		,  := .Float64()
		if !finite() {
			return 0, fmt.Errorf("inverse is outside the finite numeric domain")
		}
		return , nil
	}
	 := func( *big.Rat) *big.Rat {
		 := new(big.Rat)
		return .Neg()
	}
	,  := (&)
	if  != nil {
		return d2scene.Matrix{}, false, 
	}
	,  := ((&))
	if  != nil {
		return d2scene.Matrix{}, false, 
	}
	,  := ((&))
	if  != nil {
		return d2scene.Matrix{}, false, 
	}
	,  := (&)
	if  != nil {
		return d2scene.Matrix{}, false, 
	}
	var , ,  big.Rat
	.Mul(&, &)
	.Mul(&, &)
	.Sub(&, &)
	,  := (&)
	if  != nil {
		return d2scene.Matrix{}, false, 
	}
	var , ,  big.Rat
	.Mul(&, &)
	.Mul(&, &)
	.Sub(&, &)
	,  := (&)
	if  != nil {
		return d2scene.Matrix{}, false, 
	}
	return d2scene.Matrix{
		A: , B: , C: , D: , E: , F: ,
	}, true, nil
}

func maxRetainedHeight(,  int) int {
	if  >  {
		return 
	}
	return 
}