package d2raster

import (
	
	

	
	

	
	
)

// preparedCOLRv1SoftLight is an internal prepared-tree operation. It never
// enters d2scene, whose public blend-mode set remains unchanged.
const preparedCOLRv1SoftLight d2scene.BlendMode = 255

type colrv1TextCompiler struct {
	preflight    *preflight
	nodeID       string
	font         *preparedFont
	buffer       *sfnt.Buffer
	clipBox      *fontface.COLRv1ClipBox
	fontToDevice d2scene.Matrix
}

func ( *preflight) ( string,  *preparedFont,  *fontface.COLRv1Plan,  d2scene.Point,  fixed.Int26_6,  d2scene.Matrix) (*preparedNode, d2scene.Bounds, error) {
	if  == nil || .Root == nil {
		return nil, d2scene.Bounds{}, fmt.Errorf("d2raster: node %q has an empty COLRv1 plan", )
	}
	if  := .addPreparedNodes(.Usage.PaintNodes);  != nil {
		return nil, d2scene.Bounds{}, fmt.Errorf("d2raster: node %q COLRv1 glyph %d: %w", , .GlyphID, )
	}
	 := int(.outline.UnitsPerEm())
	if  <= 0 {
		return nil, d2scene.Bounds{}, fmt.Errorf("d2raster: node %q COLRv1 font has invalid units per em %d", , )
	}
	 := fixedToFloat() / float64()
	 := d2scene.Translate(.X, .Y).Mul(d2scene.Scale(, -))
	 := colrv1TextCompiler{
		preflight:    ,
		nodeID:       ,
		font:         ,
		buffer:       &sfnt.Buffer{},
		clipBox:      .Clip,
		fontToDevice: .Mul(),
	}
	,  := .paint(.Root, d2scene.Identity())
	if  != nil {
		return nil, d2scene.Bounds{}, fmt.Errorf("d2raster: node %q COLRv1 glyph %d: %w", , .GlyphID, )
	}
	 := d2scene.Bounds{}
	if .Clip != nil {
		,  := .clip(.Clip, d2scene.Identity())
		if  != nil {
			return nil, d2scene.Bounds{}, fmt.Errorf("d2raster: node %q COLRv1 glyph %d clip: %w", , .GlyphID, )
		}
		if  != nil {
			.clip = 
			.bounds = .bounds.Intersect(.bounds)
			.contentBounds = .bounds
		}
		 = transformedCOLRv1ClipBounds(.Clip, )
	}
	return , , nil
}

func ( *colrv1TextCompiler) ( fontface.COLRv1Paint,  d2scene.Matrix) (*preparedNode, error) {
	if  := .preflight.ctx.Err();  != nil {
		return nil, 
	}
	switch paint := .(type) {
	case fontface.COLRv1Layers:
		 := &preparedNode{opacity: 1, blend: d2scene.BlendNormal}
		for ,  := range .Paints {
			,  := .(, )
			if  != nil {
				return nil, fmt.Errorf("layer %d: %w", , )
			}
			appendPreparedChild(, )
		}
		return nonemptyPreparedNode(), nil
	case fontface.COLRv1Glyph:
		,  := .glyphFill(.Paint, )
		if  != nil {
			return nil, fmt.Errorf("glyph %d fill: %w", .GlyphID, )
		}
		return .glyph(.GlyphID, , )
	case fontface.COLRv1Transform:
		 := .Mul(colrv1Affine(.Matrix))
		if !.IsFinite() {
			return nil, fmt.Errorf("non-finite composed transform")
		}
		return .(.Paint, )
	case fontface.COLRv1Composite:
		return .composite(, )
	case fontface.COLRv1Solid, fontface.COLRv1LinearGradient, fontface.COLRv1RadialGradient:
		return .unboundedFill(, )
	default:
		return nil, fmt.Errorf("unsupported compiled paint %T", )
	}
}

// COLRv1 paints are conceptually infinite until a PaintGlyph or clip box
// bounds them. Noto uses that rule inside flag composites. The authenticated
// font guarantees a static clip for every color root, so materialize an
// otherwise-unbounded fill only over that finite box.
func ( *colrv1TextCompiler) ( fontface.COLRv1Paint,  d2scene.Matrix) (*preparedNode, error) {
	if .clipBox == nil {
		return nil, fmt.Errorf("unbounded %T paint has no static root clip", )
	}
	,  := .glyphFill(, )
	if  != nil {
		return nil, 
	}
	 := []d2scene.Point{
		{X: .clipBox.XMin, Y: .clipBox.YMin},
		{X: .clipBox.XMax, Y: .clipBox.YMin},
		{X: .clipBox.XMax, Y: .clipBox.YMax},
		{X: .clipBox.XMin, Y: .clipBox.YMax},
	}
	for range  {
		if  := .preflight.addPathSegment();  != nil {
			return nil, 
		}
	}
	,  := .preflight.finishPrimitive(.nodeID, []subpath{{points: , closed: true}}, .fontToDevice, , nil)
	if  != nil {
		return nil, 
	}
	if  == nil || .bounds.Empty() {
		return nil, nil
	}
	return &preparedNode{
		opacity: 1, blend: d2scene.BlendNormal, primitive: ,
		bounds: .bounds, contentBounds: .bounds,
	}, nil
}

