258 lines
4.7 KiB
Go
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
|
|
}
|