// SPDX-License-Identifier: Unlicense OR BSD-3-Clause

package shaping

import (
	
	
	
)

// HarfbuzzShaper implements the Shaper interface using harfbuzz.
// Reusing this shaper type across multiple shaping operations is
// faster and more memory-efficient than creating a new shaper
// for each operation.
type HarfbuzzShaper struct {
	buf *harfbuzz.Buffer

	fonts fontLRU

	features []harfbuzz.Feature
}

// SetFontCacheSize adjusts the size of the font cache within the shaper.
// It is safe to adjust the size after using the shaper, though shrinking
// it may result in many evictions on the next shaping.
func ( *HarfbuzzShaper) ( int) {
	.fonts.maxSizeOffset =  - defaultFontCacheSize
}

var _ Shaper = (*HarfbuzzShaper)(nil)

// Shaper describes the signature of a font shaping operation.
type Shaper interface {
	// Shape takes an Input and shapes it into the Output.
	Shape(Input) Output
}

const (
	// scaleShift is the power of 2 with which to automatically scale
	// up the input coordinate space of the shaper. This factor will
	// be removed prior to returning dimensions. This ensures that the
	// returned glyph dimensions take advantage of all of the precision
	// that a fixed.Int26_6 can provide.
	scaleShift = 6
)

// clamp ensures val is in the inclusive range [low,high].
func clamp(, ,  int) int {
	if  <  {
		return 
	}
	if  >  {
		return 
	}
	return 
}

// Shape turns an input into an output.
func ( *HarfbuzzShaper) ( Input) Output {
	// Prepare to shape the text.
	if .buf == nil {
		.buf = harfbuzz.NewBuffer()
	} else {
		.buf.Clear()
	}

	, ,  := .Text, .RunStart, .RunEnd
	if  <  {
		// Try to guess what the caller actually wanted.
		,  = , 
	}
	 = clamp(, 0, len())
	 = clamp(, 0, len())
	.buf.AddRunes(, , -)

	// handle vertical sideways text
	 := false
	if .Direction.IsSideways() {
		// temporarily switch to horizontal
		.Direction = .Direction.SwitchAxis()
		 = true
	}

	.buf.Props.Direction = .Direction.Harfbuzz()
	.buf.Props.Language = .Language
	.buf.Props.Script = .Script

	// reuse font when possible
	,  := .fonts.Get(.Face.Font)
	if ! { // create a new font and cache it
		 = harfbuzz.NewFont(.Face)
		.fonts.Put(.Face.Font, )
	}
	// adjust the user provided fields
	.XScale = int32(.Size.Ceil()) << scaleShift
	.YScale = .XScale

	if  := len(.FontFeatures); cap(.features) <  {
		.features = make([]harfbuzz.Feature, )
	} else {
		.features = .features[0:]
	}
	for ,  := range .FontFeatures {
		.features[] = harfbuzz.Feature{
			Tag:   .Tag,
			Value: .Value,
			Start: harfbuzz.FeatureGlobalStart,
			End:   harfbuzz.FeatureGlobalEnd,
		}
	}

	// Actually use harfbuzz to shape the text.
	.buf.Shape(, .features)

	// Convert the shaped text into an Output.
	 := .Direction.IsVertical()
	 := make([]Glyph, len(.buf.Info))
	for  := range  {
		 := .buf.Info[].Glyph
		[] = Glyph{
			ClusterIndex: .buf.Info[].Cluster,
			GlyphID:      ,
			Mask:         .buf.Info[].Mask,
		}
		,  := .GlyphExtents()
		if ! {
			// Leave the glyph having zero size if it isn't in the font. There
			// isn't really anything we can do to recover from such an error.
			continue
		}
		[].Width = fixed.I(int(.Width)) >> scaleShift
		[].Height = fixed.I(int(.Height)) >> scaleShift
		[].XBearing = fixed.I(int(.XBearing)) >> scaleShift
		[].YBearing = fixed.I(int(.YBearing)) >> scaleShift
		if  {
			[].YAdvance = fixed.I(int(.buf.Pos[].YAdvance)) >> scaleShift
			[].Advance = [].YAdvance
		} else {
			[].XAdvance = fixed.I(int(.buf.Pos[].XAdvance)) >> scaleShift
			[].Advance = [].XAdvance
		}
		[].XOffset = fixed.I(int(.buf.Pos[].XOffset)) >> scaleShift
		[].YOffset = fixed.I(int(.buf.Pos[].YOffset)) >> scaleShift
	}
	countClusters(, .RunEnd, .Direction.Progression())
	 := Output{
		Glyphs:    ,
		Direction: .Direction,
		Face:      .Face,
		Size:      .Size,
	}
	.Runes.Offset = .RunStart
	.Runes.Count = .RunEnd - .RunStart

	if  {
		// set the Direction to the correct value.
		// this is required here so that the following call to ExtentsForDirection
		// returns the vertical data.
		.sideways()
	}

	 := .ExtentsForDirection(.Direction.Harfbuzz())
	.LineBounds = Bounds{
		Ascent:  fixed.I(int(.Ascender)) >> scaleShift,
		Descent: fixed.I(int(.Descender)) >> scaleShift,
		Gap:     fixed.I(int(.LineGap)) >> scaleShift,
	}
	.RecalculateAll()
	return 
}

// countClusters tallies the number of runes and glyphs in each cluster
// and updates the relevant fields on the provided glyph slice.
func countClusters( []Glyph,  int,  di.Progression) {
	 := -1
	 := 0
	 := 0
	 := 
	for  := range  {
		 := [].ClusterIndex
		if  !=  {
			// If we're processing a new cluster, count the runes and glyphs
			// that compose it.
			 = 0
			 = 1
			 = 
			 := -1
		:
			for  :=  + 1;  < len(); ++ {
				if [].ClusterIndex ==  {
					++
				} else {
					 = [].ClusterIndex
					break 
				}
			}
			if  == -1 {
				 = 
			}
			switch  {
			case di.FromTopLeft:
				 =  - 
			case di.TowardTopLeft:
				 =  - 
			}
			 = 
		}
		[].GlyphCount = 
		[].RuneCount = 
	}
}