func ( *colrv1TextCompiler) ( uint32,  *preparedPaint,  d2scene.Matrix) (*preparedNode, error) {
	if  == 0 ||  > math.MaxUint16 || int() >= .font.outline.NumGlyphs() {
		return nil, fmt.Errorf("glyph ID %d is out of range", )
	}
	 := int(.font.outline.UnitsPerEm())
	,  := .font.outline.LoadGlyph(.buffer, sfnt.GlyphIndex(), fixed.I(), nil)
	if  != nil {
		return nil, fmt.Errorf("load glyph %d: %w", , )
	}
	if len() == 0 {
		return nil, nil
	}
	// sfnt.LoadGlyph exposes y-down outlines. COLRv1 paint coordinates are
	// y-up, so the final reflection converts the outline back into the paint
	// coordinate system before the shared font-to-device mapping is applied.
	 := .fontToDevice.Mul().Mul(d2scene.Scale(1, -1))
	,  := flattenGlyph(.preflight.ctx, , d2scene.Point{}, flattenTolerance(), .preflight.addPathSegment)
	if  != nil {
		return nil, fmt.Errorf("flatten glyph %d: %w", , )
	}
	,  := .preflight.finishPrimitive(.nodeID, , , , nil)
	if  != nil {
		return nil, 
	}
	if  == nil || .bounds.Empty() {
		return nil, nil
	}
	return &preparedNode{
		opacity: 1, blend: d2scene.BlendNormal, primitive: ,
		bounds: .bounds, contentBounds: .bounds,
	}, nil
}

func ( *colrv1TextCompiler) ( fontface.COLRv1Paint,  d2scene.Matrix) (*preparedPaint, error) {
	if  := .preflight.ctx.Err();  != nil {
		return nil, 
	}
	switch paint := .(type) {
	case fontface.COLRv1Solid:
		return &preparedPaint{kind: preparedSolidPaint, solid: .Color}, nil
	case fontface.COLRv1LinearGradient:
		return prepareCOLRv1LinearGradient(, .fontToDevice.Mul())
	case fontface.COLRv1RadialGradient:
		return prepareCOLRv1RadialGradient(, .fontToDevice.Mul())
	case fontface.COLRv1Transform:
		 := .Mul(colrv1Affine(.Matrix))
		if !.IsFinite() {
			return nil, fmt.Errorf("non-finite composed paint transform")
		}
		return .(.Paint, )
	default:
		return nil, fmt.Errorf("unsupported glyph fill %T", )
	}
}

func ( *colrv1TextCompiler) ( fontface.COLRv1Composite,  d2scene.Matrix) (*preparedNode, error) {
	,  := .paint(.Backdrop, )
	if  != nil {
		return nil, fmt.Errorf("composite backdrop: %w", )
	}
	,  := .paint(.Source, )
	if  != nil {
		return nil, fmt.Errorf("composite source: %w", )
	}
	switch .Mode {
	case fontface.COLRv1CompositeSrcIn:
		if  == nil ||  == nil {
			return nil, nil
		}
		 := &preparedNode{
			opacity: 1, blend: d2scene.BlendNormal,
			children:      []*preparedNode{},
			mask:          &preparedMask{kind: d2scene.MaskAlpha, root: },
			contentBounds: .bounds,
			bounds:        .bounds.Intersect(.bounds),
		}
		return nonemptyPreparedNode(), nil
	case fontface.COLRv1CompositeSoftLight:
		if  == nil {
			return , nil
		}
		if  == nil {
			return , nil
		}
		.blend = preparedCOLRv1SoftLight
		// PaintComposite combines its source and backdrop in isolation, then
		// returns that one result to the enclosing COLRv1 surface using ordinary
		// alpha compositing. Without this layer, SoftLight would also sample any
		// lower PaintColrLayers siblings already present in the parent.
		 := &preparedNode{opacity: 1, blend: d2scene.BlendNormal, isolated: true}
		appendPreparedChild(, )
		appendPreparedChild(, )
		return , nil
	default:
		return nil, fmt.Errorf("unsupported compiled composite mode %d", .Mode)
	}
}

