// Ported from https://github.com/faiface/pixel/tree/master/text
// Trimmed down to essentials of measuring text

package textmeasure

import (
	
	
	
	

	
	
	
	

	
	
)

const TAB_SIZE = 4
const SIZELESS_FONT_SIZE = 0
const CODE_LINE_HEIGHT = 1.3

// Runes encompasses ASCII, Latin-1, and geometric shapes like black square
var Runes []rune

func init() {
	// ASCII range (U+0000 to U+007F)
	for  := rune(0x0000);  <= rune(0x007F); ++ {
		Runes = append(Runes, )
	}

	// Latin-1 Supplement (U+0080 to U+00FF)
	for  := rune(0x0080);  <= rune(0x00FF); ++ {
		Runes = append(Runes, )
	}

	// Geometric Shapes (U+25A0 to U+25FF)
	for  := rune(0x25A0);  <= rune(0x25FF); ++ {
		Runes = append(Runes, )
	}
}

// Ruler allows for effiecient and convenient text drawing.
//
// To create a Ruler object, use the New constructor:
//
//	txt := text.New(pixel.ZV, text.NewAtlas(face, text.ASCII))
//
// As suggested by the constructor, a Ruler object is always associated with one font face and a
// fixed set of runes. For example, the Ruler we created above can draw text using the font face
// contained in the face variable and is capable of drawing ASCII characters.
//
// Here we create a Ruler object which can draw ASCII and Katakana characters:
//
//	txt := text.New(0, text.NewAtlas(face, text.ASCII, text.RangeTable(unicode.Katakana)))
//
// Similarly to IMDraw, Ruler functions as a buffer. It implements io.Writer interface, so writing
// text to it is really simple:
//
//	fmt.Print(txt, "Hello, world!")
//
// Newlines, tabs and carriage returns are supported.
//
// Finally, if we want the written text to show up on some other Target, we can draw it:
//
//	txt.Draw(target)
//
// Ruler exports two important fields: Orig and Dot. Dot is the position where the next character
// will be written. Dot is automatically moved when writing to a Ruler object, but you can also
// manipulate it manually. Orig specifies the text origin, usually the top-left dot position. Dot is
// always aligned to Orig when writing newlines. The Clear method resets the Dot to Orig.
type Ruler struct {
	// Orig specifies the text origin, usually the top-left dot position. Dot is always aligned
	// to Orig when writing newlines.
	Orig *geo.Point

	// Dot is the position where the next character will be written. Dot is automatically moved
	// when writing to a Ruler object, but you can also manipulate it manually
	Dot *geo.Point

	// lineHeight is the vertical distance between two lines of text.
	//
	// Example:
	//   txt.lineHeight = 1.5 * txt.atlas.lineHeight
	LineHeightFactor float64
	lineHeights      map[d2fonts.Font]float64

	// tabWidth is the horizontal tab width. Tab characters will align to the multiples of this
	// width.
	//
	// Example:
	//   txt.tabWidth = 8 * txt.atlas.glyph(' ').Advance
	tabWidths map[d2fonts.Font]float64

	atlases map[d2fonts.Font]*atlas

	ttfs  map[d2fonts.Font]*parsedFont
	faces map[d2fonts.Font]font.Face

	buf    []byte
	prevR  rune
	bounds *rect

	// when drawing text also union Ruler.bounds with Dot
	boundsWithDot bool
}

