185 lines
3.3 KiB
Go
185 lines
3.3 KiB
Go
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
|