github.com/utopiagio/gio@v0.0.8/text/gotext.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package text
     4  
     5  import (
     6  	"bytes"
     7  	"fmt"
     8  	"image"
     9  	"io"
    10  	"log"
    11  	"os"
    12  
    13  	"github.com/go-text/typesetting/di"
    14  	"github.com/go-text/typesetting/font"
    15  	"github.com/go-text/typesetting/fontscan"
    16  	"github.com/go-text/typesetting/language"
    17  	"github.com/go-text/typesetting/opentype/api"
    18  	"github.com/go-text/typesetting/opentype/api/metadata"
    19  	"github.com/go-text/typesetting/shaping"
    20  	"golang.org/x/exp/slices"
    21  	"golang.org/x/image/math/fixed"
    22  	"golang.org/x/text/unicode/bidi"
    23  
    24  	"github.com/utopiagio/gio/f32"
    25  	giofont "github.com/utopiagio/gio/font"
    26  	"github.com/utopiagio/gio/font/opentype"
    27  	"github.com/utopiagio/gio/internal/debug"
    28  	"github.com/utopiagio/gio/io/system"
    29  	"github.com/utopiagio/gio/op"
    30  	"github.com/utopiagio/gio/op/clip"
    31  	"github.com/utopiagio/gio/op/paint"
    32  )
    33  
    34  // document holds a collection of shaped lines and alignment information for
    35  // those lines.
    36  type document struct {
    37  	lines     []line
    38  	alignment Alignment
    39  	// alignWidth is the width used when aligning text.
    40  	alignWidth      int
    41  	unreadRuneCount int
    42  }
    43  
    44  // append adds the lines of other to the end of l and ensures they
    45  // are aligned to the same width.
    46  func (l *document) append(other document) {
    47  	l.lines = append(l.lines, other.lines...)
    48  	l.alignWidth = max(l.alignWidth, other.alignWidth)
    49  	calculateYOffsets(l.lines)
    50  }
    51  
    52  // reset empties the document in preparation to reuse its memory.
    53  func (l *document) reset() {
    54  	l.lines = l.lines[:0]
    55  	l.alignment = Start
    56  	l.alignWidth = 0
    57  	l.unreadRuneCount = 0
    58  }
    59  
    60  func max(a, b int) int {
    61  	if a > b {
    62  		return a
    63  	}
    64  	return b
    65  }
    66  
    67  // A line contains the measurements of a line of text.
    68  type line struct {
    69  	// runs contains sequences of shaped glyphs with common attributes. The order
    70  	// of runs is logical, meaning that the first run will contain the glyphs
    71  	// corresponding to the first runes of data in the original text.
    72  	runs []runLayout
    73  	// visualOrder is a slice of indices into Runs that describes the visual positions
    74  	// of each run of text. Iterating this slice and accessing Runs at each
    75  	// of the values stored in this slice traverses the runs in proper visual
    76  	// order from left to right.
    77  	visualOrder []int
    78  	// width is the width of the line.
    79  	width fixed.Int26_6
    80  	// ascent is the height above the baseline.
    81  	ascent fixed.Int26_6
    82  	// descent is the height below the baseline, including
    83  	// the line gap.
    84  	descent fixed.Int26_6
    85  	// lineHeight captures the gap that should exist between the baseline of this
    86  	// line and the previous (if any).
    87  	lineHeight fixed.Int26_6
    88  	// direction is the dominant direction of the line. This direction will be
    89  	// used to align the text content of the line, but may not match the actual
    90  	// direction of the runs of text within the line (such as an RTL sentence
    91  	// within an LTR paragraph).
    92  	direction system.TextDirection
    93  	// runeCount is the number of text runes represented by this line's runs.
    94  	runeCount int
    95  
    96  	yOffset int
    97  }
    98  
    99  // insertTrailingSyntheticNewline adds a synthetic newline to the final logical run of the line
   100  // with the given shaping cluster index.
   101  func (l *line) insertTrailingSyntheticNewline(newLineClusterIdx int) {
   102  	// If there was a newline at the end of this paragraph, insert a synthetic glyph representing it.
   103  	finalContentRun := len(l.runs) - 1
   104  	// If there was a trailing newline update the rune counts to include
   105  	// it on the last line of the paragraph.
   106  	l.runeCount += 1
   107  	l.runs[finalContentRun].Runes.Count += 1
   108  
   109  	syntheticGlyph := glyph{
   110  		id:           0,
   111  		clusterIndex: newLineClusterIdx,
   112  		glyphCount:   0,
   113  		runeCount:    1,
   114  		xAdvance:     0,
   115  		yAdvance:     0,
   116  		xOffset:      0,
   117  		yOffset:      0,
   118  	}
   119  	// Inset the synthetic newline glyph on the proper end of the run.
   120  	if l.runs[finalContentRun].Direction.Progression() == system.FromOrigin {
   121  		l.runs[finalContentRun].Glyphs = append(l.runs[finalContentRun].Glyphs, syntheticGlyph)
   122  	} else {
   123  		// Ensure capacity.
   124  		l.runs[finalContentRun].Glyphs = append(l.runs[finalContentRun].Glyphs, glyph{})
   125  		copy(l.runs[finalContentRun].Glyphs[1:], l.runs[finalContentRun].Glyphs)
   126  		l.runs[finalContentRun].Glyphs[0] = syntheticGlyph
   127  	}
   128  }
   129  
   130  func (l *line) setTruncatedCount(truncatedCount int) {
   131  	// If we've truncated the text with a truncator, adjust the rune counts within the
   132  	// truncator to make it represent the truncated text.
   133  	finalRunIdx := len(l.runs) - 1
   134  	l.runs[finalRunIdx].truncator = true
   135  	finalGlyphIdx := len(l.runs[finalRunIdx].Glyphs) - 1
   136  	// The run represents all of the truncated text.
   137  	l.runs[finalRunIdx].Runes.Count = truncatedCount
   138  	// Only the final glyph represents any runes, and it represents all truncated text.
   139  	for i := range l.runs[finalRunIdx].Glyphs {
   140  		if i == finalGlyphIdx {
   141  			l.runs[finalRunIdx].Glyphs[finalGlyphIdx].runeCount = truncatedCount
   142  		} else {
   143  			l.runs[finalRunIdx].Glyphs[finalGlyphIdx].runeCount = 0
   144  		}
   145  	}
   146  }
   147  
   148  // Range describes the position and quantity of a range of text elements
   149  // within a larger slice. The unit is usually runes of unicode data or
   150  // glyphs of shaped font data.
   151  type Range struct {
   152  	// Count describes the number of items represented by the Range.
   153  	Count int
   154  	// Offset describes the start position of the represented
   155  	// items within a larger list.
   156  	Offset int
   157  }
   158  
   159  // glyph contains the metadata needed to render a glyph.
   160  type glyph struct {
   161  	// id is this glyph's identifier within the font it was shaped with.
   162  	id GlyphID
   163  	// clusterIndex is the identifier for the text shaping cluster that
   164  	// this glyph is part of.
   165  	clusterIndex int
   166  	// glyphCount is the number of glyphs in the same cluster as this glyph.
   167  	glyphCount int
   168  	// runeCount is the quantity of runes in the source text that this glyph
   169  	// corresponds to.
   170  	runeCount int
   171  	// xAdvance and yAdvance describe the distance the dot moves when
   172  	// laying out the glyph on the X or Y axis.
   173  	xAdvance, yAdvance fixed.Int26_6
   174  	// xOffset and yOffset describe offsets from the dot that should be
   175  	// applied when rendering the glyph.
   176  	xOffset, yOffset fixed.Int26_6
   177  	// bounds describes the visual bounding box of the glyph relative to
   178  	// its dot.
   179  	bounds fixed.Rectangle26_6
   180  }
   181  
   182  type runLayout struct {
   183  	// VisualPosition describes the relative position of this run of text within
   184  	// its line. It should be a valid index into the containing line's VisualOrder
   185  	// slice.
   186  	VisualPosition int
   187  	// X is the visual offset of the dot for the first glyph in this run
   188  	// relative to the beginning of the line.
   189  	X fixed.Int26_6
   190  	// Glyphs are the actual font characters for the text. They are ordered
   191  	// from left to right regardless of the text direction of the underlying
   192  	// text.
   193  	Glyphs []glyph
   194  	// Runes describes the position of the text data this layout represents
   195  	// within the containing text.Line.
   196  	Runes Range
   197  	// Advance is the sum of the advances of all clusters in the Layout.
   198  	Advance fixed.Int26_6
   199  	// PPEM is the pixels-per-em scale used to shape this run.
   200  	PPEM fixed.Int26_6
   201  	// Direction is the layout direction of the glyphs.
   202  	Direction system.TextDirection
   203  	// face is the font face that the ID of each Glyph in the Layout refers to.
   204  	face font.Face
   205  	// truncator indicates that this run is a text truncator standing in for remaining
   206  	// text.
   207  	truncator bool
   208  }
   209  
   210  // shaperImpl implements the shaping and line-wrapping of opentype fonts.
   211  type shaperImpl struct {
   212  	// Fields for tracking fonts/faces.
   213  	fontMap      *fontscan.FontMap
   214  	faces        []font.Face
   215  	faceToIndex  map[font.Font]int
   216  	faceMeta     []giofont.Font
   217  	defaultFaces []string
   218  	logger       interface {
   219  		Printf(format string, args ...any)
   220  	}
   221  	parser parser
   222  
   223  	// Shaping and wrapping state.
   224  	shaper        shaping.HarfbuzzShaper
   225  	wrapper       shaping.LineWrapper
   226  	bidiParagraph bidi.Paragraph
   227  
   228  	// Scratch buffers used to avoid re-allocating slices during routine internal
   229  	// shaping operations.
   230  	splitScratch1, splitScratch2 []shaping.Input
   231  	outScratchBuf                []shaping.Output
   232  	scratchRunes                 []rune
   233  
   234  	// bitmapGlyphCache caches extracted bitmap glyph images.
   235  	bitmapGlyphCache bitmapCache
   236  }
   237  
   238  // debugLogger only logs messages if debug.Text is true.
   239  type debugLogger struct {
   240  	*log.Logger
   241  }
   242  
   243  func newDebugLogger() debugLogger {
   244  	return debugLogger{Logger: log.New(log.Writer(), "[text] ", log.Default().Flags())}
   245  }
   246  
   247  func (d debugLogger) Printf(format string, args ...any) {
   248  	if debug.Text.Load() {
   249  		d.Logger.Printf(format, args...)
   250  	}
   251  }
   252  
   253  func newShaperImpl(systemFonts bool, collection []FontFace) *shaperImpl {
   254  	var shaper shaperImpl
   255  	shaper.logger = newDebugLogger()
   256  	shaper.fontMap = fontscan.NewFontMap(shaper.logger)
   257  	shaper.faceToIndex = make(map[font.Font]int)
   258  	if systemFonts {
   259  		str, err := os.UserCacheDir()
   260  		if err != nil {
   261  			shaper.logger.Printf("failed resolving font cache dir: %v", err)
   262  			shaper.logger.Printf("skipping system font load")
   263  		}
   264  		if err := shaper.fontMap.UseSystemFonts(str); err != nil {
   265  			shaper.logger.Printf("failed loading system fonts: %v", err)
   266  		}
   267  	}
   268  	for _, f := range collection {
   269  		shaper.Load(f)
   270  		shaper.defaultFaces = append(shaper.defaultFaces, string(f.Font.Typeface))
   271  	}
   272  	shaper.shaper.SetFontCacheSize(32)
   273  	return &shaper
   274  }
   275  
   276  // Load registers the provided FontFace with the shaper, if it is compatible.
   277  // It returns whether the face is now available for use. FontFaces are prioritized
   278  // in the order in which they are loaded, with the first face being the default.
   279  func (s *shaperImpl) Load(f FontFace) {
   280  	desc := opentype.FontToDescription(f.Font)
   281  	s.fontMap.AddFace(f.Face.Face(), fontscan.Location{File: fmt.Sprint(desc)}, desc)
   282  	s.addFace(f.Face.Face(), f.Font)
   283  }
   284  
   285  func (s *shaperImpl) addFace(f font.Face, md giofont.Font) {
   286  	if _, ok := s.faceToIndex[f.Font]; ok {
   287  		return
   288  	}
   289  	s.logger.Printf("loaded face %s(style:%s, weight:%d)", md.Typeface, md.Style, md.Weight)
   290  	idx := len(s.faces)
   291  	s.faceToIndex[f.Font] = idx
   292  	s.faces = append(s.faces, f)
   293  	s.faceMeta = append(s.faceMeta, md)
   294  }
   295  
   296  // splitByScript divides the inputs into new, smaller inputs on script boundaries
   297  // and correctly sets the text direction per-script. It will
   298  // use buf as the backing memory for the returned slice if buf is non-nil.
   299  func splitByScript(inputs []shaping.Input, documentDir di.Direction, buf []shaping.Input) []shaping.Input {
   300  	var splitInputs []shaping.Input
   301  	if buf == nil {
   302  		splitInputs = make([]shaping.Input, 0, len(inputs))
   303  	} else {
   304  		splitInputs = buf
   305  	}
   306  	for _, input := range inputs {
   307  		currentInput := input
   308  		if input.RunStart == input.RunEnd {
   309  			return []shaping.Input{input}
   310  		}
   311  		firstNonCommonRune := input.RunStart
   312  		for i := firstNonCommonRune; i < input.RunEnd; i++ {
   313  			if language.LookupScript(input.Text[i]) != language.Common {
   314  				firstNonCommonRune = i
   315  				break
   316  			}
   317  		}
   318  		currentInput.Script = language.LookupScript(input.Text[firstNonCommonRune])
   319  		for i := firstNonCommonRune + 1; i < input.RunEnd; i++ {
   320  			r := input.Text[i]
   321  			runeScript := language.LookupScript(r)
   322  
   323  			if runeScript == language.Common || runeScript == currentInput.Script {
   324  				continue
   325  			}
   326  
   327  			if i != input.RunStart {
   328  				currentInput.RunEnd = i
   329  				splitInputs = append(splitInputs, currentInput)
   330  			}
   331  
   332  			currentInput = input
   333  			currentInput.RunStart = i
   334  			currentInput.Script = runeScript
   335  			// In the future, it may make sense to try to guess the language of the text here as well,
   336  			// but this is a complex process.
   337  		}
   338  		// close and add the last input
   339  		currentInput.RunEnd = input.RunEnd
   340  		splitInputs = append(splitInputs, currentInput)
   341  	}
   342  
   343  	return splitInputs
   344  }
   345  
   346  func (s *shaperImpl) splitBidi(input shaping.Input) []shaping.Input {
   347  	var splitInputs []shaping.Input
   348  	if input.Direction.Axis() != di.Horizontal || input.RunStart == input.RunEnd {
   349  		return []shaping.Input{input}
   350  	}
   351  	def := bidi.LeftToRight
   352  	if input.Direction.Progression() == di.TowardTopLeft {
   353  		def = bidi.RightToLeft
   354  	}
   355  	s.bidiParagraph.SetString(string(input.Text), bidi.DefaultDirection(def))
   356  	out, err := s.bidiParagraph.Order()
   357  	if err != nil {
   358  		return []shaping.Input{input}
   359  	}
   360  	for i := 0; i < out.NumRuns(); i++ {
   361  		currentInput := input
   362  		run := out.Run(i)
   363  		dir := run.Direction()
   364  		_, endRune := run.Pos()
   365  		currentInput.RunEnd = endRune + 1
   366  		if dir == bidi.RightToLeft {
   367  			currentInput.Direction = di.DirectionRTL
   368  		} else {
   369  			currentInput.Direction = di.DirectionLTR
   370  		}
   371  		splitInputs = append(splitInputs, currentInput)
   372  		input.RunStart = currentInput.RunEnd
   373  	}
   374  	return splitInputs
   375  }
   376  
   377  // ResolveFace allows shaperImpl to implement shaping.FontMap, wrapping its fontMap
   378  // field and ensuring that any faces loaded as part of the search are registered with
   379  // ids so that they can be referred to by a GlyphID.
   380  func (s *shaperImpl) ResolveFace(r rune) font.Face {
   381  	face := s.fontMap.ResolveFace(r)
   382  	if face != nil {
   383  		family, aspect := s.fontMap.FontMetadata(face.Font)
   384  		md := opentype.DescriptionToFont(metadata.Description{
   385  			Family: family,
   386  			Aspect: aspect,
   387  		})
   388  		s.addFace(face, md)
   389  		return face
   390  	}
   391  	return nil
   392  }
   393  
   394  // splitByFaces divides the inputs by font coverage in the provided faces. It will use the slice provided in buf
   395  // as the backing storage of the returned slice if buf is non-nil.
   396  func (s *shaperImpl) splitByFaces(inputs []shaping.Input, buf []shaping.Input) []shaping.Input {
   397  	var split []shaping.Input
   398  	if buf == nil {
   399  		split = make([]shaping.Input, 0, len(inputs))
   400  	} else {
   401  		split = buf
   402  	}
   403  	for _, input := range inputs {
   404  		split = append(split, shaping.SplitByFace(input, s)...)
   405  	}
   406  	return split
   407  }
   408  
   409  // shapeText invokes the text shaper and returns the raw text data in the shaper's native
   410  // format. It does not wrap lines.
   411  func (s *shaperImpl) shapeText(ppem fixed.Int26_6, lc system.Locale, txt []rune) []shaping.Output {
   412  	lcfg := langConfig{
   413  		Language:  language.NewLanguage(lc.Language),
   414  		Direction: mapDirection(lc.Direction),
   415  	}
   416  	// Create an initial input.
   417  	input := toInput(nil, ppem, lcfg, txt)
   418  	if input.RunStart == input.RunEnd && len(s.faces) > 0 {
   419  		// Give the empty string a face. This is a necessary special case because
   420  		// the face splitting process works by resolving faces for each rune, and
   421  		// the empty string contains no runes.
   422  		input.Face = s.faces[0]
   423  	}
   424  	// Break input on font glyph coverage.
   425  	inputs := s.splitBidi(input)
   426  	inputs = s.splitByFaces(inputs, s.splitScratch1[:0])
   427  	inputs = splitByScript(inputs, lcfg.Direction, s.splitScratch2[:0])
   428  	// Shape all inputs.
   429  	if needed := len(inputs) - len(s.outScratchBuf); needed > 0 {
   430  		s.outScratchBuf = slices.Grow(s.outScratchBuf, needed)
   431  	}
   432  	s.outScratchBuf = s.outScratchBuf[:0]
   433  	for _, input := range inputs {
   434  		if input.Face != nil {
   435  			s.outScratchBuf = append(s.outScratchBuf, s.shaper.Shape(input))
   436  		} else {
   437  			s.outScratchBuf = append(s.outScratchBuf, shaping.Output{
   438  				// Use the text size as the advance of the entire fake run so that
   439  				// it doesn't occupy zero space.
   440  				Advance: input.Size,
   441  				Size:    input.Size,
   442  				Glyphs: []shaping.Glyph{
   443  					{
   444  						Width:        input.Size,
   445  						Height:       input.Size,
   446  						XBearing:     0,
   447  						YBearing:     0,
   448  						XAdvance:     input.Size,
   449  						YAdvance:     input.Size,
   450  						XOffset:      0,
   451  						YOffset:      0,
   452  						ClusterIndex: input.RunStart,
   453  						RuneCount:    input.RunEnd - input.RunStart,
   454  						GlyphCount:   1,
   455  						GlyphID:      0,
   456  						Mask:         0,
   457  					},
   458  				},
   459  				LineBounds: shaping.Bounds{
   460  					Ascent:  input.Size,
   461  					Descent: 0,
   462  					Gap:     0,
   463  				},
   464  				GlyphBounds: shaping.Bounds{
   465  					Ascent:  input.Size,
   466  					Descent: 0,
   467  					Gap:     0,
   468  				},
   469  				Direction: input.Direction,
   470  				Runes: shaping.Range{
   471  					Offset: input.RunStart,
   472  					Count:  input.RunEnd - input.RunStart,
   473  				},
   474  			})
   475  		}
   476  	}
   477  	return s.outScratchBuf
   478  }
   479  
   480  func wrapPolicyToGoText(p WrapPolicy) shaping.LineBreakPolicy {
   481  	switch p {
   482  	case WrapGraphemes:
   483  		return shaping.Always
   484  	case WrapWords:
   485  		return shaping.Never
   486  	default:
   487  		return shaping.WhenNecessary
   488  	}
   489  }
   490  
   491  // shapeAndWrapText invokes the text shaper and returns wrapped lines in the shaper's native format.
   492  func (s *shaperImpl) shapeAndWrapText(params Parameters, txt []rune) (_ []shaping.Line, truncated int) {
   493  	wc := shaping.WrapConfig{
   494  		TruncateAfterLines: params.MaxLines,
   495  		TextContinues:      params.forceTruncate,
   496  		BreakPolicy:        wrapPolicyToGoText(params.WrapPolicy),
   497  	}
   498  	families := s.defaultFaces
   499  	if params.Font.Typeface != "" {
   500  		parsed, err := s.parser.parse(string(params.Font.Typeface))
   501  		if err != nil {
   502  			s.logger.Printf("Unable to parse typeface %q: %v", params.Font.Typeface, err)
   503  		} else {
   504  			families = parsed
   505  		}
   506  	}
   507  	s.fontMap.SetQuery(fontscan.Query{
   508  		Families: families,
   509  		Aspect:   opentype.FontToDescription(params.Font).Aspect,
   510  	})
   511  	if wc.TruncateAfterLines > 0 {
   512  		if len(params.Truncator) == 0 {
   513  			params.Truncator = "…"
   514  		}
   515  		// We only permit a single run as the truncator, regardless of whether more were generated.
   516  		// Just use the first one.
   517  		wc.Truncator = s.shapeText(params.PxPerEm, params.Locale, []rune(params.Truncator))[0]
   518  	}
   519  	// Wrap outputs into lines.
   520  	return s.wrapper.WrapParagraph(wc, params.MaxWidth, txt, shaping.NewSliceIterator(s.shapeText(params.PxPerEm, params.Locale, txt)))
   521  }
   522  
   523  // replaceControlCharacters replaces problematic unicode
   524  // code points with spaces to ensure proper rune accounting.
   525  func replaceControlCharacters(in []rune) []rune {
   526  	for i, r := range in {
   527  		switch r {
   528  		// ASCII File separator.
   529  		case '\u001C':
   530  		// ASCII Group separator.
   531  		case '\u001D':
   532  		// ASCII Record separator.
   533  		case '\u001E':
   534  		case '\r':
   535  		case '\n':
   536  		// Unicode "next line" character.
   537  		case '\u0085':
   538  		// Unicode "paragraph separator".
   539  		case '\u2029':
   540  		default:
   541  			continue
   542  		}
   543  		in[i] = ' '
   544  	}
   545  	return in
   546  }
   547  
   548  // Layout shapes and wraps the text, and returns the result in Gio's shaped text format.
   549  func (s *shaperImpl) LayoutString(params Parameters, txt string) document {
   550  	return s.LayoutRunes(params, []rune(txt))
   551  }
   552  
   553  // Layout shapes and wraps the text, and returns the result in Gio's shaped text format.
   554  func (s *shaperImpl) Layout(params Parameters, txt io.RuneReader) document {
   555  	s.scratchRunes = s.scratchRunes[:0]
   556  	for r, _, err := txt.ReadRune(); err != nil; r, _, err = txt.ReadRune() {
   557  		s.scratchRunes = append(s.scratchRunes, r)
   558  	}
   559  	return s.LayoutRunes(params, s.scratchRunes)
   560  }
   561  
   562  func calculateYOffsets(lines []line) {
   563  	if len(lines) < 1 {
   564  		return
   565  	}
   566  	// Ceil the first value to ensure that we don't baseline it too close to the top of the
   567  	// viewport and cut off the top pixel.
   568  	currentY := lines[0].ascent.Ceil()
   569  	for i := range lines {
   570  		if i > 0 {
   571  			currentY += lines[i].lineHeight.Round()
   572  		}
   573  		lines[i].yOffset = currentY
   574  	}
   575  }
   576  
   577  // LayoutRunes shapes and wraps the text, and returns the result in Gio's shaped text format.
   578  func (s *shaperImpl) LayoutRunes(params Parameters, txt []rune) document {
   579  	hasNewline := len(txt) > 0 && txt[len(txt)-1] == '\n'
   580  	var ls []shaping.Line
   581  	var truncated int
   582  	if hasNewline {
   583  		txt = txt[:len(txt)-1]
   584  	}
   585  	if params.MaxLines != 0 && hasNewline {
   586  		// If we might end up truncating a trailing newline, we must insert the truncator symbol
   587  		// on the final line (if we hit the limit).
   588  		params.forceTruncate = true
   589  	}
   590  	ls, truncated = s.shapeAndWrapText(params, replaceControlCharacters(txt))
   591  
   592  	hasTruncator := truncated > 0 || (params.forceTruncate && params.MaxLines == len(ls))
   593  	if hasTruncator && hasNewline {
   594  		// We have a truncator at the end of the line, so the newline is logically
   595  		// truncated as well.
   596  		truncated++
   597  		hasNewline = false
   598  	}
   599  
   600  	// Convert to Lines.
   601  	textLines := make([]line, len(ls))
   602  	maxHeight := fixed.Int26_6(0)
   603  	for i := range ls {
   604  		otLine := toLine(s.faceToIndex, ls[i], params.Locale.Direction)
   605  		if otLine.lineHeight > maxHeight {
   606  			maxHeight = otLine.lineHeight
   607  		}
   608  		if isFinalLine := i == len(ls)-1; isFinalLine {
   609  			if hasNewline {
   610  				otLine.insertTrailingSyntheticNewline(len(txt))
   611  			}
   612  			if hasTruncator {
   613  				otLine.setTruncatedCount(truncated)
   614  			}
   615  		}
   616  		textLines[i] = otLine
   617  	}
   618  	if params.LineHeight != 0 {
   619  		maxHeight = params.LineHeight
   620  	}
   621  	if params.LineHeightScale == 0 {
   622  		params.LineHeightScale = 1.2
   623  	}
   624  
   625  	maxHeight = floatToFixed(fixedToFloat(maxHeight) * params.LineHeightScale)
   626  	for i := range textLines {
   627  		textLines[i].lineHeight = maxHeight
   628  	}
   629  	calculateYOffsets(textLines)
   630  	return document{
   631  		lines:      textLines,
   632  		alignment:  params.Alignment,
   633  		alignWidth: alignWidth(params.MinWidth, textLines),
   634  	}
   635  }
   636  
   637  func alignWidth(minWidth int, lines []line) int {
   638  	for _, l := range lines {
   639  		minWidth = max(minWidth, l.width.Ceil())
   640  	}
   641  	return minWidth
   642  }
   643  
   644  // Shape converts the provided glyphs into a path. The path will enclose the forms
   645  // of all vector glyphs.
   646  func (s *shaperImpl) Shape(pathOps *op.Ops, gs []Glyph) clip.PathSpec {
   647  	var lastPos f32.Point
   648  	var x fixed.Int26_6
   649  	var builder clip.Path
   650  	builder.Begin(pathOps)
   651  	for i, g := range gs {
   652  		if i == 0 {
   653  			x = g.X
   654  		}
   655  		ppem, faceIdx, gid := splitGlyphID(g.ID)
   656  		if faceIdx >= len(s.faces) {
   657  			continue
   658  		}
   659  		face := s.faces[faceIdx]
   660  		if face == nil {
   661  			continue
   662  		}
   663  		scaleFactor := fixedToFloat(ppem) / float32(face.Upem())
   664  		glyphData := face.GlyphData(gid)
   665  		switch glyphData := glyphData.(type) {
   666  		case api.GlyphOutline:
   667  			outline := glyphData
   668  			// Move to glyph position.
   669  			pos := f32.Point{
   670  				X: fixedToFloat((g.X - x) - g.Offset.X),
   671  				Y: -fixedToFloat(g.Offset.Y),
   672  			}
   673  			builder.Move(pos.Sub(lastPos))
   674  			lastPos = pos
   675  			var lastArg f32.Point
   676  
   677  			// Convert fonts.Segments to relative segments.
   678  			for _, fseg := range outline.Segments {
   679  				nargs := 1
   680  				switch fseg.Op {
   681  				case api.SegmentOpQuadTo:
   682  					nargs = 2
   683  				case api.SegmentOpCubeTo:
   684  					nargs = 3
   685  				}
   686  				var args [3]f32.Point
   687  				for i := 0; i < nargs; i++ {
   688  					a := f32.Point{
   689  						X: fseg.Args[i].X * scaleFactor,
   690  						Y: -fseg.Args[i].Y * scaleFactor,
   691  					}
   692  					args[i] = a.Sub(lastArg)
   693  					if i == nargs-1 {
   694  						lastArg = a
   695  					}
   696  				}
   697  				switch fseg.Op {
   698  				case api.SegmentOpMoveTo:
   699  					builder.Move(args[0])
   700  				case api.SegmentOpLineTo:
   701  					builder.Line(args[0])
   702  				case api.SegmentOpQuadTo:
   703  					builder.Quad(args[0], args[1])
   704  				case api.SegmentOpCubeTo:
   705  					builder.Cube(args[0], args[1], args[2])
   706  				default:
   707  					panic("unsupported segment op")
   708  				}
   709  			}
   710  			lastPos = lastPos.Add(lastArg)
   711  		}
   712  	}
   713  	return builder.End()
   714  }
   715  
   716  func fixedToFloat(i fixed.Int26_6) float32 {
   717  	return float32(i) / 64.0
   718  }
   719  
   720  func floatToFixed(f float32) fixed.Int26_6 {
   721  	return fixed.Int26_6(f * 64)
   722  }
   723  
   724  // Bitmaps returns an op.CallOp that will display all bitmap glyphs within gs.
   725  // The positioning of the bitmaps uses the same logic as Shape(), so the returned
   726  // CallOp can be added at the same offset as the path data returned by Shape()
   727  // and will align correctly.
   728  func (s *shaperImpl) Bitmaps(ops *op.Ops, gs []Glyph) op.CallOp {
   729  	var x fixed.Int26_6
   730  	bitmapMacro := op.Record(ops)
   731  	for i, g := range gs {
   732  		if i == 0 {
   733  			x = g.X
   734  		}
   735  		_, faceIdx, gid := splitGlyphID(g.ID)
   736  		if faceIdx >= len(s.faces) {
   737  			continue
   738  		}
   739  		face := s.faces[faceIdx]
   740  		if face == nil {
   741  			continue
   742  		}
   743  		glyphData := face.GlyphData(gid)
   744  		switch glyphData := glyphData.(type) {
   745  		case api.GlyphBitmap:
   746  			var imgOp paint.ImageOp
   747  			var imgSize image.Point
   748  			bitmapData, ok := s.bitmapGlyphCache.Get(g.ID)
   749  			if !ok {
   750  				var img image.Image
   751  				switch glyphData.Format {
   752  				case api.PNG, api.JPG, api.TIFF:
   753  					img, _, _ = image.Decode(bytes.NewReader(glyphData.Data))
   754  				case api.BlackAndWhite:
   755  					// This is a complex family of uncompressed bitmaps that don't seem to be
   756  					// very common in practice. We can try adding support later if needed.
   757  					fallthrough
   758  				default:
   759  					// Unknown format.
   760  					continue
   761  				}
   762  				imgOp = paint.NewImageOp(img)
   763  				imgSize = img.Bounds().Size()
   764  				s.bitmapGlyphCache.Put(g.ID, bitmap{img: imgOp, size: imgSize})
   765  			} else {
   766  				imgOp = bitmapData.img
   767  				imgSize = bitmapData.size
   768  			}
   769  			off := op.Affine(f32.Affine2D{}.Offset(f32.Point{
   770  				X: fixedToFloat((g.X - x) - g.Offset.X),
   771  				Y: fixedToFloat(g.Offset.Y + g.Bounds.Min.Y),
   772  			})).Push(ops)
   773  			cl := clip.Rect{Max: imgSize}.Push(ops)
   774  
   775  			glyphSize := image.Rectangle{
   776  				Min: image.Point{
   777  					X: g.Bounds.Min.X.Round(),
   778  					Y: g.Bounds.Min.Y.Round(),
   779  				},
   780  				Max: image.Point{
   781  					X: g.Bounds.Max.X.Round(),
   782  					Y: g.Bounds.Max.Y.Round(),
   783  				},
   784  			}.Size()
   785  			aff := op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Point{
   786  				X: float32(glyphSize.X) / float32(imgSize.X),
   787  				Y: float32(glyphSize.Y) / float32(imgSize.Y),
   788  			})).Push(ops)
   789  			imgOp.Add(ops)
   790  			paint.PaintOp{}.Add(ops)
   791  			aff.Pop()
   792  			cl.Pop()
   793  			off.Pop()
   794  		}
   795  	}
   796  	return bitmapMacro.Stop()
   797  }
   798  
   799  // langConfig describes the language and writing system of a body of text.
   800  type langConfig struct {
   801  	// Language the text is written in.
   802  	language.Language
   803  	// Writing system used to represent the text.
   804  	language.Script
   805  	// Direction of the text, usually driven by the writing system.
   806  	di.Direction
   807  }
   808  
   809  // toInput converts its parameters into a shaping.Input.
   810  func toInput(face font.Face, ppem fixed.Int26_6, lc langConfig, runes []rune) shaping.Input {
   811  	var input shaping.Input
   812  	input.Direction = lc.Direction
   813  	input.Text = runes
   814  	input.Size = ppem
   815  	input.Face = face
   816  	input.Language = lc.Language
   817  	input.Script = lc.Script
   818  	input.RunStart = 0
   819  	input.RunEnd = len(runes)
   820  	return input
   821  }
   822  
   823  func mapDirection(d system.TextDirection) di.Direction {
   824  	switch d {
   825  	case system.LTR:
   826  		return di.DirectionLTR
   827  	case system.RTL:
   828  		return di.DirectionRTL
   829  	}
   830  	return di.DirectionLTR
   831  }
   832  
   833  func unmapDirection(d di.Direction) system.TextDirection {
   834  	switch d {
   835  	case di.DirectionLTR:
   836  		return system.LTR
   837  	case di.DirectionRTL:
   838  		return system.RTL
   839  	}
   840  	return system.LTR
   841  }
   842  
   843  // toGioGlyphs converts text shaper glyphs into the minimal representation
   844  // that Gio needs.
   845  func toGioGlyphs(in []shaping.Glyph, ppem fixed.Int26_6, faceIdx int) []glyph {
   846  	out := make([]glyph, 0, len(in))
   847  	for _, g := range in {
   848  		// To better understand how to calculate the bounding box, see here:
   849  		// https://freetype.org/freetype2/docs/glyphs/glyph-metrics-3.svg
   850  		var bounds fixed.Rectangle26_6
   851  		bounds.Min.X = g.XBearing
   852  		bounds.Min.Y = -g.YBearing
   853  		bounds.Max = bounds.Min.Add(fixed.Point26_6{X: g.Width, Y: -g.Height})
   854  		out = append(out, glyph{
   855  			id:           newGlyphID(ppem, faceIdx, g.GlyphID),
   856  			clusterIndex: g.ClusterIndex,
   857  			runeCount:    g.RuneCount,
   858  			glyphCount:   g.GlyphCount,
   859  			xAdvance:     g.XAdvance,
   860  			yAdvance:     g.YAdvance,
   861  			xOffset:      g.XOffset,
   862  			yOffset:      g.YOffset,
   863  			bounds:       bounds,
   864  		})
   865  	}
   866  	return out
   867  }
   868  
   869  // toLine converts the output into a Line with the provided dominant text direction.
   870  func toLine(faceToIndex map[font.Font]int, o shaping.Line, dir system.TextDirection) line {
   871  	if len(o) < 1 {
   872  		return line{}
   873  	}
   874  	line := line{
   875  		runs:      make([]runLayout, len(o)),
   876  		direction: dir,
   877  	}
   878  	maxSize := fixed.Int26_6(0)
   879  	for i := range o {
   880  		run := o[i]
   881  		if run.Size > maxSize {
   882  			maxSize = run.Size
   883  		}
   884  		var font font.Font
   885  		if run.Face != nil {
   886  			font = run.Face.Font
   887  		}
   888  		line.runs[i] = runLayout{
   889  			Glyphs: toGioGlyphs(run.Glyphs, run.Size, faceToIndex[font]),
   890  			Runes: Range{
   891  				Count:  run.Runes.Count,
   892  				Offset: line.runeCount,
   893  			},
   894  			Direction: unmapDirection(run.Direction),
   895  			face:      run.Face,
   896  			Advance:   run.Advance,
   897  			PPEM:      run.Size,
   898  		}
   899  		line.runeCount += run.Runes.Count
   900  		line.width += run.Advance
   901  		if line.ascent < run.LineBounds.Ascent {
   902  			line.ascent = run.LineBounds.Ascent
   903  		}
   904  		if line.descent < -run.LineBounds.Descent+run.LineBounds.Gap {
   905  			line.descent = -run.LineBounds.Descent + run.LineBounds.Gap
   906  		}
   907  	}
   908  	line.lineHeight = maxSize
   909  	computeVisualOrder(&line)
   910  	return line
   911  }
   912  
   913  // computeVisualOrder will populate the Line's VisualOrder field and the
   914  // VisualPosition field of each element in Runs.
   915  func computeVisualOrder(l *line) {
   916  	l.visualOrder = make([]int, len(l.runs))
   917  	const none = -1
   918  	bidiRangeStart := none
   919  
   920  	// visPos returns the visual position for an individual logically-indexed
   921  	// run in this line, taking only the line's overall text direction into
   922  	// account.
   923  	visPos := func(logicalIndex int) int {
   924  		if l.direction.Progression() == system.TowardOrigin {
   925  			return len(l.runs) - 1 - logicalIndex
   926  		}
   927  		return logicalIndex
   928  	}
   929  
   930  	// resolveBidi populated the line's VisualOrder fields for the elements in the
   931  	// half-open range [bidiRangeStart:bidiRangeEnd) indicating that those elements
   932  	// should be displayed in reverse-visual order.
   933  	resolveBidi := func(bidiRangeStart, bidiRangeEnd int) {
   934  		firstVisual := bidiRangeEnd - 1
   935  		// Just found the end of a bidi range.
   936  		for startIdx := bidiRangeStart; startIdx < bidiRangeEnd; startIdx++ {
   937  			pos := visPos(firstVisual)
   938  			l.runs[startIdx].VisualPosition = pos
   939  			l.visualOrder[pos] = startIdx
   940  			firstVisual--
   941  		}
   942  		bidiRangeStart = none
   943  	}
   944  	for runIdx, run := range l.runs {
   945  		if run.Direction.Progression() != l.direction.Progression() {
   946  			if bidiRangeStart == none {
   947  				bidiRangeStart = runIdx
   948  			}
   949  			continue
   950  		} else if bidiRangeStart != none {
   951  			// Just found the end of a bidi range.
   952  			resolveBidi(bidiRangeStart, runIdx)
   953  			bidiRangeStart = none
   954  		}
   955  		pos := visPos(runIdx)
   956  		l.runs[runIdx].VisualPosition = pos
   957  		l.visualOrder[pos] = runIdx
   958  	}
   959  	if bidiRangeStart != none {
   960  		// We ended iteration within a bidi segment, resolve it.
   961  		resolveBidi(bidiRangeStart, len(l.runs))
   962  	}
   963  	// Iterate and resolve the X of each run.
   964  	x := fixed.Int26_6(0)
   965  	for _, runIdx := range l.visualOrder {
   966  		l.runs[runIdx].X = x
   967  		x += l.runs[runIdx].Advance
   968  	}
   969  }