text,font/gofont: replace text.Collection with slice of FontFaces

A slice of FontFace pairs are simpler, and thread safe in case a client
wants to append or modify the font collection.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2020-06-21 21:41:20 +02:00
parent 424a728988
commit 9e3d3b6f58
3 changed files with 70 additions and 83 deletions
+21 -20
View File
@@ -29,35 +29,36 @@ import (
var ( var (
once sync.Once once sync.Once
collection *text.Collection collection []text.FontFace
) )
func Collection() *text.Collection { func Collection() []text.FontFace {
once.Do(func() { once.Do(func() {
c := new(text.Collection) register(text.Font{}, goregular.TTF)
register(c, text.Font{}, goregular.TTF) register(text.Font{Style: text.Italic}, goitalic.TTF)
register(c, text.Font{Style: text.Italic}, goitalic.TTF) register(text.Font{Weight: text.Bold}, gobold.TTF)
register(c, text.Font{Weight: text.Bold}, gobold.TTF) register(text.Font{Style: text.Italic, Weight: text.Bold}, gobolditalic.TTF)
register(c, text.Font{Style: text.Italic, Weight: text.Bold}, gobolditalic.TTF) register(text.Font{Weight: text.Medium}, gomedium.TTF)
register(c, text.Font{Weight: text.Medium}, gomedium.TTF) register(text.Font{Weight: text.Medium, Style: text.Italic}, gomediumitalic.TTF)
register(c, text.Font{Weight: text.Medium, Style: text.Italic}, gomediumitalic.TTF) register(text.Font{Variant: "Mono"}, gomono.TTF)
register(c, text.Font{Variant: "Mono"}, gomono.TTF) register(text.Font{Variant: "Mono", Weight: text.Bold}, gomonobold.TTF)
register(c, text.Font{Variant: "Mono", Weight: text.Bold}, gomonobold.TTF) register(text.Font{Variant: "Mono", Weight: text.Bold, Style: text.Italic}, gomonobolditalic.TTF)
register(c, text.Font{Variant: "Mono", Weight: text.Bold, Style: text.Italic}, gomonobolditalic.TTF) register(text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF)
register(c, text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF) register(text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF)
register(c, text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF) register(text.Font{Variant: "Smallcaps"}, gosmallcaps.TTF)
register(c, text.Font{Variant: "Smallcaps"}, gosmallcaps.TTF) register(text.Font{Variant: "Smallcaps", Style: text.Italic}, gosmallcapsitalic.TTF)
register(c, text.Font{Variant: "Smallcaps", Style: text.Italic}, gosmallcapsitalic.TTF) // Ensure that any outside appends will not reuse the backing store.
collection = c n := len(collection)
collection = collection[:n:n]
}) })
return collection return collection
} }
func register(c *text.Collection, fnt text.Font, ttf []byte) { func register(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.Errorf("failed to parse font: %v", err))
} }
fnt.Typeface = "Go" fnt.Typeface = "Go"
c.Register(fnt, face) collection = append(collection, text.FontFace{Font: fnt, Face: face})
} }
+47 -61
View File
@@ -28,10 +28,10 @@ type Shaper interface {
Metrics(font Font, size fixed.Int26_6) font.Metrics Metrics(font Font, size fixed.Int26_6) font.Metrics
} }
// Collection maps Fonts to Faces. // A FontFace is a Font and a matching Face.
type Collection struct { type FontFace struct {
def Typeface Font Font
faces map[Font]Face Face Face
} }
// Cache implements cached layout and shaping of text from a set of // Cache implements cached layout and shaping of text from a set of
@@ -43,39 +43,27 @@ type Collection struct {
// 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 Cache struct { type Cache struct {
col *Collection def Typeface
faces map[Font]*faceCache faces map[Font]*faceCache
} }
type faceCache struct { type faceCache struct {
face Face
layoutCache layoutCache layoutCache layoutCache
pathCache pathCache pathCache pathCache
} }
func (c *Collection) Register(font Font, tf Face) { func (c *Cache) lookup(font Font) *faceCache {
if c.faces == nil { var f *faceCache
c.def = font.Typeface f = c.faceForStyle(font)
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 { if f == nil {
font.Typeface = c.def font.Typeface = c.def
font, f = c.faceForStyle(font) f = c.faceForStyle(font)
} }
return font, f return f
} }
func (c *Collection) faceForStyle(font Font) (Font, Face) { func (c *Cache) faceForStyle(font Font) *faceCache {
tf := c.faces[font] tf := c.faces[font]
if tf == nil { if tf == nil {
font := font font := font
@@ -93,54 +81,52 @@ func (c *Collection) faceForStyle(font Font) (Font, Face) {
font.Weight = Normal font.Weight = Normal
tf = c.faces[font] tf = c.faces[font]
} }
return font, tf return tf
} }
func NewCache(fonts *Collection) *Cache { func NewCache(collection []FontFace) *Cache {
return &Cache{ c := &Cache{
col: fonts,
faces: make(map[Font]*faceCache), faces: make(map[Font]*faceCache),
} }
for i, ff := range collection {
if ff.Font.Weight == 0 {
ff.Font.Weight = Normal
}
if i == 0 {
c.def = ff.Font.Typeface
}
c.faces[ff.Font] = &faceCache{face: ff.Face}
}
return c
} }
func (s *Cache) Layout(font Font, size fixed.Int26_6, maxWidth int, txt io.Reader) ([]Line, error) { func (s *Cache) Layout(font Font, size fixed.Int26_6, maxWidth int, txt io.Reader) ([]Line, error) {
_, face := s.faceForFont(font) cache := s.lookup(font)
return face.Layout(size, maxWidth, txt) return cache.face.Layout(size, maxWidth, txt)
} }
func (s *Cache) Shape(font Font, size fixed.Int26_6, layout []Glyph) op.CallOp { func (s *Cache) Shape(font Font, size fixed.Int26_6, layout []Glyph) op.CallOp {
_, face := s.faceForFont(font) cache := s.lookup(font)
return face.Shape(size, layout) return cache.face.Shape(size, layout)
} }
func (s *Cache) LayoutString(font Font, size fixed.Int26_6, maxWidth int, str string) []Line { func (s *Cache) LayoutString(font Font, size fixed.Int26_6, maxWidth int, str string) []Line {
cache, face := s.faceForFont(font) cache := s.lookup(font)
return cache.layout(face, size, maxWidth, str) return cache.layout(size, maxWidth, str)
} }
func (s *Cache) ShapeString(font Font, size fixed.Int26_6, str string, layout []Glyph) op.CallOp { func (s *Cache) ShapeString(font Font, size fixed.Int26_6, str string, layout []Glyph) op.CallOp {
cache, face := s.faceForFont(font) cache := s.lookup(font)
return cache.shape(face, size, str, layout) return cache.shape(size, str, layout)
} }
func (s *Cache) Metrics(font Font, size fixed.Int26_6) font.Metrics { func (s *Cache) Metrics(font Font, size fixed.Int26_6) font.Metrics {
cache, face := s.faceForFont(font) cache := s.lookup(font)
return cache.metrics(face, size) return cache.metrics(size)
} }
func (s *Cache) faceForFont(font Font) (*faceCache, Face) { func (f *faceCache) layout(ppem fixed.Int26_6, maxWidth int, str string) []Line {
var f Face if f == nil {
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 return nil
} }
lk := layoutKey{ lk := layoutKey{
@@ -148,30 +134,30 @@ func (t *faceCache) layout(face Face, ppem fixed.Int26_6, maxWidth int, str stri
maxWidth: maxWidth, maxWidth: maxWidth,
str: str, str: str,
} }
if l, ok := t.layoutCache.Get(lk); ok { if l, ok := f.layoutCache.Get(lk); ok {
return l return l
} }
l, _ := face.Layout(ppem, maxWidth, strings.NewReader(str)) l, _ := f.face.Layout(ppem, maxWidth, strings.NewReader(str))
t.layoutCache.Put(lk, l) f.layoutCache.Put(lk, l)
return l return l
} }
func (t *faceCache) shape(face Face, ppem fixed.Int26_6, str string, layout []Glyph) op.CallOp { func (f *faceCache) shape(ppem fixed.Int26_6, str string, layout []Glyph) op.CallOp {
if t == nil { if f == nil {
return op.CallOp{} return op.CallOp{}
} }
pk := pathKey{ pk := pathKey{
ppem: ppem, ppem: ppem,
str: str, str: str,
} }
if clip, ok := t.pathCache.Get(pk); ok { if clip, ok := f.pathCache.Get(pk); ok {
return clip return clip
} }
clip := face.Shape(ppem, layout) clip := f.face.Shape(ppem, layout)
t.pathCache.Put(pk, clip) f.pathCache.Put(pk, clip)
return clip return clip
} }
func (t *faceCache) metrics(face Face, ppem fixed.Int26_6) font.Metrics { func (f *faceCache) metrics(ppem fixed.Int26_6) font.Metrics {
return face.Metrics(ppem) return f.face.Metrics(ppem)
} }
+2 -2
View File
@@ -29,9 +29,9 @@ type Theme struct {
radioUncheckedIcon *widget.Icon radioUncheckedIcon *widget.Icon
} }
func NewTheme(col *text.Collection) *Theme { func NewTheme(fontCollection []text.FontFace) *Theme {
t := &Theme{ t := &Theme{
Shaper: text.NewCache(col), Shaper: text.NewCache(fontCollection),
} }
t.Color.Primary = rgb(0x3f51b5) t.Color.Primary = rgb(0x3f51b5)
t.Color.Text = rgb(0x000000) t.Color.Text = rgb(0x000000)