package wordleimg import ( "bytes" "strings" "github.com/fogleman/gg" ) type Theme struct { BG string Nah string Not string Nearly string Good string Txt string } var Themes = map[string]Theme{ "light": { BG: "#d7dadc", Nah: "#161618", Not: "#3a3a3c", Nearly: "#b59f3b", Good: "#538d4e", Txt: "#d7dadc", }, "dark": { BG: "#121213", Nah: "#161618", Not: "#3a3a3c", Nearly: "#b59f3b", Good: "#538d4e", Txt: "#d7dadc", }, "nixos": { BG: "#121213", Nah: "#161618", Not: "#4C6eb5", Nearly: "#f8ac0d", Good: "#029839", Txt: "#d7dadc", }, "catppuccin": { BG: "#1e1e2e", Nah: "#313244", Not: "#b4befe", Nearly: "#cba6f7", Good: "#a6e3a1", Txt: "#6c7086", }, "grayscale": { BG: "#121213", Nah: "#161618", Not: "#202020", Nearly: "#505050", Good: "#a0a0a0", Txt: "#f0f0f0", }, "bloody": { BG: "#121213", Nah: "#161618", Not: "#8B0000", Nearly: "#ff4500", Good: "#92ad32", Txt: "#d7dadc", }, } const box float64 = 40 * 2 const boxEdge float64 = 4 * 2 const smallBox float64 = 25 * 2 const smallBoxEdge float64 = 2 * 2 const frameEdge float64 = 20 * 2 const round float64 = 4 * 2 var keyboard = []rune("QWERTYUIOPASDFGHJKLZXCVBNM") var keyboardRows = []float64{0, 10, 19, 26} var krMax float64 = 10 func RenderHistoryImage(history string, theme Theme, wordSize int, maxGuesses int) ([]byte, int, int, error) { keyboardValidity := make([]rune, 128) lines := strings.Split(history, "\n") sus := max(float64(wordSize), krMax/(box/smallBox)) gc := gg.NewContext( int((box*sus)+ (2*frameEdge)+((sus-1)*boxEdge)), int(((box+boxEdge)*float64(maxGuesses))+ (2*frameEdge)+(3*(smallBox+smallBoxEdge)))) edging := ((box * sus) + ((sus - 1) * boxEdge) - (float64(wordSize)*box + (float64(wordSize)-1)*boxEdge)) / 2 gc.SetHexColor(theme.BG) gc.Clear() if err := gc.LoadFontFace("./wordleimg/static/Roboto_Condensed-Regular.ttf", 24*2); err != nil { return nil, 0, 0, err } for i := 0.0; i < float64(maxGuesses); i++ { if i < float64(len(lines)) && len(lines[int(i)]) >= wordSize*3 { runes := []rune(lines[int(i)]) for n := 0.0; n < float64(wordSize); n++ { switch runes[int(3*n)] { case '[': gc.SetHexColor(theme.Good) keyboardValidity[runes[int(3*n+1)]] = 2 case '(': gc.SetHexColor(theme.Nearly) if keyboardValidity[runes[int(3*n+1)]] != 2 { keyboardValidity[runes[int(3*n+1)]] = 1 } case '.': gc.SetHexColor(theme.Not) keyboardValidity[runes[int(3*n+1)]] = -1 default: gc.SetHexColor(theme.Nah) } gc.DrawRoundedRectangle( edging+frameEdge+n*(box+boxEdge), frameEdge+i*(box+boxEdge), box, box, round) gc.Fill() gc.SetHexColor(theme.Txt) gc.DrawStringAnchored(string(runes[int(3*n+1)]), edging+ frameEdge+n*(box+boxEdge)+(box/2), frameEdge+i*(box+boxEdge)+(box/2), 0.5, 0.5) } } else { for n := 0.0; n < float64(wordSize); n++ { gc.SetHexColor(theme.Not) gc.DrawRoundedRectangle( edging+frameEdge+n*(box+boxEdge), frameEdge+i*(box+boxEdge), box, box, round) gc.Fill() gc.SetHexColor(theme.BG) gc.DrawRoundedRectangle( edging+frameEdge+n*(box+boxEdge)+boxEdge/2, frameEdge+i*(box+boxEdge)+boxEdge/2, box-boxEdge, box-boxEdge, round) gc.Fill() } } } row := 1 for n := 0.0; n < float64(len(keyboard)); n++ { switch keyboardValidity[keyboard[int(n)]] { case 2: gc.SetHexColor(theme.Good) case 1: gc.SetHexColor(theme.Nearly) case 0: gc.SetHexColor(theme.Not) case -1: gc.SetHexColor(theme.Nah) } corrector := (krMax - keyboardRows[row] + keyboardRows[row-1]) / 2.0 x := frameEdge + (n-keyboardRows[row-1]+corrector)*(smallBox+smallBoxEdge) y := frameEdge + (box+boxEdge)*float64(maxGuesses) + smallBoxEdge + float64(row-1)*(smallBox+smallBoxEdge) gc.DrawRoundedRectangle(x, y, smallBox, smallBox, round) gc.Fill() gc.SetHexColor(theme.Txt) gc.DrawStringAnchored(string(keyboard[int(n)]), x+(smallBox/2), y+(smallBox/2), 0.5, 0.5) if int(n) == int(keyboardRows[row])-1 { row++ } } var buf bytes.Buffer err := gc.EncodePNG(&buf) if err != nil { return nil, 0, 0, err } return buf.Bytes(), gc.Width(), gc.Height(), nil }