writekit/internal/og/og.go

186 lines
3.3 KiB
Go
Raw Normal View History

2026-01-09 00:16:46 +02:00
package og
import (
"bytes"
"image"
"image/color"
"image/draw"
"image/png"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/font/gofont/goregular"
"golang.org/x/image/font/gofont/gobold"
)
const (
imgWidth = 1200
imgHeight = 630
)
var (
regularFont *truetype.Font
boldFont *truetype.Font
)
func init() {
var err error
regularFont, err = freetype.ParseFont(goregular.TTF)
if err != nil {
panic(err)
}
boldFont, err = freetype.ParseFont(gobold.TTF)
if err != nil {
panic(err)
}
}
func parseHexColor(hex string) color.RGBA {
if len(hex) == 0 {
return color.RGBA{16, 185, 129, 255} // #10b981 emerald
}
if hex[0] == '#' {
hex = hex[1:]
}
if len(hex) != 6 {
return color.RGBA{16, 185, 129, 255} // #10b981 emerald
}
var r, g, b uint8
for i, c := range hex {
var v uint8
switch {
case c >= '0' && c <= '9':
v = uint8(c - '0')
case c >= 'a' && c <= 'f':
v = uint8(c - 'a' + 10)
case c >= 'A' && c <= 'F':
v = uint8(c - 'A' + 10)
}
switch i {
case 0:
r = v << 4
case 1:
r |= v
case 2:
g = v << 4
case 3:
g |= v
case 4:
b = v << 4
case 5:
b |= v
}
}
return color.RGBA{r, g, b, 255}
}
func Generate(title, siteName, accentColor string) ([]byte, error) {
accent := parseHexColor(accentColor)
img := image.NewRGBA(image.Rect(0, 0, imgWidth, imgHeight))
for y := 0; y < imgHeight; y++ {
ratio := float64(y) / float64(imgHeight)
r := uint8(float64(accent.R) * (1 - ratio*0.3))
g := uint8(float64(accent.G) * (1 - ratio*0.3))
b := uint8(float64(accent.B) * (1 - ratio*0.3))
for x := 0; x < imgWidth; x++ {
img.Set(x, y, color.RGBA{r, g, b, 255})
}
}
c := freetype.NewContext()
c.SetDPI(72)
c.SetClip(img.Bounds())
c.SetDst(img)
c.SetSrc(image.White)
c.SetHinting(font.HintingFull)
titleSize := 64.0
if len(title) > 50 {
titleSize = 48.0
}
if len(title) > 80 {
titleSize = 40.0
}
c.SetFont(boldFont)
c.SetFontSize(titleSize)
wrapped := wrapText(title, 28)
y := 200
for _, line := range wrapped {
pt := freetype.Pt(80, y)
c.DrawString(line, pt)
y += int(titleSize * 1.4)
}
c.SetFont(regularFont)
c.SetFontSize(28)
pt := freetype.Pt(80, imgHeight-80)
c.DrawString(siteName, pt)
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func wrapText(text string, maxChars int) []string {
if len(text) <= maxChars {
return []string{text}
}
var lines []string
words := splitWords(text)
var current string
for _, word := range words {
if current == "" {
current = word
} else if len(current)+1+len(word) <= maxChars {
current += " " + word
} else {
lines = append(lines, current)
current = word
}
}
if current != "" {
lines = append(lines, current)
}
if len(lines) > 4 {
lines = lines[:4]
if len(lines[3]) > 3 {
lines[3] = lines[3][:len(lines[3])-3] + "..."
}
}
return lines
}
func splitWords(s string) []string {
var words []string
var current string
for _, r := range s {
if r == ' ' || r == '\t' || r == '\n' {
if current != "" {
words = append(words, current)
current = ""
}
} else {
current += string(r)
}
}
if current != "" {
words = append(words, current)
}
return words
}
var _ = draw.Draw