package d2raster

import (
	
	
	
	

	gotextfont 
	
	
	

	
	
)

type positionedGlyph struct {
	id       sfnt.GlyphIndex
	font     *preparedFont
	asset    d2scene.AssetID
	position d2scene.Point
	empty    bool
	source   rune
	sourceAt int
}

// preparedFont keeps the immutable outline reader needed by explicit scene
// glyphs separate from go-text's large mutable shaping caches. Bundled fonts
// retain an authenticated source and construct a render-local Face only if a
// raw TextRun actually needs shaping. Custom fonts remain eagerly parsed.
type preparedFont struct {
	face        *fontface.ParsedFace
	source      *fontface.BundledFaceSource
	lazyFace    fontface.ParsedFace
	outline     *sfnt.Font
	shaping     *gotextfont.Face
	colrv1Plans map[uint32]preparedCOLRv1Plan
}

type preparedCOLRv1Plan struct {
	plan  *fontface.COLRv1Plan
	found bool
	err   error
}

func ( *preparedFont) () (*fontface.ParsedFace, error) {
	if  == nil {
		return nil, fmt.Errorf("d2raster: nil prepared font")
	}
	if .face != nil {
		return .face, nil
	}
	if .source == nil {
		return nil, fmt.Errorf("d2raster: prepared font has no face source")
	}
	if  := .source.CloneReadOnlyInto(&.lazyFace);  != nil {
		return nil, 
	}
	.face = &.lazyFace
	.shaping = .lazyFace.Shaping
	return .face, nil
}

func ( *preparedFont) ( uint32) ([]fontface.ColorGlyphLayer, bool, error) {
	if  == nil {
		return nil, false, fmt.Errorf("d2raster: nil prepared font")
	}
	if .source != nil {
		return .source.COLR0GlyphLayers()
	}
	if .face == nil {
		return nil, false, fmt.Errorf("d2raster: prepared font has no parsed face")
	}
	return .face.COLR0GlyphLayers()
}

func ( *preparedFont) ( uint32,  fixed.Int26_6) (fixed.Rectangle26_6, bool, error) {
	if  == nil {
		return fixed.Rectangle26_6{}, false, fmt.Errorf("d2raster: nil prepared font")
	}
	if .source != nil {
		return .source.GlyphRenderBounds(, )
	}
	if .face == nil {
		return fixed.Rectangle26_6{}, false, fmt.Errorf("d2raster: prepared font has no parsed face")
	}
	return .face.GlyphRenderBounds(, )

}

func ( *preparedFont) ( uint32) string {
	if  == nil {
		return "non-outline"
	}
	if .source != nil {
		,  := .source.GlyphDataKind()
		if  == nil {
			return 
		}
		return "non-outline"
	}
	if .shaping == nil {
		return "non-outline"
	}
	return glyphDataKind(.shaping.GlyphData(gotextfont.GID()))
}

// bundledCOLRv1Plan caches immutable paint plans on the preflight-local font
// wrapper. A RenderSession shares only the underlying parsed font and creates a
// new preparedFont for every preflight, so this map never needs synchronization.
func ( *preparedFont) ( uint32) (*fontface.COLRv1Plan, bool, error) {
	if  == nil {
		return nil, false, fmt.Errorf("d2raster: nil prepared font")
	}
	var  func(uint32) (*fontface.COLRv1Plan, bool, error)
	if .source != nil {
		if !.source.IsBundledNotoColorEmoji() {
			return nil, false, nil
		}
		 = .source.CompileBundledNotoColorEmojiCOLRv1Plan
	} else {
		if .face == nil || !.face.IsBundledNotoColorEmoji() {
			return nil, false, nil
		}
		 = .face.CompileBundledNotoColorEmojiCOLRv1Plan
	}
	if ,  := .colrv1Plans[];  {
		return .plan, .found, .err
	}
	, ,  := ()
	if .colrv1Plans == nil {
		.colrv1Plans = make(map[uint32]preparedCOLRv1Plan)
	}
	.colrv1Plans[] = preparedCOLRv1Plan{plan: , found: , err: }
	return , , 
}

