~eliasnaur/gio

1be58a2bc4f773c6f0c43e72e70178961b472ca7 — Chris Waldon 11 months ago b46c0f5
widget: test firstPos

This commit adds a test to lock in the correct behavior of the
firstPos helper method.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2 files changed, 152 insertions(+), 4 deletions(-)

M widget/label.go
A widget/text_test.go
M widget/label.go => widget/label.go +8 -4
@@ 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))

A widget/text_test.go => widget/text_test.go +144 -0
@@ 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)
					}
				})
			}
		}
	}
}