Render the live status to the framebuffer in Atkinson Hyperlegible Mono
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
//go:embed font/AtkinsonHyperlegibleMono-Regular.ttf font/AtkinsonHyperlegibleMono-Bold.ttf
|
||||
var fontFS embed.FS
|
||||
|
||||
type glyph struct {
|
||||
// Coverage values, cellW*cellH, origin at the cell's top left.
|
||||
cov []uint8
|
||||
w, h int
|
||||
}
|
||||
|
||||
type textFace struct {
|
||||
face font.Face
|
||||
cellW int
|
||||
cellH int
|
||||
ascent int
|
||||
cache map[rune]*glyph
|
||||
}
|
||||
|
||||
func loadFace(bold bool, sizePx float64) (*textFace, error) {
|
||||
name := "font/AtkinsonHyperlegibleMono-Regular.ttf"
|
||||
if bold {
|
||||
name = "font/AtkinsonHyperlegibleMono-Bold.ttf"
|
||||
}
|
||||
raw, err := fontFS.ReadFile(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed, err := opentype.Parse(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
face, err := opentype.NewFace(parsed, &opentype.FaceOptions{
|
||||
Size: sizePx,
|
||||
DPI: 72,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := face.Metrics()
|
||||
adv, ok := face.GlyphAdvance('0')
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("font has no digit glyphs")
|
||||
}
|
||||
return &textFace{
|
||||
face: face,
|
||||
cellW: adv.Ceil(),
|
||||
cellH: (m.Ascent + m.Descent).Ceil(),
|
||||
ascent: m.Ascent.Ceil(),
|
||||
cache: make(map[rune]*glyph),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Rasterises once per rune and keeps the coverage mask, since the same few
|
||||
// dozen runes are redrawn every frame.
|
||||
func (t *textFace) glyph(r rune) *glyph {
|
||||
if g, ok := t.cache[r]; ok {
|
||||
return g
|
||||
}
|
||||
g := &glyph{w: t.cellW, h: t.cellH, cov: make([]uint8, t.cellW*t.cellH)}
|
||||
|
||||
dr, mask, maskp, _, ok := t.face.Glyph(
|
||||
fixed.P(0, t.ascent), r)
|
||||
if ok {
|
||||
alpha := image.NewAlpha(image.Rect(0, 0, t.cellW, t.cellH))
|
||||
draw.DrawMask(alpha, dr, image.NewUniform(image.White.C), image.Point{},
|
||||
mask, maskp, draw.Src)
|
||||
for y := 0; y < t.cellH; y++ {
|
||||
for x := 0; x < t.cellW; x++ {
|
||||
g.cov[y*t.cellW+x] = alpha.AlphaAt(x, y).A
|
||||
}
|
||||
}
|
||||
}
|
||||
t.cache[r] = g
|
||||
return g
|
||||
}
|
||||
|
||||
func (t *textFace) draw(fb *framebuffer, x, y int, s string, c rgb) int {
|
||||
for _, r := range s {
|
||||
if r == ' ' {
|
||||
x += t.cellW
|
||||
continue
|
||||
}
|
||||
g := t.glyph(r)
|
||||
for gy := 0; gy < g.h; gy++ {
|
||||
row := gy * g.w
|
||||
for gx := 0; gx < g.w; gx++ {
|
||||
if cov := g.cov[row+gx]; cov != 0 {
|
||||
fb.blend(x+gx, y+gy, c, cov)
|
||||
}
|
||||
}
|
||||
}
|
||||
x += t.cellW
|
||||
}
|
||||
return x
|
||||
}
|
||||
Reference in New Issue
Block a user