erm/internal/app/darktile/font/manager.go
2021-07-30 23:29:20 +01:00

258 lines
4.7 KiB
Go

package font
import (
"fmt"
"image"
"math"
"os"
"github.com/liamg/darktile/internal/app/darktile/packed"
"github.com/liamg/fontinfo"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
)
type Style uint8
const (
Regular Style = iota
Bold
Italic
BoldItalic
)
type StyleName string
const (
StyleRegular StyleName = "Regular"
StyleBold StyleName = "Bold"
StyleItalic StyleName = "Italic"
StyleBoldItalic StyleName = "Bold Italic"
)
type Manager struct {
family string
regularFace font.Face
boldFace font.Face
italicFace font.Face
boldItalicFace font.Face
size float64
dpi float64
charSize image.Point
fontDotDepth int
}
func NewManager() *Manager {
return &Manager{
size: 16,
dpi: 72,
}
}
func (m *Manager) CharSize() image.Point {
return m.charSize
}
func (m *Manager) IncreaseSize() {
m.SetSize(m.size + 1)
}
func (m *Manager) DecreaseSize() {
if m.size < 2 {
return
}
m.SetSize(m.size - 1)
}
func (m *Manager) DotDepth() int {
return m.fontDotDepth
}
func (m *Manager) DPI() float64 {
return m.dpi
}
func (m *Manager) SetDPI(dpi float64) error {
if dpi <= 0 {
return fmt.Errorf("DPI must be >0")
}
m.dpi = dpi
return nil
}
func (m *Manager) SetSize(size float64) error {
m.size = size
if m.regularFace != nil {
// effectively reload fonts at new size
m.SetFontByFamilyName(m.family)
}
return nil
}
func (m *Manager) loadFontFace(path string) (font.Face, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
fnt, err := opentype.ParseReaderAt(f)
if err != nil {
return nil, err
}
return m.createFace(fnt)
}
func (m *Manager) createFace(f *opentype.Font) (font.Face, error) {
return opentype.NewFace(f, &opentype.FaceOptions{
Size: m.size,
DPI: m.dpi,
Hinting: font.HintingFull,
})
}
func (m *Manager) SetFontByFamilyName(name string) error {
m.family = name
if name == "" {
return m.loadDefaultFonts()
}
fonts, err := fontinfo.Match(fontinfo.MatchFamily(name))
if err != nil {
return err
}
if len(fonts) == 0 {
return fmt.Errorf("could not find font with family '%s'", name)
}
for _, fontMeta := range fonts {
switch StyleName(fontMeta.Style) {
case StyleRegular:
m.regularFace, err = m.loadFontFace(fontMeta.Path)
if err != nil {
return err
}
case StyleBold:
m.boldFace, err = m.loadFontFace(fontMeta.Path)
if err != nil {
return err
}
case StyleItalic:
m.italicFace, err = m.loadFontFace(fontMeta.Path)
if err != nil {
return err
}
case StyleBoldItalic:
m.boldItalicFace, err = m.loadFontFace(fontMeta.Path)
if err != nil {
return err
}
}
}
if m.regularFace == nil {
return fmt.Errorf("could not find regular style for font family '%s'", name)
}
return m.calcMetrics()
}
func (m *Manager) calcMetrics() error {
face := m.regularFace
var prevAdvance int
for ch := rune(32); ch <= 126; ch++ {
adv26, ok := face.GlyphAdvance(ch)
if ok && adv26 > 0 {
advance := int(adv26)
if prevAdvance > 0 && prevAdvance != advance {
return fmt.Errorf("the specified font is not monospaced: %d 0x%X=%d", prevAdvance, ch, advance)
}
prevAdvance = advance
}
}
if prevAdvance == 0 {
return fmt.Errorf("failed to calculate advance width for font face")
}
metrics := face.Metrics()
m.charSize.X = int(math.Round(float64(prevAdvance) / m.dpi))
m.charSize.Y = int(math.Round(float64(metrics.Height) / m.dpi))
m.fontDotDepth = int(math.Round(float64(metrics.Ascent) / m.dpi))
return nil
}
func (m *Manager) loadDefaultFonts() error {
regular, err := opentype.Parse(packed.MesloLGSNFRegularTTF)
if err != nil {
return err
}
m.regularFace, err = m.createFace(regular)
if err != nil {
return err
}
bold, err := opentype.Parse(packed.MesloLGSNFBoldTTF)
if err != nil {
return err
}
m.boldFace, err = m.createFace(bold)
if err != nil {
return err
}
italic, err := opentype.Parse(packed.MesloLGSNFItalicTTF)
if err != nil {
return err
}
m.italicFace, err = m.createFace(italic)
if err != nil {
return err
}
boldItalic, err := opentype.Parse(packed.MesloLGSNFBoldItalicTTF)
if err != nil {
return err
}
m.boldItalicFace, err = m.createFace(boldItalic)
if err != nil {
return err
}
return m.calcMetrics()
}
func (m *Manager) RegularFontFace() font.Face {
return m.regularFace
}
func (m *Manager) BoldFontFace() font.Face {
if m.boldFace == nil {
return m.RegularFontFace()
}
return m.boldFace
}
func (m *Manager) ItalicFontFace() font.Face {
if m.italicFace == nil {
return m.RegularFontFace()
}
return m.italicFace
}
func (m *Manager) BoldItalicFontFace() font.Face {
if m.boldItalicFace == nil {
if m.boldFace == nil {
return m.ItalicFontFace()
}
return m.BoldFontFace()
}
return m.boldItalicFace
}