From 1be58a2bc4f773c6f0c43e72e70178961b472ca7 Mon Sep 17 00:00:00 2001 From: Chris Waldon Date: Wed, 12 Oct 2022 16:59:22 -0400 Subject: [PATCH] widget: test firstPos This commit adds a test to lock in the correct behavior of the firstPos helper method. Signed-off-by: Chris Waldon --- widget/label.go | 12 ++-- widget/text_test.go | 144 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 widget/text_test.go diff --git a/widget/label.go b/widget/label.go index 08b6139a..07286cb5 100644 --- a/widget/label.go +++ b/widget/label.go @@ -76,6 +76,14 @@ func subLayout(line text.Line, start, end combinedPos) text.Layout { return line.Layout.Slice(startCluster, endCluster) } +// firstPos returns a combinedPos with *only* the x and y +// fields populated. They will be set to the location of the +// dot at the beginning of the line, with text alignment taken +// into account. For RTL text, this will +// be on the right edge of the available space. +// +// The results can be counterinuitive due to the fact that meaning +// of alignment changes depending on the text direction. func firstPos(line text.Line, alignment text.Alignment, width int) combinedPos { p := combinedPos{ x: align(alignment, line.Layout.Direction, line.Width, width), @@ -88,10 +96,6 @@ func firstPos(line text.Line, alignment text.Alignment, width int) combinedPos { return p } -func (p1 screenPos) Less(p2 screenPos) bool { - return p1.Y < p2.Y || (p1.Y == p2.Y && p1.X < p2.X) -} - func (l Label) Layout(gtx layout.Context, s text.Shaper, font text.Font, size unit.Sp, txt string) layout.Dimensions { cs := gtx.Constraints textSize := fixed.I(gtx.Sp(size)) diff --git a/widget/text_test.go b/widget/text_test.go new file mode 100644 index 00000000..b2f15618 --- /dev/null +++ b/widget/text_test.go @@ -0,0 +1,144 @@ +package widget + +import ( + "strconv" + "testing" + + nsareg "eliasnaur.com/font/noto/sans/arabic/regular" + "gioui.org/font/opentype" + "gioui.org/text" + "golang.org/x/image/font/gofont/goregular" + "golang.org/x/image/math/fixed" +) + +func TestFirstPos(t *testing.T) { + ltrFace, _ := opentype.Parse(goregular.TTF) + rtlFace, _ := opentype.Parse(nsareg.TTF) + + shaper := text.NewCache([]text.FontFace{ + { + Font: text.Font{Typeface: "LTR"}, + Face: ltrFace, + }, + { + Font: text.Font{Typeface: "RTL"}, + Face: rtlFace, + }, + }) + fontSize := 16 + lineWidth := int(fontSize) * 10 + ltrText := shaper.LayoutString(text.Font{Typeface: "LTR"}, fixed.I(fontSize), lineWidth, english, "The quick brown fox\njumps over the lazy dog.") + rtlText := shaper.LayoutString(text.Font{Typeface: "RTL"}, fixed.I(fontSize), lineWidth, arabic, "الحب سماء لا\nتمط غير الأحلام") + + type testcase struct { + name string + line text.Line + xAlignMap map[text.Alignment]map[int]fixed.Int26_6 + expected combinedPos + } + for _, tc := range []testcase{ + { + name: "ltr line 0", + line: ltrText[0], + xAlignMap: map[text.Alignment]map[int]fixed.Int26_6{ + text.Start: { + lineWidth: 0, + lineWidth * 2: 0, + }, + text.Middle: { + lineWidth: fixed.I(8), + lineWidth * 2: fixed.I(88), + }, + text.End: { + lineWidth: fixed.I(16), + lineWidth * 2: fixed.I(176), + }, + }, + expected: combinedPos{ + x: 0, + y: ltrText[0].Ascent.Ceil(), + }, + }, + { + name: "ltr line 1", + line: ltrText[1], + xAlignMap: map[text.Alignment]map[int]fixed.Int26_6{ + text.Start: { + lineWidth: 0, + lineWidth * 2: 0, + }, + text.Middle: { + lineWidth: fixed.I(8), + lineWidth * 2: fixed.I(88), + }, + text.End: { + lineWidth: fixed.I(16), + lineWidth * 2: fixed.I(176), + }, + }, + expected: combinedPos{ + x: 0, + y: ltrText[1].Ascent.Ceil(), + }, + }, + { + name: "rtl line 0", + line: rtlText[0], + xAlignMap: map[text.Alignment]map[int]fixed.Int26_6{ + text.End: { + lineWidth: rtlText[0].Width, + lineWidth * 2: rtlText[0].Width, + }, + text.Middle: { + lineWidth: fixed.Int26_6(7827), + lineWidth * 2: fixed.Int26_6(12947), + }, + text.Start: { + lineWidth: fixed.Int26_6(10195), + lineWidth * 2: fixed.Int26_6(20435), + }, + }, + expected: combinedPos{ + x: 0, + y: rtlText[0].Ascent.Ceil(), + }, + }, + { + name: "rtl line 1", + line: rtlText[1], + xAlignMap: map[text.Alignment]map[int]fixed.Int26_6{ + text.End: { + lineWidth: rtlText[1].Width, + lineWidth * 2: rtlText[1].Width, + }, + text.Middle: { + lineWidth: fixed.Int26_6(8184), + lineWidth * 2: fixed.Int26_6(13304), + }, + text.Start: { + lineWidth: fixed.Int26_6(10232), + lineWidth * 2: fixed.Int26_6(20472), + }, + }, + expected: combinedPos{ + x: 0, + y: rtlText[1].Ascent.Ceil(), + }, + }, + } { + for align, cases := range tc.xAlignMap { + for width, expectedX := range cases { + t.Run(tc.name+" "+align.String()+" "+strconv.Itoa(width), func(t *testing.T) { + actual := firstPos(tc.line, align, width) + tc.expected.x = expectedX + if tc.expected.x != actual.x { + t.Errorf("expected x=%s(%d), got %s(%d)", tc.expected.x, tc.expected.x, actual.x, actual.x) + } + if tc.expected.y != actual.y { + t.Errorf("expected y=%d(%d), got %d(%d)", tc.expected.y, tc.expected.y, actual.y, actual.y) + } + }) + } + } + } +}