fonchain-fiee/pkg/utils/pdf.go

145 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package utils
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"unicode"
"github.com/phpdave11/gofpdf"
)
// cleanTextForPDF 清理文本移除PDF不支持的字符如emoji
// gofpdf库不支持某些特殊字符
func cleanTextForPDF(text string) string {
var result []rune
for _, r := range text {
// 保留基本多文种平面BMP内的字符码点 <= 0xFFFF
// 这样可以保留中文、英文、数字等常用字符但过滤掉emoji等特殊字符
if r <= 0xFFFF && (unicode.IsPrint(r) || unicode.IsSpace(r)) {
result = append(result, r)
}
}
return string(result)
}
// loadChineseFont 加载中文字体
func loadChineseFont(pdf *gofpdf.Fpdf, fontPath string) error {
var fontData []byte
var err error
// 如果提供了本地字体路径,优先使用本地字体
if fontPath == "" {
return errors.New("字体文件路径不能为空")
}
fontData, err = os.ReadFile(fontPath)
if err != nil {
return fmt.Errorf("读取字体文件失败: %v", err)
}
// 使用本地字体文件
pdf.AddUTF8FontFromBytes("Chinese", "", fontData)
return nil
}
// GeneratePDF 生成PDF文件
func GeneratePDF(text, imageURL, outputPath, fontPath string) error {
// 创建PDF实例P=纵向mm=毫米单位A4=页面大小
pdf := gofpdf.New("P", "mm", "A4", "")
// 加载中文字体
err := loadChineseFont(pdf, fontPath)
if err != nil {
return fmt.Errorf("加载中文字体失败: %v", err)
}
// 添加新页面
pdf.AddPage()
// 设置字体使用中文字体12号字体
pdf.SetFont("Chinese", "", 12)
// 设置页面边距(左、上、右)
pdf.SetMargins(20, 10, 20)
// 设置当前位置x, y从左上角开始
pdf.SetXY(20, 10)
// 清理文本移除PDF不支持的字符如emoji
cleanedText := cleanTextForPDF(text)
// 添加文本内容
// 使用MultiCell方法处理多行文本支持自动换行
// 参数:宽度、行高、文本内容、边框、对齐方式、是否填充
// A4页面宽度210mm减去左右边距40mm可用宽度170mm
textWidth := 170.0
lineHeight := 7.0
pdf.MultiCell(textWidth, lineHeight, cleanedText, "", "L", false)
// 添加一些间距
pdf.Ln(5)
// 解析URL获取文件扩展名
u, err := url.Parse(imageURL)
if err != nil {
return fmt.Errorf("图片链接解析错误: %v", err)
}
fileExt := filepath.Ext(u.Path)
// 如果没有扩展名,默认使用.jpg
if fileExt == "" {
fileExt = ".jpg"
}
// 下载图片
resp, err := http.Get(imageURL)
if err != nil {
return fmt.Errorf("下载图片失败: %v", err)
}
defer resp.Body.Close()
// 读取图片数据
imageData, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("读取图片数据失败: %v", err)
}
// 将图片数据保存到临时文件gofpdf需要文件路径
tmpFile, err := os.CreateTemp("", "pdf_image_*"+fileExt)
if err != nil {
return fmt.Errorf("创建临时文件失败: %v", err)
}
defer os.Remove(tmpFile.Name()) // 使用完后删除临时文件
defer tmpFile.Close()
// 写入图片数据到临时文件
_, err = tmpFile.Write(imageData)
if err != nil {
return fmt.Errorf("写入临时文件失败: %v", err)
}
tmpFile.Close()
// A4纵向页面宽度210mm减去左右边距40mm可用宽度170mm
// 图片宽度设为可用宽度的70%
imageWidth := textWidth * 0.7
// 计算居中位置页面宽度210mm图片居中
imageX := (210.0 - imageWidth) / 2
currentY := pdf.GetY()
// 添加图片
// ImageOptions参数图片路径、x坐标、y坐标、宽度、高度、是否流式布局、选项、链接
// 高度设为0表示按比例自动计算
pdf.ImageOptions(tmpFile.Name(), imageX, currentY, imageWidth, 0, false, gofpdf.ImageOptions{}, 0, "")
// 生成并保存PDF文件
err = pdf.OutputFileAndClose(outputPath)
if err != nil {
return fmt.Errorf("生成PDF失败: %v", err)
}
return nil
}