mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-05 17:35:36 +00:00
widget: test positionGreaterOrEqual
This commit adds an exhaustive test case for the positionGreaterOrEqual helper function that our text widgets use to compare locations within shaped text. Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
+2
-1
@@ -1000,7 +1000,8 @@ func (e *Editor) indexPosition(pos combinedPos) combinedPos {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// positionGreaterOrEqual reports whether p1 >= p2 according to the non-zero fields
|
// positionGreaterOrEqual reports whether p1 >= p2 according to the non-zero fields
|
||||||
// of p2. All fields of p1 must be a consistent and valid.
|
// of p2. All fields of p1 must be a consistent and valid. The clusterIndex field
|
||||||
|
// is never considered, as it is a line-local property.
|
||||||
func positionGreaterOrEqual(lines []text.Line, p1, p2 combinedPos) bool {
|
func positionGreaterOrEqual(lines []text.Line, p1, p2 combinedPos) bool {
|
||||||
l := lines[p1.lineCol.Y]
|
l := lines[p1.lineCol.Y]
|
||||||
endCol := l.Layout.Runes.Count - 1
|
endCol := l.Layout.Runes.Count - 1
|
||||||
|
|||||||
+91
-6
@@ -12,7 +12,10 @@ import (
|
|||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeTestText(fontSize, lineWidth int) ([]text.Line, []text.Line) {
|
// makeTestText returns an ltr and rtl sample 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 makeTestText(fontSize, lineWidth, runeLimit int) ([]text.Line, []text.Line) {
|
||||||
ltrFace, _ := opentype.Parse(goregular.TTF)
|
ltrFace, _ := opentype.Parse(goregular.TTF)
|
||||||
rtlFace, _ := opentype.Parse(nsareg.TTF)
|
rtlFace, _ := opentype.Parse(nsareg.TTF)
|
||||||
|
|
||||||
@@ -26,15 +29,27 @@ func makeTestText(fontSize, lineWidth int) ([]text.Line, []text.Line) {
|
|||||||
Face: rtlFace,
|
Face: rtlFace,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
ltrText := shaper.LayoutString(text.Font{Typeface: "LTR"}, fixed.I(fontSize), lineWidth, english, "The quick brown fox\njumps over the lazy dog.")
|
ltrSource := "The quick brown fox\njumps over the lazy dog."
|
||||||
rtlText := shaper.LayoutString(text.Font{Typeface: "RTL"}, fixed.I(fontSize), lineWidth, arabic, "الحب سماء لا\nتمط غير الأحلام")
|
rtlSource := "الحب سماء لا\nتمط غير الأحلام"
|
||||||
|
if runeLimit != 0 {
|
||||||
|
ltrRunes := []rune(ltrSource)
|
||||||
|
rtlRunes := []rune(rtlSource)
|
||||||
|
if runeLimit < len(ltrRunes) {
|
||||||
|
ltrSource = string(ltrRunes[:runeLimit])
|
||||||
|
}
|
||||||
|
if runeLimit < len(rtlRunes) {
|
||||||
|
rtlSource = string(rtlRunes[:runeLimit])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ltrText := shaper.LayoutString(text.Font{Typeface: "LTR"}, fixed.I(fontSize), lineWidth, english, ltrSource)
|
||||||
|
rtlText := shaper.LayoutString(text.Font{Typeface: "RTL"}, fixed.I(fontSize), lineWidth, arabic, rtlSource)
|
||||||
return ltrText, rtlText
|
return ltrText, rtlText
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirstPos(t *testing.T) {
|
func TestFirstPos(t *testing.T) {
|
||||||
fontSize := 16
|
fontSize := 16
|
||||||
lineWidth := fontSize * 10
|
lineWidth := fontSize * 10
|
||||||
ltrText, rtlText := makeTestText(fontSize, lineWidth)
|
ltrText, rtlText := makeTestText(fontSize, lineWidth, 0)
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
name string
|
name string
|
||||||
line text.Line
|
line text.Line
|
||||||
@@ -151,7 +166,7 @@ func TestFirstPos(t *testing.T) {
|
|||||||
func TestIncrementPosition(t *testing.T) {
|
func TestIncrementPosition(t *testing.T) {
|
||||||
fontSize := 16
|
fontSize := 16
|
||||||
lineWidth := fontSize * 3
|
lineWidth := fontSize * 3
|
||||||
ltrText, rtlText := makeTestText(fontSize, lineWidth)
|
ltrText, rtlText := makeTestText(fontSize, lineWidth, 0)
|
||||||
type trial struct {
|
type trial struct {
|
||||||
input, output combinedPos
|
input, output combinedPos
|
||||||
}
|
}
|
||||||
@@ -248,7 +263,7 @@ func TestIncrementPosition(t *testing.T) {
|
|||||||
func TestClusterIndexFor(t *testing.T) {
|
func TestClusterIndexFor(t *testing.T) {
|
||||||
fontSize := 16
|
fontSize := 16
|
||||||
lineWidth := fontSize * 3
|
lineWidth := fontSize * 3
|
||||||
ltrText, rtlText := makeTestText(fontSize, lineWidth)
|
ltrText, rtlText := makeTestText(fontSize, lineWidth, 0)
|
||||||
type input struct {
|
type input struct {
|
||||||
runeIdx int
|
runeIdx int
|
||||||
clusterStartIdx int
|
clusterStartIdx int
|
||||||
@@ -355,3 +370,73 @@ func TestClusterIndexFor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPositionGreaterOrEqual(t *testing.T) {
|
||||||
|
fontSize := 16
|
||||||
|
lineWidth := fontSize * 10
|
||||||
|
// Be careful tuning the runeLimit here. This test case's complexity
|
||||||
|
// is O(N^2) where N=runeLimit. It's easy to make this test take a stupid
|
||||||
|
// amount of time accidentally.
|
||||||
|
ltrText, rtlText := makeTestText(fontSize, lineWidth, 15)
|
||||||
|
type testcase struct {
|
||||||
|
name string
|
||||||
|
lines []text.Line
|
||||||
|
align text.Alignment
|
||||||
|
width int
|
||||||
|
}
|
||||||
|
for _, tc := range []testcase{
|
||||||
|
{
|
||||||
|
name: "ltr",
|
||||||
|
lines: ltrText,
|
||||||
|
align: text.Start,
|
||||||
|
width: lineWidth,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rtl",
|
||||||
|
lines: rtlText,
|
||||||
|
align: text.Start,
|
||||||
|
width: lineWidth,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
finalLineRunes := tc.lines[len(tc.lines)-1].Layout.Runes
|
||||||
|
// Statically generate all valid positions.
|
||||||
|
positions := make([]combinedPos, finalLineRunes.Offset+finalLineRunes.Count+1)
|
||||||
|
positions[0] = firstPos(tc.lines[0], tc.align, tc.width)
|
||||||
|
for i := 1; i < len(positions); i++ {
|
||||||
|
positions[i], _ = incrementPosition(tc.lines, tc.align, tc.width, positions[i-1])
|
||||||
|
}
|
||||||
|
// For each valid position, check every other valid position returns the correct
|
||||||
|
// result with each permutation of populated fields.
|
||||||
|
for i, p1 := range positions {
|
||||||
|
for k, p2 := range positions {
|
||||||
|
t.Run(tc.name+" "+strconv.Itoa(i)+">="+strconv.Itoa(k), func(t *testing.T) {
|
||||||
|
for kind := 0; kind < 3; kind++ {
|
||||||
|
p2 := p2
|
||||||
|
transform := ""
|
||||||
|
switch kind {
|
||||||
|
case 0: // only runes populated
|
||||||
|
transform = "runes only"
|
||||||
|
p2.lineCol = screenPos{}
|
||||||
|
p2.x = 0
|
||||||
|
p2.y = 0
|
||||||
|
case 1: // only lineCol populated
|
||||||
|
transform = "lineCol only"
|
||||||
|
p2.runes = 0
|
||||||
|
p2.x = 0
|
||||||
|
p2.y = 0
|
||||||
|
case 2: // only x and y populated
|
||||||
|
transform = "x,y only"
|
||||||
|
p2.runes = 0
|
||||||
|
p2.lineCol = screenPos{}
|
||||||
|
}
|
||||||
|
isGreaterOrEqual := i >= k
|
||||||
|
result := positionGreaterOrEqual(tc.lines, p1, p2)
|
||||||
|
if result != isGreaterOrEqual {
|
||||||
|
t.Errorf("unexpected result comparing p[%d] >= p[%d](%s) (%v)\np1: %#+v\np2:%#+v", i, k, transform, result, p1, p2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user