diff --git a/pkg/utils/pdf.go b/pkg/utils/pdf.go index 0339b170..045cb7ff 100644 --- a/pkg/utils/pdf.go +++ b/pkg/utils/pdf.go @@ -11,6 +11,8 @@ import ( "unicode" "github.com/phpdave11/gofpdf" + "github.com/signintech/gopdf" + "go.uber.org/zap" ) // cleanTextForPDF 清理文本,移除PDF不支持的字符(如emoji) @@ -172,3 +174,178 @@ func GeneratePDF(text, imageURL, outputPath, fontPath string) error { return nil } + +// CompetitorReportData 竞品报告数据 +type CompetitorReportData struct { + HighlightAnalysis HighlightAnalysisData `json:"highlight_analysis"` + DataPerformance DataPerformanceData `json:"data_performance_analysis"` + OverallSummary string `json:"overall_summary_and_optimization"` +} + +type HighlightAnalysisData struct { + Summary string `json:"summary"` + Points PointsData `json:"points"` +} + +type PointsData struct { + Theme string `json:"theme"` + Narrative string `json:"narrative"` + Content string `json:"content"` + Copywriting string `json:"copywriting"` + Data string `json:"data"` + Music string `json:"music,omitempty"` +} + +type DataPerformanceData struct { + Views string `json:"views"` + Completion string `json:"completion_rate,omitempty"` + Engagement string `json:"engagement"` +} + +// GenerateCompetitorReportPDF 生成竞品报告PDF +// 参数: +// - templatePath: 模板文件路径 +// - outputPath: 输出PDF路径 +// - data: 竞品报告数据 +// +// 返回: 错误信息 +func GenerateCompetitorReportPDF(templatePath, outputPath string, data CompetitorReportData) error { + fmt.Println("================================templatePath:", templatePath) + fmt.Println("================================outputPath:", outputPath) + + pdf := gopdf.GoPdf{} + pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4}) + + // 导入模板文件中的页面 + err := pdf.ImportPagesFromSource(templatePath, "/MediaBox") + if err != nil { + return fmt.Errorf("无法导入页面: %v", err) + } + + // 获取模板文件的总页数 + totalPages := pdf.GetNumberOfPages() + fmt.Printf("模板文件的总页数: %d\n", totalPages) + + // 根据模板路径推断字体路径(假设字体文件和模板在同一目录或data目录下) + dir := filepath.Dir(templatePath) + fontPath := filepath.Join(dir, "simfang.ttf") + if _, err := os.Stat(fontPath); err != nil { + // 尝试使用项目根目录下的data目录 + fontPath = filepath.Join("data", "simfang.ttf") + } + fmt.Printf("字体文件路径: %s\n", fontPath) + + // 加载中文字体 + ttfErr := pdf.AddTTFFont("simfang", fontPath) + if ttfErr != nil { + zap.L().Error("加载字体失败", zap.String("fontPath", fontPath), zap.Error(ttfErr)) + return fmt.Errorf("加载中文字体失败: %v", ttfErr) + } + + // 设置字体和字号 + err = pdf.SetFont("simfang", "", 10) + if err != nil { + return fmt.Errorf("设置字体失败: %v", err) + } + + // 行高15pt + lineHeight := 15.0 + + pdf.SetPage(1) + + // 概述 - 使用MultiCell自动换行,一行最多35个字 + pdf.SetXY(200, 104) + summaryRect := gopdf.Rect{W: 350.0, H: lineHeight} + pdf.MultiCell(&summaryRect, data.HighlightAnalysis.Summary) + + // 标题亮点 - 一行最多9个字 + pdf.SetXY(200, 184) + themeRect := gopdf.Rect{W: 120.0, H: lineHeight} + pdf.MultiCell(&themeRect, data.HighlightAnalysis.Points.Theme) + + // 题材亮点 - 一行最多9个字 + pdf.SetXY(330, 184) + narrativeRect := gopdf.Rect{W: 120.0, H: lineHeight} + pdf.MultiCell(&narrativeRect, data.HighlightAnalysis.Points.Narrative) + + // 内容亮点 - 一行最多9个字 + pdf.SetXY(460, 184) + contentRect := gopdf.Rect{W: 120.0, H: lineHeight} + pdf.MultiCell(&contentRect, data.HighlightAnalysis.Points.Content) + + // 文案亮点 - 一行最多9个字 + pdf.SetXY(200, 323) + copywritingRect := gopdf.Rect{W: 120.0, H: lineHeight} + pdf.MultiCell(©writingRect, data.HighlightAnalysis.Points.Copywriting) + + // 数据亮点 - 一行最多9个字 + pdf.SetXY(330, 323) + dataRect := gopdf.Rect{W: 120.0, H: lineHeight} + pdf.MultiCell(&dataRect, data.HighlightAnalysis.Points.Data) + + // 配乐亮点(仅视频) - 一行最多9个字 + if data.HighlightAnalysis.Points.Music != "" { + pdf.SetXY(460, 323) + musicRect := gopdf.Rect{W: 120.0, H: lineHeight} + pdf.MultiCell(&musicRect, data.HighlightAnalysis.Points.Music) + } + + // 浏览量 - 一行最多35个字 + pdf.SetXY(200, 474) + viewsRect := gopdf.Rect{W: 350.0, H: lineHeight} + pdf.MultiCell(&viewsRect, data.DataPerformance.Views) + + // 完播率 - 一行最多35个字 + // 始终显示在固定位置,有内容时填充内容 + pdf.SetXY(200, 539) + if data.DataPerformance.Completion != "" { + completionRect := gopdf.Rect{W: 350.0, H: lineHeight} + pdf.MultiCell(&completionRect, data.DataPerformance.Completion) + } + + // 点赞/分享/评论 - 一行最多35个字 + // 始终固定在完播率下面的位置 + pdf.SetXY(200, 600) + engagementRect := gopdf.Rect{W: 350.0, H: lineHeight} + pdf.MultiCell(&engagementRect, data.DataPerformance.Engagement) + + // 整体总结及可优化建议 - 一行最多35个字 + pdf.SetXY(200, 676) + summaryRect = gopdf.Rect{W: 350.0, H: lineHeight} + pdf.MultiCell(&summaryRect, data.OverallSummary) + + // 生成新的 PDF + if err = pdf.WritePdf(outputPath); err != nil { + return fmt.Errorf("error writing final PDF: %v", err) + } + + return nil +} + +// wrapText 将文本按指定宽度换行(按字符数计算) +func wrapText(text string, maxLen int) []string { + if text == "" { + return []string{} + } + + var lines []string + runes := []rune(text) + currentLine := "" + + for _, r := range runes { + // 如果当前行字符数达到最大限度,换行 + if len(currentLine) >= maxLen { + lines = append(lines, currentLine) + currentLine = string(r) + } else { + currentLine += string(r) + } + } + + // 添加最后一行 + if len(currentLine) > 0 { + lines = append(lines, currentLine) + } + + return lines +}