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 }