mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-04 00:45:35 +00:00
ccf24c0bd2
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>
153 lines
4.1 KiB
Go
153 lines
4.1 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
// Package opentype implements text layout and shaping for OpenType
|
|
// files.
|
|
//
|
|
// NOTE: the OpenType specification allows for fonts to include bitmap images
|
|
// in a variety of formats. In the interest of small binary sizes, the opentype
|
|
// package only automatically imports the PNG image decoder. If you have a font
|
|
// with glyphs in JPEG or TIFF formats, register those decoders with the image
|
|
// package in order to ensure those glyphs are visible in text.
|
|
package opentype
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
_ "image/png"
|
|
|
|
giofont "gioui.org/font"
|
|
"github.com/go-text/typesetting/font"
|
|
fontapi "github.com/go-text/typesetting/opentype/api/font"
|
|
"github.com/go-text/typesetting/opentype/api/metadata"
|
|
"github.com/go-text/typesetting/opentype/loader"
|
|
)
|
|
|
|
// 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.Font
|
|
aspect metadata.Aspect
|
|
family string
|
|
variant string
|
|
}
|
|
|
|
// Parse constructs a Face from source bytes.
|
|
func Parse(src []byte) (Face, error) {
|
|
ld, err := loader.NewLoader(bytes.NewReader(src))
|
|
if err != nil {
|
|
return Face{}, err
|
|
}
|
|
font, aspect, family, variant, 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,
|
|
}, nil
|
|
}
|
|
|
|
// ParseCollection parse an Opentype font file, with support for collections.
|
|
// Single font files are supported, returning a slice with length 1.
|
|
// The returned fonts are automatically wrapped in a text.FontFace with
|
|
// inferred font metadata.
|
|
// BUG(whereswaldon): the only Variant that can be detected automatically is
|
|
// "Mono".
|
|
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
|
|
lds, err := loader.NewLoaders(bytes.NewReader(src))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]giofont.FontFace, len(lds))
|
|
for i, ld := range lds {
|
|
face, aspect, family, variant, 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,
|
|
}
|
|
out[i] = giofont.FontFace{
|
|
Face: ff,
|
|
Font: ff.Font(),
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
// 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) {
|
|
ft, err := fontapi.NewFont(ld)
|
|
if err != nil {
|
|
return nil, metadata.Aspect{}, "", "", err
|
|
}
|
|
data := metadata.Metadata(ld)
|
|
if data.IsMonospace {
|
|
variant = "Mono"
|
|
}
|
|
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 &fontapi.Face{Font: f.face}
|
|
}
|
|
|
|
// FontFace returns a text.Font with populated font metadata for the
|
|
// font.
|
|
// 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),
|
|
}
|
|
}
|
|
|
|
func (f Face) style() giofont.Style {
|
|
switch f.aspect.Style {
|
|
case metadata.StyleItalic:
|
|
return giofont.Italic
|
|
case metadata.StyleNormal:
|
|
fallthrough
|
|
default:
|
|
return giofont.Regular
|
|
}
|
|
}
|
|
|
|
func (f Face) weight() giofont.Weight {
|
|
switch f.aspect.Weight {
|
|
case metadata.WeightThin:
|
|
return giofont.Thin
|
|
case metadata.WeightExtraLight:
|
|
return giofont.ExtraLight
|
|
case metadata.WeightLight:
|
|
return giofont.Light
|
|
case metadata.WeightNormal:
|
|
return giofont.Normal
|
|
case metadata.WeightMedium:
|
|
return giofont.Medium
|
|
case metadata.WeightSemibold:
|
|
return giofont.SemiBold
|
|
case metadata.WeightBold:
|
|
return giofont.Bold
|
|
case metadata.WeightExtraBold:
|
|
return giofont.ExtraBold
|
|
case metadata.WeightBlack:
|
|
return giofont.Black
|
|
default:
|
|
return giofont.Normal
|
|
}
|
|
}
|