~eliasnaur/gio

36e768e716d11193b34f24c1c5063f76afcd084f — Chris Waldon 6 months ago 25171df
widget: make glyphIndex reusable

This commit allows the glyph index type to be reset and reused, preventing the
reallocation of numerous buffers when indexing glyphs.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
3 files changed, 39 insertions(+), 13 deletions(-)

M widget/index.go
M widget/index_test.go
M widget/text.go
M widget/index.go => widget/index.go +15 -1
@@ 20,6 20,7 @@ type lineInfo struct {
}

type glyphIndex struct {
	// glyphs holds the glyphs processed.
	glyphs []text.Glyph
	// positions contain all possible caret positions, sorted by rune index.
	positions []combinedPos


@@ 46,6 47,20 @@ type glyphIndex struct {
	skipPrior bool
}

// reset prepares the index for reuse.
func (g *glyphIndex) reset() {
	g.glyphs = g.glyphs[:0]
	g.positions = g.positions[:0]
	g.lines = g.lines[:0]
	g.currentLineMin = 0
	g.currentLineMax = 0
	g.currentLineGlyphs = 0
	g.pos = combinedPos{}
	g.prog = 0
	g.clusterAdvance = 0
	g.skipPrior = false
}

// screenPos represents a character position in text line and column numbers,
// not pixels.
type screenPos struct {


@@ 90,7 105,6 @@ func (g *glyphIndex) incrementPosition(pos combinedPos) (next combinedPos, eof b
		return g.positions[index+1], false
	}
	return candidate, true

}

// Glyph indexes the provided glyph, generating text cursor positions for it.

M widget/index_test.go => widget/index_test.go +23 -11
@@ 13,7 13,7 @@ import (
// makePosTestText returns two bidi samples of shaped text at the given
// font size and wrapped to the given line width. The runeLimit, if nonzero,
// truncates the sample text to ensure shorter output for expensive tests.
func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (bidiLTR, bidiRTL []text.Glyph) {
func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (source string, bidiLTR, bidiRTL []text.Glyph) {
	ltrFace, _ := opentype.Parse(goregular.TTF)
	rtlFace, _ := opentype.Parse(nsareg.TTF)



@@ 44,12 44,12 @@ func makePosTestText(fontSize, lineWidth int, alignOpposite bool) (bidiLTR, bidi
	for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
		bidiRTL = append(bidiRTL, g)
	}
	return bidiLTR, bidiRTL
	return bidiSource, bidiLTR, bidiRTL
}

// makeAccountingTestText shapes text designed to stress rune accounting
// logic within the index.
func makeAccountingTestText(fontSize, lineWidth int) (txt []text.Glyph) {
func makeAccountingTestText(str string, fontSize, lineWidth int) (txt []text.Glyph) {
	ltrFace, _ := opentype.Parse(goregular.TTF)
	rtlFace, _ := opentype.Parse(nsareg.TTF)



@@ 62,11 62,8 @@ func makeAccountingTestText(fontSize, lineWidth int) (txt []text.Glyph) {
			Face: rtlFace,
		},
	})
	// bidiSource is crafted to contain multiple consecutive RTL runs (by
	// changing scripts within the RTL).
	bidiSource := "The\nquick سماء של\nום لا fox\nتمط של\nום."
	params := text.Parameters{PxPerEm: fixed.I(fontSize)}
	shaper.LayoutString(params, 0, lineWidth, english, bidiSource)
	shaper.LayoutString(params, 0, lineWidth, english, str)
	for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
		txt = append(txt, g)
	}


@@ 167,6 164,7 @@ func TestIndexPositionWhitespace(t *testing.T) {
		t.Run(tc.name, func(t *testing.T) {
			glyphs := getGlyphs(16, 0, 200, tc.align, tc.str)
			var gi glyphIndex
			gi.reset()
			for _, g := range glyphs {
				gi.Glyph(g)
			}


@@ 194,7 192,7 @@ func TestIndexPositionWhitespace(t *testing.T) {
func TestIndexPositionBidi(t *testing.T) {
	fontSize := 16
	lineWidth := fontSize * 10
	bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
	_, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
	type testcase struct {
		name       string
		glyphs     []text.Glyph


@@ 223,6 221,7 @@ func TestIndexPositionBidi(t *testing.T) {
	} {
		t.Run(tc.name, func(t *testing.T) {
			var gi glyphIndex
			gi.reset()
			for _, g := range tc.glyphs {
				gi.Glyph(g)
			}


@@ 267,19 266,22 @@ func TestIndexPositionBidi(t *testing.T) {
		})
	}
}

func TestIndexPositionLines(t *testing.T) {
	fontSize := 16
	lineWidth := fontSize * 10
	bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
	bidiLTRTextOpp, bidiRTLTextOpp := makePosTestText(fontSize, lineWidth, true)
	source1, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
	source2, bidiLTRTextOpp, bidiRTLTextOpp := makePosTestText(fontSize, lineWidth, true)
	type testcase struct {
		name          string
		source        string
		glyphs        []text.Glyph
		expectedLines []lineInfo
	}
	for _, tc := range []testcase{
		{
			name:   "bidi ltr",
			source: source1,
			glyphs: bidiLTRText,
			expectedLines: []lineInfo{
				{


@@ 318,6 320,7 @@ func TestIndexPositionLines(t *testing.T) {
		},
		{
			name:   "bidi rtl",
			source: source1,
			glyphs: bidiRTLText,
			expectedLines: []lineInfo{
				{


@@ 348,6 351,7 @@ func TestIndexPositionLines(t *testing.T) {
		},
		{
			name:   "bidi ltr opposite alignment",
			source: source2,
			glyphs: bidiLTRTextOpp,
			expectedLines: []lineInfo{
				{


@@ 386,6 390,7 @@ func TestIndexPositionLines(t *testing.T) {
		},
		{
			name:   "bidi rtl opposite alignment",
			source: source2,
			glyphs: bidiRTLTextOpp,
			expectedLines: []lineInfo{
				{


@@ 417,6 422,7 @@ func TestIndexPositionLines(t *testing.T) {
	} {
		t.Run(tc.name, func(t *testing.T) {
			var gi glyphIndex
			gi.reset()
			for _, g := range tc.glyphs {
				gi.Glyph(g)
			}


@@ 439,15 445,20 @@ func TestIndexPositionLines(t *testing.T) {
func TestIndexPositionRunes(t *testing.T) {
	fontSize := 16
	lineWidth := fontSize * 10
	testText := makeAccountingTestText(fontSize, lineWidth)
	// source is crafted to contain multiple consecutive RTL runs (by
	// changing scripts within the RTL).
	source := "The\nquick سماء של\nום لا fox\nتمط של\nום."
	testText := makeAccountingTestText(source, fontSize, lineWidth)
	type testcase struct {
		name     string
		source   string
		glyphs   []text.Glyph
		expected []combinedPos
	}
	for _, tc := range []testcase{
		{
			name:   "many newlines",
			source: source,
			glyphs: testText,
			expected: []combinedPos{
				{runes: 0, lineCol: screenPos{line: 0, col: 0}, runIndex: 0, towardOrigin: false},


@@ 496,6 507,7 @@ func TestIndexPositionRunes(t *testing.T) {
	} {
		t.Run(tc.name, func(t *testing.T) {
			var gi glyphIndex
			gi.reset()
			for _, g := range tc.glyphs {
				gi.Glyph(g)
			}

M widget/text.go => widget/text.go +1 -1
@@ 413,7 413,7 @@ func (e *textView) layoutText(lt *text.Shaper) {
		e.maskReader.Reset(e, e.Mask)
		r = &e.maskReader
	}
	e.index = glyphIndex{}
	e.index.reset()
	it := textIterator{viewport: image.Rectangle{Max: image.Point{X: math.MaxInt, Y: math.MaxInt}}}
	if lt != nil {
		lt.Layout(text.Parameters{