144 lines
4.4 KiB
Go
144 lines
4.4 KiB
Go
package gel
|
|
|
|
import (
|
|
"image"
|
|
|
|
"github.com/p9c/p9/pkg/gel/gio/f32"
|
|
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
|
"github.com/p9c/p9/pkg/gel/gio/op"
|
|
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
|
"github.com/p9c/p9/pkg/gel/gio/op/paint"
|
|
"github.com/p9c/p9/pkg/gel/gio/text"
|
|
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
// GlyphIterator computes bounding boxes and paints text glyphs.
|
|
// It is the consolidated iterator used by Label, Text, and Editor widgets.
|
|
type GlyphIterator struct {
|
|
Viewport image.Rectangle
|
|
MaxLines int
|
|
Material op.CallOp // Optional material for text color
|
|
Truncated int
|
|
LinesSeen int
|
|
LineOff f32.Point
|
|
Padding image.Rectangle
|
|
Bounds image.Rectangle
|
|
Visible bool
|
|
First bool
|
|
Baseline int
|
|
}
|
|
|
|
// FixedToFloat converts a fixed.Int26_6 to float32.
|
|
func FixedToFloat(i fixed.Int26_6) float32 {
|
|
return float32(i) / 64.0
|
|
}
|
|
|
|
// ProcessGlyph checks whether the glyph is visible within the iterator's viewport
|
|
// and updates the iterator's text dimensions to include the glyph.
|
|
func (it *GlyphIterator) ProcessGlyph(g text.Glyph, ok bool) (visibleOrBefore bool) {
|
|
if it.MaxLines > 0 {
|
|
if g.Flags&text.FlagTruncator != 0 && g.Flags&text.FlagClusterBreak != 0 {
|
|
it.Truncated = int(g.Runes)
|
|
}
|
|
if g.Flags&text.FlagLineBreak != 0 {
|
|
it.LinesSeen++
|
|
}
|
|
if it.LinesSeen == it.MaxLines && g.Flags&text.FlagParagraphBreak != 0 {
|
|
return false
|
|
}
|
|
}
|
|
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
|
|
}
|
|
if d := (g.Bounds.Min.Y + g.Ascent).Floor(); d < it.Padding.Min.Y {
|
|
it.Padding.Min.Y = d
|
|
}
|
|
if d := (g.Bounds.Max.Y - g.Descent).Ceil(); d > it.Padding.Max.Y {
|
|
it.Padding.Max.Y = 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 ok && !below
|
|
}
|
|
|
|
// PaintGlyph buffers and paints text glyphs. If Material is set, it applies the material.
|
|
func (it *GlyphIterator) PaintGlyph(gtx l.Context, shaper *text.Shaper, glyph text.Glyph, line []text.Glyph) ([]text.Glyph, bool) {
|
|
visibleOrBefore := it.ProcessGlyph(glyph, true)
|
|
if it.Visible {
|
|
if len(line) == 0 {
|
|
it.LineOff = f32.Point{X: FixedToFloat(glyph.X), Y: float32(glyph.Y)}.Sub(l.FPt(it.Viewport.Min))
|
|
}
|
|
line = append(line, glyph)
|
|
}
|
|
if glyph.Flags&text.FlagLineBreak != 0 || cap(line)-len(line) == 0 || !visibleOrBefore {
|
|
t := op.Affine(f32.AffineId().Offset(it.LineOff)).Push(gtx.Ops)
|
|
path := shaper.Shape(line)
|
|
outline := clip.Outline{Path: path}.Op().Push(gtx.Ops)
|
|
if it.Material != (op.CallOp{}) {
|
|
it.Material.Add(gtx.Ops)
|
|
}
|
|
paint.PaintOp{}.Add(gtx.Ops)
|
|
outline.Pop()
|
|
if call := shaper.Bitmaps(line); call != (op.CallOp{}) {
|
|
call.Add(gtx.Ops)
|
|
}
|
|
t.Pop()
|
|
line = line[:0]
|
|
}
|
|
return line, visibleOrBefore
|
|
}
|
|
|
|
// RenderText is a helper that renders text using the GlyphIterator.
|
|
// It handles the common pattern of iterating glyphs and computing dimensions.
|
|
func RenderText(gtx l.Context, shaper *text.Shaper, maxLines int, material op.CallOp) l.Dimensions {
|
|
cs := gtx.Constraints
|
|
m := op.Record(gtx.Ops)
|
|
viewport := image.Rectangle{Max: cs.Max}
|
|
it := GlyphIterator{
|
|
Viewport: viewport,
|
|
MaxLines: maxLines,
|
|
Material: material,
|
|
}
|
|
var glyphs [32]text.Glyph
|
|
line := glyphs[:0]
|
|
for g, ok := shaper.NextGlyph(); ok; g, ok = shaper.NextGlyph() {
|
|
var ok bool
|
|
if line, ok = it.PaintGlyph(gtx, shaper, g, line); !ok {
|
|
break
|
|
}
|
|
}
|
|
call := m.Stop()
|
|
viewport.Min = viewport.Min.Add(it.Padding.Min)
|
|
viewport.Max = viewport.Max.Add(it.Padding.Max)
|
|
clipStack := clip.Rect(viewport).Push(gtx.Ops)
|
|
call.Add(gtx.Ops)
|
|
dims := l.Dimensions{Size: it.Bounds.Size()}
|
|
dims.Size = cs.Constrain(dims.Size)
|
|
dims.Baseline = dims.Size.Y - it.Baseline
|
|
clipStack.Pop()
|
|
return dims
|
|
}
|