forked from joejulian/gio
b331407e81
use them for Editor, which is no longer required to construct a string for laying out its content. Signed-off-by: Elias Naur <mail@eliasnaur.com>
160 lines
3.7 KiB
Go
160 lines
3.7 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package text
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
|
|
"golang.org/x/image/font"
|
|
|
|
"gioui.org/op"
|
|
"gioui.org/unit"
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
// Shaper implements layout and shaping of text.
|
|
type Shaper interface {
|
|
// Layout a text according to a set of options.
|
|
Layout(c unit.Converter, font Font, txt io.Reader, opts LayoutOptions) ([]Line, error)
|
|
// Shape a line of text and return a clipping operation for its outline.
|
|
Shape(c unit.Converter, font Font, layout []Glyph) op.CallOp
|
|
|
|
// LayoutString is like Layout, but for strings..
|
|
LayoutString(c unit.Converter, font Font, str string, opts LayoutOptions) []Line
|
|
// ShapeString is like Shape for lines previously laid out by LayoutString.
|
|
ShapeString(c unit.Converter, font Font, str string, layout []Glyph) op.CallOp
|
|
|
|
// Metrics returns the font metrics for font.
|
|
Metrics(c unit.Converter, font Font) font.Metrics
|
|
}
|
|
|
|
// FontRegistry implements layout and shaping of text from a set of
|
|
// registered fonts.
|
|
//
|
|
// If a font matches no registered shape, FontRegistry falls back to the
|
|
// first registered face.
|
|
//
|
|
// The LayoutString and ShapeString results are cached and re-used if
|
|
// possible.
|
|
type FontRegistry struct {
|
|
def Typeface
|
|
faces map[Font]*face
|
|
}
|
|
|
|
type face struct {
|
|
face Face
|
|
layoutCache layoutCache
|
|
pathCache pathCache
|
|
}
|
|
|
|
func (s *FontRegistry) Register(font Font, tf Face) {
|
|
if s.faces == nil {
|
|
s.def = font.Typeface
|
|
s.faces = make(map[Font]*face)
|
|
}
|
|
// Treat all font sizes equally.
|
|
font.Size = unit.Value{}
|
|
if font.Weight == 0 {
|
|
font.Weight = Normal
|
|
}
|
|
s.faces[font] = &face{
|
|
face: tf,
|
|
}
|
|
}
|
|
|
|
func (s *FontRegistry) Layout(c unit.Converter, font Font, txt io.Reader, opts LayoutOptions) ([]Line, error) {
|
|
tf := s.faceForFont(font)
|
|
ppem := fixed.I(c.Px(font.Size))
|
|
return tf.face.Layout(ppem, txt, opts)
|
|
}
|
|
|
|
func (s *FontRegistry) Shape(c unit.Converter, font Font, layout []Glyph) op.CallOp {
|
|
tf := s.faceForFont(font)
|
|
ppem := fixed.I(c.Px(font.Size))
|
|
return tf.face.Shape(ppem, layout)
|
|
}
|
|
|
|
func (s *FontRegistry) LayoutString(c unit.Converter, font Font, str string, opts LayoutOptions) []Line {
|
|
tf := s.faceForFont(font)
|
|
return tf.layout(fixed.I(c.Px(font.Size)), str, opts)
|
|
}
|
|
|
|
func (s *FontRegistry) ShapeString(c unit.Converter, font Font, str string, layout []Glyph) op.CallOp {
|
|
tf := s.faceForFont(font)
|
|
return tf.shape(fixed.I(c.Px(font.Size)), str, layout)
|
|
}
|
|
|
|
func (s *FontRegistry) Metrics(c unit.Converter, font Font) font.Metrics {
|
|
tf := s.faceForFont(font)
|
|
return tf.metrics(fixed.I(c.Px(font.Size)))
|
|
}
|
|
|
|
func (s *FontRegistry) faceForStyle(font Font) *face {
|
|
tf := s.faces[font]
|
|
if tf == nil {
|
|
font := font
|
|
font.Weight = Normal
|
|
tf = s.faces[font]
|
|
}
|
|
if tf == nil {
|
|
font := font
|
|
font.Style = Regular
|
|
tf = s.faces[font]
|
|
}
|
|
if tf == nil {
|
|
font := font
|
|
font.Style = Regular
|
|
font.Weight = Normal
|
|
tf = s.faces[font]
|
|
}
|
|
return tf
|
|
}
|
|
|
|
func (s *FontRegistry) faceForFont(font Font) *face {
|
|
font.Size = unit.Value{}
|
|
tf := s.faceForStyle(font)
|
|
if tf == nil {
|
|
font.Typeface = s.def
|
|
tf = s.faceForStyle(font)
|
|
}
|
|
return tf
|
|
}
|
|
|
|
func (t *face) layout(ppem fixed.Int26_6, str string, opts LayoutOptions) []Line {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
lk := layoutKey{
|
|
ppem: ppem,
|
|
str: str,
|
|
opts: opts,
|
|
}
|
|
if l, ok := t.layoutCache.Get(lk); ok {
|
|
return l
|
|
}
|
|
l, _ := t.face.Layout(ppem, strings.NewReader(str), opts)
|
|
t.layoutCache.Put(lk, l)
|
|
return l
|
|
}
|
|
|
|
func (t *face) shape(ppem fixed.Int26_6, str string, layout []Glyph) op.CallOp {
|
|
if t == nil {
|
|
return op.CallOp{}
|
|
}
|
|
pk := pathKey{
|
|
ppem: ppem,
|
|
str: str,
|
|
}
|
|
if clip, ok := t.pathCache.Get(pk); ok {
|
|
return clip
|
|
}
|
|
clip := t.face.Shape(ppem, layout)
|
|
t.pathCache.Put(pk, clip)
|
|
return clip
|
|
}
|
|
|
|
func (t *face) metrics(ppem fixed.Int26_6) font.Metrics {
|
|
return t.face.Metrics(ppem)
|
|
}
|