Pinball dot-matrix clock animation thingy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

194 lines
3.8 KiB

package matrix
import (
"encoding/xml"
"fmt"
"image"
"image/draw"
"image/png"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
type Font interface {
Glyph(r rune) *image.RGBA
Size() image.Point
}
type pngFont struct {
images []*image.RGBA
glyphs map[rune]int
size image.Point
}
func (f pngFont) Glyph(r rune) *image.RGBA {
i, ok := f.glyphs[r]
if ok {
return f.images[i]
}
return nil
}
func (f pngFont) Size() image.Point {
return f.size
}
type pngFontDescription struct {
Size int `xml:"size,attr"`
Family string `xml:"family,attr"`
Height int `xml:"height,attr"`
Style string `xml:"style,attr"`
Chars []pngFontChar `xml:"Char"`
}
type pngFontChar struct {
Width int `xml:"width,attr"`
Offset string `xml:"offset,attr"`
Rect string `xml:"rect,attr"`
Code string `xml:"code,attr"`
}
func (c pngFontChar) Bounds() image.Rectangle {
v := make([]int, 4)
for i, s := range strings.Split(c.Rect, " ") {
v[i], _ = strconv.Atoi(s)
}
return image.Rect(v[0], v[1], v[0]+v[2], v[1]+v[3])
}
func LoadPNGFont(pngFile, xmlFile string) (Font, error) {
var (
f *os.File
description pngFontDescription
bitmap image.Image
err error
)
if f, err = os.Open(xmlFile); err != nil {
return nil, err
}
defer f.Close()
if err = xml.NewDecoder(f).Decode(&description); err != nil {
return nil, err
}
// log.Printf("description: %#+v", description)
if f, err = os.Open(pngFile); err != nil {
return nil, err
}
defer f.Close()
if bitmap, err = png.Decode(f); err != nil {
return nil, err
}
var (
images = make([]*image.RGBA, 0, len(description.Chars))
glyphs = make(map[rune]int)
size image.Point
)
for _, char := range description.Chars {
bounds := char.Bounds()
output := image.NewRGBA(bounds.Sub(bounds.Min))
draw.Draw(output, output.Bounds(), bitmap, bounds.Min, draw.Src)
glyphs[[]rune(char.Code)[0]] = len(images)
images = append(images, output)
if x := bounds.Dx(); x > size.X {
size.X = x
}
if y := bounds.Dy(); y > size.Y {
size.Y = y
}
}
return &pngFont{
images: images,
glyphs: glyphs,
size: size,
}, nil
}
type ttfFont struct {
font *truetype.Font
face font.Face
size int
dim image.Point
}
func (f *ttfFont) Glyph(r rune) *image.RGBA {
var (
w, h = measureRune(f.face, r)
i = image.NewRGBA(image.Rectangle{Max: image.Point{X: f.dim.X, Y: h + 1}})
d = &font.Drawer{
Face: f.face,
Dst: i,
Src: image.White,
Dot: fixed.P((f.dim.X-w)/2, (f.dim.Y-h)/2+h+1),
}
)
draw.Draw(i, i.Rect, image.Transparent, image.Point{}, draw.Src)
d.DrawString(string(r))
return i
}
func (f *ttfFont) Size() image.Point {
return f.dim
}
func LoadTTFFont(name string, size int) (Font, error) {
ttf, err := ioutil.ReadFile(name)
if err != nil {
return nil, err
}
return UseTTFFont(ttf, size)
}
func UseTTFFont(ttf []byte, size int) (Font, error) {
f, err := truetype.Parse(ttf)
if err != nil {
return nil, fmt.Errorf("font: could not parse font: %v", err)
}
face := truetype.NewFace(f, &truetype.Options{
DPI: 72, // At 72 dpi, pt == px
Size: float64(size), // px
Hinting: font.HintingNone,
})
return &ttfFont{
font: f,
face: face,
size: size,
dim: calcDimensions(face),
}, nil
}
func measureRune(face font.Face, r rune) (w, h int) {
b, a, ok := face.GlyphBounds(r)
if !ok {
return
}
return a.Ceil(), (b.Max.Y - b.Min.Y).Ceil()
}
func calcDimensions(face font.Face) image.Point {
var x, y int
for _, r := range "xXmM09: " {
w, h := measureRune(face, r)
if w > x {
x = w
}
if h > y {
y = h
}
// log.Printf("font: glyph %c is %dx%d", r, w, h)
}
// log.Printf("bounds: %d,%d", x, y)
return image.Pt(x, y)
}