Files
cabletest/text.go
T

109 lines
2.2 KiB
Go

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
}