// New creates a new Ruler capable of drawing runes contained in the provided atlas. Orig and Dot
// will be initially set to orig.
//
// Here we create a Ruler capable of drawing ASCII characters using the Go Regular font.
//
//	ttf, err := parseFont(goregular.TTF)
//	if err != nil {
//	    panic(err)
//	}
//	face := ttf.newFace(14)
//	txt := text.New(orig, text.NewAtlas(face, text.ASCII))
func () (*Ruler, error) {
	 := geo.NewPoint(0, 0)
	 := &Ruler{
		Orig:             ,
		Dot:              .Copy(),
		LineHeightFactor: 1.,
		lineHeights:      make(map[d2fonts.Font]float64),
		tabWidths:        make(map[d2fonts.Font]float64),
		atlases:          make(map[d2fonts.Font]*atlas),
		ttfs:             make(map[d2fonts.Font]*parsedFont),
		faces:            make(map[d2fonts.Font]font.Face),
	}

	for ,  := range d2fonts.FontFamilies {
		for ,  := range d2fonts.FontStyles {
			 := d2fonts.Font{
				Family: ,
				Style:  ,
			}
			// Note: FontFaces lookup is size-agnostic
			,  := d2fonts.FontFaces.Lookup()
			if ! {
				continue
			}
			if ,  := .ttfs[]; ! {
				,  := parseFont()
				if  != nil {
					return nil, 
				}
				.ttfs[] = 
			}
		}
	}

	.clear()

	return , nil
}

func ( *Ruler) ( *d2fonts.FontFamily) bool {
	for ,  := range d2fonts.FontStyles {
		 := d2fonts.Font{
			Family: *,
			Style:  ,
			Size:   SIZELESS_FONT_SIZE,
		}
		,  := .ttfs[]
		if ! {
			return false
		}
	}

	return true
}

func ( *Ruler) ( d2fonts.Font) {
	 := 
	.Size = SIZELESS_FONT_SIZE
	 := .fontFace(, .ttfs[])
	 := NewAtlas(, Runes)
	.atlases[] = 
	.lineHeights[] = .lineHeight
	.tabWidths[] = .glyph(' ').advance * TAB_SIZE
}

// fontFace retains the per-size scratch buffers used by immutable font metrics.
// A Ruler already owns mutable drawing state and must not be used concurrently.
func ( *Ruler) ( d2fonts.Font,  *parsedFont) font.Face {
	if  := .faces[];  != nil {
		return 
	}
	 := .newFace(float64(.Size))
	.faces[] = 
	return 
}

func ( *Ruler) ( d2fonts.Font,  string) (float64, bool) {
	 := 
	.Size = SIZELESS_FONT_SIZE
	,  := .ttfs[]
	if ! {
		return 0, false
	}

	var  sfnt.Buffer
	for ,  := range  {
		,  := .font.GlyphIndex(&, )
		if  != nil || ( == 0 &&  != 0) {
			return 0, false
		}
	}

	 := .fontFace(, )
	,  := font.BoundString(, )
	 := min(.Min.X, 0)
	 := max(.Max.X, )
	return float64(-) / 64, true
}

// measureFontAdvance returns the cursor advance used by CSS/SVG text layout.
// MeasurePrecise intentionally includes ink bearings for legacy D2 box
// measurement; using that ink box to position adjacent styled Markdown runs
// adds visible gaps after bold and italic spans.
func ( *Ruler) ( d2fonts.Font,  string) (float64, bool) {
	 := 
	.Size = SIZELESS_FONT_SIZE
	,  := .ttfs[]
	if ! {
		return 0, false
	}

	var  sfnt.Buffer
	for ,  := range  {
		,  := .font.GlyphIndex(&, )
		if  != nil || ( == 0 &&  != 0) {
			return 0, false
		}
	}
	 := .fontFace(, )
	return float64(font.MeasureString(, )) / 64, true
}

// cssFontBoxMetrics returns the integer CSS-pixel font box that browsers use
// to place an SVG text baseline. These are the original OpenType metrics, not
// the deliberately legacy-compatible em metrics used by D2's text measurer.
func ( *Ruler) ( d2fonts.Font,  float64) (,  float64,  bool) {
	 := 
	.Size = SIZELESS_FONT_SIZE
	,  := .ttfs[]
	if ! {
		return 0, 0, false
	}

	 := fixed.Int26_6(math.Round( * 64))
	,  := .font.Metrics(nil, , font.HintingNone)
	if  != nil {
		return 0, 0, false
	}
	return math.Round(float64(.Ascent) / 64), math.Round(float64(.Descent) / 64), true
}

