package shaping

import (
	
	

	
	
	
)

// glyphIndex is the index in a Glyph slice
type glyphIndex = int

// mapRunesToClusterIndices
// returns a slice that maps rune indicies in the text to the index of the
// first glyph in the glyph cluster containing that rune in the shaped text.
// The indicies are relative to the region of runes covered by the input run.
// To translate an absolute rune index in text into a rune index into the returned
// mapping, subtract run.Runes.Offset first. If the provided buf is large enough to
// hold the return value, it will be used instead of allocating a new slice.
func mapRunesToClusterIndices( di.Direction,  Range,  []Glyph,  []glyphIndex) []glyphIndex {
	if .Count <= 0 {
		return nil
	}
	var  []glyphIndex
	if cap() >= .Count {
		 = [:.Count]
	} else {
		 = make([]glyphIndex, .Count)
	}
	 := 0
	 := .Progression() == di.TowardTopLeft
	if  {
		 = len() - 1
	}
	// off tracks the offset position of the glyphs from the first rune of the
	// shaped text. This must be subtracted from all cluster indicies in order to
	// normalize them into the range [0,runes.Count).
	 := .Offset
	for  := 0;  < .Count; ++ {
		for  >= 0 &&  < len() &&
			(( && [].ClusterIndex- <= ) ||
				(! && [].ClusterIndex- < )) {
			if  {
				--
			} else {
				++
			}
		}
		if  {
			++
		} else if ( >= 0 &&  < len() &&
			[].ClusterIndex- > ) ||
			( == len() && len() > 1) {
			--
			 := [].ClusterIndex - 
			for -1 >= 0 && [-1].ClusterIndex- ==  {
				--
			}
		}
		if  < 0 {
			 = 0
		} else if  >= len() {
			 = len() - 1
		}
		[] = 
	}
	return 
}

// mapRuneToClusterIndex finds the lowest-index glyph for the glyph cluster contiaining the rune
// at runeIdx in the source text. It uses a binary search of the glyphs in order to achieve this.
// It is equivalent to using mapRunesToClusterIndices on only a single rune index, and is thus
// more efficient for single lookups while being less efficient for runs which require many
// lookups anyway.
func mapRuneToClusterIndex( di.Direction,  Range,  []Glyph,  int) glyphIndex {
	var  int
	 := .Progression() == di.TowardTopLeft
	if ! {
		 = sort.Search(len(), func( int) bool {
			return [].ClusterIndex-.Offset > 
		})
	} else {
		 = sort.Search(len(), func( int) bool {
			return [].ClusterIndex-.Offset < 
		})
	}
	if  < 1 {
		return 0
	}
	 := [-1].ClusterIndex
	if  && -.Offset >  {
		return 
	}
	for -1 >= 0 && [-1].ClusterIndex ==  {
		--
	}
	return 
}

func mapRunesToClusterIndices2( di.Direction,  Range,  []Glyph,  []glyphIndex) []glyphIndex {
	if .Count <= 0 {
		return nil
	}
	var  []glyphIndex
	if cap() >= .Count {
		 = [:.Count]
	} else {
		 = make([]glyphIndex, .Count)
	}

	 := .Progression() == di.TowardTopLeft
	if  {
		for  := len() - 1;  >= 0; -- {
			 := [].ClusterIndex
			 := 
			for -1 >= 0 && [-1].ClusterIndex ==  {
				--
				 = 
			}
			var  int
			if -1 >= 0 {
				 = [-1].ClusterIndex
			} else {
				 = .Count + .Offset
			}
			 :=  - 
			 :=  - .Offset
			for  := ;  <= + &&  < len(); ++ {
				[] = 
			}
		}
	} else {
		for  := 0;  < len(); ++ {
			 := [].ClusterIndex
			 := 
			for +1 < len() && [+1].ClusterIndex ==  {
				++
			}
			var  int
			if +1 < len() {
				 = [+1].ClusterIndex
			} else {
				 = .Count + .Offset
			}
			 :=  - 
			 :=  - .Offset
			for  := ;  <= + &&  < len(); ++ {
				[] = 
			}
		}
	}
	return 
}

func mapRunesToClusterIndices3( di.Direction,  Range,  []Glyph,  []glyphIndex) []glyphIndex {
	if .Count <= 0 {
		return nil
	}
	var  []glyphIndex
	if cap() >= .Count {
		 = [:.Count]
	} else {
		 = make([]glyphIndex, .Count)
	}

	 := .Progression() == di.TowardTopLeft
	if  {
		for  := len() - 1;  >= 0; {
			 := &[]
			// go to the start of the cluster
			 -= (.GlyphCount - 1)
			 := .ClusterIndex - .Offset // map back to [0;runes.Count[
			 := .RuneCount + 
			for  := ;  <=  &&  < len(); ++ {
				[] = 
			}
			// go to the next cluster
			--
		}
	} else {
		for  := 0;  < len(); {
			 := &[]
			 := .ClusterIndex - .Offset // map back to [0;runes.Count[
			 := .RuneCount + 
			for  := ;  <=  &&  < len(); ++ {
				[] = 
			}
			// go to the next cluster
			 += .GlyphCount
		}
	}
	return 
}

