mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
b07d34354e
Before this change, package font implemented a global font registry, with the usual problems of package global state. This change deletes the global registry and introduces the text.Collection type for representing a list of fonts and their faces. Collection exports Lookup that finds the closest match and its face. The existing FontRegistry is renamed to Cache to reflect its new limited functionality: a cache of shapes and measurements on top of a Collection. Then, material.NewTheme is changed to take a Collection and initialize a Cache. Updates gio#19 because multiple windows require a separate (writable) Cache per window, while (read-only) Collections may be shared. Signed-off-by: Elias Naur <mail@eliasnaur.com>
178 lines
4.1 KiB
Go
178 lines
4.1 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package text
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
|
|
"golang.org/x/image/font"
|
|
|
|
"gioui.org/op"
|
|
"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(font Font, size fixed.Int26_6, maxWidth int, txt io.Reader) ([]Line, error)
|
|
// Shape a line of text and return a clipping operation for its outline.
|
|
Shape(font Font, size fixed.Int26_6, layout []Glyph) op.CallOp
|
|
|
|
// LayoutString is like Layout, but for strings..
|
|
LayoutString(font Font, size fixed.Int26_6, maxWidth int, str string) []Line
|
|
// ShapeString is like Shape for lines previously laid out by LayoutString.
|
|
ShapeString(font Font, size fixed.Int26_6, str string, layout []Glyph) op.CallOp
|
|
|
|
// Metrics returns the font metrics for font.
|
|
Metrics(font Font, size fixed.Int26_6) font.Metrics
|
|
}
|
|
|
|
// Collection maps Fonts to Faces.
|
|
type Collection struct {
|
|
def Typeface
|
|
faces map[Font]Face
|
|
}
|
|
|
|
// Cache implements cached layout and shaping of text from a set of
|
|
// registered fonts.
|
|
//
|
|
// If a font matches no registered shape, Cache falls back to the
|
|
// first registered face.
|
|
//
|
|
// The LayoutString and ShapeString results are cached and re-used if
|
|
// possible.
|
|
type Cache struct {
|
|
col *Collection
|
|
faces map[Font]*faceCache
|
|
}
|
|
|
|
type faceCache struct {
|
|
layoutCache layoutCache
|
|
pathCache pathCache
|
|
}
|
|
|
|
func (c *Collection) Register(font Font, tf Face) {
|
|
if c.faces == nil {
|
|
c.def = font.Typeface
|
|
c.faces = make(map[Font]Face)
|
|
}
|
|
if font.Weight == 0 {
|
|
font.Weight = Normal
|
|
}
|
|
c.faces[font] = tf
|
|
}
|
|
|
|
// Lookup a font and return the effective font and its
|
|
// font face.
|
|
func (c *Collection) Lookup(font Font) (Font, Face) {
|
|
var f Face
|
|
font, f = c.faceForStyle(font)
|
|
if f == nil {
|
|
font.Typeface = c.def
|
|
font, f = c.faceForStyle(font)
|
|
}
|
|
return font, f
|
|
}
|
|
|
|
func (c *Collection) faceForStyle(font Font) (Font, Face) {
|
|
tf := c.faces[font]
|
|
if tf == nil {
|
|
font := font
|
|
font.Weight = Normal
|
|
tf = c.faces[font]
|
|
}
|
|
if tf == nil {
|
|
font := font
|
|
font.Style = Regular
|
|
tf = c.faces[font]
|
|
}
|
|
if tf == nil {
|
|
font := font
|
|
font.Style = Regular
|
|
font.Weight = Normal
|
|
tf = c.faces[font]
|
|
}
|
|
return font, tf
|
|
}
|
|
|
|
func NewCache(fonts *Collection) *Cache {
|
|
return &Cache{
|
|
col: fonts,
|
|
faces: make(map[Font]*faceCache),
|
|
}
|
|
}
|
|
|
|
func (s *Cache) Layout(font Font, size fixed.Int26_6, maxWidth int, txt io.Reader) ([]Line, error) {
|
|
_, face := s.faceForFont(font)
|
|
return face.Layout(size, maxWidth, txt)
|
|
}
|
|
|
|
func (s *Cache) Shape(font Font, size fixed.Int26_6, layout []Glyph) op.CallOp {
|
|
_, face := s.faceForFont(font)
|
|
return face.Shape(size, layout)
|
|
}
|
|
|
|
func (s *Cache) LayoutString(font Font, size fixed.Int26_6, maxWidth int, str string) []Line {
|
|
cache, face := s.faceForFont(font)
|
|
return cache.layout(face, size, maxWidth, str)
|
|
}
|
|
|
|
func (s *Cache) ShapeString(font Font, size fixed.Int26_6, str string, layout []Glyph) op.CallOp {
|
|
cache, face := s.faceForFont(font)
|
|
return cache.shape(face, size, str, layout)
|
|
}
|
|
|
|
func (s *Cache) Metrics(font Font, size fixed.Int26_6) font.Metrics {
|
|
cache, face := s.faceForFont(font)
|
|
return cache.metrics(face, size)
|
|
}
|
|
|
|
func (s *Cache) faceForFont(font Font) (*faceCache, Face) {
|
|
var f Face
|
|
font, f = s.col.Lookup(font)
|
|
cache, exists := s.faces[font]
|
|
if !exists {
|
|
cache = new(faceCache)
|
|
s.faces[font] = cache
|
|
}
|
|
return cache, f
|
|
}
|
|
|
|
func (t *faceCache) layout(face Face, ppem fixed.Int26_6, maxWidth int, str string) []Line {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
lk := layoutKey{
|
|
ppem: ppem,
|
|
maxWidth: maxWidth,
|
|
str: str,
|
|
}
|
|
if l, ok := t.layoutCache.Get(lk); ok {
|
|
return l
|
|
}
|
|
l, _ := face.Layout(ppem, maxWidth, strings.NewReader(str))
|
|
t.layoutCache.Put(lk, l)
|
|
return l
|
|
}
|
|
|
|
func (t *faceCache) shape(face Face, 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 := face.Shape(ppem, layout)
|
|
t.pathCache.Put(pk, clip)
|
|
return clip
|
|
}
|
|
|
|
func (t *faceCache) metrics(face Face, ppem fixed.Int26_6) font.Metrics {
|
|
return face.Metrics(ppem)
|
|
}
|