This repository has been archived on 2025-07-20. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
kindle-dashboard/imagegen/drawing.go
AKP 2e9b8cdfb0
Alter 13 files
Add `.gitignore`
Update `go.mod`
Add `go.sum`
Add `drawing.go`
Add `JetBrainsMono-Bold.ttf`
Add `JetBrainsMono-Italic.ttf`
Add `JetBrainsMono-Regular.ttf`
Add `weathericons-regular-webfont.ttf`
Add `imagegen.go`
Add `kindle-dashboard.test.png`
Add `rttclient.go`
Add `wxclient.go`
Add `rootpasswordtool.txt`
2023-11-19 19:14:28 +00:00

274 lines
7.6 KiB
Go

package imagegen
import (
_ "embed"
"errors"
"fmt"
"github.com/tdewolff/canvas"
"time"
)
//go:embed fonts/JetBrainsMono-Regular.ttf
var srcfontJBMonoRegular []byte
//go:embed fonts/JetBrainsMono-Bold.ttf
var srcfontJBMonoBold []byte
//go:embed fonts/JetBrainsMono-Italic.ttf
var srcfontJBMonoItalic []byte
//go:embed fonts/weathericons-regular-webfont.ttf
var srcFontWeatherIcons []byte
var (
fontJetBrainsMono *canvas.FontFamily
fontWeatherIcons *canvas.Font
fontMonoTitle *canvas.FontFace
fontMonoSubtitleInverted *canvas.FontFace
fontMonoLittleSubtitle *canvas.FontFace
fontMonoMedText *canvas.FontFace
fontMonoSmallText *canvas.FontFace
fontWeatherIconsSmall *canvas.FontFace
fontWeatherIconsLarge *canvas.FontFace
)
func init() {
fontJetBrainsMono = canvas.NewFontFamily("jetbrainsmono")
if err := fontJetBrainsMono.LoadFont(srcfontJBMonoRegular, 0, canvas.FontRegular); err != nil {
panic(err)
}
if err := fontJetBrainsMono.LoadFont(srcfontJBMonoBold, 0, canvas.FontBold); err != nil {
panic(err)
}
if err := fontJetBrainsMono.LoadFont(srcfontJBMonoItalic, 0, canvas.FontItalic); err != nil {
panic(err)
}
fontMonoTitle = fontJetBrainsMono.Face(220)
fontMonoSubtitleInverted = fontJetBrainsMono.Face(95, canvas.White, canvas.FontBold)
fontMonoLittleSubtitle = fontJetBrainsMono.Face(80, canvas.FontBold)
fontMonoMedText = fontJetBrainsMono.Face(150, canvas.FontRegular)
fontMonoSmallText = fontJetBrainsMono.Face(75, canvas.FontRegular)
if i, err := canvas.LoadFont(srcFontWeatherIcons, 0, canvas.FontRegular); err != nil {
panic(err)
} else {
fontWeatherIcons = i
}
fontWeatherIconsSmall = fontWeatherIcons.Face(150, canvas.Black)
fontWeatherIconsLarge = fontWeatherIcons.Face(300, canvas.Black)
}
func drawTitle(ctx *canvas.Context, startFrom float64) float64 {
now := time.Now().UTC()
var tod string
if hr := now.Hour(); 0 <= hr && hr < 12 {
tod = "morning"
} else if 12 <= hr && hr < 17 {
tod = "afternoon"
} else if 17 <= hr && hr < 21 {
tod = "evening"
} else {
tod = "night"
}
titleText := "good " + tod
var (
titleBounds canvas.Rect
)
{
// draw title
tl := canvas.NewTextLine(fontMonoTitle, titleText, canvas.Left)
titleBounds = tl.OutlineBounds()
ctx.DrawText(padding, startFrom+titleBounds.H, tl)
}
{
// draw date
rt := canvas.NewTextLine(fontMonoLittleSubtitle, now.Format("02 Jan"), canvas.Right)
ctx.DrawText(imageWidth-padding, startFrom+titleBounds.H, rt)
}
return startFrom + titleBounds.H
}
func drawWeather(ctx *canvas.Context, startFrom float64, wxLoc string, wx []*weatherEntry) (float64, error) {
if len(wx) < 1 {
return 0, errors.New("at least 1 weather entry required")
}
cursorX := float64(0)
cursorY := startFrom + 15
startFrom += 15
tl := canvas.NewTextLine(fontMonoSubtitleInverted, "Weather", canvas.Left)
bounds := tl.OutlineBounds()
{
cursorX += padding
cursorY += padding
//posX := padding
//posY := startFrom + padding
titlePad := float64(5)
ctx.SetFill(canvas.Black)
ctx.DrawPath(cursorX, cursorY, canvas.Rectangle(bounds.W+titlePad*2, bounds.H+titlePad*2))
cursorX += titlePad
cursorY += titlePad + bounds.H
ctx.DrawText(cursorX, cursorY, tl)
cursorX += bounds.W
ctx.DrawText(cursorX, cursorY, canvas.NewTextLine(fontMonoLittleSubtitle, fmt.Sprintf(" in %s at %02d00z", wxLoc, wx[0].Time), canvas.Left))
}
cursorY += bounds.H + 5
cursorX = padding
titleBaseline := startFrom + (bounds.H * 2) + 5
//ctx.SetFill(canvas.Red)
//ctx.DrawPath(1, titleBaseline, canvas.Rectangle(900, 1))
currTempStr := fmt.Sprintf("%02d", wx[0].Temperature)
currWindSpeedStr := fmt.Sprintf("w%dmph", wx[0].WindSpeed)
currPrecipProbStr := fmt.Sprintf("r%d%%", wx[0].PrecipitationChance)
tl = canvas.NewTextLine(fontWeatherIconsLarge, weatherIcons[wx[0].Type], canvas.Left)
bounds = tl.OutlineBounds()
wxIconsBaseline := titleBaseline + fontWeatherIconsLarge.Size
ctx.DrawText(padding*2, wxIconsBaseline, tl)
minitextblockxpos := (padding * 2) + 15 + bounds.W
rt := canvas.NewRichText(fontMonoMedText)
rt.WriteString(currTempStr)
rt.SetFace(fontWeatherIconsSmall)
rt.WriteString("\uf03c")
rtt := rt.ToText(fontMonoMedText.TextWidth(currTempStr)+fontWeatherIconsSmall.TextWidth("\uf03c"), fontMonoMedText.LineHeight()+10, canvas.Left, canvas.Left, 0.0, 0.0)
ctx.DrawText(minitextblockxpos, titleBaseline, rtt)
tl = canvas.NewTextLine(fontMonoSmallText, currWindSpeedStr, canvas.Left)
ctx.DrawText(minitextblockxpos, titleBaseline+90, tl)
tl = canvas.NewTextLine(fontMonoSmallText, currPrecipProbStr, canvas.Left)
ctx.DrawText(minitextblockxpos, titleBaseline+120, tl)
// -----
minitextblockxpos += rtt.Width + 15
dividerLineHeight := wxIconsBaseline - titleBaseline
for _, weather := range wx[1:] {
ctx.SetFill(canvas.Black)
ctx.DrawPath(minitextblockxpos, titleBaseline+20, canvas.Rectangle(3, dividerLineHeight))
minitextblockxpos += 15
wtl := canvas.NewTextLine(fontWeatherIconsSmall, weatherIcons[weather.Type], canvas.Left)
bounds := wtl.OutlineBounds()
tl = canvas.NewTextLine(fontMonoSmallText, fmt.Sprintf("%02dz %02dc", weather.Time, weather.Temperature), canvas.Left)
nbounds := tl.OutlineBounds()
ctx.DrawText(minitextblockxpos, titleBaseline+bounds.H+nbounds.H+50, tl)
ctx.DrawText(minitextblockxpos+((8+nbounds.W-bounds.W)/2), titleBaseline+bounds.H+30, wtl)
minitextblockxpos += 15 + nbounds.W
}
return titleBaseline + dividerLineHeight, nil
}
// https://erikflowers.github.io/weather-icons/
var weatherIcons = map[int]string{
0: "\uf02e", // clear night
1: "\uf00d", // sunny day
2: "\uf086", // partly cloudy (night)
3: "\uf002", // partly cloudy (day)
// 4 not used
5: "\uf014", // mist
6: "\uf014", // fog
7: "\uf013", // cloudy
8: "\uf041", // overcast
9: "\uf029", // light rain shower (night)
10: "\uf009", // light rain shower (day)
11: "\uf01c", // drizzle
12: "\uf01a", // light rain
13: "\uf008", // Heavy rain shower (night)
14: "\uf028", // Heavy rain shower (day)
15: "\uf019", // Heavy rain
16: "\uf0b4", // Sleet shower (night)
17: "\uf0b2", // Sleet shower (day)
18: "\uf0b5", // Sleet
19: "\uf024", // Hail shower (night)
20: "\uf004", // Hail shower (day)
21: "\uf015", // Hail
22: "\uf067", // Light snow shower (night)
23: "\uf065", // Light snow shower (day)
24: "\uf064", // Light snow
25: "\uf076", // Heavy snow shower (night)
26: "\uf076", // Heavy snow shower (day)
27: "\uf076", // Heavy snow
28: "\uf02d", // Thunder shower (night)
29: "\uf010", // Thunder shower (day)
30: "\uf01e", // Thunder
}
func drawTrains(ctx *canvas.Context, startFrom float64, services []string) (float64, error) {
cursorX := float64(0)
cursorY := startFrom + 15
startFrom += 15
tl := canvas.NewTextLine(fontMonoSubtitleInverted, "Trains", canvas.Left)
bounds := tl.OutlineBounds()
{
cursorX += padding
cursorY += padding
//posX := padding
//posY := startFrom + padding
titlePad := float64(5)
ctx.SetFill(canvas.Black)
ctx.DrawPath(cursorX, cursorY, canvas.Rectangle(bounds.W+titlePad*2, bounds.H+titlePad*2))
cursorX += titlePad
cursorY += titlePad + bounds.H
ctx.DrawText(cursorX, cursorY, tl)
cursorX += bounds.W
ctx.DrawText(cursorX, cursorY, canvas.NewTextLine(fontMonoLittleSubtitle, " at SLY", canvas.Left))
}
cursorY += bounds.H + 10
cursorX = padding
titleBaseline := startFrom + (bounds.H * 2) + 5
if len(services) == 0 {
services = []string{"*** No services in the next 30 mins ***"}
}
var lh float64
for _, str := range services {
tl := canvas.NewTextLine(fontMonoSmallText, str, canvas.Left)
if lh == 0 {
lh = tl.OutlineBounds().H
}
ctx.DrawText(cursorX, cursorY, tl)
cursorY += lh + 5
}
return titleBaseline, nil
}