// inclusiveGlyphRange returns the inclusive range of runes and glyphs matching
// the provided start and breakAfter rune positions.
// runeToGlyph must be a valid mapping from the rune representation to the
// glyph reprsentation produced by mapRunesToClusterIndices.
// numGlyphs is the number of glyphs in the output representing the runes
// under consideration.
func inclusiveGlyphRange( di.Direction, ,  int,  []int,  int) (,  glyphIndex) {
	 := .Progression() == di.TowardTopLeft
	if  {
		 = []
		if -1 >= 0 {
			 = [-1] - 1
		} else {
			 =  - 1
		}
	} else {
		 = []
		if +1 < len() {
			 = [+1] - 1
		} else {
			 =  - 1
		}
	}
	return
}

// cutRun returns the sub-run of run containing glyphs corresponding to the provided
// _inclusive_ rune range.
// if [trimStart] is true, the leading letter spacing is removed
func cutRun( Output,  []glyphIndex, ,  int,  bool) Output {
	// Convert the rune range of interest into an inclusive range within the
	// current run's runes.
	 :=  - .Runes.Offset
	 :=  - .Runes.Offset
	if  < 0 {
		// If the start location is prior to the run of shaped text under consideration,
		// just work from the beginning of this run.
		 = 0
	}
	if  >= len() {
		// If the break location is after the entire run of shaped text,
		// keep through the end of the run.
		 = len() - 1
	}
	,  := inclusiveGlyphRange(.Direction, , , , len(.Glyphs))

	// Construct a run out of the inclusive glyph range.
	.Glyphs = .Glyphs[ : +1]
	.Runes.Count =  -  + 1
	.Runes.Offset = .Runes.Offset + 
	if  {
		.trimStartLetterSpacing()
	}
	.RecomputeAdvance()
	return 
}

// breakOption represets a location within the rune slice at which
// it may be safe to break a line of text.
type breakOption struct {
	// breakAtRune is the index at which it is safe to break.
	breakAtRune int
	// required indicates that the break option is mandatory.
	required bool
}

// isValid returns whether a given option violates shaping rules (like breaking
// a shaped text cluster).
func ( breakOption) ( []int,  Output) bool {
	 := .breakAtRune - .Runes.Offset
	 :=  + 1
	if  < len() &&  >= 0 {
		// Check if this break is valid.
		 := []
		 := []
		if  >= len(.Glyphs) ||  >= len(.Glyphs) {
			return false
		}
		 := .Glyphs[].ClusterIndex
		 := .Glyphs[].ClusterIndex
		if  ==  {
			// This break is within a harfbuzz cluster, and is
			// therefore invalid.
			return false
		}
	}
	return true
}

// breaker generates line breaking candidates for a text.
type breaker struct {
	wordSegmenter     *segmenter.LineIterator
	graphemeSegmenter *segmenter.GraphemeIterator
	totalRunes        int
	// unusedWordBreak is a break requested from the breaker in a previous iteration
	// but which was not chosen as the line ending. Subsequent invocations of
	// WrapLine should start with this break.
	unusedWordBreak breakOption
	// previousWordBreak tracks the previous line breaking candidate, if any. It is
	// used to identify the range of runes between the previous and current
	// candidate.
	previousWordBreak breakOption
	// isUnusedWord indicates that the unusedBreak field is valid.
	isUnusedWord        bool
	unusedGraphemeBreak breakOption
	isUnusedGrapheme    bool
}

// newBreaker returns a breaker initialized to break the provided text.
func newBreaker( *segmenter.Segmenter,  []rune) *breaker {
	.Init()
	 := &breaker{
		wordSegmenter:     .LineIterator(),
		graphemeSegmenter: .GraphemeIterator(),
		totalRunes:        len(),
	}
	return 
}

// nextWordRaw returns a naive break candidate on a uax#14 boundary which may be invalid.
func ( *breaker) () ( breakOption,  bool) {
	if .wordSegmenter.Next() {
		 := .wordSegmenter.Line()
		// Note : we dont use penalties for Mandatory Breaks so far,
		// we could add it with currentSegment.IsMandatoryBreak
		 := .Offset + len(.Text) - 1
		 := breakOption{
			breakAtRune: ,
			// Don't treat the EOF line break as special. We implicitly always break after
			// the end of text input anyway.
			required: .IsMandatoryBreak &&  != .totalRunes-1,
		}
		return , true
	}
	// Unicode rules impose to always break at the end
	return breakOption{}, false
}

