Files
gio/ui/text/label.go
T
Elias Naur 0061c73a89 ui: split OpImage into OpImage and OpDraw
In preparation for an OpColor (and future OpGradient and similar).

Label and Editor no longer take an explicit source image. They
draw with the current image.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2019-06-01 12:44:49 +02:00

145 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
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.OpDraw{Rect: lclip}.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
}