From 15031d0b52e458f1b7ec509bb15dd342efe26783 Mon Sep 17 00:00:00 2001 From: Chris Waldon Date: Thu, 22 Jun 2023 15:00:22 -0400 Subject: [PATCH] font{,/{opentype,gofont}},text: [API] drop monospace font metadata In the general case, it isn't possible for us to efficiently find system fonts that are monospace. Fonts don't advertise being monospace frequently, so the only way to reliably detect it is to check that all glyphs are the same width. This is expensive, far too much so to be done on every system font when there may be thousands of them. Other font resolution systems rely upon the user requesting fonts by their family name. If you want a monospace font, ask for it by name or use a generic name like 'monospace'. This will be Gio's approach from here on out. Existing code relying upon setting Variant="Mono" should instead set Typeface="Go Mono" (for the Go font) or specify another monospace typeface. The generic face "monospace" will search for one of a set of known monospace fonts that may be available on the system. Similarly, smallcaps isn't well advertised and users should rely on requesting all-smallcaps fonts by typeface. To get the Go smallcaps font, use Typeface="Go Smallcaps". Signed-off-by: Chris Waldon --- font/font.go | 10 ++-- font/gofont/gofont.go | 33 ++++++------ font/opentype/opentype.go | 102 ++++++++++++++++++++++++++------------ text/gotext.go | 2 +- 4 files changed, 92 insertions(+), 55 deletions(-) diff --git a/font/font.go b/font/font.go index 9d00f35b..17628dfc 100644 --- a/font/font.go +++ b/font/font.go @@ -20,10 +20,11 @@ type Weight int // Font specify a particular typeface variant, style and weight. type Font struct { + // Typeface specifies the name of the family of faces. Typeface Typeface - Variant Variant - Style Style - // Weight is the text weight. If zero, Normal is used instead. + // Style specifies the kind of text style. + Style Style + // Weight is the text weight. Weight Weight } @@ -37,9 +38,6 @@ type Face interface { // string denotes the default typeface. type Typeface string -// Variant denotes a typeface variant such as "Mono" or "Smallcaps". -type Variant string - const ( Regular Style = iota Italic diff --git a/font/gofont/gofont.go b/font/gofont/gofont.go index 1e2818ad..835c0265 100644 --- a/font/gofont/gofont.go +++ b/font/gofont/gofont.go @@ -37,11 +37,11 @@ var ( func loadRegular() { regOnce.Do(func() { - face, err := opentype.Parse(goregular.TTF) + faces, err := opentype.ParseCollection(goregular.TTF) if err != nil { panic(fmt.Errorf("failed to parse font: %v", err)) } - reg = []font.FontFace{{Font: font.Font{Typeface: "Go"}, Face: face}} + reg = faces collection = append(collection, reg[0]) }) } @@ -56,17 +56,17 @@ func Regular() []font.FontFace { func Collection() []font.FontFace { loadRegular() once.Do(func() { - register(font.Font{Style: font.Italic}, goitalic.TTF) - register(font.Font{Weight: font.Bold}, gobold.TTF) - register(font.Font{Style: font.Italic, Weight: font.Bold}, gobolditalic.TTF) - register(font.Font{Weight: font.Medium}, gomedium.TTF) - register(font.Font{Weight: font.Medium, Style: font.Italic}, gomediumitalic.TTF) - register(font.Font{Variant: "Mono"}, gomono.TTF) - register(font.Font{Variant: "Mono", Weight: font.Bold}, gomonobold.TTF) - register(font.Font{Variant: "Mono", Weight: font.Bold, Style: font.Italic}, gomonobolditalic.TTF) - register(font.Font{Variant: "Mono", Style: font.Italic}, gomonoitalic.TTF) - register(font.Font{Variant: "Smallcaps"}, gosmallcaps.TTF) - register(font.Font{Variant: "Smallcaps", Style: font.Italic}, gosmallcapsitalic.TTF) + register(goitalic.TTF) + register(gobold.TTF) + register(gobolditalic.TTF) + register(gomedium.TTF) + register(gomediumitalic.TTF) + register(gomono.TTF) + register(gomonobold.TTF) + register(gomonobolditalic.TTF) + register(gomonoitalic.TTF) + register(gosmallcaps.TTF) + register(gosmallcapsitalic.TTF) // Ensure that any outside appends will not reuse the backing store. n := len(collection) collection = collection[:n:n] @@ -74,11 +74,10 @@ func Collection() []font.FontFace { return collection } -func register(fnt font.Font, ttf []byte) { - face, err := opentype.Parse(ttf) +func register(ttf []byte) { + faces, err := opentype.ParseCollection(ttf) if err != nil { panic(fmt.Errorf("failed to parse font: %v", err)) } - fnt.Typeface = "Go" - collection = append(collection, font.FontFace{Font: fnt, Face: face}) + collection = append(collection, faces[0]) } diff --git a/font/opentype/opentype.go b/font/opentype/opentype.go index e5404c7f..0e51ee3d 100644 --- a/font/opentype/opentype.go +++ b/font/opentype/opentype.go @@ -26,10 +26,8 @@ import ( // should construct a face for any given font file once, reusing it across different // text shapers. type Face struct { - face font.Font - aspect metadata.Aspect - family string - variant string + face font.Font + font giofont.Font } // Parse constructs a Face from source bytes. @@ -38,15 +36,13 @@ func Parse(src []byte) (Face, error) { if err != nil { return Face{}, err } - font, aspect, family, variant, err := parseLoader(ld) + font, md, err := parseLoader(ld) if err != nil { return Face{}, fmt.Errorf("failed parsing truetype font: %w", err) } return Face{ - face: font, - aspect: aspect, - family: family, - variant: variant, + face: font, + font: md, }, nil } @@ -63,15 +59,13 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) { } out := make([]giofont.FontFace, len(lds)) for i, ld := range lds { - face, aspect, family, variant, err := parseLoader(ld) + face, md, err := parseLoader(ld) if err != nil { return nil, fmt.Errorf("reading font %d of collection: %s", i, err) } ff := Face{ - face: face, - aspect: aspect, - family: family, - variant: variant, + face: face, + font: md, } out[i] = giofont.FontFace{ Face: ff, @@ -82,17 +76,32 @@ func ParseCollection(src []byte) ([]giofont.FontFace, error) { return out, nil } +func DescriptionToFont(md metadata.Description) giofont.Font { + return giofont.Font{ + Typeface: giofont.Typeface(md.Family), + Style: gioStyle(md.Aspect.Style), + Weight: gioWeight(md.Aspect.Weight), + } +} + +func FontToDescription(font giofont.Font) metadata.Description { + return metadata.Description{ + Family: string(font.Typeface), + Aspect: metadata.Aspect{ + Style: mdStyle(font.Style), + Weight: mdWeight(font.Weight), + }, + } +} + // parseLoader parses the contents of the loader into a face and its metadata. -func parseLoader(ld *loader.Loader) (_ font.Font, _ metadata.Aspect, family, variant string, _ error) { +func parseLoader(ld *loader.Loader) (font.Font, giofont.Font, error) { ft, err := fontapi.NewFont(ld) if err != nil { - return nil, metadata.Aspect{}, "", "", err + return nil, giofont.Font{}, err } - data := metadata.Metadata(ld) - if data.IsMonospace { - variant = "Mono" - } - return ft, data.Aspect, data.Family, variant, nil + data := DescriptionToFont(metadata.Metadata(ld)) + return ft, data, nil } // Face returns a thread-unsafe wrapper for this Face suitable for use by a single shaper. @@ -107,16 +116,11 @@ func (f Face) Face() font.Face { // BUG(whereswaldon): the only Variant that can be detected automatically is // "Mono". func (f Face) Font() giofont.Font { - return giofont.Font{ - Typeface: giofont.Typeface(f.family), - Style: f.style(), - Weight: f.weight(), - Variant: giofont.Variant(f.variant), - } + return f.font } -func (f Face) style() giofont.Style { - switch f.aspect.Style { +func gioStyle(s metadata.Style) giofont.Style { + switch s { case metadata.StyleItalic: return giofont.Italic case metadata.StyleNormal: @@ -126,8 +130,19 @@ func (f Face) style() giofont.Style { } } -func (f Face) weight() giofont.Weight { - switch f.aspect.Weight { +func mdStyle(g giofont.Style) metadata.Style { + switch g { + case giofont.Italic: + return metadata.StyleItalic + case giofont.Regular: + fallthrough + default: + return metadata.StyleNormal + } +} + +func gioWeight(w metadata.Weight) giofont.Weight { + switch w { case metadata.WeightThin: return giofont.Thin case metadata.WeightExtraLight: @@ -150,3 +165,28 @@ func (f Face) weight() giofont.Weight { return giofont.Normal } } + +func mdWeight(g giofont.Weight) metadata.Weight { + switch g { + case giofont.Thin: + return metadata.WeightThin + case giofont.ExtraLight: + return metadata.WeightExtraLight + case giofont.Light: + return metadata.WeightLight + case giofont.Normal: + return metadata.WeightNormal + case giofont.Medium: + return metadata.WeightMedium + case giofont.SemiBold: + return metadata.WeightSemibold + case giofont.Bold: + return metadata.WeightBold + case giofont.ExtraBold: + return metadata.WeightExtraBold + case giofont.Black: + return metadata.WeightBlack + default: + return metadata.WeightNormal + } +} diff --git a/text/gotext.go b/text/gotext.go index 538a5ec0..a358ea8d 100644 --- a/text/gotext.go +++ b/text/gotext.go @@ -895,7 +895,7 @@ func closestFont(lookup giofont.Font, available []giofont.Font) (giofont.Font, b if cf == lookup { return lookup, true } - if cf.Typeface != lookup.Typeface || cf.Variant != lookup.Variant || cf.Style != lookup.Style { + if cf.Typeface != lookup.Typeface || cf.Style != lookup.Style { continue } if !found {