// nextGraphemeRaw returns a naive break candidate on a uax#29 boundary which may be invalid.
func ( *breaker) () ( breakOption,  bool) {
	if .graphemeSegmenter.Next() {
		 := .graphemeSegmenter.Grapheme()
		// Note : we dont use penalties for Mandatory Breaks so far,
		// we could add it with currentSegment.IsMandatoryBreak
		 := breakOption{
			breakAtRune: .Offset + len(.Text) - 1,
		}
		return , true
	}
	// Unicode rules impose to always break at the end
	return breakOption{}, false
}

// nextWordBreak returns the next rune offset at which the line can be broken
// on a UAX#14 boundary if any. If it returns false, there are no more candidates.
func ( *breaker) () (breakOption, bool) {
	var  breakOption
	if .isUnusedWord {
		 = .unusedWordBreak
		.isUnusedWord = false
	} else {
		var  bool
		,  = .nextWordRaw()
		if ! {
			return , false
		}
		.previousWordBreak = .unusedWordBreak
		.unusedWordBreak = 
	}
	return , true
}

func ( *breaker) () {
	.isUnusedWord = true
}

// nextGraphemeBreak returns the next grapheme cluster boundary break between
// the previous and current word boundary, if any. If it returns false, there are no
// more candidates between the previous and current word boundaries.
func ( *breaker) () (breakOption, bool) {
	for {
		var (
			  breakOption
			 bool
		)
		if .isUnusedGrapheme {
			.isUnusedGrapheme = false
			 = .unusedGraphemeBreak
			 = true
		} else {
			,  = .nextGraphemeRaw()
		}
		if ! {
			return , false
		}
		// We don't want to consider the previous word break position in general, as it has already
		// been tried. The one exception to this is when we're iterating the very first time, in
		// which case the previousWordBreak will have its zero value and we still want to consider
		// breaking after the first rune if there's a grapheme cluseter boundary there.
		if .breakAtRune <= .previousWordBreak.breakAtRune && .previousWordBreak.breakAtRune > 0 {
			continue
		}
		.unusedGraphemeBreak = 
		if .breakAtRune > .unusedWordBreak.breakAtRune {
			// We've walked the grapheme iterator past the end of the line wrapping
			// candidate, so mark that we may need to re-check this break option
			// when evaluating the next segment.
			.isUnusedGrapheme = true
			return , false
		}
		return , true
	}
}

func ( *breaker) () {
	.isUnusedGrapheme = true
}

// Range indicates the location of a sequence of elements within a longer slice.
type Range struct {
	Offset int
	Count  int
}

// Line holds runs of shaped text wrapped onto a single line. All the contained
// Output should be displayed sequentially on one line.
type Line []Output

// WrapConfig provides line-wrapper settings.
type WrapConfig struct {
	// Direction describes the text layout of the overall paragraph, rather than
	// individual runs of text. This is used to compute the correct visual order of
	// bidirectional text runs.
	Direction di.Direction
	// TruncateAfterLines is the number of lines of text to allow before truncating
	// the text. A value of zero means no limit.
	TruncateAfterLines int
	// Truncator, if provided, will be inserted at the end of a truncated line. This
	// feature is only active if TruncateAfterLines is nonzero. See the documentation
	// for [LineWrapper.WrapNextLine] for details about how this works.
	Truncator Output
	// TextContinues indicates that the paragraph wrapped by this config is not the
	// final paragraph in the text. This alters text truncation when filling the
	// final line permitted by TruncateAfterLines. If the text of this paragraph
	// does fit entirely on TruncateAfterLines, normally the truncator symbol would
	// not be inserted. However, if the overall body of text continues beyond this
	// paragraph (indicated by TextContinues), the truncator should still be inserted
	// to indicate that further paragraphs of text were truncated. This field has
	// no effect if TruncateAfterLines is zero.
	TextContinues bool
	// BreakPolicy determines under what circumstances the wrapper will consider
	// breaking in between UAX#14 line breaking candidates, or "within words" in
	// many scripts.
	BreakPolicy LineBreakPolicy
	// DisableTrailingWhitespaceTrim turns off a feature that automatically sets the
	// advance of trailing whitespace on a line to zero. In display contexts, you
	// usually want this feature enabled, but for text editors it is frequently
	// desirable to allow trailing whitespace to occupy space itself.
	DisableTrailingWhitespaceTrim bool
}

// LineBreakPolicy specifies when considering a line break within a "word" or UAX#14
// segment is allowed.
type LineBreakPolicy uint8

const (
	// WhenNecessary means that lines will only be broken within words when the word
	// cannot fit on the next line by itself or during truncation to preserve as much
	// of the final word as possible.
	WhenNecessary LineBreakPolicy = iota
	// Never means that words will never be broken internally, allowing them to exceed
	// the specified maxWidth.
	Never
	// Always means that lines will always choose to break within words if it means that
	// more text can fit on the line.
	Always
)

func ( LineBreakPolicy) () string {
	switch  {
	case WhenNecessary:
		return "WhenNecessary"
	case Never:
		return "Never"
	case Always:
		return "Always"
	default:
		return "unknown"
	}
}

