Files
gio/font/opentype/opentype.go
T
Egon Elbre e8c1e1ba11 font/opentype: fix font.Face creation
typesetting introduced a cache field that needs to be
properly initialized. Use constructor to avoid the issue.

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2026-05-18 14:30:12 -04:00

191 lines
4.8 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"
fontapi "github.com/go-text/typesetting/font"
"github.com/go-text/typesetting/font/opentype"
)
// 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 *fontapi.Font
font giofont.Font
}
// Parse constructs a Face from source bytes.
func Parse(src []byte) (Face, error) {
ld, err := opentype.NewLoader(bytes.NewReader(src))
if err != nil {
return Face{}, err
}
font, md, err := parseLoader(ld)
if err != nil {
return Face{}, fmt.Errorf("failed parsing truetype font: %w", err)
}
return Face{
face: font,
font: md,
}, 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 font.
// BUG(whereswaldon): the only Variant that can be detected automatically is
// "Mono".
func ParseCollection(src []byte) ([]giofont.FontFace, error) {
lds, err := opentype.NewLoaders(bytes.NewReader(src))
if err != nil {
return nil, err
}
out := make([]giofont.FontFace, len(lds))
for i, ld := range lds {
face, md, err := parseLoader(ld)
if err != nil {
return nil, fmt.Errorf("reading font %d of collection: %s", i, err)
}
ff := Face{
face: face,
font: md,
}
out[i] = giofont.FontFace{
Face: ff,
Font: ff.Font(),
}
}
return out, nil
}
func DescriptionToFont(md fontapi.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) fontapi.Description {
return fontapi.Description{
Family: string(font.Typeface),
Aspect: fontapi.Aspect{
Style: mdStyle(font.Style),
Weight: mdWeight(font.Weight),
},
}
}
// parseLoader parses the contents of the loader into a face and its font.
func parseLoader(ld *opentype.Loader) (*fontapi.Font, giofont.Font, error) {
ft, err := fontapi.NewFont(ld)
if err != nil {
return nil, giofont.Font{}, err
}
data := DescriptionToFont(ft.Describe())
return ft, data, 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() *fontapi.Face {
return fontapi.NewFace(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 f.font
}
func gioStyle(s fontapi.Style) giofont.Style {
switch s {
case fontapi.StyleItalic:
return giofont.Italic
case fontapi.StyleNormal:
fallthrough
default:
return giofont.Regular
}
}
func mdStyle(g giofont.Style) fontapi.Style {
switch g {
case giofont.Italic:
return fontapi.StyleItalic
case giofont.Regular:
fallthrough
default:
return fontapi.StyleNormal
}
}
func gioWeight(w fontapi.Weight) giofont.Weight {
switch w {
case fontapi.WeightThin:
return giofont.Thin
case fontapi.WeightExtraLight:
return giofont.ExtraLight
case fontapi.WeightLight:
return giofont.Light
case fontapi.WeightNormal:
return giofont.Normal
case fontapi.WeightMedium:
return giofont.Medium
case fontapi.WeightSemibold:
return giofont.SemiBold
case fontapi.WeightBold:
return giofont.Bold
case fontapi.WeightExtraBold:
return giofont.ExtraBold
case fontapi.WeightBlack:
return giofont.Black
default:
return giofont.Normal
}
}
func mdWeight(g giofont.Weight) fontapi.Weight {
switch g {
case giofont.Thin:
return fontapi.WeightThin
case giofont.ExtraLight:
return fontapi.WeightExtraLight
case giofont.Light:
return fontapi.WeightLight
case giofont.Normal:
return fontapi.WeightNormal
case giofont.Medium:
return fontapi.WeightMedium
case giofont.SemiBold:
return fontapi.WeightSemibold
case giofont.Bold:
return fontapi.WeightBold
case giofont.ExtraBold:
return fontapi.WeightExtraBold
case giofont.Black:
return fontapi.WeightBlack
default:
return fontapi.WeightNormal
}
}