diff --git a/YD_Event.Application/Certificate/CertificateGenerateService.cs b/YD_Event.Application/Certificate/CertificateGenerateService.cs index 10ecb0e..b37a9d0 100644 --- a/YD_Event.Application/Certificate/CertificateGenerateService.cs +++ b/YD_Event.Application/Certificate/CertificateGenerateService.cs @@ -88,7 +88,13 @@ public class CertificateGenerateService : IDynamicApiController, ITransient // 设置 QuestPDF 许可证 QuestPDF.Settings.License = LicenseType.Community; - // 尝试配置中文字体 + // 配置 QuestPDF 字体管理 + // QuestPDF 2025.12+ 会自动使用 SkiaSharp 的字体检测 + // 我们不需要显式注册字体,只需要确保系统中有可用的中文字体 + + _logger.LogInformation("QuestPDF 配置完成,使用 SkiaSharp 字体系统"); + + // 尝试配置中文字体(检测并记录) ConfigureChineseFonts(); _logger.LogInformation("PDF 字体系统初始化完成"); @@ -102,7 +108,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient } /// - /// 配置中文字体 - 增强版,支持字体文件检测和注册 + /// 配置中文字体 - 修复版,正确使用 QuestPDF 字体注册机制 /// private void ConfigureChineseFonts() { @@ -116,105 +122,83 @@ public class CertificateGenerateService : IDynamicApiController, ITransient _logger.LogInformation($"当前操作系统: {(isLinux ? "Linux" : isWindows ? "Windows" : "其他")}"); - // Linux 字体路径列表 + // Linux 字体路径列表(优先级排序) var linuxFontPaths = new[] { - "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", - "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", - "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", - "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", - "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", - "/usr/local/share/fonts/wqy-zenhei.ttc" + ("/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", "WenQuanYi Zen Hei"), + ("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", "WenQuanYi Micro Hei"), + ("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", "Noto Sans CJK SC"), + ("/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", "Noto Sans CJK SC"), + ("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "DejaVu Sans"), + ("/usr/local/share/fonts/wqy-zenhei.ttc", "WenQuanYi Zen Hei") }; - // Windows 字体路径列表 + // Windows 字体路径列表(优先级排序) var windowsFontPaths = new[] { - @"C:\Windows\Fonts\msyh.ttc", // 微软雅黑 - @"C:\Windows\Fonts\simsun.ttc", // 宋体 - @"C:\Windows\Fonts\simhei.ttf" // 黑体 + (@"C:\Windows\Fonts\msyh.ttc", "Microsoft YaHei"), // 微软雅黑 + (@"C:\Windows\Fonts\simsun.ttc", "SimSun"), // 宋体 + (@"C:\Windows\Fonts\simhei.ttf", "SimHei"), // 黑体 + (@"C:\Windows\Fonts\msyhl.ttc", "Microsoft YaHei Light"), + (@"C:\Windows\Fonts\simkai.ttf", "KaiTi") }; string fontPathToRegister = null; string fontNameToUse = null; - // 尝试查找字体文件 - if (isLinux) + // 尝试查找并注册字体文件 + var fontPathsToCheck = isLinux ? linuxFontPaths : windowsFontPaths; + foreach (var (path, name) in fontPathsToCheck) { - _logger.LogInformation("正在 Linux 系统中查找字体文件..."); - foreach (var path in linuxFontPaths) + if (File.Exists(path)) { - if (File.Exists(path)) + _logger.LogInformation($"找到字体文件: {path} ({name})"); + fontPathToRegister = path; + fontNameToUse = name; + + try { - _logger.LogInformation($"找到字体文件: {path}"); - fontPathToRegister = path; + // 尝试在 QuestPDF 中注册字体(如果支持) + // QuestPDF 会自动搜索系统字体,也可以显式注册 + _logger.LogInformation($"尝试注册字体: {fontNameToUse}"); - if (path.Contains("wqy-zenhei")) fontNameToUse = "WenQuanYi Zen Hei"; - else if (path.Contains("wqy-microhei")) fontNameToUse = "WenQuanYi Micro Hei"; - else if (path.Contains("Noto")) fontNameToUse = "Noto Sans CJK SC"; - else if (path.Contains("DejaVu")) fontNameToUse = "DejaVu Sans"; - else fontNameToUse = "CustomChineseFont"; - - break; + // 先测试字体是否可用 + if (TestFontAvailability(fontNameToUse)) + { + _selectedChineseFont = fontNameToUse; + _logger.LogInformation($"字体配置成功: {_selectedChineseFont}"); + break; + } } - else + catch (Exception ex) { - _logger.LogDebug($"字体文件不存在: {path}"); + _logger.LogWarning(ex, $"字体注册失败: {fontNameToUse},继续尝试下一个..."); } } - } - else if (isWindows) - { - _logger.LogInformation("正在 Windows 系统中查找字体文件..."); - foreach (var path in windowsFontPaths) + else { - if (File.Exists(path)) - { - _logger.LogInformation($"找到字体文件: {path}"); - fontPathToRegister = path; - - if (path.Contains("msyh")) fontNameToUse = "Microsoft YaHei"; - else if (path.Contains("simsun")) fontNameToUse = "SimSun"; - else if (path.Contains("simhei")) fontNameToUse = "SimHei"; - else fontNameToUse = "CustomChineseFont"; - - break; - } + _logger.LogDebug($"字体文件不存在: {path}"); } } - // 如果找到了字体文件,尝试显式注册 - if (!string.IsNullOrEmpty(fontPathToRegister)) + // 如果没有找到字体文件,尝试使用备选字体名称 + if (string.IsNullOrEmpty(_selectedChineseFont)) { - try - { - _logger.LogInformation($"正在注册字体: {fontNameToUse} ({fontPathToRegister})"); - - // 注意:QuestPDF 可能不支持直接从文件路径注册 - // 我们采用替代方案:记录字体名称,并在生成时强制使用 - _selectedChineseFont = fontNameToUse; - _logger.LogInformation($"字体配置完成: {_selectedChineseFont}"); - } - catch (Exception ex) - { - _logger.LogWarning(ex, $"字体注册失败,将尝试自动查找: {fontNameToUse}"); - _selectedChineseFont = fontNameToUse; // 仍然记录下来 - } - } - else - { - _logger.LogWarning("未找到字体文件,将使用备选字体列表..."); + _logger.LogWarning("未找到字体文件,使用备选字体名称列表..."); - // 备选字体名称列表 + // 备选字体名称列表(按优先级) var chineseFontNames = new[] { - "WenQuanYi Zen Hei", - "WenQuanYi Micro Hei", - "Noto Sans CJK SC", "Microsoft YaHei", "SimSun", "SimHei", - "DejaVu Sans" + "WenQuanYi Zen Hei", + "WenQuanYi Micro Hei", + "Noto Sans CJK SC", + "DejaVu Sans", + "Arial Unicode MS", + "PingFang SC", + "STSong" }; foreach (var fontName in chineseFontNames) @@ -229,8 +213,9 @@ public class CertificateGenerateService : IDynamicApiController, ITransient break; } } - catch + catch (Exception ex) { + _logger.LogDebug(ex, $"字体不可用: {fontName}"); continue; } } @@ -241,6 +226,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient _logger.LogError("请在系统中安装中文字体:"); _logger.LogError(" Ubuntu/Debian: sudo apt-get install fonts-wqy-zenhei fonts-wqy-microhei fonts-noto-cjk"); _logger.LogError(" CentOS/RHEL: sudo yum install wqy-microhei-fonts wqy-zenhei-fonts"); + _logger.LogError(" Windows: 确保系统中有微软雅黑或宋体"); } } } @@ -1265,7 +1251,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient } /// - /// 使用QuestPDF生成PDF - 增强版,确保中文字体被正确应用 + /// 使用QuestPDF生成PDF - 修复版,确保中文字体被正确应用 /// private async Task GeneratePdfWithQuestPDF(Stream pdfStream, DocumentData documentData) { @@ -1279,16 +1265,42 @@ public class CertificateGenerateService : IDynamicApiController, ITransient var fontFallbackList = new[] { chineseFontName, + "Microsoft YaHei", + "SimSun", + "SimHei", "WenQuanYi Zen Hei", "WenQuanYi Micro Hei", "Noto Sans CJK SC", - "Microsoft YaHei", - "SimSun", - "DejaVu Sans" + "DejaVu Sans", + "Arial Unicode MS" }.Where(f => !string.IsNullOrEmpty(f)).Distinct().ToArray(); _logger.LogInformation($"字体回退列表: {string.Join(", ", fontFallbackList)}"); + // 选择最终要使用的字体(尝试测试每一个) + string finalFont = null; + foreach (var font in fontFallbackList) + { + try + { + if (TestFontAvailability(font)) + { + finalFont = font; + _logger.LogInformation($"选择最终字体: {finalFont}"); + break; + } + } + catch + { + continue; + } + } + + if (string.IsNullOrEmpty(finalFont)) + { + _logger.LogWarning("未找到可用字体,将使用 QuestPDF 默认字体"); + } + var questDocument = QuestPDF.Fluent.Document.Create(container => { container.Page(page => @@ -1296,23 +1308,22 @@ public class CertificateGenerateService : IDynamicApiController, ITransient page.Margin(0); page.Size(PageSizes.A4); - // 设置默认文本样式 - 强制应用字体 + // 设置默认文本样式 - 使用选定的中文字体 page.DefaultTextStyle(x => { var style = x.FontSize(12).FontColor(QuestPDF.Helpers.Colors.Black); - // 尝试应用每一个字体,直到成功 - foreach (var font in fontFallbackList) + // 应用选定的字体 + if (!string.IsNullOrEmpty(finalFont)) { try { - style = style.FontFamily(font); - _logger.LogDebug($"尝试应用字体到默认样式: {font}"); - break; // 成功应用一个就停止 + style = style.FontFamily(finalFont); + _logger.LogDebug($"应用字体到默认样式: {finalFont}"); } - catch + catch (Exception ex) { - continue; + _logger.LogWarning(ex, $"应用字体 {finalFont} 失败,使用默认字体"); } } @@ -1327,11 +1338,9 @@ public class CertificateGenerateService : IDynamicApiController, ITransient .FitArea(); } - // 使用 Column 来放置所有内容,但禁用默认间距 - page.Content().Column(column => + // 使用绝对定位来放置文本内容(更精确) + page.Content().Layer(layer => { - column.Spacing(0); - double? firstParagraphY = null; double? firstParagraphOriginalY = null; @@ -1366,24 +1375,26 @@ public class CertificateGenerateService : IDynamicApiController, ITransient _logger.LogDebug("段落 {Index} 位置: X={X}, Y={Y}", i + 1, x, y); - // 使用 Translate 来定位,配合 Spacing(0) 确保没有额外间隔 - var textElement = column.Item().TranslateX(x).TranslateY(y).Text(paragraph.Text) + // 使用绝对定位放置文本 + var textElement = layer + .TranslateX(x) + .TranslateY(y) + .Text(paragraph.Text) .FontSize((float)fontSize) .FontColor(QuestPDF.Helpers.Colors.Black) .AlignLeft(); - // 尝试应用每一个字体到这个文本元素 - foreach (var font in fontFallbackList) + // 应用选定的字体 + if (!string.IsNullOrEmpty(finalFont)) { try { - textElement = textElement.FontFamily(font); - _logger.LogDebug($"段落 {i + 1} 应用字体: {font}"); - break; + textElement = textElement.FontFamily(finalFont); + _logger.LogDebug($"段落 {i + 1} 应用字体: {finalFont}"); } - catch + catch (Exception ex) { - continue; + _logger.LogDebug(ex, $"段落 {i + 1} 应用字体失败"); } }