func ( *colrv1TextCompiler) ( *fontface.COLRv1ClipBox,  d2scene.Matrix) (*preparedClip, error) {
	if  == nil {
		return nil, nil
	}
	 := .fontToDevice.Mul()
	 := []d2scene.Point{
		.Point(d2scene.Point{X: .XMin, Y: .YMin}),
		.Point(d2scene.Point{X: .XMax, Y: .YMin}),
		.Point(d2scene.Point{X: .XMax, Y: .YMax}),
		.Point(d2scene.Point{X: .XMin, Y: .YMax}),
	}
	for ,  := range  {
		if  := validateRasterPoint();  != nil {
			return nil, 
		}
		if  := .preflight.addPathSegment();  != nil {
			return nil, 
		}
	}
	 := []subpath{{points: , closed: true}}
	return &preparedClip{
		subpaths: ,
		fillRule: d2scene.NonZero,
		bounds:   subpathPixelBounds(, d2scene.Identity(), 0, .preflight.preparationBounds()),
		edges:    int64(len()),
	}, nil
}

func prepareCOLRv1LinearGradient( fontface.COLRv1LinearGradient,  d2scene.Matrix) (*preparedPaint, error) {
	 := d2scene.Point{X: .X0, Y: .Y0}
	 := d2scene.Point{X: .X1, Y: .Y1}
	 := d2scene.Point{X: .X2, Y: .Y2}
	 := d2scene.Point{X: .X - .X, Y: .Y - .Y}
	 := d2scene.Point{X: .X - .X, Y: .Y - .Y}
	 := .X*.X + .Y*.Y
	if  == 0 || !finite() {
		return nil, fmt.Errorf("ill-formed linear gradient rotation vector")
	}
	 := (.X*.X + .Y*.Y) / 
	 := d2scene.Point{X: .X - *.X, Y: .Y - *.Y}
	if .X*.X+.Y*.Y <= geometryEpsilon*geometryEpsilon {
		return nil, fmt.Errorf("ill-formed linear gradient projection")
	}
	 := colrv1ColorLine(.ColorLine)
	,  := prepareLinearGradient(d2scene.LinearGradient{
		Start:     ,
		End:       d2scene.Point{X: .X + .X, Y: .Y + .Y},
		Stops:     ,
		Units:     d2scene.UserSpaceOnUse,
		Transform: d2scene.Identity(),
		Spread:    d2scene.SpreadPad,
	}, d2scene.Box{}, )
	if  != nil {
		return nil, 
	}
	if .kind != preparedSolidPaint {
		.gradient.colrv1Interpolation = true
	}
	return , nil
}

func prepareCOLRv1RadialGradient( fontface.COLRv1RadialGradient,  d2scene.Matrix) (*preparedPaint, error) {
	 := colrv1ColorLine(.ColorLine)
	,  := prepareRadialGradient(d2scene.RadialGradient{
		Focal:       d2scene.Point{X: .X0, Y: .Y0},
		FocalRadius: .Radius0,
		Center:      d2scene.Point{X: .X1, Y: .Y1},
		Radius:      .Radius1,
		Stops:       ,
		Units:       d2scene.UserSpaceOnUse,
		Transform:   d2scene.Identity(),
		Spread:      d2scene.SpreadPad,
	}, d2scene.Box{}, )
	if  != nil {
		return nil, 
	}
	if .kind != preparedSolidPaint {
		.gradient.colrv1Interpolation = true
		.gradient.radialAverage = averageCOLRv1GradientColor(.gradient.stops)
	}
	return , nil
}

func colrv1ColorLine( fontface.COLRv1ColorLine) []d2scene.GradientStop {
	 := make([]d2scene.GradientStop, len(.Stops))
	for ,  := range .Stops {
		[] = d2scene.GradientStop{Offset: .Offset, Color: .Color}
	}
	return 
}

func colrv1Affine( fontface.COLRv1Affine) d2scene.Matrix {
	return d2scene.Matrix{
		A: .Xx, B: .Yx,
		C: .Xy, D: .Yy,
		E: .Dx, F: .Dy,
	}
}

func appendPreparedChild(,  *preparedNode) {
	if  == nil ||  == nil || .bounds.Empty() {
		return
	}
	.children = append(.children, )
	.bounds = unionRect(.bounds, .bounds)
	.contentBounds = .bounds
}

func nonemptyPreparedNode( *preparedNode) *preparedNode {
	if  == nil || .bounds.Empty() {
		return nil
	}
	return 
}

func transformedCOLRv1ClipBounds( *fontface.COLRv1ClipBox,  d2scene.Matrix) d2scene.Bounds {
	if  == nil {
		return d2scene.Bounds{}
	}
	return d2scene.BoundsFromPoints(
		.Point(d2scene.Point{X: .XMin, Y: .YMin}),
		.Point(d2scene.Point{X: .XMax, Y: .YMin}),
		.Point(d2scene.Point{X: .XMax, Y: .YMax}),
		.Point(d2scene.Point{X: .XMin, Y: .YMax}),
	)
}