mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 18:05:35 +00:00
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>
This commit is contained in:
+15
-1
@@ -20,6 +20,7 @@ type lineInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type glyphIndex struct {
|
type glyphIndex struct {
|
||||||
|
// glyphs holds the glyphs processed.
|
||||||
glyphs []text.Glyph
|
glyphs []text.Glyph
|
||||||
// positions contain all possible caret positions, sorted by rune index.
|
// positions contain all possible caret positions, sorted by rune index.
|
||||||
positions []combinedPos
|
positions []combinedPos
|
||||||
@@ -46,6 +47,20 @@ type glyphIndex struct {
|
|||||||
skipPrior bool
|
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,
|
// screenPos represents a character position in text line and column numbers,
|
||||||
// not pixels.
|
// not pixels.
|
||||||
type screenPos struct {
|
type screenPos struct {
|
||||||
@@ -90,7 +105,6 @@ func (g *glyphIndex) incrementPosition(pos combinedPos) (next combinedPos, eof b
|
|||||||
return g.positions[index+1], false
|
return g.positions[index+1], false
|
||||||
}
|
}
|
||||||
return candidate, true
|
return candidate, true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Glyph indexes the provided glyph, generating text cursor positions for it.
|
// Glyph indexes the provided glyph, generating text cursor positions for it.
|
||||||
|
|||||||
+23
-11
@@ -13,7 +13,7 @@ import (
|
|||||||
// makePosTestText returns two bidi samples of shaped text at the given
|
// makePosTestText returns two bidi samples of shaped text at the given
|
||||||
// font size and wrapped to the given line width. The runeLimit, if nonzero,
|
// font size and wrapped to the given line width. The runeLimit, if nonzero,
|
||||||
// truncates the sample text to ensure shorter output for expensive tests.
|
// 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)
|
ltrFace, _ := opentype.Parse(goregular.TTF)
|
||||||
rtlFace, _ := opentype.Parse(nsareg.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() {
|
for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
|
||||||
bidiRTL = append(bidiRTL, g)
|
bidiRTL = append(bidiRTL, g)
|
||||||
}
|
}
|
||||||
return bidiLTR, bidiRTL
|
return bidiSource, bidiLTR, bidiRTL
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeAccountingTestText shapes text designed to stress rune accounting
|
// makeAccountingTestText shapes text designed to stress rune accounting
|
||||||
// logic within the index.
|
// 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)
|
ltrFace, _ := opentype.Parse(goregular.TTF)
|
||||||
rtlFace, _ := opentype.Parse(nsareg.TTF)
|
rtlFace, _ := opentype.Parse(nsareg.TTF)
|
||||||
|
|
||||||
@@ -62,11 +62,8 @@ func makeAccountingTestText(fontSize, lineWidth int) (txt []text.Glyph) {
|
|||||||
Face: rtlFace,
|
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)}
|
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() {
|
for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
|
||||||
txt = append(txt, g)
|
txt = append(txt, g)
|
||||||
}
|
}
|
||||||
@@ -167,6 +164,7 @@ func TestIndexPositionWhitespace(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
glyphs := getGlyphs(16, 0, 200, tc.align, tc.str)
|
glyphs := getGlyphs(16, 0, 200, tc.align, tc.str)
|
||||||
var gi glyphIndex
|
var gi glyphIndex
|
||||||
|
gi.reset()
|
||||||
for _, g := range glyphs {
|
for _, g := range glyphs {
|
||||||
gi.Glyph(g)
|
gi.Glyph(g)
|
||||||
}
|
}
|
||||||
@@ -194,7 +192,7 @@ func TestIndexPositionWhitespace(t *testing.T) {
|
|||||||
func TestIndexPositionBidi(t *testing.T) {
|
func TestIndexPositionBidi(t *testing.T) {
|
||||||
fontSize := 16
|
fontSize := 16
|
||||||
lineWidth := fontSize * 10
|
lineWidth := fontSize * 10
|
||||||
bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
|
_, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
name string
|
name string
|
||||||
glyphs []text.Glyph
|
glyphs []text.Glyph
|
||||||
@@ -223,6 +221,7 @@ func TestIndexPositionBidi(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var gi glyphIndex
|
var gi glyphIndex
|
||||||
|
gi.reset()
|
||||||
for _, g := range tc.glyphs {
|
for _, g := range tc.glyphs {
|
||||||
gi.Glyph(g)
|
gi.Glyph(g)
|
||||||
}
|
}
|
||||||
@@ -267,19 +266,22 @@ func TestIndexPositionBidi(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIndexPositionLines(t *testing.T) {
|
func TestIndexPositionLines(t *testing.T) {
|
||||||
fontSize := 16
|
fontSize := 16
|
||||||
lineWidth := fontSize * 10
|
lineWidth := fontSize * 10
|
||||||
bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
|
source1, bidiLTRText, bidiRTLText := makePosTestText(fontSize, lineWidth, false)
|
||||||
bidiLTRTextOpp, bidiRTLTextOpp := makePosTestText(fontSize, lineWidth, true)
|
source2, bidiLTRTextOpp, bidiRTLTextOpp := makePosTestText(fontSize, lineWidth, true)
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
name string
|
name string
|
||||||
|
source string
|
||||||
glyphs []text.Glyph
|
glyphs []text.Glyph
|
||||||
expectedLines []lineInfo
|
expectedLines []lineInfo
|
||||||
}
|
}
|
||||||
for _, tc := range []testcase{
|
for _, tc := range []testcase{
|
||||||
{
|
{
|
||||||
name: "bidi ltr",
|
name: "bidi ltr",
|
||||||
|
source: source1,
|
||||||
glyphs: bidiLTRText,
|
glyphs: bidiLTRText,
|
||||||
expectedLines: []lineInfo{
|
expectedLines: []lineInfo{
|
||||||
{
|
{
|
||||||
@@ -318,6 +320,7 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bidi rtl",
|
name: "bidi rtl",
|
||||||
|
source: source1,
|
||||||
glyphs: bidiRTLText,
|
glyphs: bidiRTLText,
|
||||||
expectedLines: []lineInfo{
|
expectedLines: []lineInfo{
|
||||||
{
|
{
|
||||||
@@ -348,6 +351,7 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bidi ltr opposite alignment",
|
name: "bidi ltr opposite alignment",
|
||||||
|
source: source2,
|
||||||
glyphs: bidiLTRTextOpp,
|
glyphs: bidiLTRTextOpp,
|
||||||
expectedLines: []lineInfo{
|
expectedLines: []lineInfo{
|
||||||
{
|
{
|
||||||
@@ -386,6 +390,7 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bidi rtl opposite alignment",
|
name: "bidi rtl opposite alignment",
|
||||||
|
source: source2,
|
||||||
glyphs: bidiRTLTextOpp,
|
glyphs: bidiRTLTextOpp,
|
||||||
expectedLines: []lineInfo{
|
expectedLines: []lineInfo{
|
||||||
{
|
{
|
||||||
@@ -417,6 +422,7 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var gi glyphIndex
|
var gi glyphIndex
|
||||||
|
gi.reset()
|
||||||
for _, g := range tc.glyphs {
|
for _, g := range tc.glyphs {
|
||||||
gi.Glyph(g)
|
gi.Glyph(g)
|
||||||
}
|
}
|
||||||
@@ -439,15 +445,20 @@ func TestIndexPositionLines(t *testing.T) {
|
|||||||
func TestIndexPositionRunes(t *testing.T) {
|
func TestIndexPositionRunes(t *testing.T) {
|
||||||
fontSize := 16
|
fontSize := 16
|
||||||
lineWidth := fontSize * 10
|
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 {
|
type testcase struct {
|
||||||
name string
|
name string
|
||||||
|
source string
|
||||||
glyphs []text.Glyph
|
glyphs []text.Glyph
|
||||||
expected []combinedPos
|
expected []combinedPos
|
||||||
}
|
}
|
||||||
for _, tc := range []testcase{
|
for _, tc := range []testcase{
|
||||||
{
|
{
|
||||||
name: "many newlines",
|
name: "many newlines",
|
||||||
|
source: source,
|
||||||
glyphs: testText,
|
glyphs: testText,
|
||||||
expected: []combinedPos{
|
expected: []combinedPos{
|
||||||
{runes: 0, lineCol: screenPos{line: 0, col: 0}, runIndex: 0, towardOrigin: false},
|
{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) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var gi glyphIndex
|
var gi glyphIndex
|
||||||
|
gi.reset()
|
||||||
for _, g := range tc.glyphs {
|
for _, g := range tc.glyphs {
|
||||||
gi.Glyph(g)
|
gi.Glyph(g)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -413,7 +413,7 @@ func (e *textView) layoutText(lt *text.Shaper) {
|
|||||||
e.maskReader.Reset(e, e.Mask)
|
e.maskReader.Reset(e, e.Mask)
|
||||||
r = &e.maskReader
|
r = &e.maskReader
|
||||||
}
|
}
|
||||||
e.index = glyphIndex{}
|
e.index.reset()
|
||||||
it := textIterator{viewport: image.Rectangle{Max: image.Point{X: math.MaxInt, Y: math.MaxInt}}}
|
it := textIterator{viewport: image.Rectangle{Max: image.Point{X: math.MaxInt, Y: math.MaxInt}}}
|
||||||
if lt != nil {
|
if lt != nil {
|
||||||
lt.Layout(text.Parameters{
|
lt.Layout(text.Parameters{
|
||||||
|
|||||||
Reference in New Issue
Block a user