type preparedTextPart struct {
	paths      []subpath
	vector     *preparedNode
	userPaint  bool
	foreground bool
	color      color.NRGBA
}

func parsePreparedFont( []byte,  uint16) (*preparedFont, error) {
	, ,  := fontface.RegisteredBundledFace(, )
	if  != nil {
		return nil, 
	} else if  {
		return newPreparedBundledFont()
	} else {
		, ,  = fontface.RegisteredBundledNotoColorEmoji(, )
		if  != nil {
			return nil, 
		} else if  {
			return newPreparedBundledFont()
		}
	}
	,  := fontface.ParseFace(, )
	if  != nil {
		return nil, 
	}
	return newPreparedFont(), nil
}

func newPreparedBundledFont( *fontface.BundledFaceSource) (*preparedFont, error) {
	,  := .Outline()
	if  != nil {
		return nil, 
	}
	return &preparedFont{source: , outline: }, nil
}

func newPreparedFont( *fontface.ParsedFace) *preparedFont {
	if  == nil {
		return nil
	}
	return &preparedFont{face: , outline: .Outline, shaping: .Shaping}
}

func ( *preflight) ( string,  d2scene.TextRun,  d2scene.Matrix,  animationOverrides,  int) (*preparedPrimitive, error) {
	if !finitePoint(.Origin) || !finite(.Font.Size) || .Font.Size <= 0 || .Font.Size > float64(math.MaxInt32)/64 {
		return nil, fmt.Errorf("d2raster: node %q text has invalid origin or font size", )
	}
	if .Anchor > d2scene.AnchorEnd {
		return nil, fmt.Errorf("d2raster: node %q text has invalid anchor %d", , .Anchor)
	}
	if !.Ink.IsFinite() {
		return nil, fmt.Errorf("d2raster: node %q text has invalid ink bounds", )
	}
	if .Font.Asset == "" {
		return nil, fmt.Errorf("d2raster: node %q text has empty font asset ID", )
	}
	,  := .fonts[.Font.Asset]
	if ! {
		if ,  := .document.Assets[.Font.Asset];  {
			return nil, fmt.Errorf("d2raster: node %q text asset %q is not a usable font", , .Font.Asset)
		}
		return nil, 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 nil, fmt.Errorf("d2raster: node %q text: %w", , )
	}
	 := 0.0
	switch .Anchor {
	case d2scene.AnchorStart:
	case d2scene.AnchorMiddle:
		 = - / 2
	case d2scene.AnchorEnd:
		 = -
	}

	 := flattenTolerance()
	var  []subpath
	var  []preparedTextPart
	 := false
	 := func( preparedTextPart) error {
		if  := .addPreparedNodes(1);  != nil {
			return fmt.Errorf("d2raster: node %q color text: %w", , )
		}
		 = append(, )
		return nil
	}
	 := func() error {
		if len() == 0 {
			return nil
		}
		if  := (preparedTextPart{paths: , userPaint: true});  != nil {
			return 
		}
		 = nil
		return nil
	}
	var  []subpath
	var  d2scene.Bounds
	 := make(map[*sfnt.Font]*sfnt.Buffer)
	for ,  := range  {
		if  := .ctx.Err();  != nil {
			return nil, 
		}
		if .empty {
			continue
		}
		 := [.font.outline]
		if  == nil {
			 = &sfnt.Buffer{}
			[.font.outline] = 
		}
		 := d2scene.Point{
			X: .Origin.X +  + .position.X,
			Y: .Origin.Y + .position.Y,
		}
		, ,  := .font.bundledCOLRv1Plan(uint32(.id))
		if  != nil {
			return nil, unsupportedPositionedGlyphDataError(, )
		}
		if  {
			if ! {
				if  := .addPreparedNodes(1);  != nil {
					return nil, fmt.Errorf("d2raster: node %q color text root: %w", , )
				}
				 = 
				 = true
			}
			if  := ();  != nil {
				return nil, 
			}
			, ,  := .colrv1TextGlyph(, .font, , , , )
			if  != nil {
				return nil, unsupportedPositionedGlyphDataError(, )
			}
			if  != nil {
				 = append(, preparedTextPart{vector: })
			}
			 = .Union()
			continue
		}
		, ,  := .font.colr0GlyphLayers(uint32(.id))
		if  != nil {
			return nil, unsupportedPositionedGlyphDataError(, )
		}
		if ! {
			,  := .font.outline.LoadGlyph(, .id, , nil)
			if  != nil {
				return nil, unsupportedPositionedGlyphDataError(, )
			}
			,  := flattenGlyph(.ctx, , , , .addPathSegment)
			if  != nil {
				return nil, fmt.Errorf("flatten glyph %d (ID %d): %w", , .id, )
			}
			 = append(, ...)
			if  {
				 = append(, ...)
			}
			continue
		}

		if ! {
			if  := .addPreparedNodes(1);  != nil {
				return nil, fmt.Errorf("d2raster: node %q color text root: %w", , )
			}
			 = 
			 = true
		}
		if  := ();  != nil {
			return nil, 
		}
		for ,  := range  {
			// Charge every COLRv0 layer before loading it, including empty layers,
			// so a font cannot use them to evade structural or path-work limits.
			if  := .addPreparedNodes(1);  != nil {
				return nil, fmt.Errorf("d2raster: node %q glyph %d (ID %d) COLRv0 layer %d: %w", , , .id, , )
			}
			if  := .addPathSegment();  != nil {
				return nil, fmt.Errorf("glyph %d (ID %d) COLRv0 layer %d: %w", , .id, , )
			}
			,  := .font.outline.LoadGlyph(, sfnt.GlyphIndex(.GlyphID), , nil)
			if  != nil {
				return nil, unsupportedPositionedGlyphDataError(, fmt.Errorf("load COLRv0 layer glyph %d: %w", .GlyphID, ))
			}
			,  := flattenGlyph(.ctx, , , , .addPathSegment)
			if  != nil {
				return nil, fmt.Errorf("flatten glyph %d (ID %d) COLRv0 layer %d: %w", , .id, , )
			}
			 = append(, ...)
			if len() == 0 {
				continue
			}
			 = append(, preparedTextPart{
				paths: , foreground: .Foreground, color: .Color,
			})
		}
	}
	if  {
		if  := ();  != nil {
			return nil, 
		}
	}

	var  []subpath
	if (.Underline || .Strike) &&  > 0 {
		var  sfnt.Buffer
		,  := .outline.Metrics(&, , font.HintingNone)
		if  != nil {
			return nil, fmt.Errorf("d2raster: node %q text metrics: %w", , )
		}
		 := 1.0
		if  := .MaxScale(); finite() &&  > geometryEpsilon {
			 = 1 / 
		}
		 := math.Max(.Font.Size/16, )
		 := .Origin.X + 
		if .Underline {
			 := fixedToFloat(.Descent)
			 := .Origin.Y + math.Max(.Font.Size/12, *0.25)
			,  := decorationSubpath(, , , , .addPathSegment)
			if  != nil {
				return nil, fmt.Errorf("d2raster: node %q underline: %w", , )
			}
			if  {
				 = append(, )
				 = append(, )
			} else {
				 = append(, )
			}
		}
		if .Strike {
			 := fixedToFloat(.XHeight)
			if  <= 0 {
				 = .Font.Size * 0.5
			}
			 := .Origin.Y - /2 - /2
			,  := decorationSubpath(, , , , .addPathSegment)
			if  != nil {
				return nil, fmt.Errorf("d2raster: node %q strike: %w", , )
			}
			if  {
				 = append(, )
				 = append(, )
			} else {
				 = append(, )
			}
		}
	}
	if  && len() != 0 {
		if  := (preparedTextPart{paths: , userPaint: true});  != nil {
			return nil, 
		}
	}
	if ! {
		 = 
	}
	var  d2scene.Box
	if .Ink.Valid {
		// TextRun.Ink is the scene builder's exact measured node-local ink
		// contract. Prefer it to the flattened glyph approximation for
		// objectBoundingBox paint coordinates.
		 = .Ink.Box()
	} else {
		 = localObjectBounds()
		if .Valid {
			if len() == 0 {
				 = .Box()
			} else {
				 = .Bounds().Union().Box()
			}
		}
	}
	,  := .prepareAnimatedPaint(.Fill, .fillColor, , , )
	if  != nil {
		return nil, fmt.Errorf("d2raster: node %q text fill: %w", , )
	}
	,  := .prepareAnimatedStroke(.Stroke, .strokeColor, .dashOffset, , , )
	if  != nil {
		return nil, fmt.Errorf("d2raster: node %q text stroke: %w", , )
	}
	if ! {
		return .finishPrimitive(, , , , )
	}
	 := &preparedNode{opacity: 1, blend: d2scene.BlendNormal}
	for ,  := range  {
		if .vector != nil {
			appendPreparedChild(, .vector)
			continue
		}
		,  := preparedTextPartPaint(, , )
		,  := .finishPrimitive(, .paths, , , )
		if  != nil {
			return nil, 
		}
		 := &preparedNode{
			opacity: 1, blend: d2scene.BlendNormal, primitive: ,
			bounds: .bounds, contentBounds: .bounds,
		}
		.children = append(.children, )
		.bounds = unionRect(.bounds, .bounds)
	}
	.contentBounds = .bounds
	return &preparedPrimitive{vector: , bounds: .bounds}, nil
}

