font/opentype: make reusing font.Face efficient and safe

This commit updates the internal representation of a font to separate the
threadsafe and non-threadsafe operations in a way that enures font.Faces can
be shared by all text shapers in an application. This should ensure that applications
only need to parse fonts a single time, saving a great deal of memory for
applications that open many windows (which each need a different text shaper).

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
Chris Waldon
2023-04-20 10:16:16 -04:00
committed by Elias Naur
parent a7c9ca99f3
commit ccf24c0bd2
+12 -7
View File
@@ -22,9 +22,11 @@ import (
"github.com/go-text/typesetting/opentype/loader" "github.com/go-text/typesetting/opentype/loader"
) )
// Face is a shapeable representation of a font. // Face is a thread-safe representation of a loaded font. For efficiency, applications
// should construct a face for any given font file once, reusing it across different
// text shapers.
type Face struct { type Face struct {
face font.Face face font.Font
aspect metadata.Aspect aspect metadata.Aspect
family string family string
variant string variant string
@@ -36,12 +38,12 @@ func Parse(src []byte) (Face, error) {
if err != nil { if err != nil {
return Face{}, err return Face{}, err
} }
face, aspect, family, variant, err := parseLoader(ld) font, aspect, family, variant, err := parseLoader(ld)
if err != nil { if err != nil {
return Face{}, fmt.Errorf("failed parsing truetype font: %w", err) return Face{}, fmt.Errorf("failed parsing truetype font: %w", err)
} }
return Face{ return Face{
face: face, face: font,
aspect: aspect, aspect: aspect,
family: family, family: family,
variant: variant, variant: variant,
@@ -81,7 +83,7 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) {
} }
// parseLoader parses the contents of the loader into a face and its metadata. // parseLoader parses the contents of the loader into a face and its metadata.
func parseLoader(ld *loader.Loader) (_ font.Face, _ metadata.Aspect, family, variant string, _ error) { func parseLoader(ld *loader.Loader) (_ font.Font, _ metadata.Aspect, family, variant string, _ error) {
ft, err := fontapi.NewFont(ld) ft, err := fontapi.NewFont(ld)
if err != nil { if err != nil {
return nil, metadata.Aspect{}, "", "", err return nil, metadata.Aspect{}, "", "", err
@@ -90,11 +92,14 @@ func parseLoader(ld *loader.Loader) (_ font.Face, _ metadata.Aspect, family, var
if data.IsMonospace { if data.IsMonospace {
variant = "Mono" variant = "Mono"
} }
return &fontapi.Face{Font: ft}, data.Aspect, data.Family, variant, nil return ft, data.Aspect, data.Family, variant, nil
} }
// Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper.
// Face many be invoked any number of times and is safe so long as each return value is
// only used by one goroutine.
func (f Face) Face() font.Face { func (f Face) Face() font.Face {
return f.face return &fontapi.Face{Font: f.face}
} }
// FontFace returns a text.Font with populated font metadata for the // FontFace returns a text.Font with populated font metadata for the