package main import ( "embed" "flag" "image" "image/color" "image/draw" _ "image/png" "io" "io/fs" "log" "os" "time" "golang.org/x/image/font" "golang.org/x/image/font/opentype" "golang.org/x/image/font/sfnt" "golang.org/x/image/math/fixed" "pm3.dev/fb" ) // openSFNT opens the specified font file // It keeps the file open indefinitely for lazy loading when the font is used. // It never attempts to close the file and is meant only for loading fonts permanently at init time. func openSFNT(fs fs.FS, path string) *sfnt.Font { f, err := fs.Open(path) if err != nil { panic(err) } font, err := sfnt.ParseReaderAt(f.(io.ReaderAt)) if err != nil { panic(err) } return font } func openImage(fs fs.FS, path string) image.Image { f, err := fs.Open(path) if err != nil { panic(err) } img, _, err := image.Decode(f) if err != nil { panic(err) } return img } //go:embed *.ttf *.png var embedFS embed.FS var qtFonts = os.DirFS("/usr/lib/fonts") var ( dejaVuSans = openSFNT(qtFonts, "DejaVuSans.ttf") notoSans_Regular = openSFNT(embedFS, "NotoSans-Regular.ttf") notoSans_Bold = openSFNT(embedFS, "NotoSans-Bold.ttf") ) var ( orcaImg = openImage(embedFS, "orca.png") belowImg = openImage(embedFS, "below.png") ) type justify int const ( _ justify = iota justifyLeft justifyCenter justifyRight ) func drawText(img draw.Image, sfnt *sfnt.Font, size float64, just justify, c color.Color, dot fixed.Point26_6, text string) { face, err := opentype.NewFace(sfnt, &opentype.FaceOptions{ Size: size, DPI: 72, Hinting: font.HintingNone, }) if err != nil { panic(err) } defer face.Close() drawer := font.Drawer{ Dst: img, Src: &image.Uniform{c}, Face: face, Dot: dot, } _, adv := drawer.BoundString(text) var offset fixed.Int26_6 switch just { case justifyCenter: offset = adv / 2 case justifyRight: offset = adv } drawer.Dot.X -= offset drawer.DrawString(text) } func main() { flag.Parse() log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds) log.Println(os.Args[0], "start") fb, err := fb.Open() if err != nil { log.Fatalf("open fb: %v", err) } defer fb.Close() for { now := time.Now() img := image.NewRGBA(fb.Bounds()) draw.Draw(img, img.Bounds(), &image.Uniform{color.RGBA{34, 31, 32, 255}}, image.Point{}, draw.Src) drawText(img, dejaVuSans, 20, justifyLeft, color.White, fixed.P(50, 25), now.Format("Jan 02, 2006")) drawText(img, dejaVuSans, 20, justifyRight, color.White, fixed.P(750, 25), now.Format("3:04 pm")) drawText(img, notoSans_Regular, 16, justifyCenter, color.White, fixed.P(400, 25), "V1234567-D98765") draw.Draw(img, orcaImg.Bounds().Add(image.Point{400 - orcaImg.Bounds().Dx()/2, 75}), orcaImg, image.Point{}, draw.Over) drawText(img, notoSans_Bold, 48, justifyCenter, color.White, fixed.P(400, 330), "Tap below") draw.Draw(img, belowImg.Bounds().Add(image.Point{400 - belowImg.Bounds().Dx()/2, 380}), belowImg, image.Point{}, draw.Over) fb.WriteRGBA(img) } }