func preparedTextPartPaint( preparedTextPart,  *preparedPaint,  *preparedStroke) (*preparedPaint, *preparedStroke) {
	if .userPaint {
		return , 
	}
	if .foreground {
		return , nil
	}
	return &preparedPaint{kind: preparedSolidPaint, solid: .color}, nil
}

func ( *preflight) ( d2scene.TextRun,  fixed.Int26_6) ([]positionedGlyph, float64, error) {
	if len(.Glyphs) != 0 {
		if len(.Glyphs) > .options.MaxTextRunesPerRun {
			return nil, 0, fmt.Errorf("explicit glyph count %d exceeds per-run limit %d", len(.Glyphs), .options.MaxTextRunesPerRun)
		}
		if len(.Glyphs) > .options.MaxPathCommands-.shapedGlyphs {
			return nil, 0, fmt.Errorf("shaped glyph count exceeds limit %d", .options.MaxPathCommands)
		}
		 := make([]positionedGlyph, 0, len(.Glyphs))
		 := 0.0
		for ,  := range .Glyphs {
			if  := .ctx.Err();  != nil {
				return nil, 0, 
			}
			 := .Asset
			if  == "" {
				 = .Font.Asset
			}
			,  := .fonts[]
			if ! {
				return nil, 0, fmt.Errorf("glyph %d references unusable font asset %q", , )
			}
			if .Empty {
				if .ID != 0 {
					return nil, 0, fmt.Errorf("empty glyph at index %d has non-zero glyph ID %d", , .ID)
				}
				if !finitePoint(.Position) || !finite(.Advance) || !.Ink.IsFinite() {
					return nil, 0, fmt.Errorf("glyph %d has invalid geometry", )
				}
				 = append(, positionedGlyph{
					font: , asset: , position: .Position, empty: true,
				})
				 = math.Max(, .Position.X+.Advance)
				continue
			}
			if .ID == 0 {
				return nil, 0, fmt.Errorf("missing glyph at index %d: glyph ID 0", )
			}
			if .ID > math.MaxUint16 || int(.ID) >= .outline.NumGlyphs() {
				return nil, 0, fmt.Errorf("glyph ID %d at index %d is out of range", .ID, )
			}
			if !finitePoint(.Position) || !finite(.Advance) || !.Ink.IsFinite() {
				return nil, 0, fmt.Errorf("glyph %d has invalid geometry", )
			}
			 = append(, positionedGlyph{
				id: sfnt.GlyphIndex(.ID), font: , asset: , position: .Position,
			})
			 = math.Max(, .Position.X+.Advance)
		}
		.shapedGlyphs += len()
		return , , nil
	}

	 := .options.MaxPathCommands - .textRunes
	if  <= 0 {
		return nil, 0, fmt.Errorf("text rune count exceeds aggregate shaping-input limit %d", .options.MaxPathCommands)
	}
	if len(.Fallbacks) > .options.MaxAssets {
		return nil, 0, fmt.Errorf("fallback font reference count %d exceeds asset limit %d", len(.Fallbacks), .options.MaxAssets)
	}

	 := 1 + len(.Fallbacks)
	 := make([]fontface.ShapeFace, 0, )
	 := make([]*preparedFont, 0, )
	var  map[d2scene.AssetID]struct{}
	for  := range  {
		 := .Font.Asset
		if  != 0 {
			 = .Fallbacks[-1]
		}
		 := false
		if  == nil {
			for ,  := range  {
				if d2scene.AssetID(.ID) ==  {
					 = true
					break
				}
			}
			if ! && len() == 8 {
				 = make(map[d2scene.AssetID]struct{}, )
				for ,  := range  {
					[d2scene.AssetID(.ID)] = struct{}{}
				}
			}
		} else {
			_,  = []
		}
		if  {
			continue
		}
		if  != nil {
			[] = struct{}{}
		}
		,  := .fonts[]
		if ! {
			return nil, 0, fmt.Errorf("text references unusable font asset %q", )
		}
		,  := .faceForShaping()
		if  != nil {
			return nil, 0, fmt.Errorf("prepare shaping font asset %q: %w", , )
		}
		 = append(, fontface.ShapeFace{
			ID: string(), Face: ,
		})
		 = append(, )
	}
	 := .options.MaxTextCoverageChecks - .textCoverageChecks
	if  <= 0 {
		return nil, 0, fmt.Errorf("text font coverage checks exceed aggregate limit %d", .options.MaxTextCoverageChecks)
	}
	 := .options.MaxTextShapingRuns - .textShapingRuns
	if  <= 0 {
		return nil, 0, fmt.Errorf("text shaping run count exceeds aggregate limit %d", .options.MaxTextShapingRuns)
	}
	 := .options.MaxPathCommands - .shapedGlyphs
	if  <= 0 {
		return nil, 0, fmt.Errorf("shaped glyph count exceeds limit %d", .options.MaxPathCommands)
	}
	,  := .shapingWorkspace.ShapeTextTransient(.ctx, .Text, , , fontface.ShapeLimits{
		Runes:          min(.options.MaxTextRunesPerRun, ),
		Faces:          .options.MaxFontFacesPerText,
		CoverageChecks: ,
		Runs:           ,
		Glyphs:         ,
	})
	if  != nil {
		return nil, 0, 
	}
	.textRunes += .Runes
	.textCoverageChecks += .CoverageChecks
	.textShapingRuns += .Runs
	.shapedGlyphs += len(.Glyphs)
	 := make([]positionedGlyph, 0, len(.Glyphs))
	for ,  := range .Glyphs {
		 := d2scene.AssetID([.Face].ID)
		 = append(, positionedGlyph{
			id: sfnt.GlyphIndex(.ID), font: [.Face], asset: ,
			position: d2scene.Point{X: .PositionX, Y: .PositionY},
			empty:    .Empty, source: .Source, sourceAt: .SourceIndex,
		})
	}
	return , .Advance, nil
}