// WithTruncator returns a copy of WrapConfig with the Truncator field set to the
// result of shaping input with shaper.
func ( WrapConfig) ( Shaper,  Input) WrapConfig {
	.Truncator = .Shape()
	return 
}

// runMapper efficiently maps a run to glyph clusters.
type runMapper struct {
	// valid indicates that the mapping field is populated.
	valid bool
	// runIdx is the index of the mapped run within glyphRuns.
	runIdx int
	// mapping holds the rune->glyph mapping for the run at index mappedRun within
	// glyphRuns.
	mapping []glyphIndex
}

// mapRun updates the mapping field to be valid for the given run. It will skip the mapping
// operation if the provided runIdx is equal to the runIdx of the previous call, as the
// current mapping value is already correct.
func ( *runMapper) ( int,  Output) {
	if .runIdx !=  || !.valid {
		.mapping = mapRunesToClusterIndices3(.Direction, .Runes, .Glyphs, .mapping)
		.runIdx = 
		.valid = true
	}
}

// RunIterator defines a type that can incrementally provide shaped text.
type RunIterator interface {
	// Next returns the next run in the iterator, if any. If there is a next run,
	// its index, content, and true will be returned, and the iterator will advance
	// to the following element. Otherwise Next returns an undefined index, an empty
	// Output, and false.
	Next() (index int, run Output, isValid bool)
	// Peek returns the same thing Next() would, but does not advance the iterator (so the
	// next call to Next() will return the same thing).
	Peek() (index int, run Output, isValid bool)
	// Save marks the current iterator position such that the iterator can return to it later
	// when Restore() is called. Only one position may be saved at a time, with subsequent
	// calls to Save() overriding the current value.
	Save()
	// Restore resets the iteration state to the most recently Save()-ed position.
	Restore()
}

// shapedRunSlice is a [RunIterator] built from already-shaped text.
type shapedRunSlice struct {
	runs     []Output
	idx      int
	savedIdx int
}

var _ RunIterator = (*shapedRunSlice)(nil)

// NewSliceIterator returns a [RunIterator] backed by an already-shaped slice of [Output]s.
func ( []Output) RunIterator {
	return &shapedRunSlice{
		runs: ,
	}
}

// Reset configures the runSlice for reuse with the given shaped text.
func ( *shapedRunSlice) ( []Output) {
	.runs = 
	.idx = 0
	.savedIdx = 0
}

// Next implements [RunIterator.Next].
func ( *shapedRunSlice) () (int, Output, bool) {
	, ,  := .Peek()
	if  {
		.idx++
	}
	return , , 
}

// Peek implements [RunIterator.Peek].
func ( *shapedRunSlice) () (int, Output, bool) {
	if .idx >= len(.runs) {
		return .idx, Output{}, false
	}
	 := .runs[.idx]
	return .idx, , true
}

// Save implements [RunIterator.Save].
func ( *shapedRunSlice) () {
	.savedIdx = .idx
}

// Restore implements [RunIterator.Restore].
func ( *shapedRunSlice) () {
	.idx = .savedIdx
}

// wrapBuffer provides reusable buffers for line wrapping. When using a
// wrapBuffer, returned line wrapping results will use memory stored within
// the buffer. This means that the same buffer cannot be reused for another
// wrapping operation while the wrapped lines are still in use (unless they
// are deeply copied). If necessary, using multiple wrapBuffers can work
// around this restriction.
type wrapBuffer struct {
	// paragraph is a buffer holding paragraph allocated (primarily) from subregions
	// of the line field.
	paragraph []Line
	// line is a large buffer of Outputs that is used to build lines.
	line []Output
	// lineUsed is the index of the first unused element in line.
	lineUsed int
	// lineExhausted indicates whether the previous shaping used all of line.
	lineExhausted bool
	// alt is a smaller temporary buffer that holds candidate lines while
	// they are being built.
	alt []Output
	// altAdvance is the sum of the advances of each run in alt.
	altAdvance fixed.Int26_6
	// altSave is a slice into alt used to save and restore the state of the alt buffer.
	altSave []Output
	// altAdvanceSave is the altAdvance of the altSave field.
	altAdvanceSave fixed.Int26_6
	// best is a slice holding the best known line. When possible, it
	// is a subslice of line, but if line runs out of capacity it will
	// be heap allocated.
	best []Output
	// bestInLine tracks whether the current best is allocated from within line.
	bestInLine bool
}

func ( *wrapBuffer) () {
	if cap(.paragraph) < 10 {
		.paragraph = make([]Line, 0, 10)
	}
	.paragraph = .paragraph[:0]
	if cap(.alt) < 10 {
		.alt = make([]Output, 0, 10)
	}
	.alt = .alt[:0]
	.altAdvance = 0
	.altSave = .alt[:0]
	.altAdvanceSave = 0
	if cap(.line) < 100 {
		.line = make([]Output, 0, 100)
	}
	.line = .line[:0]
	.lineUsed = 0
	.best = nil
	.bestInLine = false
	if .lineExhausted {
		.lineExhausted = false
		// Trigger the go slice growth heuristic by appending an element to
		// the capacity.
		.line = append(.line[:cap(.line)], Output{})[:0]
	}
}

