package font
import (
"errors"
"fmt"
"sync"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"golang.org/x/image/font/sfnt"
"golang.org/x/image/math/fixed"
)
var DefaultCache *Cache = NewCache (nil )
type Font struct {
Typeface Typeface
Variant Variant
Style font .Style
Weight font .Weight
Size Length
}
func (f *Font ) Name () string {
v := f .Variant
w := weightName (f .Weight )
s := styleName (f .Style )
switch f .Style {
case font .StyleNormal :
s = ""
if f .Weight == font .WeightNormal {
w = "Regular"
}
default :
if f .Weight == font .WeightNormal {
w = ""
}
}
return fmt .Sprintf ("%s%s-%s%s" , f .Typeface , v , w , s )
}
func From (fnt Font , size Length ) Font {
o := fnt
o .Size = size
return o
}
type Typeface string
type Variant string
type Extents struct {
Ascent Length
Descent Length
Height Length
}
type Face struct {
Font Font
Face *opentype .Font
}
func (f *Face ) Name () string {
return f .Font .Name ()
}
func (f *Face ) FontFace (dpi float64 ) font .Face {
face , err := opentype .NewFace (f .Face , &opentype .FaceOptions {
Size : f .Font .Size .Points (),
DPI : dpi ,
})
if err != nil {
panic (err )
}
return face
}
const defaultHinting = font .HintingNone
func (f *Face ) Extents () Extents {
var (
buf sfnt .Buffer
ppem = fixed .Int26_6 (f .Face .UnitsPerEm ())
)
met , err := f .Face .Metrics (&buf , ppem , defaultHinting )
if err != nil {
panic (fmt .Errorf ("could not extract font extents: %v" , err ))
}
scale := f .Font .Size / Points (float64 (ppem ))
return Extents {
Ascent : Points (float64 (met .Ascent )) * scale ,
Descent : Points (float64 (met .Descent )) * scale ,
Height : Points (float64 (met .Height )) * scale ,
}
}
func (f *Face ) Width (s string ) Length {
var (
pixelsPerEm = fixed .Int26_6 (f .Face .UnitsPerEm ())
scale = f .Font .Size / Points (float64 (pixelsPerEm ))
width = 0
hasPrev = false
buf sfnt .Buffer
prev , idx sfnt .GlyphIndex
hinting = defaultHinting
)
for _ , rune := range s {
var err error
idx , err = f .Face .GlyphIndex (&buf , rune )
if err != nil {
panic (fmt .Errorf ("could not get glyph index: %v" , err ))
}
if hasPrev {
kern , err := f .Face .Kern (&buf , prev , idx , pixelsPerEm , hinting )
switch {
case err == nil :
width += int (kern )
case errors .Is (err , sfnt .ErrNotFound ):
default :
panic (fmt .Errorf ("could not get kerning: %v" , err ))
}
}
adv , err := f .Face .GlyphAdvance (&buf , idx , pixelsPerEm , hinting )
if err != nil {
panic (fmt .Errorf ("could not retrieve glyph's advance: %v" , err ))
}
width += int (adv )
prev , hasPrev = idx , true
}
return Points (float64 (width )) * scale
}
type Collection []Face
type Cache struct {
mu sync .RWMutex
def Typeface
faces map [Font ]*opentype .Font
}
func (c *Cache ) GobEncode () ([]byte , error ) { return nil , nil }
func (c *Cache ) GobDecode ([]byte ) error {
if c .faces == nil {
c .faces = make (map [Font ]*opentype .Font )
}
return nil
}
func NewCache (coll Collection ) *Cache {
cache := &Cache {
faces : make (map [Font ]*opentype .Font , len (coll )),
}
cache .Add (coll )
return cache
}
func (c *Cache ) Add (coll Collection ) {
c .mu .Lock ()
defer c .mu .Unlock ()
if c .faces == nil {
c .faces = make (map [Font ]*opentype .Font , len (coll ))
}
for i , f := range coll {
if i == 0 && c .def == "" {
c .def = f .Font .Typeface
}
fnt := f .Font
fnt .Size = 0
c .faces [fnt ] = f .Face
}
}
func (c *Cache ) Lookup (fnt Font , size Length ) Face {
c .mu .RLock ()
defer c .mu .RUnlock ()
if len (c .faces ) == 0 {
return Face {}
}
face := c .lookup (fnt )
if face == nil {
fnt .Typeface = c .def
face = c .lookup (fnt )
}
ff := Face {
Font : fnt ,
Face : face ,
}
ff .Font .Size = size
return ff
}
func (c *Cache ) Has (fnt Font ) bool {
c .mu .RLock ()
defer c .mu .RUnlock ()
face := c .lookup (fnt )
return face != nil
}
func (c *Cache ) lookup (key Font ) *opentype .Font {
key .Size = 0
tf := c .faces [key ]
if tf == nil {
key := key
key .Weight = font .WeightNormal
tf = c .faces [key ]
}
if tf == nil {
key := key
key .Style = font .StyleNormal
tf = c .faces [key ]
}
if tf == nil {
key := key
key .Style = font .StyleNormal
key .Weight = font .WeightNormal
tf = c .faces [key ]
}
return tf
}
func weightName(w font .Weight ) string {
switch w {
case font .WeightThin :
return "Thin"
case font .WeightExtraLight :
return "ExtraLight"
case font .WeightLight :
return "Light"
case font .WeightNormal :
return "Regular"
case font .WeightMedium :
return "Medium"
case font .WeightSemiBold :
return "SemiBold"
case font .WeightBold :
return "Bold"
case font .WeightExtraBold :
return "ExtraBold"
case font .WeightBlack :
return "Black"
}
return fmt .Sprintf ("weight(%d)" , w )
}
func styleName(sty font .Style ) string {
switch sty {
case font .StyleNormal :
return "Normal"
case font .StyleItalic :
return "Italic"
case font .StyleOblique :
return "Oblique"
}
return fmt .Sprintf ("style(%d)" , sty )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .