M widget/editor.go => widget/editor.go +10 -23
@@ 735,9 735,6 @@ func (e *Editor) PaintSelection(gtx layout.Context) {
}
func (e *Editor) PaintText(gtx layout.Context) {
- var gs [32]text.Glyph
- line := gs[:0]
- var lineOff image.Point
m := op.Record(gtx.Ops)
viewport := image.Rectangle{
Min: e.scrollOff,
@@ 752,26 749,15 @@ func (e *Editor) PaintText(gtx layout.Context) {
}
startGlyph += line.glyphs
}
+ var glyphs [32]text.Glyph
+ line := glyphs[:0]
for _, g := range e.index.glyphs[startGlyph:] {
- if !it.Glyph(g, true) {
+ var ok bool
+ if line, ok = it.paintGlyph(gtx, e.shaper, g, line); !ok {
break
}
- if it.visible {
- if len(line) == 0 {
- lineOff = image.Point{X: it.g.X.Floor(), Y: int(it.g.Y)}.Sub(e.scrollOff)
- }
- line = append(line, g)
- }
- if g.Flags&text.FlagLineBreak > 0 || cap(line)-len(line) == 0 {
- t := op.Offset(lineOff).Push(gtx.Ops)
- op := clip.Outline{Path: e.shaper.Shape(line)}.Op().Push(gtx.Ops)
- paint.PaintOp{}.Add(gtx.Ops)
- op.Pop()
- t.Pop()
- line = line[:0]
- }
-
}
+
call := m.Stop()
viewport.Min = viewport.Min.Add(it.padding.Min)
viewport.Max = viewport.Max.Add(it.padding.Max)
@@ 906,14 892,15 @@ func (e *Editor) layoutText(lt *text.Shaper) {
PxPerEm: e.textSize,
Alignment: e.Alignment,
}, 0, e.maxWidth, e.locale, r)
- for it.Glyph(lt.NextGlyph()) {
- e.index.Glyph(it.g)
+ for glyph, ok := it.processGlyph(lt.NextGlyph()); ok; glyph, ok = it.processGlyph(lt.NextGlyph()) {
+ e.index.Glyph(glyph)
}
} else {
// Make a fake glyph for every rune in the reader.
for _, _, err := r.ReadRune(); err != io.EOF; _, _, err = r.ReadRune() {
- it.Glyph(text.Glyph{Runes: 1, Flags: text.FlagClusterBreak}, true)
- e.index.Glyph(it.g)
+ g, _ := it.processGlyph(text.Glyph{Runes: 1, Flags: text.FlagClusterBreak}, true)
+ e.index.Glyph(g)
+
}
}
dims := layout.Dimensions{Size: it.bounds.Size()}
M widget/label.go => widget/label.go +73 -46
@@ 37,23 37,12 @@ func (l Label) Layout(gtx layout.Context, lt *text.Shaper, font text.Font, size
viewport := image.Rectangle{Max: cs.Max}
it := textIterator{viewport: viewport}
semantic.LabelOp(txt).Add(gtx.Ops)
- var gs [32]text.Glyph
- line := gs[:0]
- var lineOff image.Point
- for it.Glyph(lt.NextGlyph()) {
- if it.visible {
- if len(line) == 0 {
- lineOff = image.Point{X: it.g.X.Floor(), Y: int(it.g.Y)}
- }
- line = append(line, it.g)
- }
- if it.g.Flags&text.FlagLineBreak > 0 || cap(line)-len(line) == 0 {
- t := op.Offset(lineOff).Push(gtx.Ops)
- op := clip.Outline{Path: lt.Shape(line)}.Op().Push(gtx.Ops)
- paint.PaintOp{}.Add(gtx.Ops)
- op.Pop()
- t.Pop()
- line = line[:0]
+ var glyphs [32]text.Glyph
+ line := glyphs[:0]
+ for g, ok := lt.NextGlyph(); ok; g, ok = lt.NextGlyph() {
+ var ok bool
+ if line, ok = it.paintGlyph(gtx, lt, g, line); !ok {
+ break
}
}
call := m.Stop()
@@ 72,52 61,90 @@ func r2p(r clip.Rect) clip.Op {
return clip.Stroke{Path: r.Path(), Width: 1}.Op()
}
+// textIterator computes the bounding box of and paints text.
type textIterator struct {
- g text.Glyph
+ // viewport is the rectangle of document coordinates that the iterator is
+ // trying to fill with text.
viewport image.Rectangle
- padding image.Rectangle
- bounds image.Rectangle
- visible bool
- first 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
+ // 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
}
-func (t *textIterator) Glyph(g text.Glyph, ok bool) bool {
- t.g = g
+// 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) {
bounds := image.Rectangle{
Min: image.Pt(g.Bounds.Min.X.Floor(), g.Bounds.Min.Y.Floor()),
Max: image.Pt(g.Bounds.Max.X.Ceil(), g.Bounds.Max.Y.Ceil()),
}
// Compute the maximum extent to which glyphs overhang on the horizontal
// axis.
- if d := g.Bounds.Min.X.Floor(); d < t.padding.Min.X {
- t.padding.Min.X = d
+ 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 > t.padding.Max.X {
- t.padding.Max.X = d
+ if d := (g.Bounds.Max.X - g.Advance).Ceil(); d > it.padding.Max.X {
+ it.padding.Max.X = d
}
// Convert the bounds from dot-relative coordinates to document coordinates.
bounds = bounds.Add(image.Pt(g.X.Round(), int(g.Y)))
- if !t.first {
- t.first = true
- t.baseline = int(g.Y)
- t.bounds = bounds
+ if !it.first {
+ it.first = true
+ it.baseline = int(g.Y)
+ it.bounds = bounds
+ }
+
+ lineTop := int(g.Y) - g.Ascent.Ceil()
+ lineBottom := int(g.Y) + g.Descent.Ceil()
+ above := lineBottom < it.viewport.Min.Y
+ below := lineTop > it.viewport.Max.Y
+ left := bounds.Max.X < it.viewport.Min.X
+ right := bounds.Min.X > it.viewport.Max.X
+ it.visible = !above && !below && !left && !right
+ if it.visible {
+ it.bounds.Min.X = min(it.bounds.Min.X, bounds.Min.X)
+ it.bounds.Min.Y = min(it.bounds.Min.Y, lineTop)
+ it.bounds.Max.X = max(it.bounds.Max.X, bounds.Max.X)
+ it.bounds.Max.Y = max(it.bounds.Max.Y, lineBottom)
}
+ return g, ok && !below
+}
- above := bounds.Max.Y < t.viewport.Min.Y
- below := bounds.Min.Y > t.viewport.Max.Y
- left := bounds.Max.X < t.viewport.Min.X
- right := bounds.Min.X > t.viewport.Max.X
- t.visible = !above && !below && !left && !right
- if t.visible {
- t.bounds.Min.X = min(t.bounds.Min.X, bounds.Min.X)
- t.bounds.Min.Y = min(t.bounds.Min.Y, int(g.Y)-g.Ascent.Ceil())
- t.bounds.Max.X = max(t.bounds.Max.X, bounds.Max.X)
- t.bounds.Max.Y = max(t.bounds.Max.Y, int(g.Y)+g.Descent.Ceil())
+// 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)
+ done := !visibleOrBefore
+ if it.visible {
+ if len(line) == 0 {
+ it.lineOff = image.Point{X: glyph.X.Floor(), Y: int(glyph.Y)}.Sub(it.viewport.Min)
+ }
+ line = append(line, glyph)
}
- if t.bounds.Dy() == 0 {
- t.bounds.Min.Y = -g.Ascent.Ceil()
- t.bounds.Max.Y = g.Descent.Ceil()
+ if glyph.Flags&text.FlagLineBreak > 0 || cap(line)-len(line) == 0 || done {
+ 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 ok && !below
+ return line, !done
}