forked from joejulian/gio
text: fix EOF detection at newline boundaries
This commit tests and fixes some edge cases that threw off rune accounting when a newline character was the final rune in the input *and* the text was being truncated. I imagine they were never previously reported because it's rare to try to truncate such text. Thanks to Dominik Honnef for the report and reproducer. Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
+4
-1
@@ -316,7 +316,9 @@ func (l *Shaper) layoutText(params Parameters, txt io.Reader, str string) {
|
|||||||
if !done {
|
if !done {
|
||||||
_, re := l.reader.ReadByte()
|
_, re := l.reader.ReadByte()
|
||||||
done = re != nil
|
done = re != nil
|
||||||
_ = l.reader.UnreadByte()
|
if !done {
|
||||||
|
_ = l.reader.UnreadByte()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
idx := strings.IndexByte(str, '\n')
|
idx := strings.IndexByte(str, '\n')
|
||||||
@@ -325,6 +327,7 @@ func (l *Shaper) layoutText(params Parameters, txt io.Reader, str string) {
|
|||||||
endByte = len(str)
|
endByte = len(str)
|
||||||
} else {
|
} else {
|
||||||
endByte = idx + 1
|
endByte = idx + 1
|
||||||
|
done = endByte == len(str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(str[:endByte]) > 0 || (len(l.paragraph) > 0 || len(l.txt.lines) == 0) {
|
if len(str[:endByte]) > 0 || (len(l.paragraph) > 0 || len(l.txt.lines) == 0) {
|
||||||
|
|||||||
+22
-2
@@ -6,6 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
nsareg "eliasnaur.com/font/noto/sans/arabic/regular"
|
nsareg "eliasnaur.com/font/noto/sans/arabic/regular"
|
||||||
|
"gioui.org/font"
|
||||||
"gioui.org/font/gofont"
|
"gioui.org/font/gofont"
|
||||||
"gioui.org/font/opentype"
|
"gioui.org/font/opentype"
|
||||||
"gioui.org/io/system"
|
"gioui.org/io/system"
|
||||||
@@ -161,6 +162,7 @@ func TestShapingNewlineHandling(t *testing.T) {
|
|||||||
{textInput: "a\n", expectedLines: 1, expectedGlyphs: 3},
|
{textInput: "a\n", expectedLines: 1, expectedGlyphs: 3},
|
||||||
{textInput: "a\nb", expectedLines: 2, expectedGlyphs: 3},
|
{textInput: "a\nb", expectedLines: 2, expectedGlyphs: 3},
|
||||||
{textInput: "", expectedLines: 1, expectedGlyphs: 1},
|
{textInput: "", expectedLines: 1, expectedGlyphs: 1},
|
||||||
|
{textInput: "\n", expectedLines: 1, expectedGlyphs: 2},
|
||||||
} {
|
} {
|
||||||
t.Run(fmt.Sprintf("%q", tc.textInput), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%q", tc.textInput), func(t *testing.T) {
|
||||||
ltrFace, _ := opentype.Parse(goregular.TTF)
|
ltrFace, _ := opentype.Parse(goregular.TTF)
|
||||||
@@ -168,8 +170,13 @@ func TestShapingNewlineHandling(t *testing.T) {
|
|||||||
cache := NewShaper(NoSystemFonts(), WithCollection(collection))
|
cache := NewShaper(NoSystemFonts(), WithCollection(collection))
|
||||||
checkGlyphs := func() {
|
checkGlyphs := func() {
|
||||||
glyphs := []Glyph{}
|
glyphs := []Glyph{}
|
||||||
|
runes := 0
|
||||||
for g, ok := cache.NextGlyph(); ok; g, ok = cache.NextGlyph() {
|
for g, ok := cache.NextGlyph(); ok; g, ok = cache.NextGlyph() {
|
||||||
glyphs = append(glyphs, g)
|
glyphs = append(glyphs, g)
|
||||||
|
runes += g.Runes
|
||||||
|
}
|
||||||
|
if expected := len([]rune(tc.textInput)); expected != runes {
|
||||||
|
t.Errorf("expected %d runes, got %d", expected, runes)
|
||||||
}
|
}
|
||||||
if len(glyphs) != tc.expectedGlyphs {
|
if len(glyphs) != tc.expectedGlyphs {
|
||||||
t.Errorf("expected %d glyphs, got %d", tc.expectedGlyphs, len(glyphs))
|
t.Errorf("expected %d glyphs, got %d", tc.expectedGlyphs, len(glyphs))
|
||||||
@@ -191,8 +198,8 @@ func TestShapingNewlineHandling(t *testing.T) {
|
|||||||
}
|
}
|
||||||
breakX, breakY := breakGlyph.X, breakGlyph.Y
|
breakX, breakY := breakGlyph.X, breakGlyph.Y
|
||||||
startX, startY := startGlyph.X, startGlyph.Y
|
startX, startY := startGlyph.X, startGlyph.Y
|
||||||
if breakX == startX {
|
if breakX == startX && idx != 0 {
|
||||||
t.Errorf("expected paragraph start glyph to have cursor x")
|
t.Errorf("expected paragraph start glyph to have cursor x, got %v", startX)
|
||||||
}
|
}
|
||||||
if breakY == startY {
|
if breakY == startY {
|
||||||
t.Errorf("expected paragraph start glyph to have cursor y")
|
t.Errorf("expected paragraph start glyph to have cursor y")
|
||||||
@@ -464,6 +471,19 @@ func TestShapeStringRuneAccounting(t *testing.T) {
|
|||||||
MaxWidth: 100,
|
MaxWidth: 100,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "newline regression",
|
||||||
|
input: "\n",
|
||||||
|
params: Parameters{
|
||||||
|
Font: font.Font{Typeface: "Go", Style: font.Regular, Weight: font.Normal},
|
||||||
|
Alignment: Start,
|
||||||
|
PxPerEm: 768,
|
||||||
|
MaxLines: 1,
|
||||||
|
Truncator: "\u200b",
|
||||||
|
WrapPolicy: WrapHeuristically,
|
||||||
|
MaxWidth: 999929,
|
||||||
|
},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
for _, setup := range []setup{
|
for _, setup := range []setup{
|
||||||
|
|||||||
Reference in New Issue
Block a user