From edbf872b44af66523b17aee79b7afa3d5187a08c Mon Sep 17 00:00:00 2001 From: Chris Waldon Date: Mon, 31 Jul 2023 10:41:49 -0400 Subject: [PATCH] widget: fix label vertical glyph padding logic We previously were not handling glyphs that extended vertically beyond the ascent/descent declared by their font. This is done rarely with text fonts, but is apparently common among symbol and emoji fonts. Signed-off-by: Chris Waldon --- widget/label.go | 15 +++++++++++ widget/label_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/widget/label.go b/widget/label.go index 58a01d70..2275e3eb 100644 --- a/widget/label.go +++ b/widget/label.go @@ -150,11 +150,26 @@ func (it *textIterator) processGlyph(g text.Glyph, ok bool) (_ text.Glyph, visib // Compute the maximum extent to which glyphs overhang on the horizontal // axis. if d := g.Bounds.Min.X.Floor(); d < it.padding.Min.X { + // If the distance between the dot and the left edge of this glyph is + // less than the current padding, increase the left padding. it.padding.Min.X = d } if d := (g.Bounds.Max.X - g.Advance).Ceil(); d > it.padding.Max.X { + // If the distance between the dot and the right edge of this glyph + // minus the logical advance of this glyph is greater than the current + // padding, increase the right padding. it.padding.Max.X = d } + if d := (g.Bounds.Min.Y + g.Ascent).Floor(); d < it.padding.Min.Y { + // If the distance between the dot and the top of this glyph is greater + // than the ascent of the glyph, increase the top padding. + it.padding.Min.Y = d + } + if d := (g.Bounds.Max.Y - g.Descent).Ceil(); d > it.padding.Max.Y { + // If the distance between the dot and the bottom of this glyph is greater + // than the descent of the glyph, increase the bottom padding. + it.padding.Max.Y = d + } logicalBounds := image.Rectangle{ Min: image.Pt(g.X.Floor(), int(g.Y)-g.Ascent.Ceil()), Max: image.Pt((g.X + g.Advance).Ceil(), int(g.Y)+g.Descent.Ceil()), diff --git a/widget/label_test.go b/widget/label_test.go index 4e57d487..ca3e6c2a 100644 --- a/widget/label_test.go +++ b/widget/label_test.go @@ -166,3 +166,67 @@ func TestGlyphIterator(t *testing.T) { }) } } + +// TestGlyphIteratorPadding ensures that the glyph iterator computes correct padding +// around glyphs with unusual bounding boxes. +func TestGlyphIteratorPadding(t *testing.T) { + type testcase struct { + name string + glyph text.Glyph + viewport image.Rectangle + expectedDims image.Rectangle + expectedPadding image.Rectangle + expectedBaseline int + } + for _, tc := range []testcase{ + { + name: "simple", + glyph: text.Glyph{ + X: 0, + Y: 50, + Advance: fixed.I(50), + Ascent: fixed.I(50), + Descent: fixed.I(50), + Bounds: fixed.Rectangle26_6{ + Min: fixed.Point26_6{ + X: fixed.I(-5), + Y: fixed.I(-56), + }, + Max: fixed.Point26_6{ + X: fixed.I(57), + Y: fixed.I(58), + }, + }, + }, + viewport: image.Rectangle{Max: image.Pt(math.MaxInt, math.MaxInt)}, + expectedDims: image.Rectangle{ + Max: image.Point{X: 50, Y: 100}, + }, + expectedBaseline: 50, + expectedPadding: image.Rectangle{ + Min: image.Point{ + X: -5, + Y: -6, + }, + Max: image.Point{ + X: 7, + Y: 8, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + it := textIterator{viewport: tc.viewport} + it.processGlyph(tc.glyph, true) + 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) + } + if it.padding != tc.expectedPadding { + t.Errorf("expected padding %d, got %d", tc.expectedPadding, it.padding) + } + }) + } +}