// singleRunParagraph is an optimized helper for quickly constructing
// a []Line containing only a single run.
func ( *wrapBuffer) ( Output) []Line {
	.paragraph = .paragraph[:0]
	 := .line[.lineUsed : .lineUsed+1]
	[0] = 
	.paragraphAppend()
	return .finalParagraph()
}

func ( *wrapBuffer) ( []Output) {
	.paragraph = append(.paragraph, )
}

func ( *wrapBuffer) () []Line {
	return .paragraph
}

func ( *wrapBuffer) () {
	.alt = .alt[:0]
	.altAdvance = 0
	.altSave = .alt[:0]
	.altAdvanceSave = 0
	.best = nil
	.bestInLine = false
}

// candidateLen returns the number of [Output]s in the current line wrapping candidate.
func ( *wrapBuffer) () int { return len(.alt) }

// candidateAppend adds the given run to the current line wrapping candidate.
func ( *wrapBuffer) ( Output) {
	.alt = append(.alt, )
	.altAdvance = .altAdvance + .Advance
}

// candidateSave captures the current state of the line candidate, enabling it to
// be restored by a call to candidateRestore(). Only one state can be saved at a
// time, and a subsequent call to candidateSave will override any current saved
// state.
func ( *wrapBuffer) () {
	.altSave = .alt
	.altAdvanceSave = .altAdvance
}

// candidateRestore resets the state of the line candidate to the state at the time
// of the most recent call to candidateSave().
func ( *wrapBuffer) () {
	.alt = .altSave
	.altAdvance = .altAdvanceSave
}

func ( *wrapBuffer) () fixed.Int26_6 {
	return .altAdvance
}

// markCandidateBest marks that the current line wrapping candidate is the best
// known line wrapping candidate with the given suffixes. Providing suffixes does
// not modify the current candidate, but does ensure that the "best" candidate ends
// with them.
func ( *wrapBuffer) ( ...Output) {
	 := len(.alt) + len()
	if len(.line[.lineUsed:cap(.line)]) <  {
		.lineExhausted = true
		.best = make([]Output, )
		.bestInLine = false
	} else {
		.best = .line[.lineUsed : .lineUsed+]
		.bestInLine = true
	}
	 := copy(.best, .alt)
	copy(.best[:], )
}

// hasBest returns whether there is currently a known valid line wrapping candidate
// for the line.
func ( *wrapBuffer) () bool {
	return len(.best) > 0
}

// finalizeBest commits the storage for the current best line and returns it.
func ( *wrapBuffer) () []Output {
	if .bestInLine {
		.lineUsed += len(.best)
	}
	return .best
}

// LineWrapper holds reusable state for a line wrapping operation. Reusing
// LineWrappers for multiple paragraphs should improve performance.
type LineWrapper struct {
	// config holds the current line wrapping settings.
	config WrapConfig
	// truncating tracks whether the wrapper should be performing truncation.
	truncating bool
	// seg is an internal storage used to initiate the breaker iterator.
	seg segmenter.Segmenter

	// breaker provides line-breaking candidates.
	breaker *breaker

	// scratch holds wrapping algorithm storage buffers for reuse.
	scratch wrapBuffer

	// mapper tracks rune->glyphCluster mappings.
	mapper runMapper
	// glyphRuns holds the runs of shaped text being wrapped.
	glyphRuns RunIterator
	// lineStartRune is the rune index of the first rune on the next line to
	// be shaped.
	lineStartRune int
	// more indicates that the iteration API has more data to return.
	more bool
}

// Prepare initializes the LineWrapper for the given paragraph and shaped text.
// It must be called prior to invoking WrapNextLine. Prepare invalidates any
// lines previously returned by this wrapper.
func ( *LineWrapper) ( WrapConfig,  []rune,  RunIterator) {
	.config = 
	.truncating = .config.TruncateAfterLines > 0
	.breaker = newBreaker(&.seg, )
	.glyphRuns = 
	.lineStartRune = 0
	.more = true
	.mapper.valid = false
	.scratch.reset()
}

// WrapParagraph wraps the paragraph's shaped glyphs to a constant maxWidth.
// It is equivalent to iteratively invoking WrapLine with a constant maxWidth.
// If the config has a non-zero TruncateAfterLines, WrapParagraph will return at most
// that many lines. The truncated return value is the count of runes truncated from
// the end of the text. The returned lines are only valid until the next call to
// [*LineWrapper.WrapParagraph] or [*LineWrapper.Prepare].
//
// See [(*LineWrapper).WrapNextLine] for a description of how [WrapConfig]'s truncation
// features impact the wrapped text output. This method returns the quantity of runes
// truncated by line wrapping in the [truncated] return value.
//
// See also [WrapParagraphF] which supports a decimal [maxWidth].
func ( *LineWrapper) ( WrapConfig,  int,  []rune,  RunIterator) ( []Line,  int) {
	 := math.MaxInt32 >> 6
	if  >  {
		 = 
	}
	return .WrapParagraphF(, fixed.I(), , )
}

