Files
gio/widget/label_test.go
T
Chris Waldon 12da71821a widget: update glyph iteration
This commit updates the textIterator and glyphIndex types to consume
new flag information provided on glyphs. These changes allow widget.Label
and widget.Editor to correctly compute text bounding boxes and to
generate valid cursor positions at the end of text.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-12-16 17:27:16 -06:00

169 lines
4.7 KiB
Go

package widget
import (
"image"
"math"
"testing"
"gioui.org/text"
"golang.org/x/image/math/fixed"
)
// TestGlyphIterator ensures that the glyph iterator computes correct bounding
// boxes and baselines for a variety of glyph sequences.
func TestGlyphIterator(t *testing.T) {
fontSize := 16
stdAscent := fixed.I(fontSize)
stdDescent := fixed.I(4)
stdLineHeight := stdAscent + stdDescent
type testcase struct {
name string
str string
maxWidth int
maxLines int
viewport image.Rectangle
expectedDims image.Rectangle
expectedBaseline int
stopAtGlyph int
}
for _, tc := range []testcase{
{
name: "empty string",
str: "",
viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
expectedDims: image.Rectangle{
Max: image.Point{X: 0, Y: stdLineHeight.Round()},
},
expectedBaseline: fontSize,
stopAtGlyph: 0,
},
{
name: "simple",
str: "MMM",
viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
expectedDims: image.Rectangle{
Max: image.Point{X: 40, Y: stdLineHeight.Round()},
},
expectedBaseline: fontSize,
stopAtGlyph: 2,
},
{
name: "simple clipped horizontally",
str: "MMM",
viewport: image.Rectangle{Max: image.Pt(20, math.MaxInt)},
// The dimensions should only include the first two glyphs.
expectedDims: image.Rectangle{
Max: image.Point{X: 27, Y: stdLineHeight.Round()},
},
expectedBaseline: fontSize,
stopAtGlyph: 2,
},
{
name: "simple clipped vertically",
str: "M\nM\nM\nM",
viewport: image.Rectangle{Max: image.Pt(math.MaxInt, 2*stdLineHeight.Floor()-3)},
// The dimensions should only include the first two lines.
expectedDims: image.Rectangle{
Max: image.Point{X: 14, Y: 39},
},
expectedBaseline: fontSize,
stopAtGlyph: 4,
},
{
name: "simple truncated",
str: "mmm",
maxLines: 1,
viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
// This truncation should have no effect because the text is already one line.
expectedDims: image.Rectangle{
Max: image.Point{X: 40, Y: stdLineHeight.Round()},
},
expectedBaseline: fontSize,
stopAtGlyph: 2,
},
{
name: "whitespace",
str: " ",
viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
expectedDims: image.Rectangle{
Max: image.Point{X: 14, Y: stdLineHeight.Round()},
},
expectedBaseline: fontSize,
stopAtGlyph: 2,
},
{
name: "multi-line with hard newline",
str: "你\n好",
viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
expectedDims: image.Rectangle{
Max: image.Point{X: 12, Y: 39},
},
expectedBaseline: fontSize,
stopAtGlyph: 3,
},
{
name: "multi-line with soft newline",
str: "你好", // UAX#14 allows line breaking between these characters.
maxWidth: fontSize,
viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
expectedDims: image.Rectangle{
Max: image.Point{X: 12, Y: 39},
},
expectedBaseline: fontSize,
stopAtGlyph: 2,
},
{
name: "trailing hard newline",
str: "m\n",
viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
// We expect the dimensions to account for two vertical lines because of the
// newline at the end.
expectedDims: image.Rectangle{
Max: image.Point{X: 14, Y: 39},
},
expectedBaseline: fontSize,
stopAtGlyph: 1,
},
{
name: "truncated trailing hard newline",
str: "m\n",
maxLines: 1,
viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
// We expect the dimensions to reflect only a single line despite the newline
// at the end.
expectedDims: image.Rectangle{
Max: image.Point{X: 14, Y: 20},
},
expectedBaseline: fontSize,
stopAtGlyph: 1,
},
} {
t.Run(tc.name, func(t *testing.T) {
maxWidth := 200
if tc.maxWidth != 0 {
maxWidth = tc.maxWidth
}
glyphs := getGlyphs(16, 0, maxWidth, text.Start, tc.str)
it := textIterator{viewport: tc.viewport, maxLines: tc.maxLines}
for i, g := range glyphs {
gOut, ok := it.processGlyph(g, true)
if gOut != g {
t.Errorf("textIterator modified glyphs[%d], original:\n%#+v, modified:\n%#+v", i, g, gOut)
}
if !ok && i != tc.stopAtGlyph {
t.Errorf("expected iterator to stop at glyph %d, stopped at %d", tc.stopAtGlyph, i)
}
if !ok {
break
}
}
if it.bounds != tc.expectedDims {
t.Errorf("expected bounds %#+v, got %#+v", tc.expectedDims, it.bounds)
}
if it.baseline != tc.expectedBaseline {
t.Errorf("expected baseline %d, got %d", tc.expectedBaseline, it.baseline)
}
})
}
}