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 <christopher.waldon.dev@gmail.com>
This commit is contained in:
Chris Waldon
2023-07-31 10:41:49 -04:00
committed by Elias Naur
parent c7c49c3258
commit edbf872b44
2 changed files with 79 additions and 0 deletions
+15
View File
@@ -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()),
+64
View File
@@ -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)
}
})
}
}