// WrapParagraphF is the same as [WrapParagraph], but accepts a non integer [maxWidth].
func ( *LineWrapper) ( WrapConfig,  fixed.Int26_6,  []rune,  RunIterator) ( []Line,  int) {
	.scratch.reset()
	// Check whether we can skip line wrapping altogether for the simple single-run-that-fits case.
	if !(.TextContinues && .TruncateAfterLines == 1) {
		.Save()
		// We can only skip wrapping if the text doesn't contain any forced line
		// breaks that need to be evaluated by the real algorithm, so we need to
		// quickly scan it for that.
		.breaker = newBreaker(&.seg, )
		 := false
		for {
			,  := .breaker.nextWordBreak()
			if ! {
				break
			}
			if .required {
				 = true
				break
			}
		}
		if ! {
			, ,  := .Next()
			, ,  := .Peek()
			if  && ! {
				if .Advance <=  {
					return .scratch.singleRunParagraph(), 0
				}
			}
		}
		.Restore()
	}

	.Prepare(, , )
	var (
		 WrappedLine
		 bool
	)
	for ! {
		,  = .WrapNextLineF()
		if .Line != nil {
			.scratch.paragraphAppend(.Line)
		}
	}
	return .scratch.finalParagraph(), .Truncated
}

// fillUntil tries to fill the line candidate slice with runs until it reaches a run containing the
// provided break option.
func ( *LineWrapper) ( RunIterator,  breakOption) {
	, ,  := .Peek()
	for  && .breakAtRune >= .Runes.Count+.Runes.Offset {
		if .lineStartRune >= .Runes.Offset+.Runes.Count {
			// Consume the run we peeked (which we know is valid)
			_, _, _ = .Next()

			, ,  = .Peek()
			continue
		} else if .lineStartRune > .Runes.Offset {
			// If part of this run has already been used on a previous line, trim
			// the runes corresponding to those glyphs off.
			.mapper.mapRun(, )
			 := .scratch.candidateLen() == 0
			 = cutRun(, .mapper.mapping, .lineStartRune, .Runes.Count+.Runes.Offset, )
		}
		// While the run being processed doesn't contain the current line breaking
		// candidate, just append it to the candidate line.
		.scratch.candidateAppend()
		// Consume the run we peeked (which we know is valid)
		_, _, _ = .Next()

		, ,  = .Peek()
	}
}

// lineConfig tracks settings for line wrapping a single line of text.
type lineConfig struct {
	// truncating indicates whether this line is being truncated (if sufficiently long).
	truncating bool
	// maxWidth is the maximum space a line can occupy.
	maxWidth fixed.Int26_6
	// truncatedMaxWidth holds the maximum width of the line available for text if the truncator
	// is occupying part of the line.
	truncatedMaxWidth fixed.Int26_6
}

// WrappedLine is the result of wrapping one line of text.
type WrappedLine struct {
	// Line is the content of the line, as a slice of shaped runs
	Line Line
	// Truncated is the count of runes truncated from the end of the line,
	// if this line was truncated.
	Truncated int
	// NextLine is the indice (in the input text slice) of the begining
	// of the next line. It will equal len(text) if all the text
	// fit in one line.
	NextLine int

	// TrimmedTrailingWhitespace is the space taken by trailing whitespace
	// before if was trimmed (usually positive).
	// It is zero if [DisableTrailingWhitespaceTrim] is set to true,
	// or if there is no whitespace at the end of the line.
	TrimmedTrailingWhitespace fixed.Int26_6
}

// swapVisualOrder inverts the visual index of runs in [subline], by swapping pairs of visual indices across the midpoint
// of the slice.
func swapVisualOrder( Line) {
	 := len()
	for  := range [0 : /2] {
		 := ( - ) - 1
		[].VisualIndex, [].VisualIndex = [].VisualIndex, [].VisualIndex
	}
}

// computeBidiOrdering resolves the [VisualIndex] of each run.
func computeBidiOrdering( di.Direction,  Line) {
	 := -1
	for ,  := range  {
		 := 
		if .Progression() == di.TowardTopLeft {
			 = len() - 1 - 
		}
		[].VisualIndex = int32()
		if .Direction ==  {
			if  != -1 {
				swapVisualOrder([:])
				 = -1
			}
		} else if  == -1 {
			 = 
		}
	}
	if  != -1 {
		swapVisualOrder([:])
	}
}

