274 lines
7.7 KiB
Go
274 lines
7.7 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, location string, 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 " + location, 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
|
|
}
|