-
-
Notifications
You must be signed in to change notification settings - Fork 28
/
label.go
118 lines (108 loc) · 3.6 KB
/
label.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package theme
// A copy of (our) widget.Label, with all actual rendering removed, used when we are only interested in computing the
// dimensions.
import (
"image"
"gioui.org/font"
"gioui.org/layout"
"gioui.org/text"
"gioui.org/unit"
"honnef.co/go/gotraceui/widget"
"golang.org/x/image/math/fixed"
)
func labelDimensions(gtx layout.Context, l widget.Label, lt *text.Shaper, font font.Font, size unit.Sp, txt string) layout.Dimensions {
cs := gtx.Constraints
textSize := fixed.I(gtx.Sp(size))
lt.LayoutString(text.Parameters{
Font: font,
PxPerEm: textSize,
MaxLines: l.MaxLines,
Truncator: l.Truncator,
Alignment: l.Alignment,
MaxWidth: cs.Max.X,
MinWidth: cs.Min.X,
Locale: gtx.Locale,
}, txt)
viewport := image.Rectangle{Max: cs.Max}
it := textIterator{
viewport: viewport,
maxLines: l.MaxLines,
}
for g, ok := lt.NextGlyph(); ok; g, ok = lt.NextGlyph() {
if _, ok := it.processGlyph(g, true); !ok {
break
}
}
viewport.Min = viewport.Min.Add(it.padding.Min)
viewport.Max = viewport.Max.Add(it.padding.Max)
dims := layout.Dimensions{Size: it.bounds.Size()}
dims.Size = cs.Constrain(dims.Size)
dims.Baseline = dims.Size.Y - it.baseline
return dims
}
// textIterator computes the bounding box of and paints text.
type textIterator struct {
// viewport is the rectangle of document coordinates that the iterator is
// trying to fill with text.
viewport image.Rectangle
// maxLines is the maximum number of text lines that should be displayed.
maxLines int
// linesSeen tracks the quantity of line endings this iterator has seen.
linesSeen int
// padding is the space needed outside of the bounds of the text to ensure no
// part of a glyph is clipped.
padding image.Rectangle
// bounds is the logical bounding box of the text.
bounds image.Rectangle
// visible tracks whether the most recently iterated glyph is visible within
// the viewport.
visible bool
// first tracks whether the iterator has processed a glyph yet.
first bool
// baseline tracks the location of the first line of text's baseline.
baseline int
}
// processGlyph checks whether the glyph is visible within the iterator's configured
// viewport and (if so) updates the iterator's text dimensions to include the glyph.
func (it *textIterator) processGlyph(g text.Glyph, ok bool) (_ text.Glyph, visibleOrBefore bool) {
if !it.first && g.Flags&text.FlagTruncator != 0 {
return g, false
}
if it.maxLines > 0 {
if g.Flags&text.FlagLineBreak != 0 {
it.linesSeen++
}
if it.linesSeen == it.maxLines && g.Flags&text.FlagParagraphBreak != 0 {
return g, false
}
}
// Compute the maximum extent to which glyphs overhang on the horizontal
// axis.
if d := g.Bounds.Min.X.Floor(); d < it.padding.Min.X {
it.padding.Min.X = d
}
if d := (g.Bounds.Max.X - g.Advance).Ceil(); d > it.padding.Max.X {
it.padding.Max.X = 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()),
}
if !it.first {
it.first = true
it.baseline = int(g.Y)
it.bounds = logicalBounds
}
above := logicalBounds.Max.Y < it.viewport.Min.Y
below := logicalBounds.Min.Y > it.viewport.Max.Y
left := logicalBounds.Max.X < it.viewport.Min.X
right := logicalBounds.Min.X > it.viewport.Max.X
it.visible = !above && !below && !left && !right
if it.visible {
it.bounds.Min.X = min(it.bounds.Min.X, logicalBounds.Min.X)
it.bounds.Min.Y = min(it.bounds.Min.Y, logicalBounds.Min.Y)
it.bounds.Max.X = max(it.bounds.Max.X, logicalBounds.Max.X)
it.bounds.Max.Y = max(it.bounds.Max.Y, logicalBounds.Max.Y)
}
return g, ok && !below
}