func ( *Ruler) ( d2fonts.Font,  float64) (float64, bool) {
	 := 
	.Size = SIZELESS_FONT_SIZE
	,  := .ttfs[]
	if ! {
		return 0, false
	}
	 := fixed.Int26_6(math.Round( * 64))
	,  := .font.Metrics(nil, , font.HintingNone)
	if  != nil {
		return 0, false
	}
	return math.Round(float64(.Height) / 64), true
}

func ( *Ruler) ( float64,  d2fonts.Font,  string) float64 {
	// Keep D2's established approximation for ordinary labels. Native
	// Markdown has a separate CSS fallback path below; changing this function
	// would resize unrelated diagrams that happen to contain Unicode text.
	if uniseg.GraphemeClusterCount() != len() {
		for ,  := range strings.Split(, "\n") {
			,  := .MeasurePrecise(, )
			 := uniseg.NewGraphemes()

			 := d2fonts.SourceCodePro.Font(.Size, .Style)
			for .Next() {
				if .Width() == 1 {
					continue
				}
				var  rune
				 := .Orig.Copy()
				 := newRect()
				for ,  := range .Runes() {
					var  bool
					,  = .controlRune(, , )
					if  {
						continue
					}

					var  *rect
					_, _, ,  = .atlases[].DrawRune(, , )
					 = .union()
					 = 
				}
				 -= .w()
				 += .spaceWidth() * float64(.Width())
			}
			 = math.Max(, )
		}
	}
	return 
}

// scaleUnicodeCSS approximates the host-font fallback advances used by
// Chromium and SVG viewers when native Markdown contains glyphs absent from
// D2's embedded fonts.
func ( *Ruler) ( float64,  d2fonts.Font,  string) float64 {
	return .scaleUnicodeFallback(, , , true)
}

func ( *Ruler) ( float64,  d2fonts.Font,  string,  bool) float64 {
	// Weird unicode stuff is going on when this is true
	// See https://github.com/rivo/uniseg#grapheme-clusters
	// This method is a good-enough approximation. It overshoots, but not by much.
	// I suspect we need to import a font with the right glyphs to get the precise measurements
	// but Hans fonts are heavy.
	if uniseg.GraphemeClusterCount() != len() {
		 = 0
		for ,  := range strings.Split(, "\n") {
			,  := .MeasurePrecise(, )
			// Font subsets omit layout tables such as GPOS. Use the original
			// glyph advances so supported Unicode runes are not measured as the
			// replacement glyph used by the fixed-size atlas.
			if ,  := .measureFontWidth(, );  {
				 = math.Max(, )
				continue
			}
			 := uniseg.NewGraphemes()
			 = 0
			for .Next() {
				 := .Str()
				,  := .measureFontWidth(, )
				if  {
					 += 
					continue
				}
				 :=  && isColorEmojiGrapheme()
				if  && ! {
					 := stripDefaultIgnorableRunes()
					if  !=  {
						if  == "" {
							continue
						}
						if ,  := .measureFontWidth(, );  {
							 += 
							continue
						}
						 = 
					}
				}
				if isZeroWidthGrapheme() {
					continue
				}
				// The eventual SVG viewer supplies its own fallback font. CSS emoji
				// and East Asian clusters normally occupy one em (two terminal
				// cells); narrow missing glyphs occupy half an em. Reserving 2.5
				// monospace cells made mixed-script Markdown boxes far wider than
				// Chromium's actual layout.
				 := .Width()
				if  < 1 {
					 = 1
				}
				 := float64(.Size) * float64() / 2
				if  {
					if  {
						 = math.Max(, float64(.Size)*1.25)
					} else if hasUnicodeSymbol() {
						// Text-presentation emoji-capable symbols such as bare ⚠
						// occupy one em in Chromium's fallback font.
						 = math.Max(, float64(.Size))
					}
				}
				 += 
			}
			 = math.Max(, )
		}
	}
	return 
}

