200 lines
4.7 KiB
Go
200 lines
4.7 KiB
Go
package util
|
|
|
|
import (
|
|
"bytes"
|
|
"image"
|
|
"image/color"
|
|
"image/jpeg"
|
|
"image/png"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/fogleman/gg"
|
|
"github.com/yuin/goldmark"
|
|
"golang.org/x/net/html"
|
|
)
|
|
|
|
const (
|
|
defaultWidth = 800
|
|
defaultMargin = 20
|
|
lineHeight = 1.5
|
|
fontSize = 14
|
|
maxImageWidth = 600
|
|
maxImageHeight = 400
|
|
)
|
|
|
|
type ContentBlock struct {
|
|
Type string // "text" or "image"
|
|
Content string // text content or image path
|
|
ImgWidth float64 // image width after resize
|
|
ImgHeight float64 // image height after resize
|
|
Image image.Image // loaded image
|
|
}
|
|
|
|
// TextToPicture 将文本转换为图片
|
|
func TextToPicture(text string, isMarkdown bool) (image.Image, error) {
|
|
var htmlContent string
|
|
if isMarkdown {
|
|
// 将Markdown转换为HTML
|
|
md := goldmark.New()
|
|
var buf bytes.Buffer
|
|
if err := md.Convert([]byte(text), &buf); err != nil {
|
|
return nil, err
|
|
}
|
|
htmlContent = buf.String()
|
|
} else {
|
|
htmlContent = text
|
|
}
|
|
|
|
// 解析HTML并提取内容块
|
|
doc, err := html.Parse(strings.NewReader(htmlContent))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var blocks []ContentBlock
|
|
var extractContent func(*html.Node)
|
|
extractContent = func(n *html.Node) {
|
|
if n.Type == html.TextNode {
|
|
text := strings.TrimSpace(n.Data)
|
|
if text != "" {
|
|
blocks = append(blocks, ContentBlock{
|
|
Type: "text",
|
|
Content: text,
|
|
})
|
|
}
|
|
} else if n.Type == html.ElementNode && n.Data == "img" {
|
|
// 处理图片节点
|
|
var src string
|
|
for _, attr := range n.Attr {
|
|
if attr.Key == "src" {
|
|
src = attr.Val
|
|
break
|
|
}
|
|
}
|
|
if src != "" && !strings.HasPrefix(src, "http") {
|
|
// 处理本地图片
|
|
img, w, h, err := loadAndResizeImage(src)
|
|
if err == nil {
|
|
blocks = append(blocks, ContentBlock{
|
|
Type: "image",
|
|
Content: src,
|
|
Image: img,
|
|
ImgWidth: w,
|
|
ImgHeight: h,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
extractContent(c)
|
|
}
|
|
}
|
|
extractContent(doc)
|
|
|
|
// 计算总高度
|
|
totalHeight := calculateTotalHeight(blocks)
|
|
|
|
// 创建图片上下文
|
|
dc := gg.NewContext(defaultWidth, totalHeight)
|
|
dc.SetRGB(1, 1, 1) // 设置白色背景
|
|
dc.Clear()
|
|
|
|
// 设置字体和颜色
|
|
if err := dc.LoadFontFace("./resource/fonts/SourceHanSansCN-Regular.otf", fontSize); err != nil {
|
|
return nil, err
|
|
}
|
|
dc.SetColor(color.Black)
|
|
|
|
// 绘制内容
|
|
y := float64(defaultMargin)
|
|
for _, block := range blocks {
|
|
if block.Type == "text" {
|
|
wrapped := dc.WordWrap(block.Content, float64(defaultWidth-2*defaultMargin))
|
|
for _, wrappedLine := range wrapped {
|
|
dc.DrawString(wrappedLine, float64(defaultMargin), y+fontSize)
|
|
y += fontSize * lineHeight
|
|
}
|
|
y += fontSize // 段落间额外间距
|
|
} else if block.Type == "image" {
|
|
// 居中绘制图片
|
|
x := (float64(defaultWidth) - block.ImgWidth) / 2
|
|
dc.DrawImage(block.Image, int(x), int(y))
|
|
y += block.ImgHeight + float64(defaultMargin) // 图片后添加间距
|
|
}
|
|
}
|
|
|
|
return dc.Image(), nil
|
|
}
|
|
|
|
// loadAndResizeImage 加载并调整图片大小
|
|
func loadAndResizeImage(path string) (image.Image, float64, float64, error) {
|
|
// 确保路径是绝对路径
|
|
if !filepath.IsAbs(path) {
|
|
path = filepath.Join(".", path)
|
|
}
|
|
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, 0, 0, err
|
|
}
|
|
defer file.Close()
|
|
|
|
// 解码图片
|
|
var img image.Image
|
|
if strings.HasSuffix(strings.ToLower(path), ".png") {
|
|
img, err = png.Decode(file)
|
|
} else {
|
|
img, err = jpeg.Decode(file)
|
|
}
|
|
if err != nil {
|
|
return nil, 0, 0, err
|
|
}
|
|
|
|
// 计算调整后的尺寸
|
|
bounds := img.Bounds()
|
|
origWidth := float64(bounds.Dx())
|
|
origHeight := float64(bounds.Dy())
|
|
|
|
var newWidth, newHeight float64
|
|
if origWidth > maxImageWidth {
|
|
ratio := maxImageWidth / origWidth
|
|
newWidth = maxImageWidth
|
|
newHeight = origHeight * ratio
|
|
} else {
|
|
newWidth = origWidth
|
|
newHeight = origHeight
|
|
}
|
|
|
|
if newHeight > maxImageHeight {
|
|
ratio := maxImageHeight / newHeight
|
|
newHeight = maxImageHeight
|
|
newWidth = newWidth * ratio
|
|
}
|
|
|
|
// 创建新的图片上下文并调整大小
|
|
dc := gg.NewContext(int(newWidth), int(newHeight))
|
|
dc.DrawImage(img, 0, 0)
|
|
|
|
return dc.Image(), newWidth, newHeight, nil
|
|
}
|
|
|
|
// calculateTotalHeight 计算总高度
|
|
func calculateTotalHeight(blocks []ContentBlock) int {
|
|
var totalHeight float64 = float64(2 * defaultMargin) // 上下边距
|
|
|
|
for _, block := range blocks {
|
|
if block.Type == "text" {
|
|
// 估算文本高度
|
|
estimatedLines := (len(block.Content)*fontSize)/(defaultWidth-2*defaultMargin) + 1
|
|
totalHeight += float64(estimatedLines) * fontSize * lineHeight
|
|
totalHeight += fontSize // 段落间距
|
|
} else if block.Type == "image" {
|
|
totalHeight += block.ImgHeight + float64(defaultMargin) // 图片高度加间距
|
|
}
|
|
}
|
|
|
|
return int(totalHeight)
|
|
}
|