mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 18:35:34 +00:00
text,widget/material: make font collections explicit
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>
This commit is contained in:
@@ -1,36 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
|
||||||
|
|
||||||
// Package font implements a central font registry.
|
|
||||||
package font
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"gioui.org/text"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
mu sync.Mutex
|
|
||||||
initialized bool
|
|
||||||
shaper = new(text.FontRegistry)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Default returns a singleton *text.Shaper that contains
|
|
||||||
// the registered fonts.
|
|
||||||
func Default() *text.FontRegistry {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
initialized = true
|
|
||||||
return shaper
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register a face. Register panics if Default has been
|
|
||||||
// called.
|
|
||||||
func Register(font text.Font, face text.Face) {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
if initialized {
|
|
||||||
panic("Register must be called before Default")
|
|
||||||
}
|
|
||||||
shaper.Register(font, face)
|
|
||||||
}
|
|
||||||
+28
-18
@@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
// Package gofont registers the Go fonts in the font registry.
|
// Package gofont exports the Go fonts as a text.Collection.
|
||||||
//
|
//
|
||||||
// See https://blog.golang.org/go-fonts for a description of the
|
// See https://blog.golang.org/go-fonts for a description of the
|
||||||
// fonts, and the golang.org/x/image/font/gofont packages for the
|
// fonts, and the golang.org/x/image/font/gofont packages for the
|
||||||
@@ -9,8 +9,8 @@ package gofont
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gioui.org/font"
|
|
||||||
"gioui.org/font/opentype"
|
"gioui.org/font/opentype"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
"golang.org/x/image/font/gofont/gobold"
|
"golang.org/x/image/font/gofont/gobold"
|
||||||
@@ -27,27 +27,37 @@ import (
|
|||||||
"golang.org/x/image/font/gofont/gosmallcapsitalic"
|
"golang.org/x/image/font/gofont/gosmallcapsitalic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Register() {
|
var (
|
||||||
register(text.Font{}, goregular.TTF)
|
once sync.Once
|
||||||
register(text.Font{Style: text.Italic}, goitalic.TTF)
|
collection *text.Collection
|
||||||
register(text.Font{Weight: text.Bold}, gobold.TTF)
|
)
|
||||||
register(text.Font{Style: text.Italic, Weight: text.Bold}, gobolditalic.TTF)
|
|
||||||
register(text.Font{Weight: text.Medium}, gomedium.TTF)
|
func Collection() *text.Collection {
|
||||||
register(text.Font{Weight: text.Medium, Style: text.Italic}, gomediumitalic.TTF)
|
once.Do(func() {
|
||||||
register(text.Font{Variant: "Mono"}, gomono.TTF)
|
c := new(text.Collection)
|
||||||
register(text.Font{Variant: "Mono", Weight: text.Bold}, gomonobold.TTF)
|
register(c, text.Font{}, goregular.TTF)
|
||||||
register(text.Font{Variant: "Mono", Weight: text.Bold, Style: text.Italic}, gomonobolditalic.TTF)
|
register(c, text.Font{Style: text.Italic}, goitalic.TTF)
|
||||||
register(text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF)
|
register(c, text.Font{Weight: text.Bold}, gobold.TTF)
|
||||||
register(text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF)
|
register(c, text.Font{Style: text.Italic, Weight: text.Bold}, gobolditalic.TTF)
|
||||||
register(text.Font{Variant: "Smallcaps"}, gosmallcaps.TTF)
|
register(c, text.Font{Weight: text.Medium}, gomedium.TTF)
|
||||||
register(text.Font{Variant: "Smallcaps", Style: text.Italic}, gosmallcapsitalic.TTF)
|
register(c, text.Font{Weight: text.Medium, Style: text.Italic}, gomediumitalic.TTF)
|
||||||
|
register(c, text.Font{Variant: "Mono"}, gomono.TTF)
|
||||||
|
register(c, text.Font{Variant: "Mono", Weight: text.Bold}, gomonobold.TTF)
|
||||||
|
register(c, text.Font{Variant: "Mono", Weight: text.Bold, Style: text.Italic}, gomonobolditalic.TTF)
|
||||||
|
register(c, text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF)
|
||||||
|
register(c, text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF)
|
||||||
|
register(c, text.Font{Variant: "Smallcaps"}, gosmallcaps.TTF)
|
||||||
|
register(c, text.Font{Variant: "Smallcaps", Style: text.Italic}, gosmallcapsitalic.TTF)
|
||||||
|
collection = c
|
||||||
|
})
|
||||||
|
return collection
|
||||||
}
|
}
|
||||||
|
|
||||||
func register(fnt text.Font, ttf []byte) {
|
func register(c *text.Collection, fnt text.Font, ttf []byte) {
|
||||||
face, err := opentype.Parse(ttf)
|
face, err := opentype.Parse(ttf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("failed to parse font: %v", err))
|
panic(fmt.Sprintf("failed to parse font: %v", err))
|
||||||
}
|
}
|
||||||
fnt.Typeface = "Go"
|
fnt.Typeface = "Go"
|
||||||
font.Register(fnt, face)
|
c.Register(fnt, face)
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-56
@@ -28,94 +28,118 @@ type Shaper interface {
|
|||||||
Metrics(font Font, size fixed.Int26_6) font.Metrics
|
Metrics(font Font, size fixed.Int26_6) font.Metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// FontRegistry implements layout and shaping of text from a set of
|
// 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.
|
// registered fonts.
|
||||||
//
|
//
|
||||||
// If a font matches no registered shape, FontRegistry falls back to the
|
// If a font matches no registered shape, Cache falls back to the
|
||||||
// first registered face.
|
// first registered face.
|
||||||
//
|
//
|
||||||
// The LayoutString and ShapeString results are cached and re-used if
|
// The LayoutString and ShapeString results are cached and re-used if
|
||||||
// possible.
|
// possible.
|
||||||
type FontRegistry struct {
|
type Cache struct {
|
||||||
def Typeface
|
col *Collection
|
||||||
faces map[Font]*face
|
faces map[Font]*faceCache
|
||||||
}
|
}
|
||||||
|
|
||||||
type face struct {
|
type faceCache struct {
|
||||||
face Face
|
|
||||||
layoutCache layoutCache
|
layoutCache layoutCache
|
||||||
pathCache pathCache
|
pathCache pathCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FontRegistry) Register(font Font, tf Face) {
|
func (c *Collection) Register(font Font, tf Face) {
|
||||||
if s.faces == nil {
|
if c.faces == nil {
|
||||||
s.def = font.Typeface
|
c.def = font.Typeface
|
||||||
s.faces = make(map[Font]*face)
|
c.faces = make(map[Font]Face)
|
||||||
}
|
}
|
||||||
if font.Weight == 0 {
|
if font.Weight == 0 {
|
||||||
font.Weight = Normal
|
font.Weight = Normal
|
||||||
}
|
}
|
||||||
s.faces[font] = &face{
|
c.faces[font] = tf
|
||||||
face: 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 (s *FontRegistry) Layout(font Font, size fixed.Int26_6, maxWidth int, txt io.Reader) ([]Line, error) {
|
func (c *Collection) faceForStyle(font Font) (Font, Face) {
|
||||||
tf := s.faceForFont(font)
|
tf := c.faces[font]
|
||||||
return tf.face.Layout(size, maxWidth, txt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FontRegistry) Shape(font Font, size fixed.Int26_6, layout []Glyph) op.CallOp {
|
|
||||||
tf := s.faceForFont(font)
|
|
||||||
return tf.face.Shape(size, layout)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FontRegistry) LayoutString(font Font, size fixed.Int26_6, maxWidth int, str string) []Line {
|
|
||||||
tf := s.faceForFont(font)
|
|
||||||
return tf.layout(size, maxWidth, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FontRegistry) ShapeString(font Font, size fixed.Int26_6, str string, layout []Glyph) op.CallOp {
|
|
||||||
tf := s.faceForFont(font)
|
|
||||||
return tf.shape(size, str, layout)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FontRegistry) Metrics(font Font, size fixed.Int26_6) font.Metrics {
|
|
||||||
tf := s.faceForFont(font)
|
|
||||||
return tf.metrics(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FontRegistry) faceForStyle(font Font) *face {
|
|
||||||
tf := s.faces[font]
|
|
||||||
if tf == nil {
|
if tf == nil {
|
||||||
font := font
|
font := font
|
||||||
font.Weight = Normal
|
font.Weight = Normal
|
||||||
tf = s.faces[font]
|
tf = c.faces[font]
|
||||||
}
|
}
|
||||||
if tf == nil {
|
if tf == nil {
|
||||||
font := font
|
font := font
|
||||||
font.Style = Regular
|
font.Style = Regular
|
||||||
tf = s.faces[font]
|
tf = c.faces[font]
|
||||||
}
|
}
|
||||||
if tf == nil {
|
if tf == nil {
|
||||||
font := font
|
font := font
|
||||||
font.Style = Regular
|
font.Style = Regular
|
||||||
font.Weight = Normal
|
font.Weight = Normal
|
||||||
tf = s.faces[font]
|
tf = c.faces[font]
|
||||||
}
|
}
|
||||||
return tf
|
return font, tf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FontRegistry) faceForFont(font Font) *face {
|
func NewCache(fonts *Collection) *Cache {
|
||||||
tf := s.faceForStyle(font)
|
return &Cache{
|
||||||
if tf == nil {
|
col: fonts,
|
||||||
font.Typeface = s.def
|
faces: make(map[Font]*faceCache),
|
||||||
tf = s.faceForStyle(font)
|
|
||||||
}
|
}
|
||||||
return tf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *face) layout(ppem fixed.Int26_6, maxWidth int, str string) []Line {
|
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 {
|
if t == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -127,12 +151,12 @@ func (t *face) layout(ppem fixed.Int26_6, maxWidth int, str string) []Line {
|
|||||||
if l, ok := t.layoutCache.Get(lk); ok {
|
if l, ok := t.layoutCache.Get(lk); ok {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
l, _ := t.face.Layout(ppem, maxWidth, strings.NewReader(str))
|
l, _ := face.Layout(ppem, maxWidth, strings.NewReader(str))
|
||||||
t.layoutCache.Put(lk, l)
|
t.layoutCache.Put(lk, l)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *face) shape(ppem fixed.Int26_6, str string, layout []Glyph) op.CallOp {
|
func (t *faceCache) shape(face Face, ppem fixed.Int26_6, str string, layout []Glyph) op.CallOp {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return op.CallOp{}
|
return op.CallOp{}
|
||||||
}
|
}
|
||||||
@@ -143,11 +167,11 @@ func (t *face) shape(ppem fixed.Int26_6, str string, layout []Glyph) op.CallOp {
|
|||||||
if clip, ok := t.pathCache.Get(pk); ok {
|
if clip, ok := t.pathCache.Get(pk); ok {
|
||||||
return clip
|
return clip
|
||||||
}
|
}
|
||||||
clip := t.face.Shape(ppem, layout)
|
clip := face.Shape(ppem, layout)
|
||||||
t.pathCache.Put(pk, clip)
|
t.pathCache.Put(pk, clip)
|
||||||
return clip
|
return clip
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *face) metrics(ppem fixed.Int26_6) font.Metrics {
|
func (t *faceCache) metrics(face Face, ppem fixed.Int26_6) font.Metrics {
|
||||||
return t.face.Metrics(ppem)
|
return face.Metrics(ppem)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"gioui.org/f32"
|
"gioui.org/f32"
|
||||||
"gioui.org/font"
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op/paint"
|
"gioui.org/op/paint"
|
||||||
"gioui.org/text"
|
"gioui.org/text"
|
||||||
@@ -30,9 +29,9 @@ type Theme struct {
|
|||||||
radioUncheckedIcon *widget.Icon
|
radioUncheckedIcon *widget.Icon
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTheme() *Theme {
|
func NewTheme(col *text.Collection) *Theme {
|
||||||
t := &Theme{
|
t := &Theme{
|
||||||
Shaper: font.Default(),
|
Shaper: text.NewCache(col),
|
||||||
}
|
}
|
||||||
t.Color.Primary = rgb(0x3f51b5)
|
t.Color.Primary = rgb(0x3f51b5)
|
||||||
t.Color.Text = rgb(0x000000)
|
t.Color.Text = rgb(0x000000)
|
||||||
|
|||||||
Reference in New Issue
Block a user