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} 应用字体失败");
}
}