From ccf24c0bd2c1e5575f54bd53623be993322ba6a3 Mon Sep 17 00:00:00 2001 From: Chris Waldon Date: Thu, 20 Apr 2023 10:16:16 -0400 Subject: [PATCH] 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 --- font/opentype/opentype.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/font/opentype/opentype.go b/font/opentype/opentype.go index 43c8335a..e5404c7f 100644 --- a/font/opentype/opentype.go +++ b/font/opentype/opentype.go @@ -22,9 +22,11 @@ import ( "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 { - face font.Face + face font.Font aspect metadata.Aspect family string variant string @@ -36,12 +38,12 @@ func Parse(src []byte) (Face, error) { if err != nil { return Face{}, err } - face, aspect, family, variant, err := parseLoader(ld) + font, aspect, family, variant, err := parseLoader(ld) if err != nil { return Face{}, fmt.Errorf("failed parsing truetype font: %w", err) } return Face{ - face: face, + face: font, aspect: aspect, family: family, 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. -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) if err != nil { return nil, metadata.Aspect{}, "", "", err @@ -90,11 +92,14 @@ func parseLoader(ld *loader.Loader) (_ font.Face, _ metadata.Aspect, family, var if data.IsMonospace { 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 { - return f.face + return &fontapi.Face{Font: f.face} } // FontFace returns a text.Font with populated font metadata for the -- 2.38.5