func glyphDataKind( gotextfont.GlyphData) string {
	 := "non-outline"
	switch .(type) {
	case gotextfont.GlyphOutline:
		 = "outline"
	case gotextfont.GlyphColor:
		 = "color"
	case gotextfont.GlyphBitmap:
		 = "bitmap"
	case gotextfont.GlyphSVG:
		 = "SVG"
	case nil:
		 = "missing"
	}
	return 

}

func unsupportedPositionedGlyphDataError( positionedGlyph,  error) error {
	 := .font.glyphDataKind(uint32(.id))
	if .source != 0 {
		return fmt.Errorf("%s glyph U+%04X at rune %d in font asset %q cannot be rasterized: %w", , .source, .sourceAt, .asset, )
	}
	return fmt.Errorf("%s glyph at cluster %d in font asset %q cannot be rasterized: %w", , .sourceAt, .asset, )
}

func flattenGlyph( context.Context,  sfnt.Segments,  d2scene.Point,  float64,  func() error) ([]subpath, error) {
	 := 0
	 := 0
	for ,  := range  {
		if .Op == sfnt.SegmentOpMoveTo {
			++
		}
		if .Op == sfnt.SegmentOpQuadTo || .Op == sfnt.SegmentOpCubeTo {
			++
		}
	}
	var  []subpath
	 := len()
	// Multiple curves almost always add several subdivision points each. A
	// small reserve avoids the first growths without over-allocating the common
	// one-curve case when that curve is already flat at the requested scale.
	if  > 1 &&  <= (math.MaxInt-)/2 {
		 +=  * 2
	}
	var  []d2scene.Point
	 := 0
	var  d2scene.Point
	 := false
	 := func() {
		if len() !=  {
			if  == nil {
				 = make([]subpath, 0, )
			}
			 = append(, subpath{
				points: [:len():len()],
				closed: true,
			})
		}
		 = len()
	}
	 := func( d2scene.Point) error {
		if !finitePoint() {
			return fmt.Errorf("non-finite point")
		}
		if  := ();  != nil {
			return 
		}
		if len() ==  || !samePoint([len()-1], ) {
			if  == nil {
				 = make([]d2scene.Point, 0, )
			}
			 = append(, )
		}
		return nil
	}
	 := func( fixed.Point26_6) d2scene.Point {
		return d2scene.Point{X: .X + fixedToFloat(.X), Y: .Y + fixedToFloat(.Y)}
	}

	for ,  := range  {
		if  := .Err();  != nil {
			return nil, 
		}
		switch .Op {
		case sfnt.SegmentOpMoveTo:
			()
			 = (.Args[0])
			if  := ();  != nil {
				return nil, fmt.Errorf("segment %d: %w", , )
			}
			 = true
		case sfnt.SegmentOpLineTo:
			if ! {
				return nil, fmt.Errorf("segment %d: line before move", )
			}
			 = (.Args[0])
			if  := ();  != nil {
				return nil, fmt.Errorf("segment %d: %w", , )
			}
		case sfnt.SegmentOpQuadTo:
			if ! {
				return nil, fmt.Errorf("segment %d: quadratic before move", )
			}
			,  := (.Args[0]), (.Args[1])
			if  := flattenQuadratic(, , , , , 0, );  != nil {
				return nil, fmt.Errorf("segment %d: %w", , )
			}
			 = 
		case sfnt.SegmentOpCubeTo:
			if ! {
				return nil, fmt.Errorf("segment %d: cubic before move", )
			}
			, ,  := (.Args[0]), (.Args[1]), (.Args[2])
			if  := flattenCubic(, , , , , , 0, );  != nil {
				return nil, fmt.Errorf("segment %d: %w", , )
			}
			 = 
		default:
			return nil, fmt.Errorf("segment %d: unknown operation %d", , .Op)
		}
	}
	()
	// Rebind multiple contours after all point appends. If points grew beyond
	// its initial capacity, earlier slices still reference the old backing
	// array. Limiting capacity to length also prevents one contour from
	// appending over the next contour's points. A lone contour is flushed only
	// after the final append and already has both properties.
	if len() > 1 {
		 := 0
		for  := range  {
			 := len([].points)
			[].points = [ : + : +]
			 += 
		}
	}
	return , nil
}

func decorationSubpath(, , ,  float64,  func() error) (subpath, error) {
	 := []d2scene.Point{
		{X: , Y: },
		{X:  + , Y: },
		{X:  + , Y:  + },
		{X: , Y:  + },
	}
	for range  {
		if  := ();  != nil {
			return subpath{}, 
		}
	}
	return subpath{points: , closed: true}, nil
}

func fixedToFloat( fixed.Int26_6) float64 {
	return float64() / 64
}