func stripDefaultIgnorableRunes( string) string {
	var  strings.Builder
	for ,  := range  {
		if !isDefaultIgnorableRune() {
			.WriteRune()
		}
	}
	return .String()
}

func isColorEmojiGrapheme( string) bool {
	 := false
	 := false
	 := false
	for ,  := range  {
		if  == '\ufe0f' {
			 = true
			continue
		}
		if  == '\u20e3' {
			 = true
			continue
		}
		if unicode.IsSymbol() {
			 = true
		}
		// Uniseg's width table incorporates Unicode Emoji_Presentation.
		// Restricting the two-cell result to Other_Symbol distinguishes default
		// emoji from wide East Asian letters. Checking each base rune also keeps
		// default emoji wide when an explicit text variation selector is present,
		// matching Chromium's host fallback behavior.
		if unicode.Is(unicode.So, ) && uniseg.StringWidth(string()) == 2 {
			return true
		}
	}
	if  {
		for ,  := range  {
			if  == '#' ||  == '*' || ( >= '0' &&  <= '9') {
				 = true
				break
			}
		}
	}
	return  && 
}

func hasUnicodeSymbol( string) bool {
	for ,  := range  {
		if unicode.IsSymbol() {
			return true
		}
	}
	return false
}

// scaleUnicodeLegacy preserves the approximation historically used by D2's
// Markdown box measurement while filtering controls that browsers do not
// advance. Native painting uses scaleUnicodeCSS above.
func ( *Ruler) ( float64,  d2fonts.Font,  string) float64 {
	if uniseg.GraphemeClusterCount() == len() {
		return 
	}
	// Preserve the old fallback algorithm (including its original ink width)
	// after removing controls that Blink gives no advance. Uniseg can attach
	// such a control to a visible narrow base (for example x + ZWJ), so filtering
	// only wholly invisible clusters still leaves an atlas replacement glyph.
	// Keep wide emoji/CJK clusters intact: their variation selectors, tags, and
	// joiners can determine the browser's glyph and terminal-cell width.
	,  := legacyMeasurementText()
	if  {
		 = 0
	}
	for ,  := range strings.Split(, "\n") {
		,  := .MeasurePrecise(, )
		 := 
		 := uniseg.NewGraphemes()
		 := d2fonts.SourceCodePro.Font(.Size, .Style)
		for .Next() {
			if .Width() == 1 {
				continue
			}
			var  rune
			 := .Orig.Copy()
			 := newRect()
			for ,  := range .Runes() {
				var  bool
				,  = .controlRune(, , )
				if  {
					continue
				}
				var  *rect
				_, _, ,  = .atlases[].DrawRune(, , )
				 = .union()
				 = 
			}
			 -= .w()
			 += .spaceWidth() * float64(.Width())
		}
		if  {
			 = math.Max(, )
		}
		 = math.Max(, )
	}
	return 
}

func legacyMeasurementText( string) (string, bool) {
	var  strings.Builder
	 := false
	 := uniseg.NewGraphemes()
	for .Next() {
		 := .Str()
		if isDefaultIgnorableGrapheme() {
			 = true
			continue
		}
		if .Width() != 1 {
			.WriteString()
			continue
		}
		for ,  := range  {
			if isDefaultIgnorableRune() {
				 = true
				continue
			}
			.WriteRune()
		}
	}
	if ! {
		return , false
	}
	return .String(), true
}

func isZeroWidthGrapheme( string) bool {
	for ,  := range  {
		if unicode.Is(unicode.Mn, ) || unicode.Is(unicode.Me, ) || isDefaultIgnorableRune() {
			continue
		}
		return false
	}
	return true
}

func isDefaultIgnorableGrapheme( string) bool {
	for ,  := range  {
		if !isDefaultIgnorableRune() {
			return false
		}
	}
	return  != ""
}

