Files
tyapi-server/internal/shared/pdf/font_manager.go
2025-12-04 12:30:33 +08:00

247 lines
6.6 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 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)
fm := &FontManager{
logger: logger,
chineseFontName: "ChineseFont",
watermarkFontName: "WatermarkFont",
baseDir: baseDir,
}
return fm
}
// LoadChineseFont 加载中文字体到PDF
func (fm *FontManager) LoadChineseFont(pdf *gofpdf.Fpdf) bool {
if fm.chineseFontLoaded {
return true
}
fontPaths := fm.getChineseFontPaths()
if len(fontPaths) == 0 {
// 字体文件不存在,使用系统默认字体,不记录警告
return false
}
// 尝试加载字体
for _, fontPath := range fontPaths {
if fm.tryAddFont(pdf, fontPath, fm.chineseFontName) {
fm.chineseFontLoaded = true
return true
}
}
// 无法加载字体,使用系统默认字体,不记录警告
return false
}
// LoadWatermarkFont 加载水印字体到PDF
func (fm *FontManager) LoadWatermarkFont(pdf *gofpdf.Fpdf) bool {
if fm.watermarkFontLoaded {
return true
}
fontPaths := fm.getWatermarkFontPaths()
if len(fontPaths) == 0 {
return false
}
// 尝试加载字体
for _, fontPath := range fontPaths {
if fm.tryAddFont(pdf, fontPath, fm.watermarkFontName) {
fm.watermarkFontLoaded = true
return true
}
}
return false
}
// tryAddFont 尝试添加字体(统一处理中文字体和水印字体)
func (fm *FontManager) tryAddFont(pdf *gofpdf.Fpdf, fontPath, fontName string) bool {
defer func() {
if r := recover(); r != nil {
// 静默处理,不记录日志
}
}()
// 检查文件是否存在
if _, err := os.Stat(fontPath); err != nil {
return false
}
// 将相对路径转换为绝对路径gofpdf在Output时需要绝对路径
absFontPath, err := filepath.Abs(fontPath)
if err != nil {
absFontPath = fontPath
}
// 再次检查绝对路径文件是否存在
if _, err := os.Stat(absFontPath); err != nil {
return false
}
// gofpdf v2使用AddUTF8Font添加支持UTF-8的字体使用绝对路径
pdf.AddUTF8Font(fontName, "", absFontPath) // 常规样式
pdf.AddUTF8Font(fontName, "B", absFontPath) // 粗体样式
// 验证字体是否可用
pdf.SetFont(fontName, "", 12)
testWidth := pdf.GetStringWidth("测试")
if testWidth == 0 {
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
// 方式1: 服务器绝对路径Linux环境优先检查因为服务器上文件通常在这里
if runtime.GOOS == "linux" {
commonServerPaths := []string{
"/www/tyapi-server/internal/shared/pdf/fonts",
"/app/internal/shared/pdf/fonts",
}
for _, basePath := range commonServerPaths {
for _, fontName := range fontNames {
fontPaths = append(fontPaths, filepath.Join(basePath, fontName))
}
}
}
// 方式2: 使用 pdf/fonts/ 目录(相对于当前文件)
for _, fontName := range fontNames {
fontPaths = append(fontPaths, filepath.Join(fm.baseDir, "fonts", fontName))
}
// 方式3: 尝试相对于工作目录的路径
if workDir, err := os.Getwd(); err == nil {
for _, fontName := range fontNames {
paths := []string{
filepath.Join(workDir, "internal", "shared", "pdf", "fonts", fontName),
filepath.Join(workDir, "tyapi-server", "internal", "shared", "pdf", "fonts", fontName),
}
fontPaths = append(fontPaths, paths...)
}
}
// 方式4: 尝试使用可执行文件所在目录
if execPath, err := os.Executable(); err == nil {
execDir := filepath.Dir(execPath)
if realPath, err := filepath.EvalSymlinks(execPath); err == nil {
execDir = filepath.Dir(realPath)
}
for _, fontName := range fontNames {
paths := []string{
filepath.Join(execDir, "internal", "shared", "pdf", "fonts", fontName),
filepath.Join(filepath.Dir(execDir), "internal", "shared", "pdf", "fonts", fontName),
}
fontPaths = append(fontPaths, paths...)
}
}
// 方式5: 尝试使用环境变量指定的路径
if fontDir := os.Getenv("PDF_FONT_DIR"); fontDir != "" {
for _, fontName := range fontNames {
fontPaths = append(fontPaths, filepath.Join(fontDir, fontName))
}
}
// 方式6: 相对路径(作为最后的后备方案)
for _, fontName := range fontNames {
relativePaths := []string{
"internal/shared/pdf/fonts/" + fontName,
"./internal/shared/pdf/fonts/" + fontName,
}
fontPaths = append(fontPaths, relativePaths...)
}
// 过滤出实际存在的字体文件
var existingFonts []string
for _, fontPath := range fontPaths {
fileInfo, err := os.Stat(fontPath)
if err == nil && fileInfo.Mode().IsRegular() {
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
}