package pdf import ( "os" "path/filepath" "runtime" "github.com/jung-kurt/gofpdf/v2" "go.uber.org/zap" ) // FontManager 字体管理器 type FontManager struct { logger *zap.Logger chineseFontName string chineseFontLoaded bool watermarkFontName string watermarkFontLoaded bool baseDir string // 缓存基础目录,避免重复计算 } // NewFontManager 创建字体管理器 func NewFontManager(logger *zap.Logger) *FontManager { // 获取当前文件所在目录 _, filename, _, _ := runtime.Caller(0) baseDir := filepath.Dir(filename) return &FontManager{ logger: logger, chineseFontName: "ChineseFont", watermarkFontName: "WatermarkFont", baseDir: baseDir, } } // LoadChineseFont 加载中文字体到PDF func (fm *FontManager) LoadChineseFont(pdf *gofpdf.Fpdf) bool { if fm.chineseFontLoaded { return true } fontPaths := fm.getChineseFontPaths() if len(fontPaths) == 0 { fm.logger.Warn("未找到中文字体文件,PDF中的中文可能显示为空白") return false } // 尝试加载字体 for _, fontPath := range fontPaths { if fm.tryAddFont(pdf, fontPath, fm.chineseFontName) { fm.chineseFontLoaded = true fm.logger.Info("成功加载中文字体", zap.String("font_path", fontPath)) return true } } fm.logger.Warn("所有中文字体文件都无法加载,PDF中的中文可能显示为空白") return false } // LoadWatermarkFont 加载水印字体到PDF func (fm *FontManager) LoadWatermarkFont(pdf *gofpdf.Fpdf) bool { if fm.watermarkFontLoaded { return true } fontPaths := fm.getWatermarkFontPaths() if len(fontPaths) == 0 { fm.logger.Warn("未找到水印字体文件,将使用主字体") return false } // 尝试加载字体 for _, fontPath := range fontPaths { if fm.tryAddFont(pdf, fontPath, fm.watermarkFontName) { fm.watermarkFontLoaded = true fm.logger.Info("成功加载水印字体", zap.String("font_path", fontPath)) return true } } fm.logger.Warn("所有水印字体文件都无法加载,将使用主字体") return false } // tryAddFont 尝试添加字体(统一处理中文字体和水印字体) func (fm *FontManager) tryAddFont(pdf *gofpdf.Fpdf, fontPath, fontName string) bool { defer func() { if r := recover(); r != nil { fm.logger.Error("添加字体时发生panic", zap.Any("panic", r), zap.String("font_path", fontPath), zap.String("font_name", fontName)) } }() // 检查文件是否存在 if _, err := os.Stat(fontPath); err != nil { return false } // gofpdf v2使用AddUTF8Font添加支持UTF-8的字体 pdf.AddUTF8Font(fontName, "", fontPath) // 常规样式 pdf.AddUTF8Font(fontName, "B", fontPath) // 粗体样式 // 验证字体是否可用 pdf.SetFont(fontName, "", 12) testWidth := pdf.GetStringWidth("测试") if testWidth == 0 { fm.logger.Debug("字体加载后无法使用", zap.String("font_path", fontPath), zap.String("font_name", fontName), zap.Float64("test_width", testWidth)) return false } return true } // getChineseFontPaths 获取中文字体路径列表(仅TTF格式) func (fm *FontManager) getChineseFontPaths() []string { // 按优先级排序的字体文件列表 fontNames := []string{ "simhei.ttf", // 黑体(默认) "simkai.ttf", // 楷体(备选) "simfang.ttf", // 仿宋(备选) } return fm.buildFontPaths(fontNames) } // getWatermarkFontPaths 获取水印字体路径列表(仅TTF格式) func (fm *FontManager) getWatermarkFontPaths() []string { // 水印字体文件名(支持大小写变体) fontNames := []string{ "yunfengfeiyunti-2.ttf", "YunFengFeiYunTi-2.ttf", // 大写版本(兼容) } return fm.buildFontPaths(fontNames) } // buildFontPaths 构建字体文件路径列表 func (fm *FontManager) buildFontPaths(fontNames []string) []string { var fontPaths []string // 使用 pdf/fonts/ 目录 for _, fontName := range fontNames { fontPaths = append(fontPaths, filepath.Join(fm.baseDir, "fonts", fontName)) } // 过滤出实际存在的字体文件 var existingFonts []string for _, fontPath := range fontPaths { if _, err := os.Stat(fontPath); err == nil { existingFonts = append(existingFonts, fontPath) } } return existingFonts } // SetFont 设置中文字体 func (fm *FontManager) SetFont(pdf *gofpdf.Fpdf, style string, size float64) { if fm.chineseFontLoaded { pdf.SetFont(fm.chineseFontName, style, size) } else { // 如果没有中文字体,使用Arial作为后备 pdf.SetFont("Arial", style, size) } } // SetWatermarkFont 设置水印字体 func (fm *FontManager) SetWatermarkFont(pdf *gofpdf.Fpdf, style string, size float64) { if fm.watermarkFontLoaded { pdf.SetFont(fm.watermarkFontName, style, size) } else { // 如果水印字体不可用,使用主字体作为后备 fm.SetFont(pdf, style, size) } } // IsChineseFontAvailable 检查中文字体是否可用 func (fm *FontManager) IsChineseFontAvailable() bool { return fm.chineseFontLoaded }