func isDefaultIgnorableRune( rune) bool {
	// Blink gives most format controls and variation selectors no advance, but
	// renders several controls and Unicode "default ignorables" through fallback
	// fonts. Follow that observed behavior instead of erasing the whole derived
	// Unicode property.
	if  == '\u00ad' || // conditional; handled by line wrapping
		 == '\u180f' || // Mongolian free variation selector four has fallback ink in Blink
		 == '\u3164' ||  == '\uffa0' || // visibly advancing Hangul fillers
		( >= '\ufff9' &&  <= '\ufffb') || // interlinear annotations
		( >= '\U00013430' &&  <= '\U0001343f') || // Egyptian hieroglyph format controls
		( >= '\U0001bca0' &&  <= '\U0001bca3') || // visibly advancing shorthand controls
		unicode.Is(unicode.Prepended_Concatenation_Mark, ) ||
		unicode.IsSpace() {
		return false
	}
	return unicode.Is(unicode.Cf, ) ||
		unicode.Is(unicode.Other_Default_Ignorable_Code_Point, ) ||
		unicode.Is(unicode.Variation_Selector, )
}

func ( *Ruler) ( d2fonts.Font,  string) (,  int) {
	 := .boundsWithDot
	.boundsWithDot = true
	,  = .Measure(, )
	.boundsWithDot = 
	return , 
}

func ( *Ruler) ( d2fonts.Font,  string) (,  int) {
	,  := .MeasurePrecise(, )
	 = .scaleUnicode(, , )
	return int(math.Ceil()), int(math.Ceil())
}

func ( *Ruler) ( d2fonts.Font,  string) (,  float64) {
	if ,  := .atlases[]; ! {
		.addFontSize()
	}
	.clear()
	.buf = append(.buf, ...)
	.drawBuf()
	 := .bounds
	return .w(), .h()
}

// clear removes all written text from the Ruler. The Dot field is reset to Orig.
func ( *Ruler) () {
	.prevR = -1
	.bounds = newRect()
	.Dot = .Orig.Copy()
}

// controlRune checks if r is a control rune (newline, tab, ...). If it is, a new dot position and
// true is returned. If r is not a control rune, the original dot and false is returned.
func ( *Ruler) ( rune,  *geo.Point,  d2fonts.Font) ( *geo.Point,  bool) {
	switch  {
	case '\n':
		.X = .Orig.X
		.Y -= .LineHeightFactor * .lineHeights[]
	case '\r':
		.X = .Orig.X
	case '\t':
		 := math.Mod(.X-.Orig.X, .tabWidths[])
		 = math.Mod(, +.tabWidths[])
		if  == 0 {
			 = .tabWidths[]
		}
		.X += 
	default:
		return , false
	}
	return , true
}

func ( *Ruler) ( d2fonts.Font) {
	if !utf8.FullRune(.buf) {
		return
	}

	for utf8.FullRune(.buf) {
		,  := utf8.DecodeRune(.buf)
		.buf = .buf[:]

		var  bool
		.Dot,  = .controlRune(, .Dot, )
		if  {
			continue
		}

		, ,  := .atlases[].measureRune(.prevR, , .Dot)
		.prevR = 

		if .boundsWithDot {
			.bounds.tl.X = math.Min(.bounds.tl.X, .Dot.X)
			.bounds.tl.Y = math.Min(.bounds.tl.Y, .Dot.Y)
			.bounds.br.X = math.Max(.bounds.br.X, .Dot.X)
			.bounds.br.Y = math.Max(.bounds.br.Y, .Dot.Y)
		} else if .bounds.w()*.bounds.h() == 0 {
			*.bounds.tl, *.bounds.br = .tl, .br
			continue
		}
		.bounds.tl.X = math.Min(.bounds.tl.X, .tl.X)
		.bounds.tl.Y = math.Min(.bounds.tl.Y, .tl.Y)
		.bounds.br.X = math.Max(.bounds.br.X, .br.X)
		.bounds.br.Y = math.Max(.bounds.br.Y, .br.Y)
	}
}

func ( *Ruler) ( d2fonts.Font) float64 {
	if ,  := .atlases[]; ! {
		.addFontSize()
	}
	,  := utf8.DecodeRuneInString(" ")
	return .atlases[].glyph().advance
}