-
Notifications
You must be signed in to change notification settings - Fork 0
/
iterator.go
141 lines (131 loc) · 4.57 KB
/
iterator.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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package styledtext
import (
"image"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/text"
"golang.org/x/exp/constraints"
"golang.org/x/image/math/fixed"
)
// textIterator computes the bounding box of and paints text. This iterator is
// specialized to laying out single lines of text.
type textIterator struct {
// viewport is the rectangle of document coordinates that the iterator is
// trying to fill with text.
viewport image.Rectangle
// maxLines tracks the maximum allowed number of glyphs with FlagLineBreak.
maxLines int
// linesSeen tracks the number of FlagLineBreak glyphs we have seen.
linesSeen int
// init tracks whether the iterator has processed any glyphs.
init bool
// firstX tracks the x offset of the first processed glyph. This is subtracted
// from all glyph x offsets in order to ensure that the text is rendered at
// x=0.
firstX fixed.Int26_6
// hasNewline tracks whether the processed glyphs contained a synthetic newline
// character.
hasNewline bool
// lineOff tracks the origin for the glyphs in the current line.
lineOff image.Point
// 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
// runes is the count of runes represented by the processed glyphs.
runes int
// 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) {
it.runes += int(g.Runes)
it.hasNewline = it.hasNewline || (g.Flags&text.FlagLineBreak > 0 && g.Flags&text.FlagParagraphBreak > 0)
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
}
func min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
// paintGlyph buffers up and paints text glyphs. It should be invoked iteratively upon each glyph
// until it returns false. The line parameter should be a slice with
// a backing array of sufficient size to buffer multiple glyphs.
// A modified slice will be returned with each invocation, and is
// expected to be passed back in on the following invocation.
// This design is awkward, but prevents the line slice from escaping
// to the heap.
func (it *textIterator) paintGlyph(gtx layout.Context, shaper *text.Shaper, glyph text.Glyph, line []text.Glyph) ([]text.Glyph, bool) {
_, visibleOrBefore := it.processGlyph(glyph, true)
if it.visible {
if !it.init {
it.firstX = glyph.X
it.init = true
}
if len(line) == 0 {
it.lineOff = image.Point{X: (glyph.X - it.firstX).Floor(), Y: int(glyph.Y)}.Sub(it.viewport.Min)
}
line = append(line, glyph)
}
if glyph.Flags&text.FlagLineBreak > 0 || cap(line)-len(line) == 0 || !visibleOrBefore {
t := op.Offset(it.lineOff).Push(gtx.Ops)
op := clip.Outline{Path: shaper.Shape(line)}.Op().Push(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
op.Pop()
t.Pop()
line = line[:0]
}
return line, visibleOrBefore
}