Files
gio/ui/text/label.go
T
Elias Naur 9b3429d6da ui: switch to (more) explicit layout
The layout package switched from interfaces to functions for
composing layouts. The switch made sure that no garbage is
generated for transient layouts such as Align, Inset, Stack, Flex.

Unfortunately, that left the stateful widgets and layouts: as soon
as their layout methods are embedded in a transient layout, a
closure is generated that escapes to the heap.

To avoid garbage for both transient as well as stateful widgets,
replace the functional approach with explicit begin/end methods.

A begin method generally starts an op block and returns the adjusted
constraints. An end method takes computed dimensions, ends its op
block and returns adjusted dimensions.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2019-05-30 21:39:00 +02:00

146 lines
3.2 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package text
import (
"image"
"math"
"unicode/utf8"
"gioui.org/ui"
"gioui.org/ui/draw"
"gioui.org/ui/f32"
"gioui.org/ui/layout"
"golang.org/x/image/math/fixed"
)
type Label struct {
Face Face
Src image.Image
Alignment Alignment
Text string
it lineIterator
}
type lineIterator struct {
Lines []Line
Clip image.Rectangle
Alignment Alignment
Width int
Offset image.Point
y, prevDesc fixed.Int26_6
}
func (l *lineIterator) Next() (String, f32.Point, bool) {
for len(l.Lines) > 0 {
line := l.Lines[0]
l.Lines = l.Lines[1:]
x := align(l.Alignment, line.Width, l.Width) + fixed.I(l.Offset.X)
l.y += l.prevDesc + line.Ascent
l.prevDesc = line.Descent
// Align baseline and line start to the pixel grid.
off := fixed.Point26_6{X: fixed.I(x.Floor()), Y: fixed.I(l.y.Ceil())}
x, l.y = off.X, off.Y
off.Y += fixed.I(l.Offset.Y)
if (off.Y + line.Bounds.Min.Y).Floor() > l.Clip.Max.Y {
break
}
if (off.Y + line.Bounds.Max.Y).Ceil() < l.Clip.Min.Y {
continue
}
str := line.Text
for len(str.Advances) > 0 {
adv := str.Advances[0]
if (off.X + adv + line.Bounds.Max.X - line.Width).Ceil() >= l.Clip.Min.X {
break
}
off.X += adv
_, s := utf8.DecodeRuneInString(str.String)
str.String = str.String[s:]
str.Advances = str.Advances[1:]
}
n := 0
endx := off.X
for i, adv := range str.Advances {
if (endx + line.Bounds.Min.X).Floor() > l.Clip.Max.X {
str.String = str.String[:n]
str.Advances = str.Advances[:i]
break
}
_, s := utf8.DecodeRuneInString(str.String[n:])
n += s
endx += adv
}
offf := f32.Point{X: float32(off.X) / 64, Y: float32(off.Y) / 64}
return str, offf, true
}
return String{}, f32.Point{}, false
}
func (l Label) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
textLayout := l.Face.Layout(l.Text, false, cs.Width.Max)
lines := textLayout.Lines
dims := linesDimens(lines)
dims.Size = cs.Constrain(dims.Size)
padTop, padBottom := textPadding(lines)
clip := image.Rectangle{
Min: image.Point{X: -ui.Inf, Y: -padTop},
Max: image.Point{X: ui.Inf, Y: dims.Size.Y + padBottom},
}
l.it = lineIterator{
Lines: lines,
Clip: clip,
Alignment: l.Alignment,
Width: dims.Size.X,
}
for {
str, off, ok := l.it.Next()
if !ok {
break
}
path := l.Face.Path(str)
lclip := toRectF(clip).Sub(off)
ops.Begin()
ui.OpTransform{Transform: ui.Offset(off)}.Add(ops)
draw.OpClip{Path: path}.Add(ops)
draw.OpImage{Rect: lclip, Src: l.Src, SrcRect: l.Src.Bounds()}.Add(ops)
ops.End().Add(ops)
}
return dims
}
func itof(i int) float32 {
switch i {
case ui.Inf:
return float32(math.Inf(+1))
case -ui.Inf:
return float32(math.Inf(-1))
default:
return float32(i)
}
}
func toRectF(r image.Rectangle) f32.Rectangle {
return f32.Rectangle{
Min: f32.Point{X: itof(r.Min.X), Y: itof(r.Min.Y)},
Max: f32.Point{X: itof(r.Max.X), Y: itof(r.Max.Y)},
}
}
func textPadding(lines []Line) (padTop int, padBottom int) {
if len(lines) > 0 {
first := lines[0]
if d := -first.Bounds.Min.Y - first.Ascent; d > 0 {
padTop = d.Ceil()
}
last := lines[len(lines)-1]
if d := last.Bounds.Max.Y - last.Descent; d > 0 {
padBottom = d.Ceil()
}
}
return
}