github.com/utopiagio/gio@v0.0.8/widget/label_test.go (about)

     1  package widget
     2  
     3  import (
     4  	"image"
     5  	"math"
     6  	"testing"
     7  
     8  	"github.com/utopiagio/gio/text"
     9  	"golang.org/x/image/math/fixed"
    10  )
    11  
    12  // TestGlyphIterator ensures that the glyph iterator computes correct bounding
    13  // boxes and baselines for a variety of glyph sequences.
    14  func TestGlyphIterator(t *testing.T) {
    15  	fontSize := 16
    16  	stdAscent := fixed.I(fontSize)
    17  	stdDescent := fixed.I(4)
    18  	stdLineHeight := stdAscent + stdDescent
    19  	type testcase struct {
    20  		name             string
    21  		str              string
    22  		maxWidth         int
    23  		maxLines         int
    24  		viewport         image.Rectangle
    25  		expectedDims     image.Rectangle
    26  		expectedBaseline int
    27  		stopAtGlyph      int
    28  	}
    29  	for _, tc := range []testcase{
    30  		{
    31  			name:     "empty string",
    32  			str:      "",
    33  			viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
    34  			expectedDims: image.Rectangle{
    35  				Max: image.Point{X: 0, Y: stdLineHeight.Round()},
    36  			},
    37  			expectedBaseline: fontSize,
    38  			stopAtGlyph:      0,
    39  		},
    40  		{
    41  			name:     "simple",
    42  			str:      "MMM",
    43  			viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
    44  			expectedDims: image.Rectangle{
    45  				Max: image.Point{X: 40, Y: stdLineHeight.Round()},
    46  			},
    47  			expectedBaseline: fontSize,
    48  			stopAtGlyph:      2,
    49  		},
    50  		{
    51  			name:     "simple clipped horizontally",
    52  			str:      "MMM",
    53  			viewport: image.Rectangle{Max: image.Pt(20, math.MaxInt)},
    54  			// The dimensions should only include the first two glyphs.
    55  			expectedDims: image.Rectangle{
    56  				Max: image.Point{X: 27, Y: stdLineHeight.Round()},
    57  			},
    58  			expectedBaseline: fontSize,
    59  			stopAtGlyph:      2,
    60  		},
    61  		{
    62  			name:     "simple clipped vertically",
    63  			str:      "M\nM\nM\nM",
    64  			viewport: image.Rectangle{Max: image.Pt(math.MaxInt, 2*stdLineHeight.Floor()-3)},
    65  			// The dimensions should only include the first two lines.
    66  			expectedDims: image.Rectangle{
    67  				Max: image.Point{X: 14, Y: 39},
    68  			},
    69  			expectedBaseline: fontSize,
    70  			stopAtGlyph:      4,
    71  		},
    72  		{
    73  			name:     "simple truncated",
    74  			str:      "mmm",
    75  			maxLines: 1,
    76  			viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
    77  			// This truncation should have no effect because the text is already one line.
    78  			expectedDims: image.Rectangle{
    79  				Max: image.Point{X: 40, Y: stdLineHeight.Round()},
    80  			},
    81  			expectedBaseline: fontSize,
    82  			stopAtGlyph:      2,
    83  		},
    84  		{
    85  			name:     "whitespace",
    86  			str:      "   ",
    87  			viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
    88  			expectedDims: image.Rectangle{
    89  				Max: image.Point{X: 14, Y: stdLineHeight.Round()},
    90  			},
    91  			expectedBaseline: fontSize,
    92  			stopAtGlyph:      2,
    93  		},
    94  		{
    95  			name:     "multi-line with hard newline",
    96  			str:      "你\n好",
    97  			viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
    98  			expectedDims: image.Rectangle{
    99  				Max: image.Point{X: 12, Y: 39},
   100  			},
   101  			expectedBaseline: fontSize,
   102  			stopAtGlyph:      3,
   103  		},
   104  		{
   105  			name:     "multi-line with soft newline",
   106  			str:      "你好", // UAX#14 allows line breaking between these characters.
   107  			maxWidth: fontSize,
   108  			viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
   109  			expectedDims: image.Rectangle{
   110  				Max: image.Point{X: 12, Y: 39},
   111  			},
   112  			expectedBaseline: fontSize,
   113  			stopAtGlyph:      2,
   114  		},
   115  		{
   116  			name:     "trailing hard newline",
   117  			str:      "m\n",
   118  			viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
   119  			// We expect the dimensions to account for two vertical lines because of the
   120  			// newline at the end.
   121  			expectedDims: image.Rectangle{
   122  				Max: image.Point{X: 14, Y: 39},
   123  			},
   124  			expectedBaseline: fontSize,
   125  			stopAtGlyph:      1,
   126  		},
   127  		{
   128  			name:     "truncated trailing hard newline",
   129  			str:      "m\n",
   130  			maxLines: 1,
   131  			viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
   132  			// We expect the dimensions to reflect only a single line despite the newline
   133  			// at the end.
   134  			expectedDims: image.Rectangle{
   135  				Max: image.Point{X: 14, Y: 20},
   136  			},
   137  			expectedBaseline: fontSize,
   138  			stopAtGlyph:      1,
   139  		},
   140  	} {
   141  		t.Run(tc.name, func(t *testing.T) {
   142  			maxWidth := 200
   143  			if tc.maxWidth != 0 {
   144  				maxWidth = tc.maxWidth
   145  			}
   146  			glyphs := getGlyphs(16, 0, maxWidth, text.Start, tc.str)
   147  			it := textIterator{viewport: tc.viewport, maxLines: tc.maxLines}
   148  			for i, g := range glyphs {
   149  				ok := it.processGlyph(g, true)
   150  				if !ok && i != tc.stopAtGlyph {
   151  					t.Errorf("expected iterator to stop at glyph %d, stopped at %d", tc.stopAtGlyph, i)
   152  				}
   153  				if !ok {
   154  					break
   155  				}
   156  			}
   157  			if it.bounds != tc.expectedDims {
   158  				t.Errorf("expected bounds %#+v, got %#+v", tc.expectedDims, it.bounds)
   159  			}
   160  			if it.baseline != tc.expectedBaseline {
   161  				t.Errorf("expected baseline %d, got %d", tc.expectedBaseline, it.baseline)
   162  			}
   163  		})
   164  	}
   165  }
   166  
   167  // TestGlyphIteratorPadding ensures that the glyph iterator computes correct padding
   168  // around glyphs with unusual bounding boxes.
   169  func TestGlyphIteratorPadding(t *testing.T) {
   170  	type testcase struct {
   171  		name             string
   172  		glyph            text.Glyph
   173  		viewport         image.Rectangle
   174  		expectedDims     image.Rectangle
   175  		expectedPadding  image.Rectangle
   176  		expectedBaseline int
   177  	}
   178  	for _, tc := range []testcase{
   179  		{
   180  			name: "simple",
   181  			glyph: text.Glyph{
   182  				X:       0,
   183  				Y:       50,
   184  				Advance: fixed.I(50),
   185  				Ascent:  fixed.I(50),
   186  				Descent: fixed.I(50),
   187  				Bounds: fixed.Rectangle26_6{
   188  					Min: fixed.Point26_6{
   189  						X: fixed.I(-5),
   190  						Y: fixed.I(-56),
   191  					},
   192  					Max: fixed.Point26_6{
   193  						X: fixed.I(57),
   194  						Y: fixed.I(58),
   195  					},
   196  				},
   197  			},
   198  			viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)},
   199  			expectedDims: image.Rectangle{
   200  				Max: image.Point{X: 50, Y: 100},
   201  			},
   202  			expectedBaseline: 50,
   203  			expectedPadding: image.Rectangle{
   204  				Min: image.Point{
   205  					X: -5,
   206  					Y: -6,
   207  				},
   208  				Max: image.Point{
   209  					X: 7,
   210  					Y: 8,
   211  				},
   212  			},
   213  		},
   214  	} {
   215  		t.Run(tc.name, func(t *testing.T) {
   216  			it := textIterator{viewport: tc.viewport}
   217  			it.processGlyph(tc.glyph, true)
   218  			if it.bounds != tc.expectedDims {
   219  				t.Errorf("expected bounds %#+v, got %#+v", tc.expectedDims, it.bounds)
   220  			}
   221  			if it.baseline != tc.expectedBaseline {
   222  				t.Errorf("expected baseline %d, got %d", tc.expectedBaseline, it.baseline)
   223  			}
   224  			if it.padding != tc.expectedPadding {
   225  				t.Errorf("expected padding %d, got %d", tc.expectedPadding, it.padding)
   226  			}
   227  		})
   228  	}
   229  }