diff --git a/internal/shared/pdf/page_builder.go b/internal/shared/pdf/page_builder.go index 6cd4d6a..24f6abf 100644 --- a/internal/shared/pdf/page_builder.go +++ b/internal/shared/pdf/page_builder.go @@ -104,7 +104,7 @@ func (pb *PageBuilder) AddFirstPage(pdf *gofpdf.Fpdf, product *entities.Product, _, lineHt = pdf.GetFontSize() // 居中对齐的MultiCell(通过计算宽度实现) descWidth := pageWidth * 0.7 - descLines := pdf.SplitText(desc, descWidth) + descLines := pb.safeSplitText(pdf, desc, descWidth, chineseFontAvailable) currentX := (pageWidth - descWidth) / 2 for _, line := range descLines { pdf.SetX(currentX) @@ -120,7 +120,7 @@ func (pb *PageBuilder) AddFirstPage(pdf *gofpdf.Fpdf, product *entities.Product, pb.fontManager.SetFont(pdf, "", 12) _, lineHt = pdf.GetFontSize() contentWidth := pageWidth * 0.7 - contentLines := pdf.SplitText(content, contentWidth) + contentLines := pb.safeSplitText(pdf, content, contentWidth, chineseFontAvailable) currentX := (pageWidth - contentWidth) / 2 for _, line := range contentLines { pdf.SetX(currentX) @@ -731,3 +731,100 @@ func (pb *PageBuilder) getContentPreview(content string, maxLen int) string { } return content[:maxLen] + "..." } + +// safeSplitText 安全地分割文本,避免在没有中文字体时调用SplitText导致panic +func (pb *PageBuilder) safeSplitText(pdf *gofpdf.Fpdf, text string, width float64, chineseFontAvailable bool) []string { + // 检查文本是否包含中文字符 + hasChinese := false + for _, r := range text { + if (r >= 0x4E00 && r <= 0x9FFF) || // 中文字符范围 + (r >= 0x3400 && r <= 0x4DBF) || // 扩展A + (r >= 0x20000 && r <= 0x2A6DF) || // 扩展B + (r >= 0x3000 && r <= 0x303F) || // CJK符号和标点 + (r >= 0xFF00 && r <= 0xFFEF) { // 全角字符 + hasChinese = true + break + } + } + + // 如果文本包含中文但中文字体不可用,直接使用手动分割方法 + // 如果中文字体可用且文本不包含中文,可以尝试使用SplitText + // 即使中文字体可用,如果文本包含中文,也要小心处理(字体可能未正确加载) + if chineseFontAvailable && !hasChinese { + // 对于纯英文/ASCII文本,可以安全使用SplitText + var lines []string + func() { + defer func() { + if r := recover(); r != nil { + pb.logger.Warn("SplitText发生panic,使用备用方法", zap.Any("error", r)) + } + }() + lines = pdf.SplitText(text, width) + }() + if len(lines) > 0 { + return lines + } + } + + // 使用手动分割方法(适用于中文文本或SplitText失败的情况) + // 估算字符宽度:中文字符约6mm,英文字符约3mm(基于14号字体) + fontSize, _ := pdf.GetFontSize() + chineseCharWidth := fontSize * 0.43 // 中文字符宽度(mm) + englishCharWidth := fontSize * 0.22 // 英文字符宽度(mm) + + var lines []string + var currentLine strings.Builder + currentWidth := 0.0 + + runes := []rune(text) + for _, r := range runes { + // 计算字符宽度 + var charWidth float64 + if (r >= 0x4E00 && r <= 0x9FFF) || // 中文字符范围 + (r >= 0x3400 && r <= 0x4DBF) || // 扩展A + (r >= 0x20000 && r <= 0x2A6DF) || // 扩展B + (r >= 0x3000 && r <= 0x303F) || // CJK符号和标点 + (r >= 0xFF00 && r <= 0xFFEF) { // 全角字符 + charWidth = chineseCharWidth + } else { + charWidth = englishCharWidth + } + + // 检查是否需要换行 + if currentWidth+charWidth > width && currentLine.Len() > 0 { + // 保存当前行 + lines = append(lines, currentLine.String()) + currentLine.Reset() + currentWidth = 0.0 + } + + // 处理换行符 + if r == '\n' { + if currentLine.Len() > 0 { + lines = append(lines, currentLine.String()) + currentLine.Reset() + currentWidth = 0.0 + } else { + // 空行 + lines = append(lines, "") + } + continue + } + + // 添加字符到当前行 + currentLine.WriteRune(r) + currentWidth += charWidth + } + + // 添加最后一行 + if currentLine.Len() > 0 { + lines = append(lines, currentLine.String()) + } + + // 如果没有分割出任何行,至少返回一行 + if len(lines) == 0 { + lines = []string{text} + } + + return lines +} diff --git a/internal/shared/pdf/pdf_finder.go b/internal/shared/pdf/pdf_finder.go index 48bc0a5..0ee0f06 100644 --- a/internal/shared/pdf/pdf_finder.go +++ b/internal/shared/pdf/pdf_finder.go @@ -28,11 +28,8 @@ func GetDocumentationDir() (string, error) { checkedDirs = append(checkedDirs, docDir) if info, err := os.Stat(docDir); err == nil && info.IsDir() { - absPath, err := filepath.Abs(docDir) - if err != nil { - return "", fmt.Errorf("获取绝对路径失败: %w", err) - } - return absPath, nil + // 直接返回相对路径,不转换为绝对路径 + return docDir, nil } // 尝试父目录 @@ -177,22 +174,13 @@ func (f *PDFFinder) FindPDFByProductCode(productCode string) (string, error) { return "", fmt.Errorf("未找到产品代码为 %s 的PDF文档", productCode) } - // 转换为绝对路径 - absPath, err := filepath.Abs(foundPath) - if err != nil { - f.logger.Error("获取文件绝对路径失败", - zap.String("file_path", foundPath), - zap.Error(err), - ) - return "", fmt.Errorf("获取文件绝对路径失败: %w", err) - } - + // 直接返回相对路径,不转换为绝对路径 f.logger.Info("成功找到PDF文件", zap.String("product_code", productCode), - zap.String("file_path", absPath), + zap.String("file_path", foundPath), ) - return absPath, nil + return foundPath, nil } // FindPDFByProductCodeWithFallback 根据产品代码查找PDF文件,支持多个可能的命名格式 @@ -233,10 +221,6 @@ func (f *PDFFinder) FindPDFByProductCodeWithFallback(productCode string) (string return "", fmt.Errorf("未找到产品代码为 %s 的PDF文档", productCode) } - absPath, err := filepath.Abs(foundPath) - if err != nil { - return "", fmt.Errorf("获取文件绝对路径失败: %w", err) - } - - return absPath, nil + // 直接返回相对路径,不转换为绝对路径 + return foundPath, nil } diff --git a/internal/shared/pdf/pdf_generator.go b/internal/shared/pdf/pdf_generator.go index 664bcba..f4861dd 100644 --- a/internal/shared/pdf/pdf_generator.go +++ b/internal/shared/pdf/pdf_generator.go @@ -108,12 +108,10 @@ func (g *PDFGenerator) findLogo() { for _, logoPath := range logoPaths { if _, err := os.Stat(logoPath); err == nil { - absPath, err := filepath.Abs(logoPath) - if err == nil { - g.logoPath = absPath - g.logger.Info("找到logo文件", zap.String("logo_path", absPath)) - return - } + // 直接使用相对路径,不转换为绝对路径 + g.logoPath = logoPath + g.logger.Info("找到logo文件", zap.String("logo_path", logoPath)) + return } } diff --git a/internal/shared/pdf/pdf_generator_refactored.go b/internal/shared/pdf/pdf_generator_refactored.go index 8d907fe..78bbae4 100644 --- a/internal/shared/pdf/pdf_generator_refactored.go +++ b/internal/shared/pdf/pdf_generator_refactored.go @@ -70,12 +70,10 @@ func (g *PDFGeneratorRefactored) findLogo() { for _, logoPath := range logoPaths { if _, err := os.Stat(logoPath); err == nil { - absPath, err := filepath.Abs(logoPath) - if err == nil { - g.logoPath = absPath - g.logger.Info("找到logo文件", zap.String("logo_path", absPath)) - return - } + // 直接使用相对路径,不转换为绝对路径 + g.logoPath = logoPath + g.logger.Info("找到logo文件", zap.String("logo_path", logoPath)) + return } }