func ( *LineWrapper) ( Line,  bool) (WrappedLine, bool) {
	var  fixed.Int26_6
	if len() > 0 {
		computeBidiOrdering(.config.Direction, )
		if !.config.DisableTrailingWhitespaceTrim {
			// Here we find the last visual run in the line.
			 := len() - 1
			if .config.Direction.Progression() == di.TowardTopLeft {
				 = 0
			}
			for ,  := range  {
				if .VisualIndex == int32() {
					 = 
					break
				}
			}
			 := &[]

			// This next block locates the first/last visual glyph on the line and
			// zeroes its advance if it is whitespace.
			if  := len(.Glyphs);  > 0 {
				var  *Glyph
				if .config.Direction.Progression() == di.FromTopLeft {
					 = &.Glyphs[-1]
				} else {
					 = &.Glyphs[0]
				}

				if .Direction.IsVertical() {
					if .Height == 0 {
						.YAdvance = 0
						.Advance = 0
					}
				} else { // horizontal
					if .Width == 0 {
						.XAdvance = 0
						.Advance = 0
					}
				}
				 := .Advance
				.RecomputeAdvance()
				 =  - .Advance
			}
		}

		 := [len()-1]
		// Update the start position of the next line.
		.lineStartRune = .Runes.Count + .Runes.Offset
	}

	// Check whether we've exhausted the text.
	 =  || .lineStartRune >= .breaker.totalRunes

	// Implement truncation if needed.
	 := 0
	if .truncating {
		.config.TruncateAfterLines--
		 := false
		if .config.TruncateAfterLines == 0 {
			 = true
			 = .breaker.totalRunes - .lineStartRune
			 =  > 0 || .config.TextContinues
		}
		if  {
			 := .config.Truncator
			.Runes.Count = 
			.Runes.Offset = .lineStartRune
			 = append(, )
			// We've just modified the line, we need to recompute the bidi ordering.
			computeBidiOrdering(.config.Direction, )
		}
	}

	// Mark the paragraph as complete if needed.
	if  {
		.more = false
	}

	return WrappedLine{, , .lineStartRune, }, 
}

// WrapNextLine wraps the shaped glyphs of a paragraph to a particular max width.
// It is meant to be called iteratively to wrap each line, allowing lines to
// be wrapped to different widths within the same paragraph. When done is true,
// subsequent calls to WrapNextLine (without calling Prepare) will return a nil line.
//
// The returned line is only valid until the next call to
// [*LineWrapper.Prepare] or [*LineWrapper.WrapParagraph].
//
// If the LineWrapper's [WrapConfig].TruncateAfterLines
// is non-zero, the final line of text returned by successive calls to WrapNextLine
// may be truncated. The quantity of runes truncated by wrapping the line is
// returned in [WrappedLine].Truncated. If this is non-zero AND the LineWrapper's
// [WrapConfig].Truncator is set, the final line will end with an extra [Output].
// [Output]s before the final [Output] will represent the input runes that are still
// visible before truncation, and the final [Output] will be a copy of the Truncator
// with its Runes.Count set to the quantity of runes truncated during line wrapping.
//
// See also [WrapNextLineF] which supports a decimal [maxWidth].
func ( *LineWrapper) ( int) ( WrappedLine,  bool) {
	 := math.MaxInt32 >> 6
	if  >  {
		 = 
	}
	return .WrapNextLineF(fixed.I())
}

// WrapNextLineF is the same as [WrapNextLine], but accepts a non integer [maxWidth].
func ( *LineWrapper) ( fixed.Int26_6) ( WrappedLine,  bool) {
	// If we've already finished the paragraph, don't do any more work.
	if !.more {
		return WrappedLine{NextLine: .lineStartRune}, true
	}

	defer func() {
		,  = .postProcessLine(.Line, )
	}()

	// If the iterator is empty, return early.
	, ,  := .glyphRuns.Peek()
	if ! {
		return WrappedLine{}, true
	}
	.scratch.startLine()

	 := lineConfig{
		truncating:        .config.TruncateAfterLines == 1,
		maxWidth:          ,
		truncatedMaxWidth:  - .config.Truncator.Advance,
	}
	 = .wrapNextLine()
	 := .scratch.finalizeBest()
	return WrappedLine{Line: }, 
}

// checkpoint captures both the current candidate line and the corresponding run iteration
// state. These can be restored together by calling restore().
func ( *LineWrapper) () {
	.scratch.candidateSave()
	.glyphRuns.Save()
}

// restore resets the current candidate line and corresponding run iteration state to the
// values at the last call to checkpoint().
func ( *LineWrapper) () {
	.scratch.candidateRestore()
	.glyphRuns.Restore()
}

