diff --git a/font/font.go b/font/font.go deleted file mode 100644 index 0b90251d..00000000 --- a/font/font.go +++ /dev/null @@ -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) -} diff --git a/font/gofont/gofont.go b/font/gofont/gofont.go index 5b5ad7b6..6179c75b 100644 --- a/font/gofont/gofont.go +++ b/font/gofont/gofont.go @@ -1,6 +1,6 @@ // 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 // fonts, and the golang.org/x/image/font/gofont packages for the @@ -9,8 +9,8 @@ package gofont import ( "fmt" + "sync" - "gioui.org/font" "gioui.org/font/opentype" "gioui.org/text" "golang.org/x/image/font/gofont/gobold" @@ -27,27 +27,37 @@ import ( "golang.org/x/image/font/gofont/gosmallcapsitalic" ) -func Register() { - register(text.Font{}, goregular.TTF) - register(text.Font{Style: text.Italic}, goitalic.TTF) - 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) - register(text.Font{Weight: text.Medium, Style: text.Italic}, gomediumitalic.TTF) - register(text.Font{Variant: "Mono"}, gomono.TTF) - register(text.Font{Variant: "Mono", Weight: text.Bold}, gomonobold.TTF) - register(text.Font{Variant: "Mono", Weight: text.Bold, Style: text.Italic}, gomonobolditalic.TTF) - register(text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF) - register(text.Font{Variant: "Mono", Style: text.Italic}, gomonoitalic.TTF) - register(text.Font{Variant: "Smallcaps"}, gosmallcaps.TTF) - register(text.Font{Variant: "Smallcaps", Style: text.Italic}, gosmallcapsitalic.TTF) +var ( + once sync.Once + collection *text.Collection +) + +func Collection() *text.Collection { + once.Do(func() { + c := new(text.Collection) + register(c, text.Font{}, goregular.TTF) + register(c, text.Font{Style: text.Italic}, goitalic.TTF) + register(c, text.Font{Weight: text.Bold}, gobold.TTF) + register(c, text.Font{Style: text.Italic, Weight: text.Bold}, gobolditalic.TTF) + register(c, text.Font{Weight: text.Medium}, gomedium.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) if err != nil { panic(fmt.Sprintf("failed to parse font: %v", err)) } fnt.Typeface = "Go" - font.Register(fnt, face) + c.Register(fnt, face) } diff --git a/text/shaper.go b/text/shaper.go index 99631a2e..87b04b91 100644 --- a/text/shaper.go +++ b/text/shaper.go @@ -28,94 +28,118 @@ type Shaper interface { 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. // -// 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. // // The LayoutString and ShapeString results are cached and re-used if // possible. -type FontRegistry struct { - def Typeface - faces map[Font]*face +type Cache struct { + col *Collection + faces map[Font]*faceCache } -type face struct { - face Face +type faceCache struct { 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) +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 } - s.faces[font] = &face{ - face: tf, + 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 (s *FontRegistry) Layout(font Font, size fixed.Int26_6, maxWidth int, txt io.Reader) ([]Line, error) { - tf := s.faceForFont(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] +func (c *Collection) faceForStyle(font Font) (Font, Face) { + tf := c.faces[font] if tf == nil { font := font font.Weight = Normal - tf = s.faces[font] + tf = c.faces[font] } if tf == nil { font := font font.Style = Regular - tf = s.faces[font] + tf = c.faces[font] } if tf == nil { font := font font.Style = Regular font.Weight = Normal - tf = s.faces[font] + tf = c.faces[font] } - return tf + return font, tf } -func (s *FontRegistry) faceForFont(font Font) *face { - tf := s.faceForStyle(font) - if tf == nil { - font.Typeface = s.def - tf = s.faceForStyle(font) +func NewCache(fonts *Collection) *Cache { + return &Cache{ + col: fonts, + faces: make(map[Font]*faceCache), } - 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 { 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 { return l } - l, _ := t.face.Layout(ppem, maxWidth, strings.NewReader(str)) + l, _ := face.Layout(ppem, maxWidth, strings.NewReader(str)) t.layoutCache.Put(lk, 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 { 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 { return clip } - clip := t.face.Shape(ppem, layout) + clip := 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) +func (t *faceCache) metrics(face Face, ppem fixed.Int26_6) font.Metrics { + return face.Metrics(ppem) } diff --git a/widget/material/theme.go b/widget/material/theme.go index 160dfbd4..72e6de75 100644 --- a/widget/material/theme.go +++ b/widget/material/theme.go @@ -6,7 +6,6 @@ import ( "image/color" "gioui.org/f32" - "gioui.org/font" "gioui.org/layout" "gioui.org/op/paint" "gioui.org/text" @@ -30,9 +29,9 @@ type Theme struct { radioUncheckedIcon *widget.Icon } -func NewTheme() *Theme { +func NewTheme(col *text.Collection) *Theme { t := &Theme{ - Shaper: font.Default(), + Shaper: text.NewCache(col), } t.Color.Primary = rgb(0x3f51b5) t.Color.Text = rgb(0x000000)