// wrapNextLine iteratively processes line breaking candidates, building a line within the
// wrapper's scratch [WrapBuffer]. It returns whether the paragraph is finished once it has
// successfully built a line.
func ( *LineWrapper) ( lineConfig) ( bool) {
	for {
		.checkpoint()
		,  := .breaker.nextWordBreak()
		if ! {
			break
		}
		switch ,  := .processBreakOption(, );  {
		case breakInvalid:
			.restore()
			continue
		case fits:
			.scratch.markCandidateBest()
			if .required {
				return false
			}
			continue
		case endLine:
			// Found a valid line ending the text, append the candidateRun and use it.
			.scratch.markCandidateBest()
			return true
		case truncated:
			// The candidateRun does not fit.
			if !.scratch.hasBest() {
				.scratch.markCandidateBest()
			}
			if .config.BreakPolicy == Never {
				return true
			}
			// Fall through to try grapheme breaking.
		case newLineBeforeBreak:
			.restore()
			// We found a valid line that didn't use this break, so mark that it can be
			// reused on the next iteration.
			.breaker.markWordOptionUnused()
			if .config.BreakPolicy == Never || (.config.BreakPolicy == WhenNecessary && !.truncating) {
				return false
			}
			// Fall through to try grapheme breaking.
		case cannotFit:
			if .config.BreakPolicy == Never {
				if .truncating {
					return true
				}
				.scratch.markCandidateBest()
				return false
			}
			// Fall through to try grapheme breaking.
		}
		// Ensure that the grapheme breaking has access to
		// all runs we already tried in the iterator.
		.restore()
		// segment using UAX#29 grapheme clustering here and try
		// breaking again using only those boundaries to find a viable break in cases
		// where no UAX#14 breaks were viable above.
		for {
			.checkpoint()
			,  := .breaker.nextGraphemeBreak()
			if ! {
				break
			}
			switch ,  := .processBreakOption(, );  {
			case breakInvalid:
				.restore()
				continue
			case fits:
				// If we found at least one viable line candidate, we aren't using the word break option.
				.scratch.markCandidateBest()
				.breaker.markWordOptionUnused()
				continue
			case endLine:
				.scratch.markCandidateBest()
				return true
			case truncated:
				if !.scratch.hasBest() {
					.scratch.markCandidateBest()
				}
				return true
			case newLineBeforeBreak:
				.restore()
				// If we found at least one viable line candidate, we aren't using the word break option.
				.breaker.markWordOptionUnused()
				.breaker.markGraphemeOptionUnused()
				return false
			case cannotFit:
				if .truncating {
					// Don't append the candidate grapheme to leave as much space as possible for the
					// truncator.
					return true
				}
				// If no graphemes fit, we should still use one so that the line contains something. Maybe
				// the next grapheme will fit on the next line.
				.scratch.markCandidateBest()
				.breaker.markWordOptionUnused()
				return false
			}
		}
		return false
	}
	return true
}

type processBreakResult uint8

const (
	// breakInvalid indicates that the provided break candidate is incompatible with the shaped
	// text and should be skipped.
	breakInvalid processBreakResult = iota
	// endLine indicates that the candidate fits on the line and terminates the text.
	endLine
	// truncated indicates that the candidate does not fit on the line and that truncation needs to
	// occur.
	truncated
	// newLineBeforeBreak indicates that the candidate contains a valid line, but the latest break
	// option could not fit.
	newLineBeforeBreak
	// fits indicates that the text up to the break option fit within the line and that another break
	// option can be attempted.
	fits
	// cannotFit indicates that this is the first break option on the line, and that even this option cannot
	// fit within the space available. When cannotFit is returned, the scratch buffer's candidate will contain
	// the run that cannot fit, but it will not be committed as the best option. The choice of how to handle
	// this is left to higher-level logic.
	cannotFit
)

// processBreakOption evaluates whether the provided breakOption can fit onto the current line wrapping line.
func ( *LineWrapper) ( breakOption,  lineConfig) (processBreakResult, Output) {
	// Discard break options on previous lines.
	if .breakAtRune < .lineStartRune {
		return breakInvalid, Output{}
	}

	// Fill candidate line with runs until the run containing the break option.
	.fillUntil(.glyphRuns, )

	, ,  := .glyphRuns.Peek()
	.mapper.mapRun(, )
	if !.isValid(.mapper.mapping, ) {
		// Reject invalid line break candidate and acquire a new one.
		return breakInvalid, Output{}
	}
	 := .scratch.candidateLen() == 0
	 := cutRun(, .mapper.mapping, .lineStartRune, .breakAtRune, )
	 := .advanceSpaceAware(.config.Direction) + .scratch.candidateAdvance()
	if  > .maxWidth {
		// The run doesn't fit on the line.
		if !.scratch.hasBest() {
			return cannotFit, 
		} else {
			return newLineBeforeBreak, 
		}
	} else if .truncating &&  > .truncatedMaxWidth {
		// The run would not fit if truncated.
		 := .Runes.Count + .Runes.Offset
		if  == .breaker.totalRunes && !.config.TextContinues {
			// The run contains the entire end of the text, so no truncation is
			// necessary.
			return endLine, 
		}
		// We must truncate the line in order to show it.
		return truncated, 
	} else {
		// The run does fit on the line. Commit this line as the best known
		// line, but keep lineCandidate unmodified so that later break
		// options can be attempted to see if a more optimal solution is
		// available.
		return fits, 
	}
}